"""Logic for sending Auth0 invitations.""" from flask import g from permissions.constants.application import ROLE_IDS, ROLE_TO_AUTH0_APPLICATION_MAPPING from permissions.models import ( identity as identity_model, owsusers, tenant, vend_contact as vend_contact_model, ) from permissions.types import AdminIdentity, Identity, TenantRolesInput, TenantType def send_auth0_invite( admin_identity: AdminIdentity, assignee_identity: Identity, user_auth0_id: str | None, tenant_roles_input: TenantRolesInput, brand: str, vend_contact: vend_contact_model.VendContact | None, ) -> None: email = assignee_identity.email user_types = identity_model.user_types_from_tenant_type(tenant_roles_input.tenant.tenant_type) auth0_application_name = get_invite_application_name(tenant_roles_input.roles_to_attach) if vend_contact: vend_contact_id = vend_contact.id else: vend_contact_id = None invite_tracking = owsusers.create_invite_tracking_string( identity_id=assignee_identity.id, email=email, brand=brand, auth0_id=user_auth0_id, ) # extra context data we need to give to the user in the auth0 invitation email tenant_node = tenant.get_tenant_by_uuid_and_type( tenant_uuid=tenant_roles_input.tenant.tenant_uuid, tenant_type=tenant_roles_input.tenant.tenant_type, ) invitation_data = { 'tenant_name': tenant_node.tenant_name if tenant_node else None, } try: # If the invitee is of type ACCOUNT or higher, we don't lookup an inviter name # so the email handles the "{brandName} is inviting you" scenario if tenant_node.tenant_type not in [TenantType.ACCOUNT, TenantType.PARENT_COMPANY]: parent_tenant_node = tenant.get_tenant_parent_of_tenant(tenant_node) if parent_tenant_node: invitation_data['inviter_tenant_name'] = parent_tenant_node.tenant_name except Exception as e: # Log error but don't fail the invitation process g.log.warn('Failed to get inviter tenant name', resources={'error': str(e)}) g.log.info('Sending org invite', resources={'email': email, 'brand': brand}) owsusers.create_auth0_org_invitation( email=email, brand=brand, admin=admin_identity, auth0_application_name=auth0_application_name, user_metadata=owsusers.UserMetadata( orchardIdentityId=assignee_identity.id, user_types=user_types, username=assignee_identity.name, vend_contact_id=vend_contact_id, # OSP-1527: `type` is deprecated, only set when vend_contact_id is present type='alw' if vend_contact_id else None, inviteTracking=str(invite_tracking), auth0_user_id=user_auth0_id, invitation_data=invitation_data, ), ) g.log.info('Successfully sent org invite', resources={'email': email, 'brand': brand}) def get_invite_application_name(roles: list[str]) -> str: """Get the auth0 application the invite link should take the user to given their roles. This function can get messy very easily, so for future collaborations: Please keep conditions as simple to read/understand as possible even if that creates a bit of extra lines of code Please also add specific fixtures for the test with each new condition """ APP_MAP = ROLE_TO_AUTH0_APPLICATION_MAPPING # just a shorter name # Defaults to Settings for most cases default_app_name = APP_MAP[ROLE_IDS.SETTINGS_BASE_ROLE] # user has only one role, thats its landing app, if len(roles) == 1: # if missing map entry, also default to settings return APP_MAP.get(roles[0], default_app_name) if len(roles) == 2: # banking + accounting only combination: invite to banking if all( role in roles for role in [ROLE_IDS.BANKING_TAX_BASE_ROLE, ROLE_IDS.CUSTOMER_ACCOUNTING_BASE_ROLE] ): return APP_MAP[ROLE_IDS.BANKING_TAX_BASE_ROLE] # banking + collaborators only combination: invite to banking if all( role in roles for role in [ROLE_IDS.BANKING_TAX_BASE_ROLE, ROLE_IDS.COLLABORATORS_BASE_ROLE] ): return APP_MAP[ROLE_IDS.BANKING_TAX_BASE_ROLE] # accounting + collaborators only combination: invite to accounting if all( role in roles for role in [ROLE_IDS.CUSTOMER_ACCOUNTING_BASE_ROLE, ROLE_IDS.COLLABORATORS_BASE_ROLE] ): return APP_MAP[ROLE_IDS.CUSTOMER_ACCOUNTING_BASE_ROLE] if len(roles) == 3: # banking + accounting + collaborators only combination: invite to banking if all( role in roles for role in [ ROLE_IDS.BANKING_TAX_BASE_ROLE, ROLE_IDS.CUSTOMER_ACCOUNTING_BASE_ROLE, ROLE_IDS.COLLABORATORS_BASE_ROLE, ] ): return APP_MAP[ROLE_IDS.BANKING_TAX_BASE_ROLE] return default_app_name