Skip to content

Commit

Permalink
Merge pull request #23 from mkdf/activity-monitor
Browse files Browse the repository at this point in the history
Activity monitor
  • Loading branch information
JaseMK authored Apr 19, 2022
2 parents 5b3d3f8 + 6b5aa7b commit beeb80e
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 5 deletions.
11 changes: 11 additions & 0 deletions module/apif-core/config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -98,6 +99,15 @@
],
],
],
'activity' => [
'type' => Segment::class,
'options' => [
'route' => '/activity/:id',
'defaults' => [
'controller' => Controller\ActivityController::class,
],
],
],
'file' => [
'type' => Segment::class,
'options' => [
Expand Down Expand Up @@ -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,
Expand Down
142 changes: 142 additions & 0 deletions module/apif-core/src/Controller/ActivityController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

namespace APIF\Core\Controller;

use APIF\Core\Repository\APIFCoreRepositoryInterface;
use APIF\Core\Service\ActivityLogManagerInterface;
use Laminas\Mvc\Controller\AbstractRestfulController;
use Laminas\View\Model\JsonModel;
use MongoDB;

class ActivityController extends AbstractRestfulController
{
private $_config;
private $_repository;
private $_readLogger;
private $_activityLog;

public function __construct(APIFCoreRepositoryInterface $repository, ActivityLogManagerInterface $activityLog, array $config, $readLogger)
{
$this->_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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace APIF\Core\Controller\Factory;

use APIF\Core\Controller\ActivityController;
use APIF\Core\Repository\APIFCoreRepositoryInterface;
use APIF\Core\Service\ActivityLogManagerInterface;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;

class ActivityControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$config = $container->get("Config");
$repository = $container->get(APIFCoreRepositoryInterface::class);
$readLogger = $container->get('apifReadLogger');
$activityLog = $container->get(ActivityLogManagerInterface::class);
return new ActivityController($repository, $activityLog, $config, $readLogger);
}
}
2 changes: 1 addition & 1 deletion module/apif-core/src/Controller/ObjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
40 changes: 39 additions & 1 deletion module/apif-core/view/apif/core/index/swagger-config-main.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand All @@ -26,6 +26,10 @@
"name": "file",
"description": "Create, retrieve, update and delete files"
},
{
"name": "activity",
"description": "Activity monitoring"
},
{
"name": "schemas",
"description": "Retrieve a schema"
Expand Down Expand Up @@ -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": [
Expand Down
40 changes: 39 additions & 1 deletion module/apif-core/view/apif/core/index/swagger-config-main.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand All @@ -27,6 +27,10 @@
"description": "Create, retrieve, update and delete files"
},
{
"name": "activity",
"description": "Activity monitoring"
},
{
"name": "schemas",
"description": "Retrieve a schema"
},
Expand Down Expand Up @@ -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": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down

0 comments on commit beeb80e

Please sign in to comment.