コード例 #1
0
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
コード例 #2
0
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
コード例 #3
0
ファイル: user.py プロジェクト: syin/sbc-auth
 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()
コード例 #4
0
    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)
コード例 #5
0
ファイル: org.py プロジェクト: cameron-freshworks/sbc-auth
    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()
コード例 #6
0
    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)
コード例 #7
0
    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)
コード例 #8
0
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
コード例 #9
0
ファイル: user.py プロジェクト: shabeeb-aot/sbc-auth
    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
コード例 #10
0
ファイル: org.py プロジェクト: jeznorth/sbc-auth
    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)
コード例 #11
0
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'
コード例 #12
0
    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
コード例 #13
0
    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)
コード例 #14
0
    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)
コード例 #15
0
ファイル: invitation.py プロジェクト: saravanpa-aot/sbc-auth
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)
コード例 #16
0
ファイル: org.py プロジェクト: peter-freshworks/sbc-auth
    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)