From 50b3bf1683869fa577c4115541a96530f169deee Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Wed, 22 Sep 2021 17:59:46 +0530 Subject: [PATCH] feat(rest): Update upload information Use PATCH to update the update the upload information. New parameters `assignee` and `status` can be passed as query to the /uploads/{id} endpoint as PATCH request. The comment for Rejected and Closed status can be sent as body of `text/plain` type. BREAKING CHANGE: The PATCH was used for copy upload operations, now it is moved to PUT request with `action` parameter and have same format like PUT /folders/{id}. Signed-off-by: Gaurav Mishra --- .../ui/api/Controllers/UploadController.php | 108 +++++++++++++++--- src/www/ui/api/documentation/openapi.yaml | 54 +++++++-- src/www/ui/api/index.php | 4 +- .../api/Controllers/UploadControllerTest.php | 86 +++++++++++++- 4 files changed, 222 insertions(+), 30 deletions(-) diff --git a/src/www/ui/api/Controllers/UploadController.php b/src/www/ui/api/Controllers/UploadController.php index 3b5f3f31d0..8a15476c3a 100644 --- a/src/www/ui/api/Controllers/UploadController.php +++ b/src/www/ui/api/Controllers/UploadController.php @@ -98,6 +98,11 @@ class UploadController extends RestController */ const CONTAINER_PARAM = "containers"; + /** + * Valid status inputs + */ + const VALID_STATUS = ["open", "inprogress", "closed", "rejected"]; + public function __construct($container) { parent::__construct($container); @@ -300,20 +305,7 @@ public function deleteUpload($request, $response, $args) } /** - * Copy a given upload to a new folder - * - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @param array $args - * @return ResponseInterface - */ - public function copyUpload($request, $response, $args) - { - return $this->changeUpload($request, $response, $args, true); - } - - /** - * Move a given upload to a new folder + * Move or copy a given upload to a new folder * * @param ServerRequestInterface $request * @param ResponseInterface $response @@ -322,7 +314,13 @@ public function copyUpload($request, $response, $args) */ public function moveUpload($request, $response, $args) { - return $this->changeUpload($request, $response, $args, false); + $action = $request->getHeaderLine('action'); + if (strtolower($action) == "move") { + $copy = false; + } else { + $copy = true; + } + return $this->changeUpload($request, $response, $args, $copy); } /** @@ -443,6 +441,86 @@ public function getUploadLicenses($request, $response, $args) return $response->withJson($licenseList, 200); } + /** + * Update an upload + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param array $args + * @return ResponseInterface + */ + public function updateUpload($request, $response, $args) + { + $id = intval($args['id']); + $query = $request->getQueryParams(); + $userDao = $this->restHelper->getUserDao(); + $userId = $this->restHelper->getUserId(); + $groupId = $this->restHelper->getGroupId(); + + $perm = $userDao->isAdvisorOrAdmin($userId, $groupId); + if (!$perm) { + $error = new Info(403, "Not advisor or admin of current group. " . + "Can not update upload.", InfoType::ERROR); + return $response->withJson($error->getArray(), $error->getCode()); + } + $uploadBrowseProxy = new UploadBrowseProxy( + $groupId, + $perm, + $this->dbHelper->getDbManager() + ); + + $assignee = null; + $status = null; + $comment = null; + + $returnVal = true; + // Handle assignee info + if (array_key_exists(self::FILTER_ASSIGNEE, $query)) { + $assignee = filter_var($query[self::FILTER_ASSIGNEE], FILTER_VALIDATE_INT); + $userList = $userDao->getUserChoices($groupId); + if (!array_key_exists($assignee, $userList)) { + $returnVal = new Info( + 404, + "New assignee does not have permisison on upload.", + InfoType::ERROR + ); + } else { + $uploadBrowseProxy->updateTable("assignee", $id, $assignee); + } + } + // Handle new status + if ( + array_key_exists(self::FILTER_STATUS, $query) && + in_array(strtolower($query[self::FILTER_STATUS]), self::VALID_STATUS) && + $returnVal === true + ) { + $newStatus = strtolower($query[self::FILTER_STATUS]); + $comment = ''; + if (in_array($newStatus, ["closed", "rejected"])) { + $body = $request->getBody(); + $comment = $body->getContents(); + $body->close(); + } + $status = 0; + if ($newStatus == self::VALID_STATUS[1]) { + $status = UploadStatus::IN_PROGRESS; + } elseif ($newStatus == self::VALID_STATUS[2]) { + $status = UploadStatus::CLOSED; + } elseif ($newStatus == self::VALID_STATUS[3]) { + $status = UploadStatus::REJECTED; + } else { + $status = UploadStatus::OPEN; + } + $uploadBrowseProxy->setStatusAndComment($id, $status, $comment); + } + if ($returnVal !== true) { + return $response->withJson($returnVal->getArray(), $returnVal->getCode()); + } + + $returnVal = new Info(202, "Upload updated successfully.", InfoType::INFO); + return $response->withJson($returnVal->getArray(), $returnVal->getCode()); + } + /** * Check if upload is accessible * @param integer $groupId Group ID diff --git a/src/www/ui/api/documentation/openapi.yaml b/src/www/ui/api/documentation/openapi.yaml index 49af0b98ed..8f7fea77db 100644 --- a/src/www/ui/api/documentation/openapi.yaml +++ b/src/www/ui/api/documentation/openapi.yaml @@ -15,7 +15,7 @@ openapi: 3.0.2 info: title: FOSSology API description: Automate your fossology instance using REST API - version: 1.3.4 + version: 1.4.0 contact: email: fossology@fossology.org license: @@ -221,21 +221,43 @@ paths: default: $ref: '#/components/responses/defaultResponse' patch: - operationId: moveUploadById + operationId: updateUploadById tags: - Upload - Organize - description: Move upload from one folder to other + description: Update an upload information parameters: - - name: folderId - description: Folder Id, where upload should be moved to - in: header - required: true + - name: status + description: New status of the upload + in: query + required: false + schema: + type: string + enum: + - Open + - InProgress + - Closed + - Rejected + example: Closed + - name: assignee + description: New assignee for the project + in: query + required: false schema: type: integer + requestBody: + description: > + Comment on the status, required for Closed and Rejected states. + Ignored for others. + content: + text/plain: + schema: + description: The comment for new status + type: string + example: "The upload cleared for use." responses: '202': - description: Upload will be moved + description: Upload will be updated content: application/json: schema: @@ -243,11 +265,11 @@ paths: default: $ref: '#/components/responses/defaultResponse' put: - operationId: copyUploadById + operationId: moveUploadById tags: - Upload - Organize - description: Can be used to copy uploads + description: Copy or move an upload by id parameters: - name: folderId description: Folder Id, where upload should be copied to @@ -255,9 +277,19 @@ paths: required: true schema: type: integer + - name: action + in: header + required: true + description: Action to be performed + schema: + type: string + enum: + - copy + - move + summary: Copy/Move an upload responses: '202': - description: Upload will be copied + description: Upload will be copied/moved content: application/json: schema: diff --git a/src/www/ui/api/index.php b/src/www/ui/api/index.php index c888ca9d5e..2b087de1e8 100644 --- a/src/www/ui/api/index.php +++ b/src/www/ui/api/index.php @@ -133,8 +133,8 @@ function (){ $this->get('[/{id:\\d+}]', UploadController::class . ':getUploads'); $this->delete('/{id:\\d+}', UploadController::class . ':deleteUpload'); - $this->patch('/{id:\\d+}', UploadController::class . ':moveUpload'); - $this->put('/{id:\\d+}', UploadController::class . ':copyUpload'); + $this->patch('/{id:\\d+}', UploadController::class . ':updateUpload'); + $this->put('/{id:\\d+}', UploadController::class . ':moveUpload'); $this->post('', UploadController::class . ':postUpload'); $this->get('/{id:\\d+}/summary', UploadController::class . ':getUploadSummary'); $this->get('/{id:\\d+}/licenses', UploadController::class . ':getUploadLicenses'); diff --git a/src/www/ui_tests/api/Controllers/UploadControllerTest.php b/src/www/ui_tests/api/Controllers/UploadControllerTest.php index 952bb66fbf..fdbb156665 100644 --- a/src/www/ui_tests/api/Controllers/UploadControllerTest.php +++ b/src/www/ui_tests/api/Controllers/UploadControllerTest.php @@ -44,6 +44,7 @@ use Slim\Http\Uri; use Fossology\Lib\Dao\FolderDao; use Fossology\Lib\Dao\AgentDao; +use Fossology\Lib\Dao\UserDao; use Fossology\UI\Api\Models\Hash; function TryToDelete($uploadpk, $user_pk, $group_pk, $uploadDao) @@ -141,6 +142,7 @@ protected function setUp() $this->uploadDao = M::mock(UploadDao::class); $this->folderDao = M::mock(FolderDao::class); $this->agentDao = M::mock(AgentDao::class); + $this->userDao = M::mock(UserDao::class); $this->dbManager->shouldReceive('getSingleRow') ->withArgs([M::any(), [$this->groupId, UploadStatus::OPEN, @@ -154,6 +156,8 @@ protected function setUp() ->andReturn($this->uploadDao); $this->restHelper->shouldReceive('getFolderDao') ->andReturn($this->folderDao); + $this->restHelper->shouldReceive('getUserDao') + ->andReturn($this->userDao); $container->shouldReceive('get')->withArgs(array( 'helper.restHelper'))->andReturn($this->restHelper); @@ -486,7 +490,7 @@ public function testGetSingleUploadNotUnpacked() /** * @test - * -# Test for UploadController::copyUpload() + * -# Test for UploadController::moveUpload() for a copy action * -# Check if response status is 202 */ public function testCopyUpload() @@ -503,10 +507,11 @@ public function testCopyUpload() $requestHeaders = new Headers(); $requestHeaders->set('folderId', $folderId); + $requestHeaders->set('action', 'copy'); $body = new Body(fopen('php://temp', 'r+')); $request = new Request("PUT", new Uri("HTTP", "localhost"), $requestHeaders, [], [], $body); - $actualResponse = $this->uploadController->copyUpload($request, + $actualResponse = $this->uploadController->moveUpload($request, new Response(), ['id' => $uploadId]); $this->assertEquals($expectedResponse->getStatusCode(), $actualResponse->getStatusCode()); @@ -530,6 +535,7 @@ public function testMoveUploadInvalidFolder() $requestHeaders = new Headers(); $requestHeaders->set('folderId', 'alpha'); + $requestHeaders->set('action', 'move'); $body = new Body(fopen('php://temp', 'r+')); $request = new Request("PATCH", new Uri("HTTP", "localhost"), $requestHeaders, [], [], $body); @@ -810,4 +816,80 @@ public function testGetUploadLicensesPendingScan() $this->assertEquals($expectedResponse->getHeaders(), $actualResponse->getHeaders()); } + + /** + * @test + * -# Test for UploadController::updateUpload() + * -# Check if response status is 202 + */ + public function testUpdateUpload() + { + $upload = 2; + $assignee = 4; + $status = UploadStatus::REJECTED; + $comment = "Not helpful"; + + $body = new Body( + fopen('data://text/plain;base64,' . base64_encode($comment), 'r+')); + $request = new Request("POST", new Uri("HTTP", "localhost", 80, + "/uploads/$upload", UploadController::FILTER_STATUS . "=Rejected&" . + UploadController::FILTER_ASSIGNEE . "=$assignee"), + new Headers(), [], [], $body); + + $this->userDao->shouldReceive('isAdvisorOrAdmin') + ->withArgs([$this->userId, $this->groupId]) + ->andReturn(true); + $this->userDao->shouldReceive('getUserChoices') + ->withArgs([$this->groupId]) + ->andReturn([$this->userId => "fossy", $assignee => "friendly-fossy"]); + $this->dbManager->shouldReceive('getSingleRow') + ->withArgs([M::any(), [$assignee, $this->groupId, $upload], M::any()]); + $this->dbManager->shouldReceive('getSingleRow') + ->withArgs([M::any(), [$status, $comment, $this->groupId, $upload], + M::any()]); + + $info = new Info(202, "Upload updated successfully.", InfoType::INFO); + $expectedResponse = (new Response())->withJson($info->getArray(), + $info->getCode()); + $actualResponse = $this->uploadController->updateUpload($request, + new Response(), ['id' => $upload]); + $this->assertEquals($expectedResponse->getStatusCode(), + $actualResponse->getStatusCode()); + $this->assertEquals($this->getResponseJson($expectedResponse), + $this->getResponseJson($actualResponse)); + } + + /** + * @test + * -# Test for UploadController::updateUpload() without permission + * -# Check if response status is 403 + */ + public function testUpdateUploadNoPerm() + { + $upload = 2; + $assignee = 4; + $comment = "Not helpful"; + + $body = new Body( + fopen('data://text/plain;base64,' . base64_encode($comment), 'r+')); + $request = new Request("POST", new Uri("HTTP", "localhost", 80, + "/uploads/$upload", UploadController::FILTER_STATUS . "=Rejected&" . + UploadController::FILTER_ASSIGNEE . "=$assignee"), + new Headers(), [], [], $body); + + $this->userDao->shouldReceive('isAdvisorOrAdmin') + ->withArgs([$this->userId, $this->groupId]) + ->andReturn(false); + + $info = new Info(403, "Not advisor or admin of current group. " . + "Can not update upload.", InfoType::ERROR); + $expectedResponse = (new Response())->withJson($info->getArray(), + $info->getCode()); + $actualResponse = $this->uploadController->updateUpload($request, + new Response(), ['id' => $upload]); + $this->assertEquals($expectedResponse->getStatusCode(), + $actualResponse->getStatusCode()); + $this->assertEquals($this->getResponseJson($expectedResponse), + $this->getResponseJson($actualResponse)); + } }