"""Logic for CRUD operations on a resource.""" from ddtrace import tracer from flask import g from owsresponse import response, status from permissions.connectors import redis from permissions.constants import constants, error from permissions.logic import identity as identity_logic from permissions.models import label, profile, resource from permissions.utils import resource_utils from permissions.utils.cache_utils import get_resources_key def get_user_resources_for_admin( admin_context, identity_id, resource_type, limit=constants.DEFAULT_LIMIT, offset=constants.DEFAULT_OFFSET, active=True, ): """GET resources that identity_id has direct HAS_ACCESS_TO and the admin can administer. Args: admin_context (dict): Admin's context data. identity_id (str): Users identity id. resource_type (str): Type of resource. limit (int): Optional. Limit number of records. Default 200. offset (int): Optional. Offset result set. Default 0. active (bool): Optional. Users active state. Default True. Return: Response: containing dict of resources or error message. """ if resource_type == constants.VENDOR_STAR_RESOURCE_TYPE: if identity_id == admin_context['identity_id']: return resource.get_vend_star_for_admin(**admin_context) return resource.get_user_vend_star(admin_context, identity_id) can_access_vendor_star = profile.check_vendor_star_access(**admin_context) if can_access_vendor_star or (identity_id == admin_context['identity_id']): # for an employee looking for their resource access, return all access. return resource.get_user_resources_for_admin( identity_id, constants.SETTINGS_SUPPORT_MAPPING['profileTypes'], resource_type, limit, offset, active, ) if admin_context['profile_type'] == constants.SETTINGSPROFILE: # regular label admin using settings profile return resource.get_users_resources_for_settings_by_type( admin_context, identity_id, constants.SETTINGS_SUPPORT_MAPPING['profileTypes'], resource_type, limit, offset, active, ) # regular label user with some other profile type. This is for backward compatibility. return resource.get_users_resources_by_type( admin_context, identity_id, resource_type, limit, offset, active, ) def deactivate_resources_common_with_admin(admin_context, identity_id): """Deactivate a user and delete access to its resources. Args: admin_context (dict): Admin's context data. identity_id (str): Users identity id. Return: Response: containing dict of resources or error message. """ identity = identity_logic.get_identity_by_id(identity_id) if not identity: return identity if identity.message.get('active') and identity.message.get('active') == 'N': return response.Response(identity.message, status=202) g.log.info( 'Deactivating user', resources={ 'admin_identity_id': admin_context['identity_id'], 'identity_id': identity_id, 'email': identity.message.get('email'), }, ) return resource.deactivate_user( admin_context, identity_id, constants.SETTINGS_SUPPORT_MAPPING['deleteProfileTypes'] ) def check_label_access_for_profile(profile_uuid, resource_id): """Check for access to a resource given a profile.""" resources = get_resources_for_profile_uuid(profile_uuid, 'label').message for item in resources: if item.get('type') != 'Vendor': continue if item.get('vendorId') == '*' or item.get('vendorId') == resource_id: return status.OK return status.FORBIDDEN def get_resources_for_profile(profile_type, profile_id, resource_type): """GET all resources or type resource_type for this profile id. Args: profile_type (str): Type of profile. profile_id (int): Profile Identifier. resource_type (str): Type of resource eg: product, artistinfo or all. """ resources = [] if profile_type.lower() not in constants.RESOURCE_TO_NODE_MAPPING.keys(): return response.create_fatal_response('Invalid Profile type') profile_type = constants.RESOURCE_TO_NODE_MAPPING[profile_type.lower()] try: if resource_type == constants.RESOURCE_TYPE_ALL: resources.extend( resource.get_resources_of_type_for_profile(profile_type, profile_id, []).message ) elif resource_type == constants.COLLABORATOR_RESOURCE_TYPE: resources.extend( resource.get_resources_of_type_for_profile( profile_type, profile_id, [resource_type] ).message ) elif resource_type in constants.NEO4J_MIGRATED_RESOURCE_TYPES: resources.extend( resource.get_resources_of_type_for_profile( profile_type, profile_id, [resource_type] ).message ) elif resource_type == constants.RESOURCE_TYPE_LABEL: resources.extend( resource.get_resources_of_type_for_profile( profile_type, profile_id, [constants.VENDOR_RESOURCE_TYPE, constants.SUBACCOUNT_NEO4J_RESOURCE_TYPE], ).message ) if resource_type == constants.RESOURCE_TYPE_ALL: # get corresponding ArtistInfo for LabelParticipants. artists = resource.get_artists_for_labelparticipants(profile_type, profile_id).message resources.extend(artists) except Exception as err: raise err return response.Response(resources) def get_resources_for_profile_uuid(profile_uuid, resource_type): """GET all resources or type resource_type for this profile id. Args: profile_uuid (str): Profile UUID. resource_type (str): Type of resource eg: product, artistinfo or all. """ filter_resource_types = [] if resource_type in constants.RESOURCE_TYPE_LABEL: filter_resource_types.append(constants.VENDOR_RESOURCE_TYPE) filter_resource_types.append(constants.SUBACCOUNT_NEO4J_RESOURCE_TYPE) elif not resource_type == constants.RESOURCE_TYPE_ALL: filter_resource_types.append(resource_type) resources = resource.get_resources_of_type_for_profile_uuid(profile_uuid, filter_resource_types) if resource_type in [constants.RESOURCE_TYPE_ALL, constants.ARTIST_INFO_RESOURCE_TYPE]: # get corresponding ArtistInfo for LabelParticipants. artists = resource.get_artists_for_lp_by_profile_uuid(profile_uuid) resources.extend(artists) return response.Response(resources) def get_resources_for_identity_uuid(identity_uuid, limit, offset): """GET resources that this identity HAS_ADMIN_ACCESS_TO. Args: identity_uuid (str): Identity UUID. """ resources, total_records = resource.get_resources_for_identity_uuid( identity_uuid, limit, offset ) items = [] for each in resources: item = resource_utils.transform_resource_entity(each) if item: items.append(item) return response.Response( { 'items': items, 'pagination': { 'type': constants.PAGINATION_TYPE_STANDARD, 'total_records': total_records, }, } ) def get_user_profiles_for_admin( admin_context, profile_types=None, limit=constants.DEFAULT_USERS_LIMIT, offset=constants.DEFAULT_OFFSET, search_term=None, label_participants=None, active=None, pending=None, resource_access=None, parent_vendor_filter=None, feature_flag=False, include_subaccount_users=True, include_requester=False, ): """GET profiles from resources that this identity has admin access to. Args: admin_context (dict): Admin's context data. profile_types (list): Optional. Profile Type (ex. LabelProfile, InsightsProfile) limit (int): Optional. Limit number of records. Default 50. offset (int): Optional. Offset result set. Default 0. search_term (str): Optional. search term. Search is case-insensitive. label_participants (list): Optional. active (str): Optional. filter for active status. Eg. Y or N pending (str): Optional. filter for awaiting auth0 invite accepted. Eg. Y or N resource_access (list): Optional. Resource uuids. parent_vendor_filter (list): Optional. Filter results to parent vendor uuids. feature_flag (str): Optional. Feature flag. include_subaccount_users (bool): Optional. Whether to include subaccount users for D3 admins when "resource_access" is specified. Default is True. include_requester (bool): Optional. Whether to include the requesting user from the list of profiles returned. Default is False. Return: Response: containing dict of profile details or error message. """ can_access_vendor_star = profile.check_vendor_star_access(**admin_context) if not can_access_vendor_star: return resource.get_all_user_profiles_for_admin( admin_context['identity_id'], profile_types, limit, offset, search_term, label_participants, active, pending, resource_access, parent_vendor_filter, include_subaccount_users, include_requester, ) # should see all users return resource.get_all_profiles_with_artist_access( admin_context['identity_id'], profile_types, limit, offset, search_term, label_participants, active, pending, resource_access, feature_flag, ) def get_labels_for_artist_id(resource_type, artist_id): """Get a label for an artist id. Args: resource_type (str): ArtistInfo or LabelParticipant only. artist_id (int): ArtistInfo or LabelParticipant neo4j id. Return: Response: containing dict of label details or error message. """ return label.get_labels_for_artist_id(resource_type, artist_id) def get_resources(resource_type, resource_id): """GET a resources by type and id. Args: resource_id (str): Resource Identifier. resource_type (str): Type of resource eg: Product, ArtistInfo, etc. Return: Response: containing dict of resource details or error message. """ return resource.get_node(resource_type, resource_id) def create_resource(resource_type, resource_id, data): """Create a resource by id and type. Args: resource_type (str): Type of resource eg: product, artistinfo or all. resource_id (str): Resource identifier. data (dict): Additional information. Return: Response: containing dict of resource details or error message. """ if resource.get_node(resource_type, resource_id, True): return response.create_error_response( error.ERROR_CODE_VALIDATION_ERROR, 'Resource already exist.' ) return resource.create_node(resource_type, resource_id, data) def delete_resource(resource_type, resource_id): """Delete a resource by id and type. Args: resource_type (str): Type of resource eg: product, artistinfo or all. resource_id (str): Resource identifier. Return: Response: containing dict of resource details or error message. """ if not resource.get_node(resource_type, resource_id, True): return response.Response(status=204) return resource.delete_node(resource_type, resource_id) @tracer.wrap('logic.add_resource_to_profile') def add_resource_to_profile(add_schema: dict, admin_id: str) -> response.Response: """Add access to existing resources with role. Args: add_schema (dict): ResourceToProfileSchema data. Return: Response: containing dict of resource details or error message. """ prof_id, prof_type = add_schema['profile_id'], add_schema['profile_type'] set_full_catalog_access = False g.log.info( 'Adding resource to profile', resources={ 'resource_type': add_schema.get('resource_type'), 'resource_id': add_schema.get('resource_id'), 'profile_id': prof_id, 'profile_type': prof_type, 'admin_identity_id': admin_id, }, ) if ( add_schema.get('resource_type') == constants.VENDOR_RESOURCE_TYPE and add_schema.get('resource_id') == '*' and add_schema.get('profile_type') in [ constants.SETTINGSPROFILE, constants.INSIGHTSPROFILE, constants.SONGWHIPPROFILE, constants.DOCUMENTSPROFILE, constants.COLLABORATORSPROFILE, constants.CONTENTPROFILE, ] ): set_full_catalog_access = True if add_schema.get('profile_uuid'): result = resource.create_profile_uuid_to_resource_relationship( add_schema=add_schema, set_full_catalog_access=set_full_catalog_access, admin_id=admin_id, ) else: result = resource.create_profile_to_resource_relationship( add_schema=add_schema, set_full_catalog_access=set_full_catalog_access, admin_id=admin_id, ) redis_prefix = get_resources_key(prof_type, prof_id) redis.delete_all_matching_pattern(redis_prefix) return result def delete_resource_to_profile(delete_schema): """Delete profile's access to existing resources. Args: delete_schema (dict): ResourceToProfileSchema data. Return: Response: empty status 204 response or error message. """ prof_id, prof_type = delete_schema['profile_id'], delete_schema['profile_type'] if delete_schema.get('profile_uuid'): result = resource.soft_delete_profile_uuid_to_resource_relationship(delete_schema) else: result = resource.soft_delete_profile_to_resource_relationship(delete_schema) redis_prefix = get_resources_key(prof_type, prof_id) redis.delete_all_matching_pattern(redis_prefix) return result