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'))
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
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)
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))
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()
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
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)
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'))
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 ')
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))
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)))
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)))
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']
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
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'
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.'
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
def _create_new_user_and_membership(db_username, kc_user, membership, org_id): user_model: UserModel = UserModel(username=db_username, is_terms_of_use_accepted=False, status=Status.ACTIVE.value, type=Role.ANONYMOUS_USER.name, email=membership.get('email', None), firstname=kc_user.first_name, lastname=kc_user.last_name, login_source=LoginSource.BCROS.value) user_model.flush() membership_model = MembershipModel(org_id=org_id, user_id=user_model.id, membership_type_code=membership['membershipType'], membership_type_status=Status.ACTIVE.value) membership_model.flush() name = {'first_name': user_model.firstname, 'last_name': user_model.lastname} ActivityLogPublisher.publish_activity(Activity(org_id, ActivityAction.APPROVE_TEAM_MEMBER.value, name=json.dumps(name), value=membership['membershipType'], id=user_model.id)) return user_model
def 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
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))
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
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)
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)
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()
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') }
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)
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)