Merge branch '3.x' into feature/3.x_HttpTool

pull/9461/head
Christophe Vandeplas 2024-01-07 13:31:11 +00:00
commit 9670343ee9
17 changed files with 1208 additions and 246 deletions

View File

@ -3,19 +3,19 @@
namespace App\Controller\Admin;
use App\Controller\AppController;
use App\Model\Entity\AccessLog;
use Cake\Core\Configure;
use Cake\Http\Exception\NotFoundException;
class AccessLogsController extends AppController
{
protected $fields = ['id', 'created', 'user_id', 'org_id', 'authkey_id', 'ip', 'request_method', 'user_agent', 'request_id', 'controller', 'action', 'url', 'response_code', 'memory_usage', 'duration', 'query_count', 'request'];
protected $contain = [
'Users' => ['fields' => ['id', 'email', 'org_id']],
'Organisations' => ['fields' => ['id', 'name', 'uuid']],
];
public $paginate = [
'recursive' => -1,
'limit' => 60,
'fields' => ['id', 'created', 'user_id', 'org_id', 'authkey_id', 'ip', 'request_method', 'user_agent', 'request_id', 'controller', 'action', 'url', 'response_code', 'memory_usage', 'duration', 'query_count', 'request'],
'contain' => [
'Users' => ['fields' => ['id', 'email', 'org_id']],
'Organisations' => ['fields' => ['id', 'name', 'uuid']],
],
'order' => [
'AccessLogs.id' => 'DESC'
],
@ -77,7 +77,7 @@ class AccessLogsController extends AppController
]
);
// $conditions = $this->__searchConditions($params);
$conditions = $this->__searchConditions($params);
$afterFindHandler = function ($entry) {
if (!empty($entry['request'])) {
@ -91,6 +91,9 @@ class AccessLogsController extends AppController
'filters' => $this->filterFields,
'quickFilters' => $this->quickFilterFields,
'afterFind' => $afterFindHandler,
'conditions' => $conditions,
'contain' => $this->contain,
'fields' => $this->fields,
]
);
@ -108,8 +111,8 @@ class AccessLogsController extends AppController
$request = $this->AccessLogs->find(
'all',
[
'conditions' => ['AccessLogs.id' => $id],
'fields' => ['AccessLogs.request'],
'conditions' => ['id' => $id],
'fields' => ['request'],
]
)->first();
if (empty($request)) {
@ -164,4 +167,111 @@ class AccessLogsController extends AppController
{
$this->CRUD->filtering();
}
/**
* @param array $params
* @return array
*/
private function __searchConditions(array $params)
{
$qbRules = [];
foreach ($params as $key => $value) {
if ($key === 'created') {
$qbRules[] = [
'id' => $key,
'operator' => is_array($value) ? 'between' : 'greater_or_equal',
'value' => $value,
];
} else {
if (is_array($value)) {
$value = implode('||', $value);
}
$qbRules[] = [
'id' => $key,
'value' => $value,
];
}
}
$this->set('qbRules', $qbRules);
$conditions = [];
if (isset($params['user'])) {
if (is_numeric($params['user'])) {
$conditions['user_id'] = $params['user'];
} else {
$user = $this->User->find(
'first',
[
'conditions' => ['User.email' => $params['user']],
'fields' => ['id'],
]
);
if (!empty($user)) {
$conditions['user_id'] = $user['User']['id'];
} else {
$conditions['user_id'] = -1;
}
}
}
if (isset($params['ip'])) {
$conditions['ip'] = inet_pton($params['ip']);
}
foreach (['authkey_id', 'request_id', 'controller', 'action'] as $field) {
if (isset($params[$field])) {
$conditions['' . $field] = $params[$field];
}
}
if (isset($params['url'])) {
$conditions['url LIKE'] = "%{$params['url']}%";
}
if (isset($params['user_agent'])) {
$conditions['user_agent LIKE'] = "%{$params['user_agent']}%";
}
if (isset($params['memory_usage'])) {
$conditions['memory_usage >='] = ($params['memory_usage'] * 1024);
}
if (isset($params['memory_usage'])) {
$conditions['memory_usage >='] = ($params['memory_usage'] * 1024);
}
if (isset($params['duration'])) {
$conditions['duration >='] = $params['duration'];
}
if (isset($params['query_count'])) {
$conditions['query_count >='] = $params['query_count'];
}
if (isset($params['request_method'])) {
$methodId = array_flip(AccessLog::REQUEST_TYPES)[$params['request_method']] ?? -1;
$conditions['request_method'] = $methodId;
}
if (isset($params['org'])) {
if (is_numeric($params['org'])) {
$conditions['org_id'] = $params['org'];
} else {
$org = $this->AccessLog->Organisation->fetchOrg($params['org']);
if ($org) {
$conditions['org_id'] = $org['id'];
} else {
$conditions['org_id'] = -1;
}
}
}
if (isset($params['created'])) {
$tempData = is_array($params['created']) ? $params['created'] : [$params['created']];
foreach ($tempData as $k => $v) {
$tempData[$k] = $this->AccessLog->resolveTimeDelta($v);
}
if (count($tempData) === 1) {
$conditions['created >='] = date("Y-m-d H:i:s", $tempData[0]);
} else {
if ($tempData[0] < $tempData[1]) {
$temp = $tempData[1];
$tempData[1] = $tempData[0];
$tempData[0] = $temp;
}
$conditions['AND'][] = ['created <=' => date("Y-m-d H:i:s", $tempData[0])];
$conditions['AND'][] = ['created >=' => date("Y-m-d H:i:s", $tempData[1])];
}
}
return $conditions;
}
}

View File

@ -0,0 +1,664 @@
<?php
namespace App\Controller\Admin;
use App\Controller\AppController;
use App\Model\Entity\AuditLog;
use Cake\Core\Configure;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\NotFoundException;
use Cake\ORM\Locator\LocatorAwareTrait;
use Exception;
class AuditLogsController extends AppController
{
use LocatorAwareTrait;
/** @var array */
private $actions;
/** @var string[] */
private $models = [
'Attribute',
'Allowedlist',
'AuthKey',
'Cerebrate',
'CorrelationExclusion',
'Event',
'EventBlocklist',
'EventReport',
'Feed',
'DecayingModel',
'Object',
'ObjectTemplate',
'Organisation',
'OrgBlocklist',
'Post',
'Regexp',
'Role',
'Server',
'ShadowAttribute',
'SharingGroup',
'SystemSetting',
'Tag',
'TagCollection',
'TagCollectionTag',
'Task',
'Taxonomy',
'Template',
'Thread',
'User',
'UserSetting',
'Galaxy',
'GalaxyCluster',
'GalaxyClusterBlocklist',
'GalaxyClusterRelation',
'News',
'Warninglist',
'Workflow',
'WorkflowBlueprint',
];
// Pagination
protected $fields = ['id', 'created', 'user_id', 'org_id', 'request_action', 'model', 'model_id', 'model_title', 'event_id', 'changed'];
protected $contain = [
'Users' => ['fields' => ['id', 'email', 'org_id']],
'Organisations' => ['fields' => ['id', 'name', 'uuid']],
];
protected $conditions = [];
public $paginate = [
'limit' => 60,
'order' => [
'id' => 'DESC'
],
];
public function __construct($request = null, $response = null)
{
parent::__construct($request, $response);
$this->actions = [
AuditLog::ACTION_ADD => __('Add'),
AuditLog::ACTION_EDIT => __('Edit'),
AuditLog::ACTION_SOFT_DELETE => __('Soft delete'),
AuditLog::ACTION_DELETE => __('Delete'),
AuditLog::ACTION_UNDELETE => __('Undelete'),
AuditLog::ACTION_TAG => __('Tag'),
AuditLog::ACTION_TAG_LOCAL => __('Tag'),
AuditLog::ACTION_REMOVE_TAG => __('Remove tag'),
AuditLog::ACTION_REMOVE_TAG_LOCAL => __('Remove tag'),
AuditLog::ACTION_GALAXY => __('Galaxy cluster'),
AuditLog::ACTION_GALAXY_LOCAL => __('Galaxy cluster'),
AuditLog::ACTION_REMOVE_GALAXY => __('Remove galaxy cluster'),
AuditLog::ACTION_REMOVE_GALAXY_LOCAL => __('Remove galaxy cluster'),
AuditLog::ACTION_PUBLISH => __('Publish'),
AuditLog::ACTION_PUBLISH_SIGHTINGS => __('Publish sightings'),
];
}
private function __applyAuditACL(array $user)
{
$acl = [];
if (empty($user['Role']['perm_site_admin'])) {
if (!empty($user['Role']['perm_admin'])) {
// ORG admins can see their own org info
$acl = ['AuditLog.org_id' => $user['org_id']];
} else {
// users can see their own info
$acl = ['AuditLog.user_id' => $user['id']];
}
}
return $acl;
}
public function index()
{
$this->fields[] = 'ip';
$this->fields[] = 'request_type';
$this->fields[] = 'authkey_id';
if ($this->ParamHandler->isRest()) {
$this->fields[] = 'request_id';
}
if (!Configure::read('MISP.log_new_audit')) {
$this->Flash->warning(__("Audit log is not enabled. See 'MISP.log_new_audit' in the Server Settings. (Administration -> Server Settings -> MISP tab)"));
}
$params = $this->harvestParameters(
[
'ip',
'user',
'request_id',
'authkey_id',
'model',
'model_id',
'event_id',
'model_title',
'action',
'org',
'created',
'request_type',
]
);
$this->conditions = $this->__searchConditions($params);
$acl = $this->__applyAuditACL($this->ACL->getUser()->toArray());
if ($acl) {
$this->conditions['AND'][] = $acl;
}
$query = $this->AuditLogs->find(
'all',
[
'conditions' => $this->conditions,
'fields' => $this->fields,
'contain' => $this->contain,
]
);
$list = $this->paginate($query)->toArray();
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData($list, 'json');
}
$list = $this->__appendModelLinks($list);
foreach ($list as $k => $item) {
$list[$k]['AuditLog']['action_human'] = $this->actions[$item['AuditLog']['action']];
}
$this->set('list', $list);
$this->set(
'actions',
[
AuditLog::ACTION_ADD => __('Add'),
AuditLog::ACTION_EDIT => __('Edit'),
AuditLog::ACTION_SOFT_DELETE => __('Soft delete'),
AuditLog::ACTION_DELETE => __('Delete'),
AuditLog::ACTION_UNDELETE => __('Undelete'),
AuditLog::ACTION_TAG . '||' . AuditLog::ACTION_TAG_LOCAL => __('Tag'),
AuditLog::ACTION_REMOVE_TAG . '||' . AuditLog::ACTION_REMOVE_TAG_LOCAL => __('Remove tag'),
AuditLog::ACTION_GALAXY . '||' . AuditLog::ACTION_GALAXY_LOCAL => __('Galaxy cluster'),
AuditLog::ACTION_REMOVE_GALAXY . '||' . AuditLog::ACTION_REMOVE_GALAXY_LOCAL => __('Remove galaxy cluster'),
AuditLog::ACTION_PUBLISH => __('Publish'),
AuditLog::ACTION_PUBLISH_SIGHTINGS => $this->actions[AuditLog::ACTION_PUBLISH_SIGHTINGS],
]
);
$models = $this->models;
sort($models);
$this->set('models', $models);
$this->set('title_for_layout', __('Audit logs'));
}
public function eventIndex($eventId, $org = null)
{
$event = $this->AuditLogs->Event->fetchSimpleEvent($this->Auth->user(), $eventId);
if (empty($event)) {
throw new NotFoundException('Invalid event.');
}
$this->paginate['conditions'] = $this->__createEventIndexConditions($event);
$this->set('passedArgsArray', ['eventId' => $eventId, 'org' => $org]);
$params = $this->IndexFilter->harvestParameters(['created', 'org']);
if ($org) {
$params['org'] = $org;
}
$this->paginate['conditions'][] = $this->__searchConditions($params);
$list = $this->paginate();
if (!$this->isSiteAdmin()) {
// Remove all user info about users from different org
$orgUserIds = $this->Users->find(
'column',
[
'conditions' => ['Users.org_id' => $this->Auth->user('org_id')],
'fields' => ['Users.id'],
]
);
foreach ($list as $k => $item) {
if ($item['AuditLog']['user_id'] == 0) {
continue;
}
if (!in_array($item['User']['id'], $orgUserIds)) {
unset($list[$k]['User']);
unset($list[$k]['AuditLog']['user_id']);
}
}
}
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData($list, 'json');
}
foreach ($list as $k => $item) {
$list[$k]['AuditLog']['action_human'] = $this->actions[$item['AuditLog']['action']];
}
$this->set('data', $list);
$this->set('event', $event);
$this->set('mayModify', $this->ACL->canModifyEvent($event));
$this->set(
'menuData',
[
'menuList' => 'event',
'menuItem' => 'eventLog'
]
);
}
public function fullChange($id)
{
$log = $this->AuditLogs->find(
'all',
[
'conditions' => ['id' => $id],
'recursive' => -1,
'fields' => ['changed', 'request_action'],
]
)->first();
if (empty($log)) {
throw new Exception('Log not found.');
}
$this->set('log', $log);
}
public function returnDates($org = 'all')
{
$user = $this->closeSession();
if (!$user['Role']['perm_sharing_group'] && !empty(Configure::read('Security.hide_organisation_index_from_users'))) {
if ($org !== 'all' && $org !== $user['Organisation']['name']) {
throw new MethodNotAllowedException('Invalid organisation.');
}
}
$data = $this->AuditLogs->returnDates($org);
return $this->RestResponse->viewData($data, $this->response->getType());
}
/**
* @return array
*/
private function __searchConditions(array $params)
{
$conditions = [];
$qbRules = [];
foreach ($params as $key => $value) {
if ($key === 'model' && strpos($value, ':') !== false) {
$parts = explode(':', $value);
$qbRules[] = [
'id' => 'model',
'value' => $parts[0],
];
$qbRules[] = [
'id' => 'model_id',
'value' => $parts[1],
];
} elseif ($key === 'created') {
$qbRules[] = [
'id' => $key,
'operator' => is_array($value) ? 'between' : 'greater_or_equal',
'value' => $value,
];
} else {
if (is_array($value)) {
$value = implode('||', $value);
}
$qbRules[] = [
'id' => $key,
'value' => $value,
];
}
}
$this->set('qbRules', $qbRules);
if (isset($params['user'])) {
if (strtoupper($params['user']) === 'SYSTEM') {
$conditions['AuditLog.user_id'] = 0;
} else if (is_numeric($params['user'])) {
$conditions['AuditLog.user_id'] = $params['user'];
} else {
$user = $this->Users->find(
'first',
[
'conditions' => ['Users.email' => $params['user']],
'fields' => ['id'],
]
);
if (!empty($user)) {
$conditions['AuditLog.user_id'] = $user['User']['id'];
} else {
$conditions['AuditLog.user_id'] = -1;
}
}
}
if (isset($params['ip'])) {
$conditions['AuditLog.ip'] = inet_pton($params['ip']);
}
if (isset($params['authkey_id'])) {
$conditions['AuditLog.authkey_id'] = $params['authkey_id'];
}
if (isset($params['request_id'])) {
$conditions['AuditLog.request_id'] = $params['request_id'];
}
if (isset($params['request_type'])) {
$conditions['AuditLog.request_type'] = $params['request_type'];
}
if (isset($params['model'])) {
$conditions['AuditLog.model'] = $params['model'];
}
if (isset($params['model_id'])) {
$conditions['AuditLog.model_id'] = $params['model_id'];
}
if (isset($params['event_id'])) {
$conditions['AuditLog.event_id'] = $params['event_id'];
}
if (isset($params['model_title'])) {
$conditions['AuditLog.model_title LIKE'] = '%' . $params['model_title'] . '%';
}
if (isset($params['action'])) {
$conditions['AuditLog.request_'] = $params['action'];
}
if (isset($params['org'])) {
if (is_numeric($params['org'])) {
$conditions['AuditLog.org_id'] = $params['org'];
} else {
$org = $this->AuditLogs->Organisation->fetchOrg($params['org']);
if ($org) {
$conditions['AuditLog.org_id'] = $org['id'];
} else {
$conditions['AuditLog.org_id'] = -1;
}
}
}
if (isset($params['created'])) {
$tempData = is_array($params['created']) ? $params['created'] : [$params['created']];
foreach ($tempData as $k => $v) {
$tempData[$k] = $this->AuditLogs->resolveTimeDelta($v);
}
if (count($tempData) === 1) {
$conditions['AuditLog.created >='] = date("Y-m-d H:i:s", $tempData[0]);
} else {
if ($tempData[0] < $tempData[1]) {
$temp = $tempData[1];
$tempData[1] = $tempData[0];
$tempData[0] = $temp;
}
$conditions['AND'][] = ['AuditLog.created <=' => date("Y-m-d H:i:s", $tempData[0])];
$conditions['AND'][] = ['AuditLog.created >=' => date("Y-m-d H:i:s", $tempData[1])];
}
}
return $conditions;
}
/**
* Create conditions that will include just events parts that user can see.
* @param array $event
* @return array
*/
private function __createEventIndexConditions(array $event)
{
if ($this->isSiteAdmin() || $event['Event']['orgc_id'] == $this->Auth->user('org_id')) {
// Site admins and event owners can see all changes
return ['event_id' => $event['Event']['id']];
}
$event = $this->AuditLogs->Event->fetchEvent(
$this->Auth->user(),
[
'eventid' => $event['Event']['id'],
'sgReferenceOnly' => 1,
'deleted' => [0, 1],
'deleted_proposals' => 1,
'noSightings' => true,
'includeEventCorrelations' => false,
'excludeGalaxy' => true,
]
)[0];
$attributeIds = [];
$objectIds = [];
$proposalIds = array_column($event['ShadowAttribute'], 'id');
$objectReferenceId = [];
foreach ($event['Attribute'] as $aa) {
$attributeIds[] = $aa['id'];
if (!empty($aa['ShadowAttribute'])) {
foreach ($aa['ShadowAttribute'] as $sa) {
$proposalIds[] = $sa['id'];
}
}
}
unset($event['Attribute']);
foreach ($event['Object'] as $ob) {
foreach ($ob['Attribute'] as $aa) {
$attributeIds[] = $aa['id'];
if (!empty($aa['ShadowAttribute'])) {
foreach ($aa['ShadowAttribute'] as $sa) {
$proposalIds[] = $sa['id'];
}
}
}
foreach ($ob['ObjectReference'] as $or) {
$objectReferenceId[] = $or['id'];
}
$objectIds[] = $ob['id'];
}
unset($event['Object']);
$conditions = [];
$conditions['AND']['event_id'] = $event['Event']['id'];
$conditions['AND']['OR'][] = ['model' => 'Event'];
$parts = [
'Attribute' => $attributeIds,
'ShadowAttribute' => $proposalIds,
'Object' => $objectIds,
'ObjectReference' => $objectReferenceId,
'EventReport' => array_column($event['EventReport'], 'id'),
];
foreach ($parts as $model => $modelIds) {
if (!empty($modelIds)) {
$conditions['AND']['OR'][] = [
'AND' => [
'model' => $model,
'model_id' => $modelIds,
],
];
}
}
return $conditions;
}
/**
* Generate link to model view if exists and use has permission to access it.
* @param array $auditLogs
* @return array
*/
private function __appendModelLinks(array $auditLogs)
{
$models = [];
foreach ($auditLogs as $auditLog) {
if (isset($models[$auditLog['AuditLog']['model']])) {
$models[$auditLog['AuditLog']['model']][] = $auditLog['AuditLog']['model_id'];
} else {
$models[$auditLog['AuditLog']['model']] = [$auditLog['AuditLog']['model_id']];
}
}
$eventIds = isset($models['Event']) ? $models['Event'] : [];
if (isset($models['ObjectReference'])) {
$ObjectReferencesTable = $this->fetchTable('ObjectReferences');
$objectReferences = $ObjectReferencesTable->find(
'list',
[
'conditions' => ['ObjectReference.id' => array_unique($models['ObjectReference'])],
'fields' => ['ObjectReference.id', 'ObjectReference.object_id'],
]
)->toArray();
}
if (isset($models['Object']) || isset($objectReferences)) {
$objectIds = array_unique(
array_merge(
isset($models['Object']) ? $models['Object'] : [],
isset($objectReferences) ? array_values($objectReferences) : []
)
);
$MispObjectsTable = $this->fetchTable('MispObjects');
$conditions = $MispObjectsTable->buildConditions($this->Auth->user());
$conditions['Object.id'] = $objectIds;
$objects = $this->MispObject->find(
'all',
[
'conditions' => $conditions,
'contain' => ['Event'],
'fields' => ['Object.id', 'Object.event_id', 'Object.uuid', 'Object.deleted'],
]
);
$objects = array_column(array_column($objects, 'Object'), null, 'id');
$eventIds = array_merge($eventIds, array_column($objects, 'event_id'));
}
if (isset($models['Attribute'])) {
$AttributesTable = $this->fetchTable('Attributes');
$attributes = $AttributesTable->fetchAttributesSimple(
$this->Auth->user(),
[
'conditions' => ['Attribute.id' => array_unique($models['Attribute'])],
'fields' => ['Attribute.id', 'Attribute.event_id', 'Attribute.uuid', 'Attribute.deleted'],
]
);
$attributes = array_column(array_column($attributes, 'Attribute'), null, 'id');
$eventIds = array_merge($eventIds, array_column($attributes, 'event_id'));
}
if (isset($models['ShadowAttribute'])) {
$ShadowAttributesTable = $this->fetchTable('ShadowAttributes');
$conditions = $ShadowAttributesTable->buildConditions($this->Auth->user());
$conditions['AND'][] = ['ShadowAttribute.id' => array_unique($models['ShadowAttribute'])];
$shadowAttributes = $ShadowAttributesTable->find(
'all',
[
'conditions' => $conditions,
'fields' => ['ShadowAttribute.id', 'ShadowAttribute.event_id', 'ShadowAttribute.uuid', 'ShadowAttribute.deleted'],
'contain' => ['Event', 'Attribute'],
]
)->toArray();
$shadowAttributes = array_column(array_column($shadowAttributes, 'ShadowAttribute'), null, 'id');
$eventIds = array_merge($eventIds, array_column($shadowAttributes, 'event_id'));
}
if (!empty($eventIds)) {
$EventsTable = $this->fetchTable('Events');
$conditions = $EventsTable->createEventConditions($this->Auth->user());
$conditions['Event.id'] = array_unique($eventIds);
$events = $EventsTable->find(
'list',
[
'conditions' => $conditions,
'fields' => ['Event.id', 'Event.info'],
]
);
}
$links = [
'ObjectTemplate' => 'objectTemplates',
'AuthKey' => 'auth_keys',
'GalaxyCluster' => 'galaxy_clusters',
'Galaxy' => 'galaxies',
'Organisation' => 'organisation',
'Warninglist' => 'warninglists',
'User' => 'admin/users',
'Role' => 'roles',
'EventReport' => 'eventReports',
'SharingGroup' => 'sharing_groups',
'Taxonomy' => 'taxonomies',
];
$existingObjects = [];
foreach ($links as $modelName => $foo) {
if (isset($models[$modelName])) {
$ModelTable = $this->fetchTable($modelName);
$data = $ModelTable->find(
'column',
[
'conditions' => ['id' => array_unique($models[$modelName])],
'fields' => ['id'],
]
)->toArray();
$existingObjects[$modelName] = array_flip($data);
}
}
foreach ($auditLogs as $k => $auditLog) {
$auditLog = $auditLog['AuditLog'];
$modelId = (int)$auditLog['model_id'];
$url = null;
$eventInfo = null;
switch ($auditLog['model']) {
case 'Event':
if (isset($events[$modelId])) {
$url = '/events/view/' . $modelId;
$eventInfo = $events[$modelId];
}
break;
case 'ObjectReference':
if (isset($objectReferences[$modelId]) && isset($objects[$objectReferences[$modelId]])) {
$url = '/events/view/' . $objects[$objectReferences[$modelId]]['event_id'] . '/focus:' . $objects[$objectReferences[$modelId]]['uuid'];
if ($objects[$objectReferences[$modelId]]['deleted']) {
$url .= '/deleted:2';
}
if (isset($events[$objects[$objectReferences[$modelId]]['event_id']])) {
$eventInfo = $events[$objects[$objectReferences[$modelId]]['event_id']];
}
}
break;
case 'Object':
if (isset($objects[$modelId])) {
$url = '/events/view/' . $objects[$modelId]['event_id'] . '/focus:' . $objects[$modelId]['uuid'];
if ($objects[$modelId]['deleted']) {
$url .= '/deleted:2';
}
if (isset($events[$objects[$modelId]['event_id']])) {
$eventInfo = $events[$objects[$modelId]['event_id']];
}
}
break;
case 'Attribute':
if (isset($attributes[$modelId])) {
$url = '/events/view/' . $attributes[$modelId]['event_id'] . '/focus:' . $attributes[$modelId]['uuid'];
if ($attributes[$modelId]['deleted']) {
$url .= '/deleted:2';
}
if (isset($events[$attributes[$modelId]['event_id']])) {
$eventInfo = $events[$attributes[$modelId]['event_id']];
}
}
break;
case 'ShadowAttribute':
if (isset($shadowAttributes[$modelId])) {
$url = '/events/view/' . $shadowAttributes[$modelId]['event_id'] . '/focus:' . $shadowAttributes[$modelId]['uuid'];
if (isset($events[$shadowAttributes[$modelId]['event_id']])) {
$eventInfo = $events[$shadowAttributes[$modelId]['event_id']];
}
}
break;
default:
if (isset($existingObjects[$auditLog['model']][$modelId])) {
$url = '/' . $links[$auditLog['model']] . '/view/' . $modelId;
} else {
continue 2;
}
}
if ($url) {
$auditLogs[$k]['AuditLog']['model_link'] = $this->baseurl . $url;
}
if ($eventInfo) {
$auditLogs[$k]['AuditLog']['event_info'] = $eventInfo;
}
}
return $auditLogs;
}
}

View File

@ -51,6 +51,9 @@ class AppController extends Controller
public $MetaTemplates = null;
public $Users = null;
/** @var AuditLog|null */
protected $AuditLogs = null;
/**
* Initialization hook method.
*
@ -110,6 +113,8 @@ class AppController extends Controller
Configure::write('DebugKit.forceEnable', true);
}
$this->loadComponent('CustomPagination');
$this->AuditLogs = $this->fetchTable('AuditLogs');
// $this->loadComponent('FloodProtection'); // TODO: enable after flood protection table exists
/*
* Enable the following component for recommended CakePHP form protection settings.

View File

@ -10,6 +10,7 @@ use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\NotFoundException;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use InvalidArgumentException;
class ACLComponent extends Component
{
@ -674,4 +675,28 @@ class ACLComponent extends Component
}
return $menu;
}
/**
* Returns true if user can modify given event.
*
* @param array $event
* @param array $user
* @return bool
*/
public function canModifyEvent(array $user, array $event)
{
if (!isset($event['Event'])) {
throw new InvalidArgumentException('Passed object does not contain an Event.');
}
if ($user['Role']['perm_site_admin']) {
return true;
}
if ($user['Role']['perm_modify_org'] && $event['Event']['orgc_id'] == $user['org_id']) {
return true;
}
if ($user['Role']['perm_modify'] && $event['Event']['user_id'] == $user['id']) {
return true;
}
return false;
}
}

View File

@ -18,30 +18,30 @@ class GalaxyClustersController extends AppController
{
use LocatorAwareTrait;
protected $conditions = [];
protected $contain = [
'Tag' => [
'fields' => ['Tag.id'],
/*
'EventTag' => array(
'fields' => array('EventTag.event_id')
),
'AttributeTag' => array(
'fields' => array('AttributeTag.event_id', 'AttributeTag.attribute_id')
)
*/
],
'GalaxyElement' => [
'conditions' => ['GalaxyElement.key' => 'synonyms'],
'fields' => ['value']
],
];
public $paginate = [
'limit' => 60,
'recursive' => -1,
'order' => [
'GalaxyClusters.version' => 'DESC',
'GalaxyClusters.value' => 'ASC'
],
'contain' => [
'Tag' => [
'fields' => ['Tag.id'],
/*
'EventTag' => array(
'fields' => array('EventTag.event_id')
),
'AttributeTag' => array(
'fields' => array('AttributeTag.event_id', 'AttributeTag.attribute_id')
)
*/
],
'GalaxyElement' => [
'conditions' => ['GalaxyElement.key' => 'synonyms'],
'fields' => ['value']
],
]
];
public function initialize(): void
@ -124,11 +124,19 @@ class GalaxyClustersController extends AppController
return $this->RestResponse->viewData($clusters, $this->response->getType());
}
$this->paginate['conditions']['AND'][] = $contextConditions;
$this->paginate['conditions']['AND'][] = $searchConditions;
$this->paginate['conditions']['AND'][] = $aclConditions;
$this->paginate['contain'] = array_merge($this->paginate['contain'], ['Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation']);
$clusters = $this->paginate();
$this->conditions['AND'][] = $contextConditions;
$this->conditions['AND'][] = $searchConditions;
$this->conditions['AND'][] = $aclConditions;
$this->contain = array_merge($this->contain, ['Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation']);
$query = $this->GalaxyClusters->find(
'all',
[
'conditions' => $this->conditions,
'contain' => $this->contain
]
);
$clusters = $this->paginate($query);
$this->GalaxyClusters->attachExtendByInfo($this->ACL->getUser()->toArray(), $clusters);

View File

@ -10,7 +10,6 @@ class GalaxyElementsController extends AppController
{
public $paginate = [
'limit' => 20,
'recursive' => -1,
'order' => [
'GalaxyElement.key' => 'ASC'
]

View File

@ -15,17 +15,17 @@ class JobsController extends AppController
{
use LocatorAwareTrait;
protected $conditions = [];
protected $contain = [
'Organisations' => [
'fields' => ['id', 'name', 'uuid'],
],
];
public $paginate = [
'limit' => 20,
'recursive' => 0,
'order' => [
'Job.id' => 'DESC'
],
'contain' => [
'Organisations' => [
'fields' => ['id', 'name', 'uuid'],
],
]
];
public function beforeFilter(EventInterface $event)
@ -46,9 +46,16 @@ class JobsController extends AppController
$workers = $ServerTable->workerDiagnostics($issueCount);
$queues = ['email', 'default', 'cache', 'prio', 'update'];
if ($queue && in_array($queue, $queues, true)) {
$this->paginate['conditions'] = ['Job.worker' => $queue];
$this->conditions = ['Job.worker' => $queue];
}
$jobs = $this->paginate()->toArray();
$query = $this->Jobs->find(
'all',
[
'conditions' => $this->conditions,
'contain' => $this->contain,
]
);
$jobs = $this->paginate($query)->toArray();
foreach ($jobs as &$job) {
if (!empty($job['process_id'])) {
$job['job_status'] = $this->getJobStatus($job['process_id']);

View File

@ -6,17 +6,16 @@ use App\Controller\AppController;
class ObjectTemplateElementsController extends AppController
{
public $paginate = array(
public $paginate = [
'limit' => 60,
'order' => array(
'order' => [
'ObjectTemplateElement.id' => 'desc'
),
'recursive' => -1
);
],
];
public function viewElements($id, $context = 'all')
{
$this->paginate['conditions'] = array('ObjectTemplateElements.object_template_id' => $id);
$this->paginate['conditions'] = ['ObjectTemplateElements.object_template_id' => $id];
$elements = $this->paginate();
$this->set('list', $elements);
$this->layout = false;

View File

@ -22,7 +22,6 @@ class ObjectTemplatesController extends AppController
'order' => [
'Object.id' => 'desc'
],
'recursive' => -1
];
public function beforeFilter(EventInterface $event)
@ -41,17 +40,20 @@ class ObjectTemplatesController extends AppController
$metas = $this->ObjectTemplate->find(
'column',
[
'conditions' => ['ObjectTemplate.active' => 1],
'fields' => ['ObjectTemplate.meta_category'],
'order' => ['ObjectTemplate.meta_category asc'],
'unique' => true,
'conditions' => ['ObjectTemplate.active' => 1],
'fields' => ['ObjectTemplate.meta_category'],
'order' => ['ObjectTemplate.meta_category asc'],
'unique' => true,
]
);
$items = [[
'name' => __('All Objects'),
'value' => $this->baseurl . "/ObjectTemplates/objectChoice/$eventId/0"
]];
$items = [
[
'name' => __('All Objects'),
'value' => $this->baseurl . "/ObjectTemplates/objectChoice/$eventId/0"
]
];
foreach ($metas as $meta) {
$items[] = [
'name' => $meta,
@ -63,7 +65,7 @@ class ObjectTemplatesController extends AppController
$this->set(
'options',
[
'multiple' => 0,
'multiple' => 0,
]
);
$this->render('/Elements/generic_picker');
@ -80,10 +82,10 @@ class ObjectTemplatesController extends AppController
$templates_raw = $this->ObjectTemplate->find(
'all',
[
'recursive' => -1,
'conditions' => $conditions,
'fields' => ['id', 'meta_category', 'name', 'description'],
'order' => ['ObjectTemplate.name asc']
'recursive' => -1,
'conditions' => $conditions,
'fields' => ['id', 'meta_category', 'name', 'description'],
'order' => ['ObjectTemplate.name asc']
]
);
@ -105,11 +107,11 @@ class ObjectTemplatesController extends AppController
$this->set(
'options',
[
'functionName' => 'redirectAddObject',
'multiple' => 0,
'select_options' => [
'additionalData' => ['event_id' => $event_id],
],
'functionName' => 'redirectAddObject',
'multiple' => 0,
'select_options' => [
'additionalData' => ['event_id' => $event_id],
],
]
);
$this->render('/Elements/generic_picker');
@ -121,10 +123,10 @@ class ObjectTemplatesController extends AppController
$temp = $this->ObjectTemplates->find(
'all',
[
'recursive' => -1,
'conditions' => ['ObjectTemplates.uuid' => $id],
'fields' => ['ObjectTemplates.id', 'ObjectTemplates.uuid'],
'order' => ['ObjectTemplates.version desc']
'recursive' => -1,
'conditions' => ['ObjectTemplates.uuid' => $id],
'fields' => ['ObjectTemplates.id', 'ObjectTemplates.uuid'],
'order' => ['ObjectTemplates.version desc']
]
)->first();
if (empty($temp)) {
@ -191,12 +193,14 @@ class ObjectTemplatesController extends AppController
$conditions['ObjectTemplates.active'] = 1;
}
$this->CRUD->index([
'filters' => $this->filterFields,
'quickFilters' => $this->quickFilterFields,
'quickFilterForMetaField' => ['enabled' => true, 'wildcard_search' => true],
'conditions' => $conditions
]);
$this->CRUD->index(
[
'filters' => $this->filterFields,
'quickFilters' => $this->quickFilterFields,
'quickFilterForMetaField' => ['enabled' => true, 'wildcard_search' => true],
'conditions' => $conditions
]
);
$responsePayload = $this->CRUD->getResponsePayload();
@ -234,14 +238,14 @@ class ObjectTemplatesController extends AppController
}
$logEntry = $this->Log->newEntity(
[
'org' => $this->ACL->getUser()->Organisation->name,
'model' => 'ObjectTemplate',
'model_id' => $id,
'email' => $this->ACL->getUser()->email,
'action' => 'update',
'user_id' => $this->ACL->getUser()->id,
'title' => 'Object template updated',
'change' => $change,
'org' => $this->ACL->getUser()->Organisation->name,
'model' => 'ObjectTemplate',
'model_id' => $id,
'email' => $this->ACL->getUser()->email,
'action' => 'update',
'user_id' => $this->ACL->getUser()->id,
'title' => 'Object template updated',
'change' => $change,
]
);
$this->Log->save($logEntry);
@ -252,14 +256,14 @@ class ObjectTemplatesController extends AppController
foreach ($result['fails'] as $id => $fail) {
$logEntry = $this->Log->newEntity(
[
'org' => $this->ACL->getUser()->Organisation->name,
'model' => 'ObjectTemplate',
'model_id' => $id,
'email' => $this->ACL->getUser()->email,
'action' => 'update',
'user_id' => $this->Auth->user('id'),
'title' => 'Object template failed to update',
'change' => $fail['name'] . ' could not be installed/updated. Error: ' . $fail['fail'],
'org' => $this->ACL->getUser()->Organisation->name,
'model' => 'ObjectTemplate',
'model_id' => $id,
'email' => $this->ACL->getUser()->email,
'action' => 'update',
'user_id' => $this->Auth->user('id'),
'title' => 'Object template failed to update',
'change' => $fail['name'] . ' could not be installed/updated. Error: ' . $fail['fail'],
]
);
$this->Log->save($logEntry);
@ -269,14 +273,14 @@ class ObjectTemplatesController extends AppController
} else {
$logEntry = $this->Log->newEntity(
[
'org' => $this->ACL->getUser()->Organisation->name,
'model' => 'ObjectTemplate',
'model_id' => 0,
'email' => $this->ACL->getUser()->email,
'action' => 'update',
'user_id' => $this->ACL->getUser()->id,
'title' => 'Object template update (nothing to update)',
'change' => 'Executed an update of the Object Template library, but there was nothing to update.',
'org' => $this->ACL->getUser()->Organisation->name,
'model' => 'ObjectTemplate',
'model_id' => 0,
'email' => $this->ACL->getUser()->email,
'action' => 'update',
'user_id' => $this->ACL->getUser()->id,
'title' => 'Object template update (nothing to update)',
'change' => 'Executed an update of the Object Template library, but there was nothing to update.',
]
);
$this->Log->save($logEntry);

View File

@ -28,7 +28,10 @@ class SharingGroupsController extends AppController
public $filterFields = [
'name', 'uuid', 'releasability', 'description', 'active', 'created', 'modified', 'SharingGroups.local', 'roaming', ['name' => 'Organisations.name', 'multiple' => true],
];
public $containFields = [
public $statisticsFields = ['active', 'roaming'];
protected $fields = ['id', 'uuid', 'name', 'description', 'releasability', 'local', 'active', 'roaming'];
protected $contain = [
'SharingGroupOrgs' => [
'Organisations' => ['fields' => ['name', 'id', 'uuid']]
],
@ -42,29 +45,11 @@ class SharingGroupsController extends AppController
]
]
];
public $statisticsFields = ['active', 'roaming'];
public $paginate = [
'limit' => 60,
'maxLimit' => 9999,
'order' => [
'SharingGroup.name' => 'ASC'
],
'fields' => ['id', 'uuid', 'name', 'description', 'releasability', 'local', 'active', 'roaming'],
'contain' => [
'SharingGroupOrgs' => [
'Organisations' => ['fields' => ['name', 'id', 'uuid']]
],
'Organisations' => [
'fields' => ['id', 'name', 'uuid'],
],
'SharingGroupServers' => [
'fields' => ['sharing_group_id', 'all_orgs'],
'Servers' => [
'fields' => ['name', 'id']
]
]
],
];
public function add()
@ -269,7 +254,7 @@ class SharingGroupsController extends AppController
$this->render('add');
}
public function delete($id=false)
public function delete($id = false)
{
$this->request->allowMethod(['get', 'post', 'delete']);
$toggleParams = [
@ -280,9 +265,10 @@ class SharingGroupsController extends AppController
['path' => 'releasability', 'label' => __('Releasability')],
['path' => 'active', 'label' => __('Active'), 'element' => 'boolean',],
['path' => 'roaming', 'label' => __('Roaming'), 'element' => 'boolean',],
['path' => 'org_count', 'label' => __('Org. count'), 'formatter' => function ($field, $row) {
return count($row['SharingGroupOrg']);
}
[
'path' => 'org_count', 'label' => __('Org. count'), 'formatter' => function ($field, $row) {
return count($row['SharingGroupOrg']);
}
],
],
];
@ -320,10 +306,10 @@ class SharingGroupsController extends AppController
]
];
$containFields = $this->containFields;
$containFields = $this->contain;
$validFilterFields = $this->CRUD->getFilterFieldsName($this->filterFields);
if (!$this->__showOrgs()) {
$validFilterFields = array_filter($validFilterFields, fn($filter) => $filter != 'Organisations.name');
$validFilterFields = array_filter($validFilterFields, fn ($filter) => $filter != 'Organisations.name');
unset($containFields['SharingGroupOrgs']);
unset($containFields['SharingGroupServers']);
}
@ -355,6 +341,7 @@ class SharingGroupsController extends AppController
'custom' => $customContextFilters,
],
'contain' => $containFields,
'fields' => $this->fields,
'afterFind' => $afterFindHandler,
'statisticsFields' => $this->statisticsFields,
'wrapResponse' => true,
@ -402,9 +389,10 @@ class SharingGroupsController extends AppController
['path' => 'releasability', 'label' => __('Releasability')],
['path' => 'active', 'label' => __('Active'), 'element' => 'boolean',],
['path' => 'roaming', 'label' => __('Roaming'), 'element' => 'boolean',],
['path' => 'org_count', 'label' => __('Org. count'), 'formatter' => function ($field, $row) {
return count($row['SharingGroupOrg']);
}
[
'path' => 'org_count', 'label' => __('Org. count'), 'formatter' => function ($field, $row) {
return count($row['SharingGroupOrg']);
}
],
],
];
@ -468,7 +456,7 @@ class SharingGroupsController extends AppController
unset($contain['SharingGroupServers']);
}
$afterFindHandler = function(SharingGroup $sg) {
$afterFindHandler = function (SharingGroup $sg) {
if (isset($sg->SharingGroupServer)) {
foreach ($sg->SharingGroupServer as $key => $sgs) {
if ($sgs['server_id'] == 0) {
@ -487,9 +475,10 @@ class SharingGroupsController extends AppController
'conditions' => ['Users.id' => $sg->sync_user_id],
'recursive' => -1,
'fields' => ['Users.id'],
'contain' => ['Organisations' => [
'fields' => ['Organisations.id', 'Organisations.name', 'Organisations.uuid'],
]
'contain' => [
'Organisations' => [
'fields' => ['Organisations.id', 'Organisations.name', 'Organisations.uuid'],
]
]
]
)->first();
@ -508,7 +497,7 @@ class SharingGroupsController extends AppController
return $sg;
};
$conditions= [];
$conditions = [];
$params = [
'contain' => $contain,
'conditions' => $conditions,

View File

@ -13,14 +13,16 @@ class TaxonomiesController extends AppController
{
use LocatorAwareTrait;
protected $conditions = [];
protected $contain = [
'TaxonomyPredicates' => [
'fields' => ['TaxonomyPredicates.id', 'TaxonomyPredicates.taxonomy_id', 'TaxonomyPredicates.value'],
'TaxonomyEntries' => ['fields' => ['TaxonomyEntries.id', 'TaxonomyEntries.taxonomy_predicate_id', 'TaxonomyEntries.value']]
]
];
protected $fields = [];
public $paginate = [
'limit' => 60,
'contain' => [
'TaxonomyPredicates' => [
'fields' => ['TaxonomyPredicates.id', 'TaxonomyPredicates.taxonomy_id', 'TaxonomyPredicates.value'],
'TaxonomyEntries' => ['fields' => ['TaxonomyEntries.id', 'TaxonomyEntries.taxonomy_predicate_id', 'TaxonomyEntries.value']]
]
],
'order' => [
'Taxonomies.id' => 'DESC'
],
@ -31,24 +33,26 @@ class TaxonomiesController extends AppController
$this->paginate['recursive'] = -1;
if (!empty($this->request->getQueryParams()['value'])) {
$this->paginate['conditions']['id'] = $this->__search($this->request->getQueryParams()['value']);
$this->conditions['id'] = $this->__search($this->request->getQueryParams()['value']);
}
if (isset($this->request->getQueryParams()['enabled'])) {
$this->paginate['conditions']['enabled'] = $this->request->getQueryParams()['enabled'] ? 1 : 0;
$this->conditions['enabled'] = $this->request->getQueryParams()['enabled'] ? 1 : 0;
}
$query = $this->Taxonomies->find(
'all',
[
'conditions' => $this->conditions,
'contain' => $this->contain,
'fields' => $this->fields
]
);
if ($this->ParamHandler->isRest()) {
$keepFields = ['conditions', 'contain', 'recursive', 'sort'];
$searchParams = [];
foreach ($keepFields as $field) {
if (!empty($this->paginate[$field])) {
$searchParams[$field] = $this->paginate[$field];
}
}
$taxonomies = $this->Taxonomies->find('all', $searchParams);
$taxonomies = $query;
} else {
$taxonomies = $this->paginate();
$taxonomies = $this->paginate($query);
}
$taxonomies = $this->__tagCount($taxonomies->toArray());

View File

@ -18,7 +18,7 @@ class AuditLogBehavior extends Behavior
private $old;
/** @var AuditLog|null */
private $AuditLogs;
private $AuditLogs = null;
// Hash is faster that in_array
private $skipFields = [
@ -43,28 +43,20 @@ class AuditLogBehavior extends Behavior
{
$fields = $entity->extract($entity->getVisible(), true);
$skipFields = $this->skipFields;
$fieldsToFetch = array_filter(
$fields,
function ($key) use ($skipFields) {
return strpos($key, '_') !== 0 && !isset($skipFields[$key]);
},
ARRAY_FILTER_USE_KEY
$fieldsToFetch = array_keys(
array_filter(
$fields,
function ($key) use ($skipFields) {
return strpos($key, '_') !== 0 && !isset($skipFields[$key]);
},
ARRAY_FILTER_USE_KEY
)
);
// Do not fetch old version when just few fields will be fetched
$fieldToFetch = [];
if (!empty($options['fieldList'])) {
foreach ($options['fieldList'] as $field) {
if (!isset($this->skipFields[$field])) {
$fieldToFetch[] = $field;
}
}
if (empty($fieldToFetch)) {
$this->old = null;
return true;
}
}
$availableColumns = $this->_table->getSchema()->columns();
$fieldsToFetch = array_intersect($fieldsToFetch, $availableColumns);
if ($entity->id) {
$this->old = $this->_table->find()->where(['id' => $entity->id])->contain($fieldToFetch)->first();
$this->old = $this->_table->find()->where(['id' => $entity->id])->select($fieldsToFetch)->first();
} else {
$this->old = null;
}

View File

@ -3,67 +3,89 @@
namespace App\Model\Entity;
use App\Model\Entity\AppModel;
use Cake\ORM\Entity;
use Cake\Core\Configure;
class AuditLog extends AppModel
{
private $compressionEnabled = false;
public const ACTION_ADD = 'add',
ACTION_EDIT = 'edit',
ACTION_SOFT_DELETE = 'soft_delete',
ACTION_DELETE = 'delete',
ACTION_UNDELETE = 'undelete',
ACTION_TAG = 'tag',
ACTION_TAG_LOCAL = 'tag_local',
ACTION_REMOVE_TAG = 'remove_tag',
ACTION_REMOVE_TAG_LOCAL = 'remove_local_tag',
ACTION_GALAXY = 'galaxy',
ACTION_GALAXY_LOCAL = 'galaxy_local',
ACTION_REMOVE_GALAXY = 'remove_galaxy',
ACTION_REMOVE_GALAXY_LOCAL = 'remove_local_galaxy',
ACTION_PUBLISH = 'publish',
ACTION_PUBLISH_SIGHTINGS = 'publish_sightings',
ACTION_LOGIN = 'login',
ACTION_PASSWDCHANGE = 'password_change',
ACTION_LOGOUT = 'logout',
ACTION_LOGIN_FAILED = 'login_failed';
public const REQUEST_TYPE_DEFAULT = 0,
REQUEST_TYPE_API = 1,
REQUEST_TYPE_CLI = 2;
public function __construct(array $properties = [], array $options = [])
{
$this->compressionEnabled = Configure::read('Cerebrate.log_compress') && function_exists('brotli_compress');
parent::__construct($properties, $options);
}
protected function _getTitle(): String
protected function _getTitle(): string
{
return $this->generateUserFriendlyTitle($this);
return $this->generateUserFriendlyTitle();
}
/**
* @param string $change
* @return array|string
* @throws JsonException
*/
private function decodeChange($change)
{
if (substr($change, 0, 4) === self::BROTLI_HEADER) {
if (function_exists('brotli_uncompress')) {
$change = brotli_uncompress(substr($change, 4));
if ($change === false) {
return 'Compressed';
}
} else {
return 'Compressed';
}
}
return json_decode($change, true);
}
/**
* @param array $auditLog
* @return string
*/
public function generateUserFriendlyTitle($auditLog)
public function generateUserFriendlyTitle()
{
if (in_array($auditLog['request_action'], [self::ACTION_TAG, self::ACTION_TAG_LOCAL, self::ACTION_REMOVE_TAG, self::ACTION_REMOVE_TAG_LOCAL], true)) {
$attached = ($auditLog['request_action'] === self::ACTION_TAG || $auditLog['request_action'] === self::ACTION_TAG_LOCAL);
$local = ($auditLog['request_action'] === self::ACTION_TAG_LOCAL || $auditLog['request_action'] === self::ACTION_REMOVE_TAG_LOCAL) ? __('local') : __('global');
if (in_array($this['request_action'], [AuditLog::ACTION_TAG, AuditLog::ACTION_TAG_LOCAL, AuditLog::ACTION_REMOVE_TAG, AuditLog::ACTION_REMOVE_TAG_LOCAL], true)) {
$attached = ($this['request_action'] === AuditLog::ACTION_TAG || $this['request_action'] === AuditLog::ACTION_TAG_LOCAL);
$local = ($this['request_action'] === AuditLog::ACTION_TAG_LOCAL || $this['request_action'] === AuditLog::ACTION_REMOVE_TAG_LOCAL) ? __('local') : __('global');
if ($attached) {
return __('Attached %s tag "%s" to %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
return __('Attached %s tag "%s" to %s #%s', $local, $this['model_title'], strtolower($this['model']), $this['model_id']);
} else {
return __('Detached %s tag "%s" from %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
return __('Detached %s tag "%s" from %s #%s', $local, $this['model_title'], strtolower($this['model']), $this['model_id']);
}
}
$title = "{$auditLog['model']} #{$auditLog['model_id']}";
if (isset($auditLog['model_title']) && $auditLog['model_title']) {
$title .= ": {$auditLog['model_title']}";
if (in_array($this['request_action'], [AuditLog::ACTION_GALAXY, AuditLog::ACTION_GALAXY_LOCAL, AuditLog::ACTION_REMOVE_GALAXY, AuditLog::ACTION_REMOVE_GALAXY_LOCAL], true)) {
$attached = ($this['request_action'] === AuditLog::ACTION_GALAXY || $this['request_action'] === AuditLog::ACTION_GALAXY_LOCAL);
$local = ($this['request_action'] === AuditLog::ACTION_GALAXY_LOCAL || $this['request_action'] === AuditLog::ACTION_REMOVE_GALAXY_LOCAL) ? __('local') : __('global');
if ($attached) {
return __('Attached %s galaxy cluster "%s" to %s #%s', $local, $this['model_title'], strtolower($this['model']), $this['model_id']);
} else {
return __('Detached %s galaxy cluster "%s" from %s #%s', $local, $this['model_title'], strtolower($this['model']), $this['model_id']);
}
}
return $title;
if (in_array($this['model'], ['Attribute', 'Object', 'ShadowAttribute'], true)) {
$modelName = $this['model'] === 'ShadowAttribute' ? 'Proposal' : $this['model'];
$title = __('%s from Event #%s', $modelName, $this['event_id']);
}
if (isset($this['model_title']) && $this['model_title']) {
if (isset($title)) {
$title .= ": {$this['model_title']}";
return $title;
} else {
return $this['model_title'];
}
}
return '';
}
public function rearrangeForAPI(): void

View File

@ -1,18 +1,20 @@
<?php
namespace App\Model\Table;
use App\Model\Entity\AuditLog;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\Datasource\EntityInterface;
use Cake\Event\Event;
use Cake\Event\EventInterface;
use Cake\Auth\DefaultPasswordHasher;
use Cake\Utility\Security;
use Cake\Core\Configure;
use Cake\Routing\Router;
use Cake\Http\Exception\MethodNotAllowedException;
use ArrayObject;
use Cake\Collection\CollectionInterface;
use Cake\Core\Configure;
use Cake\Datasource\ConnectionManager;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\Http\Exception\NotFoundException;
use Cake\Log\Engine\SyslogLog;
use Cake\ORM\Query;
use Cake\Routing\Router;
use Exception;
/**
* @property Event $Event
@ -21,19 +23,15 @@ use ArrayObject;
*/
class AuditLogsTable extends AppTable
{
const BROTLI_HEADER = "\xce\xb2\xcf\x81";
const BROTLI_MIN_LENGTH = 200;
const REQUEST_TYPE_DEFAULT = 0,
REQUEST_TYPE_API = 1,
REQUEST_TYPE_CLI = 2;
/** @var array|null */
private $user = null;
/** @var bool */
private $compressionEnabled;
public const BROTLI_HEADER = "\xce\xb2\xcf\x81";
public const COMPRESS_MIN_LENGTH = 256;
/**
* Null when not defined, false when not enabled
* @var Syslog|null|false
@ -45,6 +43,13 @@ class AuditLogsTable extends AppTable
parent::initialize($config);
$this->addBehavior('Timestamp');
$this->belongsTo('Users');
$this->belongsTo(
'Organisations',
[
'className' => 'Organisations',
'foreignKey' => 'org_id'
]
);
$this->compressionEnabled = Configure::read('Cerebrate.log_new_audit_compress') && function_exists('brotli_compress');
}
@ -61,7 +66,7 @@ class AuditLogsTable extends AppTable
$defaults = [
'user_id' => 0,
'org_id' => 0,
'request_type' => self::REQUEST_TYPE_CLI,
'request_type' => AuditLog::REQUEST_TYPE_CLI,
'authkey_id' => 0
];
foreach (array_keys($defaults) as $field) {
@ -77,7 +82,7 @@ class AuditLogsTable extends AppTable
}
}
if (!isset($data['request_id'] ) && isset($_SERVER['HTTP_X_REQUEST_ID'])) {
if (!isset($data['request_id']) && isset($_SERVER['HTTP_X_REQUEST_ID'])) {
$data['request_id'] = $_SERVER['HTTP_X_REQUEST_ID'];
}
@ -93,8 +98,8 @@ class AuditLogsTable extends AppTable
if (isset($data['changed'])) {
$changed = json_encode($data['changed'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($this->compressionEnabled && strlen($changed) >= self::BROTLI_MIN_LENGTH) {
$changed = self::BROTLI_HEADER . brotli_compress($changed, 4, BROTLI_TEXT);
if ($this->compressionEnabled && strlen($changed) >= AuditLog::BROTLI_MIN_LENGTH) {
$changed = AuditLog::BROTLI_HEADER . brotli_compress($changed, 4, BROTLI_TEXT);
}
$data['changed'] = $changed;
}
@ -112,10 +117,34 @@ class AuditLogsTable extends AppTable
ArrayObject $options
) {
if ($entity->request_type === null) {
$entity->request_type = self::REQUEST_TYPE_CLI;
$entity->request_type = AuditLog::REQUEST_TYPE_CLI;
}
}
public function beforeFind(EventInterface $event, Query $query, ArrayObject $options)
{
$query->formatResults(
function (CollectionInterface $results) {
return $results->map(
function ($row) {
if (isset($row['ip'])) {
$row['ip'] = inet_ntop($row['ip']);
}
if (isset($row['changed']) && $row['changed']) {
$row['changed'] = $this->decodeChange($row['changed']);
}
if (isset($row['request_action']) && isset($row['model']) && isset($row['model_id'])) {
$row['title'] = $row->generateUserFriendlyTitle();
}
return $row;
}
);
},
$query::APPEND
);
}
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
$entity->request_ip = inet_pton($entity->request_ip);
@ -124,14 +153,38 @@ class AuditLogsTable extends AppTable
}
/**
* @param array $data
* @param resource|string $change
* @return array|string
* @throws JsonException
*/
public function decodeChange($change)
{
if (is_resource($change)) {
$change = stream_get_contents($change);
}
if (substr($change, 0, 4) === self::BROTLI_HEADER) {
if (function_exists('brotli_uncompress')) {
$change = brotli_uncompress(substr($change, 4));
if ($change === false) {
return 'Compressed';
}
} else {
return 'Compressed';
}
}
return json_decode($change, true);
}
/**
* @param AuditLog $data
* @return bool
*/
private function logData(EntityInterface $entity)
private function logData(AuditLog $data)
{
if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_audit_notifications_enable')) {
$pubSubTool = $this->getPubSubTool();
$pubSubTool->publish($data, 'audit', 'log');
$pubSubTool->publish($data->toArray(), 'audit', 'log');
}
//$this->publishKafkaNotification('audit', $data, 'log');
@ -140,7 +193,7 @@ class AuditLogsTable extends AppTable
// send off our logs to distributed /dev/null
$logIndex = Configure::read("Plugin.ElasticSearch_log_index");
$elasticSearchClient = $this->getElasticSearchTool();
$elasticSearchClient->pushDocument($logIndex, "log", $data);
$elasticSearchClient->pushDocument($logIndex, "log", $data->toArray());
}
// write to syslogd as well if enabled
@ -155,14 +208,14 @@ class AuditLogsTable extends AppTable
if ($syslogIdent) {
$options['ident'] = $syslogIdent;
}
$this->syslog = new SysLog($options);
$this->syslog = new SyslogLog($options);
} else {
$this->syslog = false;
}
}
if ($this->syslog) {
$entry = $data['request_action'];
$title = $entity->generateUserFriendlyTitle();
$title = $data->generateUserFriendlyTitle();
if ($title) {
$entry .= " -- $title";
}
@ -180,12 +233,12 @@ class AuditLogsTable extends AppTable
return $this->user;
}
$this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT, 'name' => ''];
$this->user = ['id' => 0, /*'org_id' => 0, */ 'authkey_id' => 0, 'request_type' => AuditLog::REQUEST_TYPE_DEFAULT, 'name' => ''];
$isShell = (php_sapi_name() === 'cli');
if ($isShell) {
// do not start session for shell commands and fetch user info from configuration
$this->user['request_type'] = self::REQUEST_TYPE_CLI;
$this->user['request_type'] = AuditLog::REQUEST_TYPE_CLI;
$currentUserId = Configure::read('CurrentUserId');
if (!empty($currentUserId)) {
$this->user['id'] = $currentUserId;
@ -201,7 +254,7 @@ class AuditLogsTable extends AppTable
$this->user['name'] = $authUser['name'];
//$this->user['org_id'] = $authUser['org_id'];
if (isset($authUser['logged_by_authkey']) && $authUser['logged_by_authkey']) {
$this->user['request_type'] = self::REQUEST_TYPE_API;
$this->user['request_type'] = AuditLog::REQUEST_TYPE_API;
}
if (isset($authUser['authkey_id'])) {
$this->user['authkey_id'] = $authUser['authkey_id'];
@ -236,15 +289,19 @@ class AuditLogsTable extends AppTable
$conditions['org_id'] = $org['id'];
}
$dataSource = ConnectionManager::getDataSource('default')->config['datasource'];
$dataSource = ConnectionManager::get('default')->config['datasource'];
if ($dataSource === 'Database/Mysql' || $dataSource === 'Database/MysqlObserver') {
$validDates = $this->find('all', [
'recursive' => -1,
'fields' => ['DISTINCT UNIX_TIMESTAMP(DATE(created)) AS Date', 'count(id) AS count'],
'conditions' => $conditions,
'group' => ['Date'],
'order' => ['Date'],
]);
$validDates = $this->find(
'all',
[
'recursive' => -1,
'fields' => ['DISTINCT UNIX_TIMESTAMP(DATE(created)) AS Date', 'count(id) AS count'],
'conditions' => $conditions,
'group' => ['Date'],
'order' => ['Date'],
]
);
} elseif ($dataSource === 'Database/Postgres') {
if (!empty($conditions['org_id'])) {
$condOrg = sprintf('WHERE org_id = %s', intval($conditions['org_id']));

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class AuditLogsFixture extends TestFixture
{
public $connection = 'test';
public const AUDIT_LOG_1_ID = 1000;
public function init(): void
{
$faker = \Faker\Factory::create();
$this->records = [
[
'id' => self::AUDIT_LOG_1_ID,
'created' => $faker->dateTime()->getTimestamp(),
'user_id' => UsersFixture::USER_ADMIN_ID,
'org_id' => OrganisationsFixture::ORGANISATION_A_ID,
'authkey_id' => AuthKeysFixture::ADMIN_API_ID,
'ip' => null,
'request_type' => 0,
'request_id' => '',
'request_action' => 'login',
'model' => 'Users',
'model_id' => UsersFixture::USER_ADMIN_ID,
'model_title' => '',
'event_id' => null,
'changed' => json_encode([])
]
];
parent::init();
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Api\AuditLogs;
use App\Test\Fixture\AuditLogsFixture;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Helper\ApiTestTrait;
use Cake\TestSuite\TestCase;
class IndexAuditLogsApiTest extends TestCase
{
use ApiTestTrait;
protected const ENDPOINT = '/admin/auditLogs/index';
protected $fixtures = [
'app.Organisations',
'app.Roles',
'app.Users',
'app.AuthKeys',
'app.AuditLogs',
];
public function testIndexAuditLogs(): void
{
$this->skipOpenApiValidations();
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
$this->get(self::ENDPOINT);
$this->assertResponseOk();
$this->assertResponseContains(sprintf('"id": %d', AuditLogsFixture::AUDIT_LOG_1_ID));
}
}

View File

@ -14,7 +14,8 @@ class UsersControllerTest extends TestCase
protected $fixtures = [
'app.Organisations',
'app.Users'
'app.Users',
'app.Roles',
];
public function testLogin(): void