def post(): """Post a new user using the request body (which will contain a JWT). If the user already exists, update the name. """ token = g.jwt_oidc_token_info try: request_json = request.get_json(silent=True) # For BCeID users validate schema. if token.get('loginSource', None) == LoginSource.BCEID.value and request_json is not None: valid_format, errors = schema_utils.validate(request_json, 'user') if not valid_format: return {'message': schema_utils.serialize(errors)}, http_status.HTTP_400_BAD_REQUEST user = UserService.save_from_jwt_token(token, request_json) response, status = user.as_dict(), http_status.HTTP_201_CREATED # Add the user to public_users group if the user doesn't have public_user group KeycloakService.join_users_group(token) # If the user doesn't have account_holder role check if user is part of any orgs and add to the group if token.get('loginSource', '') in \ (LoginSource.BCSC.value, LoginSource.BCROS.value, LoginSource.BCEID.value) \ and Role.ACCOUNT_HOLDER.value not in token.get('roles', []) \ and len(OrgService.get_orgs(user.identifier, [Status.ACTIVE.value])) > 0: KeycloakService.join_account_holders_group() except BusinessException as exception: response, status = {'code': exception.code, 'message': exception.message}, exception.status_code return response, status
def post(): """Post a new user using the request body (which will contain a JWT). If the user already exists, update the name. """ token = g.jwt_oidc_token_info try: request_json = request.get_json(silent=True) # For BCeID users validate schema. if token.get('loginSource', None) == LoginSource.BCEID.value and request_json is not None: valid_format, errors = schema_utils.validate(request_json, 'user') if not valid_format: return {'message': schema_utils.serialize(errors)}, http_status.HTTP_400_BAD_REQUEST user = UserService.save_from_jwt_token(token, request_json) response, status = user.as_dict(), http_status.HTTP_201_CREATED # Add the user to public_users group if the user doesn't have public_user group if token.get('loginSource', '') != LoginSource.STAFF.value: KeycloakService.join_users_group(token) # For anonymous users, there are no invitation process for members, # so whenever they login perform this check and add them to corresponding groups if token.get('loginSource', '') == LoginSource.BCROS.value: if len(OrgService.get_orgs(user.identifier, [Status.ACTIVE.value])) > 0: KeycloakService.join_account_holders_group() except BusinessException as exception: response, status = {'code': exception.code, 'message': exception.message}, exception.status_code return response, status
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'): # pylint: disable=too-many-nested-blocks 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 # skip version classes if not (hasattr(model_class, 'transaction_id') and hasattr(model_class, 'end_transaction_id')): 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() # Reset opt from keycloak if from BCEID login_source = token_info.get('loginSource', None) if login_source == LoginSource.BCEID.value: KeycloakService.reset_otp(token_info.get('sub'))
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.services.keycloak.KeycloakService._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} MembershipService(membership).update_membership(updated_fields=updated_fields, token_info=token_info()) 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 # 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} MembershipService(membership).update_membership(updated_fields=updated_fields, token_info=token_info()) 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
def test_create_user_add_membership_reenable(session, auth_mock, keycloak_mock, monkeypatch): # pylint:disable=unused-argument """Assert that an admin can add a member.""" org = factory_org_model(org_info=TestOrgInfo.org_anonymous) user = factory_user_model() factory_membership_model(user.id, org.id) factory_product_model(org.id, product_code=ProductCode.DIR_SEARCH.value) claims = TestJwtClaims.get_test_real_user(user.keycloak_guid) patch_token_info(claims, monkeypatch) anon_member = TestAnonymousMembership.generate_random_user(USER) membership = [anon_member] users = UserService.create_user_and_add_membership(membership, org.id) user_name = IdpHint.BCROS.value + '/' + membership[0]['username'] assert len(users['users']) == 1 assert users['users'][0]['username'] == user_name assert users['users'][0]['type'] == Role.ANONYMOUS_USER.name members = MembershipModel.find_members_by_org_id(org.id) # staff didnt create members..so count is count of owner+other 1 member assert len(members) == 2 # assert cant be readded users = UserService.create_user_and_add_membership(membership, org.id) assert users['users'][0]['http_status'] == 409 assert users['users'][0]['error'] == 'The username is already taken' # deactivate everything and try again anon_user = UserModel.find_by_username(user_name) anon_user.status = Status.INACTIVE.value anon_user.save() membership_model = MembershipModel.find_membership_by_userid(anon_user.id) membership_model.status = Status.INACTIVE.value update_user_request = KeycloakUser() update_user_request.user_name = membership[0]['username'] update_user_request.enabled = False KeycloakService.update_user(update_user_request) org2 = factory_org_model(org_info=TestOrgInfo.org_anonymous_2, org_type_info={'code': 'BASIC'}, org_status_info=None, payment_type_info=None) factory_membership_model(user.id, org2.id) factory_product_model(org2.id, product_code=ProductCode.DIR_SEARCH.value) users = UserService.create_user_and_add_membership(membership, org2.id) assert users['users'][0]['http_status'] == 409 assert users['users'][0]['error'] == 'The username is already taken' # add to same org.Should work users = UserService.create_user_and_add_membership(membership, org.id) assert len(users['users']) == 1 assert users['users'][0][ 'username'] == IdpHint.BCROS.value + '/' + membership[0]['username'] assert users['users'][0]['type'] == Role.ANONYMOUS_USER.name
def test_create_user_and_add_same_user_name_error_in_kc(session, auth_mock, keycloak_mock): # pylint:disable=unused-argument """Assert that same user name cannot be added twice.""" org = factory_org_model(org_info=TestOrgInfo.org_anonymous) membership = [TestAnonymousMembership.generate_random_user(ADMIN)] keycloak_service = KeycloakService() request = KeycloakScenario.create_user_request() request.user_name = membership[0]['username'] keycloak_service.add_user(request) users = UserService.create_user_and_add_membership(membership, org.id, single_mode=True) assert users['users'][0]['http_status'] == 409 assert users['users'][0]['error'] == 'The username is already taken'
def post(): """Post a new user using the request body (which will contain a JWT). If the user already exists, update the name. """ token = g.jwt_oidc_token_info try: response, status = UserService.save_from_jwt_token(token).as_dict(), http_status.HTTP_201_CREATED KeycloakService.join_public_users_group(g.jwt_oidc_token_info) except BusinessException as exception: response, status = {'code': exception.code, 'message': exception.message}, exception.status_code return response, status
def test_add_back_a_delete_bcros(client, jwt, session, keycloak_mock): """Assert different conditions of user deletion.""" org = factory_org_model(org_info=TestOrgInfo.org_anonymous) user = factory_user_model(user_info=TestUserInfo.user_bcros_active) factory_membership_model(user.id, org.id) factory_product_model(org.id, product_code=ProductCode.DIR_SEARCH.value) owner_claims = TestJwtClaims.get_test_real_user(user.keycloak_guid) member = TestAnonymousMembership.generate_random_user(USER) membership = [ member, TestAnonymousMembership.generate_random_user(COORDINATOR) ] UserService.create_user_and_add_membership(membership, org.id, token_info=owner_claims) headers = factory_auth_header(jwt=jwt, claims=owner_claims) member_user_id = IdpHint.BCROS.value + '/' + member.get('username') rv = client.delete(f'/api/v1/users/{member_user_id}', headers=headers, content_type='application/json') assert rv.status_code == http_status.HTTP_204_NO_CONTENT kc_user = KeycloakService.get_user_by_username(member.get('username')) assert kc_user.enabled is False user_model = UserService.find_by_username(member_user_id) assert user_model.as_dict().get('user_status') == UserStatus.INACTIVE.value membership = MembershipModel.find_membership_by_userid( user_model.identifier) assert membership.status == Status.INACTIVE.value
def _create_consumer(cls, name, org, env): """Create an API Gateway consumer.""" consumer_endpoint: str = cls._get_api_consumer_endpoint(env) gw_api_key = cls._get_api_gw_key(env) email = cls._get_email_id(org.id, env) client_rep = generate_client_representation(org.id, current_app.config.get('API_GW_KC_CLIENT_ID_PATTERN'), env) KeycloakService.create_client(client_rep) service_account = KeycloakService.get_service_account_user(client_rep.get('id')) KeycloakService.add_user_to_group(service_account.get('id'), GROUP_API_GW_USERS if env == 'prod' else GROUP_API_GW_SANDBOX_USERS) KeycloakService.add_user_to_group(service_account.get('id'), GROUP_ACCOUNT_HOLDERS) # Create a consumer with the keycloak client id and secret create_consumer_payload = dict(email=email, firstName=org.name, lastName=org.branch_name or 'BCR', userName=org.name, clientId=client_rep.get('clientId'), clientSecret=client_rep.get('secret'), apiAccess=['ALL_API'], apiKeyName=name) api_key_response = RestService.post( f'{consumer_endpoint}/mc/v1/consumers', additional_headers={'x-apikey': gw_api_key}, data=create_consumer_payload, generate_token=False ) return api_key_response
def test_reset_bceid_user(session, auth_mock): # pylint: disable=unused-argument """Assert that reset data from a bceid user.""" keycloak_service = KeycloakService() request = KeycloakScenario.create_user_by_user_info( TestJwtClaims.tester_bceid_role) keycloak_service.add_user(request, return_if_exists=True) user = keycloak_service.get_user_by_username(request.user_name) assert user is not None user_id = user.id user_with_token = TestUserInfo.user_bceid_tester user_with_token['keycloak_guid'] = user_id user = factory_user_model(user_info=user_with_token) org = factory_org_model(user_id=user.id) response = ResetDataService.reset( TestJwtClaims.get_test_user(user_id, 'BCEID')) assert response is None found_org = OrgService.find_by_org_id(org.id) assert found_org is None
def post(): """Post a new user using the request body (which will contain a JWT). If the user already exists, update the name. """ token = g.jwt_oidc_token_info try: user = UserService.save_from_jwt_token(token) response, status = user.as_dict(), http_status.HTTP_201_CREATED # Add the user to public_users group if the user doesn't have public_user group KeycloakService.join_users_group(g.jwt_oidc_token_info) # If the user doesn't have account_holder role check if user is part of any orgs and add to the group if token.get('loginSource', '') in (BCSC, BCROS) \ and Role.ACCOUNT_HOLDER.value not in token.get('roles', []) \ and len(OrgService.get_orgs(user.identifier, [Status.ACTIVE.value])) > 0: KeycloakService.join_account_holders_group() except BusinessException as exception: response, status = {'code': exception.code, 'message': exception.message}, exception.status_code return response, status
def test_delete_does_not_remove_user_from_account_holder_group(session, monkeypatch, auth_mock): # pylint:disable=unused-argument """Assert that if the user has multiple Orgs, and deleting one doesn't remove account holders group.""" # 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': [ ] } } monkeypatch.setattr('auth_api.services.keycloak.KeycloakService._get_token_info', token_info) org1 = OrgService.create_org(TestOrgInfo.org1, user_id=user.id) OrgService.create_org(TestOrgInfo.org2, user_id=user.id) OrgService.delete_org(org1.as_dict().get('id'), token_info()) user_groups = keycloak_service.get_user_groups(user_id=kc_user.id) groups = [] for group in user_groups: groups.append(group.get('name')) assert GROUP_ACCOUNT_HOLDERS in groups
def test_create_org_adds_user_to_account_holders_group(session, monkeypatch): # pylint:disable=unused-argument """Assert that an Org creation adds the user to account holders group.""" # 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': [ ] } } monkeypatch.setattr('auth_api.services.keycloak.KeycloakService._get_token_info', token_info) OrgService.create_org(TestOrgInfo.org1, user_id=user.id) user_groups = keycloak_service.get_user_groups(user_id=kc_user.id) groups = [] for group in user_groups: groups.append(group.get('name')) assert GROUP_ACCOUNT_HOLDERS in groups
def test_delete_otp_for_user(session, auth_mock, keycloak_mock): """Assert that the otp cant be reset.""" kc_service = KeycloakService() org = factory_org_model(org_info=TestOrgInfo.org_anonymous) admin_user = factory_user_model() factory_membership_model(admin_user.id, org.id) admin_claims = TestJwtClaims.get_test_real_user(admin_user.keycloak_guid) membership = [TestAnonymousMembership.generate_random_user(USER)] keycloak_service = KeycloakService() request = KeycloakScenario.create_user_request() request.user_name = membership[0]['username'] keycloak_service.add_user(request) user = kc_service.get_user_by_username(request.user_name) user = factory_user_model(TestUserInfo.get_bceid_user_with_kc_guid(user.id)) factory_membership_model(user.id, org.id) UserService.delete_otp_for_user(user.username, admin_claims) user1 = kc_service.get_user_by_username(request.user_name) assert 'CONFIGURE_TOTP' in json.loads(user1.value()).get('requiredActions')
def create_key(cls, org_id: int, request_json: Dict[str, str]): """Create a key for the account.""" current_app.logger.debug('<create_key ') env = request_json.get('environment', 'sandbox') name = request_json.get('keyName') org: OrgModel = OrgModel.find_by_id(org_id) # first find if there is a consumer created for this account. consumer_endpoint: str = current_app.config.get('API_GW_CONSUMERS_API_URL') gw_api_key = current_app.config.get('API_GW_KEY') if env == 'prod' else current_app.config.get( 'API_GW_NON_PROD_KEY') email = cls._get_email_id(org_id) if not org.has_api_access: # If the account doesn't have api access, add it client_rep = generate_client_representation(org_id, current_app.config.get('API_GW_KC_CLIENT_ID_PATTERN')) KeycloakService.create_client(client_rep) service_account = KeycloakService.get_service_account_user(client_rep.get('id')) KeycloakService.add_user_to_group(service_account.get('id'), GROUP_API_GW_USERS) KeycloakService.add_user_to_group(service_account.get('id'), GROUP_ACCOUNT_HOLDERS) # Create a consumer with the keycloak client id and secret create_consumer_payload = dict(email=email, firstName=org.name, lastName=org.branch_name or 'BCR', userName=org.name, clientId=client_rep.get('clientId'), clientSecret=client_rep.get('secret'), apiAccess=['ALL_API'], apiKeyName=name) api_key_response = RestService.post( f'{consumer_endpoint}/mc/v1/consumers', additional_headers={'x-apikey': gw_api_key}, data=create_consumer_payload, generate_token=False ) org.has_api_access = True org.save() else: # Create additional API Key if a consumer exists api_key_response = RestService.post( f'{consumer_endpoint}/mc/v1/consumers/{email}/apikeys', additional_headers={'x-apikey': gw_api_key}, data=dict( apiAccess=['ALL_API'], apiKeyName=name ), generate_token=False ) return api_key_response.json()
def test_create_org_adds_user_to_account_holders_group(session, monkeypatch): # pylint:disable=unused-argument """Assert that an Org creation adds the user to account holders group.""" # 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({'sub': user.keycloak_guid}, monkeypatch) OrgService.create_org(TestOrgInfo.org1, user_id=user.id) user_groups = keycloak_service.get_user_groups(user_id=kc_user.id) groups = [] for group in user_groups: groups.append(group.get('name')) assert GROUP_ACCOUNT_HOLDERS in groups
def test_delete_does_not_remove_user_from_account_holder_group(session, monkeypatch, auth_mock): # pylint:disable=unused-argument """Assert that if the user has multiple Orgs, and deleting one doesn't remove account holders group.""" # 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({'sub': user.keycloak_guid}, monkeypatch) patch_pay_account_delete(monkeypatch) org1 = OrgService.create_org(TestOrgInfo.org1, user_id=user.id) OrgService.create_org(TestOrgInfo.org2, user_id=user.id) OrgService.delete_org(org1.as_dict().get('id')) user_groups = keycloak_service.get_user_groups(user_id=kc_user.id) groups = [] for group in user_groups: groups.append(group.get('name')) assert GROUP_ACCOUNT_HOLDERS in groups
'source': 'PASSCODE' } ADD_USER_REQUEST_SAME_EMAIL = { 'username': '******', 'password': '******', 'firstname': '112', 'lastname': 'test', 'email': '*****@*****.**', 'enabled': True, 'user_type': ['/test', '/basic/editor'], 'corp_type': 'CP', 'source': 'PASSCODE' } keycloak_service = KeycloakService() def test_keycloak_add_user(session): """Add user to Keycloak. Assert return a user with the same username as the username in request.""" user = keycloak_service.add_user(ADD_USER_REQUEST) assert user.get('username') == ADD_USER_REQUEST.get('username') keycloak_service.delete_user_by_username(ADD_USER_REQUEST.get('username')) def test_keycloak_add_user_duplicate_email(session): """Add user with duplicate email. Assert response is None, error code is data conflict.""" keycloak_service.add_user(ADD_USER_REQUEST) response = None try: response = keycloak_service.add_user(ADD_USER_REQUEST_SAME_EMAIL)
import json import uuid from random import randint from auth_api import status as http_status from auth_api.schemas import utils as schema_utils from auth_api.services.keycloak import KeycloakService from auth_api.utils.constants import IdpHint from config import get_named_config from tests.utilities.factory_scenarios import BulkUserTestScenario, TestJwtClaims, \ TestOrgInfo from tests.utilities.factory_utils import (factory_auth_header, factory_invitation_anonymous) KEYCLOAK_SERVICE = KeycloakService() CONFIG = get_named_config('testing') def test_add_user(client, jwt, session): # pylint:disable=unused-argument """Assert that a user can be POSTed.""" headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) rv = client.post('/api/v1/users', headers=headers, content_type='application/json') assert rv.status_code == http_status.HTTP_201_CREATED assert schema_utils.validate(rv.json, 'anonymous_user_response')