def search_orgs(search: OrgSearch): # pylint: disable=too-many-locals """Search for orgs based on input parameters.""" orgs_result = { 'orgs': [], 'page': search.page, 'limit': search.limit, 'total': 0 } include_invitations: bool = False search.access_type, is_staff_admin = Org.refine_access_type( search.access_type) if search.statuses and OrgStatus.PENDING_ACTIVATION.value in search.statuses: # only staff admin can see director search accounts if not is_staff_admin: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) org_models, orgs_result[ 'total'] = OrgModel.search_pending_activation_orgs( name=search.name) include_invitations = True else: org_models, orgs_result['total'] = OrgModel.search_org(search) for org in org_models: orgs_result['orgs'].append({ **Org(org).as_dict(), 'contacts': [ ContactSchema(exclude=('links', )).dump( org.contacts[0].contact, many=False) ] if org.contacts else [], 'invitations': [ InvitationSchema(exclude=('membership', )).dump( org.invitations[0].invitation, many=False) ] if include_invitations and org.invitations else [], }) return orgs_result
def search_orgs(**kwargs): # pylint: disable=too-many-locals """Search for orgs based on input parameters.""" orgs = {'orgs': []} if kwargs.get('business_identifier', None): affiliation: AffiliationModel = AffiliationModel. \ find_affiliations_by_business_identifier(kwargs.get('business_identifier')) if affiliation: orgs['orgs'].append( Org(OrgModel.find_by_org_id(affiliation.org_id)).as_dict()) else: include_invitations: bool = False page: int = int(kwargs.get('page')) limit: int = int(kwargs.get('limit')) statuses: str = kwargs.get('statuses', None) name: str = kwargs.get('name', None) # https://github.com/bcgov/entity/issues/4786 access_type, is_staff_admin = Org.refine_access_type( kwargs.get('access_type', None), kwargs.get('token', None)) search_args = (access_type, name, statuses, kwargs.get('bcol_account_id', None), page, limit) if statuses and OrgStatus.PENDING_ACTIVATION.value in statuses: # only staff admin can see director search accounts # https://github.com/bcgov/entity/issues/4786 if not is_staff_admin: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) org_models, total = OrgModel.search_pending_activation_orgs( name) include_invitations = True else: org_models, total = OrgModel.search_org(*search_args) for org in org_models: org_dict = Org(org).as_dict() org_dict['contacts'] = [] org_dict['invitations'] = [] if org.contacts: org_dict['contacts'].append( ContactSchema(exclude=('links', )).dump( org.contacts[0].contact, many=False)) if include_invitations and org.invitations: org_dict['invitations'].append( InvitationSchema(exclude=('membership', )).dump( org.invitations[0].invitation, many=False)) orgs['orgs'].append(org_dict) orgs['total'] = total orgs['page'] = page orgs['limit'] = limit return orgs
def factory_org_model(org_info: dict = TestOrgInfo.org1, org_type_info: dict = None, org_status_info: dict = None, user_id=None, bcol_info: dict = TestBCOLInfo.bcol1): """Produce a templated org model.""" org_type = OrgTypeModel.get_default_type() if org_type_info and org_type_info['code'] != TestOrgTypeInfo.implicit[ 'code']: org_type = OrgTypeModel(code=org_type_info['code'], description=org_type_info['desc']) org_type.save() if org_status_info is not None: org_status = OrgStatusModel(code=org_status_info['code'], description=org_status_info['desc']) org_status.save() else: org_status = OrgStatusModel.get_default_status() org = OrgModel(name=org_info['name']) org.org_type = org_type org.access_type = org_info.get('accessType', '') org.org_status = org_status org.created_by_id = user_id org.bcol_account_id = bcol_info.get('bcol_account_id', '') org.bcol_user_id = bcol_info.get('bcol_user_id', '') org.save() return org
def test_find_similar_org_by_name(session): # pylint:disable=unused-argument """Assert that an Org can retrieved by its name.""" org = factory_org_model(name='My Test Org', session=session) session.add(org) session.commit() found_org = OrgModel.find_similar_org_by_name(org.name) assert found_org assert found_org.name == org.name found_org = OrgModel.find_similar_org_by_name('Test Or') assert found_org is None
def factory_org_model(org_info: dict = TestOrgInfo.org1, org_type_info: dict = TestOrgTypeInfo.test_type, org_status_info: dict = TestOrgStatusInfo.test_status, payment_type_info: dict = TestPaymentTypeInfo.test_type, user_id=None): """Produce a templated org model.""" org_type = OrgTypeModel.get_default_type() if org_type_info['code'] != TestOrgTypeInfo.implicit['code']: org_type = OrgTypeModel(code=org_type_info['code'], desc=org_type_info['desc']) org_type.save() if org_status_info: org_status = OrgStatusModel(code=org_status_info['code'], desc=org_status_info['desc']) org_status.save() else: org_status = OrgStatusModel.get_default_status() if payment_type_info: preferred_payment = PaymentTypeModel(code=payment_type_info['code'], desc=payment_type_info['desc']) preferred_payment.save() else: preferred_payment = PaymentTypeModel.get_default_payment_type() org = OrgModel(name=org_info['name']) org.org_type = org_type org.access_type = org_info.get('accessType', '') org.org_status = org_status org.preferred_payment = preferred_payment org.created_by_id = user_id org.save() return org
def search_orgs(**kwargs): """Search for orgs based on input parameters.""" orgs = {'orgs': []} if kwargs.get('business_identifier', None): affiliation: AffiliationModel = AffiliationModel. \ find_affiliations_by_business_identifier(kwargs.get('business_identifier')) if affiliation: orgs['orgs'].append( Org(OrgModel.find_by_org_id(affiliation.org_id)).as_dict()) elif kwargs.get('org_type', None): org_models = OrgModel.find_by_org_access_type( kwargs.get('org_type')) for org in org_models: orgs['orgs'].append(Org(org).as_dict()) return orgs
def reset_password_for_anon_user(user_info: dict, user_name, token_info: Dict = None): """Reset the password of the user.""" user = UserModel.find_by_username(user_name) membership = MembershipModel.find_membership_by_userid(user.id) org_id = membership.org_id org = OrgModel.find_by_org_id(org_id) if not org or org.access_type != AccessType.ANONYMOUS.value: raise BusinessException(Error.INVALID_INPUT, None) check_auth(org_id=org_id, token_info=token_info, one_of_roles=(ADMIN, STAFF)) update_user_request = KeycloakUser() update_user_request.user_name = user_name.replace( IdpHint.BCROS.value + '/', '') update_user_request.password = user_info['password'] update_user_request.update_password_on_login() try: kc_user = KeycloakService.update_user(update_user_request) except HTTPError as err: current_app.logger.error('update_user in keycloak failed {}', err) raise BusinessException(Error.UNDEFINED_ERROR, err) return kc_user
def create_product_subscription(org_id, subscription_data: Dict[str, Any], is_new_transaction: bool = True): """Create product subscription for the user. create product subscription first create the product role next if roles are given """ org = OrgModel.find_by_org_id(org_id) if not org: raise BusinessException(Error.DATA_NOT_FOUND, None) subscriptions_list = subscription_data.get('subscriptions') # just used for returning all the models.. not ideal.. # todo remove this and may be return the subscriptions from db subscriptions_model_list = [] for subscription in subscriptions_list: product_code = subscription.get('productCode') product = ProductCodeModel.find_by_code(product_code) if product: product_subscription = ProductSubscriptionModel( org_id=org_id, product_code=product_code).flush() subscriptions_model_list.append(product_subscription) else: raise BusinessException(Error.DATA_NOT_FOUND, None) Product._create_roles(is_new_transaction, product_code, product_subscription, subscription) # TODO return something better/useful.may be return the whole model from db return subscriptions_model_list
def update_org(self, org_info, bearer_token: str = None): """Update the passed organization with the new info.""" current_app.logger.debug('<update_org ') if self._model.type_code != OrgType.PREMIUM.value: existing_similar__org = OrgModel.find_similar_org_by_name( org_info['name'], self._model.id) if existing_similar__org is not None: raise BusinessException(Error.DATA_CONFLICT, None) bcol_credential = org_info.pop('bcOnlineCredential', None) mailing_address = org_info.pop('mailingAddress', None) # If the account is created using BCOL credential, verify its valid bc online account # If it's a valid account disable the current one and add a new one if bcol_credential: self.add_bcol_to_account(bcol_credential, bearer_token, org_info) # Update mailing address if mailing_address: contact = self._model.contacts[0].contact contact.update_from_dict(**camelback2snake(mailing_address)) contact.save() if self._model.type_code != OrgType.PREMIUM.value: self._model.update_org_from_dict(camelback2snake(org_info)) current_app.logger.debug('>update_org ') return self
async def process_event(event_message, flask_app): """Render the org status.""" if not flask_app: raise QueueException('Flask App not available.') with flask_app.app_context(): message_type = event_message.get('type', None) data = event_message.get('data') org_id = data.get('accountId') org: OrgModel = OrgModel.find_by_org_id(org_id) if org is None: logger.error('Unknown org for orgid %s', org_id) return if message_type == LOCK_ACCOUNT_MESSAGE_TYPE: org.status_code = OrgStatus.NSF_SUSPENDED.value org.suspended_on = datetime.now() await publish_mailer_events(LOCK_ACCOUNT_MESSAGE_TYPE, org_id) elif message_type == UNLOCK_ACCOUNT_MESSAGE_TYPE: org.status_code = OrgStatus.ACTIVE.value await publish_mailer_events(UNLOCK_ACCOUNT_MESSAGE_TYPE, org_id) else: logger.error('Unknown Message Type : %s', message_type) return org.flush() db.session.commit()
def approve_or_reject(org_id: int, is_approved: bool, token_info: Dict, origin_url: str = None): """Mark the affidavit as approved or rejected.""" current_app.logger.debug('<find_affidavit_by_org_id ') # Get the org and check what's the current status org: OrgModel = OrgModel.find_by_org_id(org_id) # Current User user: UserModel = UserModel.find_by_jwt_token(token=token_info) # If status is PENDING_AFFIDAVIT_REVIEW handle affidavit approve process, else raise error if org.status_code == OrgStatus.PENDING_AFFIDAVIT_REVIEW.value: AffidavitService.approve_or_reject(org_id, is_approved, user) else: raise BusinessException(Error.INVALID_INPUT, None) if is_approved: org.status_code = OrgStatus.ACTIVE.value else: org.status_code = OrgStatus.REJECTED.value org.decision_made_by = user.username org.decision_made_on = datetime.now() # TODO Publish to activity stream org.save() # Find admin email address admin_email = ContactLinkModel.find_by_user_id(org.members[0].user.id).contact.email Org.send_approved_rejected_notification(admin_email, org.name, org.status_code, origin_url) current_app.logger.debug('>find_affidavit_by_org_id ') return Org(org)
def change_org_status(org_id: int, status_code, suspension_reason_code, token_info: Dict = None): """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 ') org_model: OrgModel = OrgModel.find_by_org_id(org_id) user: UserModel = UserModel.find_by_jwt_token(token=token_info) current_app.logger.debug('<setting org status to ') 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() current_app.logger.debug('change_org_status>') return Org(org_model)
def update_org( self, org_info, token_info: Dict = None, # pylint: disable=too-many-locals bearer_token: str = None): """Update the passed organization with the new info.""" current_app.logger.debug('<update_org ') has_org_updates: bool = False # update the org table if this variable is set true has_status_changing: bool = False org_model: OrgModel = self._model # to enforce necessary details for govm account creation is_govm_account = org_model.access_type == AccessType.GOVM.value is_govm_account_creation = is_govm_account and \ org_model.status_code == OrgStatus.PENDING_INVITE_ACCEPT.value # govm name is not being updated now is_name_getting_updated = 'name' in org_info and not is_govm_account if is_name_getting_updated: existing_similar__org = OrgModel.find_similar_org_by_name( org_info['name'], self._model.id) if existing_similar__org is not None: raise BusinessException(Error.DATA_CONFLICT, None) has_org_updates = True # If the account is created using BCOL credential, verify its valid bc online account # If it's a valid account disable the current one and add a new one if bcol_credential := org_info.pop('bcOnlineCredential', None): bcol_response = Org.get_bcol_details(bcol_credential, bearer_token, self._model.id).json() Org._map_response_to_org(bcol_response, org_info) has_org_updates = True
async def test_update_internal_payment(app, session, stan_server, event_loop, client_id, events_stan, future): """Assert that the update internal payment records works.""" # Call back for the subscription from events_listener.worker import cb_subscription_handler events_subject = 'test_subject' events_queue = 'test_queue' events_durable_name = 'test_durable' org = factory_org_model() id = org.id # register the handler to test it await subscribe_to_queue(events_stan, events_subject, events_queue, events_durable_name, cb_subscription_handler) # add an event to queue await helper_add_event_to_queue(events_stan, events_subject, org_id=org.id, action='lock') # Get the internal account and invoice and assert that the identifier is new identifier new_org = OrgModel.find_by_org_id(id) assert new_org.status_code == 'NSF_SUSPENDED'
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 process(org_id, recipients, template_name, subject, **kwargs) -> dict: """Build the email for Account notification.""" logger.debug('account notification: %s', org_id) org: OrgModel = OrgModel.find_by_id(org_id) # fill in template filled_template = generate_template( current_app.config.get('TEMPLATE_PATH'), template_name) current_time = datetime.now() # render template with vars from email msg jnja_template = Template(filled_template, autoescape=True) jinja_kwargs = { 'account_name': org.name, 'url': get_login_url(), 'today': current_time.strftime('%m-%d-%Y'), **kwargs } html_out = jnja_template.render(jinja_kwargs) return { 'recipients': recipients, 'content': { 'subject': subject, 'body': html_out, 'attachments': [] } }
def delete_org( org_id, token_info: Dict = None, ): """Soft-Deletes an Org. It should not be deletable if there are members or business associated with the org """ # Check authorization for the user current_app.logger.debug('<org Inactivated') check_auth(token_info, one_of_roles=OWNER, org_id=org_id) org: OrgModel = OrgModel.find_by_org_id(org_id) if not org: raise BusinessException(Error.DATA_NOT_FOUND, None) count_members = len([ member for member in org.members if member.status in VALID_STATUSES ]) if count_members > 1 or len(org.affiliated_entities) >= 1: raise BusinessException(Error.ORG_CANNOT_BE_DISSOLVED, None) org.delete() # Remove user from thr group if the user doesn't have any other orgs membership user = UserModel.find_by_jwt_token(token=token_info) if len(MembershipModel.find_orgs_for_user(user.id)) == 0: KeycloakService.remove_from_account_holders_group( user.keycloak_guid) current_app.logger.debug('org Inactivated>')
def update_org( self, org_info, token_info: Dict = None, # pylint: disable=too-many-locals bearer_token: str = None): """Update the passed organization with the new info.""" current_app.logger.debug('<update_org ') has_org_updates: bool = False # update the org table if this variable is set true is_name_getting_updated = 'name' in org_info if is_name_getting_updated: existing_similar__org = OrgModel.find_similar_org_by_name( org_info['name'], self._model.id) if existing_similar__org is not None: raise BusinessException(Error.DATA_CONFLICT, None) has_org_updates = True # If the account is created using BCOL credential, verify its valid bc online account # If it's a valid account disable the current one and add a new one if bcol_credential := org_info.pop('bcOnlineCredential', None): bcol_response = Org.get_bcol_details(bcol_credential, bearer_token, self._model.id).json() Org._map_response_to_org(bcol_response, org_info) has_org_updates = True
def create_product_subscription(org_id, subscription_data: Tuple[Dict[str, Any]]): """Create product subscription for the user. create product subscription first create the product role next if roles are given """ org = OrgModel.find_by_org_id(org_id) if not org: raise BusinessException(Error.DATA_NOT_FOUND, None) subscriptions_list = subscription_data.get('subscriptions') # just used for returning all the models.. not ideal.. # todo remove this and may be return the subscriptions from db subscriptions_model_list = [] for subscription in subscriptions_list: product_code = subscription.get('productCode') product = ProductCodeModel.find_by_code(product_code) if product: product_subscription = ProductSubscriptionModel( org_id=org_id, product_code=product_code).save() subscriptions_model_list.append(product_subscription) else: raise BusinessException(Error.DATA_NOT_FOUND, None) if subscription.get('product_roles') is not None: for role in subscription.get('productRoles'): product_role_code = ProductRoleCodeModel.find_by_code_and_product_code( role, product_code) if product_role_code: ProductSubscriptionRoleModel( product_subscription_id=product_subscription.id, product_role_id=product_role_code.id).save() # TODO return something better/useful.may be return the whole model from db return subscriptions_model_list
def _create_staff_review_task(org: OrgModel, user: UserModel): org.status_code = OrgStatus.PENDING_STAFF_REVIEW.value # create a staff review task for this account task_type = TaskTypePrefix.GOVN_REVIEW.value if org.access_type == AccessType.GOVN.value \ else TaskTypePrefix.NEW_ACCOUNT_STAFF_REVIEW.value action = TaskAction.AFFIDAVIT_REVIEW.value if user.login_source == LoginSource.BCEID.value \ else TaskAction.ACCOUNT_REVIEW.value task_info = { 'name': org.name, 'relationshipId': org.id, 'relatedTo': user.id, 'dateSubmitted': datetime.today(), 'relationshipType': TaskRelationshipType.ORG.value, 'type': task_type, 'action': action, 'status': TaskStatus.OPEN.value, 'relationship_status': TaskRelationshipStatus.PENDING_STAFF_REVIEW.value } TaskService.create_task(task_info=task_info, do_commit=False) Org.send_staff_review_account_reminder(relationship_id=org.id)
def test_create_org_by_verified_bceid_user(session, keycloak_mock, monkeypatch): # pylint:disable=unused-argument """Assert that an Org can be created.""" # Steps # 1. Create a pending affidavit # 2. Create org # 3. Approve Org, which will mark the affidavit as approved # 4. Same user create new org, which should be ACTIVE. user = factory_user_model_with_contact(user_info=TestUserInfo.user_bceid_tester) token_info = TestJwtClaims.get_test_user(sub=user.keycloak_guid, source=LoginSource.BCEID.value) patch_token_info(token_info, monkeypatch) affidavit_info = TestAffidavit.get_test_affidavit_with_contact() AffidavitService.create_affidavit(affidavit_info=affidavit_info) org = OrgService.create_org(TestOrgInfo.org_with_mailing_address(), user_id=user.id) org_dict = org.as_dict() assert org_dict['org_status'] == OrgStatus.PENDING_STAFF_REVIEW.value task_model = TaskModel.find_by_task_for_account(org_dict['id'], status=TaskStatus.OPEN.value) assert task_model.relationship_id == org_dict['id'] assert task_model.action == TaskAction.AFFIDAVIT_REVIEW.value task_info = { 'status': TaskStatus.OPEN.value, 'relationshipStatus': TaskRelationshipStatus.ACTIVE.value, } TaskService.update_task(TaskService(task_model), task_info) org_result: OrgModel = OrgModel.find_by_org_id(org_dict['id']) assert org_result.status_code == OrgStatus.ACTIVE.value
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 create_org(org_info: dict, user_id): """Create a new organization.""" current_app.logger.debug('<create_org ') existing_similar__org = OrgModel.find_similar_org_by_name(org_info['name']) if existing_similar__org is not None: raise BusinessException(Error.DATA_CONFLICT, None) org = OrgModel.create_from_dict(camelback2snake(org_info)) org.save() current_app.logger.info(f'<created_org org_id:{org.id}') # create the membership record for this user membership = MembershipModel(org_id=org.id, user_id=user_id, membership_type_code='OWNER', membership_type_status=Status.ACTIVE.value) membership.save() return Org(org)
def get_all_product_subscription(org_id, skip_auth=False): """Get a list of all products with their subscription details.""" org = 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) product_subscriptions: List[ ProductSubscriptionModel] = ProductSubscriptionModel.find_by_org_id( org_id) subscriptions_dict = { x.product_code: x.status_code for x in product_subscriptions } products = Product.get_products(include_hidden=False) for product in products: product['subscriptionStatus'] = subscriptions_dict.get( product.get('code'), ProductSubscriptionStatus.NOT_SUBSCRIBED.value) return products
def test_create_from_dict(session): # pylint:disable=unused-argument """Assert that an Org can be created from schema.""" org_info = {'name': 'My Test Org'} result_org = OrgModel.create_from_dict(org_info).save() assert result_org.id is not None
def test_org_create_from_dictionary(session): # pylint:disable=unused-argument """Assert that an Org can be created from a dictionary.""" org_info = {'name': 'My Test Org'} org_model = OrgModel.create_from_dict(org_info).save() assert org_model assert org_model.id assert org_model.name == org_info['name']
def test_count_org_from_dict(session): # pylint:disable=unused-argument """Assert that an Org can be updated from a dictionary.""" user = factory_user_model() org = factory_org_model(name='My Test Org', session=session) org.created_by_id = user.id session.add(org) session.commit() assert OrgModel.get_count_of_org_created_by_user_id(user.id) == 1
def bcol_account_link_check(bcol_account_id, org_id=None): """Validate the BCOL id is linked or not. If already linked, return True.""" if current_app.config.get('BCOL_ACCOUNT_LINK_CHECK'): org = OrgModel.find_by_bcol_id(bcol_account_id) if org and org.id != org_id: # check if already taken up by different org return True return False
def test_org_find_by_id(session): # pylint:disable=unused-argument """Assert that an Org can retrieved by its id.""" org = factory_org_model(name='My Test Org', session=session) session.add(org) session.commit() found_org = OrgModel.find_by_org_id(org.id) assert found_org assert found_org.name == org.name
def test_org_find_by_name_inactive(session): # pylint:disable=unused-argument """Assert that an inactive Org can not be retrieved by its name.""" org = factory_org_model(name='My Test Org', session=session) session.add(org) session.commit() org.delete() found_org = OrgModel.find_by_org_name(org.name) assert len(found_org) == 0