import time from typing import Optional, Iterable from sqlalchemy import not_ from atlas_um.consts import AccountStateSource from atlas_um.extensions import auth0, usm from atlas_um.helpers.either import Either, Right from atlas_um.helpers.services import BaseLogicService from atlas_um.logs import logger from atlas_um.pgdb import ( Auth0Account, Auth0AccountExternalState, Auth0Role, Auth0AccountRole, ) from atlas_um.pgdb import pgdb class SyncAuth0Account(BaseLogicService): @classmethod def sync_all(cls): existing_users_ids = [] for user_id in auth0.list_users_ids(): existing_users_ids.append(user_id) cls.execute(user_id=user_id) time.sleep(0.1) cls.cleanup_non_existing(existing_users_ids) @staticmethod def cleanup_non_existing(existing_users_ids: Iterable): Auth0AccountExternalState.query.filter( Auth0AccountExternalState.auth0_account_id.in_( pgdb.session.query(Auth0Account.id).filter( not_(Auth0Account.user_id.in_(existing_users_ids)) ) ) ).delete(synchronize_session=False) Auth0AccountRole.query.filter( Auth0AccountRole.auth0_account_id.in_( pgdb.session.query(Auth0Account.id).filter( not_(Auth0Account.user_id.in_(existing_users_ids)) ) ) ).delete(synchronize_session=False) Auth0Account.query.filter( not_(Auth0Account.user_id.in_(existing_users_ids)) ).delete(synchronize_session=False) pgdb.session.commit() def process(self, user_id: str) -> Optional[Either]: user = auth0.get_user(user_id) roles = auth0.get_user_roles(user_id) auth0_account = Auth0Account.query.filter( Auth0Account.user_id == user.user_id ).first() if not auth0_account: auth0_account = Auth0Account() self.session.add(auth0_account) for k, v in user.dict().items(): setattr(auth0_account, k, v) auth0_account.roles = [self._get_auth0_role(role) for role in roles] logger.bind(auth0_account=auth0_account).info("Syncing Auth0 account") return Right(auth0_account) def _get_auth0_role(self, role): auth0_role = Auth0Role.query.filter(Auth0Role.id == role.id).first() if not auth0_role: auth0_role = Auth0Role() auth0_role.id = role.id auth0_role.name = role.name auth0_role.description = role.description or "" return auth0_role class SyncAuth0AccountExternalStates(BaseLogicService): @classmethod def sync_all(cls): for auth0_account in Auth0Account.query.all(): cls.execute(auth0_account=auth0_account) time.sleep(0.1) def process(self, auth0_account: Auth0Account) -> Optional[Either]: states = [] usm_state = self._sync_with_usm(auth0_account) if usm_state: states.append(usm_state) return Right(states) def _sync_with_usm(self, auth0_account: Auth0Account): """ If the account is reachable via the USM API, we are assuming that it is active. If we have some previous state but account is unreachable, we are assuming that it is disabled. If we do not have any state, but the account is on one of the internal domains , we are assuming that the account is disabled. In all other cases the state in unknown, so we are not creating any state object. """ state = Auth0AccountExternalState.query.filter( Auth0AccountExternalState.auth0_account_id == auth0_account.id, Auth0AccountExternalState.source == AccountStateSource.USM.value, ).first() result = usm.get_user(auth0_account.email) if result.is_left: result = usm.lookup_user(auth0_account.email) if result.is_left: if state and state.is_active: state.is_active = False state.last_login = auth0_account.last_login elif not state and not auth0_account.is_external: state = Auth0AccountExternalState() self.session.add(state) state.auth0_account = auth0_account state.is_active = False state.source = AccountStateSource.USM.value state.last_login = auth0_account.last_login else: if state: state.is_active = True state.last_login = auth0_account.last_login else: state = Auth0AccountExternalState() self.session.add(state) state.auth0_account = auth0_account state.is_active = True state.source = AccountStateSource.USM.value state.last_login = auth0_account.last_login logger.bind( user_id=auth0_account.user_id, email=auth0_account.email, is_active=state.is_active if state else None, ).info("Updating external state value from USM for auth0 account") return state