From 5189adf4f1867bc7f11b3676929eb186ff89ee8f Mon Sep 17 00:00:00 2001 From: allegroai <> Date: Mon, 18 Mar 2024 15:49:42 +0200 Subject: [PATCH] Improve handling of fixed users --- apiserver/config/default/apiserver.conf | 3 +++ apiserver/database/model/auth.py | 4 ++++ apiserver/mongo/initialize/__init__.py | 24 ++++++++++++++++++++---- apiserver/mongo/initialize/user.py | 9 +++++++-- apiserver/services/users.py | 12 ++++++++++-- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/apiserver/config/default/apiserver.conf b/apiserver/config/default/apiserver.conf index a5178f0..6ef0c2b 100644 --- a/apiserver/config/default/apiserver.conf +++ b/apiserver/config/default/apiserver.conf @@ -58,6 +58,9 @@ # verify user tokens verify_user_tokens: false + # If set then users that were created from secure credentials or fixed user settings and are no longer in these settings will be deleted on startup + delete_missing_autocreated_users: true + # max token expiration timeout in seconds (1 year) max_expiration_sec: 31536000 diff --git a/apiserver/database/model/auth.py b/apiserver/database/model/auth.py index cb35aee..f75b661 100644 --- a/apiserver/database/model/auth.py +++ b/apiserver/database/model/auth.py @@ -4,6 +4,7 @@ from mongoengine import ( EmbeddedDocumentListField, EmailField, DateTimeField, + BooleanField, ) from apiserver.database import Database, strict @@ -76,3 +77,6 @@ class User(DbModelMixin, AuthDocument): email = EmailField(unique=True, sparse=True) """ Email uniquely identifying the user """ + + autocreated = BooleanField(default=False) + """ Set to true if the user was auto created based on config settings""" diff --git a/apiserver/mongo/initialize/__init__.py b/apiserver/mongo/initialize/__init__.py index 2930638..94fc24c 100644 --- a/apiserver/mongo/initialize/__init__.py +++ b/apiserver/mongo/initialize/__init__.py @@ -3,7 +3,7 @@ from typing import Sequence, Union from apiserver.config_repo import config from apiserver.config.info import get_default_company -from apiserver.database.model.auth import Role +from apiserver.database.model.auth import Role, User as AuthUser from apiserver.service_repo.auth.fixed_user import FixedUser from .migration import _apply_migrations, check_mongo_empty, get_last_server_version from .pre_populate import PrePopulate @@ -60,14 +60,18 @@ def init_mongo_data(): fixed_mode = FixedUser.enabled() + internal_user_emails = set() for user, credentials in config.get("secure.credentials", {}).items(): + email = f"{user}@example.com" user_data = { "name": user, "role": credentials.role, - "email": f"{user}@example.com", + "email": email, "key": credentials.user_key, "secret": credentials.user_secret, + "autocreated": True, } + internal_user_emails.add(email.lower()) revoke = fixed_mode and credentials.get("revoke_in_fixed_mode", False) user_id = _ensure_auth_user(user_data, company_id, log=log, revoke=revoke) if credentials.role == Role.user: @@ -82,8 +86,20 @@ def init_mongo_data(): for user in FixedUser.from_config(): try: - ensure_fixed_user(user, log=log) + ensure_fixed_user(user, log=log, emails=internal_user_emails) except Exception as ex: log.error(f"Failed creating fixed user {user.name}: {ex}") + + if internal_user_emails and config.get( + f"apiserver.auth.delete_missing_autocreated_users", True + ): + for user in AuthUser.objects( + company=company_id, autocreated=True, email__nin=internal_user_emails + ): + log.info( + f"Removing user that is no longer in configuration: {user['id']}\t{user['email']}\t{user['name']}" + ) + user.delete() + except Exception as ex: - log.exception("Failed initializing mongodb") + log.exception(f"Failed initializing mongodb: {str(ex)}") diff --git a/apiserver/mongo/initialize/user.py b/apiserver/mongo/initialize/user.py index 6682fe0..d307d01 100644 --- a/apiserver/mongo/initialize/user.py +++ b/apiserver/mongo/initialize/user.py @@ -26,6 +26,7 @@ def _ensure_auth_user(user_data: dict, company_id: str, log: Logger, revoke: boo credentials = [] if revoke else [creds] user_id = user_data.get("id", f"__{user_data['name']}__") + autocreated = user_data.get("autocreated", False) log.info(f"Creating user: {user_data['name']}") @@ -37,6 +38,7 @@ def _ensure_auth_user(user_data: dict, company_id: str, log: Logger, revoke: boo email=user_data["email"], created=datetime.utcnow(), credentials=credentials, + autocreated=autocreated, ) user.save() @@ -59,7 +61,7 @@ def _ensure_backend_user(user_id: str, company_id: str, user_name: str): return user_id -def ensure_fixed_user(user: FixedUser, log: Logger): +def ensure_fixed_user(user: FixedUser, log: Logger, emails: set): db_user = User.objects(company=user.company, id=user.user_id).first() if db_user: # noinspection PyBroadException @@ -73,9 +75,12 @@ def ensure_fixed_user(user: FixedUser, log: Logger): data = attr.asdict(user) data["id"] = user.user_id - data["email"] = f"{user.user_id}@example.com" + email = f"{user.user_id}@example.com" + data["email"] = email data["role"] = Role.guest if user.is_guest else Role.user + data["autocreated"] = True _ensure_auth_user(user_data=data, company_id=user.company, log=log) + emails.add(email) return _ensure_backend_user(user.user_id, user.company, user.name) diff --git a/apiserver/services/users.py b/apiserver/services/users.py index 699a4c0..5a60066 100644 --- a/apiserver/services/users.py +++ b/apiserver/services/users.py @@ -16,7 +16,7 @@ from apiserver.bll.project import ProjectBLL from apiserver.bll.user import UserBLL from apiserver.config_repo import config from apiserver.database.errors import translate_errors_context -from apiserver.database.model.auth import Role +from apiserver.database.model.auth import Role, User as AuthUser from apiserver.database.model.company import Company from apiserver.database.model.user import User from apiserver.database.utils import parse_from_call @@ -158,9 +158,17 @@ def update_user(user_id, company_id, data: dict) -> Tuple[int, dict]: update_fields = { k: v for k, v in create_fields.items() if k in User.user_set_allowed() } + auth_user_update_fields = ("name",) partial_update_dict = parse_from_call(data, update_fields, User.get_fields()) with translate_errors_context("updating user"): - return User.safe_update(company_id, user_id, partial_update_dict) + ret = User.safe_update(company_id, user_id, partial_update_dict) + auth_update = { + k: v for k, v in partial_update_dict.items() if k in auth_user_update_fields + } + if auth_update: + AuthUser.objects(id=user_id).update(**auth_update) + + return ret @endpoint("users.update", response_data_model=UpdateResponse)