From f95ca33d55b7da05f06fc9e160135795a81666c2 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Thu, 31 Dec 2020 10:24:11 +0800 Subject: [PATCH 1/2] fix: permission rules for connect manager --- ...coder-bookings-api.postman_collection.json | 102 +++++------------- src/services/JobCandidateService.js | 16 +-- src/services/JobService.js | 14 +-- src/services/ResourceBookingService.js | 2 +- 4 files changed, 33 insertions(+), 101 deletions(-) diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index bf61a0b2..a46afda8 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -8237,7 +8237,7 @@ "id": "f25317af-4933-4c93-b02b-cae7feddac50", "exec": [ "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_candidate_id_created_for_member\",data.id);" + "postman.setEnvironmentVariable(\"job_candidate_id_created_by_member\",data.id);" ], "type": "text/javascript" } @@ -8523,7 +8523,7 @@ "id": "9754578e-91dd-437d-b5a2-cdb5668e14e4", "exec": [ "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"resource_booking_id_created_for_member\",data.id);" + "postman.setEnvironmentVariable(\"resource_booking_id_created_by_member\",data.id);" ], "type": "text/javascript" } @@ -8789,67 +8789,15 @@ "name": "Jobs", "item": [ { - "name": "Before Test", - "item": [ - { - "name": "create job", - "event": [ - { - "listen": "test", - "script": { - "id": "faaf5dc1-9869-4615-992c-3cace41f65e8", - "exec": [ - "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_id_created_for_connect_manager\",data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{token_administrator}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"full-time\",\r\n \"skills\": [\r\n \"23e00d92-207a-4b5b-b3c9-4c5662644941\",\r\n \"7d076384-ccf6-4e43-a45d-1b24b1e624aa\",\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ]\r\n}\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{URL}}/jobs", - "host": [ - "{{URL}}" - ], - "path": [ - "jobs" - ] - } - }, - "response": [] - } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true - }, - { - "name": "✘ create job with connect manager", + "name": "✔ create job with connect manager", "event": [ { "listen": "test", "script": { - "id": "ab2fa9b2-71fc-4cda-b72d-60cf2d99525d", + "id": "0a6a3140-f1fb-434f-8dbf-37ed64b7573d", "exec": [ "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_id_created_for_connect_manager\",data.id);" + "postman.setEnvironmentVariable(\"job_id_created_by_connect_manager\",data.id);" ], "type": "text/javascript" } @@ -8897,13 +8845,13 @@ } ], "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", + "raw": "{{URL}}/jobs/{{job_id_created_by_connect_manager}}", "host": [ "{{URL}}" ], "path": [ "jobs", - "{{job_id_created_for_connect_manager}}" + "{{job_id_created_by_connect_manager}}" ] } }, @@ -9005,7 +8953,7 @@ "response": [] }, { - "name": "✘ put job with connect manager", + "name": "✔ put job with connect manager", "request": { "method": "PUT", "header": [ @@ -9025,20 +8973,20 @@ } }, "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", + "raw": "{{URL}}/jobs/{{job_id_created_by_connect_manager}}", "host": [ "{{URL}}" ], "path": [ "jobs", - "{{job_id_created_for_connect_manager}}" + "{{job_id_created_by_connect_manager}}" ] } }, "response": [] }, { - "name": "✘ patch job with connect manager", + "name": "✔ patch job with connect manager", "request": { "method": "PATCH", "header": [ @@ -9058,13 +9006,13 @@ } }, "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", + "raw": "{{URL}}/jobs/{{job_id_created_by_connect_manager}}", "host": [ "{{URL}}" ], "path": [ "jobs", - "{{job_id_created_for_connect_manager}}" + "{{job_id_created_by_connect_manager}}" ] } }, @@ -9091,13 +9039,13 @@ } }, "url": { - "raw": "{{URL}}/jobs/{{job_id_created_for_connect_manager}}", + "raw": "{{URL}}/jobs/{{job_id_created_by_connect_manager}}", "host": [ "{{URL}}" ], "path": [ "jobs", - "{{job_id_created_for_connect_manager}}" + "{{job_id_created_by_connect_manager}}" ] } }, @@ -9119,7 +9067,7 @@ { "listen": "test", "script": { - "id": "5ce6dd7a-39aa-4910-aba1-37f02559d293", + "id": "8bb2aa84-0052-42f1-b4c6-2cac7a87e54b", "exec": [ "var data = JSON.parse(responseBody);\r", "postman.setEnvironmentVariable(\"job_candidate_id_created_for_connect_manager\",data.id);" @@ -9139,7 +9087,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", + "raw": "{\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", "options": { "raw": { "language": "json" @@ -9168,10 +9116,10 @@ { "listen": "test", "script": { - "id": "74e63fe5-8d71-4791-a722-5d7347e28f83", + "id": "62dadb99-c6cb-418f-9a17-347d3d92edb0", "exec": [ "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"job_candidate_id_created_for_connect_manager\",data.id);" + "postman.setEnvironmentVariable(\"job_candidate_id_created_by_connect_manager\",data.id);" ], "type": "text/javascript" } @@ -9292,7 +9240,7 @@ "response": [] }, { - "name": "✘ put job candidate with connect manager", + "name": "✔ put job candidate with connect manager", "request": { "method": "PUT", "header": [ @@ -9325,7 +9273,7 @@ "response": [] }, { - "name": "✘ patch job candidate with connect manager", + "name": "✔ patch job candidate with connect manager", "request": { "method": "PATCH", "header": [ @@ -9406,7 +9354,7 @@ { "listen": "test", "script": { - "id": "513617f1-b4ba-4041-9aaf-fd99f883939b", + "id": "65b3ece2-3411-4ff7-9432-3ba49e9143bd", "exec": [ "var data = JSON.parse(responseBody);\r", "postman.setEnvironmentVariable(\"resource_booking_id_created_for_connect_manager\",data.id);" @@ -9426,7 +9374,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", + "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", "options": { "raw": { "language": "json" @@ -9455,10 +9403,10 @@ { "listen": "test", "script": { - "id": "be4bde84-1e50-4bb4-a99c-4d03ce055023", + "id": "bc33d0bb-8e0b-46e5-ac74-f1016881c156", "exec": [ "var data = JSON.parse(responseBody);\r", - "postman.setEnvironmentVariable(\"resource_booking_id_created_for_connect_manager\",data.id);" + "postman.setEnvironmentVariable(\"resource_booking_id_created_by_connect_manager\",data.id);" ], "type": "text/javascript" } diff --git a/src/services/JobCandidateService.js b/src/services/JobCandidateService.js index 106ac1c6..5a06e426 100644 --- a/src/services/JobCandidateService.js +++ b/src/services/JobCandidateService.js @@ -25,7 +25,7 @@ const esClient = helper.getESClient() * @returns {undefined} */ async function _checkUserAccessAssociatedJob (currentUser, jobId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { + if (!currentUser.hasManagePermission && !currentUser.isMachine) { await JobService.getJob(currentUser, jobId) } } @@ -118,17 +118,9 @@ async function updateJobCandidate (currentUser, id, data) { const jobCandidate = await JobCandidate.findById(id) const userId = await helper.getUserId(currentUser.userId) - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - if (currentUser.isConnectManager) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - // check whether user can access the job associated with the jobCandidate - await JobService.getJob(currentUser, jobCandidate.dataValues.jobId) - // check whether user are allowed to update the candidate - if (jobCandidate.dataValues.userId !== userId) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - } + // check whether user can access the job associated with the jobCandidate + await _checkUserAccessAssociatedJob(currentUser, jobCandidate.dataValues.jobId) + data.updatedAt = new Date() data.updatedBy = userId diff --git a/src/services/JobService.js b/src/services/JobService.js index bc131ee0..a24c8bab 100644 --- a/src/services/JobService.js +++ b/src/services/JobService.js @@ -82,7 +82,7 @@ async function _validateSkills (skills) { * @returns {undefined} */ async function _checkUserAccessAssociatedProject (currentUser, projectId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { + if (!currentUser.hasManagePermission && !currentUser.isMachine) { await helper.getProjectById(currentUser, projectId) } } @@ -145,13 +145,8 @@ getJob.schema = Joi.object().keys({ * @returns {Object} the created job */ async function createJob (currentUser, job) { - // check if user can access the project - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - if (currentUser.isConnectManager) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } - await helper.getProjectById(currentUser, job.projectId) - } + // check whether user can access the project associated with the job + await _checkUserAccessAssociatedProject(currentUser, job.projectId) await _validateSkills(job.skills) job.id = uuid() @@ -194,9 +189,6 @@ async function updateJob (currentUser, id, data) { let job = await Job.findById(id) const ubhanUserId = await helper.getUserId(currentUser.userId) if (!currentUser.hasManagePermission && !currentUser.isMachine) { - if (currentUser.isConnectManager) { - throw new errors.ForbiddenError('You are not allowed to perform this action!') - } // Check whether user can update the job. // Note that there is no need to check if user is member of the project associated with the job here // because user who created the job must be the member of the project associated with the job diff --git a/src/services/ResourceBookingService.js b/src/services/ResourceBookingService.js index aedbe417..e68c43e7 100644 --- a/src/services/ResourceBookingService.js +++ b/src/services/ResourceBookingService.js @@ -38,7 +38,7 @@ async function _getResourceBookingFilteringFields (currentUser, resourceBooking) * @returns {undefined} */ async function _checkUserAccessAssociatedProject (currentUser, projectId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { + if (!currentUser.hasManagePermission && !currentUser.isMachine) { await helper.getProjectById(currentUser, projectId) } } From 596f5453c7ec2c958e95b1e3ae62adba5bdd5207 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Sun, 3 Jan 2021 00:18:28 +0800 Subject: [PATCH 2/2] fix: all operations except get/search cause 403 error if manager is not member of project --- ...coder-bookings-api.postman_collection.json | 66 +++++++++---------- ...topcoder-bookings.postman_environment.json | 10 +++ src/common/helper.js | 26 +++++++- src/services/JobCandidateService.js | 33 +++++----- src/services/JobService.js | 28 ++++---- src/services/ResourceBookingService.js | 21 +++--- 6 files changed, 107 insertions(+), 77 deletions(-) diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index a46afda8..76783b7c 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -7887,10 +7887,10 @@ "name": "Request with Topcoder User Role", "item": [ { - "name": "Before Test", + "name": "README", "item": [ { - "name": "[STUB] refresh the jwt token for user tester1234", + "name": "[STUB] all operations cause 403 error if user is not member of project", "request": { "method": "LOCK", "header": [], @@ -7900,9 +7900,7 @@ }, "response": [] } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true + ] }, { "name": "Jobs", @@ -8768,10 +8766,10 @@ "name": "Request with Connect Manager Role", "item": [ { - "name": "Before Test", + "name": "README", "item": [ { - "name": "[STUB] refresh the jwt token for connect manager", + "name": "[STUB] all operations except get/search cause 403 error if manager is not member of project", "request": { "method": "LOCK", "header": [], @@ -8781,9 +8779,7 @@ }, "response": [] } - ], - "protocolProfileBehavior": {}, - "_postman_isSubFolder": true + ] }, { "name": "Jobs", @@ -8808,13 +8804,13 @@ "header": [ { "key": "Authorization", - "value": "Bearer {{token_connectUser}}", + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}", "type": "text" } ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"full-time\",\r\n \"skills\": [\r\n \"23e00d92-207a-4b5b-b3c9-4c5662644941\",\r\n \"7d076384-ccf6-4e43-a45d-1b24b1e624aa\",\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ]\r\n}\r\n", + "raw": "{\r\n \"projectId\": {{project_id_16843}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"full-time\",\r\n \"skills\": [\r\n \"23e00d92-207a-4b5b-b3c9-4c5662644941\",\r\n \"7d076384-ccf6-4e43-a45d-1b24b1e624aa\",\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ]\r\n}\r\n", "options": { "raw": { "language": "json" @@ -8841,7 +8837,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "url": { @@ -8865,7 +8861,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "url": { @@ -8960,12 +8956,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", + "raw": "{\r\n \"projectId\": {{project_id_16843}},\r\n \"externalId\": \"1212\",\r\n \"description\": \"Dummy Description\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"numPositions\": 13,\r\n \"resourceType\": \"Dummy Resource Type\",\r\n \"rateType\": \"hourly\",\r\n \"workload\": \"fractional\",\r\n \"skills\": [\r\n \"cbac57a3-7180-4316-8769-73af64893158\",\r\n \"a2b4bc11-c641-4a19-9eb7-33980378f82e\"\r\n ],\r\n \"status\": \"sourcing\"\r\n}", "options": { "raw": { "language": "json" @@ -8993,7 +8989,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { @@ -9026,7 +9022,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { @@ -9131,12 +9127,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", + "raw": "{\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\"\r\n}", "options": { "raw": { "language": "json" @@ -9163,7 +9159,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "url": { @@ -9187,7 +9183,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "url": { @@ -9247,12 +9243,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"selected\"\r\n}", "options": { "raw": { "language": "json" @@ -9280,7 +9276,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { @@ -9313,7 +9309,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { @@ -9374,7 +9370,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", + "raw": "{\r\n \"projectId\": {{project_id_16843}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", "options": { "raw": { "language": "json" @@ -9418,12 +9414,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", + "raw": "{\r\n \"projectId\": {{project_id_16843}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\"\r\n}", "options": { "raw": { "language": "json" @@ -9450,7 +9446,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "url": { @@ -9474,7 +9470,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "url": { @@ -9544,12 +9540,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_for_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\"\r\n}", + "raw": "{\r\n \"projectId\": {{project_id_16843}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"startDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"endDate\": \"2020-09-27T04:17:23.131Z\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\"\r\n}", "options": { "raw": { "language": "json" @@ -9577,7 +9573,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { @@ -9610,7 +9606,7 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" } ], "body": { diff --git a/docs/topcoder-bookings.postman_environment.json b/docs/topcoder-bookings.postman_environment.json index 17491678..52dd7275 100644 --- a/docs/topcoder-bookings.postman_environment.json +++ b/docs/topcoder-bookings.postman_environment.json @@ -37,6 +37,11 @@ "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5VSkZORGd4UlRVME5EWTBOVVkzTlRkR05qTXlRamxETmpOQk5UYzVRVUV3UlRFeU56TTJRUSJ9.eyJodHRwczovL3RvcGNvZGVyLWRldi5jb20vcm9sZXMiOlsiVG9wY29kZXIgVXNlciIsImNvcGlsb3QiLCJDb25uZWN0IE1hbmFnZXIiLCJ1LWJhaG4iXSwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL3VzZXJJZCI6Ijg1NDc4OTkiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vaGFuZGxlIjoicHNoYWhfbWFuYWdlciIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS91c2VyX2lkIjoiYXV0aDB8NDAxNTI4NTYiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdGNzc28iOiI0MDE1Mjg1Nnw4MTM0ZjQ4ZWJlMTFhODQ4YTM3NTllNWVmOWU5MmYyMTQ2OTJlMjExMzA0MGM4MmI1ZDhmNTgxYzZkZmNjYzg4IiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL2FjdGl2ZSI6dHJ1ZSwibmlja25hbWUiOiJwc2hhaF9tYW5hZ2VyIiwibmFtZSI6InZpa2FzLmFnYXJ3YWwrcHNoYWhfbWFuYWdlckB0b3Bjb2Rlci5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvOTJhZmIyZjBlZDUyZmRmYWUxZjM3MTAyMWFlNjUwMTM_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZ2aS5wbmciLCJ1cGRhdGVkX2F0IjoiMjAyMC0xMC0yNFQwODoyODoyNC4xODRaIiwiZW1haWwiOiJ2aWthcy5hZ2Fyd2FsK3BzaGFoX21hbmFnZXJAdG9wY29kZXIuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYXV0aC50b3Bjb2Rlci1kZXYuY29tLyIsInN1YiI6ImF1dGgwfDQwMTUyODU2IiwiYXVkIjoiQlhXWFVXbmlsVlVQZE4wMXQyU2UyOVR3MlpZTkdadkgiLCJpYXQiOjE2MDM1NDMzMzgsImV4cCI6MzMxNjA0NTI3MzgsIm5vbmNlIjoiUjFBMmN6WXVWVFptYmpaSFJHOTJWbDlEU1VKNlVsbHZRWGMzUkhoNVMzWldkV1pEY0ROWE1FWjFYdz09In0.hxQ-lcJTw4M_nDIELABWxOB3nKXS322MJ-W7r5eA10o", "enabled": true }, + { + "key": "token_connect_manager_pshahcopmanag2", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL3RvcGNvZGVyLWRldi5jb20vcm9sZXMiOlsiQnVzaW5lc3MgVXNlciIsIlRvcGNvZGVyIFVzZXIiLCJDb25uZWN0IENvcGlsb3QgTWFuYWdlciIsIkNvbm5lY3QgTWFuYWdlciJdLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcklkIjoiODg3NzQ0ODkiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vaGFuZGxlIjoicHNoYWhjb3BtYW5hZzIiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcl9pZCI6ImF1dGgwfDg4Nzc0NDg5IiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL3Rjc3NvIjoiODg3NzQ0ODl8ODdhZDNiNjNiZGZjMmYyNjczNGJiMDIzMTM2YWEzM2NhYWY5MzdiNzdhZmQyYjE3YzljMWY3ZWVkZWI4IiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL2FjdGl2ZSI6dHJ1ZSwibmlja25hbWUiOiJwc2hhaGNvcG1hbmFnMiIsIm5hbWUiOiJtYXhjZWVtK3RjK3BzaGFoY29wbWFuYWcyQGdtYWlsLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8wZDY1NWNlZDM4NTFiM2JmY2I1Y2Y3Y2U3NjY0ODQwNj9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRm1hLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDIxLTAxLTAyVDEyOjM3OjAxLjE2MFoiLCJlbWFpbCI6Im1heGNlZW0rdGMrcHNoYWhjb3BtYW5hZzJAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vYXV0aC50b3Bjb2Rlci1kZXYuY29tLyIsInN1YiI6ImF1dGgwfDg4Nzc0NDg5IiwiYXVkIjoiQlhXWFVXbmlsVlVQZE4wMXQyU2UyOVR3MlpZTkdadkgiLCJpYXQiOjE2MDk1OTEwMjQsImV4cCI6MjE0NzQ4MzY0OCwibm9uY2UiOiJaVmhCV1dKbU5GbFlOa0pGU0ZWSU9VSkZTbFIrYkhoVVVEYzJmak41UkVWcFFuWkRWSFZUVlVKU1RRPT0ifQ.G-wrxaqoRH9GQS9cjqX93nRoH91tn-wPW1j_MA42lCY", + "enabled": true + }, { "key": "token_member", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5VSkZORGd4UlRVME5EWTBOVVkzTlRkR05qTXlRamxETmpOQk5UYzVRVUV3UlRFeU56TTJRUSJ9.eyJodHRwczovL3RvcGNvZGVyLWRldi5jb20vcm9sZXMiOlsiVG9wY29kZXIgVXNlciJdLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vdXNlcklkIjoiODU0Nzg5OSIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS9oYW5kbGUiOiJwc2hhaF9tYW5hZ2VyIiwiaHR0cHM6Ly90b3Bjb2Rlci1kZXYuY29tL3VzZXJfaWQiOiJhdXRoMHw0MDE1Mjg1NiIsImh0dHBzOi8vdG9wY29kZXItZGV2LmNvbS90Y3NzbyI6IjQwMTUyODU2fDgxMzRmNDhlYmUxMWE4NDhhMzc1OWU1ZWY5ZTkyZjIxNDY5MmUyMTEzMDQwYzgyYjVkOGY1ODFjNmRmY2NjODgiLCJodHRwczovL3RvcGNvZGVyLWRldi5jb20vYWN0aXZlIjp0cnVlLCJuaWNrbmFtZSI6InBzaGFoX21hbmFnZXIiLCJuYW1lIjoidmlrYXMuYWdhcndhbCtwc2hhaF9tYW5hZ2VyQHRvcGNvZGVyLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85MmFmYjJmMGVkNTJmZGZhZTFmMzcxMDIxYWU2NTAxMz9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRnZpLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDIwLTEwLTI0VDA4OjI4OjI0LjE4NFoiLCJlbWFpbCI6InZpa2FzLmFnYXJ3YWwrcHNoYWhfbWFuYWdlckB0b3Bjb2Rlci5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9hdXRoLnRvcGNvZGVyLWRldi5jb20vIiwic3ViIjoiYXV0aDB8NDAxNTI4NTYiLCJhdWQiOiJCWFdYVVduaWxWVVBkTjAxdDJTZTI5VHcyWllOR1p2SCIsImlhdCI6MTYwMzU0MzMzOCwiZXhwIjozMzE2MDQ1MjczOCwibm9uY2UiOiJSMUEyY3pZdVZUWm1ialpIUkc5MlZsOURTVUo2VWxsdlFYYzNSSGg1UzNaV2RXWkRjRE5YTUVaMVh3PT0ifQ.HbAisH30DLcbFNQeIifSzk1yhDmlGHNpPi9LSZbAowo", @@ -62,6 +67,11 @@ "value": "16718", "enabled": true }, + { + "key": "project_id_16843", + "value": "16843", + "enabled": true + }, { "key": "jobIdCreatedByMember", "value": "", diff --git a/src/common/helper.js b/src/common/helper.js index 0d2f849d..34a8f16e 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -603,6 +603,29 @@ async function ensureUserById (userId) { } } +/** + * Function to check whether a user is a member of a project + * by first retrieving the project detail via /v5/projects/:projectId and + * then checking whether the user was included in the `members` property of the project detail object. + * + * @param {Object} userId the id of the user + * @param {Number} projectId project id + * @returns the result + */ +async function checkIsMemberOfProject (userId, projectId) { + const m2mToken = await getM2Mtoken() + const res = await request + .get(`${config.TC_API}/projects/${projectId}`) + .set('Authorization', `Bearer ${m2mToken}`) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + const memberIdList = _.map(res.body.members, 'userId') + localLogger.debug({ context: 'checkIsMemberOfProject', message: `the members of project ${projectId}: ${memberIdList}` }) + if (!memberIdList.includes(userId)) { + throw new errors.UnauthorizedError(`userId: ${userId} the user is not a member of project ${projectId}`) + } +} + module.exports = { checkIfExists, autoWrapExpress, @@ -629,5 +652,6 @@ module.exports = { getSkillById, getUserSkill, ensureJobById, - ensureUserById + ensureUserById, + checkIsMemberOfProject } diff --git a/src/services/JobCandidateService.js b/src/services/JobCandidateService.js index 5a06e426..0aca7bc2 100644 --- a/src/services/JobCandidateService.js +++ b/src/services/JobCandidateService.js @@ -18,15 +18,16 @@ const JobCandidate = models.JobCandidate const esClient = helper.getESClient() /** - * Check whether user can access associated job of a candidate. + * Check user permission for getting job candidate. * * @param {Object} currentUser the user who perform this operation. * @param {String} jobId the job id * @returns {undefined} */ -async function _checkUserAccessAssociatedJob (currentUser, jobId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - await JobService.getJob(currentUser, jobId) +async function _checkUserPermissionForGetJobCandidate (currentUser, jobId) { + if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { + const job = await models.Job.findById(jobId) + await helper.checkIsMemberOfProject(currentUser.userId, job.projectId) } } @@ -45,8 +46,7 @@ async function getJobCandidate (currentUser, id, fromDb = false) { id }) - // check whether user can access the job associated with the jobCandidate - await _checkUserAccessAssociatedJob(currentUser, jobCandidate.body._source.jobId) + await _checkUserPermissionForGetJobCandidate(currentUser, jobCandidate.body._source.jobId) // check user permisson const jobCandidateRecord = { id: jobCandidate.body._id, ...jobCandidate.body._source } return jobCandidateRecord @@ -63,8 +63,7 @@ async function getJobCandidate (currentUser, id, fromDb = false) { logger.info({ component: 'JobCandidateService', context: 'getJobCandidate', message: 'try to query db for data' }) const jobCandidate = await JobCandidate.findById(id) - // check whether user can access the job associated with the jobCandidate - await _checkUserAccessAssociatedJob(currentUser, jobCandidate.jobId) + await _checkUserPermissionForGetJobCandidate(currentUser, jobCandidate.jobId) // check user permission return helper.clearObject(jobCandidate.dataValues) } @@ -82,6 +81,7 @@ getJobCandidate.schema = Joi.object().keys({ * @returns {Object} the created jobCandidate */ async function createJobCandidate (currentUser, jobCandidate) { + // check user permission if (!currentUser.hasManagePermission && !currentUser.isMachine) { throw new errors.ForbiddenError('You are not allowed to perform this action!') } @@ -116,10 +116,13 @@ createJobCandidate.schema = Joi.object().keys({ */ async function updateJobCandidate (currentUser, id, data) { const jobCandidate = await JobCandidate.findById(id) - const userId = await helper.getUserId(currentUser.userId) - // check whether user can access the job associated with the jobCandidate - await _checkUserAccessAssociatedJob(currentUser, jobCandidate.dataValues.jobId) + + // check user permission + if (!currentUser.hasManagePermission && !currentUser.isMachine) { + const job = await models.Job.findById(jobCandidate.jobId) + await helper.checkIsMemberOfProject(currentUser.userId, job.projectId) + } data.updatedAt = new Date() data.updatedBy = userId @@ -178,6 +181,7 @@ fullyUpdateJobCandidate.schema = Joi.object().keys({ * @params {String} id the jobCandidate id */ async function deleteJobCandidate (currentUser, id) { + // check user permission if (!currentUser.hasManagePermission && !currentUser.isMachine) { throw new errors.ForbiddenError('You are not allowed to perform this action!') } @@ -199,13 +203,12 @@ deleteJobCandidate.schema = Joi.object().keys({ * @returns {Object} the search result, contain total/page/perPage and result array */ async function searchJobCandidates (currentUser, criteria) { + // check user permission if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { - // regular user can only search with filtering by "jobId" - if (!criteria.jobId) { + if (!criteria.jobId) { // regular user can only search with filtering by "jobId" throw new errors.ForbiddenError('Not allowed without filtering by "jobId"') } - // check whether user can access the job associated with the jobCandidate - await JobService.getJob(currentUser, criteria.jobId) + await JobService.getJob(currentUser, criteria.jobId) // check whether user can access the job associated with the jobCandidate } const page = criteria.page > 0 ? criteria.page : 1 diff --git a/src/services/JobService.js b/src/services/JobService.js index a24c8bab..fd1e22f4 100644 --- a/src/services/JobService.js +++ b/src/services/JobService.js @@ -75,15 +75,15 @@ async function _validateSkills (skills) { } /** - * Check whether user can access associated project of a job. + * Check user permission for getting job. * * @param {Object} currentUser the user who perform this operation. * @param {String} projectId the project id * @returns {undefined} */ -async function _checkUserAccessAssociatedProject (currentUser, projectId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - await helper.getProjectById(currentUser, projectId) +async function _checkUserPermissionForGetJob (currentUser, projectId) { + if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { + await helper.checkIsMemberOfProject(currentUser.userId, projectId) } } @@ -102,8 +102,7 @@ async function getJob (currentUser, id, fromDb = false) { id }) - // check whether user can access the project associated with the job - await _checkUserAccessAssociatedProject(currentUser, job.body._source.projectId) + await _checkUserPermissionForGetJob(currentUser, job.body._source.projectId) // check user permission const jobId = job.body._id const jobRecord = { id: jobId, ...job.body._source } @@ -125,8 +124,7 @@ async function getJob (currentUser, id, fromDb = false) { logger.info({ component: 'JobService', context: 'getJob', message: 'try to query db for data' }) const job = await Job.findById(id, true) - // check whether user can access the project associated with the job - await _checkUserAccessAssociatedProject(currentUser, job.projectId) + await _checkUserPermissionForGetJob(currentUser, job.projectId) // check user permission job.dataValues.candidates = _.map(job.dataValues.candidates, (c) => helper.clearObject(c.dataValues)) return helper.clearObject(job.dataValues) @@ -145,8 +143,10 @@ getJob.schema = Joi.object().keys({ * @returns {Object} the created job */ async function createJob (currentUser, job) { - // check whether user can access the project associated with the job - await _checkUserAccessAssociatedProject(currentUser, job.projectId) + // check user permission + if (!currentUser.hasManagePermission && !currentUser.isMachine) { + await helper.checkIsMemberOfProject(currentUser.userId, job.projectId) + } await _validateSkills(job.skills) job.id = uuid() @@ -269,6 +269,7 @@ fullyUpdateJob.schema = Joi.object().keys({ * @params {String} id the job id */ async function deleteJob (currentUser, id) { + // check user permission if (!currentUser.hasManagePermission && !currentUser.isMachine) { throw new errors.ForbiddenError('You are not allowed to perform this action!') } @@ -291,13 +292,12 @@ deleteJob.schema = Joi.object().keys({ * @returns {Object} the search result, contain total/page/perPage and result array */ async function searchJobs (currentUser, criteria, options = { returnAll: false }) { + // check user permission if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager && !options.returnAll) { - // regular user can only search with filtering by "projectId" - if (!criteria.projectId) { + if (!criteria.projectId) { // regular user can only search with filtering by "projectId" throw new errors.ForbiddenError('Not allowed without filtering by "projectId"') } - // check if user can access the project - await helper.getProjectById(currentUser, criteria.projectId) + await helper.checkIsMemberOfProject(currentUser.userId, criteria.projectId) } const page = criteria.page > 0 ? criteria.page : 1 diff --git a/src/services/ResourceBookingService.js b/src/services/ResourceBookingService.js index e68c43e7..21a9bfd4 100644 --- a/src/services/ResourceBookingService.js +++ b/src/services/ResourceBookingService.js @@ -31,15 +31,15 @@ async function _getResourceBookingFilteringFields (currentUser, resourceBooking) } /** - * Check whether user can access associated project of a job. + * Check user permission for getting resource booking. * * @param {Object} currentUser the user who perform this operation. * @param {String} projectId the project id * @returns {undefined} */ -async function _checkUserAccessAssociatedProject (currentUser, projectId) { - if (!currentUser.hasManagePermission && !currentUser.isMachine) { - await helper.getProjectById(currentUser, projectId) +async function _checkUserPermissionForGetResourceBooking (currentUser, projectId) { + if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) { + await helper.checkIsMemberOfProject(currentUser.userId, projectId) } } @@ -58,8 +58,7 @@ async function getResourceBooking (currentUser, id, fromDb = false) { id }) - // check if user can access the project associated with the resourceBooking - await _checkUserAccessAssociatedProject(currentUser, resourceBooking.body._source.projectId) + await _checkUserPermissionForGetResourceBooking(currentUser, resourceBooking.body._source.projectId) // check user permission const resourceBookingRecord = { id: resourceBooking.body._id, ...resourceBooking.body._source } return _getResourceBookingFilteringFields(currentUser, resourceBookingRecord) @@ -76,8 +75,7 @@ async function getResourceBooking (currentUser, id, fromDb = false) { logger.info({ component: 'ResourceBookingService', context: 'getResourceBooking', message: 'try to query db for data' }) const resourceBooking = await ResourceBooking.findById(id) - // check if user can access the project associated with the resourceBooking - await _checkUserAccessAssociatedProject(currentUser, resourceBooking.projectId) + await _checkUserPermissionForGetResourceBooking(currentUser, resourceBooking.projectId) // check user permission return _getResourceBookingFilteringFields(currentUser, resourceBooking.dataValues) } @@ -263,13 +261,12 @@ deleteResourceBooking.schema = Joi.object().keys({ * @returns {Object} the search result, contain total/page/perPage and result array */ async function searchResourceBookings (currentUser, criteria, options = { returnAll: false }) { + // check user permission if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager && !options.returnAll) { - // regular user can only search with filtering by "projectId" - if (!criteria.projectId) { + if (!criteria.projectId) { // regular user can only search with filtering by "projectId" throw new errors.ForbiddenError('Not allowed without filtering by "projectId"') } - // check if user can access the project - await helper.getProjectById(currentUser, criteria.projectId) + await helper.checkIsMemberOfProject(currentUser.userId, criteria.projectId) } // `criteria`.projectIds` could be array of ids, or comma separated string of ids