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 fetch_activity_logs(org_id: int, **kwargs): # pylint: disable=too-many-locals """Search all activity logs.""" user_from_context: UserContext = kwargs['user_context'] item_name = kwargs.get('item_name') item_type = kwargs.get('item_type') action = kwargs.get('action') check_auth(one_of_roles=(ADMIN, STAFF), org_id=org_id) logs = {'activity_logs': []} page: int = int(kwargs.get('page')) limit: int = int(kwargs.get('limit')) search_args = (item_name, item_type, action, page, limit) current_app.logger.debug('<fetch_activity logs ') results, count = ActivityLogModel.fetch_activity_logs_for_account( org_id, *search_args) is_staff_access = user_from_context.is_staff() for result in results: activity_log: ActivityLogModel = result[0] log_dict = ActivityLogSchema( exclude=('actor_id', )).dump(activity_log) if user := result[1]: actor = ActivityLog._mask_user_name(is_staff_access, user) log_dict['actor'] = actor log_dict['action'] = ActivityLog._build_string(activity_log) logs['activity_logs'].append(log_dict)
def fetch_activity_logs(org_id: int, token_info: Dict = None, **kwargs): # pylint: disable=too-many-locals """Search all activity logs.""" item_name = kwargs.get('item_name') item_type = kwargs.get('item_type') action = kwargs.get('action') check_auth(token_info, one_of_roles=(ADMIN, STAFF), org_id=org_id) logs = {'activity_logs': []} page: int = int(kwargs.get('page')) limit: int = int(kwargs.get('limit')) search_args = (item_name, item_type, action, page, limit) current_app.logger.debug('<fetch_activity logs ') results, count = ActivityLogModel.fetch_activity_logs_for_account(org_id, *search_args) is_staff_access = g.jwt_oidc_token_info and 'staff' in \ g.jwt_oidc_token_info.get('realm_access', {}).get('roles', None) for result in results: activity_log: ActivityLogModel = result[0] log_dict = ActivityLogSchema(exclude=('actor_id',)).dump(activity_log) if user := result[1]: actor = ActivityLog._mask_user_name(is_staff_access, user) log_dict['actor'] = actor logs['activity_logs'].append(log_dict)
def _validate_and_throw_exception(memberships, org_id, single_mode): if single_mode: # make sure no bulk operation and only owner is created using if no auth if len(memberships) > 1 or memberships[0].get('membershipType') not in [ADMIN, COORDINATOR]: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) else: check_auth(org_id=org_id, one_of_roles=(COORDINATOR, ADMIN, STAFF)) # check if anonymous org ;these actions cannot be performed on normal orgs org = OrgModel.find_by_org_id(org_id) if not org or org.access_type != AccessType.ANONYMOUS.value: raise BusinessException(Error.INVALID_INPUT, None)
def create_user_and_add_membership(memberships: List[dict], org_id, token_info: Dict = None, skip_auth: bool = False): """ Create user(s) in the DB and upstream keycloak. accepts a list of memberships ie.a list of objects with username,password and membershipTpe skip_auth can be used if called method already perfomed the authenticaiton skip_auth= true is used now incase of invitation for admin users scenarion other cases should be invoked with skip_auth=false """ if skip_auth: # make sure no bulk operation and only owner is created using if no auth if len(memberships) > 1 or memberships[0].get('membershipType') not in [OWNER, ADMIN]: raise BusinessException(Error.INVALID_USER_CREDENTIALS, None) else: check_auth(org_id=org_id, token_info=token_info, one_of_roles=(ADMIN, OWNER)) # check if anonymous org ;these actions cannot be performed on normal orgs org = OrgModel.find_by_org_id(org_id) if not org or org.access_type != AccessType.ANONYMOUS.value: raise BusinessException(Error.INVALID_INPUT, None) current_app.logger.debug('create_user') users = [] for membership in memberships: create_user_request = KeycloakUser() current_app.logger.debug(f"create user username: {membership['username']}") create_user_request.first_name = membership['username'] create_user_request.user_name = membership['username'] create_user_request.password = membership['password'] create_user_request.enabled = True create_user_request.attributes = {'access_type': AccessType.ANONYMOUS.value} if membership.get('update_password_on_login', True): # by default , reset needed create_user_request.update_password_on_login() try: # TODO may be this method itself throw the business exception;can handle different exceptions? kc_user = KeycloakService.add_user(create_user_request) except HTTPError as err: current_app.logger.error('create_user in keycloak failed', err) raise BusinessException(Error.FAILED_ADDING_USER_IN_KEYCLOAK, None) username = IdpHint.BCROS.value + '/' + membership['username'] existing_user = UserModel.find_by_username(username) if existing_user: current_app.logger.debug('Existing users found in DB') raise BusinessException(Error.DATA_ALREADY_EXISTS, None) user_model: UserModel = UserModel(username=username, # BCROS is temporary value.Will be overwritten when user logs in is_terms_of_use_accepted=False, status=Status.ACTIVE.value, type=AccessType.ANONYMOUS.value, email=membership.get('email', None), firstname=kc_user.first_name, lastname=kc_user.last_name) user_model.save() User._add_org_membership(org_id, user_model.id, membership['membershipType']) users.append(User(user_model).as_dict()) return {'users': users}
def test_check_auth_for_service_account_valid_with_org_id(session): # pylint:disable=unused-argument """Assert that check_auth is working as expected.""" user = factory_user_model() org = factory_org_model() factory_membership_model(user.id, org.id) factory_product_model(org.id, product_code=ProductCode.BUSINESS.value) entity = factory_entity_model() factory_affiliation_model(entity.id, org.id) # Test for service account with CP corp type check_auth({'realm_access': {'roles': ['system']}, 'product_code': ProductCode.BUSINESS.value}, org_id=org.id)
def delete_otp_for_user(user_name, origin_url: str = None): """Reset the OTP of the user.""" # TODO - handle when the multiple teams implemented for bceid.. user = UserModel.find_by_username(user_name) membership = MembershipModel.find_membership_by_userid(user.id) org_id = membership.org_id check_auth(org_id=org_id, one_of_roles=(ADMIN, COORDINATOR, STAFF)) try: KeycloakService.reset_otp(str(user.keycloak_guid)) User.send_otp_authenticator_reset_notification(user.email, origin_url, org_id) except HTTPError as err: current_app.logger.error('update_user in keycloak failed {}', err) raise BusinessException(Error.UNDEFINED_ERROR, err) from err
def test_check_auth_for_service_account_valid_with_business_id(session): # pylint:disable=unused-argument """Assert that check_auth is working as expected.""" user = factory_user_model() org = factory_org_model() factory_membership_model(user.id, org.id) entity = factory_entity_model() factory_affiliation_model(entity.id, org.id) # Test for service account with CP corp type check_auth({ 'realm_access': { 'roles': ['system'] }, 'corp_type': 'CP' }, business_identifier=entity.business_identifier)
def test_check_auth_for_service_account_invalid(session): # pylint:disable=unused-argument """Assert that check_auth is working as expected and throws exception.""" user = factory_user_model() org = factory_org_model() factory_membership_model(user.id, org.id) entity = factory_entity_model() factory_affiliation_model(entity.id, org.id) # Test for invalid CP with pytest.raises(HTTPException) as excinfo: check_auth( { 'realm_access': { 'roles': ['system'] }, 'corp_type': 'IVALIDCP' }, org_id=org.id) assert excinfo.exception.code == 403 # Test for invalid CP with pytest.raises(HTTPException) as excinfo: check_auth({'realm_access': {'roles': ['system']}}, org_id=org.id) assert excinfo.exception.code == 403 # Test for invalid CP with no args with pytest.raises(HTTPException) as excinfo: check_auth({'realm_access': {'roles': ['system']}}) assert excinfo.exception.code == 403
def get_api_keys(cls, org_id: int) -> List[Dict[str, any]]: """Get all api keys.""" current_app.logger.debug('<get_api_keys ') check_auth(one_of_roles=(ADMIN, STAFF), org_id=org_id) api_keys_response = {'consumer': {'consumerKey': []}} for env in ('sandbox', 'prod'): email = cls._get_email_id(org_id, env) consumer_endpoint: str = cls._get_api_consumer_endpoint(env) gw_api_key: str = cls._get_api_gw_key(env) try: consumers_response = RestService.get( f'{consumer_endpoint}/mc/v1/consumers/{email}', additional_headers={'x-apikey': gw_api_key} ) keys = consumers_response.json()['consumer']['consumerKey'] cls._filter_and_add_keys(api_keys_response, keys, email) except HTTPError as exc: if exc.response.status_code != 404: # If consumer doesn't exist raise exc return api_keys_response
def revoke_key(cls, org_id: int, api_key: str): """Revoke api key.""" current_app.logger.debug('<revoke_key ') check_auth(one_of_roles=(ADMIN, STAFF), org_id=org_id) # Find the environment for this key, based on it consumer changes. email_id: str = None for key in cls.get_api_keys(org_id)['consumer']['consumerKey']: if key['apiKey'] == api_key: email_id = key['email'] break if not email_id: raise BusinessException(Error.DATA_NOT_FOUND, Exception()) env = 'sandbox' if email_id == cls._get_email_id(org_id, 'prod'): env = 'prod' consumer_endpoint = cls._get_api_consumer_endpoint(env) gw_api_key = cls._get_api_gw_key(env) RestService.patch( f'{consumer_endpoint}/mc/v1/consumers/{email_id}/apikeys/{api_key}?action=revoke', additional_headers={'x-apikey': gw_api_key}, data=dict(apiAccess='ALL_API'), generate_token=False )
def test_check_auth(session, monkeypatch): # pylint:disable=unused-argument """Assert that check_auth is working as expected.""" user = factory_user_model() org = factory_org_model() factory_membership_model(user.id, org.id) factory_product_model(org.id, product_code=ProductCode.BUSINESS.value) entity = factory_entity_model() factory_affiliation_model(entity.id, org.id) # Test if staff admin can access to STAFF only method patch_token_info({'realm_access': {'roles': ['staff', 'create_accounts']}, 'sub': str(user.keycloak_guid)}, monkeypatch) check_auth(one_of_roles=[STAFF]) # Test for staff admin role to only STAFF patch_token_info({'realm_access': {'roles': ['staff', 'create_accounts']}, 'sub': str(user.keycloak_guid)}, monkeypatch) check_auth(equals_role=STAFF) # Test for staff role patch_token_info({'realm_access': {'roles': ['staff']}, 'sub': str(user.keycloak_guid), 'product_code': ProductCode.BUSINESS.value}, monkeypatch) check_auth(one_of_roles=[STAFF]) # Test for owner role patch_token_info({'realm_access': {'roles': ['public']}, 'sub': str(user.keycloak_guid), 'product_code': ProductCode.BUSINESS.value}, monkeypatch) check_auth(one_of_roles=[ADMIN], business_identifier=entity.business_identifier) # Test for owner role with org id patch_token_info({'realm_access': {'roles': ['public']}, 'sub': str(user.keycloak_guid), 'product_code': ProductCode.BUSINESS.value}, monkeypatch) check_auth(one_of_roles=[ADMIN], org_id=org.id) # Test for exception, check for auth if resource is available for STAFF users with pytest.raises(HTTPException) as excinfo: patch_token_info({'realm_access': {'roles': ['public']}, 'sub': str(user.keycloak_guid)}, monkeypatch) check_auth(one_of_roles=[STAFF], business_identifier=entity.business_identifier) assert excinfo.exception.code == 403 # Test auth where STAFF role is in disabled role list with pytest.raises(HTTPException) as excinfo: patch_token_info({'realm_access': {'roles': ['staff']}, 'sub': str(user.keycloak_guid)}, monkeypatch) check_auth(disabled_roles=[STAFF], business_identifier=entity.business_identifier) assert excinfo.exception.code == 403 # Test auth where STAFF role is exact match with pytest.raises(HTTPException) as excinfo: patch_token_info({'realm_access': {'roles': ['public']}, 'sub': str(user.keycloak_guid)}, monkeypatch) check_auth(equals_role=USER, business_identifier=entity.business_identifier) assert excinfo.exception.code == 403 # Test auth where STAFF role is exact match with pytest.raises(HTTPException) as excinfo: patch_token_info({'realm_access': {'roles': ['public']}, 'sub': str(user.keycloak_guid)}, monkeypatch) check_auth(equals_role=USER, org_id=org.id) assert excinfo.exception.code == 403 # Test auth where STAFF role is exact match with pytest.raises(HTTPException) as excinfo: patch_token_info({'realm_access': {'roles': ['staff', 'create_accounts']}, 'sub': str(user.keycloak_guid)}, monkeypatch) check_auth(equals_role=USER, org_id=org.id) assert excinfo.exception.code == 403
def test_check_auth(session): # pylint:disable=unused-argument """Assert that check_auth is working as expected.""" user = factory_user_model() org = factory_org_model() factory_membership_model(user.id, org.id) entity = factory_entity_model() factory_affiliation_model(entity.id, org.id) # Test if staff admin can access to STAFF only method check_auth( { 'realm_access': { 'roles': ['staff_admin'] }, 'sub': str(user.keycloak_guid) }, one_of_roles=[STAFF]) # Test for staff admin can access to STAFF_ADMIN method check_auth( { 'realm_access': { 'roles': ['staff_admin'] }, 'sub': str(user.keycloak_guid) }, one_of_roles=[STAFF_ADMIN]) # Test for staff admin role can access to STAFF_ADMIN and STAFF method check_auth( { 'realm_access': { 'roles': ['staff_admin'] }, 'sub': str(user.keycloak_guid) }, one_of_roles=[STAFF_ADMIN, STAFF]) # Test for staff admin role can access to only STAFF_ADMIN check_auth( { 'realm_access': { 'roles': ['staff_admin'] }, 'sub': str(user.keycloak_guid) }, equals_role=STAFF_ADMIN) # Test for staff admin role to only STAFF check_auth( { 'realm_access': { 'roles': ['staff_admin'] }, 'sub': str(user.keycloak_guid) }, equals_role=STAFF) # Test for staff role check_auth( { 'realm_access': { 'roles': ['staff'] }, 'sub': str(user.keycloak_guid) }, one_of_roles=[STAFF]) # Test for owner role check_auth( { 'realm_access': { 'roles': ['public'] }, 'sub': str(user.keycloak_guid) }, one_of_roles=[OWNER], business_identifier=entity.business_identifier) # Test for owner role with org id check_auth( { 'realm_access': { 'roles': ['public'] }, 'sub': str(user.keycloak_guid) }, one_of_roles=[OWNER], org_id=org.id) # Test for exception, check for auth if resource is available for STAFF users with pytest.raises(HTTPException) as excinfo: check_auth( { 'realm_access': { 'roles': ['public'] }, 'sub': str(user.keycloak_guid) }, one_of_roles=[STAFF], business_identifier=entity.business_identifier) assert excinfo.exception.code == 403 # Test auth where STAFF role is in disabled role list with pytest.raises(HTTPException) as excinfo: check_auth( { 'realm_access': { 'roles': ['staff'] }, 'sub': str(user.keycloak_guid) }, disabled_roles=[STAFF], business_identifier=entity.business_identifier) assert excinfo.exception.code == 403 # Test auth where STAFF role is exact match with pytest.raises(HTTPException) as excinfo: check_auth( { 'realm_access': { 'roles': ['public'] }, 'sub': str(user.keycloak_guid) }, equals_role=MEMBER, business_identifier=entity.business_identifier) assert excinfo.exception.code == 403 # Test auth where STAFF role is exact match with pytest.raises(HTTPException) as excinfo: check_auth( { 'realm_access': { 'roles': ['public'] }, 'sub': str(user.keycloak_guid) }, equals_role=MEMBER, org_id=org.id) assert excinfo.exception.code == 403 # Test auth where STAFF role is exact match with pytest.raises(HTTPException) as excinfo: check_auth( { 'realm_access': { 'roles': ['staff_admin'] }, 'sub': str(user.keycloak_guid) }, equals_role=MEMBER, org_id=org.id) assert excinfo.exception.code == 403