class DomainTagResource(Resource): decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAINS) @time_me(logger, 'DomainTags') def get(self, **kwargs): """ Function will return all tags belonging to domain Note: "description" is a required field Endpoint: POST /v1/users/tags Usage: >>> headers = {'Authorization': 'Bearer {access_token}'} >>> requests.get(url="host/v1/domains/4/tags", headers=headers) """ # Retrieve tags belonging to domain # TODO: Should link Tag to domain domain_tags = Tag.query.join(CandidateTag, Candidate, User). \ filter(Candidate.user_id == User.id, User.domain_id == request.user.domain_id). \ filter(CandidateTag.candidate_id == Candidate.id).all() return { 'domain_tags': [{ 'id': tag.id, 'name': tag.name, 'added_datetime': DatetimeUtils.to_utc_str(tag.added_datetime), 'updated_datetime': DatetimeUtils.to_utc_str(tag.updated_datetime), } for tag in domain_tags] }
class UserInviteApi(Resource): # Access token and role authentication decorators decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_ADD_USERS) def post(self, **kwargs): """ POST /users/<id>/invite This endpoint will send invitation email to an already existing user :param kwargs: :return: None """ requested_user_id = kwargs.get('id') requested_user = User.get(requested_user_id) if not requested_user: raise NotFoundError("User with user id %s is not found" % requested_user_id) temp_password = gen_salt(8) requested_user.password = gettalent_generate_password_hash(temp_password) requested_user.password_reset_time = datetime.utcnow() requested_user.is_disabled = 0 requested_user.registration_id = 'Invited' db.session.commit() send_new_account_email(requested_user.email, temp_password, requested_user.email) return '', 201
class UserGroupsApi(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAIN_GROUPS) def get(self, **kwargs): """ GET /groups/<group_id>/users?include_stats=True Fetch all users in a user_group and also include stats :return A dictionary containing id and lastName of all users of a user_group :rtype: dict """ requested_group_id = kwargs.get('group_id') include_stats_flag = request.args.get('include_stats', False) requested_group = UserGroup.query.get(requested_group_id) if not requested_group: raise NotFoundError("Group with group_id %s doesn't exist" % requested_group_id) if request.user.role.name != 'TALENT_ADMIN' and requested_group.domain_id != request.user.domain_id: raise UnauthorizedError("User %s doesn't have appropriate permission to add users to a " "group %s" % (request.user.id, requested_group_id)) users_data_dict = {user.id: user.to_dict() for user in UserGroup.all_users_of_group(requested_group_id)} return {"users": get_users_stats_from_mixpanel(users_data_dict, False, include_stats_flag).values()} @require_any_permission(Permission.PermissionNames.CAN_ADD_DOMAIN_GROUPS) def post(self, **kwargs): """ POST /groups/<group_id>/users Add users in a given user_group :return A dictionary containing success message :rtype: dict """ requested_group_id = kwargs.get('group_id') requested_group = UserGroup.query.get(requested_group_id) if not requested_group: raise NotFoundError("Group with group_id %s doesn't exist" % requested_group_id) posted_data = request.get_json(silent=True) if not posted_data or 'user_ids' not in posted_data: raise InvalidUsage("Request body is empty or not provided") if request.user.role.name != 'TALENT_ADMIN' and requested_group.domain_id != request.user.domain_id: raise UnauthorizedError("User %s doesn't have appropriate permission to add users to a " "group %s" % (request.user.id, requested_group_id)) UserGroup.add_users_to_group(requested_group, posted_data.get('user_ids')) return '', 201
class DomainSourceResource(Resource): decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAINS) def post(self, **kwargs): """ Function will create a source for domain Note: "description" is a required field Endpoint: POST /v1/sources :return: {'source': {'id': int}} Usage: >>> headers = {'Authorization': 'Bearer {access_token}', 'content-type': 'application/json'} >>> data = {"source": {"description": "job fair", "notes": "recruiter initials: ahb"}} >>> requests.post(url="host/v1/sources", headers=headers, data=json.dumps(data)) """ authed_user = request.user # Authenticated user domain_id = authed_user.domain_id # user's domain ID # Validate and obtain json data from request body body_dict = get_json_data_if_validated(request_body=request, json_schema=source_schema, format_checker=False) # Normalize description & notes source = body_dict['source'] description = source['description'].encode('utf-8').strip().lower() notes = (source.get('notes') or '').strip().lower() # In case description is just a whitespace if not description: raise InvalidUsage("Source description is a required field") # Prevent duplicate sources for the same domain source = CandidateSource.get_by(domain_id=domain_id, description=description) if source: raise InvalidUsage( "Source (description: {}) already exists for domain: {}". format(description, domain_id), additional_error_info=dict(source_id=source.id)) new_source = CandidateSource(description=description, notes=notes, domain_id=domain_id, added_datetime=datetime.datetime.utcnow()) db.session.add(new_source) db.session.commit() return {'source': {'id': new_source.id}}, requests.codes.CREATED @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAINS) def get(self, **kwargs): """ Function will return domain source(s) Endpoints: i. GET /v1/sources ii. GET /v1/sources/:id Example: >>> url = 'host/v1/sources' >>> headers = {'Authorization': 'Bearer {access_token}'} >>> requests.get(url=url, headers=headers) :returns: {'source': CandidateSource} if source_id is provided in url {'source': [CandidateSource, CandidateSource, ...]} if source_id is not provided in url """ # Authenticated user & source ID authed_user, source_id = request.user, kwargs.get('id') domain_id = authed_user.domain_id # User's domain ID # Return a single source If source ID is provided if source_id is not None: source = CandidateSource.get(source_id) # Source ID must be recognized if not source_id: raise InvalidUsage( "Source ID ({}) not recognized.".format(source_id)) # Source must belong to user's domain if source.domain_id != authed_user.domain_id: raise ForbiddenError( "Source (ID: {}) does not belong to user's domain (ID: )". format(source_id, domain_id)) return { 'source': { 'id': source.id, 'description': source.description, 'notes': source.notes, 'domain_id': source.domain_id, 'added_datetime': DatetimeUtils.utc_isoformat(source.added_datetime) if source.added_datetime else None } } # Get all of user's domain sources return { 'sources': [{ 'id': source.id, 'description': source.description, 'notes': source.notes, 'domain_id': source.domain_id, 'added_datetime': DatetimeUtils.utc_isoformat(source.added_datetime) if source.added_datetime else None } for source in CandidateSource.domain_sources(domain_id=domain_id)] }
class DomainCustomFieldCategoriesResource(Resource): decorators = [require_oauth()] # TODO: update docstring @require_all_permissions( Permission.PermissionNames.CAN_ADD_DOMAIN_CUSTOM_FIELDS) def post(self): """ Function will create custom field categories for user's domain resource url: POST /v1/custom_field_categories :return: {"custom_field_categories": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> data = { >>> "custom_fields": [ >>> { >>> "name": "color", >>> "categories": [ >>> { >>> "name": "red", >>> "subcategories": [ >>> {"name": "bright"}, {"name": "dark"} >>> ] >>> }, >>> { >>> "name": "green", >>> "subcategories": [ >>> {"name": "bright"}, {"name": "dark"} >>> ] >>> } >>> ] >>> } >>> ] >>> } >>> headers = {"Authorization": "Bearer {access_token}", "content-type": "application/json"} >>> requests.post(url="host/v1/custom_field_categories", data=json.dumps(data), headers=headers) <Response [201]> """ # Validate and obtain json data from request body body_dict = get_json_data_if_validated(request, cf_schema_post, False) return { "custom_fields": [{ "id": cf_id } for cf_id in add_or_update_custom_fields( custom_fields_data=body_dict['custom_fields'], domain_id=request.user.domain_id, is_creating=True)] }, requests.codes.CREATED # TODO: update docstring @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAINS) def get(self, **kwargs): """ Function will return domain's custom field category(ies) resource url: i. GET /v1/custom_field_categories ii. GET /v1/custom_field_categories/:id :return: {"custom_field_categories": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> headers = {"Authorization": "Bearer {access_token}"} >>> requests.get(url="host/v1/custom_field_categories", headers=headers) <Response [200]> """ return retrieve_domain_custom_fields(domain_id=request.user.domain_id, custom_field_id=kwargs.get('id')) # TODO: update docstring @require_all_permissions( Permission.PermissionNames.CAN_EDIT_DOMAIN_CUSTOM_FIELDS) def patch(self, **kwargs): """ Function will update domain's custom field category(ies) resource url: i. PATCH /v1/custom_field_categories :return: {"custom_fields": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> data = {"custom_fields": [{"id": 5, "name": "Marketing"}, {"id": 6, "name": "Sales"}]} >>> headers = {"Authorization": "Bearer {access_token}"} >>> requests.get(url="host/v1/custom_field_categories", headers=headers) <Response [200]> """ # Validate and obtain json data from request body body_dict = get_json_data_if_validated(request, cf_schema_patch, False) return { "custom_fields": [{ "id": cf_id } for cf_id in add_or_update_custom_fields( body_dict['custom_fields'], request.user.domain_id)] }
class DomainAreaOfInterestResource(Resource): decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAINS) def post(self): """ Function will create area(s) of interest for user's domain :return: {"areas_of_interest": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> data = {"areas_of_interest": [{"description": "finance"}]} >>> headers = {"Authorization": "Bearer {access_token}", "content-type": "application/json"} >>> requests.post(url="host/v1/areas_of_interest", data=json.dumps(data), headers=headers) <Response [201]> """ # Authenticated user authed_user = request.user domain_id = authed_user.domain_id # Validate and get json data from request body body_dict = get_json_data_if_validated(request_body=request, json_schema=aoi_schema, format_checker=False) created_aoi_ids = create_or_update_domain_aois(domain_id, body_dict['areas_of_interest'], is_creating=True) return {"areas_of_interest": [{"id": cf_id} for cf_id in created_aoi_ids]}, 201 @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAINS) def get(self, **kwargs): """ Function will return domain's area(s) of interest resource url: /v1/custom_fields :return: {"areas_of_interest": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> headers = {"Authorization": "Bearer {access_token}"} >>> requests.get(url="host/v1/custom_fields", headers=headers) <Response [200]> """ aoi_id = kwargs.get('id') if aoi_id: return {"area_of_interest": retrieve_domain_aoi(request.user.domain_id, aoi_id)} return {"areas_of_interest": [ { "id": aoi.id, "domain_id": aoi.domain_id, "description": aoi.name } for aoi in AreaOfInterest.get_domain_areas_of_interest(request.user.domain_id)]} @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAINS) def put(self, **kwargs): """ Function will update area(s) of interest for user's domain :return: {"areas_of_interest": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> data = {"areas_of_interest": [{"id": 23, "description": "finance"}]} >>> headers = {"Authorization": "Bearer {access_token}", "content-type": "application/json"} >>> requests.put(url="host/v1/areas_of_interest", data=json.dumps(data), headers=headers) <Response [200]> """ # Validate and get json data from request body body_dict = get_json_data_if_validated(request_body=request, json_schema=aoi_schema, format_checker=False) areas_of_interest = body_dict['areas_of_interest'] # required field, validated via json-schema domain_id = request.user.domain_id aoi_id_from_url = kwargs.get('id') # Only one record may be updated if aoi_id_from_url is provided if aoi_id_from_url and (len(areas_of_interest) > 1): raise InvalidUsage("Invalid usage") updated_aoi_ids = create_or_update_domain_aois(domain_id=domain_id, aois=body_dict['areas_of_interest'], aoi_id_from_url=aoi_id_from_url, is_updating=True) return {"areas_of_interest": [{"id": aoi_id} for aoi_id in updated_aoi_ids]} @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAINS) def delete(self, **kwargs): """ Function will delete domain area(s) of interest Endpoints: i. DELETE /v1/areas_of_interest => will delete all of domain's aois ii. DELETE /v1/areas_of_interest/:id => will delete a single aoi Usage: >>> headers = {"Authorization": "Bearer {access_token}", "content-type": "application/json"} >>> requests.delete(url="host/v1/areas_of_interest", headers=headers) <Response [200]> :return: {"areas_of_interest": [{"id": int}, {"id": int}, ...]} """ aoi_id = kwargs.get('id') if aoi_id: # delete specified area of interest delete_domain_aoi(request.user.domain_id, aoi_id) return {"area_of_interest": {"id": aoi_id}} else: # delete all of domain's areas of interest deleted_aoi_ids = delete_domain_aois(request.user.domain_id) return {"areas_of_interest": [{"id": aoi_id} for aoi_id in deleted_aoi_ids]}
class UserApi(Resource): # Access token and role authentication decorators decorators = [require_oauth()] # 'SELF' is for readability. It means this endpoint will be accessible to any user @require_all_permissions(Permission.PermissionNames.CAN_GET_USERS) def get(self, **kwargs): """ GET /users/<id> Fetch user object with user's basic info GET /users?domain_id=1 Fetch all user ids of a given domain GET /users?domain_id=1&include_stats=True Fetch all user ids of a given domain and also include stats :return A dictionary containing user basic info except safety critical info or a dictionary containing all user_ids of a domain :rtype: dict """ requested_user_id = kwargs.get('id') include_stats_flag = request.args.get('include_stats', False) if requested_user_id: requested_user = User.query.get(requested_user_id) if not requested_user: raise NotFoundError("User with user id %s not found" % requested_user_id) if request.user.role.name != 'TALENT_ADMIN' and requested_user.domain_id != request.user.domain_id: raise UnauthorizedError("User %s doesn't have appropriate permission to get user %s" % ( request.user.id, requested_user_id)) return { 'user': get_users_stats_from_mixpanel(requested_user.to_dict(), True, include_stats_flag) } # User id is not provided so logged-in user wants to get all users of its domain else: domain_id = request.user.domain_id # Get all users of any domain if user is `TALENT_ADMIN` if request.user.role.name == 'TALENT_ADMIN' and request.args.get('domain_id'): domain_id = request.args.get('domain_id') if not is_number(domain_id) or not Domain.query.get(int(domain_id)): raise InvalidUsage("Invalid Domain Id is provided") users_data_dict = {user.id: user.to_dict() for user in User.all_users_of_domain(int(domain_id))} return {'users': get_users_stats_from_mixpanel(users_data_dict, False, include_stats_flag).values()} @require_all_permissions(Permission.PermissionNames.CAN_ADD_USERS) def post(self): """ POST /users Create a new user input: {'users': [userDict1, userDict2, userDict3, ... ]} Take a JSON dictionary containing array of User dictionaries A single user dict must contain user's first name, last name, and email :return: A dictionary containing array of user ids :rtype: dict """ posted_data = request.get_json(silent=True) if not posted_data or 'users' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) users = posted_data['users'] # User object(s) must be in a list if not isinstance(users, list): raise InvalidUsage("Request body is not properly formatted") for user_dict in users: first_name = user_dict.get('first_name', "").strip() last_name = user_dict.get('last_name', "").strip() email = user_dict.get('email', "").strip() if not first_name or not last_name or not email: raise InvalidUsage("first name, last name or email is missing in request body") if not is_valid_email(email=email): raise InvalidUsage("Email Address %s is not properly formatted" % email) # Check if user already exist already_existing_user = check_if_user_exists(email) if already_existing_user: if already_existing_user.is_disabled: raise InvalidUsage("User with email=%s already exists in Database but is disabled" % email) else: raise InvalidUsage("User with email=%s already exists in Database" % email) if request.user.role.name == 'TALENT_ADMIN': domain_id = user_dict.get('domain_id', request.user.domain_id) else: domain_id = request.user.domain_id user_dict['domain_id'] = domain_id role = user_dict.get('role', '') user_dict['role_id'] = validate_role(role, domain_id, request.user) if role else '' user_ids = [] # Newly created user object's id(s) are appended to this list for user_dict in users: first_name = user_dict.get('first_name', "").strip() last_name = user_dict.get('last_name', "").strip() email = user_dict.get('email', "").strip() phone = user_dict.get('phone', "").strip() dice_user_id = user_dict.get('dice_user_id') thumbnail_url = user_dict.get('thumbnail_url', '').strip() user_group_id = user_dict.get('user_group_id') locale = user_dict.get('locale', 'en-US') domain_id = user_dict.get('domain_id') role_id = user_dict.get('role_id', '') try: Locale.parse(locale, sep='-') except: raise InvalidUsage('A valid Locale value should be provided') user_id = create_user_for_company(first_name=first_name, last_name=last_name, email=email, phone=phone, domain_id=int(domain_id), dice_user_id=dice_user_id, thumbnail_url=thumbnail_url, user_group_id=user_group_id, locale=locale, role_id=role_id) user_ids.append(user_id) return {'users': user_ids} # 'SELF' is for readability. It means this endpoint will be accessible to any user @require_all_permissions(Permission.PermissionNames.CAN_EDIT_USERS) def put(self, **kwargs): """ PUT /users/<id> Function will change credentials of one user per request. User will be allowed to modify itself :return: {'updated_user' {'id': user_id}} :rtype: dict """ requested_user_id = kwargs.get('id') requested_user = User.query.get(requested_user_id) if requested_user_id else None if not requested_user: raise NotFoundError("Either user_id is not provided or user doesn't exist") posted_data = request.get_json(silent=True) if not posted_data: raise InvalidUsage("Request body is empty or not provided") if request.user.role.name != 'TALENT_ADMIN' and requested_user.domain_id != request.user.domain_id: raise UnauthorizedError("Logged-in user doesn't have appropriate permissions to edit a user") if request.user.role.name == 'USER' and requested_user_id != request.user.id: raise UnauthorizedError("Logged-in user doesn't have appropriate permissions to edit a user") first_name = posted_data.get('first_name', '').strip() last_name = posted_data.get('last_name', '').strip() email = posted_data.get('email', '').strip() phone = posted_data.get('phone', '').strip() thumbnail_url = posted_data.get('thumbnail_url', '').strip() last_read_datetime = posted_data.get('last_read_datetime', '').strip() is_disabled = posted_data.get('is_disabled', 0) locale = posted_data.get('locale', '') try: last_read_datetime = parser.parse(last_read_datetime) except Exception as e: raise InvalidUsage("Last read datetime %s is invalid because: %s" % (last_read_datetime, e.message)) if email and not is_valid_email(email=email): raise InvalidUsage("Email Address %s is not properly formatted" % email) if email and check_if_user_exists(email) and requested_user.email != email: raise InvalidUsage("Email Address %s already exists" % email) if not is_number(is_disabled) or (int(is_disabled) != 0 and int(is_disabled) != 1): raise InvalidUsage("Possible vaues of `is_disabled` are 0 and 1") is_disabled = int(is_disabled) if locale: try: Locale.parse(locale, sep='-') except: raise InvalidUsage('A valid Locale value should be provided') # Update user update_user_dict = { 'first_name': first_name, 'last_name': last_name, 'email': email, 'phone': phone, 'thumbnail_url': thumbnail_url, 'last_read_datetime': last_read_datetime, 'is_disabled': is_disabled, 'locale': locale } update_user_dict = dict((k, v) for k, v in update_user_dict.iteritems() if v) if is_disabled: update_user_dict['registration_id'] = '' User.query.filter(User.id == requested_user_id).update(update_user_dict) db.session.commit() if is_disabled: # Delete all tokens of deleted user request.user.password_reset_time = datetime.utcnow() tokens = Token.query.filter_by(user_id=requested_user_id).all() for token in tokens: token.delete() return {'updated_user': {'id': requested_user_id}}
class DomainCustomFieldsResource(Resource): decorators = [require_oauth()] @require_all_permissions( Permission.PermissionNames.CAN_ADD_DOMAIN_CUSTOM_FIELDS) def post(self): """ Function will create custom field(s) for user's domain resource url: POST /v1/custom_fields :return: {"custom_fields": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> data = {"custom_fields": [{"category_id": 1, "name": "job status"}]} >>> headers = {"Authorization": "Bearer {access_token}", "content-type": "application/json"} >>> requests.post(url="host/v1/custom_fields", data=json.dumps(data), headers=headers) <Response [201]> """ # Validate and obtain json data from request body body_dict = get_json_data_if_validated(request, custom_fields_schema, False) return { "custom_fields": create_custom_fields(body_dict['custom_fields'], request.user.domain_id) }, requests.codes.CREATED @require_all_permissions( Permission.PermissionNames.CAN_GET_DOMAIN_CUSTOM_FIELDS) def get(self, **kwargs): """ Function will return domain's custom field(s) resource url: i. GET /v1/custom_fields ii. GET /v1/custom_fields/:id :return: {"custom_fields": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> headers = {"Authorization": "Bearer {access_token}"} >>> requests.get(url="host/v1/custom_fields", headers=headers) <Response [200]> """ custom_field_id = kwargs.get('id') # Return specified custom field data if custom_field_id: # Custom field ID must be recognized & belong to user's domain custom_field = get_custom_field_if_validated( custom_field_id, request.user) return { 'custom_field': { 'id': custom_field.id, 'domain_id': custom_field.domain_id, 'name': custom_field.name, 'added_datetime': str(custom_field.added_time), "type": custom_field.type, } } # Return all or filtered domain's custom fields # Authenticated user authed_user = request.user search_query = request.args.get('query', '') sort_type = request.args.get('sort_type', 'DESC') sort_by = request.args.get('sort_by', 'added_time') cf_type = request.args.get('type', 'all') query_object = CustomField.get_by_domain_id_and_filter_by_name_and_type( authed_user.domain.id, search_query, sort_by, sort_type, cf_type) items = [field.to_json() for field in query_object] response = {'custom_fields': items} return ApiResponse(response) @require_all_permissions( Permission.PermissionNames.CAN_EDIT_DOMAIN_CUSTOM_FIELDS) def put(self, **kwargs): """ Function will update domain's custom field(s) resource url: i. PUT /v1/custom_fields ii. PUT /v1/custom_fields/:id :return: {"custom_fields": [{"id": int}, {"id": int}, ...]} :rtype: dict[list[dict]] Usage: >>> headers = {"Authorization": "Bearer {access_token}"} >>> data = {"custom_fields": [{"id": 4, "name": "job status"}]} >>> requests.put(url="host/v1/custom_fields", headers=headers, data=json.dumps(data)) <Response [200]> """ custom_field_id = kwargs.get('id') # Validate and obtain json data from request body schema = custom_field_schema if custom_field_id else custom_fields_schema body_dict = get_json_data_if_validated(request, schema, False) # Update specified custom field if custom_field_id: # Custom field ID must be recognized & belong to user's domain get_custom_field_if_validated(custom_field_id, request.user) # Remove whitespace(s) from custom field name custom_field_name = body_dict['custom_field']['name'].strip() if not custom_field_name: # In case name is just a whitespace raise InvalidUsage( "Name is required for creating custom field.") custom_field_query = CustomField.query.filter_by( id=custom_field_id) custom_field_query.update(dict(name=custom_field_name)) db.session.commit() return {'custom_field': {'id': custom_field_id}} updated_custom_field_ids = [] for custom_field in body_dict['custom_fields']: # Custom field ID must be provided custom_field_id = custom_field.get('id') if not custom_field_id: raise InvalidUsage("Custom field ID is required for updating.") # Remove whitespace(s) from custom field name custom_field_name = custom_field['name'].strip() if not custom_field_name: # In case name is just a whitespace raise InvalidUsage( "Name is required for creating custom field.") custom_field_query = CustomField.query.filter_by( id=custom_field_id) # Custom field ID must be recognized cf_object = custom_field_query.first() if not cf_object: raise NotFoundError( 'Custom field ID ({}) not recognized.'.format( custom_field_id)) # Custom field must belong to user's domain if cf_object.domain_id != request.user.domain_id: raise ForbiddenError('Not authorized') custom_field_query.update(dict(name=custom_field_name)) updated_custom_field_ids.append(custom_field_id) db.session.commit() return { 'custom_fields': [{ 'id': custom_field_id } for custom_field_id in updated_custom_field_ids] } @require_all_permissions( Permission.PermissionNames.CAN_DELETE_DOMAIN_CUSTOM_FIELDS) def delete(self, **kwargs): """ Function will delete domain's custom field(s) resource url: i. DELETE /v1/custom_fields/:id :return: {"custom_field": {"id": int}} :rtype: dict[dict] Usage: >>> headers = {"Authorization": "Bearer {access_token}"} >>> requests.delete(url="host/v1/custom_fields/4", headers=headers) <Response [200]> """ custom_field_id = kwargs.get('id') # Delete specified custom field if custom_field_id: # Custom field ID must be recognized custom_field = CustomField.get(custom_field_id) if not custom_field: raise NotFoundError( "Custom field ID ({}) not recognized.".format( custom_field_id)) # Custom field must belong to user's domain if request.user.role.name != 'TALENT_ADMIN' and custom_field.domain_id != request.user.domain_id: raise ForbiddenError("Not authorized") custom_field_candidate_ids = CandidateCustomField.query.with_entities( CandidateCustomField.candidate_id).filter_by( custom_field_id=custom_field_id).all() custom_field_candidate_ids = map( lambda custom_field_object: custom_field_object[0], custom_field_candidate_ids) update_candidates_on_cloudsearch(request.oauth_token, custom_field_candidate_ids) db.session.delete(custom_field) db.session.commit() return {'custom_field': {'id': custom_field_id}}
class DomainApi(Resource): # Access token and role authentication decorators decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAINS) def get(self, **kwargs): """ GET /domains/<id> Fetch domain object with domain's basic info GET /domains Fetch all domain objects with domain's basic info :return A dictionary containing domain info except safety critical info :rtype: dict """ requested_domain_id = kwargs.get('id') if requested_domain_id: requested_domain = Domain.query.get(requested_domain_id) if not requested_domain: raise NotFoundError("Domain with domain id %s not found" % requested_domain_id) if requested_domain_id == request.user.domain_id or request.user.role.name == 'TALENT_ADMIN': return {'domain': requested_domain.to_dict()} elif request.user.role.name == 'TALENT_ADMIN': is_disabled = request.args.get('is_disabled', 0) search_keyword = request.args.get('search', '').strip() if not is_number(is_disabled) or int(is_disabled) not in (0, 1): raise InvalidUsage('`is_disabled` can be either 0 or 1') domains = Domain.query.filter( Domain.is_disabled == is_disabled, Domain.name.ilike('%' + search_keyword + '%')).all() return {'domains': [domain.to_dict() for domain in domains]} raise UnauthorizedError( "Either logged-in user belongs to different domain as requested_domain or it doesn't " "have appropriate permissions") @require_all_permissions(Permission.PermissionNames.CAN_ADD_DOMAINS) def post(self): """ POST /domains Create a new Domain input: {'domains': [domain1, domain2, domain3, ... ]} Take a JSON dictionary containing array of Domain dictionaries A single domain dict must contain at least domain's name :return: A dictionary containing array of user ids :rtype: dict """ posted_data = request.get_json(silent=True) if not posted_data or 'domains' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save domain object(s) domains = posted_data['domains'] # Domain object(s) must be in a list if not isinstance(domains, list): raise InvalidUsage("Request body is not properly formatted") for index, domain_dict in enumerate(domains): name = domain_dict.get('name', '') default_culture_id = domain_dict.get('default_culture_id', '') expiration = domain_dict.get('expiration', '') if not name: raise InvalidUsage("Domain name should be provided") domain = Domain.query.filter_by(name=name).first() # If domain already exists then raise an exception if domain: if not domain.is_disabled: raise InvalidUsage("Domain %s already exist" % name) else: domain.is_disabled = 0 db.session.commit() domains.pop(index) continue # If Culture doesn't exist in database if default_culture_id and not Culture.query.get( default_culture_id): raise InvalidUsage("Culture %s doesn't exist" % default_culture_id) if expiration: try: parser.parse(expiration) except Exception as e: raise InvalidUsage("Expiration Time is not valid as: %s" % e.message) domain_ids = [ ] # Newly created user object's id(s) are appended to this list for domain_dict in domains: name = domain_dict.get('name', '') expiration = domain_dict.get('expiration', '') default_culture_id = domain_dict.get('default_culture_id', 1) default_tracking_code = domain_dict.get('default_tracking_code', None) dice_company_id = domain_dict.get('dice_company_id', None) expiration = parser.parse(expiration) if expiration else "" domain_id = get_or_create_domain( logged_in_user_id=request.user.id, name=name, expiration=expiration, default_culture_id=default_culture_id, default_tracking_code=default_tracking_code, dice_company_id=dice_company_id) domain_ids.append(domain_id) return {'domains': domain_ids} @require_all_permissions(Permission.PermissionNames.CAN_DELETE_DOMAINS) def delete(self, **kwargs): """ DELETE /domains/<id> Function will disable domain-object in db :return: {'deleted_domain' {'id': domain_id}} :rtype: dict """ domain_id_to_delete = kwargs.get('id') if not domain_id_to_delete: raise InvalidUsage("Domain id is not provided") # Return 404 if requested user does not exist domain_to_delete = Domain.query.get(domain_id_to_delete) if not domain_to_delete: raise NotFoundError( "Requested domain with domain_id %s doesn't exist" % domain_id_to_delete) # Disable the domain by setting is_disabled field to 1 Domain.query.filter(Domain.id == domain_id_to_delete).update( {'is_disabled': 1}) # Disable all users of this domain as Domain has been disabled User.query.filter(User.domain_id == domain_id_to_delete).update( {'is_disabled': 1}) db.session.commit() return {'deleted_domain': {'id': domain_id_to_delete}} @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAINS) def put(self, **kwargs): """ PUT /domains/<id> Function will change credentials of one domain per request. :return: {'updated_domain' {'id': domain_id}} :rtype: dict """ requested_domain_id = kwargs.get('id') requested_domain = Domain.query.get( requested_domain_id) if requested_domain_id else None if not requested_domain: raise NotFoundError( "Either domain_id is not provided or domain doesn't exist") posted_data = request.get_json(silent=True) if not posted_data: raise InvalidUsage("Request body is empty or not provided") if request.user.role.name != 'TALENT_ADMIN' and requested_domain_id != request.user.domain_id: raise UnauthorizedError( "Logged-in user doesn't have appropriate permissions for this operation" ) name = posted_data.get('name', '') expiration = posted_data.get('expiration', '') default_culture_id = posted_data.get('default_culture_id', '') default_tracking_code = posted_data.get('default_tracking_code', '') dice_company_id = posted_data.get('dice_company_Id', '') if expiration: try: expiration = parser.parse(expiration) except Exception as e: raise InvalidUsage("Expiration Time is not valid as: %s" % e.message) if name and Domain.query.filter_by(name=name).first(): raise InvalidUsage('Domain %s already exists in database' % name) # If Culture doesn't exist in database if default_culture_id and not Culture.query.get(default_culture_id): raise InvalidUsage("Culture %s doesn't exist" % default_culture_id) is_disabled = request.args.get('is_disabled', 0) if not is_number(is_disabled) or int(is_disabled) not in (0, 1): raise InvalidUsage('`is_disabled` can be either 0 or 1') # Update user update_domain_dict = { 'name': name, 'expiration': expiration, 'default_culture_id': default_culture_id, 'default_tracking_code': default_tracking_code, 'dice_company_id': dice_company_id, 'is_disabled': is_disabled, } update_domain_dict = dict( (k, v) for k, v in update_domain_dict.iteritems() if v) Domain.query.filter( Domain.id == requested_domain_id).update(update_domain_dict) db.session.commit() return {'updated_domain': {'id': requested_domain_id}} @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAINS) def patch(self, **kwargs): """ PATCH /domains/<id> This endpoint will update field `is_test_domain` of requested domain object. :rtype: dict """ requested_domain_id = kwargs.get('id') requested_domain = Domain.query.get( requested_domain_id) if requested_domain_id else None if not requested_domain: raise NotFoundError( "Either domain_id is not provided or domain doesn't exist") posted_data = request.get_json(silent=True) if not posted_data: raise InvalidUsage("Request body is empty or not provided") if request.user.role.name != 'TALENT_ADMIN' and requested_domain_id != request.user.domain_id: raise UnauthorizedError( "Logged-in user doesn't have appropriate permissions for this operation" ) is_test_domain = posted_data.get('is_test_domain', 0) if is_test_domain not in (True, False, 1, 0): raise InvalidUsage( "is_test_domain field should be a boolean, given: %s" % is_test_domain) requested_domain.update(is_test_domain=is_test_domain) logger.info("Domain(id:%s) has been marked as test domain" % requested_domain_id) return {'updated_domain': {'id': requested_domain_id}}
class DomainGroupsApi(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAIN_GROUPS) def get(self, **kwargs): """ GET /domain/<domain_id>/groups Fetch all user_groups of a given domain :return A dictionary containing id and name of all user_groups of a given domain :rtype: dict """ requested_domain_id = kwargs.get('domain_id') requested_domain = Domain.query.get(requested_domain_id) if not requested_domain: raise NotFoundError("Domain with domain_id %s doesn't exist" % requested_domain_id) if request.user.role.name != 'TALENT_ADMIN' and requested_domain_id != request.user.domain_id: raise UnauthorizedError("User %s doesn't have appropriate permission to get all user_groups " "of domain %s" % (request.user.id, requested_domain_id)) all_user_groups_of_domain = UserGroup.all_groups_of_domain(requested_domain_id) return {"user_groups": [{'id': user_group.id, 'name': user_group.name} for user_group in all_user_groups_of_domain]} @require_all_permissions(Permission.PermissionNames.CAN_ADD_DOMAIN_GROUPS) def post(self, **kwargs): """ POST /domain/<domain_id>/groups Add user_groups to a given domain :return A dictionary containing ids of user_groups added to given domain :rtype: dict """ requested_domain_id = kwargs.get('domain_id') requested_domain = Domain.query.get(requested_domain_id) if not requested_domain: raise NotFoundError("Domain with domain_id %s doesn't exist" % requested_domain_id) posted_data = request.get_json(silent=True) if not posted_data or 'groups' not in posted_data: raise InvalidUsage("Request body is empty or not provided") if request.user.role.name != 'TALENT_ADMIN' and requested_domain_id != request.user.domain_id: raise UnauthorizedError("User %s doesn't have appropriate permission to add user_groups " "to domain %s" % (request.user.id, requested_domain_id)) user_groups = UserGroup.add_groups(posted_data.get('groups'), requested_domain_id) return {'user_groups': [user_group.id for user_group in user_groups]} @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAIN_GROUPS) def put(self, **kwargs): """ PUT /domain/groups/<group_id> Edit a user group with given group_id :return A dictionary containing success message :rtype: dict """ requested_group_id = kwargs.get('group_id') requested_group = UserGroup.query.get(requested_group_id) if not requested_group: raise NotFoundError("User Group with id %s doesn't exist" % requested_group_id) posted_data = request.get_json(silent=True) if not posted_data: raise InvalidUsage("Request body is empty or not provided") if request.user.role.name != 'TALENT_ADMIN' and requested_group.domain_id != request.user.domain_id: raise UnauthorizedError("User %s doesn't have appropriate permission to edit user_groups %s" % ( request.user.id, requested_group.name)) name = posted_data.get('name') description = posted_data.get('description') if not name and not description: raise InvalidUsage("Neither name nor description is provided") if name and not UserGroup.query.filter_by(name=name, domain_id=requested_group.domain_id).all(): requested_group.name = name elif name: raise InvalidUsage("User group '%s' already exists in domain %s" % (name, requested_group.domain_id)) if description: requested_group.description = description db.session.commit() return '', 201 @require_all_permissions(Permission.PermissionNames.CAN_DELETE_DOMAIN_GROUPS) def delete(self, **kwargs): """ DELETE /domain/<domain_id>/groups Remove user_groups from a given domain :return A dictionary containing success message :rtype: dict """ requested_domain_id = kwargs.get('domain_id') requested_domain = Domain.query.get(requested_domain_id) if not requested_domain: raise NotFoundError("Domain with domain_id %s doesn't exist" % requested_domain_id) posted_data = request.get_json(silent=True) if not posted_data or 'groups' not in posted_data: raise InvalidUsage("Request body is empty or not provided") if request.user.role.name != 'TALENT_ADMIN' and requested_domain_id != request.user.domain_id: raise UnauthorizedError("User %s doesn't have appropriate permission to remove user_groups " "from domain %s" % (request.user.id, requested_domain_id)) UserGroup.delete_groups(requested_domain_id, posted_data.get('groups')) return '', 200
class RolesApi(Resource): decorators = [require_oauth()] # Access token decorator @require_all_permissions(Permission.PermissionNames.CAN_GET_USER_ROLE) def get(self, **kwargs): """ GET /users/roles Fetch all roles in the system which can be assigned to a user :param kwargs: :return: """ static_roles = Role.query.filter(Role.name.in_(['TALENT_ADMIN', 'DOMAIN_ADMIN', 'ADMIN'])).all() static_roles = {static_role.name: static_role for static_role in static_roles} user_role_name = request.user.role.name if user_role_name == 'TALENT_ADMIN': roles_prefix = static_roles.values() elif user_role_name == 'DOMAIN_ADMIN': roles_prefix = [static_roles['DOMAIN_ADMIN'], static_roles['ADMIN']] elif user_role_name == 'ADMIN': roles_prefix = [static_roles['ADMIN']] else: roles_prefix = [] roles_prefix += Role.query.filter(Role.name.notin_(['TALENT_ADMIN', 'DOMAIN_ADMIN', 'ADMIN'])) return ( { "roles": [{ 'role_id': role_object.id, 'role_name': role_object.name, 'permissions': [{ 'permission_id': permission.id, 'permission_name': permission.name } for permission in role_object.permissions] } for role_object in roles_prefix]} ) @require_all_permissions(Permission.PermissionNames.CAN_ADD_USER_ROLE) def post(self, **kwargs): """ POST /users/roles Create a New Custom role using existing roles :param kwargs: :return: """ posted_data = request.get_json(silent=True) if not posted_data or 'role' not in posted_data or not isinstance(posted_data['role'], dict): raise InvalidUsage("Request body is empty or not provided") role_data = posted_data.get('role') role_name = role_data.get('name', '') role_description = role_data.get('description', '') permission_ids = role_data.get('permissions', []) permission_ids = list(set(permission_ids)) if not role_name: raise InvalidUsage("Valid Role Name should be provided") if Permission.query.filter(Permission.id.in_(permission_ids)).count() != len(permission_ids): raise InvalidUsage("Some or All permission ids are not valid") if Role.get_by_name(role_name): raise InvalidUsage("Role with name: %s already exists" % role_name) # Check if a role already exists with same set of permissions existing_role_with_same_permissions = Role.query.filter( permission_ids.__len__() == (PermissionsOfRole.query.filter( PermissionsOfRole.permission_id.in_(permission_ids), PermissionsOfRole.role_id == Role.id).count()), (permission_ids.__len__() == PermissionsOfRole.query.filter( PermissionsOfRole.role_id == Role.id).count())).first() if existing_role_with_same_permissions: raise InvalidUsage("Another role `%s` already exists with same set of " "permissions" % existing_role_with_same_permissions.name) exclusive_permissions = PermissionsOfRole.query.with_entities(PermissionsOfRole.permission_id, func.count(PermissionsOfRole.role_id)).group_by( PermissionsOfRole.permission_id).having(func.count(PermissionsOfRole.role_id) <= 2).all() exclusive_permissions = {exclusive_permission[0]: exclusive_permission[1] for exclusive_permission in exclusive_permissions} user_role_name = request.user.role.name for permission_id in permission_ids: if permission_id in exclusive_permissions and ( (user_role_name == 'DOMAIN_ADMIN' and exclusive_permissions[permission_id] == 1) or (user_role_name == 'ADMIN' and exclusive_permissions[permission_id] <= 2)): raise UnauthorizedError("Logged-in user is not authorized to create a " "role with permission_id: %s" % permission_id) role_object = Role(name=role_name, description=role_description) db.session.add(role_object) db.session.flush() for permission_id in permission_ids: db.session.add(PermissionsOfRole(role_id=role_object.id, permission_id=permission_id)) db.session.commit() return { 'id': role_object.id, 'name': role_object.name }
class UserRolesApi(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_USER_ROLE) def get(self, **kwargs): """ GET /users/<user_id>/roles Fetch all permissions of a user :return A dictionary containing role Name and permissions of that role :rtype: dict """ requested_user_id = kwargs.get('user_id') requested_user = User.query.get(requested_user_id) if not requested_user: raise NotFoundError("User with user_id %s doesn't exist" % requested_user_id) # GET all roles of given user permissions = [permission.name for permission in requested_user.role.get_all_permissions_of_role()] return {"role_name": requested_user.role.name, "permissions": permissions} @require_all_permissions(Permission.PermissionNames.CAN_EDIT_USER_ROLE) def put(self, **kwargs): """ POST /users/<user_id>/roles Add given roles to a user :return A dictionary containing success message :rtype: dict """ requested_user_id = kwargs.get('user_id') requested_user = User.query.get(requested_user_id) if not requested_user: raise NotFoundError("User with user_id %s doesn't exist" % requested_user_id) posted_data = request.get_json(silent=True) if not posted_data or 'role' not in posted_data: raise InvalidUsage("Request body is empty or not provided") role = posted_data.get('role', '') if is_number(role): role_object = Role.query.get(int(role)) if role_object: role_id = role role_name = role_object.name else: raise NotFoundError("Role with id:%s doesn't exist in database" % role) else: role_object = Role.get_by_name(role) if role_object: role_id = role_object.id role_name = role_object.name else: raise NotFoundError("Role with name:%s doesn't exist in database" % role) user_role_name = request.user.role.name if (user_role_name == 'DOMAIN_ADMIN' and role_name == 'TALENT_ADMIN') or \ (user_role_name == 'ADMIN' and role_name in ('DOMAIN_ADMIN', 'TALENT_ADMIN')): raise UnauthorizedError("Logged-in user %s doesn't have appropriate permission to set " "role of user %s" % (request.user.id, requested_user.id)) if user_role_name != 'TALENT_ADMIN' and request.user.domain_id != requested_user.domain_id: raise UnauthorizedError("Logged-in user cannot set roles of users of other domains") requested_user.role_id = role_id db.session.commit() return '', 201
class SourceProductResource(Resource): decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_ADD_CANDIDATES) def post(self, **kwargs): # Validate and obtain json data from request body body_dict = get_json_data_if_validated(request, source_product_schema, False) # Normalize description & notes source_products = body_dict['source_products'] created_sp_ids = [] for sp in source_products: name = sp['name'].strip() notes = (sp.get('notes') or '').strip() # In case name is just a whitespace if not name: raise InvalidUsage("Source product name is required.") # Prevent inserting duplicate source product into db if name.lower() in {p.name.lower() for p in Product.query.all()}: continue new_source_product = Product(name=name, notes=notes) db.session.add(new_source_product) db.session.commit() created_sp_ids.append(new_source_product.id) return { 'source_products': [{ 'id': sp_id } for sp_id in created_sp_ids] }, 201 @require_all_permissions(Permission.PermissionNames.CAN_ADD_CANDIDATES) def get(self, **kwargs): source_product_id = kwargs.get('id') # Return a single Source Product if source product ID is provided if source_product_id: source_product = Product.get(source_product_id) if not source_product: raise InvalidUsage( error_message="Source Product ID not recognized") return { 'source_product': { 'id': source_product.id, 'name': source_product.name, 'notes': source_product.notes } } # Return all Source Products return { 'source_products': [{ 'id': sp.id, 'name': sp.name, 'notes': sp.notes, } for sp in Product.query.all()] }