chg: [wip] migrate galaxies controller

pull/9368/head
Luciano Righetti 2023-11-07 16:11:05 +01:00
parent ce159c7bb7
commit 2ca194169d
20 changed files with 3401 additions and 243 deletions

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "libraries/misp-objects"]
path = libraries/misp-objects
url = https://github.com/MISP/misp-objects.git
[submodule "libraries/misp-galaxy"]
path = libraries/misp-galaxy
url = https://github.com/MISP/misp-galaxy

View File

@ -121,7 +121,7 @@ RUN apt-get update \
# Clone MISP repository
RUN git clone --branch ${MISP_TAG_OR_BRANCH} --depth 1 https://github.com/MISP/MISP.git /var/www/html
WORKDIR /var/www/html
RUN git submodule update --init --recursive .;
RUN git submodule update --init --recursive .
USER www-data

View File

@ -75,7 +75,7 @@ init_user() {
ADMIN_USER_ID=$(echo "SELECT id FROM users WHERE EMAIL='${ADMIN_EMAIL}';" | ${MYSQLCMD} | tr -d '\n')
# Insert Admin user API key
if [ -z "$ADMIN_API_KEY" ]; then
if [ ! -z "$ADMIN_API_KEY" ]; then
echo >&2 "Creating admin user API key..."
ADMIN_API_KEY_START=$(echo ${ADMIN_API_KEY} | head -c 4)
ADMIN_API_KEY_END=$(echo ${ADMIN_API_KEY} | tail -c 5)
@ -143,9 +143,7 @@ php-fpm -t
# Finished bootstrapping, create ready flag file
touch "${MISP_READY_STATUS_FLAG}"
if [ -z "$DISABLE_BACKGROUND_WORKERS" ]; then
DISABLE_BACKGROUND_WORKERS=0
fi
[ -z "$DISABLE_BACKGROUND_WORKERS" ] && DISABLE_BACKGROUND_WORKERS=0
if [ "$DISABLE_BACKGROUND_WORKERS" -eq 1 ]; then
echo >&2 "Background workers disabled, skipping..."

1
libraries/misp-galaxy Submodule

@ -0,0 +1 @@
Subproject commit e24fecbd403106395ed806ace0a946f8f6343c0b

View File

@ -24,6 +24,7 @@ use Cake\Core\Configure;
use Cake\Event\EventInterface;
use Cake\Http\Exception\HttpException;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\Utility\Text;
use Exception;
@ -37,6 +38,7 @@ use Exception;
*/
class AppController extends Controller
{
use LocatorAwareTrait;
public $isRest = null;
public $restResponsePayload = null;
@ -122,6 +124,7 @@ class AppController extends Controller
if ($this->ParamHandler->isRest()) {
$this->authApiUser();
$this->Security->setConfig('unlockedActions', [$this->request->getParam('action')]);
$this->response = $this->setResponseType();
}
$this->ACL->setPublicInterfaces();
if (!empty($this->request->getAttribute('identity'))) {
@ -203,12 +206,12 @@ class AppController extends Controller
private function authApiUser(): void
{
if (!empty($_SERVER['HTTP_AUTHORIZATION']) && strlen($_SERVER['HTTP_AUTHORIZATION'])) {
$this->loadModel('AuthKeys');
$AuthKeysTable = $this->fetchTable('AuthKeys');
$logModel = $this->Users->auditLogs();
$authKey = $this->AuthKeys->checkKey($_SERVER['HTTP_AUTHORIZATION']);
$authKey = $AuthKeysTable->checkKey($_SERVER['HTTP_AUTHORIZATION']);
if (!empty($authKey)) {
$this->loadModel('Users');
$user = $this->Users->get($authKey['user_id']);
$UsersTable = $this->fetchTable('Users');
$user = $UsersTable->get($authKey['user_id']);
$logModel->insert(
[
'request_action' => 'login',
@ -400,4 +403,13 @@ class AppController extends Controller
throw new HttpException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.', 405, $e);
}
}
private function setResponseType()
{
foreach ($this->request->getHeader('Accept') as $accept) {
if (strpos($accept, 'application/json') !== false) {
return $this->response->withType('json');
}
}
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use Cake\Http\Exception\NotFoundException;
use Cake\Validation\Validation;
class ToolboxComponent extends Component
{
public function findIdByUuid($model, $id, $allowEmpty = false)
{
if (empty($id) && $allowEmpty) {
return $id;
}
if (Validation::uuid($id)) {
$data = $model->find('first', array(
'conditions' => array($model->alias . '.uuid' => $id),
'recursive' => -1,
'fields' => array($model->alias . '.id')
));
if (empty($data)) {
throw new NotFoundException(__('Invalid %s.', $model->alias));
}
return $data[$model->alias]['id'];
} else {
if (!is_numeric($id)) {
throw new NotFoundException(__('Invalid %s.', $model->alias));
}
$data = $model->find('first', array(
'conditions' => array($model->alias . '.id' => $id),
'recursive' => -1,
'fields' => array($model->alias . '.id')
));
if (empty($data)) {
throw new NotFoundException(__('Invalid %s.', $model->alias));
} else {
return $id;
}
}
}
}

View File

@ -4,41 +4,39 @@ namespace App\Controller;
use App\Controller\AppController;
use App\Lib\Tools\ClusterRelationsGraphTool;
use App\Lib\Tools\FileAccessTool;
use App\Lib\Tools\JsonTool;
use Cake\Core\Configure;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Exception\BadRequestException;
use Cake\Http\Exception\ForbiddenException;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\Validation\Validation;
use App\Lib\Tools\FileAccessTool;
use App\Lib\Tools\JsonTool;
use Exception;
class JobsController extends AppController
class GalaxiesController extends AppController
{
use LocatorAwareTrait;
public $paginate = [
'limit' => 60,
'recursive' => 0,
'order' => [
'Galaxy.id' => 'DESC'
'Galaxies.id' => 'DESC'
]
];
public function index()
{
$aclConditions = array();
$filterData = array(
$filterData = [
'request' => $this->request,
'named_params' => $this->params['named'],
'named_params' => $this->request->getParam('named'),
'paramArray' => ['value', 'enabled'],
'ordered_url_params' => [],
'additional_delimiters' => PHP_EOL
);
];
$exception = false;
$filters = $this->harvestParameters($filterData, $exception);
$searchConditions = [];
@ -46,33 +44,32 @@ class JobsController extends AppController
$filters['value'] = '';
} else {
$searchall = '%' . strtolower($filters['value']) . '%';
$searchConditions = array(
'OR' => array(
'LOWER(Galaxy.name) LIKE' => $searchall,
'LOWER(Galaxy.namespace) LIKE' => $searchall,
'LOWER(Galaxy.description) LIKE' => $searchall,
'LOWER(Galaxy.kill_chain_order) LIKE' => $searchall,
'Galaxy.uuid LIKE' => $searchall
)
);
$searchConditions = [
'OR' => [
'LOWER(Galaxies.name) LIKE' => $searchall,
'LOWER(Galaxies.namespace) LIKE' => $searchall,
'LOWER(Galaxies.description) LIKE' => $searchall,
'LOWER(Galaxies.kill_chain_order) LIKE' => $searchall,
'Galaxies.uuid LIKE' => $searchall
]
];
}
if (isset($filters['enabled'])) {
$searchConditions[]['enabled'] = $filters['enabled'] ? 1 : 0;
}
if ($this->ParamHandler->isRest()) {
$galaxies = $this->Galaxy->find(
$galaxies = $this->Galaxies->find(
'all',
array(
[
'recursive' => -1,
'conditions' => array(
'AND' => array($searchConditions, $aclConditions)
)
)
);
'conditions' => [
'AND' => $searchConditions
]
]
)->disableHydration()->toArray();
return $this->RestResponse->viewData($galaxies, $this->response->getType());
} else {
$this->paginate['conditions']['AND'][] = $searchConditions;
$this->paginate['conditions']['AND'][] = $aclConditions;
$galaxies = $this->paginate();
$this->set('galaxyList', $galaxies);
$this->set('passedArgsArray', $this->passedArgs);
@ -85,18 +82,18 @@ class JobsController extends AppController
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('This action is only accessible via POST requests.');
}
if (!empty($this->params['named']['force'])) {
if (!empty($this->request->getParam('named')['force'])) {
$force = 1;
} else {
$force = 0;
}
$result = $this->Galaxy->update($force);
$result = $this->Galaxies->update($force);
$message = __('Galaxies updated.');
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveSuccessResponse('Galaxy', 'update', false, $this->response->getType(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'index'));
$this->redirect(['controller' => 'galaxies', 'action' => 'index']);
}
}
@ -111,35 +108,41 @@ class JobsController extends AppController
return $this->RestResponse->saveSuccessResponse('Galaxy', 'wipe_default', false, $this->response->getType(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'index'));
$this->redirect(['controller' => 'galaxies', 'action' => 'index']);
}
}
public function view($id)
{
$id = $this->Toolbox->findIdByUuid($this->Galaxy, $id);
$passedArgsArray = array(
$passedArgsArray = [
'context' => isset($this->params['named']['context']) ? $this->params['named']['context'] : 'all'
);
];
if (isset($this->params['named']['searchall']) && strlen($this->params['named']['searchall']) > 0) {
$passedArgsArray['searchall'] = $this->params['named']['searchall'];
}
$this->set('passedArgsArray', $passedArgsArray);
if ($this->ParamHandler->isRest()) {
$galaxy = $this->Galaxy->find('first', array(
'contain' => array('GalaxyCluster' => array('GalaxyElement'/*, 'GalaxyReference'*/)),
'recursive' => -1,
'conditions' => array('Galaxy.id' => $id)
));
$galaxy = $this->Galaxy->find(
'first',
[
'contain' => ['GalaxyCluster' => ['GalaxyElement'/*, 'GalaxyReference'*/]],
'recursive' => -1,
'conditions' => ['Galaxy.id' => $id]
]
);
if (empty($galaxy)) {
throw new NotFoundException('Galaxy not found.');
}
return $this->RestResponse->viewData($galaxy, $this->response->getType());
} else {
$galaxy = $this->Galaxy->find('first', array(
'recursive' => -1,
'conditions' => array('Galaxy.id' => $id)
));
$galaxy = $this->Galaxy->find(
'first',
[
'recursive' => -1,
'conditions' => ['Galaxy.id' => $id]
]
);
if (empty($galaxy)) {
throw new NotFoundException('Galaxy not found.');
}
@ -155,10 +158,13 @@ class JobsController extends AppController
throw new NotFoundException('Invalid galaxy.');
}
$galaxy = $this->Galaxy->find('first', array(
'recursive' => -1,
'conditions' => array('Galaxy.id' => $id)
));
$galaxy = $this->Galaxy->find(
'first',
[
'recursive' => -1,
'conditions' => ['Galaxy.id' => $id]
]
);
if (empty($galaxy)) {
throw new NotFoundException('Invalid galaxy.');
}
@ -169,7 +175,7 @@ class JobsController extends AppController
return $this->RestResponse->saveSuccessResponse('Galaxy', 'delete', false, $this->response->getType(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'index'));
$this->redirect(['controller' => 'galaxies', 'action' => 'index']);
}
} else {
$message = __('Could not delete Galaxy.');
@ -200,10 +206,13 @@ class JobsController extends AppController
throw new NotFoundException('Invalid galaxy.');
}
$galaxy = $this->Galaxy->find('first', array(
'recursive' => -1,
'conditions' => array('Galaxy.id' => $id)
));
$galaxy = $this->Galaxy->find(
'first',
[
'recursive' => -1,
'conditions' => ['Galaxy.id' => $id]
]
);
if (empty($galaxy)) {
throw new NotFoundException('Invalid galaxy.');
}
@ -219,7 +228,7 @@ class JobsController extends AppController
return $this->RestResponse->saveSuccessResponse('Galaxy', 'toggle', false, $this->response->getType(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'index'));
$this->redirect(['controller' => 'galaxies', 'action' => 'index']);
}
} else {
$message = __('Could not enable Galaxy.');
@ -253,7 +262,7 @@ class JobsController extends AppController
return $this->RestResponse->saveSuccessResponse('Galaxy', 'import', false, $this->response->getType(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'index'));
$this->redirect(['controller' => 'galaxies', 'action' => 'index']);
}
} else {
$message = __('Could not import galaxy clusters. %s imported, %s ignored, %s failed. %s', $saveResult['imported'], $saveResult['ignored'], $saveResult['failed'], !empty($saveResult['errors']) ? implode(', ', $saveResult['errors']) : '');
@ -292,29 +301,32 @@ class JobsController extends AppController
public function export($galaxyId)
{
$galaxy = $this->Galaxy->find('first', array(
'recursive' => -1,
'conditions' => array('Galaxy.id' => $galaxyId)
));
$galaxy = $this->Galaxy->find(
'first',
[
'recursive' => -1,
'conditions' => ['Galaxy.id' => $galaxyId]
]
);
if (empty($galaxy) && $galaxyId !== null) {
throw new NotFoundException('Galaxy not found.');
}
if ($this->request->is('post') || $this->request->is('put')) {
$requestData = $this->request->getData()['Galaxy'] ? $this->request->getData()['Galaxy'] : $this->request->getData();
$clusterType = array();
$clusterType = [];
if ($requestData['default']) {
$clusterType[] = true;
}
if ($requestData['custom']) {
$clusterType[] = false;
}
$options = array(
'conditions' => array(
$options = [
'conditions' => [
'GalaxyCluster.galaxy_id' => $galaxyId,
'GalaxyCluster.distribution' => $requestData['distribution'],
'GalaxyCluster.default' => $clusterType
)
);
]
];
$clusters = $this->Galaxy->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), $options, $full = true);
$clusters = $this->Galaxy->GalaxyCluster->unsetFieldsForExport($clusters);
if ($requestData['format'] == 'misp-galaxy') {
@ -352,32 +364,35 @@ class JobsController extends AppController
if (!$local) {
$conditions['local_only'] = false;
}
$galaxies = $this->Galaxy->find('all', array(
'recursive' => -1,
'fields' => array('MAX(Galaxy.version) as latest_version', 'id', 'kill_chain_order', 'name', 'icon', 'description'),
'conditions' => $conditions,
'group' => array('name', 'id', 'kill_chain_order', 'icon', 'description'),
'order' => array('name asc')
));
$items = array(
array(
'name' => __('All clusters'),
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/0' . '/local:' . h($local) . '/eventid:' . h($eventid)
)
$galaxies = $this->Galaxy->find(
'all',
[
'recursive' => -1,
'fields' => ['MAX(Galaxy.version) as latest_version', 'id', 'kill_chain_order', 'name', 'icon', 'description'],
'conditions' => $conditions,
'group' => ['name', 'id', 'kill_chain_order', 'icon', 'description'],
'order' => ['name asc']
]
);
$items = [
[
'name' => __('All clusters'),
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/0/local:' . h($local) . '/eventid:' . h($eventid)
]
];
foreach ($galaxies as $galaxy) {
if (!isset($galaxy['Galaxy']['kill_chain_order']) || $noGalaxyMatrix) {
$items[] = array(
$items[] = [
'name' => h($galaxy['Galaxy']['name']),
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/' . $galaxy['Galaxy']['id'] . '/local:' . h($local) . '/eventid:' . h($eventid),
'template' => array(
'template' => [
'preIcon' => 'fa-' . $galaxy['Galaxy']['icon'],
'name' => $galaxy['Galaxy']['name'],
'infoExtra' => $galaxy['Galaxy']['description'],
)
);
]
];
} else { // should use matrix instead
$param = array(
$param = [
'name' => $galaxy['Galaxy']['name'],
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/' . $galaxy['Galaxy']['id'] . '/local:' . h($local) . '/eventid:' . h($eventid),
'functionName' => sprintf(
@ -390,7 +405,7 @@ class JobsController extends AppController
),
'isPill' => true,
'isMatrix' => true
);
];
if ($galaxy['Galaxy']['id'] == $mitreAttackGalaxyId) {
$param['img'] = $this->baseurl . "/img/mitre-attack-icon.ico";
}
@ -405,67 +420,83 @@ class JobsController extends AppController
public function selectGalaxyNamespace($target_id, $target_type = 'event', $noGalaxyMatrix = false)
{
$this->closeSession();
$namespaces = $this->Galaxy->find('column', array(
'recursive' => -1,
'fields' => array('namespace'),
'conditions' => array('enabled' => 1),
'unique' => true,
'order' => array('namespace asc')
));
$namespaces = $this->Galaxy->find(
'column',
[
'recursive' => -1,
'fields' => ['namespace'],
'conditions' => ['enabled' => 1],
'unique' => true,
'order' => ['namespace asc']
]
);
$local = !empty($this->params['named']['local']) ? '1' : '0';
$eventid = !empty($this->params['named']['eventid']) ? $this->params['named']['eventid'] : '0';
$noGalaxyMatrix = $noGalaxyMatrix ? '1' : '0';
$items = [[
'name' => __('All namespaces'),
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . h($target_id) . '/' . h($target_type) . '/0' . '/' . h($noGalaxyMatrix) . '/local:' . h($local) . '/eventid:' . h($eventid)
]];
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . h($target_id) . '/' . h($target_type) . '/0/' . h($noGalaxyMatrix) . '/local:' . h($local) . '/eventid:' . h($eventid)
]
];
foreach ($namespaces as $namespace) {
$items[] = array(
$items[] = [
'name' => $namespace,
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . h($target_id) . '/' . h($target_type) . '/' . h($namespace) . '/' . h($noGalaxyMatrix) . '/local:' . h($local) . '/eventid:' . h($eventid)
);
];
}
$this->set('items', $items);
$this->set('options', array( // set chosen (select picker) options
'multiple' => 0,
));
$this->set(
'options',
[ // set chosen (select picker) options
'multiple' => 0,
]
);
$this->render('/Elements/generic_picker');
}
public function selectCluster($target_id, $target_type = 'event', $selectGalaxy = false)
{
$user = $this->closeSession();
$conditions = array(
'OR' => array(
$conditions = [
'OR' => [
'GalaxyCluster.published' => true,
'GalaxyCluster.default' => true,
),
'AND' => array(
],
'AND' => [
'GalaxyCluster.deleted' => false,
)
);
]
];
if ($target_type == 'galaxyClusterRelation') {
$conditions['OR']['GalaxyCluster.published'] = [true, false];
}
if ($selectGalaxy) {
$conditions['GalaxyCluster.galaxy_id'] = $selectGalaxy;
}
$data = array_column($this->Galaxy->GalaxyCluster->fetchGalaxyClusters($user, array(
'conditions' => $conditions,
'fields' => array('value', 'description', 'source', 'type', 'id', 'uuid'),
'order' => array('value asc'),
)), 'GalaxyCluster');
$synonyms = $this->Galaxy->GalaxyCluster->GalaxyElement->find('all', array(
'conditions' => array(
'GalaxyElement.key' => 'synonyms',
$conditions
$data = array_column(
$this->Galaxy->GalaxyCluster->fetchGalaxyClusters(
$user,
[
'conditions' => $conditions,
'fields' => ['value', 'description', 'source', 'type', 'id', 'uuid'],
'order' => ['value asc'],
]
),
'fields' => ['GalaxyElement.galaxy_cluster_id', 'GalaxyElement.value'],
'contain' => 'GalaxyCluster',
'recursive' => -1
));
$sortedSynonyms = array();
'GalaxyCluster'
);
$synonyms = $this->Galaxy->GalaxyCluster->GalaxyElement->find(
'all',
[
'conditions' => [
'GalaxyElement.key' => 'synonyms',
$conditions
],
'fields' => ['GalaxyElement.galaxy_cluster_id', 'GalaxyElement.value'],
'contain' => 'GalaxyCluster',
'recursive' => -1
]
);
$sortedSynonyms = [];
foreach ($synonyms as $synonym) {
$sortedSynonyms[$synonym['GalaxyElement']['galaxy_cluster_id']][] = $synonym['GalaxyElement']['value'];
}
@ -478,24 +509,24 @@ class JobsController extends AppController
}
ksort($clusters);
$items = array();
$items = [];
foreach ($clusters as $cluster_data) {
foreach ($cluster_data as $cluster) {
$optionName = $cluster['value'];
if (isset($cluster['synonyms_string'])) {
$optionName .= ' (' . $cluster['synonyms_string'] . ')';
}
$itemParam = array(
$itemParam = [
'name' => $optionName,
'value' => $cluster['id'],
'template' => array(
'template' => [
'name' => $cluster['value'],
'infoExtra' => $cluster['description'],
),
'additionalData' => array(
],
'additionalData' => [
'uuid' => $cluster['uuid']
)
);
]
];
if (isset($cluster['synonyms_string'])) {
$itemParam['template']['infoContextual'] = __('Synonyms: ') . $cluster['synonyms_string'];
}
@ -512,17 +543,20 @@ class JobsController extends AppController
$this->set('mirrorOnEvent', $mirrorOnEvent);
$this->set('items', $items);
$local = !empty($this->params['named']['local']) ? $this->params['named']['local'] : '0';
$this->set('options', array( // set chosen (select picker) options
'functionName' => 'quickSubmitGalaxyForm',
'multiple' => $target_type == 'galaxyClusterRelation' ? 0 : '-1',
'select_options' => array(
'additionalData' => array(
'target_id' => $target_id,
'target_type' => $target_type,
'local' => $local
)
),
));
$this->set(
'options',
[ // set chosen (select picker) options
'functionName' => 'quickSubmitGalaxyForm',
'multiple' => $target_type == 'galaxyClusterRelation' ? 0 : '-1',
'select_options' => [
'additionalData' => [
'target_id' => $target_id,
'target_type' => $target_type,
'local' => $local
]
],
]
);
$this->render('ajax/cluster_choice');
}
@ -547,7 +581,7 @@ class JobsController extends AppController
}
$result = $this->Galaxy->attachCluster($user, $target_type, $target, $cluster_id, $local);
return new Response(array('body' => json_encode(array('saved' => true, 'success' => $result, 'check_publish' => true)), 'status' => 200, 'type' => 'json'));
return new Response(['body' => json_encode(['saved' => true, 'success' => $result, 'check_publish' => true]), 'status' => 200, 'type' => 'json']);
}
public function attachMultipleClusters($target_id, $target_type = 'event')
@ -561,32 +595,32 @@ class JobsController extends AppController
if ($target_id === 'selected') {
$target_id_list = $this->_jsonDecode($this->request->getData()['Galaxy']['attribute_ids']);
} else {
$target_id_list = array($target_id);
$target_id_list = [$target_id];
}
$cluster_ids = $this->request->getData()['Galaxy']['target_ids'];
$mirrorOnEventRequested = $mirrorOnEvent && !empty($this->request->getData()['Galaxy']['mirror_on_event']);
if (strlen($cluster_ids) > 0) {
$cluster_ids = $this->_jsonDecode($cluster_ids);
if (empty($cluster_ids)) {
return new Response(array('body' => json_encode(array('saved' => false, 'errors' => __('No clusters picked.'))), 'status' => 200, 'type' => 'json'));
return new Response(['body' => json_encode(['saved' => false, 'errors' => __('No clusters picked.')]), 'status' => 200, 'type' => 'json']);
}
} else {
return new Response(array('body' => json_encode(array('saved' => false, 'errors' => __('Failed to parse request.'))), 'status' => 200, 'type' => 'json'));
return new Response(['body' => json_encode(['saved' => false, 'errors' => __('Failed to parse request.')]), 'status' => 200, 'type' => 'json']);
}
if ($mirrorOnEventRequested && !empty($target_id_list)) {
$first_attribute_id = $target_id_list[0]; // We consider that all attributes to be tagged are contained in the same event.
$AttributeTable = $this->fetchTable('Attributes');
$attribute = $AttributeTable->fetchAttributeSimple($user, array('conditions' => array('Attribute.id' => $first_attribute_id)));
$attribute = $AttributeTable->fetchAttributeSimple($user, ['conditions' => ['Attribute.id' => $first_attribute_id]]);
if (!empty($attribute['Attribute']['event_id'])) {
$event_id = $attribute['Attribute']['event_id'];
} else {
return new Response(array('body' => json_encode(array('saved' => false, 'errors' => __('Failed to parse request. Could not fetch attribute'))), 'status' => 200, 'type' => 'json'));
return new Response(['body' => json_encode(['saved' => false, 'errors' => __('Failed to parse request. Could not fetch attribute')]), 'status' => 200, 'type' => 'json']);
}
}
$result = "";
if (!is_array($cluster_ids)) { // in case we only want to attach 1
$cluster_ids = array($cluster_ids);
$cluster_ids = [$cluster_ids];
}
foreach ($cluster_ids as $cluster_id) {
foreach ($target_id_list as $target_id) {
@ -610,7 +644,7 @@ class JobsController extends AppController
}
}
if ($this->request->is('ajax')) {
return new Response(array('body' => json_encode(array('saved' => true, 'success' => $result, 'check_publish' => true)), 'status' => 200, 'type' => 'json'));
return new Response(['body' => json_encode(['saved' => true, 'success' => $result, 'check_publish' => true]), 'status' => 200, 'type' => 'json']);
}
$this->Flash->info($result);
@ -628,11 +662,14 @@ class JobsController extends AppController
public function viewGraph($id)
{
$cluster = $this->Galaxy->GalaxyCluster->find('first', array(
'conditions' => array('GalaxyCluster.id' => $id),
'contain' => array('Galaxy'),
'recursive' => -1
));
$cluster = $this->Galaxy->GalaxyCluster->find(
'first',
[
'conditions' => ['GalaxyCluster.id' => $id],
'contain' => ['Galaxy'],
'recursive' => -1
]
);
if (empty($cluster)) {
throw new MethodNotAllowedException('Invalid Galaxy.');
}
@ -648,31 +685,34 @@ class JobsController extends AppController
{
if ($scope === 'event') {
$EventsTable = $this->fetchTable('Events');
$object = $EventsTable->fetchEvent($this->Auth->user(), array('eventid' => $id, 'metadata' => 1));
$object = $EventsTable->fetchEvent($this->Auth->user(), ['eventid' => $id, 'metadata' => 1]);
if (empty($object)) {
throw new NotFoundException('Invalid event.');
}
$object = $object[0];
} elseif ($scope === 'attribute') {
$AttributesTable = $this->fetchTable('Attributes');
$object = $AttributesTable->fetchAttributeSimple($this->Auth->user(), [
'conditions' => ['Attribute.id' => $id],
'contain' => [
'Event',
'Object',
'AttributeTag' => [
'fields' => ['AttributeTag.id', 'AttributeTag.tag_id', 'AttributeTag.relationship_type', 'AttributeTag.local'],
'Tag' => ['fields' => ['Tag.id', 'Tag.name', 'Tag.colour', 'Tag.exportable']],
$object = $AttributesTable->fetchAttributeSimple(
$this->Auth->user(),
[
'conditions' => ['Attribute.id' => $id],
'contain' => [
'Event',
'Object',
'AttributeTag' => [
'fields' => ['AttributeTag.id', 'AttributeTag.tag_id', 'AttributeTag.relationship_type', 'AttributeTag.local'],
'Tag' => ['fields' => ['Tag.id', 'Tag.name', 'Tag.colour', 'Tag.exportable']],
],
],
],
]);
]
);
if (empty($object)) {
throw new NotFoundException('Invalid attribute.');
}
$object = $AttributesTable->Event->massageTags($this->Auth->user(), $object, 'Attribute');
} elseif ($scope === 'tag_collection') {
$TagsCollectionTable = $this->fetchTable('TagCollections');
$object = $TagsCollectionTable->fetchTagCollection($this->Auth->user(), array('conditions' => array('TagCollection.id' => $id)));
$object = $TagsCollectionTable->fetchTagCollection($this->Auth->user(), ['conditions' => ['TagCollection.id' => $id]]);
if (empty($object)) {
throw new NotFoundException('Invalid Tag Collection.');
}
@ -689,7 +729,7 @@ class JobsController extends AppController
public function forkTree($galaxyId, $pruneRootLeaves = true)
{
$clusters = $this->Galaxy->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array('conditions' => array('GalaxyCluster.galaxy_id' => $galaxyId)), $full = true);
$clusters = $this->Galaxy->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), ['conditions' => ['GalaxyCluster.galaxy_id' => $galaxyId]], $full = true);
if (empty($clusters)) {
throw new MethodNotAllowedException('Invalid Galaxy.');
}
@ -697,10 +737,13 @@ class JobsController extends AppController
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->Galaxy->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
}
$galaxy = $this->Galaxy->find('first', array(
'recursive' => -1,
'conditions' => array('Galaxy.id' => $galaxyId)
));
$galaxy = $this->Galaxy->find(
'first',
[
'recursive' => -1,
'conditions' => ['Galaxy.id' => $galaxyId]
]
);
$tree = $this->Galaxy->generateForkTree($clusters, $galaxy, $pruneRootLeaves = $pruneRootLeaves);
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData($tree, $this->response->getType());
@ -712,14 +755,17 @@ class JobsController extends AppController
public function relationsGraph($galaxyId, $includeInbound = 0)
{
$clusters = $this->Galaxy->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array('conditions' => array('GalaxyCluster.galaxy_id' => $galaxyId)), $full = true);
$clusters = $this->Galaxy->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), ['conditions' => ['GalaxyCluster.galaxy_id' => $galaxyId]], $full = true);
if (empty($clusters)) {
throw new MethodNotAllowedException('Invalid Galaxy.');
}
$galaxy = $this->Galaxy->find('first', array(
'recursive' => -1,
'conditions' => array('Galaxy.id' => $galaxyId)
));
$galaxy = $this->Galaxy->find(
'first',
[
'recursive' => -1,
'conditions' => ['Galaxy.id' => $galaxyId]
]
);
$grapher = new ClusterRelationsGraphTool($this->Auth->user(), $this->Galaxy->GalaxyCluster);
$relations = $grapher->getNetwork($clusters, $includeInbound, $includeInbound);
if ($this->ParamHandler->isRest()) {

View File

@ -0,0 +1,934 @@
<?php
namespace App\Controller;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\Validation\Validation;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\NotFoundException;
use Cake\Routing\Router;
use Cake\Core\Configure;
use Cake\Utility\Hash;
use App\Lib\Tools\ColourGradientTool;
use App\Lib\Tools\ClusterRelationsTreeTool;
use Cake\Http\Response;
class GalaxyClustersController extends AppController
{
use LocatorAwareTrait;
public $components = array('Session', 'RequestHandler');
public $paginate = array(
'limit' => 60,
'recursive' => -1,
'order' => array(
'GalaxyCluster.version' => 'DESC',
'GalaxyCluster.value' => 'ASC'
),
'contain' => array(
'Tag' => array(
'fields' => array('Tag.id'),
/*
'EventTag' => array(
'fields' => array('EventTag.event_id')
),
'AttributeTag' => array(
'fields' => array('AttributeTag.event_id', 'AttributeTag.attribute_id')
)
*/
),
'GalaxyElement' => array(
'conditions' => array('GalaxyElement.key' => 'synonyms'),
'fields' => array('value')
),
)
);
public function initialize(): void
{
$this->loadComponent('Toolbox');
}
public function index($galaxyId)
{
$galaxyId = $this->Toolbox->findIdByUuid($this->GalaxyCluster->Galaxy, $galaxyId);
$filterData = array(
'request' => $this->request,
'named_params' => $this->params['named'],
'paramArray' => ['context', 'searchall'],
'ordered_url_params' => [],
'additional_delimiters' => PHP_EOL
);
$exception = false;
$filters = $this->harvestParameters($filterData, $exception);
$aclConditions = $this->GalaxyCluster->buildConditions($this->Auth->user());
$contextConditions = array();
if (empty($filters['context'])) {
$filters['context'] = 'all';
} else {
$contextConditions = array('GalaxyCluster.deleted' => false);
}
if ($filters['context'] == 'default') {
$contextConditions['GalaxyCluster.default'] = true;
} elseif ($filters['context'] == 'custom') {
$contextConditions['GalaxyCluster.default'] = false;
} elseif ($filters['context'] == 'org') {
$contextConditions['GalaxyCluster.org_id'] = $this->Auth->user('org_id');
} elseif ($filters['context'] == 'deleted') {
$contextConditions['GalaxyCluster.deleted'] = true;
}
$this->set('passedArgs', json_encode(array('context' => $filters['context'], 'searchall' => isset($filters['searchall']) ? $filters['searchall'] : '')));
$this->set('context', $filters['context']);
$searchConditions = array();
if (empty($filters['searchall'])) {
$filters['searchall'] = '';
}
if (strlen($filters['searchall']) > 0) {
$searchall = '%' . strtolower($filters['searchall']) . '%';
$synonym_hits = $this->GalaxyCluster->GalaxyElement->find(
'list',
array(
'recursive' => -1,
'conditions' => array(
'LOWER(GalaxyElement.value) LIKE' => $searchall,
'GalaxyElement.key' => 'synonyms'
),
'fields' => array(
'GalaxyElement.galaxy_cluster_id'
)
)
);
$searchConditions = array(
'OR' => array(
'LOWER(GalaxyCluster.value) LIKE' => $searchall,
'LOWER(GalaxyCluster.description) LIKE' => $searchall,
'GalaxyCluster.uuid' => $filters['searchall'],
'GalaxyCluster.id' => array_values($synonym_hits),
),
);
}
$searchConditions['GalaxyCluster.galaxy_id'] = $galaxyId;
if ($this->ParamHandler->isRest()) {
$clusters = $this->GalaxyCluster->find(
'all',
array(
'conditions' => array(
'AND' => array($contextConditions, $searchConditions, $aclConditions)
),
)
);
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'], array('Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation'));
$clusters = $this->paginate();
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
$tagIds = array();
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k]['GalaxyCluster']['relation_counts'] = array(
'out' => count($clusters[$k]['GalaxyClusterRelation']),
'in' => count($clusters[$k]['TargetingClusterRelation']),
);
if (isset($cluster['Tag']['id'])) {
$tagIds[] = $cluster['Tag']['id'];
$clusters[$k]['GalaxyCluster']['tag_id'] = $cluster['Tag']['id'];
}
$clusters[$k]['GalaxyCluster']['synonyms'] = array();
foreach ($cluster['GalaxyElement'] as $element) {
$clusters[$k]['GalaxyCluster']['synonyms'][] = $element['value'];
}
$clusters[$k]['GalaxyCluster']['event_count'] = 0; // real number is assigned later
}
$eventCountsForTags = $this->GalaxyCluster->Tag->EventTag->countForTags($tagIds, $this->Auth->user());
$SightingsTable = $this->fetchTable('Sightings');
$csvForTags = $SightingsTable->tagsSparkline($tagIds, $this->Auth->user(), '0');
foreach ($clusters as $k => $cluster) {
if (isset($cluster['GalaxyCluster']['tag_id'])) {
if (isset($csvForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['csv'] = $csvForTags[$cluster['GalaxyCluster']['tag_id']];
}
if (isset($eventCountsForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['GalaxyCluster']['event_count'] = $eventCountsForTags[$cluster['GalaxyCluster']['tag_id']];
}
}
}
$customClusterCount = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), [
'count' => true,
'conditions' => [
'AND' => [$searchConditions, $aclConditions],
'GalaxyCluster.default' => 0,
]
]);
$EventsTable = $this->fetchTable('Events');
$distributionLevels = $EventsTable->shortDist;
$this->set('distributionLevels', $distributionLevels);
$this->set('list', $clusters);
$this->set('galaxy_id', $galaxyId);
$this->set('custom_cluster_count', $customClusterCount);
if ($this->request->is('ajax')) {
$this->layout = false;
$this->render('ajax/index');
}
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function view($id)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', $throwErrors = true, $full = true);
$tag = $this->GalaxyCluster->Tag->find('first', array(
'conditions' => array(
'LOWER(name)' => strtolower($cluster['GalaxyCluster']['tag_name']),
),
'fields' => array('id'),
'recursive' => -1,
'contain' => array('EventTag.event_id')
));
if (!empty($tag)) {
$cluster['GalaxyCluster']['tag_count'] = $this->GalaxyCluster->Tag->EventTag->countForTag($tag['Tag']['id'], $this->Auth->user());
$cluster['GalaxyCluster']['tag_id'] = $tag['Tag']['id'];
}
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData($cluster, $this->response->getType());
}
$clusters = [$cluster];
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
$cluster = $clusters[0];
$cluster = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $cluster);
$this->set('id', $cluster['GalaxyCluster']['id']);
$this->set('galaxy', ['Galaxy' => $cluster['GalaxyCluster']['Galaxy']]);
$this->set('galaxy_id', $cluster['GalaxyCluster']['galaxy_id']);
$this->set('cluster', $cluster);
$this->set('defaultCluster', $cluster['GalaxyCluster']['default']);
if (!empty($cluster['GalaxyCluster']['extended_from'])) {
$newVersionAvailable = $cluster['GalaxyCluster']['extended_from']['GalaxyCluster']['version'] > $cluster['GalaxyCluster']['extends_version'];
} else {
$newVersionAvailable = false;
}
$this->set('newVersionAvailable', $newVersionAvailable);
$AttributesTable = $this->fetchTable('Attributes');
$distributionLevels = $AttributesTable->distributionLevels;
$this->set('distributionLevels', $distributionLevels);
if (!$cluster['GalaxyCluster']['default'] && !$cluster['GalaxyCluster']['published'] && $cluster['GalaxyCluster']['orgc_id'] == $this->Auth->user()['org_id']) {
$this->Flash->warning(__('This cluster is not published. Users will not be able to use it'));
}
$this->set('title_for_layout', __('Galaxy cluster %s', $cluster['GalaxyCluster']['value']));
}
/**
* @param mixed $galaxyId ID of the galaxy to which the cluster will be added
*/
public function add($galaxyId)
{
if (Validation::uuid($galaxyId)) {
$temp = $this->GalaxyCluster->Galaxy->find('first', array(
'recursive' => -1,
'fields' => array('Galaxy.id', 'Galaxy.uuid'),
'conditions' => array('Galaxy.uuid' => $galaxyId)
));
if ($temp === null) {
throw new NotFoundException(__('Invalid galaxy'));
}
$galaxyId = $temp['Galaxy']['id'];
} elseif (!is_numeric($galaxyId)) {
throw new NotFoundException(__('Invalid galaxy'));
}
$AttributesTable = $this->fetchTable('Attributes');
$distributionLevels = $AttributesTable->distributionLevels;
unset($distributionLevels[5]);
$initialDistribution = 3;
$configuredDistribution = Configure::check('MISP.default_attribute_distribution');
if ($configuredDistribution != null && $configuredDistribution != 'event') {
$initialDistribution = $configuredDistribution;
}
$SharingGroupsTable = $this->fetchTable('SharingGroups');
$sgs = $SharingGroupsTable->fetchAllAuthorised($this->Auth->user(), 'name', 1);
if (isset($this->params['named']['forkUuid'])) {
$forkUuid = $this->params['named']['forkUuid'];
$forkedCluster = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array(
'conditions' => array('GalaxyCluster.uuid' => $forkUuid),
), true);
if (!empty($forkedCluster)) {
$forkedCluster = $forkedCluster[0];
$forkedClusterMeta = $forkedCluster['GalaxyCluster'];
if (empty($this->request->getData())) {
$data = $forkedCluster;
unset($data['GalaxyCluster']['id']);
unset($data['GalaxyCluster']['uuid']);
foreach ($forkedCluster['GalaxyCluster']['GalaxyElement'] as $k => $element) {
unset($forkedCluster['GalaxyCluster']['GalaxyElement'][$k]['id']);
unset($forkedCluster['GalaxyCluster']['GalaxyElement'][$k]['galaxy_cluster_id']);
}
$data['GalaxyCluster']['extends_uuid'] = $forkedCluster['GalaxyCluster']['uuid'];
$data['GalaxyCluster']['extends_version'] = $forkedCluster['GalaxyCluster']['version'];
$data['GalaxyCluster']['elements'] = json_encode($forkedCluster['GalaxyCluster']['GalaxyElement']);
$data['GalaxyCluster']['elementsDict'] = $forkedCluster['GalaxyCluster']['GalaxyElement'];
$data['GalaxyCluster']['authors'] = json_encode($forkedCluster['GalaxyCluster']['authors']);
}
unset($forkedClusterMeta['Galaxy']);
unset($forkedClusterMeta['Org']);
unset($forkedClusterMeta['Orgc']);
$this->set('forkedCluster', $forkedCluster);
$this->set('forkedClusterMeta', $forkedClusterMeta);
} else {
throw new NotFoundException('Forked cluster not found.');
}
}
if ($this->request->is('post') || $this->request->is('put')) {
$cluster = $this->request->getData();
if (!isset($cluster['GalaxyCluster'])) {
$cluster = array('GalaxyCluster' => $cluster);
}
$cluster['GalaxyCluster']['galaxy_id'] = $galaxyId;
$cluster['GalaxyCluster']['published'] = false;
$errors = array();
if (empty($cluster['GalaxyCluster']['elements'])) {
if (empty($cluster['GalaxyCluster']['GalaxyElement'])) {
$cluster['GalaxyCluster']['GalaxyElement'] = array();
}
} else {
$decoded = json_decode($cluster['GalaxyCluster']['elements'], true);
if (is_null($decoded)) {
$this->GalaxyCluster->validationErrors['values'][] = __('Invalid JSON');
$errors[] = sprintf(__('Invalid JSON'));
}
$cluster['GalaxyCluster']['GalaxyElement'] = $decoded;
}
if (!empty($cluster['GalaxyCluster']['extends_uuid'])) {
$extendId = $this->Toolbox->findIdByUuid($this->GalaxyCluster, $cluster['GalaxyCluster']['extends_uuid']);
$forkedCluster = $this->GalaxyCluster->fetchGalaxyClusters(
$this->Auth->user(),
array('conditions' => array('GalaxyCluster.id' => $extendId))
);
if (!empty($forkedCluster)) {
$cluster['GalaxyCluster']['extends_uuid'] = $forkedCluster[0]['GalaxyCluster']['uuid'];
if (empty($cluster['GalaxyCluster']['extends_version'])) {
$cluster['GalaxyCluster']['extends_version'] = $forkedCluster[0]['GalaxyCluster']['version'];
}
} else {
$cluster['GalaxyCluster']['extends_uuid'] = null;
}
} else {
$cluster['GalaxyCluster']['extends_uuid'] = null;
}
$errors = $this->GalaxyCluster->saveCluster($this->Auth->user(), $cluster);
if (!empty($errors)) {
$message = implode(', ', $errors);
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'add', $this->GalaxyCluster->id, $message, $this->response->getType());
} else {
$this->Flash->error($message);
}
} else {
$message = __('Galaxy cluster saved');
if ($this->request->is('ajax')) {
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'add', $this->GalaxyCluster->id, $this->response->getType());
} else if ($this->ParamHandler->isRest()) {
$saved_cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $this->GalaxyCluster->id, 'view', $throwErrors = true, $full = true);
return $this->RestResponse->viewData($saved_cluster);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxy_clusters', 'action' => 'view', $this->GalaxyCluster->id));
}
}
}
$this->set('galaxy', ['Galaxy' => ['id' => $galaxyId]]);
$this->set('galaxy_id', $galaxyId);
$this->set('distributionLevels', $distributionLevels);
$this->set('initialDistribution', $initialDistribution);
$this->set('sharingGroups', $sgs);
$this->set('action', 'add');
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function edit($id)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'edit', $throwErrors = true, $full = true);
if ($cluster['GalaxyCluster']['default']) {
throw new MethodNotAllowedException('Default galaxy cluster cannot be edited');
}
$this->GalaxyCluster->data = array('GalaxyCluster' => $cluster['GalaxyCluster'], 'GalaxyElement' => $cluster['GalaxyCluster']['GalaxyElement']);
$AttributesTable = $this->fetchTable('Attributes');
$distributionLevels = $AttributesTable->distributionLevels;
unset($distributionLevels[5]);
$initialDistribution = 3;
$configuredDistribution = Configure::check('MISP.default_attribute_distribution');
if ($configuredDistribution != null && $configuredDistribution != 'event') {
$initialDistribution = $configuredDistribution;
}
$SharingGroupsTable = $this->fetchTable('SharingGroups');
$sgs = $SharingGroupsTable->fetchAllAuthorised($this->Auth->user(), 'name', 1);
if (!empty($cluster['GalaxyCluster']['extends_uuid'])) {
$forkedCluster = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array(
'conditions' => array('uuid' => $cluster['GalaxyCluster']['extends_uuid']),
), false);
} else {
$forkedCluster = array();
}
if (!empty($forkedCluster)) {
$forkedCluster = $forkedCluster[0];
$this->set('forkUuid', $cluster['GalaxyCluster']['extends_uuid']);
$forkedClusterMeta = $forkedCluster['GalaxyCluster'];
$this->set('forkedCluster', $forkedCluster);
$this->set('forkedClusterMeta', $forkedClusterMeta);
}
if ($this->request->is('post') || $this->request->is('put')) {
$cluster = $this->request->getData();
if (!isset($cluster['GalaxyCluster'])) {
$cluster = array('GalaxyCluster' => $cluster);
}
$errors = array();
if (!isset($cluster['GalaxyCluster']['uuid'])) {
$cluster['GalaxyCluster']['uuid'] = $this->GalaxyCluster->data['GalaxyCluster']['uuid']; // freeze the uuid
}
if (!isset($cluster['GalaxyCluster']['id'])) {
$cluster['GalaxyCluster']['id'] = $id;
}
if (empty($cluster['GalaxyCluster']['elements'])) {
if (empty($cluster['GalaxyCluster']['GalaxyElement'])) {
$cluster['GalaxyCluster']['GalaxyElement'] = array();
}
} else {
$decoded = json_decode($cluster['GalaxyCluster']['elements'], true);
if (is_null($decoded)) {
$this->GalaxyCluster->validationErrors['values'][] = __('Invalid JSON');
$errors[] = sprintf(__('Invalid JSON'));
}
$cluster['GalaxyCluster']['GalaxyElement'] = $decoded;
}
if (empty($cluster['GalaxyCluster']['authors'])) {
$cluster['GalaxyCluster']['authors'] = [];
} else if (is_array($cluster['GalaxyCluster']['authors'])) {
// This is as intended, move on
} else {
$decoded = json_decode($cluster['GalaxyCluster']['authors'], true);
if (is_null($decoded)) { // authors might be comma separated
$decoded = array_map('trim', explode(',', $cluster['GalaxyCluster']['authors']));
}
$cluster['GalaxyCluster']['authors'] = $decoded;
}
$cluster['GalaxyCluster']['authors'] = json_encode($cluster['GalaxyCluster']['authors']);
$cluster['GalaxyCluster']['published'] = false;
if (!empty($errors)) {
$message = implode(', ', $errors);
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'edit', $cluster['GalaxyCluster']['id'], $message, $this->response->getType());
} else {
$this->Flash->error($message);
}
} else {
$errors = $this->GalaxyCluster->editCluster($this->Auth->user(), $cluster);
if (!empty($errors)) {
$message = implode(', ', $errors);
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'edit', $cluster['GalaxyCluster']['id'], $message, $this->response->getType());
} else {
$this->Flash->error($message);
}
} else {
$message = __('Galaxy cluster saved');
if ($this->request->is('ajax')) {
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'edit', $cluster['GalaxyCluster']['id'], $this->response->getType());
} else if ($this->ParamHandler->isRest()) {
$saved_cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', $throwErrors = true, $full = true);
return $this->RestResponse->viewData($saved_cluster);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxy_clusters', 'action' => 'view', $this->GalaxyCluster->id));
}
}
}
} else {
$this->GalaxyCluster->data['GalaxyCluster']['elements'] = json_encode($this->GalaxyCluster->data['GalaxyElement']);
$this->GalaxyCluster->data['GalaxyCluster']['elementsDict'] = $this->GalaxyCluster->data['GalaxyElement'];
$this->GalaxyCluster->data['GalaxyCluster']['authors'] = !empty($this->GalaxyCluster->data['GalaxyCluster']['authors']) ? json_encode($this->GalaxyCluster->data['GalaxyCluster']['authors']) : '';
$data = $this->GalaxyCluster->data;
}
$fieldDesc = array(
'authors' => __('Valid JSON array or comma separated'),
'elements' => __('Valid JSON array composed from Object of the form {key: keyname, value: actualValue}'),
'distribution' => Hash::extract($AttributesTable->distributionDescriptions, '{n}.formdesc'),
);
$this->set('id', $cluster['GalaxyCluster']['id']);
$this->set('cluster', $cluster);
$this->set('fieldDesc', $fieldDesc);
$this->set('distributionLevels', $distributionLevels);
$this->set('initialDistribution', $initialDistribution);
$this->set('sharingGroups', $sgs);
$this->set('galaxy_id', $cluster['GalaxyCluster']['galaxy_id']);
$this->set('clusterId', $id);
$this->set('defaultCluster', $cluster['GalaxyCluster']['default']);
$this->set('action', 'edit');
$this->render('add');
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function publish($id)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'publish', $throwErrors = true, $full = false);
if ($cluster['GalaxyCluster']['published']) {
throw new MethodNotAllowedException(__('You can\'t publish a galaxy cluster that is already published'));
}
if ($cluster['GalaxyCluster']['default']) {
throw new MethodNotAllowedException(__('Default galaxy cluster cannot be published'));
}
if ($this->request->is('post') || $this->request->is('put')) {
$success = $this->GalaxyCluster->publishRouter($this->Auth->user(), $cluster, $passAlong = null);
if (Configure::read('MISP.BackgroundJobs.enabled')) {
$message = __('Publish job queued. Job ID: %s', $success);
$this->Flash->success($message);
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData(array('message' => $message), $this->response->getType());
}
} else {
if (!$success) {
$message = __('Could not publish galaxy cluster');
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'publish', $cluster['GalaxyCluster']['id'], $message, $this->response->getType());
} else {
$this->Flash->error($message);
}
} else {
$message = __('Galaxy cluster published');
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'publish', $cluster['GalaxyCluster']['id'], $this->response->getType());
} else {
$this->Flash->success($message);
}
}
}
$this->redirect(array('controller' => 'galaxy_clusters', 'action' => 'view', $cluster['GalaxyCluster']['id']));
} else {
$this->set('cluster', $cluster);
$this->set('type', 'publish');
$this->render('ajax/publishConfirmationForm');
}
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function unpublish($id)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'publish', $throwErrors = true, $full = false);
if (!$cluster['GalaxyCluster']['published']) {
throw new MethodNotAllowedException(__('You can\'t unpublish a galaxy cluster that is not published'));
}
if ($cluster['GalaxyCluster']['default']) {
throw new MethodNotAllowedException(__('Default galaxy cluster cannot be unpublished'));
}
if ($this->request->is('post') || $this->request->is('put')) {
$success = $this->GalaxyCluster->unpublish($cluster);
if (!$success) {
$message = __('Could not unpublish galaxy cluster');
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'unpublish', $cluster['GalaxyCluster']['id'], $message, $this->response->getType());
} else {
$this->Flash->error($message);
}
} else {
$message = __('Galaxy cluster unpublished');
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'unpublish', $cluster['GalaxyCluster']['id'], $this->response->getType());
} else {
$this->Flash->success($message);
}
}
$this->redirect(array('controller' => 'galaxy_clusters', 'action' => 'view', $cluster['GalaxyCluster']['id']));
} else {
$this->set('cluster', $cluster);
$this->set('type', 'unpublish');
$this->render('ajax/publishConfirmationForm');
}
}
public function detach($target_id, $target_type, $tag_id)
{
if ($this->request->is('ajax') && $this->request->is('get')) {
$this->set('url', Router::url());
return $this->render('/Elements/emptyForm', false);
}
$this->request->allowMethod(['post']);
try {
$this->GalaxyCluster->Galaxy->detachClusterByTagId($this->Auth->user(), $target_id, $target_type, $tag_id);
} catch (NotFoundException $e) {
if (!$this->request->is('ajax')) {
$this->Flash->error($e->getMessage());
} else {
throw $e;
}
}
$message = __('Galaxy successfully detached.');
if ($this->request->is('ajax')) {
return $this->RestResponse->viewData(['saved' => true, 'check_publish' => true, 'success' => $message], 'json');
}
$this->Flash->success($message);
$this->redirect($this->referer());
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function delete($id, $hard = false)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'delete', $throwErrors = true, $full = false);
if ($this->request->is('post')) {
if (!empty($this->request->getData()['hard'])) {
$hard = true;
}
$result = $this->GalaxyCluster->deleteCluster($cluster['GalaxyCluster']['id'], $hard = $hard);
$galaxyId = $cluster['GalaxyCluster']['galaxy_id'];
if ($result) {
$message = __(
'Galaxy cluster successfuly %s deleted%s.',
$hard ? __('hard') : __('soft'),
$hard ? __(' and added to the block list') : ''
);
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $this->response->getType(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
}
} else {
$message = __('Galaxy cluster could not be %s deleted.', $hard ? __('hard') : __('soft'));
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $message, $this->response->getType(), $message);
} else {
$this->Flash->error($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
}
}
} else {
if ($this->request->is('ajax')) {
$this->set('id', $cluster['GalaxyCluster']['id']);
$this->set('cluster', $cluster['GalaxyCluster']);
$this->render('ajax/galaxy_cluster_delete_confirmation');
} else {
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
}
}
}
public function restore($id)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'delete', $throwErrors = true, $full = false);
if ($this->request->is('post')) {
$result = $this->GalaxyCluster->restoreCluster($cluster['GalaxyCluster']['id']);
$galaxyId = $cluster['GalaxyCluster']['galaxy_id'];
if ($result) {
$message = __('Galaxy cluster successfuly restored.');
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'restore', $cluster['GalaxyCluster']['id'], $this->response->getType());
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
}
} else {
$message = __('Galaxy cluster could not be %s restored.');
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'restore', $cluster['GalaxyCluster']['id'], $message, $this->response->getType());
} else {
$this->Flash->error($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
}
}
} else {
throw new MethodNotAllowedException(__('This function can only be reached via POST.'));
}
}
public function viewCyCatRelations($id)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', true, false);
$CyCatRelations = $this->GalaxyCluster->getCyCatRelations($cluster);
$this->set('cluster', $cluster);
$this->set('CyCatRelations', $CyCatRelations);
$this->render('cluster_cycatrelations');
}
public function viewGalaxyMatrix($id)
{
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException('This function can only be reached via AJAX.');
}
$cluster = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array(
'conditions' => array('id' => $id)
), $full = false);
if (empty($cluster)) {
throw new MethodNotAllowedException("Invalid Galaxy Cluster.");
}
$cluster = $cluster[0];
$EventsTable = $this->fetchTable('Events');
$mitreAttackGalaxyId = $this->GalaxyCluster->Galaxy->getMitreAttackGalaxyId();
if ($mitreAttackGalaxyId == 0) { // Mitre Att&ck galaxy not found
return new Response(array('body' => '', 'status' => 200, 'type' => 'text'));
}
$attackPatternTagNames = $this->GalaxyCluster->find('list', array(
'conditions' => array('galaxy_id' => $mitreAttackGalaxyId),
'fields' => array('tag_name')
));
$cluster = $cluster['GalaxyCluster'];
$tag_name = $cluster['tag_name'];
// fetch all event ids having the requested cluster
$eventIds = $EventsTable->EventTag->find('list', array(
'contain' => array('Tag'),
'conditions' => array(
'Tag.name' => $tag_name
),
'fields' => array('event_id'),
'recursive' => -1
));
// fetch all attribute ids having the requested cluster
$attributes = $EventsTable->Attribute->AttributeTag->find('all', array(
'contain' => array('Tag'),
'conditions' => array(
'Tag.name' => $tag_name
),
'fields' => array('attribute_id', 'event_id'),
'recursive' => -1
));
$attributeIds = array();
$additional_event_ids = array();
foreach ($attributes as $attribute) {
$attributeIds[] = $attribute['AttributeTag']['attribute_id'];
$additional_event_ids[$attribute['AttributeTag']['event_id']] = $attribute['AttributeTag']['event_id'];
}
$additional_event_ids = array_keys($additional_event_ids);
$eventIds = array_merge($eventIds, $additional_event_ids);
unset($attributes);
unset($additional_event_ids);
// fetch all related tags belonging to attack pattern
$eventTags = $EventsTable->EventTag->find('all', array(
'contain' => array('Tag'),
'conditions' => array(
'event_id' => $eventIds,
'Tag.name' => $attackPatternTagNames
),
'fields' => array('Tag.name, COUNT(DISTINCT event_id) as tag_count'),
'recursive' => -1,
'group' => array('Tag.name', 'Tag.id')
));
// fetch all related tags belonging to attack pattern or belonging to an event having this cluster
$attributeTags = $EventsTable->Attribute->AttributeTag->find('all', array(
'contain' => array('Tag'),
'conditions' => array(
'OR' => array(
'event_id' => $eventIds,
'attribute_id' => $attributeIds
),
'Tag.name' => $attackPatternTagNames
),
'fields' => array('Tag.name, COUNT(DISTINCT event_id) as tag_count'),
'recursive' => -1,
'group' => array('Tag.name', 'Tag.id')
));
$scores = array();
foreach ($attributeTags as $tag) {
$tagName = $tag['Tag']['name'];
$scores[$tagName] = intval($tag[0]['tag_count']);
}
foreach ($eventTags as $tag) {
$tagName = $tag['Tag']['name'];
if (isset($scores[$tagName])) {
$scores[$tagName] = $scores[$tagName] + intval($tag[0]['tag_count']);
} else {
$scores[$tagName] = intval($tag[0]['tag_count']);
}
}
$maxScore = count($scores) > 0 ? max(array_values($scores)) : 0;
$matrixData = $this->GalaxyCluster->Galaxy->getMatrix($mitreAttackGalaxyId, $scores);
$tabs = $matrixData['tabs'];
$matrixTags = $matrixData['matrixTags'];
$killChainOrders = $matrixData['killChain'];
$instanceUUID = $matrixData['instance-uuid'];
$gradientTool = new ColourGradientTool();
$colours = $gradientTool->createGradientFromValues($scores);
$this->set('target_type', 'attribute');
$this->set('columnOrders', $killChainOrders);
$this->set('tabs', $tabs);
$this->set('scores', $scores);
$this->set('maxScore', $maxScore);
if (!empty($colours)) {
$this->set('colours', $colours['mapping']);
$this->set('interpolation', $colours['interpolation']);
}
$this->set('pickingMode', false);
$this->set('defaultTabName', 'mitre-attack');
$this->set('removeTrailling', 2);
$this->render('cluster_matrix');
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function updateCluster($id)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'edit', $throwErrors = true, $full = true);
if ($cluster['GalaxyCluster']['default']) {
throw new MethodNotAllowedException(__('Default galaxy cluster cannot be updated'));
}
if (empty($cluster['GalaxyCluster']['extends_uuid'])) {
throw new NotFoundException(__('Galaxy cluster is not a fork'));
}
$conditions = array('conditions' => array('GalaxyCluster.uuid' => $cluster['GalaxyCluster']['extends_uuid']));
$parentCluster = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), $conditions, true);
if (empty($parentCluster)) {
throw new NotFoundException('Invalid parent galaxy cluster');
}
$parentCluster = $parentCluster[0];
$forkVersion = $cluster['GalaxyCluster']['extends_version'];
$parentVersion = $parentCluster['GalaxyCluster']['version'];
if ($this->request->is('post') || $this->request->is('put')) {
$elements = array();
if (!empty($this->request->getData()['GalaxyCluster'])) {
foreach ($this->request->getData()['GalaxyCluster'] as $k => $jElement) {
$element = json_decode($jElement, true);
if (!is_null($element) && $element != 0) {
$elements[] = array(
'key' => $element['key'],
'value' => $element['value'],
);
}
}
}
$cluster['GalaxyCluster']['GalaxyElement'] = $elements;
$cluster['GalaxyCluster']['extends_version'] = $parentVersion;
$cluster['GalaxyCluster']['published'] = false;
$errors = $this->GalaxyCluster->editCluster($this->Auth->user(), $cluster, $fieldList = array('extends_version', 'published'), $deleteOldElements = false);
if (!empty($errors)) {
$flashErrorMessage = implode(', ', $errors);
$this->Flash->error($flashErrorMessage);
} else {
$this->Flash->success(__('Cluster updated to the newer version'));
$this->redirect(array('controller' => 'galaxy_clusters', 'action' => 'view', $id));
}
}
$missingElements = array();
foreach ($parentCluster['GalaxyCluster']['GalaxyElement'] as $k => $parentElement) {
$found = false;
foreach ($cluster['GalaxyCluster']['GalaxyElement'] as $k => $clusterElement) {
if (
$parentElement['key'] == $clusterElement['key'] &&
$parentElement['value'] == $clusterElement['value']
) {
$found = true;
break; // element exists in parent
}
}
if (!$found) {
$missingElements[] = $parentElement;
}
}
$this->set('missingElements', $missingElements);
$this->set('parentElements', $parentCluster['GalaxyCluster']['GalaxyElement']);
$this->set('clusterElements', $cluster['GalaxyCluster']['GalaxyElement']);
$this->set('forkVersion', $forkVersion);
$this->set('parentVersion', $parentVersion);
$this->set('newVersionAvailable', $parentVersion > $forkVersion);
$this->set('id', $cluster['GalaxyCluster']['id']);
$this->set('galaxy_id', $cluster['GalaxyCluster']['galaxy_id']);
$this->set('defaultCluster', $cluster['GalaxyCluster']['default']);
$this->set('cluster', $cluster);
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function viewRelations($id, $includeInbound = 1)
{
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException('This function can only be reached via AJAX.');
}
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', true, true);
$existingRelations = $this->GalaxyCluster->GalaxyClusterRelation->getExistingRelationships();
$cluster = $this->GalaxyCluster->attachClusterToRelations($this->Auth->user(), $cluster, $includeInbound);
$grapher = new ClusterRelationsTreeTool();
$grapher->construct($this->Auth->user(), $this->GalaxyCluster);
$tree = $grapher->getTree($cluster);
$this->set('existingRelations', $existingRelations);
$this->set('cluster', $cluster);
$relations = $cluster['GalaxyCluster']['GalaxyClusterRelation'];
if ($includeInbound && !empty($cluster['GalaxyCluster']['TargetingClusterRelation'])) {
foreach ($cluster['GalaxyCluster']['TargetingClusterRelation'] as $targetingCluster) {
$targetingCluster['isInbound'] = true;
$relations[] = $targetingCluster;
}
}
$this->set('passedArgs', json_encode([]));
$this->set('relations', $relations);
$this->set('tree', $tree);
$this->set('includeInbound', $includeInbound);
$AttributesTable = $this->fetchTable('Attributes');
$distributionLevels = $AttributesTable->distributionLevels;
unset($distributionLevels[4]);
unset($distributionLevels[5]);
$this->set('distributionLevels', $distributionLevels);
}
/**
* @param mixed $id ID or UUID of the cluster
*/
public function viewRelationTree($id, $includeInbound = 1)
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', $throwErrors = true, $full = true);
$cluster = $this->GalaxyCluster->attachClusterToRelations($this->Auth->user(), $cluster, $includeInbound);
$grapher = new ClusterRelationsTreeTool();
$grapher->construct($this->Auth->user(), $this->GalaxyCluster);
$tree = $grapher->getTree($cluster);
$this->set('tree', $tree);
$this->set('cluster', $cluster);
$this->set('includeInbound', $includeInbound);
$this->set('testtest', 'testtest');
$this->render('/Elements/GalaxyClusters/view_relation_tree');
}
}

View File

@ -0,0 +1,119 @@
<?php
namespace App\Controller;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Utility\Hash;
use Exception;
class GalaxyElementsController extends AppController
{
public $components = array('Session', 'RequestHandler');
public $paginate = array(
'limit' => 20,
'recursive' => -1,
'order' => array(
'GalaxyElement.key' => 'ASC'
)
);
public function index($clusterId)
{
$user = $this->closeSession();
$filters = $this->IndexFilter->harvestParameters(array('context', 'searchall'));
$aclConditions = $this->GalaxyElement->buildClusterConditions($user, $clusterId);
if (empty($filters['context'])) {
$filters['context'] = 'all';
}
$searchConditions = array();
if (empty($filters['searchall'])) {
$filters['searchall'] = '';
}
if (strlen($filters['searchall']) > 0) {
$searchall = '%' . strtolower($filters['searchall']) . '%';
$searchConditions = array(
'OR' => array(
'LOWER(GalaxyElement.key) LIKE' => $searchall,
'LOWER(GalaxyElement.value) LIKE' => $searchall,
),
);
}
$this->paginate['conditions'] = ['AND' => [$aclConditions, $searchConditions]];
$this->paginate['contain'] = ['GalaxyCluster' => ['fields' => ['id', 'distribution', 'org_id']]];
$elements = $this->paginate();
$this->set('elements', $elements);
$this->set('clusterId', $clusterId);
$this->set('context', $filters['context']);
$this->set('passedArgs', json_encode([
'context' => $filters['context'],
'searchall' => isset($filters['searchall']) ? $filters['searchall'] : ''
]));
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($user, $clusterId, array('edit', 'delete'), false, false);
$canModify = !empty($cluster['authorized']);
$this->set('canModify', $canModify);
if ($filters['context'] === 'JSONView') {
$expanded = $this->GalaxyElement->getExpandedJSONFromElements($elements);
$this->set('JSONElements', $expanded);
}
$this->layout = false;
$this->render('ajax/index');
}
public function delete($elementId)
{
$element = $this->GalaxyElement->find('first', array('conditions' => array('GalaxyElement.id' => $elementId)));
if (empty($element)) {
throw new Exception(__('Element not found'));
}
$this->set('element', $element);
$clusterId = $element['GalaxyElement']['galaxy_cluster_id'];
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit'), true, false);
if ($this->request->is('post')) {
$deleteResult = $this->GalaxyElement->delete($elementId);
if ($deleteResult) {
$this->GalaxyElement->GalaxyCluster->editCluster($this->Auth->user(), $cluster, [], false);
$message = __('Galaxy element %s deleted', $elementId);
$this->Flash->success($message);
} else {
$message = __('Could not delete galaxy element');
$this->Flash->error($message);
}
$this->redirect($this->referer());
} else {
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
} else {
$this->layout = false;
$this->set('elementId', $elementId);
$this->render('ajax/delete');
}
}
}
public function flattenJson($clusterId)
{
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit'), true, false);
if ($this->request->is('post') || $this->request->is('put')) {
$json = $this->_jsonDecode($this->request->getData()['GalaxyElement']['jsonData']);
$flattened = Hash::flatten($json);
$newElements = [];
foreach ($flattened as $k => $v) {
$newElements[] = ['key' => $k, 'value' => $v];
}
$cluster['GalaxyCluster']['GalaxyElement'] = $newElements;
$errors = $this->GalaxyElement->GalaxyCluster->editCluster($this->Auth->user(), $cluster, [], false);
if (empty($errors)) {
return $this->RestResponse->saveSuccessResponse('GalaxyElement', 'flattenJson', $clusterId, false);
} else {
$message = implode(', ', $errors);
return $this->RestResponse->saveFailResponse('GalaxyElement', 'flattenJson', $clusterId, $message, false);
}
}
$this->set('clusterId', $clusterId);
if ($this->request->is('ajax')) {
$this->layout = false;
$this->render('ajax/flattenJson');
}
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Lib\Tools;
class ClusterRelationsTreeTool
{
private $GalaxyCluster = false;
private $user = false;
private $lookup = array();
public function construct($user, $GalaxyCluster)
{
$this->GalaxyCluster = $GalaxyCluster;
$this->user = $user;
return true;
}
/**
* getTree Creates the relation tree with referencing clusters (left) and referenced clusters (right) for the given cluster
*
* @param array $cluster
* @return array
*/
public function getTree(array $cluster)
{
$relationCache = []; // Needed as some default clusters have the same UUID
$treeRight = array(array(
'GalaxyCluster' => $cluster['GalaxyCluster'],
'children' => array()
));
// add relation info between the two clusters
foreach ($cluster['GalaxyCluster']['GalaxyClusterRelation'] as $relation) {
if (empty($relation['GalaxyCluster'])) { // unkown cluster, create placeholder
$relation['GalaxyCluster'] = array(
'uuid' => $relation['referenced_galaxy_cluster_uuid'],
'type' => 'unkown galaxy',
'value' => $relation['referenced_galaxy_cluster_uuid'],
);
}
$tmp = array(
'Relation' => array_diff_key($relation, array_flip(array('GalaxyCluster'))),
'children' => array(
array('GalaxyCluster' => $relation['GalaxyCluster']),
)
);
$treeRight[0]['children'][] = $tmp;
}
$treeLeft = array(array(
'GalaxyCluster' => $cluster['GalaxyCluster'],
'children' => array()
));
if (!empty($cluster['GalaxyCluster']['TargetingClusterRelation'])) {
foreach ($cluster['GalaxyCluster']['TargetingClusterRelation'] as $relation) {
if (isset($relation['GalaxyCluster']) && !isset($relationCache[$relation['GalaxyCluster']['uuid']])) { // not set if cluster is unkown
$relationCache[$relation['GalaxyCluster']['uuid']] = true;
$tmp = array(
'Relation' => array_diff_key($relation, array_flip(array('GalaxyCluster'))),
'children' => array(
array('GalaxyCluster' => $relation['GalaxyCluster']),
)
);
$treeLeft[0]['children'][] = $tmp;
}
}
}
$tree = array(
'right' => $treeRight,
'left' => $treeLeft,
);
return $tree;
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Lib\Tools;
class ColourGradientTool
{
// source: https://graphicdesign.stackexchange.com/a/83867
// $values of the form array(item1: val1, item2: val2, ...)
public function createGradientFromValues($items)
{
$starColor = '#0000FF';
$endColor = '#FF0000';
if (count($items) == 0) {
return array();
}
$vals = array_values($items);
$maxDec = max($vals);
$minDec = min($vals);
$interpolation = $this->interpolateColors($starColor, $endColor, $maxDec + 1, true);
$coloursMapping = array();
foreach ($items as $name => $val) {
$color = $interpolation[$val];
$coloursMapping[$name] = '#' . str_pad(dechex($color[0]), 2, '0', STR_PAD_LEFT) . str_pad(dechex($color[1]), 2, '0', STR_PAD_LEFT) . str_pad(dechex($color[2]), 2, '0', STR_PAD_LEFT);
}
return array('mapping' => $coloursMapping, 'interpolation' => $interpolation);
}
private function hue2rgb($p, $q, $t)
{
if ($t < 0) $t += 1;
if ($t > 1) $t -= 1;
if ($t < 1 / 6) return $p + ($q - $p) * 6 * $t;
if ($t < 1 / 2) return $q;
if ($t < 2 / 3) return $p + ($q - $p) * (2 / 3 - $t) * 6;
return $p;
}
private function hsl2rgb($color)
{
$l = $color[2];
if ($color[1] == 0) {
$l = round($l * 255);
return array($l, $l, $l);
} else {
$s = $color[1];
$q = ($l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s);
$p = 2 * $l - $q;
$r = $this->hue2rgb($p, $q, $color[0] + 1 / 3);
$g = $this->hue2rgb($p, $q, $color[0]);
$b = $this->hue2rgb($p, $q, $color[0] - 1 / 3);
return array(round($r * 255), round($g * 255), round($b * 255));
}
}
private function rgb2hsl($color)
{
$r = $color[0] / 255;
$g = $color[1] / 255;
$b = $color[2] / 255;
$arrRGB = array($r, $g, $b);
$max = max($arrRGB);
$min = min($arrRGB);
$h = ($max - $min) / 2;
$s = $h;
$l = $h;
if ($max == $min) {
$s = 0; // achromatic
$h = 0;
} else {
$d = $max - $min;
$s = ($l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min));
if ($max == $r) {
$h = ($g - $b) / $d + ($g < $b ? 6 : 0);
} elseif ($max == $g) {
$h = ($b - $r) / $d + 2;
} elseif ($max == $b) {
$h = ($r - $g) / $d + 4;
}
$h = $h / 6;
return array($h, $s, $l);
}
}
private function interpolateColor($color1, $color2, $factor, $useHSL = false)
{
if ($useHSL) {
$hsl1 = $this->rgb2hsl($color1);
$hsl2 = $this->rgb2hsl($color2);
for ($i = 0; $i < 3; $i++) {
$hsl1[$i] += $factor * ($hsl2[$i] - $hsl1[$i]);
}
$result = $this->hsl2rgb($hsl1);
} else {
$result = $color1;
for ($i = 0; $i < 3; $i++) {
$result[$i] = round($result[$i] + $factor * ($color2[$i] - $color1[$i]));
}
}
return $result;
}
public function interpolateColors($hexColor1, $hexColor2, $steps, $useHSL = false)
{
$stepFactor = 1 / ($steps - 1);
$interpolatedColorArray = array();
$color1 = sscanf($hexColor1, "#%02x%02x%02x");
$color2 = sscanf($hexColor2, "#%02x%02x%02x");
for ($i = 0; $i < $steps; $i++) {
$interpolatedColorArray[$i] = $this->interpolateColor($color1, $color2, $stepFactor * $i, $useHSL);
}
return $interpolatedColorArray;
}
}

View File

@ -19,6 +19,7 @@ class NamedParamsParserMiddleware implements MiddlewareInterface
public const NAMED_PARAMS = [
'events.index' => ['limit', 'order', 'page', 'sort', 'direction', 'fields', 'search'],
'galaxies.index' => ['limit', 'order', 'page', 'sort', 'direction', 'value'],
];
public function process(
@ -28,7 +29,7 @@ class NamedParamsParserMiddleware implements MiddlewareInterface
$namedConfig = array_merge(Configure::read('NamedParams', []), self::NAMED_PARAMS);
$action = $request->getParam('controller') . '.' . $request->getParam('action');
$action = strtolower($request->getParam('controller') . '.' . $request->getParam('action'));
if (!array_key_exists($action, $namedConfig)) {
return $handler->handle($request);

View File

@ -1,18 +1,13 @@
<?php
namespace App\Model\Behavior;
use App\Model\Entity\AppModel;
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\Utility\Text;
use Cake\Utility\Security;
use \Cake\Http\Session;
use Cake\Core\Configure;
use Cake\ORM\TableRegistry;
use App\Model\Table\AuditLogTable;
class AuditLogBehavior extends Behavior
{
@ -48,9 +43,13 @@ class AuditLogBehavior extends Behavior
{
$fields = $entity->extract($entity->getVisible(), true);
$skipFields = $this->skipFields;
$fieldsToFetch = array_filter($fields, function($key) use ($skipFields) {
$fieldsToFetch = array_filter(
$fields,
function ($key) use ($skipFields) {
return strpos($key, '_') !== 0 && !isset($skipFields[$key]);
}, ARRAY_FILTER_USE_KEY);
},
ARRAY_FILTER_USE_KEY
);
// Do not fetch old version when just few fields will be fetched
$fieldToFetch = [];
if (!empty($options['fieldList'])) {
@ -59,7 +58,7 @@ class AuditLogBehavior extends Behavior
$fieldToFetch[] = $field;
}
}
if (empty($fieldToFetch)) {
if (empty($fieldToFetch)) {
$this->old = null;
return true;
}
@ -81,14 +80,14 @@ class AuditLogBehavior extends Behavior
}
if ($entity->isNew()) {
$action = $entity->getConstant('ACTION_ADD');
$action = AppModel::ACTION_ADD;
} else {
$action = $entity->getConstant('ACTION_EDIT');
$action = AppModel::ACTION_EDIT;
if (isset($entity['deleted'])) {
if ($entity['deleted']) {
$action = $entity->getConstant('ACTION_SOFT_DELETE');
$action = AppModel::ACTION_SOFT_DELETE;
} else if (!$entity['deleted'] && $this->old['deleted']) {
$action = $entity->getConstant('ACTION_UNDELETE');
$action = AppModel::ACTION_UNDELETE;
}
}
}
@ -105,13 +104,15 @@ class AuditLogBehavior extends Behavior
} else if ($this->old[$modelTitleField]) {
$modelTitle = $this->old[$modelTitleField];
}
$this->auditLogs()->insert([
'request_action' => $action,
'model' => $entity->getSource(),
'model_id' => $id,
'model_title' => $modelTitle,
'changed' => $changedFields
]);
$this->auditLogs()->insert(
[
'request_action' => $action,
'model' => $entity->getSource(),
'model_id' => $id,
'model_title' => $modelTitle,
'changed' => $changedFields
]
);
}
public function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)
@ -129,13 +130,15 @@ class AuditLogBehavior extends Behavior
$modelTitle = $entity[$modelTitleField];
}
$this->auditLogs()->insert([
'request_action' => $entity->getConstant('ACTION_DELETE'),
'model' => $entity->getSource(),
'model_id' => $this->old->id,
'model_title' => $modelTitle,
'changed' => $this->changedFields($entity)
]);
$this->auditLogs()->insert(
[
'request_action' => AppModel::ACTION_DELETE,
'model' => $entity->getSource(),
'model_id' => $this->old->id,
'model_title' => $modelTitle,
'changed' => $this->changedFields($entity)
]
);
}
/**
@ -208,6 +211,5 @@ class AuditLogBehavior extends Behavior
public function log()
{
}
}

View File

@ -9,7 +9,7 @@ class AppModel extends Entity
const BROTLI_HEADER = "\xce\xb2\xcf\x81";
const BROTLI_MIN_LENGTH = 200;
const ACTION_ADD = 'add',
public const ACTION_ADD = 'add',
ACTION_EDIT = 'edit',
ACTION_SOFT_DELETE = 'soft_delete',
ACTION_DELETE = 'delete',

View File

@ -0,0 +1,31 @@
<?php
namespace App\Model\Entity;
use App\Model\Entity\AppModel;
class Distribution extends AppModel
{
public const ORGANISATION_ONLY = '0';
public const COMMUNITY_ONLY = '1';
public const CONNECTED_COMMUNITIES = '2';
public const ALL_COMMUNITIES = '3';
public const SHARING_GROUP = '4';
public const DESCRIPTION = [
self::ORGANISATION_ONLY => 'Your organisation only',
self::COMMUNITY_ONLY => 'This community only',
self::CONNECTED_COMMUNITIES => 'Connected communities',
self::ALL_COMMUNITIES => 'All communities',
self::SHARING_GROUP => 'Sharing group',
];
public const ALL = [
self::ORGANISATION_ONLY,
self::COMMUNITY_ONLY,
self::CONNECTED_COMMUNITIES,
self::ALL_COMMUNITIES,
self::SHARING_GROUP,
];
}

View File

@ -0,0 +1,919 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Locator\LocatorAwareTrait;
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\Core\Configure;
use App\Lib\Tools\FileAccessTool;
use Cake\Http\Exception\MethodNotAllowedException;
use InvalidArgumentException;
use RuntimeException;
use Cake\Http\Exception\NotFoundException;
use Exception;
use GlobIterator;
use Cake\Utility\Inflector;
use Cake\ORM\Query;
/**
* @property GalaxyClusters $GalaxyCluster
* @property Galaxy $Galaxy
*/
class GalaxiesTable extends AppTable
{
use LocatorAwareTrait;
public $useTable = 'galaxies';
public $recursive = -1;
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('AuditLog');
$this->addBehavior(
'JsonFields',
[
'fields' => ['kill_chain_order'],
]
);
$this->hasMany(
'GalaxyClusters',
[
'dependent' => true,
'propertyName' => 'GalaxyCluster'
]
);
}
function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
$this->GalaxyClusters->deleteAll(array('GalaxyCluster.galaxy_id' => $entity->id));
}
/**
* @param bool $force
* @return array Galaxy type => Galaxy ID
* @throws Exception
*/
private function __load_galaxies($force = false)
{
$files = new GlobIterator(APP . '../libraries' . DS . 'misp-galaxy' . DS . 'galaxies' . DS . '*.json');
$galaxies = array();
foreach ($files as $file) {
$galaxies[] = FileAccessTool::readJsonFromFile($file->getPathname());
}
$existingGalaxies = $this->find('all', array(
'fields' => array('uuid', 'version', 'id', 'icon'),
'recursive' => -1
))->disableHydration()->toArray();
$existingGalaxies = array_column(array_column($existingGalaxies, 'Galaxy'), null, 'uuid');
foreach ($galaxies as $galaxy) {
if (isset($existingGalaxies[$galaxy['uuid']])) {
if (
$force ||
$existingGalaxies[$galaxy['uuid']]['version'] < $galaxy['version'] ||
(!empty($galaxy['icon']) && ($existingGalaxies[$galaxy['uuid']]['icon'] != $galaxy['icon']))
) {
$galaxy['id'] = $existingGalaxies[$galaxy['uuid']]['id'];
$galaxyEntity = $this->newEntity($galaxy);
$galaxyEntity->kill_chain_order = $galaxy['kill_chain_order'];
$this->save($galaxyEntity);
}
} else {
$galaxyEntity = $this->newEntity($galaxy);
$galaxyEntity->kill_chain_order = $galaxy['kill_chain_order'] ?? [];
$this->save($galaxyEntity);
}
}
return $this->find('list', array('recursive' => -1, 'fields' => array('type', 'id')))->disableHydration()->toArray();
}
private function __update_prepare_template(array $cluster_package, array $galaxies)
{
return [
'source' => isset($cluster_package['source']) ? $cluster_package['source'] : '',
'authors' => json_encode(isset($cluster_package['authors']) ? $cluster_package['authors'] : array()),
'collection_uuid' => isset($cluster_package['uuid']) ? $cluster_package['uuid'] : '',
'galaxy_id' => $galaxies[$cluster_package['type']],
'type' => $cluster_package['type'],
'tag_name' => 'misp-galaxy:' . $cluster_package['type'] . '="'
];
}
/**
* @param array $galaxies
* @param array $cluster_package
* @return array
*/
private function __getPreExistingClusters(array $galaxies, array $cluster_package)
{
$temp = $this->GalaxyClusters->find('all', array(
'conditions' => array(
'GalaxyCluster.galaxy_id' => $galaxies[$cluster_package['type']]
),
'recursive' => -1,
'fields' => array('version', 'id', 'value', 'uuid')
));
return array_column(array_column($temp, 'GalaxyCluster'), null, 'value');
}
private function __deleteOutdated(bool $force, array $cluster_package, array $existingClusters)
{
// Delete all existing outdated clusters
$cluster_ids_to_delete = array();
$cluster_uuids_to_delete = array();
foreach ($cluster_package['values'] as $k => $cluster) {
if (empty($cluster['value'])) {
continue;
}
if (isset($cluster['version'])) {
} elseif (!empty($cluster_package['version'])) {
$cluster_package['values'][$k]['version'] = $cluster_package['version'];
} else {
$cluster_package['values'][$k]['version'] = 0;
}
if (isset($existingClusters[$cluster['value']])) {
$existing = $existingClusters[$cluster['value']];
if ($force || $existing['version'] < $cluster_package['values'][$k]['version']) {
$cluster_ids_to_delete[] = $existing['id'];
$cluster_uuids_to_delete[] = $existing['uuid'];
} else {
unset($cluster_package['values'][$k]);
}
}
}
if (!empty($cluster_ids_to_delete)) {
$this->GalaxyClusters->GalaxyElement->deleteAll(array('GalaxyElement.galaxy_cluster_id' => $cluster_ids_to_delete), false);
$this->GalaxyClusters->GalaxyClusterRelation->deleteAll(array('GalaxyClusterRelation.galaxy_cluster_uuid' => $cluster_uuids_to_delete));
$this->GalaxyClusters->deleteAll(array('GalaxyCluster.id' => $cluster_ids_to_delete), false);
}
return $cluster_package;
}
private function __createClusters($cluster_package, $template)
{
$relations = [];
$elements = [];
$this->GalaxyClusters->bulkEntry = true;
// Start transaction
$this->getDataSource()->begin();
foreach ($cluster_package['values'] as $cluster) {
if (empty($cluster['version'])) {
$cluster['version'] = 1;
}
$template['version'] = $cluster['version'];
$this->GalaxyClusters->create();
$cluster_to_save = $template;
if (isset($cluster['description'])) {
$cluster_to_save['description'] = $cluster['description'];
unset($cluster['description']);
}
$cluster_to_save['value'] = $cluster['value'];
$cluster_to_save['tag_name'] = $cluster_to_save['tag_name'] . $cluster['value'] . '"';
if (!empty($cluster['uuid'])) {
$cluster_to_save['uuid'] = $cluster['uuid'];
}
unset($cluster['value']);
if (empty($cluster_to_save['description'])) {
$cluster_to_save['description'] = '';
}
$cluster_to_save['distribution'] = 3;
$cluster_to_save['default'] = true;
$cluster_to_save['published'] = false;
$cluster_to_save['org_id'] = 0;
$cluster_to_save['orgc_id'] = 0;
// We are already in transaction
$result = $this->GalaxyClusters->save($cluster_to_save, ['atomic' => false, 'validate' => false]);
if (!$result) {
$this->log("Could not save galaxy cluster with UUID {$cluster_to_save['uuid']}.");
continue;
}
$galaxyClusterId = $this->GalaxyClusters->id;
if (isset($cluster['meta'])) {
foreach ($cluster['meta'] as $key => $value) {
if (!is_array($value)) {
$value = [$value];
}
foreach ($value as $v) {
if (is_array($v)) {
$LogTable = $this->fetchTable('Logs');
$logEntry = $LogTable->newEntity([
'org' => 'SYSTEM',
'model' => 'Galaxy',
'model_id' => 0,
'email' => 0,
'action' => 'error',
'title' => sprintf('Found a malformed galaxy cluster (%s) during the update, skipping. Reason: Malformed meta field, embedded array found.', $cluster['uuid']),
'change' => ''
]);
$LogTable->save($logEntry);
} else {
$elements[] = array(
$galaxyClusterId,
$key,
(string)$v
);
}
}
}
}
if (isset($cluster['related'])) {
foreach ($cluster['related'] as $relation) {
$relations[] = [
'galaxy_cluster_id' => $galaxyClusterId,
'galaxy_cluster_uuid' => $cluster['uuid'],
'referenced_galaxy_cluster_uuid' => $relation['dest-uuid'],
'referenced_galaxy_cluster_type' => $relation['type'],
'default' => true,
'distribution' => 3,
'tags' => $relation['tags'] ?? []
];
}
}
}
// Commit transaction
$this->getDataSource()->commit();
return [$elements, $relations];
}
public function update($force = false)
{
$galaxies = $this->__load_galaxies($force);
$files = new GlobIterator(APP . '../libraries' . DS . 'misp-galaxy' . DS . 'clusters' . DS . '*.json');
$force = (bool)$force;
$allRelations = [];
foreach ($files as $file) {
$cluster_package = FileAccessTool::readJsonFromFile($file->getPathname());
if (!isset($galaxies[$cluster_package['type']])) {
continue;
}
$template = $this->__update_prepare_template($cluster_package, $galaxies);
$existingClusters = $this->__getPreExistingClusters($galaxies, $cluster_package);
$cluster_package = $this->__deleteOutdated($force, $cluster_package, $existingClusters);
// create all clusters
list($elements, $relations) = $this->__createClusters($cluster_package, $template);
if (!empty($elements)) {
$db = $this->getDataSource();
$fields = array('galaxy_cluster_id', 'key', 'value');
$db->insertMulti('galaxy_elements', $fields, $elements);
}
$allRelations = array_merge($allRelations, $relations);
}
// Save relation as last part when all clusters are created
if (!empty($allRelations)) {
$this->GalaxyClusters->GalaxyClusterRelation->bulkSaveRelations($allRelations);
}
// Probably unnecessary anymore
$this->GalaxyClusters->generateMissingRelations();
return true;
}
/**
* Capture the Galaxy
*
* @param array $user
* @param array $galaxy The galaxy to be captured
* @return array|false the captured galaxy or false on error
*/
public function captureGalaxy(array $user, array $galaxy)
{
if (empty($galaxy['uuid'])) {
return false;
}
$existingGalaxy = $this->find('all', array(
'recursive' => -1,
'conditions' => array('Galaxy.uuid' => $galaxy['uuid'])
))->first()->toArray();
if (empty($existingGalaxy)) {
if ($user['Role']['perm_site_admin'] || $user['Role']['perm_galaxy_editor']) {
$this->create();
unset($galaxy['id']);
$galaxyEntity = $this->newEntity($galaxy);
$this->save($galaxyEntity);
$existingGalaxy = $this->find('all', array(
'recursive' => -1,
'conditions' => array('Galaxy.id' => $this->id)
))->first()->toArray();
} else {
return false;
}
}
return $existingGalaxy;
}
/**
* Import all clusters into the Galaxy they are shipped with, creating the galaxy if not existant.
*
* This function is meant to be used with manual import or push from remote instance
* @param array $user
* @param array $clusters clusters to import
* @return array The import result with errors if any
*/
public function importGalaxyAndClusters(array $user, array $clusters)
{
$results = array('success' => false, 'imported' => 0, 'ignored' => 0, 'failed' => 0, 'errors' => array());
foreach ($clusters as $cluster) {
if (!empty($cluster['GalaxyCluster']['Galaxy'])) {
$existingGalaxy = $this->captureGalaxy($user, $cluster['GalaxyCluster']['Galaxy']);
} elseif (!empty($cluster['GalaxyCluster']['type'])) {
$existingGalaxy = $this->find('first', array(
'recursive' => -1,
'fields' => array('id'),
'conditions' => array('Galaxy.type' => $cluster['GalaxyCluster']['type']),
));
if (empty($existingGalaxy)) { // We don't have enough info to create the galaxy
$results['failed']++;
$results['errors'][] = __('Galaxy not found');
continue;
}
} else { // We don't have the galaxy nor can create it
$results['failed']++;
$results['errors'][] = __('Galaxy not found');
continue;
}
$cluster['GalaxyCluster']['galaxy_id'] = $existingGalaxy['Galaxy']['id'];
$cluster['GalaxyCluster']['locked'] = true;
$saveResult = $this->GalaxyClusters->captureCluster($user, $cluster, $fromPull = false);
if (empty($saveResult['errors'])) {
$results['imported'] += $saveResult['imported'];
} else {
$results['ignored'] += $saveResult['ignored'];
$results['failed'] += $saveResult['failed'];
$results['errors'] = array_merge($results['errors'], $saveResult['errors']);
}
}
$results['success'] = !($results['failed'] > 0 && $results['imported'] == 0);
return $results;
}
/**
* @param array $user
* @param string $targetType
* @param int $targetId
* @return array
*/
public function fetchTarget(array $user, $targetType, $targetId)
{
$TagsTable = $this->fetchTable('Tags');
if ($targetType === 'event') {
return $TagsTable->EventTag->Event->fetchSimpleEvent($user, $targetId);
} elseif ($targetType === 'attribute') {
return $TagsTable->AttributeTag->Attribute->fetchAttributeSimple($user, array('conditions' => array('Attribute.id' => $targetId)));
} elseif ($targetType === 'tag_collection') {
$target = $TagsTable->TagCollectionTag->TagCollection->fetchTagCollection($user, array('conditions' => array('TagCollection.id' => $targetId)));
if (!empty($target)) {
$target = $target[0];
}
return $target;
} else {
throw new InvalidArgumentException("Invalid target type $targetType");
}
}
/**
* @param array $user
* @param string $targetType Can be 'event', 'attribute' or 'tag_collection'
* @param array $target
* @param int $cluster_id
* @param bool $local
* @return string
* @throws Exception
*/
public function attachCluster(array $user, $targetType, array $target, $cluster_id, $local = false)
{
$connectorModel = Inflector::camelize($targetType) . 'Tag';
$local = $local == 1 || $local === true ? 1 : 0;
$cluster_alias = $this->GalaxyClusters->alias;
$galaxy_alias = $this->alias;
$cluster = $this->GalaxyClusters->fetchGalaxyClusters($user, array(
'first' => true,
'conditions' => array("$cluster_alias.id" => $cluster_id),
'contain' => array('Galaxy'),
'fields' => array('tag_name', 'id', 'value', "$galaxy_alias.local_only"),
));
if (empty($cluster)) {
throw new NotFoundException(__('Invalid Galaxy cluster'));
}
$local_only = $cluster['GalaxyCluster']['Galaxy']['local_only'];
if ($local_only && !$local) {
throw new MethodNotAllowedException(__("This Cluster can only be attached in a local scope"));
}
$TagsTable = $this->fetchTable('Tags');
$tag_id = $TagsTable->captureTag(array('name' => $cluster['GalaxyCluster']['tag_name'], 'colour' => '#0088cc', 'exportable' => 1, 'local_only' => $local_only), $user, true);
if ($targetType === 'event') {
$target_id = $target['Event']['id'];
} elseif ($targetType === 'attribute') {
$target_id = $target['Attribute']['id'];
} else {
$target_id = $target['TagCollection']['id'];
}
$existingTag = $TagsTable->$connectorModel->hasAny(array($targetType . '_id' => $target_id, 'tag_id' => $tag_id));
if ($existingTag) {
return 'Cluster already attached.';
}
$TagsTable->$connectorModel->create();
$toSave = array($targetType . '_id' => $target_id, 'tag_id' => $tag_id, 'local' => $local);
if ($targetType === 'attribute') {
$toSave['event_id'] = $target['Attribute']['event_id'];
}
$result = $TagsTable->$connectorModel->save($toSave);
if ($result) {
if (!$local) {
if ($targetType === 'attribute') {
$TagsTable->AttributeTag->Attribute->touch($target);
} elseif ($targetType === 'event') {
$TagsTable->EventTag->Event->unpublishEvent($target);
}
}
if ($targetType === 'attribute' || $targetType === 'event') {
$TagsTable->EventTag->Event->insertLock($user, $target['Event']['id']);
}
$logTitle = 'Attached ' . $cluster['GalaxyCluster']['value'] . ' (' . $cluster['GalaxyCluster']['id'] . ') to ' . $targetType . ' (' . $target_id . ')';
$this->loadLog()->createLogEntry($user, 'galaxy', ucfirst($targetType), $target_id, $logTitle);
return 'Cluster attached.';
}
return 'Could not attach the cluster';
}
public function detachCluster($user, $target_type, $target_id, $cluster_id)
{
$cluster = $this->GalaxyClusters->find('first', array(
'recursive' => -1,
'conditions' => array('id' => $cluster_id),
'fields' => array('tag_name', 'id', 'value')
));
$TagsTable = $this->fetchTable('Tags');
if ($target_type === 'event') {
$target = $TagsTable->EventTag->Event->fetchEvent($user, array('eventid' => $target_id, 'metadata' => 1));
if (empty($target)) {
throw new NotFoundException(__('Invalid %s.', $target_type));
}
$target = $target[0];
$event = $target;
$org_id = $event['Event']['org_id'];
$orgc_id = $event['Event']['orgc_id'];
} elseif ($target_type === 'attribute') {
$target = $TagsTable->AttributeTag->Attribute->fetchAttributes($user, array('conditions' => array('Attribute.id' => $target_id), 'flatten' => 1));
if (empty($target)) {
throw new NotFoundException(__('Invalid %s.', $target_type));
}
$target = $target[0];
$event_id = $target['Attribute']['event_id'];
$event = $TagsTable->EventTag->Event->fetchEvent($user, array('eventid' => $event_id, 'metadata' => 1));
if (empty($event)) {
throw new NotFoundException(__('Invalid event'));
}
$event = $event[0];
$org_id = $event['Event']['org_id'];
$orgc_id = $event['Event']['org_id'];
} elseif ($target_type === 'tag_collection') {
$target = $TagsTable->TagCollectionTag->TagCollection->fetchTagCollection($user, array('conditions' => array('TagCollection.id' => $target_id)));
if (empty($target)) {
throw new NotFoundException(__('Invalid %s.', $target_type));
}
$target = $target[0];
$org_id = $target['org_id'];
$orgc_id = $org_id;
}
if (!$user['Role']['perm_site_admin'] && !$user['Role']['perm_sync']) {
if (
($target_type === 'tag_collection' && !$user['Role']['perm_tag_editor']) ||
($target_type !== 'tag_collection' && !$user['Role']['perm_tagger']) ||
($user['org_id'] !== $org_id && $user['org_id'] !== $orgc_id)
) {
throw new MethodNotAllowedException('Invalid ' . Inflector::humanize($target_type) . '.');
}
}
$tag_id = $TagsTable->captureTag(array('name' => $cluster['GalaxyCluster']['tag_name'], 'colour' => '#0088cc', 'exportable' => 1), $user);
if ($target_type === 'attribute') {
$existingTargetTag = $TagsTable->AttributeTag->find('first', array(
'conditions' => array('AttributeTag.tag_id' => $tag_id, 'AttributeTag.attribute_id' => $target_id),
'recursive' => -1,
'contain' => array('Tag')
));
} elseif ($target_type === 'event') {
$existingTargetTag = $TagsTable->EventTag->find('first', array(
'conditions' => array('EventTag.tag_id' => $tag_id, 'EventTag.event_id' => $target_id),
'recursive' => -1,
'contain' => array('Tag')
));
} elseif ($target_type === 'tag_collection') {
$existingTargetTag = $TagsTable->TagCollectionTag->TagCollection->find('first', array(
'conditions' => array('tag_id' => $tag_id, 'tag_collection_id' => $target_id),
'recursive' => -1,
'contain' => array('Tag')
));
}
if (empty($existingTargetTag)) {
return 'Cluster not attached.';
}
if ($target_type === 'event') {
$result = $TagsTable->EventTag->delete($existingTargetTag['EventTag']['id']);
} elseif ($target_type === 'attribute') {
$result = $TagsTable->AttributeTag->delete($existingTargetTag['AttributeTag']['id']);
} elseif ($target_type === 'tag_collection') {
$result = $TagsTable->TagCollectionTag->delete($existingTargetTag['TagCollectionTag']['id']);
}
if ($result) {
if ($target_type !== 'tag_collection') {
$TagsTable->EventTag->Event->insertLock($user, $event['Event']['id']);
$TagsTable->EventTag->Event->unpublishEvent($event);
}
$logTitle = 'Detached ' . $cluster['GalaxyCluster']['value'] . ' (' . $cluster['GalaxyCluster']['id'] . ') to ' . $target_type . ' (' . $target_id . ')';
$this->loadLog()->createLogEntry($user, 'galaxy', ucfirst($target_type), $target_id, $logTitle);
return 'Cluster detached';
} else {
return 'Could not detach cluster';
}
}
/**
* @param array $user
* @param int $targetId
* @param string $targetType Can be 'attribute', 'event' or 'tag_collection'
* @param int $tagId
* @return void
* @throws Exception
*/
public function detachClusterByTagId(array $user, $targetId, $targetType, $tagId)
{
if ($targetType === 'attribute') {
$attribute = $this->GalaxyClusters->Tag->EventTag->Event->Attribute->find('first', array(
'recursive' => -1,
'fields' => array('id', 'event_id'),
'conditions' => array('Attribute.id' => $targetId)
));
if (empty($attribute)) {
throw new NotFoundException('Invalid Attribute.');
}
$event_id = $attribute['Attribute']['event_id'];
} elseif ($targetType === 'event') {
$event_id = $targetId;
} elseif ($targetType !== 'tag_collection') {
throw new InvalidArgumentException('Invalid target type');
}
if ($targetType === 'tag_collection') {
$tag_collection = $this->GalaxyClusters->Tag->TagCollectionTag->TagCollection->fetchTagCollection($user, array(
'conditions' => array('TagCollection.id' => $targetId),
'recursive' => -1,
));
if (empty($tag_collection)) {
throw new NotFoundException('Invalid Tag Collection');
}
$tag_collection = $tag_collection[0];
if (!$user['Role']['perm_site_admin']) {
if (!$user['Role']['perm_tag_editor'] || $user['org_id'] !== $tag_collection['TagCollection']['org_id']) {
throw new NotFoundException('Invalid Tag Collection');
}
}
} else {
$event = $this->GalaxyClusters->Tag->EventTag->Event->fetchSimpleEvent($user, $event_id);
if (empty($event)) {
throw new NotFoundException('Invalid Event.');
}
if (!$user['Role']['perm_site_admin'] && !$user['Role']['perm_sync']) {
if (!$user['Role']['perm_tagger'] || ($user['org_id'] !== $event['Event']['org_id'] && $user['org_id'] !== $event['Event']['orgc_id'])) {
throw new NotFoundException('Invalid Event.');
}
}
}
if ($targetType === 'attribute') {
$existingTargetTag = $this->GalaxyClusters->Tag->AttributeTag->find('first', array(
'conditions' => array('AttributeTag.tag_id' => $tagId, 'AttributeTag.attribute_id' => $targetId),
'recursive' => -1,
'contain' => array('Tag')
));
} elseif ($targetType === 'event') {
$existingTargetTag = $this->GalaxyClusters->Tag->EventTag->find('first', array(
'conditions' => array('EventTag.tag_id' => $tagId, 'EventTag.event_id' => $targetId),
'recursive' => -1,
'contain' => array('Tag')
));
} elseif ($targetType === 'tag_collection') {
$existingTargetTag = $this->GalaxyClusters->Tag->TagCollectionTag->find('first', array(
'conditions' => array('TagCollectionTag.tag_id' => $tagId, 'TagCollectionTag.tag_collection_id' => $targetId),
'recursive' => -1,
'contain' => array('Tag')
));
}
if (empty($existingTargetTag)) {
throw new NotFoundException('Galaxy not attached.');
}
$cluster = $this->GalaxyClusters->find('first', array(
'recursive' => -1,
'conditions' => array('GalaxyCluster.tag_name' => $existingTargetTag['Tag']['name'])
));
if (empty($cluster)) {
throw new NotFoundException('Tag is not cluster');
}
if ($targetType === 'event') {
$result = $this->GalaxyClusters->Tag->EventTag->delete($existingTargetTag['EventTag']['id']);
} elseif ($targetType === 'attribute') {
$result = $this->GalaxyClusters->Tag->AttributeTag->delete($existingTargetTag['AttributeTag']['id']);
} elseif ($targetType === 'tag_collection') {
$result = $this->GalaxyClusters->Tag->TagCollectionTag->delete($existingTargetTag['TagCollectionTag']['id']);
}
if (!$result) {
throw new RuntimeException('Could not detach galaxy from event.');
}
if ($targetType !== 'tag_collection') {
$this->GalaxyClusters->Tag->EventTag->Event->unpublishEvent($event);
}
$logTitle = 'Detached ' . $cluster['GalaxyCluster']['value'] . ' (' . $cluster['GalaxyCluster']['id'] . ') from ' . $targetType . ' (' . $targetId . ')';
$this->loadLog()->createLogEntry($user, 'galaxy', ucfirst($targetType), $targetId, $logTitle);
}
public function getMitreAttackGalaxyId($type = "mitre-attack-pattern", $namespace = "mitre-attack")
{
$galaxy = $this->find('first', array(
'recursive' => -1,
'fields' => array('MAX(Galaxy.version) as latest_version', 'id'),
'conditions' => array(
'Galaxy.type' => $type,
'Galaxy.namespace' => $namespace
),
'group' => array('name', 'id')
));
return empty($galaxy) ? 0 : $galaxy['Galaxy']['id'];
}
public function getAllowedMatrixGalaxies()
{
$conditions = array(
'NOT' => array(
'kill_chain_order' => ''
)
);
$galaxies = $this->find('all', array(
'recursive' => -1,
'conditions' => $conditions,
));
return $galaxies;
}
public function getMatrix($galaxy_id, $scores = array())
{
$conditions = array('Galaxy.id' => $galaxy_id);
$contains = array(
'GalaxyCluster' => array('GalaxyElement'),
);
$galaxy = $this->find('first', array(
'recursive' => -1,
'contain' => $contains,
'conditions' => $conditions,
));
$mispUUID = Configure::read('MISP')['uuid'];
if (!isset($galaxy['Galaxy']['kill_chain_order'])) {
throw new MethodNotAllowedException(__("Galaxy cannot be represented as a matrix"));
}
$matrixData = array(
'killChain' => $galaxy['Galaxy']['kill_chain_order'],
'tabs' => array(),
'matrixTags' => array(),
'instance-uuid' => $mispUUID,
'galaxy' => $galaxy['Galaxy']
);
$clusters = $galaxy['GalaxyCluster'];
$cols = array();
foreach ($clusters as $cluster) {
if (empty($cluster['GalaxyElement'])) {
continue;
}
$toBeAdded = false;
$clusterType = $cluster['type'];
$galaxyElements = $cluster['GalaxyElement'];
foreach ($galaxyElements as $element) {
// add cluster if kill_chain is present
if ($element['key'] == 'kill_chain') {
$kc = explode(":", $element['value']);
$galaxyType = $kc[0];
$kc = $kc[1];
$cols[$galaxyType][$kc][] = $cluster;
$toBeAdded = true;
}
if ($element['key'] == 'external_id') {
$cluster['external_id'] = $element['value'];
}
if ($toBeAdded) {
$matrixData['matrixTags'][$cluster['tag_name']] = 1;
}
}
}
$matrixData['tabs'] = $cols;
$this->sortMatrixByScore($matrixData['tabs'], $scores);
// #FIXME temporary fix: retrieve tag name of deprecated mitre galaxies (for the stats)
if ($galaxy['Galaxy']['id'] == $this->getMitreAttackGalaxyId()) {
$names = array('Enterprise Attack - Attack Pattern', 'Pre Attack - Attack Pattern', 'Mobile Attack - Attack Pattern');
$tag_names = array();
$gals = $this->find('all', array(
'recursive' => -1,
'contain' => array('GalaxyCluster.tag_name'),
'conditions' => array('Galaxy.name' => $names)
));
foreach ($gals as $gal => $temp) {
foreach ($temp['GalaxyCluster'] as $value) {
$matrixData['matrixTags'][$value['tag_name']] = 1;
}
}
}
// end FIXME
$matrixData['matrixTags'] = array_keys($matrixData['matrixTags']);
return $matrixData;
}
public function sortMatrixByScore(&$tabs, $scores)
{
foreach (array_keys($tabs) as $i) {
foreach (array_keys($tabs[$i]) as $j) {
// major ordering based on score, minor based on alphabetical
usort($tabs[$i][$j], function ($a, $b) use ($scores) {
if ($a['tag_name'] == $b['tag_name']) {
return 0;
}
if (isset($scores[$a['tag_name']]) && isset($scores[$b['tag_name']])) {
if ($scores[$a['tag_name']] < $scores[$b['tag_name']]) {
$ret = 1;
} elseif ($scores[$a['tag_name']] == $scores[$b['tag_name']]) {
$ret = strcmp($a['value'], $b['value']);
} else {
$ret = -1;
}
} elseif (isset($scores[$a['tag_name']])) {
$ret = -1;
} elseif (isset($scores[$b['tag_name']])) {
$ret = 1;
} else { // none are set
$ret = strcmp($a['value'], $b['value']);
}
return $ret;
});
}
}
}
/**
* generateForkTree
*
* @param mixed $clusters The accessible cluster for the user to be arranged into a fork tree
* @param mixed $galaxy The galaxy for which the fork tree is generated
* @param bool $pruneRootLeaves Should the nonforked clusters be removed from the tree
* @return array The generated fork tree where the children of a node are contained in the `children` key
*/
public function generateForkTree(array $clusters, array $galaxy, $pruneRootLeaves = true)
{
$tree = array();
$lookup = array();
$lastNodeAdded = array();
// generate the lookup table used to immediatly get the correct cluster
foreach ($clusters as $i => $cluster) {
$clusters[$i]['children'] = array();
$lookup[$cluster['GalaxyCluster']['id']] = &$clusters[$i];
}
foreach ($clusters as $i => $cluster) {
if (!empty($cluster['GalaxyCluster']['extended_from'])) {
$parent = $cluster['GalaxyCluster']['extended_from'];
$clusterVersion = $cluster['GalaxyCluster']['extends_version'];
$parentVersion = $lookup[$parent['GalaxyCluster']['id']]['GalaxyCluster']['version'];
if ($clusterVersion == $parentVersion) {
$lookup[$parent['GalaxyCluster']['id']]['children'][] = &$clusters[$i];
} else {
// version differs, insert version node between child and parent
$lastVersionNode = array(
'isVersion' => true,
'isLast' => true,
'version' => $parentVersion,
'parentUuid' => $parent['GalaxyCluster']['uuid'],
'children' => array()
);
$versionNode = array(
'isVersion' => true,
'isLast' => false,
'version' => $clusterVersion,
'parentUuid' => $parent['GalaxyCluster']['uuid'],
'children' => array(&$clusters[$i])
);
$lookup[$parent['GalaxyCluster']['id']]['children'][] = $versionNode;
if (!isset($lastNodeAdded[$parent['GalaxyCluster']['id']])) {
$lookup[$parent['GalaxyCluster']['id']]['children'][] = $lastVersionNode;
$lastNodeAdded[$parent['GalaxyCluster']['id']] = true;
}
}
} else {
$tree[] = &$clusters[$i];
}
}
if ($pruneRootLeaves) {
foreach ($tree as $i => $node) {
if (empty($node['children'])) {
unset($tree[$i]);
}
}
}
$tree = array(array(
'Galaxy' => $galaxy['Galaxy'],
'children' => array_values($tree)
));
return $tree;
}
/**
* convertToMISPGalaxyFormat
*
* @param array $galaxy
* @param array $clusters
* @return array the converted clusters into the misp-galaxy format
*
* Special cases:
* - authors: (since all clusters have their own, takes all of them)
* - version: Takes the higher version number of all clusters
* - uuid: Is actually the collection_uuid. Takes the last one
* - source (since all clusters have their own, takes the last one)
* - category (not saved in MISP nor used)
* - description (not used as the description in the galaxy.json is used instead)
*/
public function convertToMISPGalaxyFormat($galaxy, $clusters)
{
$converted = [];
$converted['name'] = $galaxy['Galaxy']['name'];
$converted['type'] = $galaxy['Galaxy']['type'];
$converted['authors'] = [];
$converted['version'] = 0;
$values = [];
$fieldsToSave = ['description', 'uuid', 'value', 'extends_uuid', 'extends_version'];
foreach ($clusters as $i => $cluster) {
foreach ($fieldsToSave as $field) {
$values[$i][$field] = $cluster['GalaxyCluster'][$field];
}
$converted['uuid'] = $cluster['GalaxyCluster']['collection_uuid'];
$converted['source'] = $cluster['GalaxyCluster']['source'];
if (!empty($cluster['GalaxyCluster']['authors'])) {
foreach ($cluster['GalaxyCluster']['authors'] as $author) {
if (!is_null($author) && $author != 'null') {
$converted['authors'][$author] = $author;
}
}
}
$converted['version'] = $converted['version'] > $cluster['GalaxyCluster']['version'];
foreach ($cluster['GalaxyCluster']['GalaxyElement'] as $element) {
if (isset($values[$i]['meta'][$element['key']])) {
if (is_array($values[$i]['meta'][$element['key']])) {
$values[$i]['meta'][$element['key']][] = $element['value'];
} else {
$values[$i]['meta'][$element['key']] = [$values[$i]['meta'][$element['key']], $element['value']];
}
} else {
$values[$i]['meta'][$element['key']] = $element['value'];
}
}
foreach ($cluster['GalaxyCluster']['GalaxyClusterRelation'] as $j => $relation) {
$values[$i]['related'][$j] = [
'dest-uuid' => $relation['referenced_galaxy_cluster_uuid'],
'type' => $relation['referenced_galaxy_cluster_type'],
];
if (!empty($relation['Tag'])) {
foreach ($relation['Tag'] as $tag) {
$values[$i]['related'][$j]['tags'][] = $tag['name'];
}
}
}
}
$converted['authors'] = array_values($converted['authors']);
$converted['values'] = $values;
return $converted;
}
}

View File

@ -0,0 +1,569 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\Utility\Hash;
use Cake\Validation\Validator;
use App\Model\Entity\Distribution;
/**
* @property GalaxyClusterRelationTag $GalaxyClusterRelationTag
* @property GalaxyCluster $TargetCluster
* @property SharingGroup $SharingGroup
*/
class GalaxyClusterRelations extends AppTable
{
use LocatorAwareTrait;
public $useTable = 'galaxy_cluster_relations';
public $recursive = -1;
public function validationDefault(Validator $validator): Validator
{
$validator
->requirePresence(['referenced_galaxy_cluster_type'])
->add(
'galaxy_cluster_uuid',
'uuid',
[
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
]
)
->add(
'referenced_galaxy_cluster_uuid',
'uuid',
[
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
]
)
->add(
'distribution',
'inList',
[
'rule' => ['inList', Distribution::ALL],
'message' => 'Options: ' . implode(', ', Distribution::DESCRIPTION)
]
);
return $validator;
}
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('AuditLog');
$this->belongsTo(
'SourceCluster',
[
'className' => 'GalaxyCluster',
'foreignKey' => 'galaxy_cluster_id',
]
);
$this->belongsTo(
'TargetCluster',
[
'className' => 'GalaxyCluster',
'foreignKey' => 'galaxy_cluster_id',
]
);
$this->belongsTo(
'SharingGroup',
[
'className' => 'SharingGroup',
'foreignKey' => 'sharing_group_id'
]
);
$this->hasMany(
'GalaxyClusterRelationTag',
[
'dependent' => true,
]
);
}
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $result) {
if (isset($result['TargetCluster']) && key_exists('id', $result['TargetCluster']) && is_null($result['TargetCluster']['id'])) {
$results[$k]['TargetCluster'] = array();
}
if (isset($result['GalaxyClusterRelation']['distribution']) && $result['GalaxyClusterRelation']['distribution'] != 4) {
unset($results[$k]['SharingGroup']);
}
}
return $results;
}
public function buildConditions($user, $clusterConditions = true)
{
$conditions = [];
if (!$user['Role']['perm_site_admin']) {
$alias = $this->alias;
$SharingGroupsTable = $this->fetchTable('SharingGroups');
$sgids = $SharingGroupsTable->authorizedIds($user);
$gcOwnerIds = $this->SourceCluster->cacheGalaxyClusterOwnerIDs($user);
$conditionsRelations['AND']['OR'] = [
"$alias.galaxy_cluster_id" => $gcOwnerIds,
[
'AND' => [
"$alias.distribution >" => 0,
"$alias.distribution <" => 4
],
],
[
'AND' => [
"$alias.sharing_group_id" => $sgids,
"$alias.distribution" => 4
]
]
];
$conditionsSourceCluster = $clusterConditions ? $this->SourceCluster->buildConditions($user) : [];
$conditions = [
'AND' => [
$conditionsRelations,
$conditionsSourceCluster
]
];
}
return $conditions;
}
public function fetchRelations($user, $options, $full = false)
{
$params = array(
'conditions' => $this->buildConditions($user),
'recursive' => -1
);
if (!empty($options['contain'])) {
$params['contain'] = $options['contain'];
} elseif ($full) {
$params['contain'] = array('SharingGroup', 'SourceCluster', 'TargetCluster');
}
if (empty($params['contain'])) {
$params['contain'] = ['SourceCluster'];
}
if (!in_array('SourceCluster', $params['contain'])) {
$params['contain'][] = 'SourceCluster';
}
if (isset($options['fields'])) {
$params['fields'] = $options['fields'];
}
if (isset($options['conditions'])) {
$params['conditions']['AND'][] = $options['conditions'];
}
if (isset($options['group'])) {
$params['group'] = empty($options['group']) ? $options['group'] : false;
}
$relations = $this->find('all', $params);
return $relations;
}
public function getExistingRelationships()
{
$existingRelationships = $this->find('column', array(
'recursive' => -1,
'fields' => array('referenced_galaxy_cluster_type'),
'unique' => true,
))->disableHydration()->toArray();
$ObjectRelationshipsTable = $this->fetchTable('ObjectRelationships');
$objectRelationships = $ObjectRelationshipsTable->find('column', array(
'recursive' => -1,
'fields' => array('name'),
'unique' => true,
))->disableHydration()->toArray();
return array_unique(array_merge($existingRelationships, $objectRelationships));
}
/**
* saveRelations
*
* @see saveRelation
* @return array List of errors if any
*/
public function saveRelations(array $user, array $cluster, array $relations, $captureTag = false, $force = false)
{
$errors = array();
foreach ($relations as $k => $relation) {
$saveResult = $this->saveRelation($user, $cluster, $relation, $captureTag = $captureTag, $force = $force);
$errors = array_merge($errors, $saveResult);
}
return $errors;
}
/**
* saveRelation Respecting ACL saves a relation and set correct fields where applicable.
* Contrary to its capture equivalent, trying to save a relation for a unknown target cluster will fail.
*
* @param array $user
* @param array $cluster The cluster from which the relation is originating
* @param array $relation The relation to save
* @param bool $captureTag Should the tag be captured if it doesn't exists
* @param bool $force Should the relation be edited if it exists
* @return array List errors if any
*/
public function saveRelation(array $user, array $cluster, array $relation, $captureTag = false, $force = false)
{
$errors = array();
if (!isset($relation['GalaxyClusterRelation']) && !empty($relation)) {
$relation = array('GalaxyClusterRelation' => $relation);
}
$authorizationCheck = $this->SourceCluster->fetchIfAuthorized($user, $cluster, array('edit'), $throwErrors = false, $full = false);
if (isset($authorizationCheck['authorized']) && !$authorizationCheck['authorized']) {
$errors[] = $authorizationCheck['error'];
return $errors;
}
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $cluster['uuid'];
$existingRelation = $this->find('first', [
'conditions' => [
'galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid'],
'referenced_galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'],
'referenced_galaxy_cluster_type' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_type'],
],
'fields' => ['id'],
'recursive' => -1,
]);
if (!empty($existingRelation)) {
if (!$force) {
$errors[] = __('Relation already exists');
return $errors;
} else {
$relation['GalaxyClusterRelation']['id'] = $existingRelation['GalaxyClusterRelation']['id'];
}
} else {
$this->create();
}
if (empty($errors)) {
if (!isset($relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'])) {
$errors[] = __('referenced_galaxy_cluster_uuid not provided');
return $errors;
}
if (!$force) {
$targetCluster = $this->TargetCluster->fetchIfAuthorized($user, $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], 'view', $throwErrors = false, $full = false);
if (isset($targetCluster['authorized']) && !$targetCluster['authorized']) { // do not save the relation if referenced cluster is not accessible by the user (or does not exist)
$errors[] = array(__('Invalid referenced galaxy cluster'));
return $errors;
}
}
$relation = $this->syncUUIDsAndIDs($user, $relation);
$relationEntity = $this->newEntity($relation);
$saveSuccess = $this->save($relationEntity);
if ($saveSuccess) {
$savedRelation = $this->find('first', array(
'conditions' => array('id' => $this->id),
'recursive' => -1
));
$tags = array();
if (!empty($relation['GalaxyClusterRelation']['tags'])) {
$tags = $relation['GalaxyClusterRelation']['tags'];
} elseif (!empty($relation['GalaxyClusterRelation']['GalaxyClusterRelationTag'])) {
$tags = $relation['GalaxyClusterRelation']['GalaxyClusterRelationTag'];
$tags = Hash::extract($tags, '{n}.name');
} elseif (!empty($relation['GalaxyClusterRelation']['Tag'])) {
$tags = $relation['GalaxyClusterRelation']['Tag'];
$tags = Hash::extract($tags, '{n}.name');
}
if (!empty($tags)) {
$tagSaveResults = $this->GalaxyClusterRelationTag->attachTags($user, $this->id, $tags, $capture = $captureTag);
if (!$tagSaveResults) {
$errors[] = __('Tags could not be saved for relation (%s)', $this->id);
}
}
} else {
foreach ($this->validationErrors as $validationError) {
$errors[] = $validationError[0];
}
}
}
return $errors;
}
/**
* editRelation Respecting ACL edits a relation and set correct fields where applicable.
* Contrary to its capture equivalent, trying to save a relation for a unknown target cluster will fail.
*
* @param array $user
* @param array $relation The relation to be saved
* @param array $fieldList Only edit the fields provided
* @param bool $captureTag Should the tag be captured if it doesn't exists
* @return array List of errors if any
*/
public function editRelation(array $user, array $relation, array $fieldList = array(), $captureTag = false)
{
$SharingGroupsTable = $this->fetchTable('SharingGroups');
$errors = array();
if (!isset($relation['GalaxyClusterRelation']['galaxy_cluster_id'])) {
$errors[] = __('galaxy_cluster_id not provided');
return $errors;
}
$authorizationCheck = $this->SourceCluster->fetchIfAuthorized($user, $relation['GalaxyClusterRelation']['galaxy_cluster_id'], array('edit'), $throwErrors = false, $full = false);
if (isset($authorizationCheck['authorized']) && !$authorizationCheck['authorized']) {
$errors[] = $authorizationCheck['error'];
return $errors;
}
if (isset($relation['GalaxyClusterRelation']['id'])) {
$existingRelation = $this->find('first', array('conditions' => array('GalaxyClusterRelation.id' => $relation['GalaxyClusterRelation']['id'])));
} else {
$errors[] = __('UUID not provided');
}
if (empty($existingRelation)) {
$errors[] = __('Unkown ID');
} else {
$options = array('conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid']
));
$cluster = $this->SourceCluster->fetchGalaxyClusters($user, $options);
if (empty($cluster)) {
$errors[] = __('Invalid source galaxy cluster');
}
$cluster = $cluster[0];
$relation['GalaxyClusterRelation']['id'] = $existingRelation['GalaxyClusterRelation']['id'];
$relation['GalaxyClusterRelation']['galaxy_cluster_id'] = $cluster['SourceCluster']['id'];
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $cluster['SourceCluster']['uuid'];
if (isset($relation['GalaxyClusterRelation']['distribution']) && $relation['GalaxyClusterRelation']['distribution'] == 4 && !$SharingGroupsTable->checkIfAuthorised($user, $relation['GalaxyClusterRelation']['sharing_group_id'])) {
$errors[] = array(__('Galaxy Cluster Relation could not be saved: The user has to have access to the sharing group in order to be able to edit it.'));
}
if (empty($errors)) {
if (!isset($relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'])) {
$errors[] = __('referenced_galaxy_cluster_uuid not provided');
return $errors;
}
$targetCluster = $this->TargetCluster->fetchIfAuthorized($user, $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], 'view', $throwErrors = false, $full = false);
if (isset($targetCluster['authorized']) && !$targetCluster['authorized']) { // do not save the relation if referenced cluster is not accessible by the user (or does not exist)
$errors[] = array(__('Invalid referenced galaxy cluster'));
return $errors;
}
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = $targetCluster['TargetCluster']['id'];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'] = $targetCluster['TargetCluster']['uuid'];
$relation['GalaxyClusterRelation']['default'] = false;
if (empty($fieldList)) {
$fieldList = array('galaxy_cluster_id', 'galaxy_cluster_uuid', 'referenced_galaxy_cluster_id', 'referenced_galaxy_cluster_uuid', 'referenced_galaxy_cluster_type', 'distribution', 'sharing_group_id', 'default');
}
$relationEntity = $this->newEntity($relation);
$saveSuccess = $this->save($relationEntity, array('fieldList' => $fieldList));
if (!$saveSuccess) {
foreach ($this->validationErrors as $validationError) {
$errors[] = $validationError[0];
}
} else {
$this->GalaxyClusterRelationTag->deleteAll(array('GalaxyClusterRelationTag.galaxy_cluster_relation_id' => $relation['GalaxyClusterRelation']['id']));
$this->GalaxyClusterRelationTag->attachTags($user, $relation['GalaxyClusterRelation']['id'], $relation['GalaxyClusterRelation']['tags'], $capture = $captureTag);
}
}
}
return $errors;
}
public function bulkSaveRelations(array $relations)
{
// Fetch existing tags Name => ID mapping
$tagNameToId = $this->GalaxyClusterRelationTag->Tag->find('list', [
'fields' => ['Tag.name', 'Tag.id'],
'callbacks' => false,
]);
// Fetch all cluster UUID => ID mapping
$galaxyClusterUuidToId = $this->TargetCluster->find('list', [
'fields' => ['uuid', 'id'],
'callbacks' => false,
]);
$lookupSavedIds = [];
$relationTagsToSave = [];
foreach ($relations as &$relation) {
if (isset($galaxyClusterUuidToId[$relation['referenced_galaxy_cluster_uuid']])) {
$relation['referenced_galaxy_cluster_id'] = $galaxyClusterUuidToId[$relation['referenced_galaxy_cluster_uuid']];
} else {
$relation['referenced_galaxy_cluster_id'] = 0; // referenced cluster doesn't exists
}
if (!empty($relation['tags'])) {
$lookupSavedIds[$relation['galaxy_cluster_id']] = true;
foreach ($relation['tags'] as $tag) {
if (!isset($tagNameToId[$tag])) {
$tagNameToId[$tag] = $this->GalaxyClusterRelationTag->Tag->quickAdd($tag);
}
$relationTagsToSave[$relation['galaxy_cluster_uuid']][$relation['referenced_galaxy_cluster_uuid']][] = $tagNameToId[$tag];
}
}
}
unset($galaxyClusterUuidToId, $tagNameToId);
$this->saveMany($relations, ['validate' => false]); // Some clusters uses invalid UUID :/
// Insert tags
$savedRelations = $this->find('all', [
'recursive' => -1,
'conditions' => ['galaxy_cluster_id' => array_keys($lookupSavedIds)],
'fields' => ['id', 'galaxy_cluster_uuid', 'referenced_galaxy_cluster_uuid']
]);
$relation_tags = [];
foreach ($savedRelations as $savedRelation) {
$uuid1 = $savedRelation['GalaxyClusterRelation']['galaxy_cluster_uuid'];
$uuid2 = $savedRelation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'];
if (isset($relationTagsToSave[$uuid1][$uuid2])) {
foreach ($relationTagsToSave[$uuid1][$uuid2] as $tagId) {
$relation_tags[] = [$savedRelation['GalaxyClusterRelation']['id'], $tagId];
}
}
}
if (!empty($relation_tags)) {
$db = $this->getDataSource();
$fields = array('galaxy_cluster_relation_id', 'tag_id');
$db->insertMulti('galaxy_cluster_relation_tags', $fields, $relation_tags);
}
}
/**
* Gets a relation then save it.
*
* @param array $user
* @param array $cluster The cluster for which the relation is being saved
* @param array $relation The relation to be saved
* @param bool $fromPull If the current capture is performed from a PULL sync. If set, it allows edition of existing relations
* @return array The capture success results
*/
public function captureRelations(array $user, array $cluster, array $relations, $fromPull = false)
{
$results = array('success' => false, 'imported' => 0, 'failed' => 0);
$LogsTable = $this->fetchTable('Logs');
$clusterUuid = $cluster['GalaxyCluster']['uuid'];
foreach ($relations as $k => $relation) {
if (!isset($relation['GalaxyClusterRelation'])) {
$relation = array('GalaxyClusterRelation' => $relation);
}
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $clusterUuid;
$relation['GalaxyClusterRelation']['galaxy_cluster_id'] = $cluster['GalaxyCluster']['id'];
if (empty($relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'])) {
$LogsTable->createLogEntry($user, 'captureRelations', 'GalaxyClusterRelation', 0, __('No referenced cluster UUID provided'), __('relation for cluster (%s)', $clusterUuid));
$results['failed']++;
continue;
} else {
$options = array(
'conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'],
),
'fields' => array(
'id', 'uuid',
)
);
$referencedCluster = $this->SourceCluster->fetchGalaxyClusters($user, $options);
if (empty($referencedCluster)) {
if (!$fromPull) {
$LogsTable->createLogEntry($user, 'captureRelations', 'GalaxyClusterRelation', 0, __('Referenced cluster not found'), __('relation to (%s) for cluster (%s)', $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], $clusterUuid));
}
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = 0;
} else {
$referencedCluster = $referencedCluster[0];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = $referencedCluster['SourceCluster']['id'];
}
}
$existingRelation = $this->find('first', array('conditions' => array(
'GalaxyClusterRelation.galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid'],
'GalaxyClusterRelation.referenced_galaxy_cluster_uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'],
'GalaxyClusterRelation.referenced_galaxy_cluster_type' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_type'],
)));
if (!empty($existingRelation)) {
if (!$fromPull) {
$LogsTable->createLogEntry($user, 'captureRelations', 'GalaxyClusterRelation', 0, __('Relation already exists'), __('relation to (%s) for cluster (%s)', $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'], $clusterUuid));
$results['failed']++;
continue;
} else {
$relation['GalaxyClusterRelation']['id'] = $existingRelation['GalaxyClusterRelation']['id'];
}
} else {
unset($relation['GalaxyClusterRelation']['id']);
$this->create();
}
$EventsTable = $this->fetchTable('Events');
if (isset($relation['GalaxyClusterRelation']['distribution']) && $relation['GalaxyClusterRelation']['distribution'] == 4) {
$relation['GalaxyClusterRelation'] = $EventsTable->captureSGForElement($relation['GalaxyClusterRelation'], $user);
}
$saveSuccess = $this->save($relation);
if ($saveSuccess) {
$results['imported']++;
$modelKey = false;
if (!empty($relation['GalaxyClusterRelation']['GalaxyClusterRelationTag'])) {
$modelKey = 'GalaxyClusterRelationTag';
} elseif (!empty($relation['GalaxyClusterRelation']['Tag'])) {
$modelKey = 'Tag';
}
if ($modelKey !== false) {
$tagNames = Hash::extract($relation['GalaxyClusterRelation'][$modelKey], '{n}.name');
// Similar behavior as for AttributeTags: Here we only attach tags. If they were removed at some point it's not taken into account.
// Since we don't have tag soft-deletion, tags added by users will be kept.
$this->GalaxyClusterRelationTag->attachTags($user, $this->id, $tagNames, $capture = true);
}
} else {
$results['failed']++;
}
}
$results['success'] = $results['imported'] > 0;
return $results;
}
public function removeNonAccessibleTargetCluster($user, $relations)
{
$availableTargetClusterIDs = $this->TargetCluster->cacheGalaxyClusterIDs($user);
$availableTargetClusterIDsKeyed = array_flip($availableTargetClusterIDs);
foreach ($relations as $i => $relation) {
if (
isset($relation['TargetCluster']['id']) &&
!isset($availableTargetClusterIDsKeyed[$relation['TargetCluster']['id']])
) {
$relations[$i]['TargetCluster'] = null;
}
}
return $relations;
}
/**
* syncUUIDsAndIDs Adapt IDs of source and target cluster inside the relation based on the provided two UUIDs
*
* @param array $user
* @param array $relation
* @return array The adpated relation
*/
private function syncUUIDsAndIDs(array $user, array $relation)
{
$options = array('conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['galaxy_cluster_uuid']
));
$sourceCluster = $this->SourceCluster->fetchGalaxyClusters($user, $options);
if (!empty($sourceCluster)) {
$sourceCluster = $sourceCluster[0];
$relation['GalaxyClusterRelation']['galaxy_cluster_id'] = $sourceCluster['SourceCluster']['id'];
$relation['GalaxyClusterRelation']['galaxy_cluster_uuid'] = $sourceCluster['SourceCluster']['uuid'];
}
$options = array('conditions' => array(
'uuid' => $relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid']
));
$targetCluster = $this->TargetCluster->fetchGalaxyClusters($user, $options);
if (!empty($targetCluster)) {
$targetCluster = $targetCluster[0];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = $targetCluster['TargetCluster']['id'];
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_uuid'] = $targetCluster['TargetCluster']['uuid'];
} else {
$relation['GalaxyClusterRelation']['referenced_galaxy_cluster_id'] = 0;
}
return $relation;
}
}

View File

@ -25,6 +25,9 @@ use Cake\Http\Exception\NotFoundException;
use Cake\Validation\Validation;
use Exception;
use App\Http\Exception\HttpSocketHttpException;
use Cake\Validation\Validator;
use App\Model\Entity\Distribution;
use Cake\ORM\RulesChecker;
/**
* @property Tag $Tag
@ -33,7 +36,7 @@ use App\Http\Exception\HttpSocketHttpException;
* @property GalaxyElement $GalaxyElement
* @property SharingGroup $SharingGroup
*/
class GalaxyClusterTable extends AppTable
class GalaxyClustersTable extends AppTable
{
use LocatorAwareTrait;
@ -46,10 +49,113 @@ class GalaxyClusterTable extends AppTable
'json' => array('json', 'JsonExport', 'json'),
);
public function validationDefault(Validator $validator): Validator
{
$validator
->requirePresence(['value'])
->add(
'value',
'stringNotEmpty',
[
'rule' => 'stringNotEmpty',
'message' => 'Please provide a value'
]
)
->add(
'uuid',
'uuid',
[
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
]
)
->add(
'distribution',
'inList',
[
'rule' => ['inList', Distribution::ALL],
'message' => 'Options: ' . implode(', ', Distribution::DESCRIPTION)
]
)
->add(
'published',
'boolean',
[
'rule' => 'boolean'
]
);
return $validator;
}
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['uuid']));
return $rules;
}
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('AuditLog');
$this->belongsTo(
'Galaxy',
[
'className' => 'Galaxy',
'foreignKey' => 'galaxy_id',
]
);
$this->belongsTo(
'Tag',
[
'foreignKey' => false,
'conditions' => array('GalaxyCluster.tag_name = Tag.name')
]
);
$this->belongsTo(
'Org',
[
'className' => 'Organisation',
'foreignKey' => 'org_id'
]
);
$this->belongsTo(
'Orgc',
[
'className' => 'Organisation',
'foreignKey' => 'orgc_id'
]
);
$this->belongsTo(
'SharingGroup',
[
'foreignKey' => 'sharing_group_id',
'propertyName' => 'SharingGroup'
]
);
$this->hasMany(
'GalaxyElement',
[
'dependent' => true,
]
);
$this->hasMany(
'GalaxyClusterRelations',
[
'foreignKey' => 'galaxy_cluster_id',
'dependent' => true,
'propertyName' => 'GalaxyClusterRelation'
]
);
$this->hasMany(
'TargetingClusterRelation',
[
'foreignKey' => 'referenced_galaxy_cluster_id',
'propertyName' => 'TargetingClusterRelation'
]
);
}
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
@ -74,7 +180,7 @@ class GalaxyClusterTable extends AppTable
public function find(string $type = 'all', array $options = []): Query
{
$mapper = function ($row) {
// TODO: use JsonFieldBehavior
// TODO: [3.x-MIGRATION] use JsonFieldBehavior
if (isset($row['authors'])) {
$row['authors'] = json_decode($row['authors'], true);
}
@ -109,9 +215,9 @@ class GalaxyClusterTable extends AppTable
// Update all relations IDs that are unknown but saved
if (!$this->bulkEntry) {
$cluster = $this->fetchAndSetUUID($entity);
$this->GalaxyClusterRelation->updateAll(
array('GalaxyClusterRelation.referenced_galaxy_cluster_id' => $cluster['id']),
array('GalaxyClusterRelation.referenced_galaxy_cluster_uuid' => $cluster['uuid'])
$this->GalaxyClusterRelations->updateAll(
array('GalaxyClusterRelations.referenced_galaxy_cluster_id' => $cluster['id']),
array('GalaxyClusterRelations.referenced_galaxy_cluster_uuid' => $cluster['uuid'])
);
}
}
@ -134,12 +240,12 @@ class GalaxyClusterTable extends AppTable
{
// Remove all relations IDs now that the cluster is unknown
if (!empty($this->deletedClusterUUID)) {
$this->GalaxyClusterRelation->updateAll(
array('GalaxyClusterRelation.referenced_galaxy_cluster_id' => 0),
array('GalaxyClusterRelation.referenced_galaxy_cluster_uuid' => $this->deletedClusterUUID)
$this->GalaxyClusterRelations->updateAll(
array('GalaxyClusterRelations.referenced_galaxy_cluster_id' => 0),
array('GalaxyClusterRelations.referenced_galaxy_cluster_uuid' => $this->deletedClusterUUID)
);
$this->GalaxyElement->deleteAll(array('GalaxyElement.galaxy_cluster_id' => $entity->id));
$this->GalaxyClusterRelation->deleteAll(array('GalaxyClusterRelation.galaxy_cluster_uuid' => $this->deletedClusterUUID));
$this->GalaxyClusterRelations->deleteAll(array('GalaxyClusterRelations.galaxy_cluster_uuid' => $this->deletedClusterUUID));
}
}
@ -162,7 +268,7 @@ class GalaxyClusterTable extends AppTable
public function generateMissingRelations()
{
$missingRelations = $this->GalaxyClusterRelation->find('column', [
$missingRelations = $this->GalaxyClusterRelations->find('all', [
'conditions' => ['referenced_galaxy_cluster_id' => 0],
'fields' => ['referenced_galaxy_cluster_uuid'],
'unique' => true,
@ -175,7 +281,7 @@ class GalaxyClusterTable extends AppTable
'fields' => ['uuid', 'id']
]);
foreach ($ids as $uuid => $id) {
$this->GalaxyClusterRelation->updateAll(
$this->GalaxyClusterRelations->updateAll(
['referenced_galaxy_cluster_id' => $id],
['referenced_galaxy_cluster_uuid' => $uuid]
);
@ -310,7 +416,7 @@ class GalaxyClusterTable extends AppTable
$this->GalaxyElement->updateElements(-1, $savedCluster['GalaxyCluster']['id'], $elementsToSave);
}
if (!empty($cluster['GalaxyCluster']['GalaxyClusterRelation'])) {
$this->GalaxyClusterRelation->saveRelations($user, $cluster['GalaxyCluster'], $cluster['GalaxyCluster']['GalaxyClusterRelation'], $captureTag = true);
$this->GalaxyClusterRelations->saveRelations($user, $cluster['GalaxyCluster'], $cluster['GalaxyCluster']['GalaxyClusterRelation'], $captureTag = true);
}
} else {
foreach ($this->validationErrors as $validationError) {
@ -386,7 +492,7 @@ class GalaxyClusterTable extends AppTable
$this->GalaxyElement->updateElements($cluster['GalaxyCluster']['id'], $cluster['GalaxyCluster']['id'], $elementsToSave, $delete = $deleteOldElements);
}
if (!empty($cluster['GalaxyCluster']['GalaxyClusterRelation'])) {
$this->GalaxyClusterRelation->saveRelations($user, $cluster['GalaxyCluster'], $cluster['GalaxyCluster']['GalaxyClusterRelation'], $captureTag = true, $force = true);
$this->GalaxyClusterRelations->saveRelations($user, $cluster['GalaxyCluster'], $cluster['GalaxyCluster']['GalaxyClusterRelation'], $captureTag = true, $force = true);
}
} else {
foreach ($this->validationErrors as $validationError) {
@ -555,18 +661,18 @@ class GalaxyClusterTable extends AppTable
]);
$cluster_ids = Hash::extract($clusters, '{n}.GalaxyCluster.id');
$cluster_uuids = Hash::extract($clusters, '{n}.GalaxyCluster.uuid');
$relation_ids = $this->GalaxyClusterRelation->find('list', [
$relation_ids = $this->GalaxyClusterRelations->find('list', [
'conditions' => ['galaxy_cluster_id' => $cluster_ids],
'fields' => ['id']
]);
$this->deleteAll(['GalaxyCluster.default' => true], false, false);
$this->GalaxyElement->deleteAll(['GalaxyElement.galaxy_cluster_id' => $cluster_ids], false, false);
$this->GalaxyClusterRelation->deleteAll(['GalaxyClusterRelation.galaxy_cluster_id' => $cluster_ids], false, false);
$this->GalaxyClusterRelation->updateAll(
['GalaxyClusterRelation.referenced_galaxy_cluster_id' => 0],
['GalaxyClusterRelation.referenced_galaxy_cluster_uuid' => $cluster_uuids] // For all default clusters being referenced
$this->GalaxyClusterRelations->deleteAll(['GalaxyClusterRelations.galaxy_cluster_id' => $cluster_ids], false, false);
$this->GalaxyClusterRelations->updateAll(
['GalaxyClusterRelations.referenced_galaxy_cluster_id' => 0],
['GalaxyClusterRelations.referenced_galaxy_cluster_uuid' => $cluster_uuids] // For all default clusters being referenced
);
$this->GalaxyClusterRelation->GalaxyClusterRelationTag->deleteAll(['GalaxyClusterRelationTag.galaxy_cluster_relation_id' => $relation_ids], false, false);
$this->GalaxyClusterRelations->GalaxyClusterRelationTag->deleteAll(['GalaxyClusterRelationTag.galaxy_cluster_relation_id' => $relation_ids], false, false);
$LogTable = $this->fetchTable('Logs');
$LogTable->createLogEntry('SYSTEM', 'wipe_default', 'GalaxyCluster', 0, "Wiping default galaxy clusters");
}
@ -788,8 +894,8 @@ class GalaxyClusterTable extends AppTable
$this->GalaxyElement->captureElements($user, $cluster['GalaxyCluster']['GalaxyElement'], $savedCluster['GalaxyCluster']['id']);
}
if (!empty($cluster['GalaxyCluster']['GalaxyClusterRelation'])) {
$this->GalaxyClusterRelation->deleteAll(array('GalaxyClusterRelation.galaxy_cluster_id' => $savedCluster['GalaxyCluster']['id']));
$saveResult = $this->GalaxyClusterRelation->captureRelations($user, $savedCluster, $cluster['GalaxyCluster']['GalaxyClusterRelation'], $fromPull = $fromPull);
$this->GalaxyClusterRelations->deleteAll(array('GalaxyClusterRelations.galaxy_cluster_id' => $savedCluster['GalaxyCluster']['id']));
$saveResult = $this->GalaxyClusterRelations->captureRelations($user, $savedCluster, $cluster['GalaxyCluster']['GalaxyClusterRelation'], $fromPull = $fromPull);
if ($saveResult['failed'] > 0) {
$results['errors'][] = __('Issues while capturing relations have been logged.');
}
@ -868,8 +974,9 @@ class GalaxyClusterTable extends AppTable
/* Return a list of all tags associated with the cluster specific cluster within the galaxy (or all clusters if $clusterValue is false)
* The counts are restricted to the event IDs that the user is allowed to see.
*/
public function getTags($galaxyType, $clusterValue = false, $user)
public function getTags($galaxyType, $clusterValue, $user)
{
$clusterValue = $clusterValue ? $clusterValue : false;
$EventsTable = $this->fetchTable('Events');
$event_ids = $EventsTable->fetchEventIds($user, [
'list' => true
@ -1000,7 +1107,7 @@ class GalaxyClusterTable extends AppTable
'Galaxy',
'GalaxyElement',
'GalaxyClusterRelation' => array(
'conditions' => $this->GalaxyClusterRelation->buildConditions($user, false),
'conditions' => $this->GalaxyClusterRelations->buildConditions($user, false),
'GalaxyClusterRelationTag',
'SharingGroup',
),
@ -1069,11 +1176,11 @@ class GalaxyClusterTable extends AppTable
)
));
$tagsToFetch = Hash::extract($clusters, "{n}.GalaxyClusterRelation.{n}.GalaxyClusterRelationTag.{n}.tag_id");
$tagsToFetch = Hash::extract($clusters, "{n}.GalaxyClusterRelations.{n}.GalaxyClusterRelationTag.{n}.tag_id");
$tagsToFetch = array_merge($tagsToFetch, Hash::extract($targetingClusterRelations, "GalaxyClusterRelationTag.{n}.tag_id"));
if (!empty($tagsToFetch)) {
$tags = $this->GalaxyClusterRelation->GalaxyClusterRelationTag->Tag->find('all', [
$tags = $this->GalaxyClusterRelations->GalaxyClusterRelationTag->Tag->find('all', [
'conditions' => ['id' => array_unique($tagsToFetch, SORT_REGULAR)],
'recursive' => -1,
]);

View File

@ -0,0 +1,181 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\Utility\Hash;
/**
* @property GalaxyCluster $GalaxyCluster
*/
class GalaxyElements extends AppTable
{
public $useTable = 'galaxy_elements';
public $recursive = -1;
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('AuditLog');
$this->belongsTo(
'GalaxyClusters',
[
'className' => 'GalaxyCluster',
'foreignKey' => 'galaxy_cluster_id',
]
);
}
public function updateElements($oldClusterId, $newClusterId, $elements, $delete = true)
{
if ($delete) {
$this->deleteAll(array('GalaxyElement.galaxy_cluster_id' => $oldClusterId));
}
$tempElements = array();
foreach ($elements as $key => $value) {
if (is_array($value)) {
foreach ($value as $arrayElement) {
$tempElements[] = array(
'key' => $key,
'value' => $arrayElement,
'galaxy_cluster_id' => $newClusterId
);
}
} else {
$tempElements[] = array(
'key' => $key,
'value' => $value,
'galaxy_cluster_id' => $newClusterId
);
}
}
$this->saveMany($tempElements);
}
public function update($galaxy_id, $oldClusters, $newClusters)
{
$elementsToSave = array();
// Since we are dealing with flat files as the end all be all content, we are safe to just drop all of the old clusters and recreate them.
foreach ($oldClusters as $oldCluster) {
$this->deleteAll(array('GalaxyElement.galaxy_cluster_id' => $oldCluster['GalaxyCluster']['id']));
}
foreach ($newClusters as $newCluster) {
$tempCluster = array();
foreach ($newCluster as $key => $value) {
// Don't store the reserved fields as elements
if ($key == 'description' || $key == 'value') {
continue;
}
if (is_array($value)) {
foreach ($value as $arrayElement) {
$tempCluster[] = array('key' => $key, 'value' => $arrayElement);
}
} else {
$tempCluster[] = array('key' => $key, 'value' => $value);
}
}
foreach ($tempCluster as $key => $value) {
$tempCluster[$key]['galaxy_cluster_id'] = $oldCluster['GalaxyCluster']['id'];
}
$elementsToSave = array_merge($elementsToSave, $tempCluster);
}
$this->saveMany($elementsToSave);
}
public function captureElements($user, $elements, $clusterId)
{
$tempElements = array();
foreach ($elements as $k => $element) {
$tempElements[] = array(
'key' => $element['key'],
'value' => $element['value'],
'galaxy_cluster_id' => $clusterId,
);
}
$this->saveMany($tempElements);
}
public function buildACLConditions($user)
{
$conditions = [];
if (!$user['Role']['perm_site_admin']) {
$conditions = $this->GalaxyCluster->buildConditions($user);
}
return $conditions;
}
public function buildClusterConditions($user, $clusterId)
{
return [
$this->buildACLConditions($user),
'GalaxyCluster.id' => $clusterId
];
}
public function fetchElements(array $user, $clusterId)
{
$params = array(
'conditions' => $this->buildClusterConditions($user, $clusterId),
'contain' => ['GalaxyCluster' => ['fields' => ['id', 'distribution', 'org_id']]],
'recursive' => -1
);
$elements = $this->find('all', $params);
foreach ($elements as $i => $element) {
$elements[$i] = $elements[$i]['GalaxyElement'];
unset($elements[$i]['GalaxyCluster']);
unset($elements[$i]['GalaxyElement']);
}
return $elements;
}
public function getExpandedJSONFromElements($elements)
{
$keyedValue = [];
foreach ($elements as $i => $element) {
$keyedValue[$element['GalaxyElement']['key']][] = $element['GalaxyElement']['value'];
}
$expanded = Hash::expand($keyedValue);
return $expanded;
}
/**
* getClusterIDsFromMatchingElements
*
* @param array $user
* @param array $elements an associative array containg the elements to search for
* Example: {"synonyms": "apt42"}
* @return array
*/
public function getClusterIDsFromMatchingElements(array $user, array $elements): array
{
$conditionCount = 0;
$elementConditions = [];
foreach ($elements as $key => $value) {
$elementConditions['OR'][] = [
'GalaxyElement.key' => $key,
'GalaxyElement.value' => $value,
];
$conditionCount += is_array($value) ? count($value) : 1;
}
$conditions = [
$this->buildACLConditions($user),
$elementConditions,
];
$elements = $this->find('all', [
'fields' => ['GalaxyElement.galaxy_cluster_id'],
'conditions' => $conditions,
'contain' => ['GalaxyCluster' => ['fields' => ['id', 'distribution', 'org_id']]],
'group' => ['GalaxyElement.galaxy_cluster_id'],
'having' => ['COUNT(GalaxyElement.id) =' => $conditionCount],
'recursive' => -1
]);
$clusterIDs = [];
foreach ($elements as $element) {
$clusterIDs[] = $element['GalaxyElement']['galaxy_cluster_id'];
}
return $clusterIDs;
}
}

View File

@ -2,36 +2,34 @@
declare(strict_types=1);
namespace App\Test\TestCase\Api\ObjectTemplates;
namespace App\Test\TestCase\Api\Noticelists;
use Cake\TestSuite\TestCase;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\ObjectTemplatesFixture;
use App\Test\Helper\ApiTestTrait;
use Cake\TestSuite\TestCase;
class IndexObjectTemplatesApiTest extends TestCase
class UpdateNoticelistsApiTest extends TestCase
{
use ApiTestTrait;
protected const ENDPOINT = '/object-templates/index';
protected const ENDPOINT = '/noticelists/update';
protected $fixtures = [
'app.Organisations',
'app.Users',
'app.AuthKeys',
'app.ObjectTemplates',
'app.ObjectTemplateElements',
'app.Galaxies'
];
public function testIndexObjectTemplates(): void
public function testUpdateNoticelists(): void
{
$this->skipOpenApiValidations();
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
$this->get(self::ENDPOINT);
$this->post(self::ENDPOINT);
$this->assertResponseOk();
$this->assertResponseContains(sprintf('"name": "%s"', ObjectTemplatesFixture::OBJECT_TEMPLATE_1_NAME));
$this->assertDbRecordExists('Noticelists', ['name' => 'gdpr']);
}
}