예제 #1
0
def test_patch_org_status(session, monkeypatch, auth_mock):  # pylint:disable=unused-argument
    """Assert that an Org status can be updated."""
    org = factory_org_service()
    user = factory_user_model_with_contact()
    token_info = TestJwtClaims.get_test_user(sub=user.keycloak_guid, source=LoginSource.BCEID.value)
    patch_token_info(token_info, monkeypatch)

    # Validate and update org status
    patch_info = {
        'action': PatchActions.UPDATE_STATUS.value,
        'statusCode': OrgStatus.SUSPENDED.value,
    }
    with pytest.raises(BusinessException) as exception:
        org.patch_org(PatchActions.UPDATE_STATUS.value, patch_info)
    assert exception.value.code == Error.INVALID_INPUT.name

    patch_info['suspensionReasonCode'] = SuspensionReasonCode.OWNER_CHANGE.name
    with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
        updated_org = org.patch_org(PatchActions.UPDATE_STATUS.value, patch_info)
        mock_alp.assert_called_with(Activity(action=ActivityAction.ACCOUNT_SUSPENSION.value,
                                             org_id=ANY, name=ANY, id=ANY,
                                             value=SuspensionReasonCode.OWNER_CHANGE.value))
        assert updated_org['status_code'] == OrgStatus.SUSPENDED.value

    patch_info = {
        'action': PatchActions.UPDATE_STATUS.value,
        'statusCode': OrgStatus.ACTIVE.value,
    }
    updated_org = org.patch_org(PatchActions.UPDATE_STATUS.value, patch_info)
    assert updated_org['status_code'] == OrgStatus.ACTIVE.value

    with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
        OrgService.update_login_option(org._model.id, 'BCROS')
        mock_alp.assert_called_with(Activity(action=ActivityAction.AUTHENTICATION_METHOD_CHANGE.value,
                                             org_id=ANY, name=ANY, id=ANY, value='BCROS'))
예제 #2
0
    def deactivate_membership(self, **kwargs):
        """Mark this membership as inactive."""
        current_app.logger.debug('<deactivate_membership')
        user_from_context: UserContext = kwargs['user_context']
        # if this is a member removing another member, check that they admin or owner
        if self._model.user.username != user_from_context.user_name:
            check_auth(org_id=self._model.org_id,
                       one_of_roles=(COORDINATOR, ADMIN))

        # check to ensure that owner isn't removed by anyone but an owner
        if self._model.membership_type == ADMIN:
            check_auth(org_id=self._model.org_id, one_of_roles=(ADMIN))

        self._model.membership_status = MembershipStatusCodeModel.get_membership_status_by_code(
            'INACTIVE')
        current_app.logger.info(
            f'<deactivate_membership for {self._model.user.username}')
        self._model.save()
        # Remove from account_holders group in keycloak
        Membership._add_or_remove_group(self._model)
        name = {
            'first_name': self._model.user.firstname,
            'last_name': self._model.user.lastname
        }
        ActivityLogPublisher.publish_activity(
            Activity(self._model.org_id,
                     ActivityAction.REMOVE_TEAM_MEMBER.value,
                     name=json.dumps(name),
                     id=self._model.user.id))
        current_app.logger.debug('>deactivate_membership')
        return self
예제 #3
0
    def publish_activity(activity: Activity):  # pylint:disable=unused-argument
        """Publish the activity asynchronously, using the given details."""
        try:
            # find user_id if haven't passed in
            if not activity.actor_id and g and 'jwt_oidc_token_info' in g:
                user: UserModel = UserModel.find_by_jwt_token()
                activity.actor_id = user.id if user else None
            data = {
                'actorId': activity.actor_id,
                'action': activity.action,
                'itemType': 'ACCOUNT',
                'itemName': activity.name,
                'itemId': activity.id,
                'itemValue': activity.value,
                'orgId': activity.org_id,
                'remoteAddr': fetch_remote_addr(),
                'createdAt': f'{datetime.now()}'
            }
            source = 'https://api.auth.bcregistry.gov.bc.ca/v1/accounts'

            payload = {
                'specversion': '1.x-wip',
                'type': 'bc.registry.auth.activity',
                'source': source,
                'id': str(uuid.uuid1()),
                'time': f'{datetime.now()}',
                'datacontenttype': 'application/json',
                'data': data
            }
            publish_response(payload=payload, client_name=CONFIG.NATS_ACTIVITY_CLIENT_NAME,
                             subject=CONFIG.NATS_ACTIVITY_SUBJECT)
        except Exception as err:  # noqa: B902 # pylint: disable=broad-except
            capture_message('Activity Queue Publish Event Error:' + str(err), level='error')
            current_app.logger.error('Activity Queue Publish Event Error:', exc_info=True)
예제 #4
0
 def _publish_activity_on_name_change(org_id: int, org_name: str):
     if org_name:
         ActivityLogPublisher.publish_activity(
             Activity(org_id,
                      ActivityAction.ACCOUNT_NAME_CHANGE.value,
                      name=org_name,
                      value=org_name))
예제 #5
0
def test_create_invitation(session, auth_mock, keycloak_mock, monkeypatch):  # pylint:disable=unused-argument
    """Assert that an Invitation can be created."""
    with patch.object(InvitationService, 'send_invitation',
                      return_value=None) as mock_notify:
        user = factory_user_model(TestUserInfo.user_test)
        patch_token_info({'sub': user.keycloak_guid}, monkeypatch)
        org = OrgService.create_org(TestOrgInfo.org1, user_id=user.id)
        org_dictionary = org.as_dict()
        invitation_info = factory_invitation(org_dictionary['id'])

        with patch.object(ActivityLogPublisher,
                          'publish_activity',
                          return_value=None) as mock_alp:
            invitation = InvitationService.create_invitation(
                invitation_info, User(user), '')
            mock_alp.assert_called_with(
                Activity(action=ActivityAction.INVITE_TEAM_MEMBER.value,
                         org_id=ANY,
                         name=invitation_info['recipientEmail'],
                         id=ANY,
                         value='USER'))

        invitation_dictionary = invitation.as_dict()
        assert invitation_dictionary['recipient_email'] == invitation_info[
            'recipientEmail']
        assert invitation_dictionary['id']
        mock_notify.assert_called()
예제 #6
0
    def update_login_option(org_id, login_source):
        """Create a new contact for this org."""
        # check for existing contact (only one contact per org for now)
        current_app.logger.debug('>update_login_option')
        org = OrgModel.find_by_org_id(org_id)
        if org is None:
            raise BusinessException(Error.DATA_NOT_FOUND, None)

        check_auth(one_of_roles=ADMIN, org_id=org_id)

        existing_login_option = AccountLoginOptionsModel.find_active_by_org_id(
            org_id)
        if existing_login_option is not None:
            existing_login_option.is_active = False
            existing_login_option.add_to_session()

        login_option = AccountLoginOptionsModel(login_source=login_source,
                                                org_id=org_id)
        login_option.save()
        ActivityLogPublisher.publish_activity(
            Activity(org_id,
                     ActivityAction.AUTHENTICATION_METHOD_CHANGE.value,
                     name=org.name,
                     value=login_source,
                     id=login_option.id))
        return login_option
예제 #7
0
    def change_org_status(self, status_code, suspension_reason_code):
        """Update the status of the org.

        Used now for suspending/activate account.

            1) check access .only staff can do it now
            2) check org status/eligiblity
            3) suspend it

        """
        current_app.logger.debug('<change_org_status ')

        user: UserModel = UserModel.find_by_jwt_token()
        org_model = self._model
        org_model.status_code = status_code
        org_model.decision_made_by = user.username  # not sure if a new field is needed for this.
        if status_code == OrgStatus.SUSPENDED.value:
            org_model.suspended_on = datetime.today()
            org_model.suspension_reason_code = suspension_reason_code
        org_model.save()
        if status_code == OrgStatus.SUSPENDED.value:
            suspension_reason_description = SuspensionReasonCode[suspension_reason_code].value \
                if suspension_reason_code in [
                item.name for item in SuspensionReasonCode] else ''
            ActivityLogPublisher.publish_activity(
                Activity(org_model.id,
                         ActivityAction.ACCOUNT_SUSPENSION.value,
                         name=org_model.name,
                         value=suspension_reason_description))
        current_app.logger.debug('change_org_status>')
        return Org(org_model)
예제 #8
0
def test_update_product_subscription(session, keycloak_mock, monkeypatch):
    """Assert that updating product subscription works."""
    user = factory_user_model(TestUserInfo.user_test)
    patch_token_info({'sub': user.keycloak_guid}, monkeypatch)
    org = Org.create_org(TestOrgInfo.org1, user_id=user.id)
    product_subscription = ProductSubscription(
        org_id=org._model.id,
        product_code='PPR',
        status_code=ProductSubscriptionStatus.ACTIVE.value).flush()

    class MockContact(object):
        email = ''

    class MockPerson(object):
        def __init__(self, contact: MockContact):
            self.contact = contact

    with patch.object(ActivityLogPublisher,
                      'publish_activity',
                      return_value=None) as mock_alp:
        with patch.object(ContactLinkModel,
                          'find_by_user_id',
                          return_value=MockPerson(contact=MockContact())):
            ProductService.update_product_subscription(product_subscription.id,
                                                       True, org._model.id)
            mock_alp.assert_called_with(
                Activity(action=ActivityAction.ADD_PRODUCT_AND_SERVICE.value,
                         org_id=ANY,
                         value=ANY,
                         id=ANY,
                         name='Personal Property Registry'))
예제 #9
0
    def update_product_subscription(product_subscription_id: int,
                                    is_approved: bool,
                                    org_id: int,
                                    is_new_transaction: bool = True):
        """Update Product Subscription."""
        current_app.logger.debug('<update_task_product ')
        # Approve/Reject Product subscription
        product_subscription: ProductSubscriptionModel = ProductSubscriptionModel.find_by_id(
            product_subscription_id)
        if is_approved:
            product_subscription.status_code = ProductSubscriptionStatus.ACTIVE.value
        else:
            product_subscription.status_code = ProductSubscriptionStatus.REJECTED.value
        product_subscription.flush()
        if is_new_transaction:  # Commit the transaction if it's a new transaction
            db.session.commit()

        # Get the org and to get admin mail address
        org: OrgModel = OrgModel.find_by_org_id(org_id)
        # Find admin email address
        admin_email = ContactLinkModel.find_by_user_id(
            org.members[0].user.id).contact.email
        product_model: ProductCodeModel = ProductCodeModel.find_by_code(
            product_subscription.product_code)
        Product.send_approved_product_subscription_notification(
            admin_email, product_model.description,
            product_subscription.status_code)
        if is_approved:
            ActivityLogPublisher.publish_activity(
                Activity(org_id,
                         ActivityAction.ADD_PRODUCT_AND_SERVICE.value,
                         name=product_model.description))
        current_app.logger.debug('>update_task_product ')
예제 #10
0
    def delete_affiliation(org_id, business_identifier, email_addresses: str = None,
                           reset_passcode: bool = False, log_delete_draft: bool = False):
        """Delete the affiliation for the provided org id and business id."""
        current_app.logger.info(f'<delete_affiliation org_id:{org_id} business_identifier:{business_identifier}')
        org = OrgService.find_by_org_id(org_id, allowed_roles=(*CLIENT_AUTH_ROLES, STAFF))
        if org is None:
            raise BusinessException(Error.DATA_NOT_FOUND, None)

        entity = EntityService.find_by_business_identifier(business_identifier,
                                                           allowed_roles=(*CLIENT_AUTH_ROLES, STAFF))
        if entity is None:
            raise BusinessException(Error.DATA_NOT_FOUND, None)

        entity_id = entity.identifier

        affiliation = AffiliationModel.find_affiliation_by_org_and_entity_ids(org_id=org_id, entity_id=entity_id)
        if affiliation is None:
            raise BusinessException(Error.DATA_NOT_FOUND, None)

        if reset_passcode:
            entity.reset_passcode(entity.business_identifier, email_addresses)
        affiliation.delete()
        entity.set_pass_code_claimed(False)

        # When registering a business it will affiliate a NR -> unaffiliate a NR draft -> affiliate a business.
        # Users can also intentionally delete a draft. We want to log this action.
        should_publish = (log_delete_draft or not (entity.status == NRStatus.DRAFT.value and
                                                   entity.corp_type == CorpType.NR.value))
        if entity.corp_type != CorpType.RTMP.value and should_publish:
            name = entity.name if len(entity.name) > 0 else entity.business_identifier
            ActivityLogPublisher.publish_activity(Activity(org_id, ActivityAction.REMOVE_AFFILIATION.value,
                                                           name=name, id=entity.business_identifier))
예제 #11
0
 def _publish_activity_on_mailing_address_change(org_id: int, org_name: str,
                                                 mailing_address: str):
     if mailing_address:
         ActivityLogPublisher.publish_activity(
             Activity(org_id,
                      ActivityAction.ACCOUNT_ADDRESS_CHANGE.value,
                      name=org_name,
                      value=json.dumps(mailing_address)))
예제 #12
0
 def _publish_activity_if_active(membership: MembershipModel,
                                 user: UserContext):
     """Purpose: GOVM accounts - they instantly get accepted."""
     if membership.status == Status.ACTIVE.value:
         name = {'first_name': user.first_name, 'last_name': user.last_name}
         ActivityLogPublisher.publish_activity(
             Activity(membership.org_id,
                      ActivityAction.APPROVE_TEAM_MEMBER.value,
                      name=json.dumps(name)))
예제 #13
0
def test_create_org_products(session, keycloak_mock, monkeypatch):
    """Assert that an Org with products can be created."""
    user = factory_user_model()
    patch_token_info({'sub': user.keycloak_guid}, monkeypatch)
    with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
        org = OrgService.create_org(TestOrgInfo.org_with_products, user_id=user.id)
        mock_alp.assert_called_with(Activity(action=ActivityAction.ADD_PRODUCT_AND_SERVICE.value,
                                             org_id=ANY, value=ANY, id=ANY, name='Business Registry & Name Request'))
        assert org
    dictionary = org.as_dict()
    assert dictionary['name'] == TestOrgInfo.org_with_products['name']
예제 #14
0
def test_delete_affiliation(session, auth_mock, monkeypatch):  # pylint:disable=unused-argument
    """Assert that an affiliation can be deleted."""
    entity_service = factory_entity_service(TestEntityInfo.entity_lear_mock)
    entity_dictionary = entity_service.as_dict()
    business_identifier = entity_dictionary['business_identifier']

    org_service = factory_org_service()
    org_dictionary = org_service.as_dict()
    org_id = org_dictionary['id']
    patch_token_info(TestJwtClaims.user_test, monkeypatch)
    with patch.object(ActivityLogPublisher,
                      'publish_activity',
                      return_value=None) as mock_alp:
        affiliation = AffiliationService.create_affiliation(
            org_id, business_identifier,
            TestEntityInfo.entity_lear_mock['passCode'])
        mock_alp.assert_called_with(
            Activity(action=ActivityAction.CREATE_AFFILIATION.value,
                     org_id=ANY,
                     name=ANY,
                     id=ANY))

    with patch.object(ActivityLogPublisher,
                      'publish_activity',
                      return_value=None) as mock_alp:
        AffiliationService.delete_affiliation(
            org_id=org_id,
            business_identifier=business_identifier,
            email_addresses=None)
        mock_alp.assert_called_with(
            Activity(action=ActivityAction.REMOVE_AFFILIATION.value,
                     org_id=ANY,
                     name=ANY,
                     id=ANY))

    found_affiliation = AffiliationModel.query.filter_by(
        id=affiliation.identifier).first()
    assert found_affiliation is None
예제 #15
0
def test_accept_invitation_for_govm(session, auth_mock, keycloak_mock,
                                    monkeypatch):  # pylint:disable=unused-argument
    """Accept the invitation and add membership from the invitation to the org."""
    with patch.object(InvitationService, 'send_invitation', return_value=None):
        with patch.object(auth, 'check_auth', return_value=True):
            with patch.object(InvitationService,
                              'notify_admin',
                              return_value=None):
                user_with_token = TestUserInfo.user_staff_admin
                user_with_token[
                    'keycloak_guid'] = TestJwtClaims.public_user_role['sub']
                user = factory_user_model(user_with_token)

                patch_token_info(TestJwtClaims.staff_admin_role, monkeypatch)

                org = OrgService.create_org(TestOrgInfo.org_govm,
                                            user_id=user.id)
                org_dictionary = org.as_dict()
                invitation_info = factory_invitation(org_dictionary['id'])
                user_with_token_invitee = TestUserInfo.user1
                user_with_token_invitee[
                    'keycloak_guid'] = TestJwtClaims.edit_role_2['sub']
                user_invitee = factory_user_model(user_with_token_invitee)
                new_invitation = InvitationService.create_invitation(
                    invitation_info, User(user_invitee), '')
                new_invitation_dict = new_invitation.as_dict()
                with patch.object(ActivityLogPublisher,
                                  'publish_activity',
                                  return_value=None) as mock_alp:
                    InvitationService.accept_invitation(
                        new_invitation_dict['id'], User(user_invitee), '')
                    mock_alp.assert_called_with(
                        Activity(
                            action=ActivityAction.APPROVE_TEAM_MEMBER.value,
                            org_id=ANY,
                            name=ANY,
                            id=ANY,
                            value=ANY))

                members = MembershipService.get_members_for_org(
                    org_dictionary['id'], 'ACTIVE')
                assert members
                assert len(members) == 1, 'user gets active membership'
예제 #16
0
def test_update_org_name(session, monkeypatch):  # pylint:disable=unused-argument
    """Assert that an Org name cannot be updated."""
    org = factory_org_service()

    with patch.object(RestService, 'put') as mock_put:
        with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
            org = org.update_org({'name': 'My Test'})
            mock_alp.assert_called_with(Activity(action=ActivityAction.ACCOUNT_NAME_CHANGE.value,
                                                 org_id=ANY, value='My Test', id=ANY,
                                                 name=ANY))
        assert org
        dictionary = org.as_dict()
        mock_put.assert_called()
        actual_data = mock_put.call_args.kwargs.get('data')
        expected_data = {
            'accountId': dictionary.get('id'),
            'accountName': dictionary.get('name'),
        }
        assert expected_data == actual_data, 'name update work.'
예제 #17
0
 def send_otp_authenticator_reset_notification(recipient_email, origin_url, org_id):
     """Send Authenticator reset notification to the user."""
     current_app.logger.debug('<send_otp_authenticator_reset_notification')
     app_url = f"{origin_url}/{current_app.config.get('AUTH_WEB_TOKEN_CONFIRM_PATH')}"
     context_path = 'signin/bceid'
     login_url = f'{app_url}/{context_path}'
     data = {
         'accountId': org_id,
         'emailAddresses': recipient_email,
         'contextUrl': login_url
     }
     try:
         publish_to_mailer('otpAuthenticatorResetNotification', org_id=org_id, data=data)
         current_app.logger.debug('<send_otp_authenticator_reset_notification')
         ActivityLogPublisher.publish_activity(Activity(org_id, ActivityAction.RESET_2FA.value,
                                                        name=recipient_email))
     except Exception as e:  # noqa=B901
         current_app.logger.error('<send_otp_authenticator_reset_notification failed')
         raise BusinessException(Error.FAILED_NOTIFICATION, None) from e
예제 #18
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
예제 #19
0
def test_put_basic_org_assert_pay_request_is_govm(session,
                                                  keycloak_mock, staff_user_mock,
                                                  monkeypatch):  # pylint:disable=unused-argument
    """Assert that while org creation , pay-api gets called with proper data for basic accounts."""
    user = factory_user_model()
    staff_token_info = TestJwtClaims.get_test_user(sub=user.keycloak_guid, source=LoginSource.STAFF.value,
                                                   roles=['create_accounts'])
    user2 = factory_user_model(TestUserInfo.user2)
    public_token_info = TestJwtClaims.get_test_user(sub=user2.keycloak_guid, source=LoginSource.STAFF.value,
                                                    roles=['gov_account_user'])
    patch_token_info(staff_token_info, monkeypatch)
    org: OrgService = OrgService.create_org(TestOrgInfo.org_govm, user_id=user.id)
    assert org
    with patch.object(RestService, 'put') as mock_post:
        payment_details = TestPaymentMethodInfo.get_payment_method_input_with_revenue()
        org_body = {
            'mailingAddress': TestOrgInfo.get_mailing_address(),
            **payment_details

        }
        patch_token_info(public_token_info, monkeypatch)
        with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
            org = OrgService.update_org(org, org_body)
            mock_alp.assert_called_with(Activity(action=ActivityAction.ACCOUNT_ADDRESS_CHANGE.value,
                                        org_id=ANY, name=ANY, id=ANY,
                                        value=ANY))
            assert org
        dictionary = org.as_dict()
        assert dictionary['name'] == TestOrgInfo.org_govm['name']
        mock_post.assert_called()
        actual_data = mock_post.call_args.kwargs.get('data')
        expected_data = {
            'accountId': dictionary.get('id'),
            'accountName': dictionary.get('name') + '-' + dictionary.get('branch_name'),
            'paymentInfo': {
                'methodOfPayment': 'EJV',
                'revenueAccount': payment_details.get('paymentInfo').get('revenueAccount')
            },
            'contactInfo': TestOrgInfo.get_mailing_address()

        }
        assert expected_data == actual_data
예제 #20
0
def test_update_basic_org_assert_pay_request_activity(session, keycloak_mock, monkeypatch):
    """Assert that while org payment update touches activity log."""
    user_with_token = TestUserInfo.user_test
    user_with_token['keycloak_guid'] = TestJwtClaims.public_user_role['sub']
    user = factory_user_model(user_info=user_with_token)
    patch_token_info({'sub': user.keycloak_guid}, monkeypatch)
    # Have to patch this because the pay spec is wrong and returns 201, not 202 or 200.
    patch_pay_account_post(monkeypatch)
    org = OrgService.create_org(TestOrgInfo.org1, user_id=user.id)
    new_payment_method = TestPaymentMethodInfo.get_payment_method_input(PaymentMethod.ONLINE_BANKING)

    patch_token_info(TestJwtClaims.public_user_role, monkeypatch)

    # Have to patch this because the pay spec is wrong and returns 201, not 202 or 200.
    patch_pay_account_put(monkeypatch)
    with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
        org = OrgService.update_org(org, new_payment_method)
        mock_alp.assert_called_with(Activity(action=ActivityAction.PAYMENT_INFO_CHANGE.value,
                                             org_id=ANY, name=ANY, id=ANY,
                                             value=PaymentMethod.ONLINE_BANKING.value))
예제 #21
0
    def update_membership(self, updated_fields, **kwargs):
        """Update an existing membership with the given role."""
        # Ensure that this user is an COORDINATOR or ADMIN on the org associated with this membership
        current_app.logger.debug('<update_membership')
        user_from_context: UserContext = kwargs['user_context']
        check_auth(org_id=self._model.org_id,
                   one_of_roles=(COORDINATOR, ADMIN, STAFF))

        # bceid Members cant be ADMIN's.Unless they have an affidavit approved.
        # TODO when multiple teams for bceid are present , do if the user has affidavit present check
        is_bceid_user = self._model.user.login_source == LoginSource.BCEID.value
        if is_bceid_user and getattr(
                updated_fields.get('membership_type', None), 'code',
                None) == ADMIN:
            raise BusinessException(Error.BCEID_USERS_CANT_BE_OWNERS, None)

        # Ensure that a member does not upgrade a member to ADMIN from COORDINATOR unless they are an ADMIN themselves
        if self._model.membership_type.code == COORDINATOR and updated_fields.get(
                'membership_type', None) == ADMIN:
            check_auth(org_id=self._model.org_id, one_of_roles=(ADMIN, STAFF))

        updated_membership_status = updated_fields.get('membership_status')
        admin_getting_removed: bool = False
        # Admin can be removed by other admin or staff. #4909
        if updated_membership_status \
                and updated_membership_status.id == Status.INACTIVE.value \
                and self._model.membership_type.code == ADMIN:
            admin_getting_removed = True
            if OrgService(self._model.org).get_owner_count() == 1:
                raise BusinessException(Error.CHANGE_ROLE_FAILED_ONLY_OWNER,
                                        None)

        # Ensure that if downgrading from owner that there is at least one other owner in org
        if self._model.membership_type.code == ADMIN and \
                updated_fields.get('membership_type', None) != ADMIN and \
                OrgService(self._model.org).get_owner_count() == 1:
            raise BusinessException(Error.CHANGE_ROLE_FAILED_ONLY_OWNER, None)

        for key, value in updated_fields.items():
            if value is not None:
                setattr(self._model, key, value)
        self._model.save()

        membership_type = updated_fields.get(
            'membership_type') or self._model.membership_type.code
        if updated_membership_status \
                and updated_membership_status.id in [Status.INACTIVE.value, Status.ACTIVE.value]:
            action = ActivityAction.APPROVE_TEAM_MEMBER.value \
                if updated_membership_status.id == Status.ACTIVE.value  \
                else ActivityAction.REMOVE_TEAM_MEMBER.value
            name = {
                'first_name': self._model.user.firstname,
                'last_name': self._model.user.lastname
            }
            ActivityLogPublisher.publish_activity(
                Activity(self._model.org_id,
                         action,
                         name=json.dumps(name),
                         id=self._model.user.id,
                         value=membership_type))
        # Add to account_holders group in keycloak
        Membership._add_or_remove_group(self._model)
        is_bcros_user = self._model.user.login_source == LoginSource.BCROS.value
        # send mail if staff modifies , not applicable for bcros , only if anything is getting updated
        if user_from_context.is_staff(
        ) and not is_bcros_user and len(updated_fields) != 0:
            publish_to_mailer(notification_type='teamModified',
                              org_id=self._model.org.id)

        # send mail to the person itself who is getting removed by staff ;if he is admin
        if user_from_context.is_staff(
        ) and not is_bcros_user and admin_getting_removed:
            recipient_email = ContactLinkModel.find_by_user_id(
                self._model.user.id).contact.email
            data = {
                'accountId': self._model.org.id,
                'recipientEmail': recipient_email
            }
            publish_to_mailer(notification_type='adminRemoved',
                              org_id=self._model.org.id,
                              data=data)

        current_app.logger.debug('>update_membership')
        return self
예제 #22
0
    def create_new_business_affiliation(org_id,  # pylint: disable=too-many-arguments, too-many-locals
                                        business_identifier=None, email=None, phone=None,
                                        bearer_token: str = None):
        """Initiate a new incorporation."""
        current_app.logger.info(f'<create_affiliation org_id:{org_id} business_identifier:{business_identifier}')

        if not email and not phone:
            raise BusinessException(Error.NR_INVALID_CONTACT, None)

        # Validate if org_id is valid by calling Org Service.
        org = OrgService.find_by_org_id(org_id, allowed_roles=CLIENT_AUTH_ROLES)
        if org is None:
            raise BusinessException(Error.DATA_NOT_FOUND, None)

        entity = EntityService.find_by_business_identifier(business_identifier, skip_auth=True)
        # If entity already exists and passcode is already claimed, throw error
        if entity and entity.as_dict()['pass_code_claimed']:
            raise BusinessException(Error.NR_CONSUMED, None)

        # Call the legal-api to verify the NR details
        nr_json = Affiliation._get_nr_details(business_identifier, bearer_token)

        if nr_json:
            status = nr_json.get('state')
            nr_phone = nr_json.get('applicants').get('phoneNumber')
            nr_email = nr_json.get('applicants').get('emailAddress')

            if status not in (NRStatus.APPROVED.value, NRStatus.CONDITIONAL.value):
                raise BusinessException(Error.NR_NOT_APPROVED, None)

            # If consentFlag is not R, N or Null for a CONDITIONAL NR throw error
            if status == NRStatus.CONDITIONAL.value and nr_json.get('consentFlag', None) not in (None, 'R', 'N'):
                raise BusinessException(Error.NR_NOT_APPROVED, None)

            if (phone and phone != nr_phone) or (email and email.casefold() != nr_email.casefold()):
                raise BusinessException(Error.NR_INVALID_CONTACT, None)

            # Create an entity with the Name from NR if entity doesn't exist
            if not entity:
                # Filter the names from NR response and get the name which has status APPROVED as the name.
                # Filter the names from NR response and get the name which has status CONDITION as the name.
                nr_name_state = NRNameStatus.APPROVED.value if status == NRStatus.APPROVED.value \
                    else NRNameStatus.CONDITION.value
                name = next((name.get('name') for name in nr_json.get('names') if
                             name.get('state', None) == nr_name_state), None)

                entity = EntityService.save_entity({
                    'businessIdentifier': business_identifier,
                    'name': name,
                    'corpTypeCode': CorpType.NR.value,
                    'passCodeClaimed': True
                })
            # Create an affiliation with org
            affiliation_model = AffiliationModel(org_id=org_id, entity_id=entity.identifier)
            affiliation_model.save()
            if entity.corp_type != CorpType.RTMP.value:
                ActivityLogPublisher.publish_activity(Activity(org_id, ActivityAction.CREATE_AFFILIATION.value,
                                                               name=entity.name, id=entity.business_identifier))
            entity.set_pass_code_claimed(True)
        else:
            raise BusinessException(Error.NR_NOT_FOUND, None)

        return Affiliation(affiliation_model)
예제 #23
0
    def create_affiliation(org_id, business_identifier, pass_code=None, bearer_token=None):
        """Create an Affiliation."""
        # Validate if org_id is valid by calling Org Service.
        current_app.logger.info(f'<create_affiliation org_id:{org_id} business_identifier:{business_identifier}')
        org = OrgService.find_by_org_id(org_id, allowed_roles=ALL_ALLOWED_ROLES)
        if org is None:
            raise BusinessException(Error.DATA_NOT_FOUND, None)

        entity = EntityService.find_by_business_identifier(business_identifier, skip_auth=True)
        if entity is None:
            raise BusinessException(Error.DATA_NOT_FOUND, None)
        current_app.logger.debug('<create_affiliation entity found')
        entity_id = entity.identifier
        entity_type = entity.corp_type

        authorized = True

        if entity_type in ['SP', 'GP']:
            if not pass_code:
                authorized = False
            else:
                authorized = Affiliation._validate_firms_party(bearer_token, business_identifier, pass_code)
        else:
            # Unauthorized if the entity has been claimed
            # Leaving the code as it may come back. Removing as part of #8863
            # if entity.as_dict()['pass_code_claimed']:
            #     authorized = False
            #     already_claimed = True
            # If a passcode was provided...
            if pass_code:
                # ... and the entity has a passcode on it, check that they match
                authorized = validate_passcode(pass_code, entity.pass_code)
            # If a passcode was not provided...
            else:
                # ... check that the entity does not have a passcode protecting it
                if entity.pass_code:
                    authorized = False

        # show a different message when the passcode is already claimed
        # if already_claimed:
        #     current_app.logger.debug('<create_affiliation passcode already claimed')
        #     raise BusinessException(Error.ALREADY_CLAIMED_PASSCODE, None)

        if not authorized:
            current_app.logger.debug('<create_affiliation not authorized')
            raise BusinessException(Error.INVALID_USER_CREDENTIALS, None)

        current_app.logger.debug('<create_affiliation find affiliation')
        # Ensure this affiliation does not already exist
        affiliation = AffiliationModel.find_affiliation_by_org_and_entity_ids(org_id, entity_id)
        if affiliation is not None:
            raise BusinessException(Error.DATA_ALREADY_EXISTS, None)

        affiliation = AffiliationModel(org_id=org_id, entity_id=entity_id)
        affiliation.save()

        if entity_type not in ['SP', 'GP']:
            entity.set_pass_code_claimed(True)
        if entity_type != CorpType.RTMP.value:
            name = entity.name if len(entity.name) > 0 else entity.business_identifier
            ActivityLogPublisher.publish_activity(Activity(org_id, ActivityAction.CREATE_AFFILIATION.value,
                                                           name=name, id=entity.business_identifier))
        return Affiliation(affiliation)
예제 #24
0
def test_remove_member_removes_group_to_the_user(session, monkeypatch):  # pylint:disable=unused-argument
    """Assert that accepting an invite adds group to the user."""
    # Create a user in keycloak
    keycloak_service = KeycloakService()
    request = KeycloakScenario.create_user_request()
    keycloak_service.add_user(request, return_if_exists=True)
    kc_user = keycloak_service.get_user_by_username(request.user_name)
    user = factory_user_model(TestUserInfo.get_user_with_kc_guid(kc_guid=kc_user.id))

    # Patch token info
    def token_info():  # pylint: disable=unused-argument; mocks of library methods
        return {
            'sub': str(kc_user.id),
            'username': '******',
            'realm_access': {
                'roles': [
                    'edit'
                ]
            },
            'product_code': ProductCode.BUSINESS.value
        }

    monkeypatch.setattr('auth_api.utils.user_context._get_token_info', token_info)
    org = OrgService.create_org(TestOrgInfo.org1, user_id=user.id)
    # Create another user
    request = KeycloakScenario.create_user_request()
    keycloak_service.add_user(request, return_if_exists=True)
    kc_user2 = keycloak_service.get_user_by_username(request.user_name)
    user2 = factory_user_model(TestUserInfo.get_user_with_kc_guid(kc_guid=kc_user2.id))

    # Add a membership to the user for the org created
    factory_membership_model(user2.id, org.as_dict().get('id'), member_type='COORDINATOR', member_status=4)

    # Add a product to org
    factory_product_model(org.as_dict().get('id'), product_code=ProductCode.BUSINESS.value)

    # Find the membership and update to ACTIVE
    membership = MembershipService.get_membership_for_org_and_user(org.as_dict().get('id'), user2.id)
    active_membership_status = MembershipStatusCodeModel.get_membership_status_by_code(Status.ACTIVE.name)
    updated_fields = {'membership_status': active_membership_status}
    with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
        MembershipService(membership).update_membership(updated_fields=updated_fields, token_info=token_info())
        mock_alp.assert_called_with(Activity(action=ActivityAction.APPROVE_TEAM_MEMBER.value,
                                             org_id=ANY, name=ANY, id=ANY,
                                             value=ANY))
    user_groups = keycloak_service.get_user_groups(user_id=kc_user2.id)
    groups = []
    for group in user_groups:
        groups.append(group.get('name'))
    assert GROUP_ACCOUNT_HOLDERS in groups

    # Deactivate Membership
    with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
        MembershipService(membership).deactivate_membership(token_info=token_info())
        mock_alp.assert_called_with(Activity(action=ActivityAction.REMOVE_TEAM_MEMBER.value,
                                             org_id=ANY, name=ANY, id=ANY,
                                             value=ANY))

    # ACTIVE
    active_membership_status = MembershipStatusCodeModel.get_membership_status_by_code(Status.ACTIVE.name)
    updated_fields = {'membership_status': active_membership_status}
    MembershipService(membership).update_membership(updated_fields=updated_fields, token_info=token_info())

    # Find the membership and update to INACTIVE
    active_membership_status = MembershipStatusCodeModel.get_membership_status_by_code(Status.INACTIVE.name)
    updated_fields = {'membership_status': active_membership_status}
    with patch.object(ActivityLogPublisher, 'publish_activity', return_value=None) as mock_alp:
        MembershipService(membership).update_membership(updated_fields=updated_fields, token_info=token_info())
        mock_alp.assert_called_with(Activity(action=ActivityAction.REMOVE_TEAM_MEMBER.value,
                                             org_id=ANY, name=ANY, id=ANY,
                                             value=ANY))

    user_groups = keycloak_service.get_user_groups(user_id=kc_user2.id)
    groups = []
    for group in user_groups:
        groups.append(group.get('name'))
    assert GROUP_ACCOUNT_HOLDERS not in groups

    MembershipService(membership).deactivate_membership()
예제 #25
0
        if response.status_code == http_status.HTTP_200_OK:
            payment_account_status = PaymentAccountStatus.CREATED
        elif response.status_code == http_status.HTTP_202_ACCEPTED:
            payment_account_status = PaymentAccountStatus.PENDING
        else:
            payment_account_status = PaymentAccountStatus.FAILED

        if payment_account_status != PaymentAccountStatus.FAILED and payment_method:
            payment_method_description = PaymentMethod(
                payment_method).name if payment_method in [
                    item.value for item in PaymentMethod
                ] else ''
            ActivityLogPublisher.publish_activity(
                Activity(org_model.id,
                         ActivityAction.PAYMENT_INFO_CHANGE.value,
                         name=org_model.name,
                         value=payment_method_description))
        return payment_account_status

    @staticmethod
    def _validate_and_raise_error(org_info: dict):
        """Execute the validators in chain and raise error or return."""
        validators = [
            account_limit_validate, access_type_validate,
            duplicate_org_name_validate
        ]
        arg_dict = {
            'accessType': org_info.get('accessType', None),
            'name': org_info.get('name'),
            'branch_name': org_info.get('branchName')
        }
예제 #26
0
    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']
        membership_type = invitation_info['membership'][0]['membershipType']
        token_email_query_params: Dict = {}
        # 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
            account_login_options = AccountLoginOptionsModel.find_active_by_org_id(
                org.id)
            mandatory_login_source = getattr(
                account_login_options, 'login_source',
                default_login_option_based_on_accesstype)

            if membership_type == ADMIN \
                    and mandatory_login_source == LoginSource.BCEID.value:
                token_email_query_params['affidavit'] = 'true'

        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(),
                                   f'{invitation_origin}/{context_path}',
                                   mandatory_login_source,
                                   org_status=org.status_code,
                                   query_params=token_email_query_params)
        ActivityLogPublisher.publish_activity(
            Activity(org_id,
                     ActivityAction.INVITE_TEAM_MEMBER.value,
                     name=invitation_info['recipientEmail'],
                     value=membership_type,
                     id=invitation.id))
        # 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)
예제 #27
0
    def create_product_subscription(
            org_id,
            subscription_data: Dict[str, Any],  # pylint: disable=too-many-locals
            is_new_transaction: bool = True,
            skip_auth=False):
        """Create product subscription for the user.

        create product subscription first
        create the product role next if roles are given
        """
        org: OrgModel = OrgModel.find_by_org_id(org_id)
        if not org:
            raise BusinessException(Error.DATA_NOT_FOUND, None)
        # Check authorization for the user
        if not skip_auth:
            check_auth(one_of_roles=(*CLIENT_ADMIN_ROLES, STAFF),
                       org_id=org_id)

        subscriptions_list = subscription_data.get('subscriptions')
        for subscription in subscriptions_list:
            product_code = subscription.get('productCode')
            existing_product_subscriptions = ProductSubscriptionModel.find_by_org_id_product_code(
                org_id, product_code)
            if existing_product_subscriptions:
                raise BusinessException(Error.PRODUCT_SUBSCRIPTION_EXISTS,
                                        None)
            product_model: ProductCodeModel = ProductCodeModel.find_by_code(
                product_code)
            if product_model:
                # Check if product needs premium account, if yes skip and continue.
                if product_model.premium_only and org.type_code != OrgType.PREMIUM.value:
                    continue

                subscription_status = Product.find_subscription_status(
                    org, product_model)
                product_subscription = ProductSubscriptionModel(
                    org_id=org_id,
                    product_code=product_code,
                    status_code=subscription_status).flush()
                if subscription_status == ProductSubscriptionStatus.ACTIVE.value:
                    ActivityLogPublisher.publish_activity(
                        Activity(org_id,
                                 ActivityAction.ADD_PRODUCT_AND_SERVICE.value,
                                 name=product_model.description))

                # If there is a linked product, add subscription to that too.
                # This is to handle cases where Names and Business Registry is combined together.
                if product_model.linked_product_code:
                    ProductSubscriptionModel(
                        org_id=org_id,
                        product_code=product_model.linked_product_code,
                        status_code=subscription_status).flush()
                    if subscription_status == ProductSubscriptionStatus.ACTIVE.value:
                        ActivityLogPublisher.publish_activity(
                            Activity(
                                org_id,
                                ActivityAction.ADD_PRODUCT_AND_SERVICE.value,
                                name=product_model.description))

                # create a staff review task for this product subscription if pending status
                if subscription_status == ProductSubscriptionStatus.PENDING_STAFF_REVIEW.value:
                    user = UserModel.find_by_jwt_token()
                    Product._create_review_task(org, product_model,
                                                product_subscription, user)

            else:
                raise BusinessException(Error.DATA_NOT_FOUND, None)

        if is_new_transaction:  # Commit the transaction if it's a new transaction
            db.session.commit()

        return Product.get_all_product_subscription(org_id=org_id,
                                                    skip_auth=True)