"""Logic for all identity and profiles.""" from copy import deepcopy from connector_neo4j import get_session from ddtrace import tracer from flask import g from owsresponse import response from python_pdp_sdk import UnauthenticatedException from pythonfeatures import pythonfeatures from pythonfeatures.constants import split as split_constants from users import config, constants from users.models import identities, profiles from users.utils import authorization @tracer.wrap() def get_profiles_for_identity(orchard_identity_id, app_name, profile_types=None): """GET profiles from db for this orchard_identity that app_name supports. Args: orchard_identity_id (str): Orchard-Identity-Id (auth0 id). app_name (str): Application name eg: workstation, orchardgo etc. profile_types (arr): Optional list of profile types that should be permitted. """ if app_name not in constants.APPLICATION_PROFILE_TYPE_MAPPING.keys(): return response.create_error_response( 'invalid_app', f'Application {app_name} not supported.' ) filtered_list = {} if not profile_types: profile_types = [] for mapping in constants.APPLICATION_PROFILE_TYPE_MAPPING.get(app_name): if isinstance(mapping, str): profile_types.append(mapping) if isinstance(mapping, tuple): profile_types.extend(mapping) try: filtered_list.update(get_profile_and_roles_from_graph(orchard_identity_id, profile_types)) except Exception as err: raise err return response.Response( { 'items': list(filtered_list.values()), 'pagination': {'type': 'none', 'total_records': len(filtered_list)}, } ) @tracer.wrap() def get_all_profiles_for_identity(orchard_identity_id): """GET all profiles from db for this orchard_identity id. Args: orchard_identity_id (str): Orchard-Identity-Id of a user. """ all_profiles = profiles.get_profiles(orchard_identity_id) return response.Response( { 'items': list(all_profiles.message), 'pagination': {'type': 'none', 'total_records': len(all_profiles.message)}, } ) @tracer.wrap() def has_label_profile_access( orchard_identity_id: str, label_profile_id: str | int ) -> response.Response: """Check if this identity currently has access to the given LabelProfile. Uses the same query that feeds the Workstation account switcher dropdown (get_linked_label_profiles), so the access check cannot drift from the listing that generated the options. Args: orchard_identity_id (str): Identity uuid. label_profile_id (str|int): LabelProfile profileId (vend_contact id). Returns: Response: {'has_access': bool}. """ linked = profiles.get_linked_label_profiles(orchard_identity_id) target = str(label_profile_id) has_access = any(str(p.get('profile_id')) == target for p in linked) return response.Response({'has_access': has_access}) @tracer.wrap() def get_profile_and_roles_from_graph(orchard_identity, profile_types): """Get profile from graphdb and their roles. Args: orchard_identity (str): Identity (auth0 id). profile_types (list): List of profile types Return: dict: Dict containing supported profiles key by profile id. """ filtered_list = {} strict_app_access = ( pythonfeatures.get_single_feature_by_attributes( constants.FEATURE_STRICT_APP_ACCESS, { 'identity_id': orchard_identity, }, ).message == split_constants.FEATURE_ENABLED ) if strict_app_access: data = profiles.get_profiles_for_applications(orchard_identity) else: data = profiles.get_profiles(orchard_identity) # filter profiles supported by this application. profile_ids = [] for each_profile in data.message: if each_profile['profile_type'] in profile_types: profile_ids.append(each_profile['profile_id']) filtered_list[each_profile['profile_id']] = each_profile return filtered_list @tracer.wrap() def create_profile_if_not_exist(orchard_identity_id, payload): """Create a new profile if it does not exist, and add resource access. Note: It will reuse an existing profile of profile_type if it exists. Args: orchard_identity_id (str): Identity (or auth0 id for now). payload (dict): object representing the Profile to be created - profile_id (int): the id of the Profile relative to `profile_type` - profile_name (str): the name of the Profile - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) - roles (list): list of strings reprenting roles e.g. ['catalog', 'analytics'] - brand (str): the brand of the Profile Return: dict: Dict containing new profile. """ identity_result = identities.get_identity(orchard_identity_id) if not identity_result: raise Exception(identity_result.errors['message']) profiles_result = profiles.get_profiles_by_type( orchard_identity_id, payload.get('profile_type') ).message if profiles_result: # reuse existing profile but update roles profile = profiles_result.pop() roles = list(set().union(profile.get('roles', []), payload.get('roles', []))) roles.sort() updated_profile = profiles.update_profile( orchard_identity_id, profile.get('profile_id'), profile.get('profile_type'), {'roles': roles}, ) return updated_profile # create new and link profile to identity new_profile = profiles.create_profile(payload) if not new_profile: raise Exception(new_profile.errors['message']) profiles.link_identity_to_profile( orchard_identity_id, new_profile.message.get('profile_id'), new_profile.message.get('profile_type'), ) return new_profile @tracer.wrap() def grant_access(orchard_identity_id, profile, resource_type, resource_id): """Create a HAS_ACCESS_TO relation from this profile to resource. Args: orchard_identity_id (str): Identity identifier (in this case auth0 id) profile (dict): object representing the Profile to be created - profile_id (int): the id of the Profile relative to `profile_type` - profile_name (str): the name of the Profile - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) - roles (list): list of strings reprenting roles e.g. ['catalog', 'analytics'] resource_type (string): the type of Resource resource_id (int): the id of the Resource Return: dict: Dict containing. """ identity_result = identities.get_identity(orchard_identity_id).message return profiles.create_profile_to_resource_relationship( identity_result['id'], profile['profile_type'], profile['profile_id'], resource_type, resource_id, profile['roles'], ) @tracer.wrap() def create_profile(orchard_identity_id, payload): """Validate request, create profile, link to identity. Assumes the payload has been validated using `validate_request_data()` Args: orchard_identity_id (str): Identity (or auth0 id for now). payload (dict): object representing the Profile to be created - profile_id (int): the id of the Profile relative to `profile_type` - profile_name (str): the name of the Profile - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) - roles (list): list of strings reprenting roles e.g. ['catalog', 'analytics'] - brand (str): the brand of the Profile Returns: Response: dict of the newly created profile. """ # verify identity exists identity_result = identities.get_identity(orchard_identity_id) if not identity_result: g.log.warn( 'Error creating profile; identity not found', resources={ 'identity_id': orchard_identity_id, 'profile_type': payload.get('profile_type'), }, ) return identity_result # validate profile with same type and id does not exist if payload.get('profile_id') is not None: profile_result = profiles.get_by_profile_id_and_type( payload.get('profile_id'), payload.get('profile_type') ) if profile_result: return response.create_error_response( code=constants.ERROR_CODE_VALIDATION_ERROR, message=constants.ERROR_MESSAGE_PROFILE_EXISTS, ) # set brand to default brand if it exists if payload.get('brand') is None and identity_result.message.get('default_brand') is not None: payload['brand'] = identity_result.message.get('default_brand') if payload['brand'] == constants.AUTH0_ORCHARD_ORG_NAME: payload['brand'] = constants.ORCHARD_BRAND # create the profile profile_response = profiles.create_profile(payload) if not profile_response: g.log.warn( 'Error creating profile', resources={ 'identity_id': orchard_identity_id, 'profile_type': payload.get('profile_type'), }, ) return profile_response g.log.info( 'Profile created', resources={ 'identity_id': orchard_identity_id, 'profile_id': profile_response.message.get('profile_id'), 'profile_type': profile_response.message.get('profile_type'), }, ) # link profile to identity profiles.link_identity_to_profile( orchard_identity_id, profile_response.message.get('profile_id'), profile_response.message.get('profile_type'), ) g.log.info( 'Profile linked to identity', resources={ 'identity_id': orchard_identity_id, 'profile_id': profile_response.message.get('profile_id'), 'profile_type': profile_response.message.get('profile_type'), }, ) return profile_response @tracer.wrap() def get_profile(profile_id, profile_type): """Get a Profile by profile_id and profile_type. Args: - profile_id (int): the id of the Profile. - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) Returns: Response: dict of the profile. """ profile_result = profiles.get_by_profile_id_and_type(profile_id, profile_type) if not profile_result: return response.create_not_found_response(message=constants.ERROR_MESSAGE_PROFILE_NOT_FOUND) return profile_result @tracer.wrap() def get_profile_by_uuid(uuid): """Get a Profile by uuid. Args: uuid (str): the uuid of the Profile. Returns: Response: dict of the profile. """ profile_result = profiles.get_by_uuid(uuid) if not profile_result: return response.create_not_found_response(message=constants.ERROR_MESSAGE_PROFILE_NOT_FOUND) return profile_result @tracer.wrap() def delete_profile(profile_id, profile_type): """Delete profile if exists. Args: - profile_id (int): the id of the Profile. - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) Returns: Response: success with no message, or profile does not exist error. """ # validate profile type and id exists profile_result = profiles.get_by_profile_id_and_type(profile_id, profile_type) if not profile_result: return response.create_not_found_response(message=constants.ERROR_MESSAGE_PROFILE_NOT_FOUND) # delete the profile return profiles.delete_profile(profile_id, profile_type) @tracer.wrap() def delete_profile_by_uuid(profile_uuid): """Delete profile if exists for this UUID. Args: - profile_uuid (str): Optionally you can send only profile's 'uuid. Returns: Response: success with no message, or profile does not exist error. """ profile_result = profiles.get_by_uuid(profile_uuid) if not profile_result: return response.create_not_found_response(message=constants.ERROR_MESSAGE_PROFILE_NOT_FOUND) return profiles.delete_profile_by_uuid(profile_uuid) @tracer.wrap() def update_profile(profile_id, profile_type, payload): """Update profile if exists. Args: - profile_id (int): the id of the Profile. - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) - payload (dict) - profile_name (str): name for the Profile - roles (arr): list of strings as the roles for this Profile Returns: Response: dict of the updated Profile in the message """ # validate profile type and id exists profile_result = profiles.get_by_profile_id_and_type(profile_id, profile_type) if not profile_result: return response.create_not_found_response(message=constants.ERROR_MESSAGE_PROFILE_NOT_FOUND) profile_data = { 'profile_name': profile_result.message.get('profile_name'), 'roles': profile_result.message.get('roles'), } profile_data.update(payload) identity_response = identities.get_identity_for_profile(profile_id, profile_type) # update the profile return profiles.update_profile( identity_response.message.get('id'), profile_id, profile_type, profile_data ) @tracer.wrap() def update_profile_by_uuid(profile_uuid, payload): """Update profile if exists. Args: - profile_uuid (str): Optionally you can send only profile's 'uuid. - payload (dict) - profile_name (str): name for the Profile - roles (arr): list of strings as the roles for this Profile Returns: Response: dict of the updated Profile in the message """ profile_result = profiles.get_by_uuid(profile_uuid) if not profile_result: return response.create_not_found_response(message=constants.ERROR_MESSAGE_PROFILE_NOT_FOUND) profile_data = { 'profile_name': profile_result.message.get('profile_name'), 'roles': profile_result.message.get('roles'), } profile_data.update(payload) return profiles.update_profile_by_uuid(profile_uuid, profile_data) @tracer.wrap() def add_profile_to_identity(orchard_identity_id, profile_id, profile_type): """Link an existing identity to an existing profile. Args: - orchard_identity_id - profile_id (int): the id of the Profile. - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) """ # Check if profile exists profile_result = profiles.get_by_profile_id_and_type(profile_id, profile_type) if not profile_result: return profile_result # Check if relationship already exists identity_profiles = profiles.get_profiles(orchard_identity_id) for profile in identity_profiles.message: if profile.get('profile_id') == profile_id and profile.get('profile_type') == profile_type: return response.create_error_response( code=constants.ERROR_CODE_RELATIONSHIP_ALREADY_EXISTS, message=constants.ERROR_MESSAGE_RELATIONSHIP_ALREADY_EXISTS, ) return profiles.link_identity_to_profile(orchard_identity_id, profile_id, profile_type) @tracer.wrap() def add_profile_uuid_to_identity(orchard_identity_id, profile_uuid): """Link an existing identity to an existing profile. Args: - orchard_identity_id - profile_id (int): the id of the Profile. - profile_uuid (str): profile's 'uuid. """ profile_result = profiles.get_by_uuid(profile_uuid) if not profile_result: return profile_result # Check if relationship already exists identity_profiles = profiles.get_profiles(orchard_identity_id) for profile in identity_profiles.message: if profile.get('uuid') == profile_uuid: return response.create_error_response( code=constants.ERROR_CODE_RELATIONSHIP_ALREADY_EXISTS, message=constants.ERROR_MESSAGE_RELATIONSHIP_ALREADY_EXISTS, ) return profiles.link_identity_to_profile_by_uuid(orchard_identity_id, profile_uuid) @tracer.wrap() def delete_profile_to_identity(orchard_identity_id, profile_id, profile_type): """Delete existing identity to profile relationship. Args: delete_schema (dict): IdentityToProfileSchema data. Return: Response: empty status 204 response or error message. """ return profiles.soft_delete_profile_to_identity_relationship( orchard_identity_id, profile_id, profile_type ) @tracer.wrap() def delete_profile_uuid_to_identity(orchard_identity_id, profile_uuid): """Delete existing identity to profile relationship. Args: identity_id (str): Unique identity identifier. profile_uuid (str): Optionally you can send only profile's 'uuid. Return: Response: empty status 204 response or error message. """ return profiles.soft_delete_profile_to_identity_relationship_by_uuid( orchard_identity_id, profile_uuid ) @tracer.wrap() def create_identity_in_graph(identity_data): """Create identity. Args: identity_data: Identity object """ # verify identity doesn't exist identity_id = identity_data.get('identity_id') identity_email = identity_data.get('email') identity_exists = identities.get_identity(identity_id) if identity_exists: g.ows.log.info(f'create_identity because identity {identity_id}' 'already exists.') return response.create_error_response( code=constants.ERROR_CODE_IDENTITY_ALREADY_EXISTS, message=constants.ERROR_MESSAGE_IDENTITY_ALREADY_EXISTS, ) identity_exists = identities.get_identity_by_email(identity_email) if identity_exists: g.ows.log.info( f'create_identity because identity with email {identity_email}' 'already exists.' ) return response.create_error_response( code=constants.ERROR_CODE_IDENTITY_ALREADY_EXISTS, message=constants.ERROR_MESSAGE_IDENTITY_ALREADY_EXISTS_EMAIL, ) new_identity = identities.create_identity(identity_data) g.log.info( 'Identity created by create_identity_in_graph', resources={'identity_id': identity_id} ) # Create and link Settings Profile profiles.create_settings_profile( identity_data.get('identity_id'), identity_data.get('name'), identity_data.get('default_brand'), ) g.log.info( 'Settings profile created by create_identity_in_graph', resources={'identity_id': identity_id}, ) return new_identity @tracer.wrap() def get_identity_from_graph_tx(identity_id): """Get identity from graphdb using a Managed Transaction. Args: identity_id (str): Identity (auth0 id). """ session = get_session() return session.execute_read(identities.get_identity_tx, identity_id) @tracer.wrap() def get_identity_for_admin_tx(admin_context, identity_id): """Get identity if this admin has access to it. Args: admin_context (dict): Admin Identity data. identity_id(str): Identity uuid for user that is being viewed/edited. """ admin_is_super_admin = ( pythonfeatures.get_single_feature_by_attributes( constants.EDIT_SUPER_ADMINS, { 'identity_id': admin_context['identity_id'], }, ).message == split_constants.FEATURE_ENABLED ) if not admin_is_super_admin: # A SEAT admin can also view employees try: admin_is_super_admin = authorization.pdp_authorize_list_employees() except UnauthenticatedException: admin_is_super_admin = False session = get_session() # super admins have access to all users. if admin_context['identity_id'] == identity_id or admin_is_super_admin: return session.execute_read(identities.get_identity_tx, identity_id) return session.execute_read( identities.get_identity_for_admin_tx, admin_context, identity_id=identity_id ) @tracer.wrap() def get_identity_for_admin_by_email(admin_context, email): """Get identity for this email if this admin has access to it. Args: admin_context (dict): Admin Identity data. email(str): Identity email. """ return identities.get_identity_for_admin(admin_context, email=email) @tracer.wrap() def update_identity_in_graph(identity_id, update_identity_data): """Update identity. Args: identity_id (str): Identity (auth0_id). update_identity_data (dict): email (str): identity email name (str): full name first_name (str): User's first name. last_name (str): User's last name. localization (str): language preference number_format (str): Number format. date_format (str): Date format. Returns: Response: dict of updated identity. """ # validate identity exists if not identities.get_identity(identity_id): return response.create_error_response( code=constants.ERROR_CODE_IDENTITY_NOT_FOUND, message=constants.ERROR_MESSAGE_IDENTITY_NOT_FOUND, ) return identities.update_identity(identity_id, update_identity_data) @tracer.wrap() def update_identity_by_email(email, update_data): """Update identity attributes except id and email. Args: email (str): User Email. update_data (dict): data to be updated. Returns: Response: dict of updated identity. """ existing = identities.get_identity_by_email(email) if not existing: return response.create_error_response( code=constants.ERROR_CODE_IDENTITY_NOT_FOUND, message=constants.ERROR_MESSAGE_IDENTITY_NOT_FOUND, ) overwrite = {'identity_id': existing.message['id'], 'email': existing.message['email']} update_data.update(overwrite) return identities.update_identity(existing.message['id'], update_data) @tracer.wrap() def update_identity_and_id_by_email(email, update_data): """Update all identity attributes except email. Args: email (str): User Email. update_data (dict): data to be updated. Returns: Response: dict of updated identity. """ existing = identities.get_identity_by_email(email) if not existing: return response.create_error_response( code=constants.ERROR_CODE_IDENTITY_NOT_FOUND, message=constants.ERROR_MESSAGE_IDENTITY_NOT_FOUND, ) overwrite = {'email': existing.message['email']} update_data.update(overwrite) if update_data.get('identity_id'): update_data['id'] = update_data.get('identity_id') del update_data['identity_id'] return identities.update_identity_by_email(email, update_data) @tracer.wrap() def delete_identity_from_graph(identity_id): """Delete identity. Args: identity_id (str): Identity (auth0 id). Returns: Response: empty response with status 204 """ # verify identity exists identity_exists = identities.get_identity(identity_id) if not identity_exists: return response.create_error_response( code=constants.ERROR_CODE_IDENTITY_NOT_FOUND, message=constants.ERROR_MESSAGE_IDENTITY_NOT_FOUND, ) return identities.delete_identity(identity_id) @tracer.wrap() def get_identity_by_email(email): """Get identity node by email. Args: email (str): email address. Returns: Response: Dict containing identity details. """ return identities.get_identity_by_email(email) @tracer.wrap() def _apply_awal_branding(all_applications): """Apply AWAL branding to all applications.""" for _, app in all_applications.items(): app['url'] = app['url'].replace(config.ORCHARD_DOMAIN, config.AWAL_DOMAIN) @tracer.wrap() def _apply_knr_branding(all_applications): """Apply KNR branding to applications, except Documents. Insights redirects to SME.""" for profile, app in all_applications.items(): if profile == constants.DOCUMENTS_PROFILE: continue elif profile == constants.INSIGHTS_PROFILE: # KNR Insights does not exist, so redirect to SME app['url'] = app['url'].replace(config.ORCHARD_DOMAIN, config.SONY_DOMAIN) else: app['url'] = app['url'].replace(config.ORCHARD_DOMAIN, config.KNR_DOMAIN) @tracer.wrap() def _apply_sony_branding(all_applications): """Apply SME branding to Insights, Fansifter, and Settings applications only.""" for profile, app in all_applications.items(): if profile in ( constants.INSIGHTS_PROFILE, constants.AUDIENCE_PROFILE, constants.SETTINGS_PROFILE, ): app['url'] = app['url'].replace(config.ORCHARD_DOMAIN, config.SONY_DOMAIN) @tracer.wrap() def _get_all_branded_applications(brand): """Return a copy of PROFILE_TYPE_APPLICATION_MAPPING with branded urls.""" all_applications = deepcopy(config.PROFILE_TYPE_APPLICATION_MAPPING) if brand == constants.AWAL_BRAND: _apply_awal_branding(all_applications) return all_applications if brand == constants.KNR_BRAND: _apply_knr_branding(all_applications) return all_applications if brand == constants.SONY_BRAND: _apply_sony_branding(all_applications) return all_applications return config.PROFILE_TYPE_APPLICATION_MAPPING @tracer.wrap() def _get_extra_branded_applications(brand): """Get available extra applications for brand. Args: brand (str): brand name. Returns: Iterable of extra applications like: { 'name': 'Help & Support', 'roles': [], 'id': 'help-center', 'url': 'https://theorchard.zendesk.com/' } """ for app_id, application in config.EXTRA_APPS.items(): url = config.EXTRA_APPS_BRAND_URL_MAPPING.get(app_id, {}).get(brand) if not url: continue application_ = deepcopy(application) application_['id'] = app_id application_['url'] = url yield application_ @tracer.wrap() def _get_extra_application_for_identity(identity_id, brand): """Filter extra applications using feature flags. Args: identity_id (str): uuid brand (str): brand name e.g. 'theorchard', 'awal' etc. Returns: Iterable of filtered extra applications. """ attributes = { 'identity_id': identity_id, 'brand_name': brand, } supported_extra_apps = set() for ff_name, app_id in constants.EXTRA_APPS_FEATURE_FLAG_APP_MAPPING.items(): feature = pythonfeatures.get_single_feature_by_attributes(ff_name, attributes).message if feature == split_constants.FEATURE_ENABLED: supported_extra_apps.add(app_id) for application in _get_extra_branded_applications(brand): if application['id'] not in supported_extra_apps: continue yield application @tracer.wrap() def get_applications_for_identity_tx( identity_id, role=None, brand=constants.ORCHARD_BRAND, resource_type=None, resource_uuid=None ): """Get a list of applications for a given identity. Args: identity_id (str): uuid role (str): optionally filter by role brand (str): optionally get branded urls resource_type (str): optionally filter by resource resource_uuid (str): optionally filter by resource Returns: Response: a list of applications with role information in the format: { "items": [ { "name": "Application Name", # Display name of the application "id": "app-id", # Unique identifier for the application "url": "https://app.domain.com", # Application URL with brand domain "roles": ["role1", "role2"] # User's roles for this application }, ... ], "pagination": { "type": "none", "total_records": } } """ # settings mvp: Insights, Workstation, Moneyhub, and Settings supported. supported_profiles = constants.APPLICATIONS_SUPPORTED_PROFILES.copy() show_songwhip = pythonfeatures.get_single_feature_by_attributes( constants.FEATURE_SHOW_SONGWHIP_APP, {'identity_id': identity_id} ) if show_songwhip.message != 'enabled': supported_profiles.remove(constants.SONGWHIP_PROFILE) show_account360_app_switcher = pythonfeatures.get_single_feature_by_attributes( constants.FEATURE_ACCOUNT360_APP_SWITCHER, {'identity_id': identity_id} ) if show_account360_app_switcher.message != 'enabled': supported_profiles.remove(constants.ACCOUNT360_PROFILE) # Depending of the endpoint's Neo4jSession transaction=True/False session_or_tx = get_session() if hasattr(session_or_tx, 'run'): # does not have transaction enabled data = profiles.get_profiles_for_applications_tx( session_or_tx, identity_id, resource_type, resource_uuid ) else: data = session_or_tx.execute_read( profiles.get_profiles_for_applications_tx, identity_id, resource_type, resource_uuid ) identity_profiles = {} for each_profile in data.message: profile_type = each_profile['profile_type'] # merge info if user has multiple profiles of the same type if profile_type in identity_profiles.keys() and each_profile.get('roles'): identity_profiles[profile_type]['roles'].extend( x for x in each_profile['roles'] if x not in identity_profiles[profile_type]['roles'] ) elif profile_type in supported_profiles: identity_profiles[each_profile['profile_type']] = each_profile all_applications = _get_all_branded_applications(brand) identity_applications = {k: all_applications[k] for k in (identity_profiles)} applications_info = {} for profile in identity_profiles.values(): # filter by role, if provided if role and (not profile.get('roles') or role not in profile['roles']): continue application = deepcopy(identity_applications.get(profile['profile_type'])) if application['name'] not in applications_info.keys(): applications_info[application['name']] = application # merged all profile's roles at application level. if 'roles' not in application: application['roles'] = [] if 'roles' in profile: application['roles'].extend(profile['roles']) application['roles'].sort() # If user has Account360Profile, add ABACUS app with A360 roles for each_profile in data.message: if ( each_profile['profile_type'] == constants.ACCOUNT360_PROFILE and 'Abacus' not in applications_info ): abacus_app = deepcopy(all_applications.get(constants.ABACUS_PROFILE)) if abacus_app: abacus_app['roles'] = each_profile.get('roles', []) applications_info['Abacus'] = abacus_app break for application in _get_extra_application_for_identity(identity_id, brand): applications_info[application['name']] = application return response.Response( { 'items': list(applications_info.values()), 'pagination': {'type': 'none', 'total_records': len(applications_info)}, } ) @tracer.wrap() def get_identity_for_profile(profile_id, profile_type): """Get identity from graph for given profile_id and profile_type. Args: - profile_id (int): the id of the Profile. - profile_type (str): the type of profile e.g. (LabelProfile, ArtistProfile, etc) Returns: Response: dict of the identity. """ return identities.get_identity_for_profile(profile_id, profile_type) @tracer.wrap() def get_identity_for_profile_uuid(profile_uuid): """Get identity from graph for given its uuid. Args: - profile_uuid (str): Optionally you can send only profile's 'uuid. Returns: Response: dict of the identity. """ return identities.get_identity_for_profile_uuid(profile_uuid) @tracer.wrap() def get_identity_by_auth0_id(auth0_id): """Get Identity graph node associated with this auth0_id. Args: auth0_id (str): Auth0 user id. Returns: Response: dict of the identity. """ return identities.get_identity_by_auth0_id(auth0_id)