diff --git a/module/apif-core/config/module.config.php b/module/apif-core/config/module.config.php index d34b6c8..f076e8c 100644 --- a/module/apif-core/config/module.config.php +++ b/module/apif-core/config/module.config.php @@ -10,6 +10,7 @@ namespace APIF\Core; +use APIF\Core\Controller\Factory\ActivityControllerFactory; use APIF\Core\Controller\Factory\BrowseControllerFactory; use APIF\Core\Controller\Factory\DatasetManagementControllerFactory; use APIF\Core\Controller\Factory\FileControllerFactory; @@ -98,6 +99,15 @@ ], ], ], + 'activity' => [ + 'type' => Segment::class, + 'options' => [ + 'route' => '/activity/:id', + 'defaults' => [ + 'controller' => Controller\ActivityController::class, + ], + ], + ], 'file' => [ 'type' => Segment::class, 'options' => [ @@ -180,6 +190,7 @@ Controller\ObjectController::class => ObjectControllerFactory::class, Controller\BrowseController::class => BrowseControllerFactory::class, Controller\FileController::class => FileControllerFactory::class, + Controller\ActivityController::class => ActivityControllerFactory::class, Controller\DatasetManagementController::class => DatasetManagementControllerFactory::class, Controller\PermissionsManagementController::class => PermissionsManagementControllerFactory::class, Controller\SchemaRetrievalController::class => SchemaRetrievalControllerFactory::class, diff --git a/module/apif-core/src/Controller/ActivityController.php b/module/apif-core/src/Controller/ActivityController.php new file mode 100644 index 0000000..c9321af --- /dev/null +++ b/module/apif-core/src/Controller/ActivityController.php @@ -0,0 +1,142 @@ +_config = $config; + $this->_repository = $repository; + $this->_readLogger = $readLogger; + $this->_activityLog = $activityLog; + } + + private function _getAuth() { + //Check AUTH has been passed + $request_method = $_SERVER["REQUEST_METHOD"]; + if (!isset($_SERVER['PHP_AUTH_USER'])) { + header('WWW-Authenticate: Basic realm="Realm"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'Dataset key must be provided as HTTP Basic Auth username'; + exit; + } else { + $auth = [ + 'user' => $_SERVER['PHP_AUTH_USER'], + 'pwd' => $_SERVER['PHP_AUTH_PW'] + ]; + return $auth; + } + } + + private function _handleException($ex) { + if (is_a($ex, MongoDB\Driver\Exception\AuthenticationException::class) ){ + $this->getResponse()->setStatusCode(403); + }elseif(is_a($ex->getPrevious(), MongoDB\Driver\Exception\AuthenticationException::class)){ + $this->getResponse()->setStatusCode(403); + }elseif(is_a($ex, \Throwable::class)){ + $this->getResponse()->setStatusCode(500); + }else{ + // This will never happen + $this->getResponse()->setStatusCode(500); + } + } + + private function getPermissionsOnDataset($roles, $datasetId) { + $permissions = [ + 'read' => false, + 'write' => false + ]; + foreach ($roles as $role) { + $roleLabel = $role['role']; + $permissionLabel = substr($roleLabel, -1); + $datasetLabel = substr($roleLabel, 0, -2); + + // If we find a matching dataset... + if ($datasetLabel == $datasetId) { + if (($permissionLabel == 'R') AND ($permissions['read'] == false)) { + $permissions['read'] = true; + } + if (($permissionLabel == 'W') AND ($permissions['write'] == false)) { + $permissions['write'] = true; + } + } + } + + return $permissions; + } + + public function get($id) { + // Credentials supplied with the request + $key = $this->_getAuth()['user']; + $pwd = $this->_getAuth()['pwd']; + + //Get URL params + $timestampParam = $this->params()->fromQuery('timestamp', ""); + // This should = 0 or 1 if timestamp not supplied or an invalid string supplied. + // Either way, all items will be returned. + $timestamp = intval($timestampParam); + + // Admin credentials for querying the permissions DB + $adminUser = $this->_config['mongodb']['adminUser']; + $adminPwd = $this->_config['mongodb']['adminPwd']; + $adminAuth = [ + 'user' => $adminUser, + 'pwd' => $adminPwd + ]; + + // CHECK PERMISSIONS ON DATASET + try { + $results = $this->_repository->getKey($key,$adminAuth); + }catch (\Throwable $ex) { + $this->_handleException($ex); + return new JsonModel(['error' => 'Failed to retrieve permissions - ' . $ex->getMessage()]); + } + + $roles = $results[0]['users'][0]['roles']; + $permissions = $this->getPermissionsOnDataset($roles, $id); + + // ###### + // Access is granted to activity log if user/key has both READ and WRITE permission on this dataset + // ###### + if ($permissions['read'] AND $permissions['write']) { + $activityLogId = $this->_config['activityLog']['dataset']; + // ONLY RETRIEVE WRITE ACTIONS, NOT READS. + $query = [ + 'al:datasetId' => $id, + '$or' => [ + ['@type' => 'al:Create'], + ['@type' => 'al:Update'], + ['@type' => 'al:Delete'], + ['@type' => 'al:CreateFile'], + ['@type' => 'al:DeleteFile'], + ['@type' => 'al:OverwriteFile'], + ], + '_timestamp' => ['$gte' => $timestamp] + ]; + // DO NOT RETURN ANY FIELDS WHICH CONTAIN THE KEY USED IN THE ORIGINAL REQUEST + $projection = [ + 'al:summary' => false, + 'al:request.al:agent' => false, + ]; + $docs = $this->_repository->findDocs($activityLogId, $adminUser, $adminPwd, $query, $limit = null, $sort = null ,$projection); + } + else { + $this->getResponse()->setStatusCode(403); + return new JsonModel(['error' => 'Authorization failed, you do not have access to activity entries for this dataset']); + } + + return new JsonModel($docs); + } +} \ No newline at end of file diff --git a/module/apif-core/src/Controller/Factory/ActivityControllerFactory.php b/module/apif-core/src/Controller/Factory/ActivityControllerFactory.php new file mode 100644 index 0000000..9910dc7 --- /dev/null +++ b/module/apif-core/src/Controller/Factory/ActivityControllerFactory.php @@ -0,0 +1,21 @@ +get("Config"); + $repository = $container->get(APIFCoreRepositoryInterface::class); + $readLogger = $container->get('apifReadLogger'); + $activityLog = $container->get(ActivityLogManagerInterface::class); + return new ActivityController($repository, $activityLog, $config, $readLogger); + } +} \ No newline at end of file diff --git a/module/apif-core/src/Controller/ObjectController.php b/module/apif-core/src/Controller/ObjectController.php index 7cc1190..e8282d4 100644 --- a/module/apif-core/src/Controller/ObjectController.php +++ b/module/apif-core/src/Controller/ObjectController.php @@ -407,7 +407,7 @@ public function delete($id) { $datasetUUID = $id; $action = "Delete"; $summary = "Remove a document from dataset"; - $logData = $this->_assembleLogData($datasetUUID, $key, $action, $summary); + $logData = $this->_assembleLogData($datasetUUID, $key, $action, $summary, $docID); $this->_activityLog->logActivity($logData); return new JsonModel($response); diff --git a/module/apif-core/view/apif/core/index/swagger-config-main.json b/module/apif-core/view/apif/core/index/swagger-config-main.json index 1a612bb..4206e0f 100644 --- a/module/apif-core/view/apif/core/index/swagger-config-main.json +++ b/module/apif-core/view/apif/core/index/swagger-config-main.json @@ -11,7 +11,7 @@ "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, - "version": "0.8.4" + "version": "0.9.0" }, "tags": [ { @@ -26,6 +26,10 @@ "name": "file", "description": "Create, retrieve, update and delete files" }, + { + "name": "activity", + "description": "Activity monitoring" + }, { "name": "schemas", "description": "Retrieve a schema" @@ -521,6 +525,40 @@ } } }, + "/activity/{dataset-id}": { + "get": { + "tags": [ + "activity" + ], + "summary": "Retrieve details of write activities on a dataset", + "operationId": "getActivities", + "parameters": [ + { + "name": "dataset-id", + "in": "path", + "description": "Dataset ID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "timestamp", + "in": "query", + "description": "Optionally only retrieve entries since this timestamp", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Success" + } + } + } + }, "/schemas/{schema-id}": { "get": { "tags": [ diff --git a/module/apif-core/view/apif/core/index/swagger-config-main.phtml b/module/apif-core/view/apif/core/index/swagger-config-main.phtml index ae74407..ec634d2 100644 --- a/module/apif-core/view/apif/core/index/swagger-config-main.phtml +++ b/module/apif-core/view/apif/core/index/swagger-config-main.phtml @@ -11,7 +11,7 @@ "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, -"version": "0.8.4" +"version": "0.9.0" }, "tags": [ { @@ -27,6 +27,10 @@ "description": "Create, retrieve, update and delete files" }, { +"name": "activity", +"description": "Activity monitoring" +}, +{ "name": "schemas", "description": "Retrieve a schema" }, @@ -552,6 +556,40 @@ } } }, +"/activity/{dataset-id}": { +"get": { +"tags": [ +"activity" +], +"summary": "Retrieve details of write activities on a dataset", +"operationId": "getActivities", +"parameters": [ +{ +"name": "dataset-id", +"in": "path", +"description": "Dataset ID", +"required": true, +"schema": { +"type": "string" +} +}, +{ +"name": "timestamp", +"in": "query", +"description": "Optionally only retrieve entries since this timestamp", +"required": false, +"schema": { +"type": "integer" +} +} +], +"responses": { +"200": { +"description": "Success" +} +} +} +}, "/schemas/{schema-id}": { "get": { "tags": [ diff --git a/module/apif-core/view/apif/core/index/swagger-config-management.json b/module/apif-core/view/apif/core/index/swagger-config-management.json index 92c779f..7f7e0de 100644 --- a/module/apif-core/view/apif/core/index/swagger-config-management.json +++ b/module/apif-core/view/apif/core/index/swagger-config-management.json @@ -11,7 +11,7 @@ "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, - "version": "0.8.4" + "version": "0.9.0" }, "tags": [ { diff --git a/module/apif-core/view/apif/core/index/swagger-config-management.phtml b/module/apif-core/view/apif/core/index/swagger-config-management.phtml index 31eb9ef..e7055f9 100644 --- a/module/apif-core/view/apif/core/index/swagger-config-management.phtml +++ b/module/apif-core/view/apif/core/index/swagger-config-management.phtml @@ -11,7 +11,7 @@ "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, -"version": "0.8.4" +"version": "0.9.0" }, "tags": [ {