def test_delete_user_is_member_returns_204(client, jwt, session, keycloak_mock): # pylint:disable=unused-argument """Test if the user is the member of a team assert status is 204.""" user_model = factory_user_model(user_info=TestUserInfo.user_test) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model contact_link.commit() org = OrgService.create_org(TestOrgInfo.org1, user_id=user_model.id) org_dictionary = org.as_dict() org_id = org_dictionary['id'] entity = factory_entity_model(entity_info=TestEntityInfo.entity_lear_mock) affiliation = AffiliationModel(org_id=org_id, entity_id=entity.id) affiliation.save() user_model2 = factory_user_model(user_info=TestUserInfo.user2) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model2 contact_link.commit() membership = MembershipModel(org_id=org_id, user_id=user_model2.id, membership_type_code='MEMBER', membership_type_status=Status.ACTIVE.value) membership.save() claims = copy.deepcopy(TestJwtClaims.public_user_role.value) claims['sub'] = str(user_model2.keycloak_guid) headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.delete('/api/v1/users/@me', headers=headers, content_type='application/json') assert rv.status_code == http_status.HTTP_204_NO_CONTENT
def factory_membersip_model(session): """Produce a templated org model.""" user = factory_user_model() org_type = OrgTypeModel(code='TEST', description='Test') session.add(org_type) session.commit() org_status = OrgStatusModel(code='TEST', description='Test') session.add(org_status) session.commit() preferred_payment = PaymentTypeModel(code='TEST', description='Test') session.add(preferred_payment) session.commit() org = OrgModel(name='Test Org') org.org_type = org_type org.org_status = OrgStatusModel.get_default_status() org.preferred_payment = preferred_payment org.save() membership = MembershipModel(org_id=org.id, user_id=user.id, membership_type_code=ADMIN, status=1) membership.save() return membership
def _add_org_membership(org_id, user_id, membership_type): membership = MembershipModel( org_id=org_id, user_id=user_id, membership_type_code=membership_type, membership_type_status=Status.ACTIVE.value) membership.save()
def accept_invitation(invitation_id, user: UserService, origin, add_membership: bool = True, token_info: Dict = None): """Add user, role and org from the invitation to membership.""" current_app.logger.debug('>accept_invitation') invitation: InvitationModel = InvitationModel.find_invitation_by_id(invitation_id) if invitation is None: raise BusinessException(Error.DATA_NOT_FOUND, None) if invitation.invitation_status_code == 'ACCEPTED': raise BusinessException(Error.ACTIONED_INVITATION, None) if invitation.invitation_status_code == 'EXPIRED': raise BusinessException(Error.EXPIRED_INVITATION, None) if getattr(token_info, 'loginSource', None) is not None: # bcros comes with out token login_source = token_info.get('loginSource', None) if invitation.login_source != login_source: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) if add_membership: for membership in invitation.membership: membership_model = MembershipModel() membership_model.org_id = membership.org_id membership_model.user_id = user.identifier membership_model.membership_type = membership.membership_type # check to ensure an invitation for this user/org has not already been processed existing_membership = MembershipService \ .get_membership_for_org_and_user(org_id=membership_model.org_id, user_id=membership_model.user_id) if existing_membership: raise BusinessException(Error.DATA_ALREADY_EXISTS, None) org_model: OrgModel = OrgModel.find_by_org_id(membership.org_id) # GOVM users gets direct approval since they are IDIR users. membership_model.status = Invitation._get_status_based_on_org(org_model) membership_model.save() try: Invitation.notify_admin(user, invitation_id, membership_model.id, origin) except BusinessException as exception: current_app.logger.error('<send_notification_to_admin failed', exception.message) invitation.accepted_date = datetime.now() invitation.invitation_status = InvitationStatusModel.get_status_by_code('ACCEPTED') invitation.save() # Call keycloak to add the user to the group. if user: group_name: str = KeycloakService.join_users_group(token_info) KeycloakService.join_account_holders_group(user.keycloak_guid) if group_name == GROUP_GOV_ACCOUNT_USERS: # TODO Remove this if gov account users needs Terms of Use. tos_document = DocumentsModel.fetch_latest_document_by_type(DocumentType.TERMS_OF_USE.value) user.update_terms_of_use(token_info, True, tos_document.version_id) # Add contact to the user. user.add_contact(token_info, dict(email=token_info.get('email', None))) current_app.logger.debug('<accept_invitation') return Invitation(invitation)
def create_membership(access_type, is_staff_admin, org, user_id): """Create membership account.""" if not is_staff_admin and access_type != AccessType.ANONYMOUS.value: membership = MembershipModel(org_id=org.id, user_id=user_id, membership_type_code='ADMIN', membership_type_status=Status.ACTIVE.value) membership.add_to_session() # Add the user to account_holders group KeycloakService.join_account_holders_group()
def accept_invitation(invitation_id, user, origin, add_membership: bool = True): """Add user, role and org from the invitation to membership.""" current_app.logger.debug('>accept_invitation') invitation: InvitationModel = InvitationModel.find_invitation_by_id( invitation_id) if invitation is None: raise BusinessException(Error.DATA_NOT_FOUND, None) if invitation.invitation_status_code == 'ACCEPTED': raise BusinessException(Error.ACTIONED_INVITATION, None) if invitation.invitation_status_code == 'EXPIRED': raise BusinessException(Error.EXPIRED_INVITATION, None) if add_membership: for membership in invitation.membership: membership_model = MembershipModel() membership_model.org_id = membership.org_id membership_model.user_id = user.identifier membership_model.membership_type = membership.membership_type # check to ensure an invitation for this user/org has not already been processed existing_membership = MembershipService \ .get_membership_for_org_and_user(org_id=membership_model.org_id, user_id=membership_model.user_id) if existing_membership: raise BusinessException(Error.DATA_ALREADY_EXISTS, None) # user needs to get approval is_auto_approval = OrgSettingsModel.is_admin_auto_approved_invitees( membership.org_id) if is_auto_approval: membership_model.status = Status.ACTIVE.value else: membership_model.status = Status.PENDING_APPROVAL.value membership_model.save() if not is_auto_approval: try: Invitation.notify_admin(user, invitation_id, membership_model.id, origin) except BusinessException as exception: current_app.logger.error( '<send_notification_to_admin failed', exception.message) invitation.accepted_date = datetime.now() invitation.invitation_status = InvitationStatusModel.get_status_by_code( 'ACCEPTED') invitation.save() current_app.logger.debug('<accept_invitation') return Invitation(invitation)
def accept_invitation(invitation_id, user, origin, add_membership: bool = True, token_info: Dict = None): """Add user, role and org from the invitation to membership.""" current_app.logger.debug('>accept_invitation') invitation: InvitationModel = InvitationModel.find_invitation_by_id( invitation_id) if invitation is None: raise BusinessException(Error.DATA_NOT_FOUND, None) if invitation.invitation_status_code == 'ACCEPTED': raise BusinessException(Error.ACTIONED_INVITATION, None) if invitation.invitation_status_code == 'EXPIRED': raise BusinessException(Error.EXPIRED_INVITATION, None) if getattr(token_info, 'loginSource', None) is not None: # bcros comes with out token login_source = token_info.get('loginSource', None) if invitation.login_source != login_source: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) if add_membership: for membership in invitation.membership: membership_model = MembershipModel() membership_model.org_id = membership.org_id membership_model.user_id = user.identifier membership_model.membership_type = membership.membership_type # check to ensure an invitation for this user/org has not already been processed existing_membership = MembershipService \ .get_membership_for_org_and_user(org_id=membership_model.org_id, user_id=membership_model.user_id) if existing_membership: raise BusinessException(Error.DATA_ALREADY_EXISTS, None) membership_model.status = Status.PENDING_APPROVAL.value membership_model.save() try: Invitation.notify_admin(user, invitation_id, membership_model.id, origin) except BusinessException as exception: current_app.logger.error( '<send_notification_to_admin failed', exception.message) invitation.accepted_date = datetime.now() invitation.invitation_status = InvitationStatusModel.get_status_by_code( 'ACCEPTED') invitation.save() current_app.logger.debug('<accept_invitation') return Invitation(invitation)
def test_get_count_active_owner_org_id_multiple(session): # pylint:disable=unused-argument """Assert that an Org can be updated from a dictionary.""" membership1 = factory_membersip_model(session) user2 = factory_user_model(TestUserInfo.user2) membership2 = MembershipModel(org_id=membership1.org_id, user_id=user2.id, membership_type_code=ADMIN, status=1) membership2.save() assert MembershipModel.get_count_active_owner_org_id( membership2.org_id) == 2
def _create_new_user_and_membership(db_username, kc_user, membership, org_id): user_model: UserModel = UserModel(username=db_username, is_terms_of_use_accepted=False, status=Status.ACTIVE.value, type=Role.ANONYMOUS_USER.name, email=membership.get('email', None), firstname=kc_user.first_name, lastname=kc_user.last_name) user_model.flush() membership_model = MembershipModel(org_id=org_id, user_id=user_model.id, membership_type_code=membership['membershipType'], membership_type_status=Status.ACTIVE.value) membership_model.flush() return user_model
def create_org(org_info: dict, user_id): """Create a new organization.""" current_app.logger.debug('<create_org ') existing_similar__org = OrgModel.find_similar_org_by_name(org_info['name']) if existing_similar__org is not None: raise BusinessException(Error.DATA_CONFLICT, None) org = OrgModel.create_from_dict(camelback2snake(org_info)) org.save() current_app.logger.info(f'<created_org org_id:{org.id}') # create the membership record for this user membership = MembershipModel(org_id=org.id, user_id=user_id, membership_type_code='OWNER', membership_type_status=Status.ACTIVE.value) membership.save() return Org(org)
def test_delete_user_where_org_has_another_owner(session, auth_mock, keycloak_mock, monkeypatch): # pylint:disable=unused-argument """Assert that a user can be deleted.""" # Create a user and org user_model = factory_user_model(user_info=TestUserInfo.user_test) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model contact_link.commit() patch_token_info(TestJwtClaims.get_test_user(user_model.keycloak_guid), monkeypatch) org = OrgService.create_org(TestOrgInfo.org1, user_id=user_model.id) org_dictionary = org.as_dict() org_id = org_dictionary['id'] entity = factory_entity_model(entity_info=TestEntityInfo.entity_lear_mock) affiliation = AffiliationModel(org_id=org_id, entity_id=entity.id) affiliation.save() # Create another user and add membership to the above org user_model2 = factory_user_model(user_info=TestUserInfo.user2) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model2 contact_link.commit() membership = MembershipModel(org_id=org_id, user_id=user_model2.id, membership_type_code='ADMIN', membership_type_status=Status.ACTIVE.value) membership.save() membership.commit() # with pytest.raises(BusinessException) as exception: patch_token_info(TestJwtClaims.get_test_user(user_model2.keycloak_guid), monkeypatch) UserService.delete_user() updated_user = UserModel.find_by_jwt_token() assert len(updated_user.contacts) == 0 user_orgs = MembershipModel.find_orgs_for_user(updated_user.id) for org in user_orgs: assert org.status_code == 'INACTIVE'
def _create_new_user_and_membership(db_username, kc_user, membership, org_id): user_model: UserModel = UserModel(username=db_username, is_terms_of_use_accepted=False, status=Status.ACTIVE.value, type=Role.ANONYMOUS_USER.name, email=membership.get('email', None), firstname=kc_user.first_name, lastname=kc_user.last_name, login_source=LoginSource.BCROS.value) user_model.flush() membership_model = MembershipModel(org_id=org_id, user_id=user_model.id, membership_type_code=membership['membershipType'], membership_type_status=Status.ACTIVE.value) membership_model.flush() name = {'first_name': user_model.firstname, 'last_name': user_model.lastname} ActivityLogPublisher.publish_activity(Activity(org_id, ActivityAction.APPROVE_TEAM_MEMBER.value, name=json.dumps(name), value=membership['membershipType'], id=user_model.id)) return user_model
def create_org(org_info: dict, user_id, token_info: Dict = None): """Create a new organization.""" current_app.logger.debug('<create_org ') is_staff_admin = token_info and 'staff_admin' in token_info.get( 'realm_access').get('roles') if not is_staff_admin: # staff can create any number of orgs count = OrgModel.get_count_of_org_created_by_user_id(user_id) if count >= current_app.config.get('MAX_NUMBER_OF_ORGS'): raise BusinessException(Error.MAX_NUMBER_OF_ORGS_LIMIT, None) if org_info.get('accessType', None) == AccessType.ANONYMOUS.value: raise BusinessException(Error.USER_CANT_CREATE_ANONYMOUS_ORG, None) existing_similar__org = OrgModel.find_similar_org_by_name( org_info['name']) if existing_similar__org is not None: raise BusinessException(Error.DATA_CONFLICT, None) org = OrgModel.create_from_dict(camelback2snake(org_info)) if is_staff_admin: org.access_type = AccessType.ANONYMOUS.value org.billable = False else: org.access_type = AccessType.BCSC.value org.billable = True org.save() current_app.logger.info(f'<created_org org_id:{org.id}') # create the membership record for this user if its not created by staff and access_type is anonymous if not is_staff_admin and org_info.get( 'access_type') != AccessType.ANONYMOUS: membership = MembershipModel( org_id=org.id, user_id=user_id, membership_type_code='OWNER', membership_type_status=Status.ACTIVE.value) membership.save() # Add the user to account_holders group KeycloakService.join_account_holders_group() # TODO Remove later, create payment settings now with default values AccountPaymentModel.create_from_dict({'org_id': org.id}) return Org(org)
def accept_invitation(invitation_id, user, origin): """Add user, role and org from the invitation to membership.""" current_app.logger.debug('>accept_invitation') invitation: InvitationModel = InvitationModel.find_invitation_by_id( invitation_id) if invitation is None: raise BusinessException(Error.DATA_NOT_FOUND, None) if invitation.invitation_status_code == 'ACCEPTED': raise BusinessException(Error.ACTIONED_INVITATION, None) if invitation.invitation_status_code == 'EXPIRED': raise BusinessException(Error.EXPIRED_INVITATION, None) # TODO : isnt this only one?remove for loop for membership in invitation.membership: membership_model = MembershipModel() membership_model.org_id = membership.org_id membership_model.user_id = user.identifier membership_model.membership_type = membership.membership_type # user needs to get approval is_auto_approval = OrgSettingsModel.is_admin_auto_approved_invitees( membership.org_id) if is_auto_approval: membership_model.status = Status.ACTIVE.value else: membership_model.status = Status.PENDING_APPROVAL.value membership_model.save() if not is_auto_approval: try: Invitation.notify_admin(user, invitation_id, membership_model.id, origin) except BusinessException as exception: current_app.logger.error( '<send_notification_to_admin failed', exception.message) invitation.accepted_date = datetime.now() invitation.invitation_status = InvitationStatusModel.get_status_by_code( 'ACCEPTED') invitation.save() current_app.logger.debug('<accept_invitation') return Invitation(invitation)
class Invitation: """Manages Invitation data. This service manages creating, updating, and retrieving Invitation data via the Invitation model. """ def __init__(self, model): """Return an invitation service instance.""" self._model = model @ServiceTracing.disable_tracing def as_dict(self): """Return the internal Invitation model as a dictionary.""" invitation_schema = InvitationSchema() obj = invitation_schema.dump(self._model, many=False) return obj @staticmethod @user_context def create_invitation(invitation_info: Dict, user, invitation_origin, **kwargs): # pylint: disable=too-many-locals """Create a new invitation.""" user_from_context: UserContext = kwargs['user_context'] # Ensure that the current user is ADMIN or COORDINATOR on each org being invited to context_path = CONFIG.AUTH_WEB_TOKEN_CONFIRM_PATH org_id = invitation_info['membership'][0]['orgId'] # get the org and check the access_type org: OrgModel = OrgModel.find_by_org_id(org_id) if not org: raise BusinessException(Error.DATA_NOT_FOUND, None) check_auth(org_id=org_id, one_of_roles=(ADMIN, COORDINATOR, STAFF)) org_name = org.name invitation_type = Invitation._get_inv_type(org) if org.access_type == AccessType.ANONYMOUS.value: # anonymous account never get bceid or bcsc choices mandatory_login_source = LoginSource.BCROS.value elif org.access_type == AccessType.GOVM.value: mandatory_login_source = LoginSource.STAFF.value else: default_login_option_based_on_accesstype = LoginSource.BCSC.value if \ org.access_type == AccessType.REGULAR.value else LoginSource.BCEID.value role = invitation_info['membership'][0]['membershipType'] account_login_options = AccountLoginOptionsModel.find_active_by_org_id( org.id) mandatory_login_source = LoginSource.BCSC.value if \ role == ADMIN else getattr(account_login_options, 'login_source', default_login_option_based_on_accesstype) invitation = InvitationModel.create_from_dict(invitation_info, user.identifier, invitation_type) confirmation_token = Invitation.generate_confirmation_token( invitation.id, invitation.type) invitation.token = confirmation_token invitation.login_source = mandatory_login_source invitation.save() Invitation.send_invitation(invitation, org_name, org.id, user.as_dict(), '{}/{}'.format(invitation_origin, context_path), mandatory_login_source, org_status=org.status_code) # notify admin if staff adds team members if user_from_context.is_staff( ) and invitation_type == InvitationType.STANDARD.value: try: current_app.logger.debug( '<send_team_member_invitation_notification') publish_to_mailer(notification_type='teamMemberInvited', org_id=org_id) current_app.logger.debug( 'send_team_member_invitation_notification>') except Exception as e: # noqa=B901 current_app.logger.error( '<send_team_member_invitation_notification failed') raise BusinessException(Error.FAILED_NOTIFICATION, None) from e return Invitation(invitation) @staticmethod def _get_inv_type(org): """Return the correct invitation type.""" inv_types = { AccessType.GOVM.value: InvitationType.GOVM.value, AccessType.ANONYMOUS.value: InvitationType.DIRECTOR_SEARCH.value, AccessType.REGULAR.value: InvitationType.STANDARD.value } return inv_types.get(org.access_type, InvitationType.STANDARD.value) def update_invitation(self, user, invitation_origin): """Update the specified invitation with new data.""" # Ensure that the current user is ADMIN or COORDINATOR on each org being re-invited to context_path = CONFIG.AUTH_WEB_TOKEN_CONFIRM_PATH for membership in self._model.membership: org_id = membership.org_id check_auth(org_id=org_id, one_of_roles=(ADMIN, COORDINATOR, STAFF)) # TODO doesnt work when invited to multiple teams.. Re-work the logic when multiple teams introduced confirmation_token = Invitation.generate_confirmation_token( self._model.id, self._model.type) self._model.token = confirmation_token updated_invitation = self._model.update_invitation_as_retried() org_name = OrgModel.find_by_org_id( self._model.membership[0].org_id).name Invitation.send_invitation( updated_invitation, org_name, self._model.membership[0].org_id, user.as_dict(), '{}/{}'.format(invitation_origin, context_path), self._model.login_source) return Invitation(updated_invitation) @staticmethod def delete_invitation(invitation_id): """Delete the specified invitation.""" # Ensure that the current user is ADMIN or COORDINATOR for each org in the invitation invitation = InvitationModel.find_invitation_by_id(invitation_id) if invitation is None: raise BusinessException(Error.DATA_NOT_FOUND, None) for membership in invitation.membership: org_id = membership.org_id check_auth(org_id=org_id, one_of_roles=(ADMIN, COORDINATOR, STAFF)) invitation.delete() @staticmethod @user_context def get_invitations_for_org(org_id, status=None, **kwargs): """Get invitations for an org.""" user_from_context: UserContext = kwargs['user_context'] org_model = OrgModel.find_by_org_id(org_id) if not org_model: return None if status: status = InvitationStatus[status] # If staff return full list if user_from_context.is_staff(): return InvitationModel.find_pending_invitations_by_org(org_id) current_user: UserService = UserService.find_by_jwt_token() current_user_membership: MembershipModel = \ MembershipModel.find_membership_by_user_and_org(user_id=current_user.identifier, org_id=org_id) # If no active membership return empty array if current_user_membership is None or \ current_user_membership.status != Status.ACTIVE.value: return [] # Ensure either ADMIN or COORDINATOR if current_user_membership.membership_type_code == USER: return [] return InvitationModel.find_invitations_by_org(org_id=org_id, status=status) @staticmethod def find_invitation_by_id(invitation_id): """Find an existing invitation with the provided id.""" if invitation_id is None: return None invitation = InvitationModel.find_invitation_by_id(invitation_id) if not invitation: return None # Ensure that the current user is an ADMIN or COORDINATOR on each org in the invite being retrieved for membership in invitation.membership: org_id = membership.org_id check_auth(org_id=org_id, one_of_roles=(ADMIN, COORDINATOR, STAFF)) return Invitation(invitation) @staticmethod def send_admin_notification(user, url, recipient_email_list, org_name, org_id): """Send the admin email notification.""" data = { 'accountId': org_id, 'emailAddresses': recipient_email_list, 'contextUrl': url, 'userFirstName': user['firstname'], 'userLastName': user['lastname'], 'orgName': org_name } try: current_app.logger.debug('<send_admin_notification') publish_to_mailer(notification_type='adminNotification', org_id=org_id, data=data) current_app.logger.debug('send_admin_notification>') except Exception as e: # noqa=B901 current_app.logger.error('<send_admin_notification failed') raise BusinessException(Error.FAILED_NOTIFICATION, None) from e @staticmethod def send_invitation( invitation: InvitationModel, org_name, org_id, user, # pylint: disable=too-many-arguments app_url, login_source, org_status=None): """Send the email notification.""" current_app.logger.debug('<send_invitation') mail_configs = Invitation._get_invitation_configs( org_name, login_source, org_status) recipient = invitation.recipient_email token_confirm_url = '{}/{}/{}'.format( app_url, mail_configs.get('token_confirm_path'), invitation.token) role = invitation.membership[0].membership_type.display_name data = { 'accountId': org_id, 'emailAddresses': recipient, 'contextUrl': token_confirm_url, 'userFirstName': user['firstname'], 'userLastName': user['lastname'], 'orgName': org_name, 'role': role } try: publish_to_mailer( notification_type=mail_configs.get('notification_type'), org_id=org_id, data=data) except BusinessException as exception: invitation.invitation_status_code = 'FAILED' invitation.save() current_app.logger.debug('>send_invitation failed') current_app.logger.debug(exception) raise BusinessException(Error.FAILED_INVITATION, None) from exception current_app.logger.debug('>send_invitation') @staticmethod def _get_invitation_configs(org_name, login_source, org_status=None): """Get the config for different email types.""" login_source = login_source or LoginSource.BCSC.value escape_url = escape_wam_friendly_url(org_name) token_confirm_path = f'{escape_url}/validatetoken/{login_source}' if login_source == LoginSource.STAFF.value: # for GOVM accounts , there are two kinda of invitation. Its same login source # if its first invitation to org , its an account set up invitation else normal joining invite login_source = 'IDIR/ACCOUNTSETUP' if Invitation._is_first_user_to_a_gov_accnt( org_status) else login_source govm_setup_configs = { 'token_confirm_path': token_confirm_path, 'notification_type': 'govmBusinessInvitation', } govm_member_configs = { 'token_confirm_path': token_confirm_path, 'notification_type': 'govmMemberInvitation', } director_search_configs = { 'token_confirm_path': token_confirm_path, 'notification_type': 'dirsearchBusinessInvitation', } bceid_configs = { 'token_confirm_path': token_confirm_path, 'notification_type': 'businessInvitationForBceid', } default_configs = { 'token_confirm_path': token_confirm_path, 'notification_type': 'businessInvitation', } mail_configs = { 'BCROS': director_search_configs, 'BCEID': bceid_configs, 'IDIR': govm_member_configs, 'IDIR/ACCOUNTSETUP': govm_setup_configs } return mail_configs.get(login_source, default_configs) @staticmethod def generate_confirmation_token(invitation_id, invitation_type=''): """Generate the token to be sent in the email.""" serializer = URLSafeTimedSerializer(CONFIG.EMAIL_TOKEN_SECRET_KEY) token = {'id': invitation_id, 'type': invitation_type} return serializer.dumps(token, salt=CONFIG.EMAIL_SECURITY_PASSWORD_SALT) @staticmethod def _is_first_user_to_a_gov_accnt(org_status: str) -> bool: return org_status == OrgStatusEnum.PENDING_INVITE_ACCEPT.value @staticmethod def validate_token(token): """Check whether the passed token is valid.""" serializer = URLSafeTimedSerializer(CONFIG.EMAIL_TOKEN_SECRET_KEY) token_valid_for = int( CONFIG.TOKEN_EXPIRY_PERIOD ) * 3600 * 24 if CONFIG.TOKEN_EXPIRY_PERIOD else 3600 * 24 * 7 try: invitation_id = serializer.loads( token, salt=CONFIG.EMAIL_SECURITY_PASSWORD_SALT, max_age=token_valid_for).get('id') except Exception as e: # noqa: E722 raise BusinessException(Error.EXPIRED_INVITATION, None) from e invitation: InvitationModel = InvitationModel.find_invitation_by_id( invitation_id) if invitation is None: raise BusinessException(Error.DATA_NOT_FOUND, None) if invitation.invitation_status_code == 'ACCEPTED': raise BusinessException(Error.ACTIONED_INVITATION, None) if invitation.invitation_status_code == 'EXPIRED': raise BusinessException(Error.EXPIRED_INVITATION, None) return Invitation(invitation) @staticmethod def notify_admin(user, invitation_id, membership_id, invitation_origin): """Admins should be notified if user has responded to invitation.""" current_app.logger.debug('<notify_admin') admin_list = UserService.get_admins_for_membership(membership_id) invitation: InvitationModel = InvitationModel.find_invitation_by_id( invitation_id) context_path = CONFIG.AUTH_WEB_TOKEN_CONFIRM_PATH # Don't send email in case no admin exist in the org. (staff sent invitation) if len(admin_list) >= 1: admin_emails = ','.join([ str(x.contacts[0].contact.email) for x in admin_list if x.contacts ]) else: # No admin, find Sender email to notify sender (staff) admin_emails = invitation.sender.email if admin_emails != '': Invitation.send_admin_notification( user.as_dict(), '{}/{}'.format(invitation_origin, context_path), admin_emails, invitation.membership[0].org.name, invitation.membership[0].org.id) current_app.logger.debug('>notify_admin') return Invitation(invitation) @staticmethod @user_context def accept_invitation(invitation_id, user: UserService, origin, add_membership: bool = True, **kwargs): """Add user, role and org from the invitation to membership.""" current_app.logger.debug('>accept_invitation') user_from_context: UserContext = kwargs['user_context'] invitation: InvitationModel = InvitationModel.find_invitation_by_id( invitation_id) if invitation is None: raise BusinessException(Error.DATA_NOT_FOUND, None) if invitation.invitation_status_code == 'ACCEPTED': raise BusinessException(Error.ACTIONED_INVITATION, None) if invitation.invitation_status_code == 'EXPIRED': raise BusinessException(Error.EXPIRED_INVITATION, None) if (login_source := user_from_context.login_source ) is not None: # bcros comes with out token if invitation.login_source != login_source: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) if add_membership: for membership in invitation.membership: membership_model = MembershipModel() membership_model.org_id = membership.org_id membership_model.user_id = user.identifier membership_model.membership_type = membership.membership_type # check to ensure an invitation for this user/org has not already been processed existing_membership = MembershipService \ .get_membership_for_org_and_user(org_id=membership_model.org_id, user_id=membership_model.user_id) if existing_membership: raise BusinessException(Error.DATA_ALREADY_EXISTS, None) org_model: OrgModel = OrgModel.find_by_org_id( membership.org_id) # GOVM users gets direct approval since they are IDIR users. membership_model.status = Invitation._get_status_based_on_org( org_model) membership_model.save() try: # skip notifying admin if it auto approved # for now , auto approval happens for GOVM.If more auto approval comes , just check if its GOVM if membership_model.status != Status.ACTIVE.value: Invitation.notify_admin(user, invitation_id, membership_model.id, origin) except BusinessException as exception: current_app.logger.error( '<send_notification_to_admin failed', exception.message) invitation.accepted_date = datetime.now() invitation.invitation_status = InvitationStatusModel.get_status_by_code( 'ACCEPTED') invitation.save() # Call keycloak to add the user to the group. if user: group_name: str = KeycloakService.join_users_group() KeycloakService.join_account_holders_group(user.keycloak_guid) if group_name == GROUP_GOV_ACCOUNT_USERS: # Add contact to the user. user.add_contact(dict( email=user_from_context.token_info.get('email', None)), throw_error_for_duplicates=False) current_app.logger.debug('<accept_invitation') return Invitation(invitation)
def create_org( org_info: dict, user_id, # pylint: disable=too-many-locals, too-many-statements token_info: Dict = None, bearer_token: str = None): """Create a new organization.""" current_app.logger.debug('<create_org ') bcol_credential = org_info.pop('bcOnlineCredential', None) mailing_address = org_info.pop('mailingAddress', None) bcol_account_number = None bcol_user_id = None # If the account is created using BCOL credential, verify its valid bc online account if bcol_credential: bcol_response = Org.get_bcol_details(bcol_credential, org_info, bearer_token).json() bcol_account_number = bcol_response.get('accountNumber') bcol_user_id = bcol_response.get('userId') org_info[ 'typeCode'] = OrgType.PREMIUM.value if bcol_account_number else OrgType.BASIC.value is_staff_admin = token_info and 'staff_admin' in token_info.get( 'realm_access').get('roles') if not is_staff_admin: # staff can create any number of orgs count = OrgModel.get_count_of_org_created_by_user_id(user_id) if count >= current_app.config.get('MAX_NUMBER_OF_ORGS'): raise BusinessException(Error.MAX_NUMBER_OF_ORGS_LIMIT, None) if org_info.get('accessType', None) == AccessType.ANONYMOUS.value: raise BusinessException(Error.USER_CANT_CREATE_ANONYMOUS_ORG, None) if not bcol_account_number: # Allow duplicate names if premium Org.raise_error_if_duplicate_name(org_info['name']) org = OrgModel.create_from_dict(camelback2snake(org_info)) org.add_to_session() if is_staff_admin: org.access_type = AccessType.ANONYMOUS.value org.billable = False else: org.access_type = AccessType.BCSC.value org.billable = True # If mailing address is provided, save it if mailing_address: Org.add_contact_to_org(mailing_address, org) # create the membership record for this user if its not created by staff and access_type is anonymous if not is_staff_admin and org_info.get( 'access_type') != AccessType.ANONYMOUS: membership = MembershipModel( org_id=org.id, user_id=user_id, membership_type_code='OWNER', membership_type_status=Status.ACTIVE.value) membership.add_to_session() # Add the user to account_holders group KeycloakService.join_account_holders_group() Org.add_payment_settings(org.id, bcol_account_number, bcol_user_id) org.save() current_app.logger.info(f'<created_org org_id:{org.id}') return Org(org)