def find_by_jwt_token(cls, token: dict = None): """Find user from database by user token.""" if not token: return None user_model = UserModel.find_by_jwt_token(token) if not user_model: raise BusinessException(Error.DATA_NOT_FOUND, None) is_anonymous_user = token.get('accessType', None) == AccessType.ANONYMOUS.value # If terms accepted , double check if there is a new TOS in place. If so, update the flag to false. if user_model.is_terms_of_use_accepted: document_type = DocumentType.TERMS_OF_USE_DIRECTOR_SEARCH.value if is_anonymous_user \ else DocumentType.TERMS_OF_USE.value # get the digit version of the terms of service..ie d1 gives 1 ; d2 gives 2..for proper comparison latest_version = util.digitify( DocumentService.find_latest_version_by_type(document_type)) current_version = util.digitify( user_model.terms_of_use_accepted_version) if latest_version > current_version: user_model.is_terms_of_use_accepted = False return User(user_model)
def test_delete_user_where_org_has_affiliations(session, auth_mock): # pylint:disable=unused-argument """Assert that a user can be deleted.""" user_model = factory_user_model(user_info=TestUserInfo.user_test) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model contact_link.commit() org = OrgService.create_org(TestOrgInfo.org1, user_id=user_model.id) org_dictionary = org.as_dict() org_id = org_dictionary['id'] entity = factory_entity_model(entity_info=TestEntityInfo.entity_lear_mock) affiliation = AffiliationModel(org_id=org_id, entity_id=entity.id) affiliation.save() with pytest.raises(BusinessException) as exception: UserService.delete_user(TestJwtClaims.user_test) assert exception.code == Error.DELETE_FAILED_ONLY_OWNER updated_user = UserModel.find_by_jwt_token(TestJwtClaims.user_test) assert len(updated_user.contacts) == 1 user_orgs = MembershipModel.find_orgs_for_user(updated_user.id) for org in user_orgs: assert org.status_code == 'ACTIVE'
def _create_gov_account_task(org_model: OrgModel, token_info: dict, origin_url: str): # create a staff review task for this account task_type = TaskTypePrefix.GOVM_REVIEW.value user: UserModel = UserModel.find_by_jwt_token(token=token_info) task_info = { 'name': org_model.name, 'relationshipId': org_model.id, 'relatedTo': user.id, 'dateSubmitted': datetime.today(), 'relationshipType': TaskRelationshipType.ORG.value, 'type': task_type, 'status': TaskStatus.OPEN.value, 'relationship_status': TaskRelationshipStatus.PENDING_STAFF_REVIEW.value } TaskService.create_task(task_info=task_info, user=user, do_commit=False, origin_url=origin_url)
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 add_contact(token, contact_info: dict, throw_error_for_duplicates: bool = True): """Add contact information for an existing user.""" current_app.logger.debug('add_contact') user = UserModel.find_by_jwt_token(token) if user is None: raise BusinessException(Error.DATA_NOT_FOUND, None) # check for existing contact (we only want one contact per user) contact_link = ContactLinkModel.find_by_user_id(user.id) if contact_link is not None: if not throw_error_for_duplicates: # TODO may be throw whole object return None raise BusinessException(Error.DATA_ALREADY_EXISTS, None) contact = ContactModel(**camelback2snake(contact_info)) contact = contact.flush() contact_link = ContactLinkModel() contact_link.user = user contact_link.contact = contact contact_link.save() return ContactService(contact)
def _handle_bceid_status_and_notification(org, origin_url, token_info): org.status_code = OrgStatus.PENDING_STAFF_REVIEW.value user = UserModel.find_by_jwt_token(token=token_info) # Org.send_staff_review_account_reminder(user, org.id, origin_url) # create a staff review task for this account task_type = TaskTypePrefix.NEW_ACCOUNT_STAFF_REVIEW.value task_info = { 'name': org.name, 'relationshipId': org.id, 'relatedTo': user.id, 'dateSubmitted': datetime.today(), 'relationshipType': TaskRelationshipType.ORG.value, 'type': task_type, 'status': TaskStatus.OPEN.value, 'relationship_status': TaskRelationshipStatus.PENDING_STAFF_REVIEW.value } TaskService.create_task(task_info=task_info, user=user, origin_url=origin_url, do_commit=False)
def test_user_find_by_jwt_token(session): """Assert that a User can be stored in the service. Start with a blank database. """ user = User(username='******', roles='{edit, uma_authorization, staff}', keycloak_guid='1b20db59-19a0-4727-affe-c6f64309fd04') session.add(user) session.commit() token = { 'preferred_username': '******', 'sub': '1b20db59-19a0-4727-affe-c6f64309fd04', 'realm_access': { 'roles': [ 'edit', 'uma_authorization', 'basic' ] } } u = User.find_by_jwt_token(token) assert u.id is not None
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 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 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 find_by_jwt_token(cls, **kwargs): """Find user from database by user token.""" user_from_context: UserContext = kwargs['user_context'] if not user_from_context.token_info: return None user_model = UserModel.find_by_jwt_token() if not user_model: if kwargs.get('silent_mode', False): return None raise BusinessException(Error.DATA_NOT_FOUND, None) is_anonymous_user = user_from_context.token_info.get('accessType', None) == AccessType.ANONYMOUS.value is_govm_user = user_from_context.login_source == LoginSource.STAFF.value # If terms accepted , double check if there is a new TOS in place. If so, update the flag to false. if user_model.is_terms_of_use_accepted: if is_anonymous_user: document_type = DocumentType.TERMS_OF_USE_DIRECTOR_SEARCH.value elif is_govm_user: document_type = DocumentType.TERMS_OF_USE_GOVM.value else: document_type = DocumentType.TERMS_OF_USE.value # get the digit version of the terms of service..ie d1 gives 1 ; d2 gives 2..for proper comparison latest_version = util.digitify(DocumentService.find_latest_version_by_type(document_type)) current_version = util.digitify(user_model.terms_of_use_accepted_version) if latest_version > current_version: user_model.is_terms_of_use_accepted = False return User(user_model)
def delete_contact(token): """Delete the contact for an existing user.""" current_app.logger.info('delete_contact') user = UserModel.find_by_jwt_token(token) if not user or not user.contacts: raise BusinessException(Error.DATA_NOT_FOUND, None) User.__delete_contact(user) return User(user)
def find_by_jwt_token(cls, token: dict = None): """Find user from database by user token.""" if not token: return None user = UserModel.find_by_jwt_token(token) if not user: raise BusinessException(Error.DATA_NOT_FOUND, None) return User(user)
def get_contacts(token): """Get the contact associated with this user.""" current_app.logger.debug('get_contact') user = UserModel.find_by_jwt_token(token) if user is None: raise BusinessException(Error.DATA_NOT_FOUND, None) collection = [] for contact_link in user.contacts: collection.append(ContactService(contact_link.contact).as_dict()) return {'contacts': collection}
def find_by_jwt_token(cls, token: dict = None): if not token: return None user_dao = UserModel.find_by_jwt_token(token) if not user_dao: return None user = User() user._dao = user_dao # pylint: disable=protected-access return user
def delete_contact(): """Delete the contact for an existing user.""" current_app.logger.info('delete_contact') user = UserModel.find_by_jwt_token() if not user or not user.contacts: raise BusinessException(Error.DATA_NOT_FOUND, None) deleted_contact = User.__delete_contact(user) if deleted_contact: return ContactService(deleted_contact) return None
def delete_anonymous_user(user_name, token_info: Dict = None): """ Delete User Profile. 1) check if the token user is admin/owner of the current user 2) disable the user from kc 3) set user status as INACTIVE 4) set membership as inactive """ admin_user: UserModel = UserModel.find_by_jwt_token(token_info) if not admin_user: raise BusinessException(Error.DATA_NOT_FOUND, None) if admin_user.status == UserStatus.INACTIVE.value: raise BusinessException(Error.DELETE_FAILED_INACTIVE_USER, None) # handle validations. user = UserModel.find_by_username(user_name) membership = MembershipModel.find_membership_by_userid(user.id) org_id = membership.org_id is_valid_action = False # admin/owner deleteion admin_user_membership = MembershipModel.find_membership_by_user_and_org( admin_user.id, org_id) if admin_user_membership.membership_type_code in [ADMIN]: is_valid_action = True # staff admin deleteion is_staff_admin = token_info and Role.STAFF_CREATE_ACCOUNTS.value in token_info.get( 'realm_access').get('roles') if is_staff_admin: is_valid_action = True # self deletion if user.keycloak_guid == admin_user.keycloak_guid: is_valid_action = True # is the only owner getting deleted if is_valid_action and membership.membership_type_code == ADMIN: count_of_owners = MembershipModel.get_count_active_owner_org_id( org_id) if count_of_owners == 1: is_valid_action = False if not is_valid_action: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) user.is_terms_of_use_accepted = False user.status = UserStatus.INACTIVE.value user.save() membership.status = Status.INACTIVE.value membership.save() update_user_request = KeycloakUser() update_user_request.user_name = user_name.replace( IdpHint.BCROS.value + '/', '') update_user_request.enabled = False KeycloakService.update_user(update_user_request)
def validate(is_fatal=False, **kwargs) -> ValidatorResponse: """Validate account limit for user.""" user_from_context: UserContext = kwargs['user_context'] validator_response = ValidatorResponse() if not user_from_context.is_staff_admin(): user: UserModel = UserModel.find_by_jwt_token() count = OrgModel.get_count_of_org_created_by_user_id(user.id) if count >= current_app.config.get('MAX_NUMBER_OF_ORGS'): validator_response.add_error(Error.MAX_NUMBER_OF_ORGS_LIMIT) if is_fatal: raise BusinessException(Error.MAX_NUMBER_OF_ORGS_LIMIT, None) return validator_response
def reset(token_info: Dict): """Cleanup all the data from all tables create by the provided user id.""" if Role.TESTER.value in token_info.get('realm_access').get('roles'): user = UserModel.find_by_jwt_token(token_info) if user: # TODO need to find a way to avoid using protected function for model_class in db.Model._decl_class_registry.values(): # pylint:disable=protected-access if hasattr(model_class, 'created_by_id'): for model in model_class.query.filter_by( created_by_id=user.id).all(): model.reset() if hasattr(model_class, 'modified_by_id'): for model in model_class.query.filter_by( modified_by_id=user.id).all(): model.reset() # check the user is still exists or not user = UserModel.find_by_jwt_token(token_info) if user: user.modified_by = None user.modified_by_id = None user.reset()
def find_by_jwt_token(cls, token: dict = None): """Find user from database by user token.""" if not token: return None user_dao = UserModel.find_by_jwt_token(token) if not user_dao: return None user = User() user._dao = user_dao # pylint: disable=protected-access return user
def delete_user(token): """Delete User Profile. Does the following 1) Find the user using token 2) Find all org membership for the user 3) Check if the current user is the only owner for any org - If yes, deny the action 4) Mark the membership as inactive on all orgs for the user 5) Delete the contact information for the user and the accepted terms of service 6) Mark the user record as inactive """ current_app.logger.debug('<delete_user') user: UserModel = UserModel.find_by_jwt_token(token) if not user: raise BusinessException(Error.DATA_NOT_FOUND, None) if user.status == UserStatus.INACTIVE.value: raise BusinessException(Error.DELETE_FAILED_INACTIVE_USER, None) user_orgs: List[OrgModel] = MembershipModel.find_orgs_for_user(user.id) current_app.logger.info('Found {} orgs for the user'.format( len(user_orgs) if user_orgs else 0)) if user_orgs: for org in user_orgs: current_app.logger.debug( f'Org : {org.name}, Status : {org.status_code}') if org.status_code == Status.ACTIVE.name: User.__remove_org_membership(org, user.id) # Delete contact User.__delete_contact(user=user) # Set the user status as inactive user.status = UserStatus.INACTIVE.value # Remove accepted terms user.terms_of_use_accepted_version = None user.terms_of_use_version = None user.is_terms_of_use_accepted = False user.save() # Remove user from account_holders group KeycloakService.remove_from_account_holders_group(user.keycloak_guid) current_app.logger.debug('<delete_user')
def save_from_jwt_token(cls, token: dict = None): """Save user to database (create/update).""" if not token: return None existing_user = UserModel.find_by_jwt_token(token) if existing_user is None: user_model = UserModel.create_from_jwt_token(token) else: user_model = UserModel.update_from_jwt_token(token, existing_user) if not user_model: return None user = User(user_model) return user
def test_delete_user_where_org_has_another_owner(session, auth_mock, keycloak_mock, monkeypatch): # pylint:disable=unused-argument """Assert that a user can be deleted.""" # Create a user and org user_model = factory_user_model(user_info=TestUserInfo.user_test) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model contact_link.commit() patch_token_info(TestJwtClaims.get_test_user(user_model.keycloak_guid), monkeypatch) org = OrgService.create_org(TestOrgInfo.org1, user_id=user_model.id) org_dictionary = org.as_dict() org_id = org_dictionary['id'] entity = factory_entity_model(entity_info=TestEntityInfo.entity_lear_mock) affiliation = AffiliationModel(org_id=org_id, entity_id=entity.id) affiliation.save() # Create another user and add membership to the above org user_model2 = factory_user_model(user_info=TestUserInfo.user2) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model2 contact_link.commit() membership = MembershipModel(org_id=org_id, user_id=user_model2.id, membership_type_code='ADMIN', membership_type_status=Status.ACTIVE.value) membership.save() membership.commit() # with pytest.raises(BusinessException) as exception: patch_token_info(TestJwtClaims.get_test_user(user_model2.keycloak_guid), monkeypatch) UserService.delete_user() updated_user = UserModel.find_by_jwt_token() assert len(updated_user.contacts) == 0 user_orgs = MembershipModel.find_orgs_for_user(updated_user.id) for org in user_orgs: assert org.status_code == 'INACTIVE'
def save_from_jwt_token(cls, token: dict, request_json: Dict = None): """Save user to database (create/update).""" current_app.logger.debug('save_from_jwt_token') if not token: return None request_json = {} if not request_json else request_json is_anonymous_user = token.get('accessType', None) == AccessType.ANONYMOUS.value if not is_anonymous_user: existing_user = UserModel.find_by_jwt_token(token) else: existing_user = UserModel.find_by_username( token.get('preferred_username')) first_name, last_name = User._get_names(existing_user, request_json, token) if existing_user is None: user_model = UserModel.create_from_jwt_token( token, first_name, last_name) else: user_model = UserModel.update_from_jwt_token( existing_user, token, first_name, last_name, is_login=request_json.get('isLogin', False)) if not user_model: return None # if accepted , double check if there is a new TOS in place .IF so , update the flag to false if user_model.is_terms_of_use_accepted: document_type = DocumentType.TERMS_OF_USE_DIRECTOR_SEARCH.value if is_anonymous_user \ else DocumentType.TERMS_OF_USE.value # get the digit version of the terms of service..ie d1 gives 1 ; d2 gives 2..for proper comparison latest_version = util.digitify( DocumentService.find_latest_version_by_type(document_type)) current_version = util.digitify( user_model.terms_of_use_accepted_version) if latest_version > current_version: user_model.is_terms_of_use_accepted = False user = User(user_model) return user
def approve_or_reject(org_id: int, is_approved: bool, 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() # If status is PENDING_STAFF_REVIEW handle affidavit approve process, else raise error if org.status_code == OrgStatus.PENDING_STAFF_REVIEW.value and \ org.access_type in (AccessType.EXTRA_PROVINCIAL.value, AccessType.REGULAR_BCEID.value): AffidavitService.approve_or_reject(org_id, is_approved, user) elif org.status_code != OrgStatus.PENDING_STAFF_REVIEW.value or \ org.access_type not in \ (AccessType.EXTRA_PROVINCIAL.value, AccessType.REGULAR_BCEID.value, AccessType.GOVM.value): 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 if org.access_type in (AccessType.EXTRA_PROVINCIAL.value, AccessType.REGULAR_BCEID.value): Org.send_approved_rejected_notification(admin_email, org.name, org.id, org.status_code, origin_url) elif org.access_type == AccessType.GOVM.value: Org.send_approved_rejected_govm_notification( admin_email, org.name, org.id, org.status_code, origin_url) current_app.logger.debug('>find_affidavit_by_org_id ') return Org(org)
def delete_contact(token): """Delete the contact for an existing user.""" user = UserModel.find_by_jwt_token(token) if not user or not user.contacts: raise BusinessException(Error.DATA_NOT_FOUND, None) # unlink the user from its contact contact_link = ContactLinkModel.find_by_user_id(user.id) del contact_link.user contact_link.commit() # clean up any orphaned contacts and links if not contact_link.has_links(): contact = contact_link.contact contact_link.delete() contact.delete() return User(user)
def test_user_find_by_jwt_token(session): """Assert that a User can be stored in the service. Start with a blank database. """ user = User(username='******', roles='{edit, uma_authorization, staff}') session.add(user) session.commit() token = { 'preferred_username': '******', "realm_access": { "roles": ["edit", "uma_authorization", "basic"] } } u = User.find_by_jwt_token(token) assert u.id is not None
def test_delete_user(session, auth_mock): # pylint:disable=unused-argument """Assert that a user can be deleted.""" user_model = factory_user_model(user_info=TestUserInfo.user_test) contact = factory_contact_model() contact_link = ContactLinkModel() contact_link.contact = contact contact_link.user = user_model contact_link.commit() org = OrgService.create_org(TestOrgInfo.org1, user_id=user_model.id) UserService.delete_user(TestJwtClaims.user_test) updated_user = UserModel.find_by_jwt_token(TestJwtClaims.user_test) assert len(updated_user.contacts) == 0 user_orgs = MembershipModel.find_orgs_for_user(updated_user.id) for org in user_orgs: assert org.status_code == 'INACTIVE'
def update_task(self, task_info: Dict = None, origin_url: str = None): """Update a task record.""" current_app.logger.debug('<update_task ') task_model: TaskModel = self._model task_relationship_status = task_info.get('relationshipStatus') user: UserModel = UserModel.find_by_jwt_token() task_model.status = task_info.get('status', TaskStatus.COMPLETED.value) task_model.remarks = task_info.get('remarks', None) task_model.decision_made_by = user.username task_model.decision_made_on = datetime.now() task_model.relationship_status = task_relationship_status task_model.flush() # Update its relationship self._update_relationship(origin_url=origin_url) current_app.logger.debug('>update_task ') db.session.commit() return Task(task_model)
def update_contact(token, contact_info: dict): """Update a contact for an existing user.""" current_app.logger.debug('update_contact') user = UserModel.find_by_jwt_token(token) if user is None: raise BusinessException(Error.DATA_NOT_FOUND, None) # find the contact link for this user contact_link = ContactLinkModel.find_by_user_id(user.id) # now find the contact for the link if contact_link is None or contact_link.contact is None: raise BusinessException(Error.DATA_NOT_FOUND, None) contact = contact_link.contact contact.update_from_dict(**camelback2snake(contact_info)) contact = contact.save() # return the updated contact return ContactService(contact)