예제 #1
0
    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
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
 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)
예제 #5
0
    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}
예제 #6
0
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)
예제 #7
0
 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
예제 #8
0
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)
예제 #9
0
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
예제 #10
0
    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
예제 #11
0
    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
        )
예제 #12
0
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
예제 #13
0
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