Merge branch 'develop' into 2.4

pull/9602/head
iglocska 2024-02-29 11:19:51 +01:00
commit c6710443e0
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
99 changed files with 10289 additions and 3534 deletions

2
PyMISP

@ -1 +1 @@
Subproject commit 492cfba2d2ad015d3fcda6e16c221fdefd93eca2
Subproject commit 4715f91d2ab948fb44640426be6a40099f94c910

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":185}
{"major":2, "minor":4, "hotfix":186}

View File

@ -85,6 +85,7 @@ class ServerShell extends AppShell
$userId = $this->args[0];
$user = $this->getUser($userId);
Configure::write('CurrentUserId', $userId);
if (!empty($this->args[1])) {
$technique = $this->args[1];
@ -129,6 +130,7 @@ class ServerShell extends AppShell
$user = $this->getUser($userId);
$serverId = $this->args[1];
$server = $this->getServer($serverId);
Configure::write('CurrentUserId', $userId);
if (!empty($this->args[2])) {
$technique = $this->args[2];
} else {
@ -146,7 +148,7 @@ class ServerShell extends AppShell
try {
$result = $this->Server->pull($user, $technique, $server, $jobId, $force);
if (is_array($result)) {
$message = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4]);
$message = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled, %s analyst data pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4], $result[5]);
$this->Job->saveStatus($jobId, true, $message);
} else {
$message = __('ERROR: %s', $result);
@ -169,6 +171,7 @@ class ServerShell extends AppShell
$user = $this->getUser($userId);
$serverId = $this->args[1];
$server = $this->getServer($serverId);
Configure::write('CurrentUserId', $userId);
$technique = empty($this->args[2]) ? 'full' : $this->args[2];
if (!empty($this->args[3])) {
$jobId = $this->args[3];
@ -203,6 +206,7 @@ class ServerShell extends AppShell
$userId = $this->args[0];
$user = $this->getUser($userId);
Configure::write('CurrentUserId', $userId);
$technique = isset($this->args[1]) ? $this->args[1] : 'full';
$servers = $this->Server->find('list', array(

View File

@ -0,0 +1,103 @@
<?php
App::uses('AppController', 'Controller');
class AnalystDataBlocklistsController extends AppController
{
public $components = array('Session', 'RequestHandler', 'BlockList');
public $paginate = array(
'limit' => 60,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 entries <- no we won't, this is the max a user van view/page.
'order' => array(
'AnalystDataBlocklist.created' => 'DESC'
),
);
public function index()
{
$passedArgsArray = array();
$passedArgs = $this->passedArgs;
$params = array();
$validParams = array('analyst_data_uuid', 'comment', 'analyst_data_info', 'analyst_data_orgc');
foreach ($validParams as $validParam) {
if (!empty($this->params['named'][$validParam])) {
$params[$validParam] = $this->params['named'][$validParam];
}
}
if (!empty($this->params['named']['searchall'])) {
$params['AND']['OR'] = array(
'analyst_data_uuid' => $this->params['named']['searchall'],
'comment' => $this->params['named']['searchall'],
'analyst_data_info' => $this->params['named']['searchall'],
'analyst_data_orgc' => $this->params['named']['searchall']
);
}
$this->set('passedArgs', json_encode($passedArgs));
$this->set('passedArgsArray', $passedArgsArray);
return $this->BlockList->index($this->_isRest(), $params);
}
public function add()
{
$this->set('action', 'add');
return $this->BlockList->add($this->_isRest());
}
public function edit($id)
{
$this->set('action', 'edit');
return $this->BlockList->edit($this->_isRest(), $id);
}
public function delete($id)
{
if (Validation::uuid($id)) {
$entry = $this->AnalystDataBlocklist->find('first', array(
'conditions' => array('analyst_data_uuid' => $id)
));
if (empty($entry)) {
throw new NotFoundException(__('Invalid blocklist entry'));
}
$id = $entry['AnalystDataBlocklist']['id'];
}
return $this->BlockList->delete($this->_isRest(), $id);
}
public function massDelete()
{
if ($this->request->is('post') || $this->request->is('put')) {
if (!isset($this->request->data['AnalystDataBlocklist'])) {
$this->request->data = array('AnalystDataBlocklist' => $this->request->data);
}
$ids = $this->request->data['AnalystDataBlocklist']['ids'];
$analyst_data_ids = json_decode($ids, true);
if (empty($analyst_data_ids)) {
throw new NotFoundException(__('Invalid Analyst Data IDs.'));
}
$result = $this->AnalystDataBlocklist->deleteAll(array('AnalystDataBlocklist.id' => $analyst_data_ids));
if ($result) {
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('AnalystDataBlocklist', 'Deleted', $ids, $this->response->type());
} else {
$this->Flash->success('Blocklist entry removed');
$this->redirect(array('controller' => 'AnalystDataBlocklist', 'action' => 'index'));
}
} else {
$error = __('Failed to delete Analyst Data from AnalystDataBlocklist. Error: ') . PHP_EOL . h($result);
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('AnalystDataBlocklist', 'Deleted', false, $error, $this->response->type());
} else {
$this->Flash->error($error);
$this->redirect(array('controller' => 'AnalystDataBlocklists', 'action' => 'index'));
}
}
} else {
$ids = json_decode($this->request->query('ids'), true);
if (empty($ids)) {
throw new NotFoundException(__('Invalid analyst data IDs.'));
}
$this->set('analyst_data_ids', $ids);
}
}
}

View File

@ -0,0 +1,328 @@
<?php
App::uses('AppController', 'Controller');
class AnalystDataController extends AppController
{
public $components = ['Session', 'RequestHandler'];
public $paginate = [
'limit' => 60,
'order' => []
];
public $uses = [
'Opinion',
'Note',
'Relationship'
];
private $__valid_types = [
'Opinion',
'Note',
'Relationship'
];
// public $modelSelection = 'Note';
private function _setViewElements()
{
$dropdownData = [];
$this->loadModel('Event');
$dropdownData['distributionLevels'] = $this->Event->distributionLevels;
$this->set('initialDistribution', Configure::read('MISP.default_event_distribution'));
$dropdownData['sgs'] = $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1);
$dropdownData['valid_targets'] = array_combine($this->AnalystData->valid_targets, $this->AnalystData->valid_targets);
$this->set(compact('dropdownData'));
$this->set('modelSelection', $this->modelSelection);
$this->set('distributionLevels', $this->Event->distributionLevels);
App::uses('LanguageRFC5646Tool', 'Tools');
$this->set('languageRFC5646', ['' => __('- No language -'), LanguageRFC5646Tool::getLanguages()]);
}
public function add($type = 'Note', $object_uuid = null, $object_type = null)
{
$this->__typeSelector($type);
if (!empty($object_uuid)) {
$this->request->data[$this->modelSelection]['object_uuid'] = $object_uuid;
}
if (!empty($object_type)) {
$this->request->data[$this->modelSelection]['object_type'] = $object_type;
}
if (empty($this->request->data[$this->modelSelection]['object_type']) && !empty($this->request->data[$this->modelSelection]['object_uuid'])) {
$this->request->data[$this->modelSelection]['object_type'] = $this->AnalystData->deduceType($object_uuid);
}
$params = [];
$this->CRUD->add($params);
if ($this->restResponsePayload) {
return $this->restResponsePayload;
}
$this->_setViewElements();
if ($type == 'Relationship') {
$this->set('existingRelations', $this->AnalystData->getExistingRelationships());
}
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'add_' . strtolower($type)));
$this->render('add');
}
public function edit($type = 'Note', $id)
{
if ($type === 'all' && Validation::uuid($id)) {
$this->loadModel('AnalystData');
$type = $this->AnalystData->deduceType($id);
}
$this->__typeSelector($type);
if (!is_numeric($id) && Validation::uuid($id)) {
$id = $this->AnalystData->getIDFromUUID($type, $id);
}
$this->set('id', $id);
$conditions = $this->AnalystData->buildConditions($this->Auth->user());
$params = [
'fields' => $this->AnalystData->getEditableFields(),
'conditions' => $conditions,
'afterFind' => function(array $analystData): array {
$canEdit = $this->ACL->canEditAnalystData($this->Auth->user(), $analystData, $this->modelSelection);
if (!$canEdit) {
throw new MethodNotAllowedException(__('You are not authorised to do that.'));
}
return $analystData;
},
'beforeSave' => function(array $analystData): array {
$analystData[$this->modelSelection]['modified'] = date('Y-m-d H:i:s');
return $analystData;
}
];
$this->CRUD->edit($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->_setViewElements();
if ($type == 'Relationship') {
$this->set('existingRelations', $this->AnalystData->getExistingRelationships());
}
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'edit'));
$this->render('add');
}
public function delete($type = 'Note', $id, $hard=true)
{
if ($type === 'all' && Validation::uuid($id)) {
$this->loadModel('AnalystData');
$type = $this->AnalystData->deduceType($id);
}
$this->__typeSelector($type);
if (!is_numeric($id) && Validation::uuid($id)) {
$id = $this->AnalystData->getIDFromUUID($type, $id);
}
$params = [
'afterFind' => function(array $analystData) {
$canEdit = $this->ACL->canEditAnalystData($this->Auth->user(), $analystData, $this->modelSelection);
if (!$canEdit) {
throw new MethodNotAllowedException(__('You are not authorised to do that.'));
}
return $analystData;
},
'afterDelete' => function($deletedAnalystData) use ($hard) {
if (empty($hard)) {
return;
}
$type = $this->AnalystData->deduceAnalystDataType($deletedAnalystData);
$info = '- Unsupported analyst type -';
if ($type === 'Note') {
$info = $deletedAnalystData[$type]['note'];
} else if ($type === 'Opinion') {
$info = sprintf('%s/100 :: %s', $deletedAnalystData[$type]['opinion'], $deletedAnalystData[$type]['comment']);
} else if ($type === 'Relationship') {
$info = sprintf('-- %s --> %s :: %s', $deletedAnalystData[$type]['relationship_type'] ?? '[undefined]', $deletedAnalystData[$type]['related_object_type'], $deletedAnalystData[$type]['related_object_uuid']);
}
$this->AnalystDataBlocklist = ClassRegistry::init('AnalystDataBlocklist');
$this->AnalystDataBlocklist->create();
if (!empty($deletedAnalystData[$type]['orgc_uuid'])) {
if (!empty($deletedAnalystData[$type]['Orgc'])) {
$orgc = $deletedAnalystData[$type];
} else {
$orgc = $this->Orgc->find('first', array(
'conditions' => ['Orgc.uuid' => $deletedAnalystData[$type]['orgc_uuid']],
'recursive' => -1,
'fields' => ['Orgc.name'],
));
}
} else {
$orgc = ['Orgc' => ['name' => 'MISP']];
}
$this->AnalystDataBlocklist->save(['analyst_data_uuid' => $deletedAnalystData[$type]['uuid'], 'analyst_data_info' => $info, 'analyst_data_orgc' => $orgc['Orgc']['name']]);
}
];
$this->CRUD->delete($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
public function view($type = 'Note', $id)
{
if ($type === 'all' && Validation::uuid($id)) {
$this->loadModel('AnalystData');
$type = $this->AnalystData->getAnalystDataTypeFromUUID($id);
}
$this->__typeSelector($type);
if (!is_numeric($id) && Validation::uuid($id)) {
$id = $this->AnalystData->getIDFromUUID($type, $id);
}
if (!$this->IndexFilter->isRest()) {
$this->AnalystData->fetchRecursive = true;
}
$conditions = $this->AnalystData->buildConditions($this->Auth->user());
$this->CRUD->view($id, [
'conditions' => $conditions,
'contain' => ['Org', 'Orgc'],
'afterFind' => function(array $analystData) {
if (!$this->request->is('ajax')) {
unset($analystData[$this->modelSelection]['_canEdit']);
}
return $analystData;
}
]);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('id', $id);
$this->loadModel('Event');
$this->_setViewElements();
$this->set('distributionLevels', $this->Event->distributionLevels);
$this->set('shortDist', $this->Event->shortDist);
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'view'));
$this->render('view');
}
public function index($type = 'Note')
{
$this->__typeSelector($type);
$conditions = $this->AnalystData->buildConditions($this->Auth->user());
$params = [
'filters' => ['uuid', 'target_object'],
'quickFilters' => ['name'],
'conditions' => $conditions,
'afterFind' => function(array $data) {
foreach ($data as $i => $analystData) {
if (!$this->request->is('ajax')) {
unset($analystData[$this->modelSelection]['_canEdit']);
}
}
return $data;
}
];
$this->CRUD->index($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->_setViewElements();
$this->set('menuData', array('menuList' => 'analyst_data', 'menuItem' => 'index'));
}
public function getRelatedElement($type, $uuid)
{
$this->__typeSelector('Relationship');
$data = $this->AnalystData->getRelatedElement($this->Auth->user(), $type, $uuid);
return $this->RestResponse->viewData($data, 'json');
}
public function getChildren($type = 'Note', $uuid, $depth=2)
{
$this->__typeSelector($type);
$data = $this->AnalystData->getChildren($this->Auth->user(), $uuid, $depth);
return $this->RestResponse->viewData($data, 'json');
}
public function filterAnalystDataForPush()
{
if (!$this->request->is('post')) {
throw new MethodNotAllowedException(__('This function is only accessible via POST requests.'));
}
$this->loadModel('AnalystData');
$allIncomingAnalystData = $this->request->data;
$allData = $this->AnalystData->filterAnalystDataForPush($allIncomingAnalystData);
return $this->RestResponse->viewData($allData, $this->response->type());
}
public function indexMinimal()
{
$this->loadModel('AnalystData');
$filters = [];
if ($this->request->is('post')) {
$filters = $this->request->data;
}
$options = [];
if (!empty($filters['orgc_name'])) {
$orgcNames = $filters['orgc_name'];
if (!is_array($orgcNames)) {
$orgcName = [$orgcNames];
}
$filterName = 'orgc_uuid';
foreach ($orgcNames as $orgcName) {
if ($orgcName[0] === '!') {
$orgc = $this->AnalystData->Orgc->fetchOrg(substr($orgcName, 1));
if ($orgc === false) {
continue;
}
$options[]['AND'][] = ["{$filterName} !=" => $orgc['uuid']];
} else {
$orgc = $this->AnalystData->Orgc->fetchOrg($orgcName);
if ($orgc === false) {
continue;
}
$options['OR'][] = [$filterName => $orgc['uuid']];
}
}
}
$allData = $this->AnalystData->indexMinimal($this->Auth->user(), $options);
return $this->RestResponse->viewData($allData, $this->response->type());
}
public function pushAnalystData()
{
if (!$this->Auth->user()['Role']['perm_sync'] || !$this->Auth->user()['Role']['perm_analyst_data']) {
throw new MethodNotAllowedException(__('You do not have the permission to do that.'));
}
if (!$this->_isRest()) {
throw new MethodNotAllowedException(__('This action is only accessible via a REST request.'));
}
if ($this->request->is('post')) {
$this->loadModel('AnalystData');
$analystData = $this->request->data;
$saveResult = $this->AnalystData->captureAnalystData($this->Auth->user(), $analystData);
$messageInfo = __('%s imported, %s ignored, %s failed. %s', $saveResult['imported'], $saveResult['ignored'], $saveResult['failed'], !empty($saveResult['errors']) ? implode(', ', $saveResult['errors']) : '');
if ($saveResult['success']) {
$message = __('Analyst Data imported. ') . $messageInfo;
return $this->RestResponse->saveSuccessResponse('AnalystData', 'pushAnalystData', false, $this->response->type(), $message);
} else {
$message = __('Could not import analyst data. ') . $messageInfo;
return $this->RestResponse->saveFailResponse('AnalystData', 'pushAnalystData', false, $message);
}
}
}
private function __typeSelector($type) {
foreach ($this->__valid_types as $vt) {
if ($type === $vt) {
$this->modelSelection = $vt;
$this->loadModel($vt);
$this->AnalystData = $this->{$vt};
$this->modelClass = $vt;
$this->{$vt}->current_user = $this->Auth->user();
return $vt;
}
}
throw new MethodNotAllowedException(__('Invalid type.'));
}
}

View File

@ -33,8 +33,8 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName');
private $__queryVersion = '158';
public $pyMispVersion = '2.4.185';
private $__queryVersion = '159';
public $pyMispVersion = '2.4.186';
public $phpmin = '7.2';
public $phprec = '7.4';
public $phptoonew = '8.0';
@ -306,6 +306,7 @@ class AppController extends Controller
$this->set('isAclTagger', $role['perm_tagger']);
$this->set('isAclGalaxyEditor', !empty($role['perm_galaxy_editor']));
$this->set('isAclSighting', $role['perm_sighting'] ?? false);
$this->set('isAclAnalystDataCreator', $role['perm_analyst_data'] ?? false);
$this->set('aclComponent', $this->ACL);
$this->userRole = $role;

View File

@ -0,0 +1,146 @@
<?php
use PHPUnit\Framework\MockObject\InvalidMethodNameException;
App::uses('AppController', 'Controller');
class CollectionElementsController extends AppController
{
public $components = ['Session', 'RequestHandler'];
public $paginate = [
'limit' => 60,
'order' => []
];
public $uses = [
];
public function add($collection_id)
{
$this->CollectionElement->Collection->current_user = $this->Auth->user();
if (!$this->CollectionElement->Collection->mayModify($this->Auth->user('id'), intval($collection_id))) {
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
}
$this->CRUD->add([
'beforeSave' => function (array $collectionElement) use ($collection_id) {
$collectionElement['CollectionElement']['collection_id'] = intval($collection_id);
return $collectionElement;
}
]);
if ($this->restResponsePayload) {
return $this->restResponsePayload;
}
$dropdownData = [
'types' => array_combine($this->CollectionElement->valid_types, $this->CollectionElement->valid_types)
];
$this->set(compact('dropdownData'));
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'add_element'));
}
public function delete($element_id)
{
$collectionElement = $this->CollectionElement->find('first', [
'recursive' => -1,
'conditions' => [
'CollectionElement.id' => $element_id
]
]);
$collection_id = $collectionElement['CollectionElement']['collection_id'];
if (!$this->CollectionElement->Collection->mayModify($this->Auth->user('id'), $collection_id)) {
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
}
$this->CRUD->delete($element_id);
if ($this->restResponsePayload) {
return $this->restResponsePayload;
}
}
public function index($collection_id)
{
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'index'));
if (!$this->CollectionElement->Collection->mayView($this->Auth->user('id'), intval($collection_id))) {
throw new NotFoundException(__('Invalid collection or no access.'));
}
$params = [
'filters' => ['uuid', 'type', 'name'],
'quickFilters' => ['name'],
'conditions' => ['collection_id' => $collection_id]
];
$this->loadModel('Event');
$this->set('distributionLevels', $this->Event->distributionLevels);
$this->CRUD->index($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
public function addElementToCollection($element_type, $element_uuid)
{
if ($this->request->is('get')) {
$validCollections = $this->CollectionElement->Collection->find('list', [
'recursive' => -1,
'fields' => ['Collection.id', 'Collection.name'],
'conditions' => ['Collection.orgc_id' => $this->Auth->user('org_id')]
]);
if (empty($validCollections)) {
if ($this->request->is('ajax')) {
return $this->redirect(['controller' => 'collections', 'action' => 'add']);
}
throw new NotFoundException(__('You don\'t have any collections yet. Make sure you create one first before you can start adding elements.'));
}
$dropdownData = [
'collections' => $validCollections
];
$this->set(compact('dropdownData'));
} else if ($this->request->is('post')) {
if (!isset($this->request->data['CollectionElement'])) {
$this->request->data = ['CollectionElement' => $this->request->data];
}
if (!isset($this->request->data['CollectionElement']['collection_id'])) {
throw new NotFoundException(__('No collection_id specified.'));
}
$collection_id = intval($this->request->data['CollectionElement']['collection_id']);
if (!$this->CollectionElement->Collection->mayModify($this->Auth->user('id'), $collection_id)) {
throw new NotFoundException(__('Invalid collection or not authorized.'));
}
$description = empty($this->request->data['CollectionElement']['description']) ? '' : $this->request->data['CollectionElement']['description'];
$dataToSave = [
'CollectionElement' => [
'element_uuid' => $element_uuid,
'element_type' => $element_type,
'description' => $description,
'collection_id' => $collection_id
]
];
$this->CollectionElement->create();
$error = '';
try {
$result = $this->CollectionElement->save($dataToSave);
} catch (PDOException $e) {
if ($e->errorInfo[0] == 23000) {
$error = __(' Element already in Collection.');
}
}
if ($result) {
$message = __('Element added to the Collection.');
if ($this->IndexFilter->isRest()) {
return $this->RestResponse->saveSuccessResponse('CollectionElements', 'addElementToCollection', false, $this->response->type(), $message);
} else {
$this->Flash->success($message);
$this->redirect(Router::url($this->referer(), true));
}
} else {
$message = __('Element could not be added to the Collection.%s', $error);
if ($this->IndexFilter->isRest()) {
return $this->RestResponse->saveFailResponse('CollectionElements', 'addElementToCollection', false, $message, $this->response->type());
} else {
$this->Flash->error($message);
$this->redirect(Router::url($this->referer(), true));
}
}
}
}
}

View File

@ -0,0 +1,171 @@
<?php
App::uses('AppController', 'Controller');
class CollectionsController extends AppController
{
public $components = ['Session', 'RequestHandler'];
public $paginate = [
'limit' => 60,
'order' => []
];
public $uses = [
];
private $valid_types = [
'campaign',
'intrusion_set',
'named_threat',
'other',
'research'
];
public function add()
{
$this->Collection->current_user = $this->Auth->user();
$params = [];
if ($this->request->is('post')) {
$data = $this->request->data;
$params = [
'afterSave' => function (array $collection) use ($data) {
$this->Collection->CollectionElement->captureElements($collection);
return $collection;
}
];
}
$this->CRUD->add($params);
if ($this->restResponsePayload) {
return $this->restResponsePayload;
}
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'add'));
$this->loadModel('Event');
$dropdownData = [
'types' => array_combine($this->valid_types, $this->valid_types),
'distributionLevels' => $this->Event->distributionLevels,
'sgs' => $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1)
];
$this->set('initialDistribution', Configure::read('MISP.default_event_distribution'));
$this->set(compact('dropdownData'));
$this->render('add');
}
public function edit($id)
{
$this->Collection->current_user = $this->Auth->user();
if (!$this->Collection->mayModify($this->Auth->user('id'), $id)) {
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
}
$params = [];
if ($this->request->is('post') || $this->request->is('put')) {
$oldCollection = $this->Collection->find('first', [
'recursive' => -1,
'conditions' => ['Collection.id' => intval($id)]
]);
if (empty($oldCollection)) {
throw new NotFoundException(__('Invalid collection.'));
}
if (empty($this->request->data['Collection'])) {
$this->request->data = ['Collection' => $this->request->data];
}
$data = $this->request->data;
if (
isset($data['Collection']['modified']) &&
$data['Collection']['modified'] <= $oldCollection['Collection']['modified']
) {
throw new ForbiddenException(__('Collection received older or same as local version.'));
}
$params = [
'afterSave' => function (array &$collection) use ($data) {
$collection = $this->Collection->CollectionElement->captureElements($collection);
return $collection;
}
];
}
$this->set('id', $id);
$this->CRUD->edit($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'edit'));
$this->loadModel('Event');
$dropdownData = [
'types' => $this->valid_types,
'distributionLevels' => $this->Event->distributionLevels,
'sgs' => $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1)
];
$this->set(compact('dropdownData'));
$this->render('add');
}
public function delete($id)
{
if (!$this->Collection->mayModify($this->Auth->user('id'), $id)) {
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
}
$this->CRUD->delete($id);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
public function view($id)
{
$this->set('mayModify', $this->Collection->mayModify($this->Auth->user('id'), $id));
if (!$this->Collection->mayView($this->Auth->user('id'), $id)) {
throw new MethodNotAllowedException(__('Invalid Collection or insuficient privileges'));
}
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'view'));
$params = [
'contain' => [
'Orgc',
'Org',
'User',
'CollectionElement'
],
'afterFind' => function (array $collection){
return $this->Collection->rearrangeCollection($collection);
}
];
$this->CRUD->view($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('id', $id);
$this->loadModel('Event');
$this->set('distributionLevels', $this->Event->distributionLevels);
$this->render('view');
}
public function index($filter = null)
{
$this->set('menuData', array('menuList' => 'collections', 'menuItem' => 'index'));
$params = [
'filters' => ['Collection.uuid', 'Collection.type', 'Collection.name'],
'quickFilters' => ['Collection.name'],
'contain' => ['Orgc'],
'afterFind' => function($collections) {
foreach ($collections as $k => $collection) {
$collections[$k]['Collection']['element_count'] = $this->Collection->CollectionElement->find('count', [
'recursive' => -1,
'conditions' => ['CollectionElement.collection_id' => $collection['Collection']['id']]
]);
}
return $collections;
}
];
if ($filter === 'my_collections') {
$params['conditions']['Collection.user_id'] = $this->Auth->user('id');
}
if ($filter === 'org_collections') {
$params['conditions']['Collection.orgc_id'] = $this->Auth->user('org_id');
}
$this->loadModel('Event');
$this->set('distributionLevels', $this->Event->distributionLevels);
$this->CRUD->index($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
}

View File

@ -19,6 +19,25 @@ class ACLComponent extends Component
'queryACL' => array(),
'restSearch' => array('*'),
),
'analystData' => [
'add' => ['AND' => ['perm_add', 'perm_analyst_data']],
'delete' => ['AND' => ['perm_add', 'perm_analyst_data']],
'edit' => ['AND' => ['perm_add', 'perm_analyst_data']],
'filterAnalystDataForPush' => ['perm_sync'],
'getChildren' => ['*'],
'getRelatedElement' => ['*'],
'index' => ['*'],
'indexMinimal' => ['*'],
'pushAnalystData' => ['perm_sync'],
'view' => ['*'],
],
'analystDataBlocklists' => array(
'add' => array(),
'delete' => array(),
'edit' => array(),
'index' => array(),
'massDelete' => array(),
),
'api' => [
'rest' => ['perm_auth'],
'viewDeprecatedFunctionUse' => [],
@ -89,6 +108,19 @@ class ACLComponent extends Component
'pull_sgs' => [],
'view' => []
],
'collections' => [
'add' => ['perm_modify'],
'delete' => ['perm_modify'],
'edit' => ['perm_modify'],
'index' => ['*'],
'view' => ['*']
],
'collectionElements' => [
'add' => ['perm_modify'],
'addElementToCollection' => ['perm_modify'],
'delete' => ['perm_modify'],
'index' => ['*']
],
'correlationExclusions' => [
'add' => [],
'edit' => [],
@ -1079,6 +1111,27 @@ class ACLComponent extends Component
return $cluster['GalaxyCluster']['orgc_id'] == $user['org_id'];
}
/**
* Checks if user can modify given analyst data
*
* @param array $user
* @param array $analystData
* @return bool
*/
public function canEditAnalystData(array $user, array $analystData, $modelType): bool
{
if (!isset($analystData[$modelType])) {
throw new InvalidArgumentException('Passed object does not contain a(n) ' . $modelType);
}
if ($user['Role']['perm_site_admin']) {
return true;
}
if ($analystData[$modelType]['orgc_uuid'] == $user['Organisation']['uuid']) {
return true;
}
return false;
}
/**
* Checks if user can publish given galaxy cluster
*

View File

@ -25,6 +25,7 @@ class CRUDComponent extends Component
}
$options['filters'][] = 'quickFilter';
}
$this->Controller->{$this->Controller->modelClass}->includeAnalystData = true;
$params = $this->Controller->IndexFilter->harvestParameters(empty($options['filters']) ? [] : $options['filters']);
$query = [];
$query = $this->setFilters($params, $query);
@ -39,6 +40,7 @@ class CRUDComponent extends Component
if (!empty($this->Controller->paginate['fields'])) {
$query['fields'] = $this->Controller->paginate['fields'];
}
$query['includeAnalystData'] = true;
$data = $this->Controller->{$this->Controller->modelClass}->find('all', $query);
if (isset($options['afterFind'])) {
if (is_callable($options['afterFind'])) {
@ -49,6 +51,7 @@ class CRUDComponent extends Component
}
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
} else {
$query['includeAnalystData'] = true;
$this->Controller->paginate = $query;
$data = $this->Controller->paginate();
if (isset($options['afterFind'])) {
@ -93,7 +96,7 @@ class CRUDComponent extends Component
$savedData = $model->save($data);
if ($savedData) {
if (isset($params['afterSave'])) {
$params['afterSave']($data);
$params['afterSave']($savedData);
}
$data = $model->find('first', [
'recursive' => -1,
@ -200,7 +203,7 @@ class CRUDComponent extends Component
if (isset($params['beforeSave'])) {
$data = $params['beforeSave']($data);
}
if ($model->save($data)) {
if ($data = $model->save($data)) {
if (isset($params['afterSave'])) {
$params['afterSave']($data);
}
@ -231,6 +234,8 @@ class CRUDComponent extends Component
if (empty($id)) {
throw new NotFoundException(__('Invalid %s.', $modelName));
}
$this->Controller->{$modelName}->includeAnalystData = true;
$this->Controller->{$modelName}->includeAnalystDataRecursive = true;
$query = [
'recursive' => -1,
'conditions' => [$modelName . '.id' => $id],
@ -297,6 +302,9 @@ class CRUDComponent extends Component
$result = $this->Controller->{$modelName}->delete($id);
}
if ($result) {
if (isset($params['afterDelete']) && is_callable($params['afterDelete'])) {
$params['afterDelete']($data);
}
$message = __('%s deleted.', $modelName);
if ($this->Controller->IndexFilter->isRest()) {
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveSuccessResponse($modelName, 'delete', $id, 'json', $message);
@ -336,7 +344,9 @@ class CRUDComponent extends Component
if ($filter === 'quickFilter') {
continue;
}
if (strlen(trim($filterValue, '%')) === strlen($filterValue)) {
if (is_array($filterValue)) {
$query['conditions']['AND'][] = [$filter => $filterValue];
} else if (strlen(trim($filterValue, '%')) === strlen($filterValue)) {
$query['conditions']['AND'][] = [$filter => $filterValue];
} else {
$query['conditions']['AND'][] = [$filter . ' LIKE' => $filterValue];

View File

@ -213,6 +213,7 @@ class RestResponseComponent extends Component
'perm_tag_editor',
'default_role',
'perm_sighting',
'perm_analyst_data',
'permission'
)
),
@ -234,6 +235,7 @@ class RestResponseComponent extends Component
'perm_tag_editor',
'default_role',
'perm_sighting',
'perm_analyst_data',
'permission'
)
)
@ -1557,6 +1559,11 @@ class RestResponseComponent extends Component
'type' => 'integer',
'values' => array(1 => 'True', 0 => 'False')
),
'perm_analyst_data' => array(
'input' => 'radio',
'type' => 'integer',
'values' => array(1 => 'True', 0 => 'False')
),
'permission' => array(
'input' => 'select',
'type' => 'string',

View File

@ -124,6 +124,7 @@ class RestSearchComponent extends Component
'extended',
'extensionList',
'excludeGalaxy',
'includeAnalystData',
'includeRelatedTags',
'includeDecayScore',
'includeScoresOnEvent',

View File

@ -61,6 +61,8 @@ class EventReportsController extends AppController
public function view($reportId, $ajax=false)
{
$this->EventReport->includeAnalystData = true;
$this->EventReport->includeAnalystDataRecursive = true;
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $reportId);
if ($this->_isRest()) {
return $this->RestResponse->viewData($report, $this->response->type());
@ -175,6 +177,7 @@ class EventReportsController extends AppController
$filters = $this->IndexFilter->harvestParameters(['event_id', 'value', 'context', 'index_for_event', 'extended_event']);
$filters['embedded_view'] = $this->request->is('ajax');
$compiledConditions = $this->__generateIndexConditions($filters);
$this->EventReport->includeAnalystData = true;
if ($this->_isRest()) {
$reports = $this->EventReport->find('all', [
'recursive' => -1,
@ -515,6 +518,7 @@ class EventReportsController extends AppController
{
$distributionLevels = $this->EventReport->Event->Attribute->distributionLevels;
$this->set('distributionLevels', $distributionLevels);
$this->set('shortDist', $this->EventReport->Event->Attribute->shortDist);
$this->set('initialDistribution', $this->EventReport->Event->Attribute->defaultDistribution());
}

View File

@ -755,7 +755,8 @@ class EventsController extends AppController
if ($nothing) {
$this->paginate['conditions']['AND'][] = ['Event.id' => -1]; // do not fetch any event
}
$this->Event->includeAnalystData = true;
$this->paginate['includeAnalystData'] = true;
$events = $this->paginate();
if (count($events) === 1 && isset($this->passedArgs['searchall'])) {
@ -811,6 +812,7 @@ class EventsController extends AppController
$rules = [
'contain' => ['EventTag'],
'fields' => array_keys($fieldNames),
'includeAnalystData' => isset($passedArgs['includeAnalystData']) ? $passedArgs['includeAnalystData'] : true,
];
}
if (isset($passedArgs['sort']) && isset($fieldNames[$passedArgs['sort']])) {
@ -1227,6 +1229,9 @@ class EventsController extends AppController
}
}
$this->Event->Attribute->includeAnalystData = true;
$this->Event->Attribute->includeAnalystDataRecursive = true;
if (isset($filters['focus'])) {
$this->set('focus', $filters['focus']);
}
@ -1691,7 +1696,7 @@ class EventsController extends AppController
}
$namedParams = $this->request->params['named'];
$conditions['includeAnalystData'] = true;
if ($this->_isRest()) {
$conditions['includeAttachments'] = isset($namedParams['includeAttachments']) ? $namedParams['includeAttachments'] : true;
} else {
@ -1786,7 +1791,6 @@ class EventsController extends AppController
} else {
$user = $this->Auth->user();
}
$results = $this->Event->fetchEvent($user, $conditions);
if (empty($results)) {
throw new NotFoundException(__('Invalid event'));
@ -2404,14 +2408,14 @@ class EventsController extends AppController
}
if (isset($this->params['named']['distribution'])) {
$distribution = intval($this->params['named']['distribution']);
if (array_key_exists($distribution, $distributionLevels)) {
$initialDistribution = $distribution;
} else {
if (!array_key_exists($distribution, $distributionLevels)) {
throw new MethodNotAllowedException(__('Wrong distribution level'));
}
} else {
$distribution = $initialDistribution;
}
$sharingGroupId = null;
if ($initialDistribution == 4) {
if ($distribution == 4) {
if (!isset($this->params['named']['sharing_group_id'])) {
throw new MethodNotAllowedException(__('The sharing group id is needed when the distribution is set to 4 ("Sharing group").'));
}
@ -2420,8 +2424,25 @@ class EventsController extends AppController
throw new MethodNotAllowedException(__('Please select a valid sharing group id.'));
}
}
$clusterDistribution = $initialDistribution;
$clusterSharingGroupId = null;
if (isset($this->params['named']['galaxies_as_tags'])) {
$galaxies_as_tags = $this->params['named']['galaxies_as_tags'];
if (isset($this->params['name']['cluster_distribution'])) {
$clusterDistribution = intval($this->params['named']['cluster_distribution']);
if (!array_key_exists($clusterDistribution, $distributionLevels)) {
throw new MethodNotAllowedException(__('Wrong cluster distribution level'));
}
if ($clusterDistribution == 4) {
if (!isset($this->params['named']['cluster_sharing_group_id'])) {
throw new MethodNotAllowedException(__('The cluster sharing group id is needed when the cluster distribution is set to 4 ("Sharing group").'));
}
$clusterSharingGroupId = intval($this->params['named']['cluster_sharing_group_id']);
if (!array_key_exists($clusterSharingGroupId, $sgs)) {
throw new MethodNotAllowedException(__('Please select a valid cluster sharing group id.'));
}
}
}
}
if (isset($this->params['named']['debugging'])) {
$debug = $this->params['named']['debugging'];
@ -2433,9 +2454,11 @@ class EventsController extends AppController
$stix_version,
'uploaded_stix_file.' . ($stix_version == '1' ? 'xml' : 'json'),
$publish,
$initialDistribution,
$distribution,
$sharingGroupId,
$galaxies_as_tags,
$clusterDistribution,
$clusterSharingGroupId,
$debug
);
if (is_numeric($result)) {
@ -2467,6 +2490,8 @@ class EventsController extends AppController
$this->data['Event']['distribution'],
$this->data['Event']['sharing_group_id'] ?? null,
$this->data['Event']['galaxies_handling'],
$this->data['Event']['cluster_distribution'],
$this->data['Event']['cluster_sharing_group_id'] ?? null,
$debug
);
if (is_numeric($result)) {
@ -2692,7 +2717,7 @@ class EventsController extends AppController
$this->request->data = $this->request->data['Event'];
}
$eventToSave = $event;
$capturedObjects = ['Attribute', 'Object', 'Tag', 'Galaxy', 'EventReport'];
$capturedObjects = ['Attribute', 'Object', 'Tag', 'Galaxy', 'EventReport', 'Note', 'Opinion', 'Relationship',];
foreach ($capturedObjects as $objectType) {
if (!empty($this->request->data[$objectType])) {
if (!empty($regenerateUUIDs)) {
@ -4373,12 +4398,12 @@ class EventsController extends AppController
$id = $event['Event']['id'];
$exports = array(
'json' => array(
'url' => $this->baseurl . '/events/restSearch/json/eventid:' . $id . '.json',
'url' => $this->baseurl . '/events/restSearch/json/includeAnalystData:1/eventid:' . $id . '.json',
'text' => __('MISP JSON (metadata + all attributes)'),
'requiresPublished' => false,
'checkbox' => true,
'checkbox_text' => __('Encode Attachments'),
'checkbox_set' => $this->baseurl . '/events/restSearch/json/withAttachments:1/eventid:' . $id . '.json',
'checkbox_set' => $this->baseurl . '/events/restSearch/json/withAttachments:1/includeAnalystData:1/eventid:' . $id . '.json',
'checkbox_default' => true,
),
'xml' => array(

View File

@ -173,6 +173,8 @@ class GalaxyClustersController extends AppController
*/
public function view($id)
{
$this->GalaxyCluster->includeAnalystData = true;
$this->GalaxyCluster->includeAnalystDataRecursive = true;
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', $throwErrors=true, $full=true);
$tag = $this->GalaxyCluster->Tag->find('first', array(
'conditions' => array(
@ -208,6 +210,7 @@ class GalaxyClustersController extends AppController
$this->loadModel('Attribute');
$distributionLevels = $this->Attribute->distributionLevels;
$this->set('distributionLevels', $distributionLevels);
$this->set('shortDist', $this->Attribute->shortDist);
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'));
}

View File

@ -481,8 +481,8 @@ class OrganisationsController extends AppController
$extension = pathinfo($logo['name'], PATHINFO_EXTENSION);
$filename = $orgId . '.' . ($extension === 'svg' ? 'svg' : 'png');
if ($logo['size'] > 250*1024) {
$this->Flash->error(__('This organisation logo is too large, maximum file size allowed is 250kB.'));
if ($logo['size'] > 250 * 1024) {
$this->Flash->error(__('This organisation logo is too large, maximum file size allowed is 250 kB.'));
return false;
}
@ -492,10 +492,12 @@ class OrganisationsController extends AppController
}
$imgMime = mime_content_type($logo['tmp_name']);
if ($extension === 'png' && !exif_imagetype($logo['tmp_name'])) {
if ($extension === 'png' && (function_exists('exif_imagetype') && !exif_imagetype($logo['tmp_name']))) {
$this->Flash->error(__('This is not a valid PNG image.'));
return false;
} else if ($extension === 'svg' && !($imgMime === 'image/svg+xml' || $imgMime === 'image/svg')) {
}
if ($extension === 'svg' && !($imgMime === 'image/svg+xml' || $imgMime === 'image/svg')) {
$this->Flash->error(__('This is not a valid SVG image.'));
return false;
}

View File

@ -529,7 +529,7 @@ class ServersController extends AppController
if (!$fail) {
// say what fields are to be updated
$fieldList = array('id', 'url', 'push', 'pull', 'push_sightings', 'push_galaxy_clusters', 'pull_galaxy_clusters', 'caching_enabled', 'unpublish_event', 'publish_without_email', 'remote_org_id', 'name' ,'self_signed', 'remove_missing_tags', 'cert_file', 'client_cert_file', 'push_rules', 'pull_rules', 'internal', 'skip_proxy');
$fieldList = array('id', 'url', 'push', 'pull', 'push_sightings', 'push_galaxy_clusters', 'pull_galaxy_clusters', 'push_analyst_data', 'pull_analyst_data', 'caching_enabled', 'unpublish_event', 'publish_without_email', 'remote_org_id', 'name' ,'self_signed', 'remove_missing_tags', 'cert_file', 'client_cert_file', 'push_rules', 'pull_rules', 'internal', 'skip_proxy');
$this->request->data['Server']['id'] = $id;
if (isset($this->request->data['Server']['authkey']) && "" != $this->request->data['Server']['authkey']) {
$fieldList[] = 'authkey';
@ -776,7 +776,7 @@ class ServersController extends AppController
if (!Configure::read('MISP.background_jobs')) {
$result = $this->Server->pull($this->Auth->user(), $technique, $s);
if (is_array($result)) {
$success = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4]);
$success = __('Pull completed. %s events pulled, %s events could not be pulled, %s proposals pulled, %s sightings pulled, %s clusters pulled, %s analyst data pulled.', count($result[0]), count($result[1]), $result[2], $result[3], $result[4], $result[5]);
} else {
$error = $result;
}
@ -784,6 +784,7 @@ class ServersController extends AppController
$this->set('fails', $result[1]);
$this->set('pulledProposals', $result[2]);
$this->set('pulledSightings', $result[3]);
$this->set('pulledAnalystData', $result[5]);
} else {
$this->loadModel('Job');
$jobId = $this->Job->createJob(
@ -1889,6 +1890,7 @@ class ServersController extends AppController
'perm_sync' => (bool) $user['Role']['perm_sync'],
'perm_sighting' => (bool) $user['Role']['perm_sighting'],
'perm_galaxy_editor' => (bool) $user['Role']['perm_galaxy_editor'],
'perm_analyst_data' => (bool) $user['Role']['perm_analyst_data'],
'uuid' => $user['Role']['perm_sync'] ? Configure::read('MISP.uuid') : '-',
'request_encoding' => $this->CompressedRequestHandler->supportedEncodings(),
'filter_sightings' => true, // check if Sightings::filterSightingUuidsForPush method is supported

View File

@ -37,6 +37,10 @@ class MispAdminSyncTestWidget
$colour = 'orange';
$message .= ' ' . __('No sighting access.');
}
if (empty($result['info']['perm_analyst_data'])) {
$colour = 'orange';
$message .= ' ' . __('No analyst data sync access.');
}
} else {
$colour = 'red';
$message = $syncTestErrorCodes[$result['status']];

View File

@ -21,7 +21,7 @@ class JSONConverterTool
public static function convertObject($object, $isSiteAdmin = false, $raw = false)
{
$toRearrange = array('SharingGroup', 'Attribute', 'ShadowAttribute', 'Event', 'CryptographicKey');
$toRearrange = array('SharingGroup', 'Attribute', 'ShadowAttribute', 'Event', 'CryptographicKey', 'Note', 'Opinion', 'Relationship');
foreach ($toRearrange as $element) {
if (isset($object[$element])) {
$object['Object'][$element] = $object[$element];
@ -40,7 +40,7 @@ class JSONConverterTool
public static function convert($event, $isSiteAdmin=false, $raw = false)
{
$toRearrange = array('Org', 'Orgc', 'SharingGroup', 'Attribute', 'ShadowAttribute', 'RelatedAttribute', 'RelatedEvent', 'Galaxy', 'Object', 'EventReport', 'CryptographicKey');
$toRearrange = array('Org', 'Orgc', 'SharingGroup', 'Attribute', 'ShadowAttribute', 'RelatedAttribute', 'RelatedEvent', 'Galaxy', 'Object', 'EventReport', 'CryptographicKey', 'Note', 'Opinion', 'Relationship');
foreach ($toRearrange as $object) {
if (isset($event[$object])) {
$event['Event'][$object] = $event[$object];
@ -69,6 +69,16 @@ class JSONConverterTool
}
}
if (isset($event['Event']['Note'])) {
$event['Event']['Note'] = self::__cleanAnalystData($event['Event']['Note']);
}
if (isset($event['Event']['Opinion'])) {
$event['Event']['Opinion'] = self::__cleanAnalystData($event['Event']['Opinion']);
}
if (isset($event['Event']['Relationship'])) {
$event['Event']['Relationship'] = self::__cleanAnalystData($event['Event']['Relationship']);
}
// cleanup the array from things we do not want to expose
$tempSightings = array();
if (!empty($event['Sighting'])) {
@ -209,6 +219,17 @@ class JSONConverterTool
return $objects;
}
private function __cleanAnalystData($data)
{
foreach ($data as $k => $entry) {
if (empty($entry['SharingGroup'])) {
unset($data[$k]['SharingGroup']);
}
}
$data = array_values($data);
return $data;
}
public static function arrayPrinter($array, $root = true)
{
if (is_array($array)) {

View File

@ -0,0 +1,245 @@
<?php
declare(strict_types=1);
class LanguageRFC5646Tool
{
const RFC5646_LANGUAGE = [
'af' => 'Afrikaans',
'af-ZA' => 'Afrikaans (South Africa)',
'ar' => 'Arabic',
'ar-AE' => 'Arabic (U.A.E.)',
'ar-BH' => 'Arabic (Bahrain)',
'ar-DZ' => 'Arabic (Algeria)',
'ar-EG' => 'Arabic (Egypt)',
'ar-IQ' => 'Arabic (Iraq)',
'ar-JO' => 'Arabic (Jordan)',
'ar-KW' => 'Arabic (Kuwait)',
'ar-LB' => 'Arabic (Lebanon)',
'ar-LY' => 'Arabic (Libya)',
'ar-MA' => 'Arabic (Morocco)',
'ar-OM' => 'Arabic (Oman)',
'ar-QA' => 'Arabic (Qatar)',
'ar-SA' => 'Arabic (Saudi Arabia)',
'ar-SY' => 'Arabic (Syria)',
'ar-TN' => 'Arabic (Tunisia)',
'ar-YE' => 'Arabic (Yemen)',
'az' => 'Azeri (Latin)',
'az-AZ' => 'Azeri (Latin) (Azerbaijan)',
'az-Cyrl-AZ' => 'Azeri (Cyrillic) (Azerbaijan)',
'be' => 'Belarusian',
'be-BY' => 'Belarusian (Belarus)',
'bg' => 'Bulgarian',
'bg-BG' => 'Bulgarian (Bulgaria)',
'bs-BA' => 'Bosnian (Bosnia and Herzegovina)',
'ca' => 'Catalan',
'ca-ES' => 'Catalan (Spain)',
'cs' => 'Czech',
'cs-CZ' => 'Czech (Czech Republic)',
'cy' => 'Welsh',
'cy-GB' => 'Welsh (United Kingdom)',
'da' => 'Danish',
'da-DK' => 'Danish (Denmark)',
'de' => 'German',
'de-AT' => 'German (Austria)',
'de-CH' => 'German (Switzerland)',
'de-DE' => 'German (Germany)',
'de-LI' => 'German (Liechtenstein)',
'de-LU' => 'German (Luxembourg)',
'dv' => 'Divehi',
'dv-MV' => 'Divehi (Maldives)',
'el' => 'Greek',
'el-GR' => 'Greek (Greece)',
'en' => 'English',
'en-AU' => 'English (Australia)',
'en-BZ' => 'English (Belize)',
'en-CA' => 'English (Canada)',
'en-CB' => 'English (Caribbean)',
'en-GB' => 'English (United Kingdom)',
'en-IE' => 'English (Ireland)',
'en-JM' => 'English (Jamaica)',
'en-NZ' => 'English (New Zealand)',
'en-PH' => 'English (Republic of the Philippines)',
'en-TT' => 'English (Trinidad and Tobago)',
'en-US' => 'English (United States)',
'en-ZA' => 'English (South Africa)',
'en-ZW' => 'English (Zimbabwe)',
'eo' => 'Esperanto',
'es' => 'Spanish',
'es-AR' => 'Spanish (Argentina)',
'es-BO' => 'Spanish (Bolivia)',
'es-CL' => 'Spanish (Chile)',
'es-CO' => 'Spanish (Colombia)',
'es-CR' => 'Spanish (Costa Rica)',
'es-DO' => 'Spanish (Dominican Republic)',
'es-EC' => 'Spanish (Ecuador)',
'es-ES' => 'Spanish (Spain)',
'es-GT' => 'Spanish (Guatemala)',
'es-HN' => 'Spanish (Honduras)',
'es-MX' => 'Spanish (Mexico)',
'es-NI' => 'Spanish (Nicaragua)',
'es-PA' => 'Spanish (Panama)',
'es-PE' => 'Spanish (Peru)',
'es-PR' => 'Spanish (Puerto Rico)',
'es-PY' => 'Spanish (Paraguay)',
'es-SV' => 'Spanish (El Salvador)',
'es-UY' => 'Spanish (Uruguay)',
'es-VE' => 'Spanish (Venezuela)',
'et' => 'Estonian',
'et-EE' => 'Estonian (Estonia)',
'eu' => 'Basque',
'eu-ES' => 'Basque (Spain)',
'fa' => 'Farsi',
'fa-IR' => 'Farsi (Iran)',
'fi' => 'Finnish',
'fi-FI' => 'Finnish (Finland)',
'fo' => 'Faroese',
'fo-FO' => 'Faroese (Faroe Islands)',
'fr' => 'French',
'fr-BE' => 'French (Belgium)',
'fr-CA' => 'French (Canada)',
'fr-CH' => 'French (Switzerland)',
'fr-FR' => 'French (France)',
'fr-LU' => 'French (Luxembourg)',
'fr-MC' => 'French (Principality of Monaco)',
'gl' => 'Galician',
'gl-ES' => 'Galician (Spain)',
'gu' => 'Gujarati',
'gu-IN' => 'Gujarati (India)',
'he' => 'Hebrew',
'he-IL' => 'Hebrew (Israel)',
'hi' => 'Hindi',
'hi-IN' => 'Hindi (India)',
'hr' => 'Croatian',
'hr-BA' => 'Croatian (Bosnia and Herzegovina)',
'hr-HR' => 'Croatian (Croatia)',
'hu' => 'Hungarian',
'hu-HU' => 'Hungarian (Hungary)',
'hy' => 'Armenian',
'hy-AM' => 'Armenian (Armenia)',
'id' => 'Indonesian',
'id-ID' => 'Indonesian (Indonesia)',
'is' => 'Icelandic',
'is-IS' => 'Icelandic (Iceland)',
'it' => 'Italian',
'it-CH' => 'Italian (Switzerland)',
'it-IT' => 'Italian (Italy)',
'ja' => 'Japanese',
'ja-JP' => 'Japanese (Japan)',
'ka' => 'Georgian',
'ka-GE' => 'Georgian (Georgia)',
'kk' => 'Kazakh',
'kk-KZ' => 'Kazakh (Kazakhstan)',
'kn' => 'Kannada',
'kn-IN' => 'Kannada (India)',
'ko' => 'Korean',
'ko-KR' => 'Korean (Korea)',
'kok' => 'Konkani',
'kok-IN' => 'Konkani (India)',
'ky' => 'Kyrgyz',
'ky-KG' => 'Kyrgyz (Kyrgyzstan)',
'lt' => 'Lithuanian',
'lt-LT' => 'Lithuanian (Lithuania)',
'lv' => 'Latvian',
'lv-LV' => 'Latvian (Latvia)',
'mi' => 'Maori',
'mi-NZ' => 'Maori (New Zealand)',
'mk' => 'FYRO Macedonian',
'mk-MK' => 'FYRO Macedonian (Former Yugoslav Republic of Macedonia)',
'mn' => 'Mongolian',
'mn-MN' => 'Mongolian (Mongolia)',
'mr' => 'Marathi',
'mr-IN' => 'Marathi (India)',
'ms' => 'Malay',
'ms-BN' => 'Malay (Brunei Darussalam)',
'ms-MY' => 'Malay (Malaysia)',
'mt' => 'Maltese',
'mt-MT' => 'Maltese (Malta)',
'nb' => 'Norwegian (Bokm?l)',
'nb-NO' => 'Norwegian (Bokm?l) (Norway)',
'nl' => 'Dutch',
'nl-BE' => 'Dutch (Belgium)',
'nl-NL' => 'Dutch (Netherlands)',
'nn-NO' => 'Norwegian (Nynorsk) (Norway)',
'ns' => 'Northern Sotho',
'ns-ZA' => 'Northern Sotho (South Africa)',
'pa' => 'Punjabi',
'pa-IN' => 'Punjabi (India)',
'pl' => 'Polish',
'pl-PL' => 'Polish (Poland)',
'ps' => 'Pashto',
'ps-AR' => 'Pashto (Afghanistan)',
'pt' => 'Portuguese',
'pt-BR' => 'Portuguese (Brazil)',
'pt-PT' => 'Portuguese (Portugal)',
'qu' => 'Quechua',
'qu-BO' => 'Quechua (Bolivia)',
'qu-EC' => 'Quechua (Ecuador)',
'qu-PE' => 'Quechua (Peru)',
'ro' => 'Romanian',
'ro-RO' => 'Romanian (Romania)',
'ru' => 'Russian',
'ru-RU' => 'Russian (Russia)',
'sa' => 'Sanskrit',
'sa-IN' => 'Sanskrit (India)',
'se' => 'Sami',
'se-FI' => 'Sami (Finland)',
'se-NO' => 'Sami (Norway)',
'se-SE' => 'Sami (Sweden)',
'sk' => 'Slovak',
'sk-SK' => 'Slovak (Slovakia)',
'sl' => 'Slovenian',
'sl-SI' => 'Slovenian (Slovenia)',
'sq' => 'Albanian',
'sq-AL' => 'Albanian (Albania)',
'sr-BA' => 'Serbian (Latin) (Bosnia and Herzegovina)',
'sr-Cyrl-BA' => 'Serbian (Cyrillic) (Bosnia and Herzegovina)',
'sr-SP' => 'Serbian (Latin) (Serbia and Montenegro)',
'sr-Cyrl-SP' => 'Serbian (Cyrillic) (Serbia and Montenegro)',
'sv' => 'Swedish',
'sv-FI' => 'Swedish (Finland)',
'sv-SE' => 'Swedish (Sweden)',
'sw' => 'Swahili',
'sw-KE' => 'Swahili (Kenya)',
'syr' => 'Syriac',
'syr-SY' => 'Syriac (Syria)',
'ta' => 'Tamil',
'ta-IN' => 'Tamil (India)',
'te' => 'Telugu',
'te-IN' => 'Telugu (India)',
'th' => 'Thai',
'th-TH' => 'Thai (Thailand)',
'tl' => 'Tagalog',
'tl-PH' => 'Tagalog (Philippines)',
'tn' => 'Tswana',
'tn-ZA' => 'Tswana (South Africa)',
'tr' => 'Turkish',
'tr-TR' => 'Turkish (Turkey)',
'tt' => 'Tatar',
'tt-RU' => 'Tatar (Russia)',
'ts' => 'Tsonga',
'uk' => 'Ukrainian',
'uk-UA' => 'Ukrainian (Ukraine)',
'ur' => 'Urdu',
'ur-PK' => 'Urdu (Islamic Republic of Pakistan)',
'uz' => 'Uzbek (Latin)',
'uz-UZ' => 'Uzbek (Latin) (Uzbekistan)',
'uz-Cyrl-UZ' => 'Uzbek (Cyrillic) (Uzbekistan)',
'vi' => 'Vietnamese',
'vi-VN' => 'Vietnamese (Viet Nam)',
'xh' => 'Xhosa',
'xh-ZA' => 'Xhosa (South Africa)',
'zh' => 'Chinese',
'zh-CN' => 'Chinese (S)',
'zh-HK' => 'Chinese (Hong Kong)',
'zh-MO' => 'Chinese (Macau)',
'zh-SG' => 'Chinese (Singapore)',
'zh-TW' => 'Chinese (T)',
'zu' => 'Zulu',
'zu-ZA' => 'Zulu (South Africa)'
];
public static function getLanguages()
{
return self::RFC5646_LANGUAGE;
}
}

View File

@ -14,6 +14,7 @@ class ServerSyncTool
FEATURE_EDIT_OF_GALAXY_CLUSTER = 'edit_of_galaxy_cluster',
PERM_SYNC = 'perm_sync',
PERM_GALAXY_EDITOR = 'perm_galaxy_editor',
PERM_ANALYST_DATA = 'perm_analyst_data',
FEATURE_SIGHTING_REST_SEARCH = 'sighting_rest';
/** @var array */
@ -215,6 +216,71 @@ class ServerSyncTool
return $this->post('/galaxies/pushCluster', [$cluster], $logMessage);
}
/**
* @param array $rules
* @return HttpSocketResponseExtended
* @throws HttpSocketHttpException
* @throws HttpSocketJsonException
*/
public function filterAnalystDataForPush(array $candidates)
{
if (!$this->isSupported(self::PERM_ANALYST_DATA)) {
return [];
}
return $this->post('/analyst_data/filterAnalystDataForPush', $candidates);
}
/**
* @param array $rules
* @return HttpSocketResponseExtended
* @throws HttpSocketHttpException
* @throws HttpSocketJsonException
*/
public function fetchIndexMinimal(array $rules)
{
if (!$this->isSupported(self::PERM_ANALYST_DATA)) {
return [];
}
return $this->post('/analyst_data/indexMinimal', $rules);
}
/**
* @throws HttpSocketJsonException
* @throws HttpSocketHttpException
*/
public function fetchAnalystData($type, array $uuids)
{
if (!$this->isSupported(self::PERM_ANALYST_DATA)) {
return [];
}
$params = [
'uuid' => $uuids,
];
$url = '/analyst_data/index/' . $type;
$url .= $this->createParams($params);
$url .= '.json';
return $this->get($url);
// $response = $this->post('/analyst_data/restSearch' , $params);
// return $response->json();
}
/**
* @param array $analystData
* @return HttpSocketResponseExtended
* @throws HttpSocketHttpException
* @throws HttpSocketJsonException
*/
public function pushAnalystData($type, array $analystData)
{
$logMessage = "Pushing Analyst Data #{$analystData[$type]['uuid']} to Server #{$this->serverId()}";
return $this->post('/analyst_data/pushAnalystData', $analystData, $logMessage);
}
/**
* @param array $params
* @return HttpSocketResponseExtended
@ -414,6 +480,8 @@ class ServerSyncTool
return isset($info['perm_sync']) && $info['perm_sync'];
case self::PERM_GALAXY_EDITOR:
return isset($info['perm_galaxy_editor']) && $info['perm_galaxy_editor'];
case self::PERM_ANALYST_DATA:
return isset($info['perm_analyst_data']) && $info['perm_analyst_data'];
case self::FEATURE_SIGHTING_REST_SEARCH:
$version = explode('.', $info['version']);
return $version[0] == 2 && $version[1] == 4 && $version[2] > 164;

1129
app/Model/AnalystData.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
<?php
App::uses('AppModel', 'Model');
class AnalystDataBlocklist extends AppModel
{
public $useTable = 'analyst_data_blocklists';
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full'
),
'Containable',
);
public $blocklistFields = array('analyst_data_uuid', 'comment', 'analyst_data_info', 'analyst_data_orgc');
public $blocklistTarget = 'analyst_data';
public $validate = array(
'analyst_data_uuid' => array(
'unique' => array(
'rule' => 'isUnique',
'message' => 'Analyst Data already blocklisted.'
),
'uuid' => array(
'rule' => array('uuid'),
'message' => 'Please provide a valid UUID'
),
)
);
public function beforeValidate($options = array())
{
parent::beforeValidate();
if (empty($this->data['AnalystDataBlocklist']['id'])) {
$this->data['AnalystDataBlocklist']['date_created'] = date('Y-m-d H:i:s');
}
if (empty($this->data['AnalystDataBlocklist']['comment'])) {
$this->data['AnalystDataBlocklist']['comment'] = '';
}
return true;
}
/**
* @param string $analystDataUUID
* @return bool
*/
public function checkIfBlocked($analystDataUUID)
{
return $this->hasAny([
'analyst_data_uuid' => $analystDataUUID,
]);
}
}

View File

@ -48,6 +48,9 @@ class AppModel extends Model
/** @var Workflow|null */
private $Workflow;
public $includeAnalystData;
public $includeAnalystDataRecursive;
// deprecated, use $db_changes
// major -> minor -> hotfix -> requires_logout
const OLD_DB_CHANGES = array(
@ -87,7 +90,8 @@ class AppModel extends Model
99 => false, 100 => false, 101 => false, 102 => false, 103 => false, 104 => false,
105 => false, 106 => false, 107 => false, 108 => false, 109 => false, 110 => false,
111 => false, 112 => false, 113 => true, 114 => false, 115 => false, 116 => false,
117 => false, 118 => false, 119 => false, 120 => false
117 => false, 118 => false, 119 => false, 120 => false, 121 => false, 122 => false,
123 => false,
);
const ADVANCED_UPDATES_DESCRIPTION = array(
@ -2013,6 +2017,153 @@ class AppModel extends Model
case 119:
$sqlArray[] = "ALTER TABLE `access_logs` MODIFY `action` varchar(191) NOT NULL";
break;
case 121:
$sqlArray[] = "CREATE TABLE `notes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`authors` text,
`org_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`orgc_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
`distribution` tinyint(4) NOT NULL,
`sharing_group_id` int(10) unsigned,
`locked` tinyint(1) NOT NULL DEFAULT 0,
`note` mediumtext,
`language` varchar(16) DEFAULT 'en',
PRIMARY KEY (`id`),
UNIQUE KEY `uuid` (`uuid`),
KEY `object_uuid` (`object_uuid`),
KEY `object_type` (`object_type`),
KEY `org_uuid` (`org_uuid`),
KEY `orgc_uuid` (`orgc_uuid`),
KEY `distribution` (`distribution`),
KEY `sharing_group_id` (`sharing_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
$sqlArray[] = "CREATE TABLE `opinions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`authors` text,
`org_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`orgc_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
`distribution` tinyint(4) NOT NULL,
`sharing_group_id` int(10) unsigned,
`locked` tinyint(1) NOT NULL DEFAULT 0,
`opinion` int(10) unsigned,
`comment` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uuid` (`uuid`),
KEY `object_uuid` (`object_uuid`),
KEY `object_type` (`object_type`),
KEY `org_uuid` (`org_uuid`),
KEY `orgc_uuid` (`orgc_uuid`),
KEY `distribution` (`distribution`),
KEY `sharing_group_id` (`sharing_group_id`),
KEY `opinion` (`opinion`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
$sqlArray[] = "CREATE TABLE `relationships` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) CHARACTER SET ascii NOT NULL,
`object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`authors` text,
`org_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`orgc_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
`distribution` tinyint(4) NOT NULL,
`sharing_group_id` int(10) unsigned,
`locked` tinyint(1) NOT NULL DEFAULT 0,
`relationship_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci,
`related_object_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`related_object_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uuid` (`uuid`),
KEY `object_uuid` (`object_uuid`),
KEY `object_type` (`object_type`),
KEY `org_uuid` (`org_uuid`),
KEY `orgc_uuid` (`orgc_uuid`),
KEY `distribution` (`distribution`),
KEY `sharing_group_id` (`sharing_group_id`),
KEY `relationship_type` (`relationship_type`),
KEY `related_object_uuid` (`related_object_uuid`),
KEY `related_object_type` (`related_object_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `analyst_data_blocklists` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`analyst_data_uuid` varchar(40) COLLATE utf8_bin NOT NULL,
`created` datetime NOT NULL,
`analyst_data_info` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`comment` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci,
`analyst_data_orgc` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`),
KEY `analyst_data_uuid` (`analyst_data_uuid`),
KEY `analyst_data_orgc` (`analyst_data_orgc`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;";
$sqlArray[] = "ALTER TABLE `roles` ADD `perm_analyst_data` tinyint(1) NOT NULL DEFAULT 0;";
$sqlArray[] = "UPDATE `roles` SET `perm_analyst_data`=1 WHERE `perm_add` = 1;";
$sqlArray[] = "ALTER TABLE `servers` ADD `push_analyst_data` tinyint(1) NOT NULL DEFAULT 0 AFTER `push_galaxy_clusters`;";
$sqlArray[] = "ALTER TABLE `servers` ADD `pull_analyst_data` tinyint(1) NOT NULL DEFAULT 0 AFTER `push_analyst_data`;";
break;
case 122:
$sqlArray[] = "CREATE TABLE `collections` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`org_id` int(10) unsigned NOT NULL,
`orgc_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime ON UPDATE CURRENT_TIMESTAMP,
`distribution` tinyint(4) NOT NULL,
`sharing_group_id` int(10) unsigned,
`name` varchar(191) NOT NULL,
`type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`description` mediumtext,
PRIMARY KEY (`id`),
UNIQUE KEY `uuid` (`uuid`),
KEY `name` (`name`),
KEY `type` (`type`),
KEY `org_id` (`org_id`),
KEY `orgc_id` (`orgc_id`),
KEY `user_id` (`user_id`),
KEY `distribution` (`distribution`),
KEY `sharing_group_id` (`sharing_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
$sqlArray[] = "CREATE TABLE `collection_elements` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`element_uuid` varchar(40) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`element_type` varchar(80) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`collection_id` int(10) unsigned NOT NULL,
`description` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uuid` (`uuid`),
KEY `element_uuid` (`element_uuid`),
KEY `element_type` (`element_type`),
KEY `collection_id` (`collection_id`),
UNIQUE KEY `unique_element` (`element_uuid`, `collection_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
break;
case 123:
$sqlArray[] = 'ALTER TABLE `notes` MODIFY `created` datetime NOT NULL';
$sqlArray[] = 'ALTER TABLE `opinions` MODIFY `created` datetime NOT NULL;';
$sqlArray[] = 'ALTER TABLE `relationships` MODIFY `created` datetime NOT NULL;';
$sqlArray[] = 'ALTER TABLE `notes` MODIFY `modified` datetime NOT NULL;';
$sqlArray[] = 'ALTER TABLE `opinions` MODIFY `modified` datetime NOT NULL;';
$sqlArray[] = 'ALTER TABLE `relationships` MODIFY `modified` datetime NOT NULL;';
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
@ -4051,8 +4202,17 @@ class AppModel extends Model
if (!empty($query['order']) && $this->validOrderClause($query['order']) === false) {
throw new InvalidArgumentException('Invalid order clause');
}
return parent::find($type, $query);
$results = parent::find($type, $query);
if (!empty($query['includeAnalystData']) && $this->Behaviors->enabled('AnalystDataParent')) {
if ($type === 'first') {
$results[$this->alias] = array_merge($results[$this->alias], $this->attachAnalystData($results[$this->alias]));
} else if ($type === 'all') {
foreach ($results as $k => $result) {
$results[$k][$this->alias] = array_merge($results[$k][$this->alias], $this->attachAnalystData($results[$k][$this->alias]));
}
}
}
return $results;
}
private function validOrderClause($order)
@ -4087,7 +4247,6 @@ class AppModel extends Model
$newImageDir = APP . 'files/img';
$oldOrgDir = new Folder($oldImageDir . '/orgs');
$oldCustomDir = new Folder($oldImageDir . '/custom');
$result = false;
$result = $oldOrgDir->copy([
'from' => $oldImageDir . '/orgs',
'to' => $newImageDir . '/orgs',

View File

@ -36,7 +36,8 @@ class Attribute extends AppModel
'Trim',
'Containable',
'Regexp' => array('fields' => array('value')),
'LightPaginator'
'LightPaginator',
'AnalystDataParent',
);
public $displayField = 'value';
@ -2658,6 +2659,7 @@ class Attribute extends AppModel
if (!empty($attribute['Sighting'])) {
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
}
$this->Event->captureAnalystData($user, $attribute);
}
if (!empty($this->validationErrors)) {
$validationErrors = $this->validationErrors;
@ -2798,6 +2800,7 @@ class Attribute extends AppModel
if (!empty($attribute['Sighting'])) {
$this->Sighting->captureSightings($attribute['Sighting'], $attributeId, $eventId, $user);
}
$this->Event->captureAnalystData($user, $attribute);
if ($user['Role']['perm_tagger']) {
/*
We should unwrap the line below and remove the server option in the future once we have tag soft-delete

View File

@ -0,0 +1,49 @@
<?php
/**
* Common functions for the 3 analyst objects
*/
class AnalystDataBehavior extends ModelBehavior
{
public $SharingGroup;
private $__current_type = null;
public function setup(Model $Model, $settings = array()) {
// We want to know whether we're a Note, Opinion or Relationship
$this->__current_type = $Model->alias;
}
// Return the analystData of the current type for a given UUID (this only checks the ACL of the analystData, NOT of the parent.)
public function fetchForUuid(Model $Model, $uuid, $user = null)
{
$conditions = [
'object_uuid' => $uuid
];
$type = $Model->current_type;
if (empty($user['Role']['perm_site_admin'])) {
$validSharingGroups = $Model->SharingGroup->authorizedIds($user, true);
$conditions['AND'][] = [
'OR' => [
$type . '.orgc_uuid' => $user['Organisation']['uuid'],
$type . '.org_uuid' => $user['Organisation']['uuid'],
$type . '.distribution IN' => [1, 2, 3],
'AND' => [
$type . '.distribution' => 4,
$type . '.sharing_group_id IN' => $validSharingGroups
]
]
];
}
return $Model->find('all', [
'recursive' => -1,
'conditions' => $conditions,
'contain' => ['Org', 'Orgc', 'SharingGroup'],
]);
}
public function checkACL()
{
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Common functions for the 3 analyst objects
*/
class AnalystDataParentBehavior extends ModelBehavior
{
private $__currentUser = null;
public $User;
public function attachAnalystData(Model $model, array $object, array $types = ['Note', 'Opinion', 'Relationship'])
{
// No uuid, nothing to attach
if (empty($object['uuid'])) {
return $object;
}
if (empty($this->__currentUser)) {
$user_id = Configure::read('CurrentUserId');
$this->User = ClassRegistry::init('User');
if ($user_id) {
$this->__currentUser = $this->User->getAuthUser($user_id);
}
}
$data = [];
foreach ($types as $type) {
$this->{$type} = ClassRegistry::init($type);
$this->{$type}->fetchRecursive = !empty($model->includeAnalystDataRecursive);
$temp = $this->{$type}->fetchForUuid($object['uuid'], $this->__currentUser);
if (!empty($temp)) {
foreach ($temp as $k => $temp_element) {
$data[$type][] = $temp_element[$type];
}
}
}
return $data;
}
public function afterFind(Model $model, $results, $primary = false)
{
if (!empty($model->includeAnalystData)) {
foreach ($results as $k => $item) {
if (isset($item[$model->alias])) {
$results[$k] = array_merge($results[$k], $this->attachAnalystData($model, $item[$model->alias]));
}
}
}
return $results;
}
}

145
app/Model/Collection.php Normal file
View File

@ -0,0 +1,145 @@
<?php
App::uses('AppModel', 'Model');
class Collection extends AppModel
{
public $recursive = -1;
public $actsAs = array(
'Containable'
);
public $belongsTo = [
'Orgc' => array(
'className' => 'Organisation',
'foreignKey' => 'orgc_id',
'fields' => [
'Orgc.id',
'Orgc.uuid',
'Orgc.name'
]
),
'Org' => array(
'className' => 'Organisation',
'foreignKey' => 'org_id',
'fields' => [
'Org.id',
'Org.uuid',
'Org.name'
]
),
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'fields' => [
'User.id',
'User.email'
]
)
];
public $hasMany = [
'CollectionElement' => [
'dependent' => true
]
];
public $valid_targets = [
'Attribute',
'Event',
'GalaxyCluster',
'Galaxy',
'Object',
'Note',
'Opinion',
'Relationship',
'Organisation',
'SharingGroup'
];
public $current_user = null;
public function beforeValidate($options = array())
{
if (empty($this->data['Collection'])) {
$this->data = ['Collection' => $this->data];
}
if (empty($this->id) && empty($this->data['Collection']['uuid'])) {
$this->data['Collection']['uuid'] = CakeText::uuid();
}
if (empty($this->id)) {
$this->data['Collection']['user_id'] = $this->current_user['id'];
if (empty($this->data['Collection']['orgc_id']) || empty($this->current_user['Role']['perm_sync'])) {
$this->data['Collection']['orgc_id'] = $this->current_user['Organisation']['id'];
}
$this->data['Collection']['org_id'] = $this->current_user['Organisation']['id'];
$this->data['Collection']['user_id'] = $this->current_user['id'];
}
return true;
}
public function mayModify($user_id, $collection_id)
{
$user = $this->User->getAuthUser($user_id);
$collection = $this->find('first', [
'recursive' => -1,
'conditions' => ['Collection.id' => $collection_id]
]);
if ($user['Role']['perm_site_admin']) {
return true;
}
if (empty($user['Role']['perm_modify'])) {
return false;
}
if (!empty($user['Role']['perm_modify_org'])) {
if ($user['org_id'] == $collection['Collection']['Orgc_id']) {
return true;
}
if ($user['Role']['perm_sync'] && $user['org_id'] == $collection['Collection']['Org_id']) {
return true;
}
}
if (!empty($user['Role']['perm_modify']) && $user['id'] === $collection['Collection']['user_id']) {
}
}
public function mayView($user_id, $collection_id)
{
$user = $this->User->getAuthUser($user_id);
$collection = $this->find('first', [
'recursive' => -1,
'conditions' => ['Collection.id' => $collection_id]
]);
if ($user['Role']['perm_site_admin']) {
return true;
}
if ($collection['Collection']['org_id'] == $user('org_id')) {
return true;
}
if (in_array($collection['Collection']['distribution'], [1,2,3])) {
return true;
}
if ($collection['Collection']['distribution'] === 4) {
$SharingGroup = ClassRegistry::init('SharingGroup');
$sgs = $this->SharingGroup->fetchAllAuthorised($user, 'uuid');
if (isset($sgs[$collection['Collection']['sharing_group_id']])) {
return true;
} else {
return false;
}
}
return false;
}
public function rearrangeCollection(array $collection) {
foreach ($collection as $key => $elements) {
if ($key !== 'Collection') {
$collection['Collection'][$key] = $elements;
unset($collection[$key]);
}
}
return $collection;
}
}

View File

@ -0,0 +1,205 @@
<?php
App::uses('AppModel', 'Model');
class CollectionElement extends AppModel
{
public $recursive = -1;
public $actsAs = array(
'Containable'
);
public $belongsTo = array(
'Collection' => array(
'className' => 'Collection',
'foreignKey' => 'collection_id'
)
);
// Make sure you also update the validation for element_type to include anything you add here.
public $valid_types = [
'Event',
'GalaxyCluster'
];
public $validate = [
'collection_id' => [
'numeric' => [
'rule' => ['numeric']
]
],
'uuid' => [
'uuid' => [
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
]
],
'element_uuid' => [
'element_uuid' => [
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
]
],
'element_type' => [
'element_type' => [
'rule' => ['inList', ['Event', 'GalaxyCluster']],
'message' => 'Invalid object type.'
]
]
];
public function beforeValidate($options = array())
{
// Massage to a common format
if (empty($this->data['CollectionElement'])) {
$this->data = ['CollectionElement' => $this->data];
}
// if we're creating a new element, assign a uuid (unless provided)
if (empty($this->id) && empty($this->data['CollectionElement']['uuid'])) {
$this->data['CollectionElement']['uuid'] = CakeText::uuid();
}
if (
empty($this->id) &&
empty($this->data['CollectionElement']['element_type']) &&
!empty($this->data['CollectionElement']['element_uuid'])
) {
$this->data['CollectionElement']['element_type'] = $this->deduceType($this->data['CollectionElement']['element_uuid']);
}
return true;
}
public function mayModify(int $user_id, int $collection_id)
{
$user = $this->User->getAuthUser($user_id);
$collection = $this->find('first', [
'recursive' => -1,
'conditions' => ['Collection.id' => $collection_id]
]);
if ($user['Role']['perm_site_admin']) {
return true;
}
if (empty($user['Role']['perm_modify'])) {
return false;
}
if (!empty($user['Role']['perm_modify_org'])) {
if ($user['org_id'] == $collection['Collection']['Orgc_id']) {
return true;
}
if ($user['Role']['perm_sync'] && $user['org_id'] == $collection['Collection']['Org_id']) {
return true;
}
}
if (!empty($user['Role']['perm_modify']) && $user['id'] === $collection['Collection']['user_id']) {
}
}
public function mayView(int $user_id, int $collection_id)
{
$user = $this->User->getAuthUser($user_id);
$collection = $this->find('first', [
'recursive' => -1,
'conditions' => ['Collection.id' => $collection_id]
]);
if ($user['Role']['perm_site_admin']) {
return true;
}
if ($collection['Collection']['org_id'] == $user('org_id')) {
return true;
}
if (in_array($collection['Collection']['distribution'], [1,2,3])) {
return true;
}
if ($collection['Collection']['distribution'] === 4) {
$SharingGroup = ClassRegistry::init('SharingGroup');
$sgs = $this->SharingGroup->fetchAllAuthorised($user, 'uuid');
if (isset($sgs[$collection['Collection']['sharing_group_id']])) {
return true;
} else {
return false;
}
}
return false;
}
public function deduceType(string $uuid)
{
foreach ($this->valid_types as $valid_type) {
$this->{$valid_type} = ClassRegistry::init($valid_type);
$result = $this->$valid_type->find('first', [
'conditions' => [$valid_type.'.uuid' => $uuid],
'recursive' => -1
]);
if (!empty($result)) {
return $valid_type;
}
}
throw new NotFoundException(__('Invalid UUID'));
}
/*
* Pass a Collection as received from another instance to this function to capture the elements
* The received object is authoritative, so all elements that no longer exist in the upstream will be culled.
*/
public function captureElements($data) {
$temp = $this->find('all', [
'recursive' => -1,
'conditions' => ['CollectionElement.collection_id' => $data['Collection']['id']]
]);
$oldElements = [];
foreach ($temp as $oldElement) {
$oldElements[$oldElement['CollectionElement']['uuid']] = $oldElement['CollectionElement'];
}
if (isset($data['Collection']['CollectionElement'])) {
$elementsToSave = [];
foreach ($data['Collection']['CollectionElement'] as $k => $element) {
if (empty($element['uuid'])) {
$element['uuid'] = CakeText::uuid();
}
if (isset($oldElements[$element['uuid']])) {
if (isset($element['description'])) {
$oldElements[$element['uuid']]['description'] = $element['description'];
}
$elementsToSave[$k] = $oldElements[$element['uuid']];
unset($oldElements[$element['uuid']]);
} else {
$elementsToSave[$k] = [
'CollectionElement' => [
'uuid' => $element['uuid'],
'element_uuid' => $element['element_uuid'],
'element_type' => $element['element_type'],
'description' => $element['description'],
'collection_id' => $data['Collection']['id']
]
];
}
}
foreach ($elementsToSave as $k => $element) {
if (empty($element['CollectionElement']['id'])) {
$this->create();
}
try{
$this->save($element);
} catch (PDOException $e) {
// duplicate value?
}
}
foreach ($oldElements as $toDelete) {
$this->delete($toDelete['id']);
}
$temp = $this->find('all', [
'conditions' => ['CollectionElement.collection_id' => $data['Collection']['id']],
'recursive' => -1
]);
$data['Collection']['CollectionElement'] = [];
foreach ($temp as $element) {
$data['Collection']['CollectionElement'][] = $element['CollectionElement'];
}
}
return $data;
}
}

View File

@ -19,6 +19,9 @@ App::uses('ProcessTool', 'Tools');
* @property Organisation $Org
* @property Organisation $Orgc
* @property CryptographicKey $CryptographicKey
* @property Note $Note
* @property Opinion $Opinion
* @property Relationship $Relationship
*/
class Event extends AppModel
{
@ -40,7 +43,8 @@ class Event extends AppModel
'change' => 'full'),
'Trim',
'Containable',
'EventWarning'
'EventWarning',
'AnalystDataParent'
);
public $displayField = 'id';
@ -1760,6 +1764,11 @@ class Event extends AppModel
if (!isset($options['fetchFullClusterRelationship'])) {
$options['fetchFullClusterRelationship'] = false;
}
if (!isset($options['includeAnalystData'])) {
$options['includeAnalystData'] = false;
} else {
$options['includeAnalystData'] = !empty($options['includeAnalystData']);
}
foreach ($this->possibleOptions as $opt) {
if (!isset($options[$opt])) {
$options[$opt] = false;
@ -2038,6 +2047,8 @@ class Event extends AppModel
if (!empty($options['page'])) {
$params['page'] = $options['page'];
}
$this->includeAnalystData = $options['includeAnalystData'];
$this->includeAnalystDataRecursive = $options['includeAnalystData'];
if (!empty($options['order'])) {
$params['order'] = $this->findOrder(
$options['order'],
@ -2198,6 +2209,13 @@ class Event extends AppModel
if (!empty($options['includeGranularCorrelations'])) {
$event['Attribute'] = $this->Attribute->Correlation->attachCorrelationExclusion($event['Attribute']);
}
if (!empty($options['includeAnalystData'])) {
foreach ($event['Attribute'] as $k => $attribute) {
$this->Attribute->includeAnalystDataRecursive = true;
$analyst_data = $this->Attribute->attachAnalystData($attribute);
$event['Attribute'][$k] = array_merge($event['Attribute'][$k], $analyst_data);
}
}
// move all object attributes to a temporary container
$tempObjectAttributeContainer = array();
@ -2248,6 +2266,11 @@ class Event extends AppModel
if (isset($tempObjectAttributeContainer[$objectValue['id']])) {
$objectValue['Attribute'] = $tempObjectAttributeContainer[$objectValue['id']];
}
if (!empty($options['includeAnalystData'])) {
$this->Object->includeAnalystDataRecursive = true;
$analyst_data = $this->Object->attachAnalystData($objectValue);
$objectValue = array_merge($objectValue, $analyst_data);
}
}
unset($tempObjectAttributeContainer);
}
@ -3633,6 +3656,7 @@ class Event extends AppModel
$created_id = 0;
$event['Event']['locked'] = 1;
$event['Event']['published'] = $publish;
$event = $this->updatedLockedFieldForAllAnalystData($event);
$result = $this->_add($event, true, $user, '', null, false, null, $created_id, $validationIssues);
$results[] = [
'info' => $event['Event']['info'],
@ -3644,6 +3668,59 @@ class Event extends AppModel
return $results;
}
private function updatedLockedFieldForAllAnalystData(array $event): array
{
$event = $this->updatedLockedFieldForAnalystData($event, 'Event');
if (!empty($event['Event']['Attribute'])) {
for ($i=0; $i < count($event['Event']['Attribute']); $i++) {
$event['Event']['Attribute'][$i] = $this->updatedLockedFieldForAnalystData($event['Event']['Attribute'][$i]);
}
}
if (!empty($event['Event']['Object'])) {
for ($i=0; $i < count($event['Event']['Object']); $i++) {
$event['Event']['Object'][$i] = $this->updatedLockedFieldForAnalystData($event['Event']['Object'][$i]);
if (!empty($event['Event']['Object'][$i])) {
for ($j=0; $j < count($event['Event']['Object'][$i]['Attribute']); $j++) {
$event['Event']['Object'][$i]['Attribute'][$j] = $this->updatedLockedFieldForAnalystData($event['Event']['Object'][$i]['Attribute'][$j]);
}
}
}
}
if (!empty($event['Event']['EventReport'])) {
for ($i=0; $i < count($event['Event']['EventReport']); $i++) {
$event['Event']['EventReport'][$i] = $this->updatedLockedFieldForAnalystData($event['Event']['EventReport'][$i]);
}
}
return $event;
}
private function updatedLockedFieldForAnalystData(array $data, $model=false): array
{
$this->AnalystData = ClassRegistry::init('AnalystData');
if (!empty($model)) {
$data = $data[$model];
}
foreach ($this->AnalystData::ANALYST_DATA_TYPES as $type) {
if (!empty($data[$type])) {
for ($i=0; $i < count($data[$type]); $i++) {
$data[$type][$i]['locked'] = true;
foreach ($this->AnalystData::ANALYST_DATA_TYPES as $childType) {
if (!empty($data[$type][$i][$childType])) {
for ($j=0; $j < count($data[$type][$i][$childType]); $j++) {
$data[$type][$i][$childType][$j]['locked'] = true;
$data[$type][$i][$childType][$j] = $this->updatedLockedFieldForAnalystData($data[$type][$i][$childType][$j]);
}
}
}
}
}
}
if (!empty($model)) {
$data = [$model => $data];
}
return $data;
}
/**
* Low level function to add an Event based on an Event $data array.
*
@ -3881,6 +3958,8 @@ class Event extends AppModel
if (isset($data['Sighting']) && !empty($data['Sighting'])) {
$this->Sighting->captureSightings($data['Sighting'], null, $this->id, $user);
}
$this->captureAnalystData($user, $data['Event']);
if ($fromXml) {
$created_id = $this->id;
}
@ -4182,6 +4261,8 @@ class Event extends AppModel
if (isset($data['Sighting']) && !empty($data['Sighting'])) {
$this->Sighting->captureSightings($data['Sighting'], null, $this->id, $user);
}
$this->captureAnalystData($user, $data['Event']);
// if published -> do the actual publishing
if ($changed && (!empty($data['Event']['published']) && 1 == $data['Event']['published'])) {
// The edited event is from a remote server ?
@ -5914,15 +5995,17 @@ class Event extends AppModel
* @param int $distribution
* @param int|null $sharingGroupId
* @param bool $galaxiesAsTags
* @param int $clusterDistribution
* @param int|null $clusterSharingGroupId
* @param bool $debug
* @return int|string|array
* @throws JsonException
* @throws InvalidArgumentException
* @throws Exception
*/
public function upload_stix(array $user, $file, $stixVersion, $originalFile, $publish, $distribution, $sharingGroupId, $galaxiesAsTags, $debug = false)
public function upload_stix(array $user, $file, $stixVersion, $originalFile, $publish, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $debug = false)
{
$decoded = $this->convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $debug);
$decoded = $this->convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $user['Organisation']['uuid'], $debug);
if (!empty($decoded['success'])) {
$data = JsonTool::decodeArray($decoded['converted']);
@ -5986,11 +6069,14 @@ class Event extends AppModel
* @param int $distribution
* @param int|null $sharingGroupId
* @param bool $galaxiesAsTags
* @param int $clusterDistribution
* @param int|null $clusterSharingGroupId
* @param string $orgUuid
* @param bool $debug
* @return array
* @throws Exception
*/
private function convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $debug)
private function convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $orgUuid, $debug)
{
$scriptDir = APP . 'files' . DS . 'scripts';
if ($stixVersion === '2' || $stixVersion === '2.0' || $stixVersion === '2.1') {
@ -6001,12 +6087,18 @@ class Event extends AppModel
$scriptFile,
'-i', $file,
'--distribution', $distribution,
'--org_uuid', $orgUuid
];
if ($distribution == 4) {
array_push($shellCommand, '--sharing_group_id', $sharingGroupId);
}
if ($galaxiesAsTags) {
$shellCommand[] = '--galaxies_as_tags';
} else {
array_push($shellCommand, '--cluster_distribution', $clusterDistribution);
if ($clusterDistribution == 4) {
array_push($shellCommand, '--cluster_sharing_group_id', $clusterSharingGroupId);
}
}
if ($debug) {
$shellCommand[] = '--debug';
@ -7948,6 +8040,20 @@ class Event extends AppModel
}
}
public function captureAnalystData($user, $data)
{
$this->Note = ClassRegistry::init('Note');
$this->Opinion = ClassRegistry::init('Opinion');
$this->Relationship = ClassRegistry::init('Relationship');
foreach ($this->Note::ANALYST_DATA_TYPES as $type) {
if (!empty($data[$type])) {
foreach ($data[$type] as $analystData) {
$this->{$type}->captureAnalystData($user, $analystData);
}
}
}
}
public function getTrendsForTags(array $user, array $eventFilters=[], int $baseDayRange, int $rollingWindows=3, $tagFilterPrefixes=null): array
{
$fullDayNumber = $baseDayRange + $baseDayRange * $rollingWindows;

View File

@ -17,6 +17,7 @@ class EventReport extends AppModel
'change' => 'full'
),
'Regexp' => array('fields' => array('value')),
'AnalystDataParent',
);
public $validate = array(
@ -118,6 +119,8 @@ class EventReport extends AppModel
__('Event Report dropped due to validation for Event report %s failed: %s', $this->data['EventReport']['uuid'], $this->data['EventReport']['name']),
__('Validation errors: %s.%sFull report: %s', json_encode($errors), PHP_EOL, json_encode($report['EventReport']))
);
} else {
$this->Event->captureAnalystData($user, $report);
}
return $errors;
}
@ -188,6 +191,7 @@ class EventReport extends AppModel
}
$errors = $this->saveAndReturnErrors($report, ['fieldList' => self::CAPTURE_FIELDS], $errors);
if (empty($errors)) {
$this->Event->captureAnalystData($user, $report['EventReport']);
$this->Event->unpublishEvent($eventId);
}
return $errors;

View File

@ -22,6 +22,7 @@ class GalaxyCluster extends AppModel
'userKey' => 'user_id',
'change' => 'full'),
'Containable',
'AnalystDataParent',
);
private $__assetCache = array();
@ -196,7 +197,7 @@ class GalaxyCluster extends AppModel
*/
public function arrangeData($cluster)
{
$models = array('Galaxy', 'SharingGroup', 'GalaxyElement', 'GalaxyClusterRelation', 'Org', 'Orgc', 'TargetingClusterRelation');
$models = array('Galaxy', 'SharingGroup', 'GalaxyElement', 'GalaxyClusterRelation', 'Org', 'Orgc', 'TargetingClusterRelation', 'Note', 'Opinion', 'Relationship');
foreach ($models as $model) {
if (isset($cluster[$model])) {
$cluster['GalaxyCluster'][$model] = $cluster[$model];

View File

@ -20,11 +20,12 @@ class MispObject extends AppModel
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full'),
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full'),
'AnalystDataParent'
);
public $belongsTo = array(
@ -571,11 +572,18 @@ class MispObject extends AppModel
if (isset($options['fields'])) {
$params['fields'] = $options['fields'];
}
$contain = [];
if (isset($options['contain'])) {
$contain = $options['contain'];
}
if (empty($contain['Event'])) {
$contain = ['Event' => ['distribution', 'id', 'user_id', 'orgc_id', 'org_id']];
}
$results = $this->find('all', array(
'conditions' => $params['conditions'],
'recursive' => -1,
'fields' => $params['fields'],
'contain' => array('Event' => array('distribution', 'id', 'user_id', 'orgc_id', 'org_id')),
'contain' => $contain,
'sort' => false
));
return $results;
@ -1131,6 +1139,7 @@ class MispObject extends AppModel
$this->Attribute->captureAttribute($attribute, $eventId, $user, $objectId, false, $parentEvent);
}
}
$this->Event->captureAnalystData($user, $object['Object']);
return true;
}
@ -1210,6 +1219,7 @@ class MispObject extends AppModel
);
return $this->validationErrors;
}
$this->Event->captureAnalystData($user, $object);
if (!empty($object['Attribute'])) {
$attributes = [];
foreach ($object['Attribute'] as $attribute) {

29
app/Model/Note.php Normal file
View File

@ -0,0 +1,29 @@
<?php
App::uses('AppModel', 'Model');
App::uses('AnalystData', 'Model');
class Note extends AnalystData
{
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'Containable',
'AnalystData',
);
public $current_type = 'Note';
public $current_type_id = 0;
public const EDITABLE_FIELDS = [
'note',
];
public $validate = [];
public function beforeValidate($options = array())
{
parent::beforeValidate();
return true;
}
}

30
app/Model/Opinion.php Normal file
View File

@ -0,0 +1,30 @@
<?php
App::uses('AppModel', 'Model');
App::uses('AnalystData', 'Model');
class Opinion extends AnalystData
{
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'Containable',
'AnalystData',
);
public $current_type = 'Opinion';
public $current_type_id = 1;
public const EDITABLE_FIELDS = [
'opinion',
'comment',
];
public $validate = [];
public function beforeValidate($options = array())
{
parent::beforeValidate();
return true;
}
}

View File

@ -196,7 +196,7 @@ class Organisation extends AppModel
* @return int Organisation ID
* @throws Exception
*/
public function captureOrg($org, array $user, $force = false)
public function captureOrg($org, array $user, $force = false, $returnUUID = false)
{
$fieldsToFetch = $force ?
['id', 'uuid', 'type', 'date_created', 'date_modified', 'nationality', 'sector', 'contacts'] :
@ -238,7 +238,7 @@ class Organisation extends AppModel
}
$this->create();
$this->save($organisation);
return $this->id;
return $returnUUID ? $organisation['uuid'] : $this->id;
} else {
$changed = false;
if (isset($org['uuid']) && empty($existingOrg[$this->alias]['uuid'])) {
@ -262,7 +262,7 @@ class Organisation extends AppModel
$this->save($existingOrg);
}
}
return $existingOrg[$this->alias]['id'];
return $returnUUID ? $existingOrg[$this->alias]['uuid']: $existingOrg[$this->alias]['id'];
}
/**

149
app/Model/Relationship.php Normal file
View File

@ -0,0 +1,149 @@
<?php
App::uses('AppModel', 'Model');
App::uses('AnalystData', 'Model');
class Relationship extends AnalystData
{
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'Containable',
'AnalystData',
);
public $current_type = 'Relationship';
public $current_type_id = 2;
protected $EDITABLE_FIELDS = [
'relationship_type',
];
public $validate = [];
/** @var object|null */
protected $Event;
/** @var object|null */
protected $Attribute;
/** @var object|null */
protected $Object;
/** @var object|null */
protected $Note;
/** @var object|null */
protected $Opinion;
/** @var object|null */
protected $Relationship;
/** @var object|null */
protected $User;
/** @var array|null */
private $__currentUser;
public function afterFind($results, $primary = false)
{
$results = parent::afterFind($results, $primary);
if (empty($this->__currentUser)) {
$user_id = Configure::read('CurrentUserId');
$this->User = ClassRegistry::init('User');
if ($user_id) {
$this->__currentUser = $this->User->getAuthUser($user_id);
}
}
foreach ($results as $i => $v) {
if (!empty($v[$this->alias]['related_object_type']) && !empty($v[$this->alias]['related_object_uuid'])) {
$results[$i][$this->alias]['related_object'] = $this->getRelatedElement($this->__currentUser, $v[$this->alias]['related_object_type'], $v[$this->alias]['related_object_uuid']);
}
}
return $results;
}
public function getRelatedElement(array $user, $type, $uuid): array
{
$data = [];
if ($type == 'Event') {
$this->Event = ClassRegistry::init('Event');
$params = [
];
$backup = $this->Event->includeAnalystData;
$this->Event->includeAnalystData = false;
$data = $this->Event->fetchSimpleEvent($user, $uuid, $params);
$this->Event->includeAnalystData = $backup;
} else if ($type == 'Attribute') {
$this->Attribute = ClassRegistry::init('Attribute');
$params = [
'conditions' => [
['Attribute.uuid' => $uuid],
],
'contain' => ['Event' => 'Orgc', 'Object',]
];
$backup = $this->Attribute->includeAnalystData;
$this->Attribute->includeAnalystData = false;
$data = $this->Attribute->fetchAttributeSimple($user, $params);
$this->Attribute->includeAnalystData = $backup;
$data = $this->rearrangeData($data, 'Attribute');
} else if ($type == 'Object') {
$this->Object = ClassRegistry::init('MispObject');
$params = [
'conditions' => [
['Object.uuid' => $uuid],
],
'contain' => ['Event' => 'Orgc',]
];
$backup = $this->Object->includeAnalystData;
$this->Object->includeAnalystData = false;
$data = $this->Object->fetchObjectSimple($user, $params);
$this->Object->includeAnalystData = $backup;
if (!empty($data)) {
$data = $data[0];
}
$data = $this->rearrangeData($data, 'Object');
} else if ($type == 'Note') {
$this->Note = ClassRegistry::init('Note');
$params = [
];
$backup = $this->Note->includeAnalystData;
$this->Note->includeAnalystData = false;
$data = $this->Note->fetchNote();
$this->Note->includeAnalystData = $backup;
} else if ($type == 'Opinion') {
$this->Opinion = ClassRegistry::init('Opinion');
$params = [
];
$backup = $this->Opinion->includeAnalystData;
$this->Opinion->includeAnalystData = false;
$data = $this->Opinion->fetchOpinion();
$this->Opinion->includeAnalystData = $backup;
} else if ($type == 'Relationship') {
$this->Relationship = ClassRegistry::init('Relationship');
$params = [
];
$backup = $this->Relationship->includeAnalystData;
$this->Relationship->includeAnalystData = false;
$data = $this->Relationship->fetchRelationship();
$this->Relationship->includeAnalystData = $backup;
}
return $data;
}
private function rearrangeData(array $data, $objectType): array
{
$models = ['Event', 'Attribute', 'Object', 'Organisation', ];
if (!empty($data) && !empty($data[$objectType])) {
foreach ($models as $model) {
if ($model == $objectType) {
continue;
}
if (isset($data[$model])) {
$data[$objectType][$model] = $data[$model];
unset($data[$model]);
}
}
$data[$objectType]['Organisation'] = $data[$objectType]['Event']['Orgc'];
$data[$objectType]['orgc_uuid'] = $data[$objectType]['Event']['Orgc']['uuid'];
unset($data[$objectType]['Event']['Orgc']);
}
return $data;
}
}

View File

@ -331,6 +331,12 @@ class Role extends AppModel
'readonlyenabled' => true,
'title' => __('Allow the viewing of feed correlations. Enabling this can come at a performance cost.'),
),
'perm_analyst_data' => array(
'id' => 'RolePermAnalystData',
'text' => 'Analyst Data Creator',
'readonlyenabled' => false,
'title' => __('Create or modify Analyst Data such as Analyst Notes or Opinions.'),
),
);
}
}

View File

@ -683,21 +683,24 @@ class Server extends AppModel
$job->saveProgress($jobId, 'Pulling sightings.', 75);
}
$pulledSightings = $eventModel->Sighting->pullSightings($user, $serverSync);
$this->AnalystData = ClassRegistry::init('AnalystData');
$pulledAnalystData = $this->AnalystData->pull($user, $serverSync);
}
if ($jobId) {
$job->saveStatus($jobId, true, 'Pull completed.');
}
$change = sprintf(
'%s events, %s proposals, %s sightings and %s galaxy clusters pulled or updated. %s events failed or didn\'t need an update.',
'%s events, %s proposals, %s sightings, %s galaxy clusters and %s analyst data pulled or updated. %s events failed or didn\'t need an update.',
count($successes),
$pulledProposals,
$pulledSightings,
$pulledClusters,
$pulledAnalystData,
count($fails)
);
$this->loadLog()->createLogEntry($user, 'pull', 'Server', $server['Server']['id'], 'Pull from ' . $server['Server']['url'] . ' initiated by ' . $email, $change);
return [$successes, $fails, $pulledProposals, $pulledSightings, $pulledClusters];
return [$successes, $fails, $pulledProposals, $pulledSightings, $pulledClusters, $pulledAnalystData];
}
public function filterRuleToParameter($filter_rules)
@ -757,6 +760,41 @@ class Server extends AppModel
return $clusterArray;
}
/**
* fetchUUIDsFromServer Fetch remote analyst datas' UUIDs and timestamp
*
* @param ServerSyncTool $serverSync
* @param array $conditions
* @return array The list of analyst data
* @throws JsonException|HttpSocketHttpException|HttpSocketJsonException
*/
public function fetchUUIDsFromServer(ServerSyncTool $serverSync, array $conditions = [])
{
$filterRules = $conditions;
$dataArray = $serverSync->fetchIndexMinimal($filterRules)->json();
if (isset($dataArray['response'])) {
$dataArray = $dataArray['response'];
}
return $dataArray;
}
/**
* filterAnalystDataForPush Send a candidate data to be pushed and returns the list of accepted entries
*
* @param ServerSyncTool $serverSync
* @param array $conditions
* @return array The list of analyst data
* @throws JsonException|HttpSocketHttpException|HttpSocketJsonException
*/
public function filterAnalystDataForPush(ServerSyncTool $serverSync, array $candidates = [])
{
$dataArray = $serverSync->filterAnalystDataForPush($candidates)->json();
if (isset($dataArray['response'])) {
$dataArray = $dataArray['response'];
}
return $dataArray;
}
/**
* Get a list of cluster IDs that are present on the remote server and returns clusters that should be pulled
*
@ -1239,6 +1277,20 @@ class Server extends AppModel
} else {
$successes = array_merge($successes, $sightingSuccesses);
}
if ($push['canPush'] || $push['canEditAnalystData']) {
$this->AnalystData = ClassRegistry::init('AnalystData');
$analystDataSuccesses = $this->AnalystData->push($user, $serverSync);
} else {
$analystDataSuccesses = array();
}
if (!isset($successes)) {
$successes = $analystDataSuccesses;
} else {
$successes = array_merge($successes, $analystDataSuccesses);
}
if (!isset($fails)) {
$fails = array();
}
@ -2543,33 +2595,33 @@ class Server extends AppModel
public function getFileRules()
{
return array(
'orgs' => array(
return [
'orgs' => [
'name' => __('Organisation logos'),
'description' => __('The logo used by an organisation on the event index, event view, discussions, proposals, etc. Make sure that the filename is in the org.png format, where org is the case-sensitive organisation name.'),
'expected' => array(),
'expected' => [],
'valid_format' => __('48x48 pixel .png files or .svg file'),
'path' => APP . 'webroot' . DS . 'img' . DS . 'orgs',
'path' => APP . 'files' . DS . 'img' . DS . 'orgs',
'regex' => '.*\.(png|svg)$',
'regex_error' => __('Filename must be in the following format: *.png or *.svg'),
'files' => array(),
),
'img' => array(
'files' => [],
],
'img' => [
'name' => __('Additional image files'),
'description' => __('Image files uploaded into this directory can be used for various purposes, such as for the login page logos'),
'expected' => array(
'expected' => [
'MISP.footer_logo' => Configure::read('MISP.footer_logo'),
'MISP.home_logo' => Configure::read('MISP.home_logo'),
'MISP.welcome_logo' => Configure::read('MISP.welcome_logo'),
'MISP.welcome_logo2' => Configure::read('MISP.welcome_logo2'),
),
],
'valid_format' => __('PNG or SVG file'),
'path' => APP . 'webroot' . DS . 'img' . DS . 'custom',
'path' => APP . 'files' . DS . 'img' . DS . 'custom',
'regex' => '.*\.(png|svg)$',
'regex_error' => __('Filename must be in the following format: *.png or *.svg'),
'files' => array(),
),
);
],
];
}
public function grabFiles()
@ -2752,6 +2804,7 @@ class Server extends AppModel
$canPush = isset($remoteVersion['perm_sync']) ? $remoteVersion['perm_sync'] : false;
$canSight = isset($remoteVersion['perm_sighting']) ? $remoteVersion['perm_sighting'] : false;
$canEditGalaxyCluster = isset($remoteVersion['perm_galaxy_editor']) ? $remoteVersion['perm_galaxy_editor'] : false;
$canEditAnalystData = isset($remoteVersion['perm_analyst_data']) ? $remoteVersion['perm_analyst_data'] : false;
$remoteVersionString = $remoteVersion['version'];
$remoteVersion = explode('.', $remoteVersion['version']);
if (!isset($remoteVersion[0])) {
@ -2801,6 +2854,7 @@ class Server extends AppModel
'response' => $response,
'canPush' => $canPush,
'canSight' => $canSight,
'canEditAnalystData' => $canEditAnalystData,
'canEditGalaxyCluster' => $canEditGalaxyCluster,
'version' => $remoteVersion,
'protectedMode' => $protectedMode,
@ -2874,12 +2928,15 @@ class Server extends AppModel
return $result;
}
/**
* @return array
*/
public function redisInfo()
{
$output = array(
$output = [
'extensionVersion' => phpversion('redis'),
'connection' => false,
);
];
try {
$redis = RedisTool::init();

View File

@ -39,22 +39,21 @@ class Module_attribute_edition_operation extends WorkflowBaseActionModule
protected function __saveAttributes(array $attributes, array $rData, array $params, array $user): array
{
$success = false;
$attributes = [];
$newAttributes = [];
foreach ($attributes as $k => $attribute) {
$newAttribute = $this->_editAttribute($attribute, $rData, $params);
$newAttributes[] = $newAttribute;
unset($newAttribute['timestamp']);
$newAttributes[] = $newAttribute;
$result = $this->Attribute->editAttribute($newAttribute, $rData, $user, $newAttribute['object_id']);
if (is_array($result)) {
$attributes[] = $result;
}
}
$this->Attribute->editAttributeBulk($attributes, $rData, $user);
foreach ($attributes as $k => $attribute) {
$this->Attribute->editAttributeBulk($newAttributes, $rData, $user);
foreach ($newAttributes as $k => $attribute) {
$saveSuccess = empty($this->Attribute->validationErrors[$k]);
if ($saveSuccess) {
$rData = $this->_overrideAttribute($attribute, $newAttribute, $rData);
$rData = $this->_overrideAttribute($attribute, $attribute, $rData);
}
$success = $success || !empty($saveSuccess);
}

View File

@ -7,7 +7,7 @@ class Module_tag_replacement_generic extends Module_tag_operation
public $blocking = false;
public $id = 'tag_replacement_generic';
public $name = 'Tag Replacement Generic';
public $description = 'Toggle or remove the IDS flag on selected attributes.';
public $description = 'Attach a tag, or substitue a tag by another';
public $icon = 'tags';
public $inputs = 1;
public $outputs = 1;

View File

@ -1,16 +1,16 @@
# Configure Azure AD to use SIngle SignOn for MISP
# Configure Azure AD to use Single Sign-On (SSO) for MISP
This plugin enables authentication with an Azure Active Directory server. Under the hood it uses oAuth2. There are still a number of rough edges but in general the plugin works.
This plugin enables authentication with an Azure Active Directory (now called [Entra-ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id)) server. Under the hood it uses oAuth2. There are still a number of rough edges but in general the plugin works.
It supports verification if a user has the proper MISP AD groups. Users should already exist in MISP. Future enhancement could include auto-create users
## Azure ADApp Registration Configuration
In Azure, add a new App Registration. Select Web and set the Redirect URI to your MISP server login page `https://misp.yourdomain.com/users/login`. The MISP instance does not need to be publicly accessible if it is reachable by your browser. The redirect URI that you specify here must be the same as used in the MISP configuration.
In Azure, add a new App Registration. Select Web and set the Redirect URI to your MISP server login page `https://misp.yourdomain.com/users/login`. The MISP instance does not need to be publicly accessible if it is reachable by your browser. The redirect URI that you specify here must be the same as used in the MISP configuration (including `/users/login`). You can add as many redirect URIs as needed, meaning you can have multiple MISP servers use the same Azure App.
![AppReg Configuration](.images/Picture29.png)
On the Overview page of the new MISP App Registration capture the following inforamtion.
On the Overview page of the new MISP App Registration capture the following information.
- [x] Application (client) ID
- [x] Directory (tenant) ID
@ -44,7 +44,7 @@ Create the following groups in Azure AD, these can be called anything you like f
Make a name of your groups, we'll need these later.
- [x] Misp Users
- [x] Misp ORG Admins
- [x] Misp Org Admins
- [x] Misp Site Admins
## Enable the AAD Plugin for MISP
@ -122,7 +122,7 @@ Scroll down to near the bottom of the page and add in the following configuratio
),
```
Add the information we made a note of earlier when creating the `App Registation` and optionally the Azure AD groups you created.
Add the information we made a note of earlier when creating the `App Registration` and optionally the Azure AD groups you created.
![AadAuth.configuration](.images/Picture38.png)
@ -139,4 +139,12 @@ Additionally, it is recommended to set the following settings in the MISP config
* `MISP.disable_user_login_change => true`: Removes the ability of users to change their username (email), except for site admins.
* `MISP.disable_user_password_change => true`: Removes the ability of users to change their own password.
This way users will not be able to change their passwords and by-pass the AAD login flow.
This way users will not be able to change their passwords and by-pass the AAD login flow.
# Create users via the MISP REST API
Because users already need to exist in MISP before they can authenticate with AAD it can be useful to provision them in an automated fashion. This can be done by creating the users via the MISP REST API. The below `curl` command provides an example on how to do this. Note that you need an API key.
```
curl -k -d '{"email":"newuser@mycompany.com", "role_id":"3", "org_id":"1", "enable_password":"1", "change_pw":"0"}' -H "Authorization: API_KEY" -H "Accept: application/json" -H "Content-type: application/json" -X POST htps://misp.mycompany.com/admin/users/add
```

View File

@ -0,0 +1,230 @@
<?php
$edit = $this->request->params['action'] === 'edit' ? true : false;
$fields = [
[
'field' => 'object_type',
'class' => 'span2',
'disabled' => !empty($this->data[$modelSelection]['object_type']),
'default' => empty($this->data[$modelSelection]['object_type']) ? null : $this->data[$modelSelection]['object_type'],
'options' => $dropdownData['valid_targets'],
'type' => 'dropdown',
'stayInLine' => 1
],
[
'field' => 'object_uuid',
'class' => 'span4',
'disabled' => !empty($this->data[$modelSelection]['object_uuid']),
'default' => empty($this->data[$modelSelection]['object_uuid']) ? null : $this->data[$modelSelection]['object_uuid']
],
[
'field' => 'distribution',
'class' => 'input',
'options' => $dropdownData['distributionLevels'],
'default' => isset($attribute['Attribute']['distribution']) ? $attribute['Attribute']['distribution'] : $initialDistribution,
'stayInLine' => 1,
'type' => 'dropdown'
],
[
'field' => 'sharing_group_id',
'class' => 'input',
'options' => $dropdownData['sgs'],
'label' => __("Sharing Group"),
'type' => 'dropdown'
],
[
'field' => 'authors',
'class' => 'span3',
'stayInLine' => $modelSelection === 'Note',
'default' => $me['email'],
],
];
if ($modelSelection === 'Note') {
$fields = array_merge($fields,
[
[
'field' => 'language',
'class' => 'span3',
'options' => $languageRFC5646,
'type' => 'dropdown',
],
[
'field' => 'note',
'type' => 'textarea',
'class' => 'input span6',
]
]
);
} else if ($modelSelection === 'Opinion') {
$fields = array_merge($fields,
[
[
'field' => 'opinion',
'class' => '',
'type' => 'opinion',
],
[
'field' => 'comment',
'type' => 'textarea',
'class' => 'input span6',
]
]
);
} else if ($modelSelection === 'Relationship') {
$fields = array_merge($fields,
[
[
'field' => 'relationship_type',
'class' => 'span4',
'options' => $existingRelations,
'type' => 'text',
'picker' => array(
'text' => __('Pick Relationship'),
'function' => 'pickerRelationshipTypes',
)
],
[
'field' => 'related_object_type',
'class' => 'span2',
'options' => $dropdownData['valid_targets'],
'type' => 'dropdown',
'stayInLine' => 1,
'disabled' => !empty($this->data[$modelSelection]['related_object_type']),
],
[
'field' => 'related_object_uuid',
'class' => 'span4',
'disabled' => !empty($this->data[$modelSelection]['related_object_uuid']),
],
sprintf('<div style="max-width: 960px;"><label>%s:</label><div id="related-object-container">%s</div></div>', __('Related Object'), __('- No UUID provided -'))
]
);
}
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => false,
'model' => $modelSelection,
'title' => $edit ? __('Edit %s', $modelSelection) : __('Add new %s', $modelSelection),
'fields' => $fields,
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace(analystDataSubmitSuccess, true);'
]
]
]);
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}
?>
<script>
function analystDataSubmitSuccess(data) {
<?php if ($edit): ?>
replaceNoteInUI(data)
<?php else: ?>
addNoteInUI(data)
<?php endif; ?>
}
function replaceNoteInUI(data) {
var noteType = Object.keys(data)[0]
var noteHTMLID = '#' + data[noteType].note_type_name + '-' + data[noteType].id
var $noteToReplace = $(noteHTMLID)
if ($noteToReplace.length == 1) {
var relatedObjects = {}
if (noteType == 'Relationship') {
var relationship = data[noteType]
relatedObjects[relationship['object_type']] = {}
relatedObjects[relationship['object_type']][relationship['related_object_uuid']] = relationship['related_object'][relationship['object_type']]
}
var compiledUpdatedNote = renderNote(data[noteType], relatedObjects)
$noteToReplace[0].outerHTML = compiledUpdatedNote
$(noteHTMLID).css({'opacity': 0})
setTimeout(() => {
$(noteHTMLID).css({'opacity': 1})
}, 750);
}
}
function addNoteInUI(data) {
location.reload()
}
function displayRelatedObject(data) {
if (Object.keys(data).length == 0) {
$('#related-object-container').html('<span class="text-muted"><?= __('Could not fetch remote object or fetching not supported yet.') ?></span>')
} else {
var parsed = syntaxHighlightJson(data)
$('#related-object-container').html(parsed)
}
}
function fetchAndDisplayRelatedObject(type, uuid) {
if (!type || !uuid) {
return
}
var url = baseurl + '/analystData/getRelatedElement/' + type + '/' + uuid
$.ajax({
type: "get",
url: url,
headers: { Accept: "application/json" },
success: function (data) {
displayRelatedObject(data)
},
error: function (data, textStatus, errorThrown) {
showMessage('fail', textStatus + ": " + errorThrown);
}
});
}
$(document).ready(function() {
$('#<?= h($modelSelection) ?>Distribution').change(function() {
checkSharingGroup('<?= h($modelSelection) ?>');
});
checkSharingGroup('<?= h($modelSelection) ?>');
$('#RelationshipRelatedObjectType').change(function(e) {
if ($('#RelationshipRelatedObjectUuid').val().length == 36) {
fetchAndDisplayRelatedObject($('#RelationshipRelatedObjectType').val(),$('#RelationshipRelatedObjectUuid').val())
}
})
$('#RelationshipRelatedObjectUuid').on('input', function(e) {
if ($('#RelationshipRelatedObjectUuid').val().length == 36) {
fetchAndDisplayRelatedObject($('#RelationshipRelatedObjectType').val(),$('#RelationshipRelatedObjectUuid').val())
}
})
fetchAndDisplayRelatedObject($('#RelationshipRelatedObjectType').val(),$('#RelationshipRelatedObjectUuid').val())
})
<?php if ($modelSelection === 'Relationship'): ?>
function pickerRelationshipTypes() {
var existingRelationTypes = <?= json_encode(array_values($existingRelations)) ?> ;
var $select = $('<select id="pickerRelationshipTypeSelect"/>');
existingRelationTypes.forEach(function(type) {
$select.append($('<option/>').val(type).text(type))
})
var html = '<div>' + $select[0].outerHTML + '</div>';
var that = this
openPopover(this, html, false, 'right', function($popover) {
$popover.css('z-index', 1060)
$popover.find('select').chosen({
width: '300px',
}).on('change', function(evt, param) {
$('#RelationshipRelationshipType').val($('#pickerRelationshipTypeSelect').val());
$(that).popover('hide')
});
});
}
<?php endif; ?>
</script>
<style>
#related-object-container {
box-shadow: 0 0 5px 0px #22222266;
padding: 0.5rem;
max-height: 400px;
overflow: auto;
margin-bottom: 1rem;
}
</style>

View File

@ -0,0 +1,182 @@
<?php
$fields = [
[
'name' => __('Id'),
'sort' => $modelSelection . '.id',
'data_path' => $modelSelection . '.id'
],
[
'name' => __('OrgC'),
'element' => 'org',
'data_path' => $modelSelection . '.Orgc'
],
[
'name' => __('UUID'),
'data_path' => $modelSelection . '.uuid'
],
[
'name' => __('Parent Object Type'),
'sort' => $modelSelection . '.object_type',
'data_path' => $modelSelection . '.object_type'
],
[
'name' => __('Target Object'),
'sort' => $modelSelection . '.object_type',
'data_path' => $modelSelection . '.object_uuid'
],
[
'name' => __('Creator org'),
'data_path' => $modelSelection . '.orgc_uuid'
],
[
'name' => __('Created'),
'sort' => $modelSelection . '.created',
'data_path' => $modelSelection . '.created'
],
[
'name' => __('Modified'),
'sort' => $modelSelection . '.modified',
'data_path' => $modelSelection . '.modified'
],
[
'name' => __('Distribution'),
'element' => 'distribution_levels',
'sort' => $modelSelection . '.distribution',
'class' => 'short',
'data_path' => $modelSelection . '.distribution',
'sg_path' => $modelSelection . '.SharingGroup',
]
];
if ($modelSelection === 'Note') {
$fields = array_merge($fields,
[
[
'name' => __('Language'),
'sort' => $modelSelection . '.language',
'data_path' => $modelSelection . '.language'
],
[
'name' => __('Note'),
'sort' => $modelSelection . '.note',
'data_path' => $modelSelection . '.note'
]
]
);
} else if ($modelSelection === 'Opinion') {
$fields = array_merge($fields,
[
[
'name' => __('Comment'),
'data_path' => $modelSelection . '.comment'
],
[
'name' => __('Opinion'),
'data_path' => $modelSelection . '.opinion',
'element' => 'opinion_scale',
],
]
);
} else if ($modelSelection === 'Relationship') {
$fields = array_merge($fields,
[
[
'name' => __('Related Object'),
'element' => 'custom',
'function' => function (array $row) use ($baseurl, $modelSelection) {
$path = Inflector::pluralize(strtolower($row[$modelSelection]['related_object_type']));
return sprintf(
'<span class="bold">%s</span>: <a href="%s/%s/view/%s">%s</a>',
h($row[$modelSelection]['related_object_type']),
h($baseurl),
h($path),
h($row[$modelSelection]['related_object_uuid']),
h($row[$modelSelection]['related_object_uuid'])
);
}
],
[
'name' => __('Relationship_type'),
'sort' => $modelSelection . '.relationship_type',
'data_path' => $modelSelection . '.relationship_type'
],
]
);
}
echo $this->element('genericElements/IndexTable/scaffold', [
'scaffold_data' => [
'data' => [
'data' => $data,
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'simple',
'children' => [
[
'active' => $modelSelection === 'Note',
'url' => sprintf('%s/analyst_data/index/Note', $baseurl),
'text' => __('Note'),
],
[
'active' => $modelSelection === 'Opinion',
'class' => 'defaultContext',
'url' => sprintf('%s/analyst_data/index/Opinion', $baseurl),
'text' => __('Opinion'),
],
[
'active' => $modelSelection === 'Relationship',
'url' => sprintf('%s/analyst_data/index/Relationship', $baseurl),
'text' => __('Relationship'),
],
]
],
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'quickFilter'
]
]
],
'fields' => $fields,
'title' => empty($ajax) ? __('%s index', Inflector::pluralize($modelSelection)) : false,
'actions' => [
[
'url' => $baseurl . '/analystData/view/' . $modelSelection,
'url_params_data_paths' => [$modelSelection . '.id'],
'icon' => 'eye',
'dbclickAction' => true,
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/analystData/edit/' . $modelSelection . '/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => $modelSelection . '.id',
'title' => __('Edit %s', $modelSelection),
'icon' => 'edit',
'complex_requirement' => function($item) use ($modelSelection) {
return !empty($item[$modelSelection]['_canEdit']);
}
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/analystData/delete/' . $modelSelection . '/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => $modelSelection . '.id',
'icon' => 'trash',
'complex_requirement' => function($item) use ($modelSelection) {
return !empty($item[$modelSelection]['_canEdit']);
}
]
]
]
]
]);
?>

View File

@ -0,0 +1,145 @@
<?php
$fields = [
[
'key' => __('ID'),
'path' => $modelSelection . '.id'
],
[
'key' => 'UUID',
'path' => $modelSelection . '.uuid',
'class' => '',
'type' => 'uuid',
'object_type' => $modelSelection,
'notes_path' => $modelSelection . '.Note',
'opinions_path' => $modelSelection . '.Opinion',
'relationships_path' => $modelSelection . '.Relationship',
],
[
'key' => __('Note Type'),
'path' => $modelSelection . '.note_type_name'
],
[
'key' => __('Target Object'),
'type' => 'custom',
'function' => function (array $row) use ($baseurl, $modelSelection) {
$path = Inflector::pluralize(strtolower($row[$modelSelection]['object_type']));
return sprintf(
'<span class="bold">%s</span>: <a href="%s/%s/view/%s">%s</a>',
h($row[$modelSelection]['object_type']),
h($baseurl),
h($path),
h($row[$modelSelection]['object_uuid']),
h($row[$modelSelection]['object_uuid'])
);
}
],
[
'key' => __('Creator org'),
'path' => $modelSelection . '.Orgc',
'pathName' => $modelSelection . '.orgc_uuid',
'type' => 'org',
'model' => 'organisations'
],
[
'key' => __('Created'),
'path' => $modelSelection . '.created'
],
[
'key' => __('Modified'),
'path' => $modelSelection . '.modified'
],
[
'key' => __('Distribution'),
'path' => $modelSelection . '.distribution',
'event_id_path' => $modelSelection . '.id',
'disable_distribution_graph' => true,
'sg_path' => $modelSelection . '.SharingGroup',
'type' => 'distribution'
],
[
'key' => __('Authors'),
'path' => $modelSelection . '.authors'
],
];
if ($modelSelection === 'Note') {
$fields[] = [
'key' => __('Language'),
'path' => $modelSelection . '.language'
];
$fields[] = [
'key' => __('Note'),
'path' => $modelSelection . '.note'
];
} else if ($modelSelection === 'Opinion') {
$fields[] = [
'key' => __('Comment'),
'path' => $modelSelection . '.comment'
];
$fields[] = [
'key' => __('Opinion'),
'path' => $modelSelection . '.opinion',
'type' => 'opinion_scale',
];
} else if ($modelSelection === 'Relationship') {
$fields[] = [
'key' => __('Related Object'),
'type' => 'custom',
'function' => function (array $row) use ($baseurl, $modelSelection) {
$path = Inflector::pluralize(strtolower($row[$modelSelection]['related_object_type']));
return sprintf(
'<span class="bold">%s</span>: <a href="%s/%s/view/%s">%s</a>',
h($row[$modelSelection]['related_object_type']),
h($baseurl),
h($path),
h($row[$modelSelection]['related_object_uuid']),
h($row[$modelSelection]['related_object_uuid'])
);
}
];
$fields[] = [
'key' => __('Relationship_type'),
'path' => $modelSelection . '.relationship_type'
];
}
echo $this->element(
'genericElements/SingleViews/single_view',
[
'title' => __('%s view', h($modelSelection)),
'data' => $data,
'fields' => $fields,
'side_panels' => [
[
'type' => 'html',
'html' => '<div id="analyst_data_thread" class="panel-container"></div>',
]
],
]
);
$object_uuid = Hash::get($data, $modelSelection . '.uuid');
$options = [
'container_id' => 'analyst_data_thread',
'object_type' => $modelSelection,
'object_uuid' => $object_uuid,
'shortDist' => $shortDist,
'notes' => $data[$modelSelection]['Note'] ?? [],
'opinions' => $data[$modelSelection]['Opinion'] ?? [],
'relationships' => $data[$modelSelection]['Relationship'] ?? [],
];
echo $this->element('genericElements/assetLoader', [
'js' => ['doT', 'moment.min'],
'css' => ['analyst-data',],
]);
echo $this->element('genericElements/Analyst_data/thread', $options);
?>
<?php if ($modelSelection == 'Relationship') : ?>
<script>
$(document).ready(function() {
$('#analyst_data_thread').find('li > a[href^="#relationship"]').tab('show')
})
</script>
<?php endif; ?>

View File

@ -0,0 +1,52 @@
<?php
$fieldDesc = array();
$fieldDesc['uuids'] = __('Enter a single or a list of UUIDs');
$fieldDesc['analyst_data_orgc'] = __('(Optional) The organisation that the event is associated with');
$fieldDesc['analyst_data_info'] = __('(Optional) The analyst data value that you would like to block');
$fieldDesc['comment'] = __('(Optional) Any comments you would like to add regarding this (or these) entries');
echo $this->element('genericElements/Form/genericForm', array(
'form' => $this->Form,
'data' => array(
'model' => 'AnalystDataBlocklist',
'title' => $action == 'add' ? __('Add block entry for Analyst Data') : __('Edit block entry for Analyst Data'),
'fields' => array(
array(
'disabled' => $action != 'add' ? 'disabled' : '',
'field' => 'uuids',
'class' => 'span6',
'label' => __('Analyst Data UUID'),
'type' => 'textarea',
'default' => isset($blockEntry['AnalystDataBlocklist']['analyst_data_uuid']) ? $blockEntry['AnalystDataBlocklist']['analyst_data_uuid'] : '',
),
array(
'field' => 'analyst_data_orgc',
'label' => __('Creating organisation'),
'class' => 'span6',
'type' => 'text',
'default' => isset($blockEntry['AnalystDataBlocklist']['analyst_data_orgc']) ? $blockEntry['AnalystDataBlocklist']['analyst_data_orgc'] : ''
),
array(
'field' => 'analyst_data_info',
'label' => __('Analyst Data value'),
'class' => 'span6',
'type' => 'text',
'default' => isset($blockEntry['AnalystDataBlocklist']['analyst_data_info']) ? $blockEntry['AnalystDataBlocklist']['analyst_data_info'] : ''
),
array(
'field' => 'comment',
'label' => __('Comment'),
'class' => 'span6',
'type' => 'text',
'default' => isset($blockEntry['AnalystDataBlocklist']['comment']) ? $blockEntry['AnalystDataBlocklist']['comment'] : ''
),
),
'submit' => array(
'ajaxSubmit' => ''
)
),
'fieldDesc' => $fieldDesc
));
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'analyst_data', 'menuItem' => 'index_blocklist'));
?>
<?php echo $this->Js->writeBuffer(); // Write cached scripts

View File

@ -0,0 +1,96 @@
<?php
echo '<div class="index">';
echo $this->element('/genericElements/IndexTable/index_table', array(
'data' => array(
'data' => $response,
'top_bar' => array(
'children' => array(
array(
'type' => 'simple',
'children' => array(
array(
'url' => sprintf('%s/analyst_data_blocklists/add/', $baseurl),
'text' => __('+ Add entry to blocklist'),
),
)
),
array(
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'searchall'
)
)
),
'fields' => array(
array(
'name' => __('Id'),
'sort' => 'id',
'class' => 'short',
'data_path' => 'AnalystDataBlocklist.id',
),
array(
'name' => __('Org'),
'class' => 'short',
'data_path' => 'AnalystDataBlocklist.analyst_data_orgc',
),
array(
'name' => __('Analyst Data UUID'),
'class' => 'short',
'data_path' => 'AnalystDataBlocklist.analyst_data_uuid',
),
array(
'name' => __('Created'),
'sort' => 'created',
'class' => 'short',
'data_path' => 'AnalystDataBlocklist.created',
),
array(
'name' => __('Analyst Data value'),
'sort' => 'value',
'class' => 'short',
'data_path' => 'AnalystDataBlocklist.analyst_data_info',
),
array(
'name' => __('Comment'),
'sort' => 'comment',
'class' => 'short',
'data_path' => 'AnalystDataBlocklist.comment',
),
),
'title' => __('Analyst Data Blocklist Index'),
'description' => __('List all analyst data that will be prevented to be created (also via synchronization) on this instance'),
'actions' => array(
array(
'title' => 'Delete',
'url' => $baseurl . '/analyst_data_blocklists/delete',
'url_params_data_paths' => array(
'AnalystDataBlocklist.id'
),
'postLink' => true,
'postLinkConfirm' => __('Are you sure you want to delete the entry?'),
'icon' => 'trash'
),
)
)
));
echo '</div>';
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'analyst_data', 'menuItem' => 'index_blocklist'));
?>
<script type="text/javascript">
var passedArgsArray = <?php echo $passedArgs; ?>;
if (passedArgsArray['context'] === undefined) {
passedArgsArray['context'] = 'pending';
}
$(document).ready(function() {
$('#quickFilterButton').click(function() {
runIndexQuickFilter('/context:' + passedArgsArray['context']);
});
$('#quickFilterField').on('keypress', function (e) {
if(e.which === 13) {
runIndexQuickFilter('/context:' + passedArgsArray['context']);
}
});
});
</script>

View File

@ -0,0 +1,37 @@
<?php
$edit = $this->request->params['action'] === 'edit' ? true : false;
$fields = [
[
'field' => 'element_uuid',
'class' => 'input span6',
'onChange' => 'alert(1);'
],
[
'field' => 'element_type',
'class' => 'input span6',
'options' => $dropdownData['types'],
'type' => 'dropdown'
],
[
'field' => 'description',
'class' => 'span6',
'type' => 'textarea'
]
];
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => null,
'model' => 'CollectionElement',
'title' => __('Add element to Collection'),
'fields' => $fields,
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}

View File

@ -0,0 +1,28 @@
<?php
$fields = [
[
'field' => 'collection_id',
'class' => 'input span6',
'options' => $dropdownData['collections'],
'type' => 'dropdown'
],
[
'field' => 'description',
'class' => 'span6',
'type' => 'textarea'
]
];
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => null,
'model' => 'CollectionElement',
'title' => __('Add element to Collection'),
'fields' => $fields,
'submit' => [
'action' => $this->request->params['action'],
//'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);

View File

@ -0,0 +1,61 @@
<?php
$fields = [
[
'name' => __('Id'),
'sort' => 'CollectionElement.id',
'data_path' => 'CollectionElement.id'
],
[
'name' => __('UUID'),
'data_path' => 'CollectionElement.uuid'
],
[
'name' => __('Element'),
'sort' => 'CollectionElement.element_type',
'element' => 'model',
'model_name' => 'CollectionElement.element_type',
'model_id' => 'CollectionElement.element_uuid'
],
[
'name' => __('Element type'),
'data_path' => 'CollectionElement.element_type'
],
[
'name' => __('Description'),
'data_path' => 'CollectionElement.description'
]
];
echo $this->element('genericElements/IndexTable/scaffold', [
'scaffold_data' => [
'data' => [
'data' => $data,
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'quickFilter'
]
]
],
'fields' => $fields,
'title' => empty($ajax) ? __('Collection element index') : false,
'actions' => [
[
'onclick' => sprintf(
'openGenericModal(\'%s/collectionElements/delete/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'CollectionElement.id',
'icon' => 'trash'
]
]
]
]
]);
?>

View File

@ -0,0 +1,51 @@
<?php
$edit = $this->request->params['action'] === 'edit' ? true : false;
$fields = [
[
'field' => 'name',
'class' => 'span6'
],
[
'field' => 'type',
'class' => 'input span6',
'options' => $dropdownData['types'],
'type' => 'dropdown'
],
[
'field' => 'description',
'class' => 'span6',
'type' => 'textarea'
],
[
'field' => 'distribution',
'class' => 'input',
'options' => $dropdownData['distributionLevels'],
'default' => isset($data['Collection']['distribution']) ? $data['Collection']['distribution'] : $initialDistribution,
'stayInLine' => 1,
'type' => 'dropdown'
],
[
'field' => 'sharing_group_id',
'class' => 'input',
'options' => $dropdownData['sgs'],
'label' => __("Sharing Group"),
'type' => 'dropdown'
]
];
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => __('Create collections to organise data shared by the community into buckets based on commonalities or as part of your research process. Collections are first class citizens and adhere to the same sharing rules as for example events do.'),
'model' => 'Collection',
'title' => $edit ? __('Edit collection') : __('Add new collection'),
'fields' => $fields,
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}

View File

@ -0,0 +1,96 @@
<?php
$fields = [
[
'name' => __('Id'),
'sort' => 'Collection.id',
'data_path' => 'Collection.id'
],
[
'name' => __('Name'),
'sort' => 'Collection.name',
'data_path' => 'Collection.name'
],
[
'name' => __('Organisation'),
'sort' => 'Orgc.name',
'data_path' => 'Orgc',
'element' => 'org'
],
[
'name' => __('Elements'),
'sort' => 'Collection.element_count',
'data_path' => 'Collection.element_count'
],
[
'name' => __('UUID'),
'data_path' => 'Collection.uuid'
],
[
'name' => __('Type'),
'data_path' => 'Collection.type'
],
[
'name' => __('Created'),
'sort' => 'Collection.created',
'data_path' => 'Collection.created'
],
[
'name' => __('Modified'),
'sort' => 'Collection.modified',
'data_path' => 'Collection.modified'
],
[
'name' => __('Distribution'),
'sort' => 'distribution',
'data_path' => 'Collection.distribution',
'element' => 'distribution_levels'
],
];
echo $this->element('genericElements/IndexTable/scaffold', [
'scaffold_data' => [
'data' => [
'data' => $data,
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'quickFilter'
]
]
],
'fields' => $fields,
'title' => empty($ajax) ? __('Collections index') : false,
'actions' => [
[
'url' => $baseurl . '/collections/view',
'url_params_data_paths' => ['Collection.id'],
'icon' => 'eye'
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/collections/edit/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'Collection.id',
'title' => __('Edit Collection'),
'icon' => 'edit'
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/collections/delete/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'Collection.id',
'icon' => 'trash'
]
]
]
]
]);
?>

View File

@ -0,0 +1,64 @@
<?php
echo $this->element(
'genericElements/SingleViews/single_view',
[
'title' => __('Collection view'),
'data' => $data,
'fields' => [
[
'key' => __('ID'),
'path' => 'Collection.id'
],
[
'key' => __('UUID'),
'path' => 'Collection.uuid'
],
[
'key' => __('Creator org'),
'path' => 'Collection.orgc_id',
'pathName' => 'Collection.Orgc.name',
'type' => 'model',
'model' => 'organisations'
],
[
'key' => __('Owner org'),
'path' => 'Collection.org_id',
'pathName' => 'Collection.Org.name',
'type' => 'model',
'model' => 'organisations'
],
[
'key' => __('Created'),
'path' => 'Collection.created'
],
[
'key' => __('Modified'),
'path' => 'Collection.modified'
],
[
'key' => __('Name'),
'path' => 'Collection.name'
],
[
'key' => __('Description'),
'path' => 'Collection.description'
],
[
'key' => __('Distribution'),
'path' => 'Collection.distribution',
'event_id_path' => 'Collection.id',
'disable_distribution_graph' => true,
'sg_path' => 'Collection.sharing_group_id',
'type' => 'distribution'
]
],
'children' => [
[
'url' => '/collectionElements/index/{{0}}/',
'url_params' => ['Collection.id'],
'title' => __('Collection elements'),
'elementId' => 'preview_elements_container'
]
]
]
);

View File

@ -68,11 +68,37 @@
</td>
<?php endif; ?>
<td class="short context hidden"><?= $objectId ?></td>
<td class="short context hidden uuid quickSelect"><?php echo h($object['uuid']); ?></td>
<td class="short context hidden uuid">
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short timestamp <?= $isNew ? 'bold red' : '' ?>" <?= $isNew ? 'title="' . __('Element or modification to an existing element has not been published yet.') . '"' : '' ?>><?= $this->Time->date($object['timestamp']) . ($isNew ? '*' : '') ?></td>
<td class="short context">
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
]);
?>
</td>
<?php
if (!empty($extended)):
?>

View File

@ -30,11 +30,37 @@ $objectId = intval($object['id']);
endif;
?>
<td class="short context hidden"><?= $objectId ?></td>
<td class="short context hidden uuid quickSelect"><?php echo h($object['uuid']); ?></td>
<td class="short context hidden uuid">
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short timestamp <?= $isNew ? 'bold red' : '' ?>" <?= $isNew ? 'title="' . __('Element or modification to an existing element has not been published yet.') . '"' : '' ?>><?= $this->Time->date($object['timestamp']) . ($isNew ? '*' : '') ?></td>
<td class="short context">
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
]);
?>
</td>
<?php
if ($extended):
?>

View File

@ -45,7 +45,19 @@
echo h($object['id']);
?>
</td>
<td class="short context hidden uuid quickSelect"><?= h($object['uuid']) ?></td>
<td class="short context hidden uuid">
<span class="quickSelect"><?php echo h($object['uuid']); ?></span>
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
@ -55,6 +67,20 @@
else echo '&nbsp';
?>
</td>
<td class="short context">
<?php
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
]);
?>
</td>
<?php
if ($extended):
?>

View File

@ -91,6 +91,7 @@
<th class="context hidden">UUID</th>
<th class="context hidden"><?= $this->Paginator->sort('first_seen', __('First seen')) ?> <i class="fas fa-arrow-right"></i> <?= $this->Paginator->sort('last_seen', __('Last seen')) ?></th>
<th><?php echo $this->Paginator->sort('timestamp', __('Date'), array('direction' => 'desc'));?></th>
<th class="context"><?= __('Context') ?></th>
<?php if ($extended): ?>
<th class="event_id"><?php echo $this->Paginator->sort('event_id', __('Event'));?></th>
<?php endif; ?>
@ -212,7 +213,6 @@ attributes or the appropriate distribution level. If you think there is a mistak
<?php
endif;
?>
setContextFields();
popoverStartup();
$('.select_attribute').prop('checked', false).click(function(e) {
if ($(this).is(':checked')) {

View File

@ -34,7 +34,7 @@
<div class="pull-right" style="position:relative;padding-top:9px;z-index:2;">
<?php
if (Configure::read('MISP.footer_logo')) {
echo $this->Html->image('custom/' . h(Configure::read('MISP.footer_logo')), array('alt' => 'Footer Logo', 'onerror' => "this.style.display='none';", 'style' => 'height:24px'));
echo '<img src="' . $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.footer_logo')) . '" alt="' . __('Footer logo') . '" style="height:24px" onerror="this.style.display=\'none\';">';
}
?>
</div>

View File

@ -0,0 +1,86 @@
<?php
$seed = mt_rand();
$notes = $analyst_data['notes'] ?? [];
$opinions = $analyst_data['opinions'] ?? [];
$relationships = $analyst_data['relationships'] ?? [];
$notesOpinions = array_merge($notes, $opinions);
$notesOpinionsRelationships = array_merge($notesOpinions, $relationships);
if(!function_exists("countNotes")) {
function countNotes($notesOpinions) {
$notesTotalCount = count($notesOpinions);
$notesCount = 0;
$relationsCount = 0;
foreach ($notesOpinions as $notesOpinion) {
if ($notesOpinion['note_type'] == 2) { // relationship
$relationsCount += 1;
} else {
$notesCount += 1;
}
if (!empty($notesOpinion['Note'])) {
$nestedCounts = countNotes($notesOpinion['Note']);
$notesTotalCount += $nestedCounts['total'];
$notesCount += $nestedCounts['notesOpinions'];
$relationsCount += $nestedCounts['relations'];
}
if (!empty($notesOpinion['Opinion'])) {
$nestedCounts = countNotes($notesOpinion['Opinion']);
$notesTotalCount += $nestedCounts['total'];
$notesCount += $nestedCounts['notesOpinions'];
$relationsCount += $nestedCounts['relations'];
}
}
return ['total' => $notesTotalCount, 'notesOpinions' => $notesCount, 'relations' => $relationsCount];
}
}
$counts = countNotes($notesOpinions);
$notesOpinionCount = $counts['notesOpinions'];
$relationshipsCount = count($relationships);
?>
<?php if (empty($notesOpinions) && empty($relationshipsCount)): ?>
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer node-opener-<?= $seed ?>" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?php else: ?>
<span class="label label-info useCursorPointer node-opener-<?= $seed ?> highlight-on-hover">
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?= $notesOpinionCount; ?>
<i class="<?= $this->FontAwesome->getClass('project-diagram') ?> useCursorPointer" title="<?= __('Relationships for this UUID') ?>"></i>
<?= $relationshipsCount; ?>
</span>
<?php endif; ?>
<script>
$(document).ready(function() {
$('.node-opener-<?= $seed ?>').click(function() {
openNotes(this)
})
function adjustPopoverPosition() {
var $popover = $('.popover:last');
$popover.css('top', Math.max($popover.position().top, 50) + 'px')
}
function openNotes(clicked) {
openPopover(clicked, renderedNotes<?= $seed ?>, undefined, undefined, function() {
adjustPopoverPosition()
$(clicked).removeClass('have-a-popover') // avoid closing the popover if a confirm popover (like the delete one) is called
})
}
})
</script>
<?php
echo $this->element('genericElements/Analyst_data/thread', [
'seed' => $seed,
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
'object_type' => $object_type,
'object_uuid' => $object_uuid,
'shortDist' => $shortDist,
]);
?>

View File

@ -0,0 +1,55 @@
<?php
echo $this->element('genericElements/assetLoader', [
'css' => ['analyst-data',],
]);
$seed = mt_rand();
$forceInline = empty($forceInline) ? false : !empty($forceInline);
$opinion_color_scale_100 = ['rgb(164, 0, 0)', 'rgb(166, 15, 0)', 'rgb(169, 25, 0)', 'rgb(171, 33, 0)', 'rgb(173, 40, 0)', 'rgb(175, 46, 0)', 'rgb(177, 52, 0)', 'rgb(179, 57, 0)', 'rgb(181, 63, 0)', 'rgb(183, 68, 0)', 'rgb(186, 72, 0)', 'rgb(188, 77, 0)', 'rgb(190, 82, 0)', 'rgb(191, 86, 0)', 'rgb(193, 90, 0)', 'rgb(195, 95, 0)', 'rgb(197, 98, 0)', 'rgb(198, 102, 0)', 'rgb(200, 106, 0)', 'rgb(201, 110, 0)', 'rgb(203, 114, 0)', 'rgb(204, 118, 0)', 'rgb(206, 121, 0)', 'rgb(208, 125, 0)', 'rgb(209, 128, 0)', 'rgb(210, 132, 0)', 'rgb(212, 135, 0)', 'rgb(213, 139, 0)', 'rgb(214, 143, 0)', 'rgb(216, 146, 0)', 'rgb(217, 149, 0)', 'rgb(218, 153, 0)', 'rgb(219, 156, 0)', 'rgb(220, 160, 0)', 'rgb(222, 163, 0)', 'rgb(223, 166, 0)', 'rgb(224, 169, 0)', 'rgb(225, 173, 0)', 'rgb(226, 176, 0)', 'rgb(227, 179, 0)', 'rgb(228, 182, 0)', 'rgb(229, 186, 0)', 'rgb(230, 189, 0)', 'rgb(231, 192, 0)', 'rgb(232, 195, 0)', 'rgb(233, 198, 0)', 'rgb(234, 201, 0)', 'rgb(235, 204, 0)', 'rgb(236, 207, 0)', 'rgb(237, 210, 0)', 'rgb(237, 212, 0)', 'rgb(234, 211, 0)', 'rgb(231, 210, 0)', 'rgb(229, 209, 1)', 'rgb(226, 208, 1)', 'rgb(223, 207, 1)', 'rgb(220, 206, 1)', 'rgb(218, 204, 1)', 'rgb(215, 203, 2)', 'rgb(212, 202, 2)', 'rgb(209, 201, 2)', 'rgb(206, 200, 2)', 'rgb(204, 199, 2)', 'rgb(201, 198, 3)', 'rgb(198, 197, 3)', 'rgb(195, 196, 3)', 'rgb(192, 195, 3)', 'rgb(189, 194, 3)', 'rgb(186, 193, 3)', 'rgb(183, 192, 4)', 'rgb(180, 190, 4)', 'rgb(177, 189, 4)', 'rgb(174, 188, 4)', 'rgb(171, 187, 4)', 'rgb(168, 186, 4)', 'rgb(165, 185, 4)', 'rgb(162, 183, 4)', 'rgb(159, 182, 4)', 'rgb(156, 181, 4)', 'rgb(153, 180, 4)', 'rgb(149, 179, 5)', 'rgb(146, 178, 5)', 'rgb(143, 177, 5)', 'rgb(139, 175, 5)', 'rgb(136, 174, 5)', 'rgb(133, 173, 5)', 'rgb(130, 172, 5)', 'rgb(126, 170, 5)', 'rgb(123, 169, 5)', 'rgb(119, 168, 5)', 'rgb(115, 167, 5)', 'rgb(112, 165, 6)', 'rgb(108, 164, 6)', 'rgb(104, 163, 6)', 'rgb(100, 162, 6)', 'rgb(96, 160, 6)', 'rgb(92, 159, 6)', 'rgb(88, 157, 6)', 'rgb(84, 156, 6)', 'rgb(80, 155, 6)', 'rgb(78, 154, 6)'];
$opinion = min(100, max(0, intval($opinion)));
$opinionText = ($opinion >= 81) ? __("Strongly Agree") : (($opinion >= 61) ? __("Agree") : (($opinion >= 41) ? __("Neutral") : (($opinion >= 21) ? __("Disagree") : __("Strongly Disagree"))));
$opinionColor = $opinion == 50 ? '#333' : ( $opinion > 50 ? '#468847' : '#b94a48');
?>
<div class="main-container-<?= $seed ?>" style="margin: 0.75rem 0 0.25rem 0;" title="<?= __('Opinion:') ?> 50 /100">
<div class="opinion-gradient-container" style="width: 10rem; height: 6px; position: relative;">
<span class="opinion-gradient-dot"></span>
<div class="opinion-gradient opinion-gradient-negative"></div>
<div class="opinion-gradient opinion-gradient-positive"></div>
</div>
<span style="line-height: 1em; margin-left: 0.25rem; margin-top: -3px;">
<b class="opinion-text" style="margin-left: 0.5rem; color: <?= $opinionColor ?>"><?= $opinionText ?></b>
<b class="opinion-value" style="margin-left: 0.25rem; color: <?= $opinionColor ?>"><?= $opinion ?></b>
<span style="font-size: 0.7em; font-weight: lighter; color: #999">/100</span>
</span>
</div>
<style>
.main-container-<?= $seed ?> {
<?php if ($forceInline): ?>
display: flex;
flex-direction: row;
<?php endif; ?>
}
.main-container-<?= $seed ?> .opinion-gradient-<?= $opinion >= 50 ? 'negative' : 'positive' ?> {
opacity: 0;
}
.main-container-<?= $seed ?> .opinion-gradient-dot {
left: calc(<?= $opinion ?>% - 6px);
background-color: <?= $opinion == 50 ? '#555' : $opinion_color_scale_100[$opinion] ?>;
}
<?php if ($opinion >= 50): ?>
.main-container-<?= $seed ?> .opinion-gradient-positive {
-webkit-mask-image: linear-gradient(90deg, black 0 <?= abs(-50 + $opinion)*2 ?>%, transparent <?= abs(-50 + $opinion)*2 ?>% 100%);
mask-image: linear-gradient(90deg, black 0 <?= abs(-50 + $opinion)*2 ?>%, transparent <?= abs(-50 + $opinion)*2 ?>% 100%);
}
<?php else: ?>
.main-container-<?= $seed ?> .opinion-gradient-negative {
-webkit-mask-image: linear-gradient(90deg, transparent 0 <?= 100-(abs(-50 + $opinion)*2) ?>%, black <?= 100-(abs(-50 + $opinion)*2) ?>% 100%);
mask-image: linear-gradient(90deg, transparent 0 <?= 100-(abs(-50 + $opinion)*2) ?>%, black <?= 100-(abs(-50 + $opinion)*2) ?>% 100%);
}
<?php endif; ?>
</style>

View File

@ -0,0 +1,608 @@
<?php
$URL_ADD = '/analystData/add/';
$URL_EDIT = '/analystData/edit/';
$URL_DELETE = '/analystData/delete/';
$seed = isset($seed) ? $seed : mt_rand();
$notes = !empty($notes) ? $notes : [];
$opinions = !empty($opinions) ? $opinions : [];
$relationships = !empty($relationships) ? $relationships : [];
$related_objects = [
'Attribute' => [],
'Event' => [],
'Object' => [],
'Organisation' => [],
'GalaxyCluster' => [],
'Galaxy' => [],
'Note' => [],
'Opinion' => [],
'SharingGroup' => [],
];
foreach ($relationships as $relationship) {
if (!empty($relationship['related_object'][$relationship['related_object_type']])) {
$related_objects[$relationship['related_object_type']][$relationship['related_object_uuid']] = $relationship['related_object'][$relationship['related_object_type']];
}
}
$notesOpinions = array_merge($notes, $opinions);
$notesOpinionsRelationships = array_merge($notesOpinions, $relationships);
?>
<script>
if (!window.shortDist) {
var shortDist = <?= json_encode($shortDist) ?>;
}
var renderedNotes<?= $seed ?> = null
function renderNotes(notes, relationship_related_object) {
var renderedNotesArray = []
if (notes.length == 0) {
var emptyHtml = '<span style="text-align: center; color: #777;"><?= __('No notes for this UUID.') ?></span>'
renderedNotesArray.push(emptyHtml)
} else {
notes.forEach(function(note) {
var noteHtml = renderNote(note, relationship_related_object)
if (note.Opinion && note.Opinion.length > 0) { // The notes has more notes attached
noteHtml += replyNoteTemplate({notes_html: renderNotes(note.Opinion, relationship_related_object), })
}
if (note.Note && note.Note.length > 0) { // The notes has more notes attached
noteHtml += replyNoteTemplate({notes_html: renderNotes(note.Note, relationship_related_object), })
}
if (note._max_depth_reached) {
noteHtml += replyNoteTemplate({notes_html: maxDepthReachedTemplate({note: note}), })
}
renderedNotesArray.push(noteHtml)
});
}
return renderedNotesArray.join('')
}
function renderNote(note, relationship_related_object) {
note.modified_relative = note.modified ? moment(note.modified).fromNow() : note.modified
note.created_relative = note.created ? moment(note.created).fromNow() : note.created
note.modified = note.modified ? (new Date(note.modified)).toLocaleString() : note.modified
note.created = note.created ? (new Date(note.created)).toLocaleString() : note.created
note.distribution_text = note.distribution != 4 ? shortDist[note.distribution] : note.SharingGroup.name
note.distribution_color = note.distribution == 0 ? '#ff0000' : (note.distribution == 4 ? '#0088cc' : '#000')
note.authors = Array.isArray(note.authors) ? note.authors.join(', ') : note.authors;
if (note.note_type == 0) { // analyst note
note.content = analystTemplate(note)
} else if (note.note_type == 1) { // opinion
note.opinion_color = note.opinion == 50 ? '#333' : ( note.opinion > 50 ? '#468847' : '#b94a48');
note.opinion_text = (note.opinion >= 81) ? '<?= __("Strongly Agree") ?>' : ((note.opinion >= 61) ? '<?= __("Agree") ?>' : ((note.opinion >= 41) ? '<?= __("Neutral") ?>' : ((note.opinion >= 21) ? '<?= __("Disagree") ?>' : '<?= __("Strongly Disagree") ?>')))
note.content = opinionTemplate(note)
} else if (note.note_type == 2) {
note.content = renderRelationshipEntryFromType(note, relationship_related_object)
}
var noteHtml = baseNoteTemplate(note)
return noteHtml
}
function getURLFromRelationship(note) {
if (note.related_object_type == 'Event') {
return baseurl + '/events/view/' + note.related_object_uuid
} else if (note.related_object_type == 'Attribute') {
return note?.attribute?.event_id ? baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid : '#'
} else if (note.related_object_type == 'Object') {
return note?.object?.event_id ? baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid : '#'
}
return '#'
}
function renderRelationshipEntryFromType(note, relationship_related_object) {
var contentHtml = ''
var template = doT.template('\
<span style="border: 1px solid #ddd !important; border-radius: 3px; padding: 0.25rem;"> \
<span class="ellipsis-overflow" style="max-width: 12em;">{{!it.related_object_type}}</span> \
:: \
<span class="ellipsis-overflow" style="max-width: 12em;">{{!it.related_object_uuid}}</span> \
</span> \
')
var templateEvent = doT.template('\
<span class="misp-element-wrapper attribute" title="<?= __('Event') ?>"> \
<span class="bold"> \
<span class="attr-type"><span><i class="<?= $this->FontAwesome->getClass('envelope') ?>"></i></span></span> \
<span class=""><span class="attr-value"> \
<span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.urlEvent}}" target="_blank">{{!it.content}}</a></span> \
</span></span> \
</span> \
</span> \
')
if (note.related_object_type == 'Event' && relationship_related_object.Event[note.related_object_uuid]) {
note.event = relationship_related_object.Event[note.related_object_uuid]
template = doT.template(templateEvent({content: '{{!it.event.info}}', urlEvent: '{{!it.url}}'}))
} else if (note.related_object_type == 'Attribute' && relationship_related_object.Attribute[note.related_object_uuid]) {
var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'})
note.attribute = relationship_related_object.Attribute[note.related_object_uuid]
if (note.attribute.object_relation !== undefined && note.attribute.object_relation !== null) {
template = doT.template('\
' + event + ' \
<b>↦</b> \
<span class="misp-element-wrapper object"> \
<span class="bold"> \
<span class="obj-type"> \
<span class="object-name" title="<?= __('Object') ?>">{{!it.attribute.Object.name}}</span> \
↦ <span class="object-attribute-type" title="<?= __('Object Relation') ?>">{{!it.attribute.object_relation}}</span> \
</span> \
<span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.attribute.value}}</a></span></span> \
</span> \
')
} else if (relationship_related_object.Attribute[note.related_object_uuid]) {
var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'})
template = doT.template('\
' + event + ' \
<b>↦</b> \
<span class="misp-element-wrapper attribute"> \
<span class="bold"> \
<span class="attr-type"><span title="<?= __('Attribute') ?>">{{!it.attribute.type}}</span></span> \
<span class="blue"><span class="attr-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.attribute.value}}</a></span></span></span> \
</span> \
</span> \
')
}
} else if (note.related_object_type == 'Object') {
var event = templateEvent({content: '{{!it.object.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.object.event_id}}'})
note.object = relationship_related_object.Object[note.related_object_uuid]
template = doT.template('\
' + event + ' \
<b>↦</b> \
<span class="misp-element-wrapper object"> \
<span class="bold"> \
<span class="obj-type"> \
<i class="<?= $this->FontAwesome->getClass('cubes') ?>" title="<?= __('Object') ?>" style="margin: 0 0 0 0.25rem;"></i> \
<span>{{!it.object.name}}</span> \
</span> \
<span class="blue"><span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.object.id}}</a></span></span></span> \
</span> \
</span> \
')
}
note.url = getURLFromRelationship(note)
contentHtml = template(note)
return relationshipDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
}
var noteFilteringTemplate = '\
<div class="btn-group notes-filtering-container" style="margin-bottom: 0.5rem"> \
<btn class="btn btn-small btn-primary" href="#" onclick="filterNotes(this, \'all\')"><?= __('All notes') ?></btn> \
<btn class="btn btn-small btn-inverse" href="#" onclick="filterNotes(this, \'org\')"><?= __('Organisation notes') ?></btn> \
<btn class="btn btn-small btn-inverse" href="#" onclick="filterNotes(this, \'notorg\')"><?= __('Non-Org notes') ?></btn> \
</div> \
'
var baseNoteTemplate = doT.template('\
<div id="{{!it.note_type_name}}-{{!it.id}}" \
class="analyst-note" \
style="display: flex; flex-direction: row; align-items: center; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 1px 5px -2px rgb(0 0 0 / 0.5); border-radius: 0.25rem; padding: 0.25rem; margin-bottom: 0.0rem; background-color: #fff; transition: ease-out opacity 0.5s;" \
data-org-uuid="{{!it.orgc_uuid}}" \
> \
<div style="flex-grow: 1;"> \
<div style="display: flex; flex-direction: column;"> \
<div style="display: flex; min-width: 250px; gap: 0.5rem;"> \
<img src="<?= $baseurl ?>/img/orgs/{{!it.Orgc.id}}.png" width="20" height="20" class="orgImg" style="width: 20px; height: 20px;" onerror="this.remove()" alt="Organisation logo"></object> \
<span style="margin-left: 0rem; margin-right: 0.5rem;"> \
<span>{{!it.Orgc.name}}</span> \
<i class="<?= $this->FontAwesome->getClass('angle-right') ?>" style="color: #999; margin: 0 0.25rem;"></i> \
<b>{{!it.authors}}</b> \
</span> \
<span style="display: inline-block; font-weight: lighter; color: #999">{{!it.modified_relative}} • {{!it.modified}}</span> \
<span style="margin-left: 0.5rem; flex-grow: 1; text-align: right; color: {{!it.distribution_color}}"> \
{{? it.distribution == 4 }} \
<a href="<?= $baseurl ?>/sharingGroups/view/{{!it.SharingGroup.id}}" target="_blank">{{!it.distribution_text}}</a> \
{{??}} \
{{!it.distribution_text}} \
{{?}} \
</span> \
<span class="action-button-container" style="margin-left: auto; display: flex; gap: 0.2rem;"> \
{{? 1 == <?= $me['Role']['perm_modify'] ? 1 : 0 ?> }} \
<span role="button" onclick="addOpinion(this, \'{{!it.uuid}}\', \'{{!it.note_type_name}}\')" title="<?= __('Add an opinion to this note') ?>"><i class="<?= $this->FontAwesome->getClass('gavel') ?> useCursorPointer"></i></span> \
{{?}} \
{{? 1 == <?= $me['Role']['perm_modify'] ? 1 : 0 ?> }} \
<span role="button" onclick="addNote(this, \'{{!it.uuid}}\', \'{{!it.note_type_name}}\')" title="<?= __('Add a note to this ') ?>{{!it.note_type_name}}"><i class="<?= $this->FontAwesome->getClass('comment-alt') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._canEdit }} \
<span role="button" onclick="editNote(this, {{!it.id}}, \'{{!it.note_type_name}}\')" title="<?= __('Edit this note') ?>"><i class="<?= $this->FontAwesome->getClass('edit') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._canEdit }} \
<span role="button" onclick="deleteNote(this, {{!it.id}})" title="<?= __('Delete this note') ?>" href="<?= $baseurl . $URL_DELETE ?>{{!it.note_type_name}}/{{!it.id}}"><i class="<?= $this->FontAwesome->getClass('trash') ?> useCursorPointer"></i></span> \
{{?}} \
</span> \
</div> \
<div style="">{{=it.content}}</div> \
</div> \
</div> \
</div> \
')
var analystTemplate = doT.template('\
<div style="max-width: 40vw; margin-top: 0.5rem; font-size:"> \
{{!it.note}} \
</div> \
')
var opinionGradient = '\
<div class="opinion-gradient-container" style="width: 10rem; height: 6px;">\
<span class="opinion-gradient-dot"></span> \
<div class="opinion-gradient opinion-gradient-negative"></div> \
<div class="opinion-gradient opinion-gradient-positive"></div> \
</div> \
'
var opinionTemplate = doT.template('\
<div style="margin: 0.75rem 0 0.25rem 0; display: flex; flex-direction: row;" title="<?= __('Opinion:') ?> {{!it.opinion}} /100"> \
' + opinionGradient + ' \
<span style="line-height: 1em; margin-left: 0.25rem; margin-top: -3px;"> \
<b style="margin-left: 0.5rem; color: {{!it.opinion_color}}">{{!it.opinion_text}}</b> \
<b style="margin-left: 0.25rem; color: {{!it.opinion_color}}">{{!it.opinion}}</b> \
<span style="font-size: 0.7em; font-weight: lighter; color: #999">/100</span> \
</span> \
</div> \
{{? it.comment }} \
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
{{!it.comment}} \
</div> \
{{?}} \
')
var relationshipDefaultEntryTemplate = doT.template('\
<div style="max-width: 40vw; margin: 0.5rem 0 0.5rem 0.25rem;"> \
<div style="display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;"> \
<i class="<?= $this->FontAwesome->getClass('minus') ?>" style="font-size: 1.5em; color: #555"></i> \
<span style="text-wrap: nowrap; padding: 0 0.25rem; border: 2px solid #555; border-radius: 0.25rem; max-width: 20rem; overflow-x: hidden; text-overflow: ellipsis;"> \
{{? it.relationship_type }} \
{{!it.relationship_type}} \
{{??}} \
<i style="font-weight: lighter; color: #999;"> - empty -</i> \
{{?}} \
</span> \
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
<div style="margin-left: 0.5rem;">{{=it.content}}</div> \
</div> \
{{? it.comment }} \
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
{{!it.comment}} \
</div> \
{{?}} \
</div> \
')
var replyNoteTemplate = doT.template('\
<span class="reply-to-note-collapse-button reply-to-group" onclick="$(this).toggleClass(\'collapsed\').next().toggle()" title="<?= __('Toggle annotation for this note') ?>" \
style="width: 12px; height: 12px; border-radius: 50%; border: 1px solid #0035dc20; background: #ccccccdd; box-sizing: border-box; line-height: 12px; padding: 0 1px; cursor: pointer; margin: calc(-0.5rem - 6px) 0 calc(-0.5rem - 6px) -1px; z-index: 2;" \
> \
<i class="<?= $this->FontAwesome->getClass('angle-up') ?>" style="line-height: 8px;"></i> \
</span> \
<div class="reply-to-note reply-to-group" style="position: relative; display: flex; flex-direction: column; gap: 0.5rem; margin-left: 3px; border-left: 4px solid #ccccccaa; background: #0035dc10; padding: 0.5rem; border-radius: 5px; border-top-left-radius: 0;"> \
{{=it.notes_html}} \
</div> \
')
var maxDepthReachedTemplate = doT.template('\
<div class="max-depth-container"> \
<div> \
<span style="font-weight: lighter; color: #999;"> \
- Max depth reached, there is at least one entry remaining - \
<a href="<?= $baseurl ?>/analystData/view/{{!it.note.note_type_name}}/{{!it.note.id}}" target="_blank"> \
<i class="<?= $this->FontAwesome->getClass('search') ?>"></i> \
<?= __('View entry') ?> \
</a> \
</span> \
</div> \
<div> \
<span> \
<a onclick="fetchMoreNotes(this, \'{{!it.note.note_type_name}}\', \'{{!it.note.uuid}}\')" target="_blank" class="useCursorPointer"> \
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> \
<?= __('Load more notes') ?> \
</a> \
</span> \
</div> \
</div> \
')
function filterNotes(clicked, filter) {
$(clicked).closest('.notes-filtering-container').find('.btn').addClass('btn-inverse').removeClass('btn-primary')
$(clicked).removeClass('btn-inverse').addClass('btn-primary')
var $container = $(clicked).parent().parent().find('.all-notes')
var $addButtonContainer = $('#add-button-container');
if (filter == 'notorg') {
$addButtonContainer.hide()
} else {
$addButtonContainer.show()
}
$container.find('.analyst-note').show()
$container.find('.reply-to-group').show()
$container.find('.analyst-note').filter(function() {
var $note = $(this)
// WEIRD. reply-to-group is not showing up!
if (filter == 'all') {
return false
} else if (filter == 'org') {
var shouldHide = $note.data('org-uuid') != '<?= $me['Organisation']['uuid'] ?>'
if (shouldHide && $note.next().hasClass('reply-to-group')) { // Also hide reply to button and container
$note.next().hide().next().hide()
}
return shouldHide
} else if (filter == 'notorg') {
var shouldHide = $note.data('org-uuid') == '<?= $me['Organisation']['uuid'] ?>'
if (shouldHide && $note.next().hasClass('reply-to-group')) { // Also hide reply to button and container
$note.next().hide().next().hide()
}
return shouldHide
}
}).hide()
}
function fetchMoreNotes(clicked, noteType, uuid) {
var depth = 3
var $maxDepthContainer = $(clicked).closest('.max-depth-container')
var url = '<?= $baseurl ?>/analystData/getChildren/' + noteType + '/' + uuid + '/' + depth + '.json'
$.ajax({
beforeSend: function () {
$maxDepthContainer.css('filter', 'blur(2px)')
},
cache: false,
success:function (data, textStatus) {
var notesOpinions = [].concat(data.Note ?? [], data.Opinion ?? [])
var renderedAdditionalNotes = renderNotes(notesOpinions, [])
$maxDepthContainer[0].outerHTML = renderedAdditionalNotes
},
error:function(xhr) {
showMessage('fail', 'Could not fetch additional analyst data.');
},
complete: function() {
$maxDepthContainer.css('filter', 'unset')
},
url: url
});
}
(function() {
var notes = <?= json_encode($notesOpinions) ?>;
var relationships = <?= json_encode($relationships) ?>;
var relationship_related_object = <?= json_encode($related_objects) ?>;
var container_id = false
<?php if (isset($container_id)): ?>
container_id = '<?= h($container_id) ?>'
<?php endif; ?>
var nodeContainerTemplate = doT.template('\
<div> \
<ul class="nav nav-tabs" style="margin-bottom: 10px;"> \
<li class="active"><a href="#notes-<?= $seed ?>" data-toggle="tab"><?= __('Notes & Opinions') ?></a></li> \
<li><a href="#relationships-<?= $seed ?>" data-toggle="tab"><?= __('Relationships') ?></a></li> \
</ul> \
<div class="tab-content" style="padding: 0.25rem; max-width: 1200px; min-width: 400px;"> \
<div id="notes-<?= $seed ?>" class="tab-pane active"> \
' + noteFilteringTemplate + ' \
<div style="display: flex; flex-direction: column; gap: 0.5rem;" class="all-notes">{{=it.content_notes}}</div>\
</div> \
<div id="relationships-<?= $seed ?>" class="tab-pane"> \
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships}}</div>\
</div> \
</div> \
</div> \
')
function renderAllNotesWithForm(relationship_related_object) {
var buttonContainer = '<div id="add-button-container" style="margin-top: 0.5rem;">' + addNoteButton + addOpinionButton + '</div>'
renderedNotes<?= $seed ?> = nodeContainerTemplate({
content_notes: renderNotes(notes.filter(function(note) { return note.note_type != 2}), relationship_related_object) + buttonContainer,
content_relationships: renderNotes(relationships, relationship_related_object) + addRelationshipButton,
})
if (container_id) {
$('#' + container_id).html(renderedNotes<?= $seed ?>)
}
}
var addNoteButton = '<button class="btn btn-small btn-block btn-primary" type="button" onclick="createNewNote(this, \'<?= $object_type ?>\', \'<?= $object_uuid ?>\')"> \
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> <?= __('Add a note') ?> \
</button>'
var addOpinionButton = '<button class="btn btn-small btn-block btn-primary" style="margin-top: 2px;" type="button" onclick="createNewOpinion(this, \'<?= $object_type ?>\', \'<?= $object_uuid ?>\')"> \
<i class="<?= $this->FontAwesome->getClass('gavel') ?>"></i> <?= __('Add an opinion') ?> \
</button>'
var addRelationshipButton = '<button class="btn btn-small btn-block btn-primary" type="button" onclick="createNewRelationship(this, \'<?= $object_type ?>\', \'<?= $object_uuid ?>\')"> \
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> <?= __('Add a relationship') ?> \
</button>'
$(document).ready(function() {
renderAllNotesWithForm(relationship_related_object)
})
})()
function createNewNote(clicked, object_type, object_uuid) {
note_type = 'Note';
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + object_uuid + '/' + object_type)
}
function createNewOpinion(clicked, object_type, object_uuid) {
note_type = 'Opinion';
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + object_uuid + '/' + object_type)
}
function createNewRelationship(clicked, object_type, object_uuid) {
note_type = 'Relationship';
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + object_uuid + '/' + object_type)
}
function addNote(clicked, note_uuid, object_type) {
note_type = 'Note';
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + note_uuid + '/' + object_type)
}
function addOpinion(clicked, note_uuid, object_type) {
note_type = 'Opinion';
openGenericModal(baseurl + '<?= $URL_ADD ?>' + note_type + '/' + note_uuid + '/' + object_type)
}
function editNote(clicked, note_id, note_type) {
openGenericModal(baseurl + '<?= $URL_EDIT ?>' + note_type + '/' + note_id)
}
function deleteNote(clicked, note_id) {
var deletionSuccessCallback = function(data) {
$(clicked).closest('.analyst-note').remove()
}
popoverConfirm(clicked, '<?= __('Confirm deletion of this note') ?>', undefined, deletionSuccessCallback)
}
function replaceNoteInUI(data) {
var noteType = Object.keys(data)[0]
var noteHTMLID = '#' + data[noteType].note_type_name + '-' + data[noteType].id
var $noteToReplace = $(noteHTMLID)
if ($noteToReplace.length == 1) {
var compiledUpdatedNote = renderNote(data[noteType])
$noteToReplace[0].outerHTML = compiledUpdatedNote
$(noteHTMLID).css({'opacity': 0})
setTimeout(() => {
$(noteHTMLID).css({'opacity': 1})
}, 750);
}
}
</script>
<style>
.action-button-container > span {
visibility: hidden;
}
.analyst-note:hover .action-button-container > span {
visibility: visible;
}
.reply-to-note-collapse-button.collapsed {
margin-bottom: -0.25rem !important;
}
.v-bar-text-opinion::before {
content: '';
margin-right: 5px;
margin-left: 2px;
border-left: 1px solid;
border-bottom: 1px solid;
height: 1.3rem;
width: 5px;
display: inline-block;
float: left;
margin-top: -12px;
border-color: #969696;
}
.reply-to-note-collapse-button.collapsed > i {
transform: rotate(180deg);
}
<?php
if(!function_exists("genStyleForOpinionNotes")) {
function genStyleForOpinionNotes($notes) {
foreach ($notes as $note) {
genStyleForOpinionNote($note);
if (!empty($note['Note'])) {
genStyleForOpinionNotes($note['Note']);
}
if (!empty($note['Opinion'])) {
genStyleForOpinionNotes($note['Opinion']);
}
}
}
}
if(!function_exists("genStyleForOpinionNote")) {
function genStyleForOpinionNote($note) {
if ($note['note_type'] != 1) { // opinion
return;
}
$opinion_color_scale_100 = ['rgb(164, 0, 0)', 'rgb(166, 15, 0)', 'rgb(169, 25, 0)', 'rgb(171, 33, 0)', 'rgb(173, 40, 0)', 'rgb(175, 46, 0)', 'rgb(177, 52, 0)', 'rgb(179, 57, 0)', 'rgb(181, 63, 0)', 'rgb(183, 68, 0)', 'rgb(186, 72, 0)', 'rgb(188, 77, 0)', 'rgb(190, 82, 0)', 'rgb(191, 86, 0)', 'rgb(193, 90, 0)', 'rgb(195, 95, 0)', 'rgb(197, 98, 0)', 'rgb(198, 102, 0)', 'rgb(200, 106, 0)', 'rgb(201, 110, 0)', 'rgb(203, 114, 0)', 'rgb(204, 118, 0)', 'rgb(206, 121, 0)', 'rgb(208, 125, 0)', 'rgb(209, 128, 0)', 'rgb(210, 132, 0)', 'rgb(212, 135, 0)', 'rgb(213, 139, 0)', 'rgb(214, 143, 0)', 'rgb(216, 146, 0)', 'rgb(217, 149, 0)', 'rgb(218, 153, 0)', 'rgb(219, 156, 0)', 'rgb(220, 160, 0)', 'rgb(222, 163, 0)', 'rgb(223, 166, 0)', 'rgb(224, 169, 0)', 'rgb(225, 173, 0)', 'rgb(226, 176, 0)', 'rgb(227, 179, 0)', 'rgb(228, 182, 0)', 'rgb(229, 186, 0)', 'rgb(230, 189, 0)', 'rgb(231, 192, 0)', 'rgb(232, 195, 0)', 'rgb(233, 198, 0)', 'rgb(234, 201, 0)', 'rgb(235, 204, 0)', 'rgb(236, 207, 0)', 'rgb(237, 210, 0)', 'rgb(237, 212, 0)', 'rgb(234, 211, 0)', 'rgb(231, 210, 0)', 'rgb(229, 209, 1)', 'rgb(226, 208, 1)', 'rgb(223, 207, 1)', 'rgb(220, 206, 1)', 'rgb(218, 204, 1)', 'rgb(215, 203, 2)', 'rgb(212, 202, 2)', 'rgb(209, 201, 2)', 'rgb(206, 200, 2)', 'rgb(204, 199, 2)', 'rgb(201, 198, 3)', 'rgb(198, 197, 3)', 'rgb(195, 196, 3)', 'rgb(192, 195, 3)', 'rgb(189, 194, 3)', 'rgb(186, 193, 3)', 'rgb(183, 192, 4)', 'rgb(180, 190, 4)', 'rgb(177, 189, 4)', 'rgb(174, 188, 4)', 'rgb(171, 187, 4)', 'rgb(168, 186, 4)', 'rgb(165, 185, 4)', 'rgb(162, 183, 4)', 'rgb(159, 182, 4)', 'rgb(156, 181, 4)', 'rgb(153, 180, 4)', 'rgb(149, 179, 5)', 'rgb(146, 178, 5)', 'rgb(143, 177, 5)', 'rgb(139, 175, 5)', 'rgb(136, 174, 5)', 'rgb(133, 173, 5)', 'rgb(130, 172, 5)', 'rgb(126, 170, 5)', 'rgb(123, 169, 5)', 'rgb(119, 168, 5)', 'rgb(115, 167, 5)', 'rgb(112, 165, 6)', 'rgb(108, 164, 6)', 'rgb(104, 163, 6)', 'rgb(100, 162, 6)', 'rgb(96, 160, 6)', 'rgb(92, 159, 6)', 'rgb(88, 157, 6)', 'rgb(84, 156, 6)', 'rgb(80, 155, 6)', 'rgb(78, 154, 6)'];
$opinion = min(100, max(0, intval($note['opinion'])));
?>
#Opinion-<?= $note['id'] ?> .opinion-gradient-<?= $opinion >= 50 ? 'negative' : 'positive' ?> {
opacity: 0;
}
#Opinion-<?= $note['id'] ?> .opinion-gradient-dot {
left: calc(<?= $opinion ?>% - 6px);
background-color: <?= $opinion == 50 ? '#555' : $opinion_color_scale_100[$opinion] ?>;
}
<?php if ($opinion >= 50): ?>
#Opinion-<?= $note['id'] ?> .opinion-gradient-positive {
-webkit-mask-image: linear-gradient(90deg, black 0 <?= abs(-50 + $opinion)*2 ?>%, transparent <?= abs(-50 + $opinion)*2 ?>% 100%);
mask-image: linear-gradient(90deg, black 0 <?= abs(-50 + $opinion)*2 ?>%, transparent <?= abs(-50 + $opinion)*2 ?>% 100%);
}
<?php else: ?>
#Opinion-<?= $note['id'] ?> .opinion-gradient-negative {
-webkit-mask-image: linear-gradient(90deg, transparent 0 <?= 100-(abs(-50 + $opinion)*2) ?>%, black <?= 100-(abs(-50 + $opinion)*2) ?>% 100%);
mask-image: linear-gradient(90deg, transparent 0 <?= 100-(abs(-50 + $opinion)*2) ?>%, black <?= 100-(abs(-50 + $opinion)*2) ?>% 100%);
}
<?php endif; ?>
<?php
}
}
genStyleForOpinionNotes($notesOpinionsRelationships)
?>
</style>
<style>
span.misp-element-wrapper {
margin: 3px 3px;
border: 1px solid #ddd !important;
border-radius: 3px;
white-space: nowrap;
display: inline-block;
padding: 0;
}
.misp-element-wrapper.attribute .attr-type {
background-color: #f5f5f5 !important;
border-right: 1px solid #ddd !important;
display: inline-block;
}
.misp-element-wrapper.attribute .attr-type > span {
margin: 2px 3px;
}
.misp-element-wrapper.attribute .attr-value {
display: inline-table;
margin: 0px 3px;
}
.misp-element-wrapper.attribute .attr-value > span {
max-width: 300px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: table-cell;
}
span.misp-element-wrapper.object {
border: 1px solid #3465a4 !important;
}
.misp-element-wrapper.object .obj-type {
display: inline-block;
background-color: #3465a4 !important;
color: #ffffff !important;
}
.misp-element-wrapper.object .obj-type .object-attribute-type {
margin-left: 0;
background-color: #f5f5f5;
color: black;
padding: 1px 3px;
border-radius: 7px;
}
.misp-element-wrapper.object .obj-type > span {
margin: 2px 3px;
}
.misp-element-wrapper.object .obj-value {
display: inline-table;
margin: 0px 3px;
}
.misp-element-wrapper.object .obj-value > span {
max-width: 300px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: table-cell;
}
</style>

View File

@ -0,0 +1,159 @@
<?php
$seed = mt_rand();
$params['type'] = 'number';
$params['min'] = 0;
$params['max'] = 100;
$params['class'] .= ' opinion-' . $seed;
echo $this->Form->input($fieldData['field'], $params);
echo $this->element('genericElements/assetLoader', [
'css' => ['analyst-data',],
]);
?>
<script>
var opinionGradient<?= $seed ?> = '\
<div class="opinion-gradient-container" style="width: 10rem; height: 6px; position: relative;">\
<div class="opinion-gradient opinion-gradient-negative"></div> \
<div class="opinion-gradient opinion-gradient-positive"></div> \
<input type="range" min="0" max="100" value="50" step="10" class="slider" id="opinion-slider">\
</div> \
'
var opinionTemplate<?= $seed ?> = '\
<div class="main-container" style="margin: 0.75rem 0 0.25rem 0; display: flex; flex-direction: row;" title="<?= __('Opinion:') ?> 50 /100"> \
' + opinionGradient<?= $seed ?> + ' \
<span style="line-height: 1em; margin-left: 0.25rem; margin-top: -3px;"> \
<b class="opinion-text" style="margin-left: 0.5rem; color: #333"></b> \
<b class="opinion-value" style="margin-left: 0.25rem; color: #333"></b> \
<span style="font-size: 0.7em; font-weight: lighter; color: #999">/100</span> \
</span> \
</div> \
'
var opinionColorScale = ['rgb(164, 0, 0)', 'rgb(166, 15, 0)', 'rgb(169, 25, 0)', 'rgb(171, 33, 0)', 'rgb(173, 40, 0)', 'rgb(175, 46, 0)', 'rgb(177, 52, 0)', 'rgb(179, 57, 0)', 'rgb(181, 63, 0)', 'rgb(183, 68, 0)', 'rgb(186, 72, 0)', 'rgb(188, 77, 0)', 'rgb(190, 82, 0)', 'rgb(191, 86, 0)', 'rgb(193, 90, 0)', 'rgb(195, 95, 0)', 'rgb(197, 98, 0)', 'rgb(198, 102, 0)', 'rgb(200, 106, 0)', 'rgb(201, 110, 0)', 'rgb(203, 114, 0)', 'rgb(204, 118, 0)', 'rgb(206, 121, 0)', 'rgb(208, 125, 0)', 'rgb(209, 128, 0)', 'rgb(210, 132, 0)', 'rgb(212, 135, 0)', 'rgb(213, 139, 0)', 'rgb(214, 143, 0)', 'rgb(216, 146, 0)', 'rgb(217, 149, 0)', 'rgb(218, 153, 0)', 'rgb(219, 156, 0)', 'rgb(220, 160, 0)', 'rgb(222, 163, 0)', 'rgb(223, 166, 0)', 'rgb(224, 169, 0)', 'rgb(225, 173, 0)', 'rgb(226, 176, 0)', 'rgb(227, 179, 0)', 'rgb(228, 182, 0)', 'rgb(229, 186, 0)', 'rgb(230, 189, 0)', 'rgb(231, 192, 0)', 'rgb(232, 195, 0)', 'rgb(233, 198, 0)', 'rgb(234, 201, 0)', 'rgb(235, 204, 0)', 'rgb(236, 207, 0)', 'rgb(237, 210, 0)', 'rgb(237, 212, 0)', 'rgb(234, 211, 0)', 'rgb(231, 210, 0)', 'rgb(229, 209, 1)', 'rgb(226, 208, 1)', 'rgb(223, 207, 1)', 'rgb(220, 206, 1)', 'rgb(218, 204, 1)', 'rgb(215, 203, 2)', 'rgb(212, 202, 2)', 'rgb(209, 201, 2)', 'rgb(206, 200, 2)', 'rgb(204, 199, 2)', 'rgb(201, 198, 3)', 'rgb(198, 197, 3)', 'rgb(195, 196, 3)', 'rgb(192, 195, 3)', 'rgb(189, 194, 3)', 'rgb(186, 193, 3)', 'rgb(183, 192, 4)', 'rgb(180, 190, 4)', 'rgb(177, 189, 4)', 'rgb(174, 188, 4)', 'rgb(171, 187, 4)', 'rgb(168, 186, 4)', 'rgb(165, 185, 4)', 'rgb(162, 183, 4)', 'rgb(159, 182, 4)', 'rgb(156, 181, 4)', 'rgb(153, 180, 4)', 'rgb(149, 179, 5)', 'rgb(146, 178, 5)', 'rgb(143, 177, 5)', 'rgb(139, 175, 5)', 'rgb(136, 174, 5)', 'rgb(133, 173, 5)', 'rgb(130, 172, 5)', 'rgb(126, 170, 5)', 'rgb(123, 169, 5)', 'rgb(119, 168, 5)', 'rgb(115, 167, 5)', 'rgb(112, 165, 6)', 'rgb(108, 164, 6)', 'rgb(104, 163, 6)', 'rgb(100, 162, 6)', 'rgb(96, 160, 6)', 'rgb(92, 159, 6)', 'rgb(88, 157, 6)', 'rgb(84, 156, 6)', 'rgb(80, 155, 6)', 'rgb(78, 154, 6)'];
$(document).ready(function() {
initOpinionSlider()
})
function getOpinionColor(opinion) {
return opinion == 50 ? '#333' : ( opinion > 50 ? '#468847' : '#b94a48');
}
function getOpinionText(opinion) {
return (opinion >= 81) ? '<?= __("Strongly Agree") ?>' : ((opinion >= 61) ? '<?= __("Agree") ?>' : ((opinion >= 41) ? '<?= __("Neutral") ?>' : ((opinion >= 21) ? '<?= __("Disagree") ?>' : '<?= __("Strongly Disagree") ?>')))
}
function setOpinionLevel(opinion) {
opinion = Number.parseInt(opinion)
var $formContainer = $('.opinion-<?= $seed ?>').parent()
var $mainContainer = $formContainer.find('.main-container')
var $gradientContainer = $formContainer.find('.opinion-gradient-container')
var $opinionSlider = $gradientContainer.find('#opinion-slider')
var backgroundColor = getOpinionColor(opinion)
var backgroundColorDot = opinion == 50 ? '#555' : opinionColorScale[opinion]
$mainContainer.attr('title', '<?= __('Opinion:') ?> ' + opinion + ' /100')
$mainContainer.find('.opinion-text')
.css('color', backgroundColor)
.text(getOpinionText(opinion))
$mainContainer.find('.opinion-value')
.css('color', backgroundColor)
.text(opinion)
if (opinion >= 50) {
var opinionMask = Math.abs(-50 + opinion)*2
$gradientContainer.find('.opinion-gradient-negative').css({
'opacity': 0,
'-webkit-mask-image': 'unset',
'mask-image': 'unset',
})
$gradientContainer.find('.opinion-gradient-positive').css({
'opacity': 1,
'-webkit-mask-image': 'linear-gradient(90deg, black 0 ' + opinionMask + '%, transparent ' + opinionMask + '% 100%)',
'mask-image': 'linear-gradient(90deg, black 0 ' + opinionMask + '%, transparent ' + opinionMask + '% 100%)',
})
} else {
var opinionMask = 100-(Math.abs(-50 + opinion)*2)
$gradientContainer.find('.opinion-gradient-negative').css({
'opacity': 1,
'-webkit-mask-image': 'linear-gradient(90deg, transparent 0 ' + opinionMask + '%, black ' + opinionMask + '% 100%)',
'mask-image': 'linear-gradient(90deg, transparent 0 ' + opinionMask + '%, black ' + opinionMask + '% 100%)',
})
$gradientContainer.find('.opinion-gradient-positive').css({
'opacity': 0,
'-webkit-mask-image': 'unset',
'mask-image': 'unset'
})
}
$opinionSlider.val(opinion)
$opinionSlider[0].style.setProperty('--color', backgroundColorDot);
$('input#OpinionOpinion').val(opinion)
}
function genSlider() {
var $div = $('<div style="display: inline-block;"></div>')
var $opinionTemplate = $(opinionTemplate<?= $seed ?>)
var $div = $div.append($opinionTemplate)
return $div
}
function initOpinionSlider() {
var $input = $('.opinion-<?= $seed ?>')
$input.css({
'width': '2.5rem',
'margin': '0 0.5rem 0 0',
})
$input.parent().append(genSlider())
var currentOpinionValue = !Number.isNaN(Number.parseInt($input.val())) ? Number.parseInt($input.val()) : 50
setOpinionLevel(currentOpinionValue)
$('.opinion-<?= $seed ?>').parent().find('#opinion-slider')
.on('input', function(e) {
setOpinionLevel(this.value)
})
$input.on('input', function(e) {
setOpinionLevel(this.value)
})
}
</script>
<style>
input#opinion-slider {
position: absolute;
width: 160px;
-webkit-appearance: none;
appearance: none;
height: 6px;
background: #ffffff00;
outline: none;
opacity: 0.8;
-webkit-transition: .2s;
transition: opacity .2s;
}
#opinion-slider:hover {
opacity: 1;
}
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
#opinion-slider::-webkit-slider-thumb {
border-radius: 50%;
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 12px;
box-shadow: 0 0 2px 0px #00000066;
background-color: var(--color, white);
cursor: pointer;
}
#opinion-slider::-moz-range-thumb {
border-radius: 50%;
width: 12px;
height: 12px;
box-shadow: 0 0 2px 0px #00000066;
background-color: var(--color, white);
cursor: pointer;
}
</style>

View File

@ -7,6 +7,9 @@ if ($quickedit) {
}
$distributionLevel = Hash::extract($row, $field['data_path'])[0];
if ($distributionLevel == 4) {
$sg = empty($field['sg_path']) ? $row['SharingGroup'] : Hash::extract($row, $field['sg_path']);
}
echo sprintf('<div%s>', $quickedit ? sprintf(
" onmouseenter=\"quickEditHover(this, '%s', %s, 'distribution');\"",
@ -25,8 +28,8 @@ echo sprintf(
sprintf(
'<a href="%s/sharing_groups/view/%s">%s</a>',
$baseurl,
h($row['SharingGroup']['id']),
h($row['SharingGroup']['name'])
h($sg['id']),
h($sg['name'])
)
);
if ($quickedit) {

View File

@ -1,3 +1,22 @@
<?php
$data = Hash::extract($row, $field['data_path']);
echo h($data['model']) . ' #' . intval($data['model_id']);
if (!empty($field['data_path'])) {
$data = Hash::extract($row, $field['data_path']);
if (isset($data['model_id'])) {
echo h($data['model']) . ' #' . intval($data['model_id']);
}
} else {
$model_name = Hash::extract($row, $field['model_name'])[0];
$model_path = Inflector::Pluralize($model_name);
$model_id = Hash::extract($row, $field['model_id'])[0];
echo sprintf(
'<a href="%s/%s/view/%s">%s (%s)</a>',
$baseurl,
h($model_path),
h($model_id),
h($model_name),
h($model_id)
);
}

View File

@ -0,0 +1,6 @@
<?php
$opinion = Hash::get($row, $field['data_path']);
echo $this->element('genericElements/Analyst_data/opinion_scale', [
'opinion' => $opinion,
]);

View File

@ -0,0 +1,16 @@
<?php
$uuid = Hash::get($row, $field['data_path']);
if (empty($uuid) || empty($field['object_type'])) {
throw new MethodNotAllowedException(__('No UUID or object_type provided'), 500);
}
$notes = Hash::extract($row, $field['notes_data_path'] ?? 'Note');
$opinions = Hash::extract($row, $field['opinions_data_path'] ?? 'Opnion');
$relationships = Hash::extract($row, $field['relationships_data_path'] ?? 'Relationship');
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $uuid,
'object_type' => $field['object_type'],
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
]);

View File

@ -265,6 +265,22 @@ $divider = '<li class="divider"></li>';
'text' => __('Download as…')
));
echo $divider;
if ($me['Role']['perm_modify']) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'onClick' => array(
'function' => 'openGenericModal',
'params' => [
sprintf(
'%s/collectionElements/addElementToCollection/Event/%s',
$baseurl,
h($event['Event']['uuid'])
)
]
),
'text' => __('Add Event to Collection')
));
echo $divider;
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/events/index',
'text' => __('List Events')
@ -314,7 +330,50 @@ $divider = '<li class="divider"></li>';
);
}
break;
case 'collections':
if ($menuItem === 'edit' || $menuItem === 'view') {
if ($this->Acl->canAccess('collections', 'add') && $mayModify) {
echo $this->element('/genericElements/SideMenu/side_menu_link', [
'element_id' => 'edit',
'url' => $baseurl . '/collections/edit/' . h($id),
'text' => __('Edit Collection')
]);
echo $this->element('/genericElements/SideMenu/side_menu_link', [
'element_id' => 'delete',
'onClick' => [
'function' => 'openGenericModal',
'params' => array($baseurl . '/Collections/delete/' . h($id))
],
'text' => __('Delete Collection')
]);
echo $this->element('/genericElements/SideMenu/side_menu_link', [
'text' => __('Add Element to Collection'),
'onClick' => [
'function' => 'openGenericModal',
'params' => array($baseurl . '/CollectionElements/add/' . h($id))
],
]);
echo $divider;
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'view',
'url' => $baseurl . '/collections/view/' . h($id),
'text' => __('View Collection')
));
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index',
'url' => $baseurl . '/collections/index',
'text' => __('List Collections')
));
if ($this->Acl->canAccess('collection', 'add')) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'add',
'url' => $baseurl . '/collections/add',
'text' => __('Add Collection')
));
}
break;
case 'event-collection':
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index',
@ -1493,6 +1552,22 @@ $divider = '<li class="divider"></li>';
'text' => __('View Correlation Graph')
));
}
if ($me['Role']['perm_modify']) {
echo $divider;
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'onClick' => array(
'function' => 'openGenericModal',
'params' => [
sprintf(
'%s/collectionElements/addElementToCollection/GalaxyCluster/%s',
$baseurl,
h($cluster['GalaxyCluster']['uuid'])
)
]
),
'text' => __('Add Cluster to Collection')
));
}
}
if ($menuItem === 'view' || $menuItem === 'export') {
echo $divider;
@ -1726,6 +1801,54 @@ $divider = '<li class="divider"></li>';
}
}
break;
case 'analyst_data':
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index',
'url' => '/analystData/index',
'text' => __('List Analyst Data')
));
if ($this->Acl->canAccess('analyst_data_blocklists', 'index')) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index_blocklist',
'url' => $baseurl . '/analyst_data_blocklists/index',
'text' => __('List Analyst-Data Blocklists')
));
}
if ($this->Acl->canAccess('analyst_data', 'add')) {
echo $divider;
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'add_note',
'url' => sprintf('/analystData/add/Note'),
'text' => __('Add Analyst Note')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'add_opinion',
'url' => sprintf('/analystData/add/Opinion'),
'text' => __('Add Analyst Opinion')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'add_relationship',
'url' => sprintf('/analystData/add/Relationship'),
'text' => __('Add Analyst Relationship')
));
}
if ($menuItem === 'view' || $menuItem === 'edit') {
echo $divider;
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'view',
'url' => sprintf('/analystData/view/%s/%s', h($modelSelection), h($id)),
'text' => __('View Analyst Data')
));
if ($isSiteAdmin) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'edit',
'url' => sprintf('/analystData/edit/%s/%s', h($modelSelection), h($id)),
'text' => __('Edit Analyst Data')
));
}
}
break;
}
?>
</ul>

View File

@ -0,0 +1,3 @@
<?php
echo !empty($side_panel['html']) ? $side_panel['html'] : '';
?>

View File

@ -24,25 +24,27 @@ if ($distribution == 4) {
}
$eventDistributionGraph = '';
if (!($distribution == 4 && empty($sg))) {
$eventDistributionGraph = sprintf(
'%s %s %s',
sprintf(
'<span id="distribution_graph_bar" style="margin-left: 5px;" data-object-id="%s" data-object-context="event"></span>',
h($event_id_path)
),
sprintf(
'<it class="%s" data-object-id="%s" data-object-context="event" data-shown="false"></it><div style="display: none">%s</div>',
'useCursorPointer fa fa-info-circle distribution_graph',
h($event_id_path),
$this->element('view_event_distribution_graph')
),
sprintf(
'<it type="button" id="showAdvancedSharingButton" title="%s" class="%s" aria-hidden="true" style="margin-left: 5px;"></it>',
__('Toggle advanced sharing network viewer'),
'fa fa-share-alt useCursorPointer'
)
);
if (empty($field['disable_distribution_graph'])) {
if (!($distribution == 4 && empty($sg))) {
$eventDistributionGraph = sprintf(
'%s %s %s',
sprintf(
'<span id="distribution_graph_bar" style="margin-left: 5px;" data-object-id="%s" data-object-context="event"></span>',
h($event_id_path)
),
sprintf(
'<it class="%s" data-object-id="%s" data-object-context="event" data-shown="false"></it><div style="display: none">%s</div>',
'useCursorPointer fa fa-info-circle distribution_graph',
h($event_id_path),
$this->element('view_event_distribution_graph')
),
sprintf(
'<it type="button" id="showAdvancedSharingButton" title="%s" class="%s" aria-hidden="true" style="margin-left: 5px;"></it>',
__('Toggle advanced sharing network viewer'),
'fa fa-share-alt useCursorPointer'
)
);
}
}
echo sprintf(

View File

@ -0,0 +1,7 @@
<?php
$opinion = Hash::get($data, $field['path']);
echo $this->element('genericElements/Analyst_data/opinion_scale', [
'opinion' => $opinion,
'forceInline' => true,
]);

View File

@ -4,3 +4,17 @@
'<span class="quickSelect">%s</span>',
h($uuid)
);
if (!empty($field['object_type'])) {
$field['notes_path'] = !empty($field['notes_path']) ? $field['notes_path'] : 'Note';
$field['opinions_path'] = !empty($field['opinions_path']) ? $field['opinions_path'] : 'Opinion';
$field['relationships_path'] = !empty($field['relationships_path']) ? $field['relationships_path'] : 'Relationship';
$notes = !empty($field['notes']) ? $field['notes'] : Hash::extract($data, $field['notes_path']);
$opinions = !empty($field['opinions']) ? $field['opinions'] : Hash::extract($data, $field['opinions_path']);
$relationships = !empty($field['relationships']) ? $field['relationships'] : Hash::extract($data, $field['relationships_path']);
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'object_uuid' => $uuid,
'object_type' => $field['object_type']
]);
}

View File

@ -0,0 +1,16 @@
<?php
$uuidHalfWidth = 3;
$shortUUID = sprintf('%s...%s', substr($uuid, 0, $uuidHalfWidth), substr($uuid, 36-$uuidHalfWidth, $uuidHalfWidth));
echo sprintf('<span title="%s">%s</span>', $uuid, $shortUUID);
if (!empty($object_type)) {
$notes = !empty($notes) ? $notes : [];
$opinions = !empty($opinions) ? $opinions : [];
$relationships = !empty($relationships) ? $relationships : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships,],
'object_uuid' => $uuid,
'object_type' => $object_type
]);
}

File diff suppressed because it is too large Load Diff

View File

@ -371,17 +371,23 @@ $humanReadableFilesize = function ($bytes, $dec = 2) {
<b><?= __('PHP extension version') ?>:</b> <?= $redisInfo['extensionVersion'] ?: ('<span class="red bold">' . __('Not installed.') . '</span>') ?><br>
<?php if ($redisInfo['connection']): ?>
<b><?= __('Redis version') ?>:</b> <?= h($redisInfo['redis_version']) ?><br>
<?php if (isset($redisInfo['dfly_version']) || isset($redisInfo['dragonfly_version'])): ?>
<b><?= __('Dragonfly version') ?>:</b> <?= h($redisInfo['dfly_version'] ?? $redisInfo['dragonfly_version']) ?><br>
<?php endif; ?>
<?php if (isset($redisInfo['mem_allocator'])): ?>
<b><?= __('Memory allocator') ?>:</b> <?= h($redisInfo['mem_allocator']) ?><br>
<?php endif; ?>
<b><?= __('Memory usage') ?>:</b> <?= $humanReadableFilesize($redisInfo['used_memory']) ?><br>
<?php if (isset($redisInfo['mem_allocator'])): ?>
<?php if (isset($redisInfo['used_memory_peak'])): ?>
<b><?= __('Peak memory usage') ?>:</b> <?= $humanReadableFilesize($redisInfo['used_memory_peak']) ?><br>
<?php endif; ?>
<?php if (isset($redisInfo['mem_fragmentation_ratio'])): ?>
<b><?= __('Fragmentation ratio') ?>:</b> <?= h($redisInfo['mem_fragmentation_ratio']) ?><br>
<?php endif; ?>
<?php if (isset($redisInfo['total_system_memory_human'])): ?>
<?php if (isset($redisInfo['maxmemory'])): ?>
<b><?= __('Maximum memory') ?>:</b> <?= $humanReadableFilesize($redisInfo['maxmemory']) ?><br>
<?php endif; ?>
<?php if (isset($redisInfo['total_system_memory'])): ?>
<b><?= __('Total system memory') ?>:</b> <?= $humanReadableFilesize($redisInfo['total_system_memory']) ?>
<?php endif; ?>
<?php elseif ($redisInfo['extensionVersion']): ?>
@ -390,23 +396,23 @@ $humanReadableFilesize = function ($bytes, $dec = 2) {
</div>
<h3><?php echo __('Advanced attachment handler');?></h3>
<?php echo __('The advanced attachment tools are used by the add attachment functionality to extract additional data about the uploaded sample.');?>
<div class="diagnostics-box">
<?php
if (empty($advanced_attachments)):
?>
<b><?php echo __('PyMISP');?></b>:… <span class="red bold"><?php echo __('Not installed or version outdated.');?></span><br />
<?php
endif;
if (!empty($advanced_attachments)):
foreach ($advanced_attachments as $k => $v):
?>
<b><?php echo h($k); ?></b>:… <?php echo $v === false ? '<span class="green bold">' . __('OK') . '</span>' : '<span class="red bold">' . h($v) . '</span>'; ?><br />
<?php
endforeach;
endif;
?>
</div>
<?php echo __('The advanced attachment tools are used by the add attachment functionality to extract additional data about the uploaded sample.');?>
<div class="diagnostics-box">
<?php
if (empty($advanced_attachments)):
?>
<b><?php echo __('PyMISP');?></b>:… <span class="red bold"><?php echo __('Not installed or version outdated.');?></span><br>
<?php
endif;
if (!empty($advanced_attachments)):
foreach ($advanced_attachments as $k => $v):
?>
<b><?php echo h($k); ?></b>:… <?php echo $v === false ? '<span class="green bold">' . __('OK') . '</span>' : '<span class="red bold">' . h($v) . '</span>'; ?><br>
<?php
endforeach;
endif;
?>
</div>
<h3><?= __('Attachment scan module') ?></h3>
<div class="diagnostics-box">

View File

@ -73,6 +73,17 @@
'class' => 'short',
'data_path' => 'EventReport.id',
),
array(
'name' => __('Context'),
'sort' => 'uuid',
'class' => 'short',
'data_path' => 'EventReport.uuid',
'notes_data_path' => 'Note',
'opinions_data_path' => 'Opinion',
'relationships_data_path' => 'Relationship',
'element' => 'shortUUIDWithNotes',
'object_type' => 'EventReport',
),
array(
'name' => __('Name'),
'class' => 'useCursorPointer',

View File

@ -52,6 +52,17 @@
'element' => 'links',
'url' => $baseurl . '/eventReports/view/%s'
),
array(
'name' => __('Context'),
'sort' => 'uuid',
'class' => 'short',
'data_path' => 'EventReport.uuid',
'notes_data_path' => 'Note',
'opinions_data_path' => 'Opinion',
'relationships_data_path' => 'Relationship',
'element' => 'shortUUIDWithNotes',
'object_type' => 'EventReport',
),
array(
'name' => __('Name'),
'data_path' => 'EventReport.name',

View File

@ -1,7 +1,22 @@
<?php
echo $this->element('genericElements/assetLoader', [
'js' => ['doT', 'moment.min'],
'css' => ['analyst-data',],
]);
$table_data = array();
$table_data[] = array('key' => __('ID'), 'value' => $report['EventReport']['id']);
$table_data[] = array('key' => __('UUID'), 'value' => $report['EventReport']['uuid'], 'value_class' => 'quickSelect');
$table_data[] = [
'key' => __('UUID'),
'element' => 'genericElements/SingleViews/Fields/uuidField',
'element_params' => [
'data' => $report,
'field' => [
'path' => 'EventReport.uuid',
'object_type' => 'EventReport',
]
],
];
$table_data[] = array(
'key' => __('Event'),
'html' => sprintf(

View File

@ -28,10 +28,13 @@
'selected' => $initialDistribution,
));
if (!empty($sharingGroups)) {
echo $this->Form->input('sharing_group_id', array(
'options' => array($sharingGroups),
'label' => __('Sharing Group'),
));
$SGContainer = $this->Form->input(
'sharing_group_id', array(
'options' => array($sharingGroups),
'label' => __('Sharing Group'),
)
);
echo '<div id="SGContainer" style="display:none;">' . $SGContainer . '</div>';
}
?>
<div class="input clear"></div>
@ -64,6 +67,36 @@
'label' => __('How to handle Galaxies and Clusters') . $galaxiesFormInfo,
'selected' => 0
));
?>
<div class="input clear"></div>
<?php
$clusterDistributionFormInfo = $this->element(
'genericElements/Form/formInfo',
[
'field' => [
'field' => 'cluster_distribution'
],
'modelForForm' => 'Event',
'fieldDesc' => $fieldDesc['distribution'],
]
);
$clusterDistribution = $this->Form->input(
'cluster_distribution', array(
'options' => $distributionLevels,
'label' => __('Cluster distribution ') . $clusterDistributionFormInfo,
'selected' => $initialDistribution,
)
);
echo '<div id="ClusterDistribution" style="display:none;">' . $clusterDistribution . '</div>';
if (!empty($sharingGroups)) {
$clusterSGContainer = $this->Form->input(
'cluster_sharing_group_id', array(
'options' => array($sharingGroups),
'label' => __('Cluster Sharing Group'),
)
);
echo '<div id="ClusterSGContainer" style="display:none;">' . $clusterSGContainer . '</div>';
}
}
if ($me['Role']['perm_site_admin'] && Configure::read('debug') > 0) {
$debugFormInfo = $this->element(
@ -101,4 +134,26 @@ $(function(){
});
checkSharingGroup('Event');
});
$(function(){
$('#EventGalaxiesHandling').change(function() {
if ($(this).val() == 0) {
$('#ClusterDistribution').show();
if ($('#EventClusterDistribution').val() == 4) {
$('#ClusterSGContainer').show();
}
} else {
$('#ClusterDistribution').hide();
$('#ClusterSGContainer').hide();
}
}).change();
});
$(function(){
$('#EventClusterDistribution').change(function() {
if ($(this).val() == 4 && $('#EventGalaxiesHandling').val() == 0) {
$('#ClusterSGContainer').show();
} else {
$('#ClusterSGContainer').hide();
}
}).change();
});
</script>

View File

@ -1,6 +1,6 @@
<?php
echo $this->element('genericElements/assetLoader', [
'css' => ['query-builder.default', 'attack_matrix'],
'css' => ['query-builder.default', 'attack_matrix', 'analyst-data'],
'js' => ['doT', 'extendext', 'moment.min', 'query-builder', 'network-distribution-graph', 'd3', 'd3.custom', 'jquery-ui.min'],
]);
echo $this->element(
@ -16,8 +16,12 @@
[
'key' => 'UUID',
'path' => 'Event.uuid',
'class' => 'quickSelect',
'class' => '',
'type' => 'uuid',
'object_type' => 'Event',
'notes_path' => 'Note',
'opinions_path' => 'Opinion',
'relationship_path' => 'Relationship',
'action_buttons' => [
[
'url' => $baseurl . '/events/add/extends:' . h($event['Event']['uuid']),

View File

@ -1,4 +1,9 @@
<?php
echo $this->element('genericElements/assetLoader', [
'js' => ['doT', 'moment.min'],
'css' => ['analyst-data',],
]);
$extendedFromHtml = '';
if (!empty($cluster['GalaxyCluster']['extended_from'])) {
$element = $this->element('genericElements/IndexTable/Fields/links', array(
@ -50,7 +55,20 @@ if (!$cluster['GalaxyCluster']['default']) {
}
$table_data[] = array('key' => __('Default'), 'boolean' => $cluster['GalaxyCluster']['default'], 'class' => 'black');
$table_data[] = array('key' => __('Version'), 'value' => $cluster['GalaxyCluster']['version']);
$table_data[] = array('key' => __('UUID'), 'value' => $cluster['GalaxyCluster']['uuid'], 'value_class' => 'quickSelect');
$table_data[] = [
'key' => __('UUID'),
'element' => 'genericElements/SingleViews/Fields/uuidField',
'element_params' => [
'data' => $cluster,
'field' => [
'path' => 'GalaxyCluster.uuid',
'object_type' => 'GalaxyCluster',
'notes_path' => 'GalaxyCluster.Note',
'opinions_path' => 'GalaxyCluster.Opinion',
'relationships_path' => 'GalaxyCluster.Relationship',
]
],
];
$table_data[] = array('key' => __('Collection UUID'), 'value' => $cluster['GalaxyCluster']['collection_uuid'], 'value_class' => 'quickSelect');
$table_data[] = array(
'key' => __('Source'),
@ -94,6 +112,9 @@ if (!empty($extendedByHtml)) {
</h2>
<?php echo $this->element('genericElements/viewMetaTable', array('table_data' => $table_data)); ?>
</div>
<div class="span4">
<div id="analyst_data_thread" class="panel-container"></div>
</div>
</div>
<div class="row-fuild">
<div id="matrix_container"></div>
@ -143,4 +164,21 @@ md.disable(['image'])
var $md = $('.md');
$md.html(md.render($md.text()));
</script>
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster'));
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster')); ?>
<?php
$object_uuid = $cluster['GalaxyCluster']['uuid'];
$options = [
'container_id' => 'analyst_data_thread',
'object_type' => 'GalaxyCluster',
'object_uuid' => $object_uuid,
'shortDist' => $shortDist,
'notes' => $cluster['GalaxyCluster']['Note'] ?? [],
'opinions' => $cluster['GalaxyCluster']['Opinion'] ?? [],
'relationships' => $cluster['GalaxyCluster']['Relationship'] ?? [],
];
echo $this->element('genericElements/Analyst_data/thread', $options);
?>

View File

@ -115,4 +115,13 @@ class AclHelper extends Helper
{
return $this->ACL->canModifyGalaxyCluster($this->me, $cluster);
}
/**
* @param array $cluster
* @return bool
*/
public function canEditAnalystData(array $analystData, $modelType): bool
{
return $this->ACL->canEditAnalystData($this->me, $analystData, $modelType);
}
}

View File

@ -0,0 +1,42 @@
<?php
App::uses('AppHelper', 'View/Helper');
App::uses('FileAccessTool', 'Lib/Tools');
class ImageHelper extends AppHelper
{
/** @var array */
private $imageCache = [];
/**
* @param string $imagePath Path to file
* @return string
* @throws Exception
*/
public function base64($imagePath)
{
if (isset($this->imageCache[$imagePath])) {
return $this->imageCache[$imagePath];
}
$ext = pathinfo($imagePath, PATHINFO_EXTENSION);
if ($ext === 'svg') {
$mime = 'image/svg+xml';
} else if ($ext === 'png') {
$mime = 'image/png';
} else {
throw new InvalidArgumentException("Only SVG and PNG images are supported");
}
try {
$fileContent = FileAccessTool::readFromFile($imagePath);
} catch (Exception $e) {
CakeLog::warning($e);
return 'data:null'; // in case file doesn't exists or is not readable
}
$fileContentEncoded = base64_encode($fileContent);
$base64 = "data:$mime;base64,$fileContentEncoded";
return $this->imageCache[$imagePath] = $base64;
}
}

View File

@ -22,8 +22,8 @@ class OrgImgHelper extends AppHelper
$link = $baseurl . '/organisations/view/' . (empty($organisation['Organisation']['id']) ? h($organisation['Organisation']['name']) : h($organisation['Organisation']['id']));
}
if ($orgImgName) {
$orgImgUrl = $baseurl . '/img/orgs/' . $orgImgName;
return sprintf('<a href="%s" style="background-image: url(\'%s\')" class="orgImg">%s</a>', $link, $orgImgUrl, h($organisation['Organisation']['name']));
$base64 = $this->_View->Image->base64(self::IMG_PATH . $orgImgName);
return sprintf('<a href="%s" style="background-image: url(\'%s\')" class="orgImg">%s</a>', $link, $base64, h($organisation['Organisation']['name']));
} else {
return sprintf('<a href="%s">%s</a>', $link, h($organisation['Organisation']['name']));
}
@ -56,9 +56,8 @@ class OrgImgHelper extends AppHelper
if ($orgImgName) {
$size = !empty($options['size']) ? $options['size'] : 48;
$result = sprintf(
'<img src="data:image/%s;base64,%s" title="%s" width="%s" height="%s">',
'png',
base64_encode(FileAccessTool::readFromFile(self::IMG_PATH . $orgImgName)),
'<img src="%s" title="%s" width="%s" height="%s">',
$this->_View->Image->base64(self::IMG_PATH . $orgImgName),
isset($options['name']) ? h($options['name']) : h($options['id']),
(int)$size,
(int)$size

View File

@ -83,7 +83,7 @@ foreach ($list as $template):
<?php
if ($template['ObjectTemplate']['fixed']):
?>
<img src="<?php echo $baseurl;?>/img/orgs/MISP.png" width="24" height="24" style="padding-bottom:3px;" />
<?php echo '<img src="' . $this->Image->base64(APP . 'files/img/orgs/MISP.png') . '" alt="' . __('MISP logo') . '" width="24" height="24" style="padding-bottom:3px" onerror="this.style.display=\'none\';">';?>
<?php
endif;
?>

View File

@ -93,6 +93,8 @@
echo $this->Form->input('caching_enabled', array());
echo $this->Form->input('push_galaxy_clusters', array());
echo $this->Form->input('pull_galaxy_clusters', array());
echo $this->Form->input('push_analyst_data', array());
echo $this->Form->input('pull_analyst_data', array());
echo '<div class="input clear" style="width:100%;"><hr><h4>' . __('Misc settings') . '</h4></div>';
echo $this->Form->input('unpublish_event', array(
'type' => 'checkbox',

View File

@ -23,6 +23,8 @@
<th><?php echo $this->Paginator->sort('push_sightings', 'Push Sightings');?></th>
<th><?php echo $this->Paginator->sort('push_galaxy_clusters', 'Push Clusters');?></th>
<th><?php echo $this->Paginator->sort('pull_galaxy_clusters', 'Pull Clusters');?></th>
<th><?php echo $this->Paginator->sort('push_analyst_data', 'Push Analyst Data');?></th>
<th><?php echo $this->Paginator->sort('pull_analyst_data', 'Pull Analyst Data');?></th>
<th><?php echo $this->Paginator->sort('caching_enabled', 'Cache');?></th>
<th><?php echo $this->Paginator->sort('unpublish_event');?></th>
<th><?php echo $this->Paginator->sort('publish_without_email');?></th>
@ -120,6 +122,8 @@ foreach ($servers as $server):
<td class="short"><span class="<?= $server['Server']['push_sightings'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['push_sightings'] ? __('Yes') : __('No'); ?>"></span></td>
<td class="short"><span class="<?= $server['Server']['push_galaxy_clusters'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['push_galaxy_clusters'] ? __('Yes') : __('No'); ?>"></span></td>
<td class="short"><span class="<?= $server['Server']['pull_galaxy_clusters'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['pull_galaxy_clusters'] ? __('Yes') : __('No'); ?>"></span></td>
<td class="short"><span class="<?= $server['Server']['push_analyst_data'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['push_analyst_data'] ? __('Yes') : __('No'); ?>"></span></td>
<td class="short"><span class="<?= $server['Server']['pull_analyst_data'] ? 'fa fa-check' : 'fa fa-times' ?>" role="img" aria-label="<?= $server['Server']['pull_analyst_data'] ? __('Yes') : __('No'); ?>"></span></td>
<td>
<?php
if ($server['Server']['caching_enabled']) {

View File

@ -5,7 +5,9 @@
<table style="margin-left:auto;margin-right:auto;">
<tr>
<td style="text-align:right;width:250px;padding-right:50px">
<?php if (Configure::read('MISP.welcome_logo')) echo $this->Html->image('custom/' . h(Configure::read('MISP.welcome_logo')), array('alt' => __('Logo'), 'onerror' => "this.style.display='none';")); ?>
<?php if (Configure::read('MISP.welcome_logo') && file_exists(APP . '/files/img/custom/' . Configure::read('MISP.welcome_logo'))): ?>
<img src="<?= $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.welcome_logo')) ?>" alt="<?= __('Logo') ?>" onerror="this.style.display='none';">
<?php endif; ?>
</td>
<td style="width:460px">
<span style="font-size:18px;">
@ -16,10 +18,10 @@
?>
</span><br /><br />
<div>
<?php if (Configure::read('MISP.main_logo') && file_exists(APP . '/webroot/img/custom/' . Configure::read('MISP.main_logo'))): ?>
<img src="<?php echo $baseurl?>/img/custom/<?php echo h(Configure::read('MISP.main_logo'));?>" style=" display:block; margin-left: auto; margin-right: auto;" />
<?php if (Configure::read('MISP.main_logo') && file_exists(APP . '/files/img/custom/' . Configure::read('MISP.main_logo'))): ?>
<img src="<?= $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.main_logo')) ?>" style=" display:block; margin-left: auto; margin-right: auto;">
<?php else: ?>
<img src="<?php echo $baseurl?>/img/misp-logo-s-u.png" style="display:block; margin-left: auto; margin-right: auto;"/>
<img src="<?php echo $baseurl?>/img/misp-logo-s-u.png" style="display:block; margin-left: auto; margin-right: auto;">
<?php endif;?>
</div>
<?php
@ -82,7 +84,9 @@
?>
</td>
<td style="width:250px;padding-left:50px">
<?php if (Configure::read('MISP.welcome_logo2')) echo $this->Html->image('custom/' . h(Configure::read('MISP.welcome_logo2')), array('alt' => 'Logo2', 'onerror' => "this.style.display='none';")); ?>
<?php if (Configure::read('MISP.welcome_logo2') && file_exists(APP . '/files/img/custom/' . Configure::read('MISP.welcome_logo2'))): ?>
<img src="<?= $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.welcome_logo2')) ?>" alt="<?= __('Logo2') ?>" onerror="this.style.display='none';">
<?php endif; ?>
</td>
</tr>
</table>

View File

@ -29,8 +29,8 @@
<td style="width:460px">
<br /><br />
<div>
<?php if (Configure::read('MISP.main_logo') && file_exists(APP . '/webroot/img/custom/' . Configure::read('MISP.main_logo'))): ?>
<img src="<?php echo $baseurl?>/img/custom/<?php echo h(Configure::read('MISP.main_logo'));?>" style=" display:block; margin-left: auto; margin-right: auto;" />
<?php if (Configure::read('MISP.main_logo') && file_exists(APP . '/files/img/custom/' . Configure::read('MISP.main_logo'))): ?>
<img src="<?= $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.main_logo')) ?>" style=" display:block; margin-left: auto; margin-right: auto;">
<?php else: ?>
<img src="<?php echo $baseurl?>/img/misp-logo-s-u.png" style="display:block; margin-left: auto; margin-right: auto;"/>
<?php endif;?>

View File

@ -1 +1 @@
Subproject commit 7e8d57e741ee1ba6e764c1a5e0ba236fc2f64126
Subproject commit 838f6497660af602c5d7bde93ce34be1783287dd

@ -1 +1 @@
Subproject commit 260920bf7c9d8f678b0d69730acb17e9a34811f2
Subproject commit 0428b4a43a67feed33837107ece1b144042c619e

View File

@ -29,10 +29,31 @@ sys.path.insert(2, str(_scripts_path / 'python-cybox'))
sys.path.insert(3, str(_scripts_path / 'mixbox'))
sys.path.insert(4, str(_scripts_path / 'misp-stix'))
from misp_stix_converter import (
ExternalSTIX2toMISPParser, InternalSTIX2toMISPParser, _from_misp)
ExternalSTIX2toMISPParser, InternalSTIX2toMISPParser,
MISP_org_uuid, _from_misp)
from stix2.parsing import parse as stix2_parser
def _get_stix_parser(from_misp, args):
arguments = {
'distribution': args.distribution,
'galaxies_as_tags': args.galaxies_as_tags
}
if args.distribution == 4 and args.sharing_group_id is not None:
arguments['sharing_group_id'] = args.sharing_group_id
if from_misp:
return 'InternalSTIX2toMISPParser', arguments
arguments.update(
{
'cluster_distribution': args.cluster_distribution,
'organisation_uuid': args.org_uuid
}
)
if args.cluster_distribution == 4 and args.cluster_sharing_group_id is not None:
arguments['cluster_sharing_group_id'] = args.cluster_sharing_group_id
return 'ExternalSTIX2toMISPParser', arguments
def _handle_return_message(traceback):
if isinstance(traceback, dict):
messages = []
@ -51,16 +72,10 @@ def _process_stix_file(args: argparse.Namespace):
f.read(), allow_custom=True, interoperability=True
)
stix_version = getattr(bundle, 'version', '2.1')
to_call = 'Internal' if _from_misp(bundle.objects) else 'External'
arguments = {
'distribution': args.distribution,
'galaxies_as_tags': args.galaxies_as_tags
}
if args.distribution == 4 and args.sharing_group_id is not None:
arguments['sharing_group_id'] = args.sharing_group_id
parser = globals()[f'{to_call}STIX2toMISPParser'](**arguments)
to_call, arguments = _get_stix_parser(_from_misp(bundle.objects), args)
parser = globals()[to_call](**arguments)
parser.load_stix_bundle(bundle)
parser.parse_stix_bundle()
parser.parse_stix_bundle(single_event=True)
with open(f'{args.input}.out', 'wt', encoding='utf-8') as f:
f.write(parser.misp_event.to_json())
print(
@ -94,6 +109,10 @@ if __name__ == '__main__':
'-i', '--input', required=True, type=Path,
help='Input file containing STIX 2 content.'
)
argparser.add_argument(
'--org_uuid', default=MISP_org_uuid,
help='Organisation UUID to use when creating custom Galaxy clusters.'
)
argparser.add_argument(
'--distribution', type=int, default=0,
help='Distribution level for the resulting MISP Event.'
@ -110,6 +129,14 @@ if __name__ == '__main__':
'--galaxies_as_tags', action='store_true',
help='Import MISP Galaxies as tag names.'
)
argparser.add_argument(
'--cluster_distribution', type=int, default=0,
help='Cluster distribution level for clusters generated from STIX 2.x objects'
)
argparser.add_argument(
'--cluster_sharing_group_id', type=int,
help='Cluster sharing group id when the cluster distribution level is 4.'
)
try:
args = argparser.parse_args()
except SystemExit as e:
@ -122,4 +149,4 @@ if __name__ == '__main__':
)
sys.exit(1)
_process_stix_file(args)
_process_stix_file(args)

@ -1 +1 @@
Subproject commit 80eb7028f9de974d7f163a7563e66b582f61cec0
Subproject commit 2765252e7d097703828cd8b3bdd63ba8ba75c63b

View File

@ -0,0 +1,31 @@
.opinion-gradient-container {
display: flex;
position: relative;
background: #ccc;
border-radius: 3px;
}
.opinion-gradient {
display: inline-block;
position: relative;
height: 100%;
width: 50%;
}
.opinion-gradient-positive {
border-radius: 0 3px 3px 0;
background-image: linear-gradient(90deg, rgb(237, 212, 0), rgb(236, 211, 0), rgb(234, 211, 0), rgb(233, 210, 0), rgb(231, 210, 0), rgb(230, 209, 1), rgb(229, 209, 1), rgb(227, 208, 1), rgb(226, 208, 1), rgb(224, 207, 1), rgb(223, 207, 1), rgb(222, 206, 1), rgb(220, 206, 1), rgb(219, 205, 1), rgb(218, 204, 1), rgb(216, 204, 2), rgb(215, 203, 2), rgb(213, 203, 2), rgb(212, 202, 2), rgb(211, 202, 2), rgb(209, 201, 2), rgb(208, 201, 2), rgb(206, 200, 2), rgb(205, 200, 2), rgb(204, 199, 2), rgb(202, 199, 2), rgb(201, 198, 3), rgb(199, 197, 3), rgb(198, 197, 3), rgb(197, 196, 3), rgb(195, 196, 3), rgb(194, 195, 3), rgb(192, 195, 3), rgb(191, 194, 3), rgb(189, 194, 3), rgb(188, 193, 3), rgb(186, 193, 3), rgb(185, 192, 4), rgb(183, 192, 4), rgb(182, 191, 4), rgb(180, 190, 4), rgb(179, 190, 4), rgb(177, 189, 4), rgb(175, 189, 4), rgb(174, 188, 4), rgb(173, 188, 4), rgb(171, 187, 4), rgb(170, 186, 4), rgb(168, 186, 4), rgb(167, 185, 4), rgb(165, 185, 4), rgb(164, 184, 4), rgb(162, 183, 4), rgb(161, 183, 4), rgb(159, 182, 4), rgb(158, 182, 4), rgb(156, 181, 4), rgb(154, 180, 4), rgb(153, 180, 4), rgb(151, 179, 4), rgb(149, 179, 5), rgb(148, 178, 5), rgb(146, 178, 5), rgb(144, 177, 5), rgb(143, 177, 5), rgb(141, 176, 5), rgb(139, 175, 5), rgb(138, 175, 5), rgb(136, 174, 5), rgb(134, 173, 5), rgb(133, 173, 5), rgb(131, 172, 5), rgb(130, 172, 5), rgb(128, 171, 5), rgb(126, 170, 5), rgb(125, 170, 5), rgb(123, 169, 5), rgb(121, 168, 5), rgb(119, 168, 5), rgb(117, 167, 5), rgb(115, 167, 5), rgb(113, 166, 6), rgb(112, 165, 6), rgb(110, 165, 6), rgb(108, 164, 6), rgb(106, 163, 6), rgb(104, 163, 6), rgb(102, 162, 6), rgb(100, 162, 6), rgb(98, 161, 6), rgb(96, 160, 6), rgb(94, 159, 6), rgb(92, 159, 6), rgb(90, 158, 6), rgb(88, 157, 6), rgb(86, 157, 6), rgb(84, 156, 6), rgb(82, 155, 6), rgb(80, 155, 6),rgb(78, 154, 6))
}
.opinion-gradient-negative {
border-radius: 3px 0 0 3px;
background-image: linear-gradient(90deg, rgb(164, 0, 0), rgb(165, 8, 0), rgb(166, 15, 0), rgb(167, 21, 0), rgb(169, 25, 0), rgb(170, 30, 0), rgb(171, 33, 0), rgb(172, 37, 0), rgb(173, 40, 0), rgb(174, 43, 0), rgb(175, 46, 0), rgb(176, 49, 0), rgb(177, 52, 0), rgb(178, 55, 0), rgb(179, 57, 0), rgb(180, 60, 0), rgb(181, 63, 0), rgb(182, 65, 0), rgb(183, 68, 0), rgb(184, 70, 0), rgb(186, 72, 0), rgb(187, 75, 0), rgb(188, 77, 0), rgb(189, 80, 0), rgb(190, 82, 0), rgb(190, 84, 0), rgb(191, 86, 0), rgb(192, 88, 0), rgb(193, 90, 0), rgb(194, 92, 0), rgb(195, 95, 0), rgb(196, 96, 0), rgb(197, 98, 0), rgb(197, 100, 0), rgb(198, 102, 0), rgb(199, 104, 0), rgb(200, 106, 0), rgb(201, 108, 0), rgb(201, 110, 0), rgb(202, 112, 0), rgb(203, 114, 0), rgb(204, 116, 0), rgb(204, 118, 0), rgb(205, 119, 0), rgb(206, 121, 0), rgb(207, 123, 0), rgb(208, 125, 0), rgb(208, 127, 0), rgb(209, 128, 0), rgb(210, 130, 0), rgb(210, 132, 0), rgb(211, 134, 0), rgb(212, 135, 0), rgb(212, 137, 0), rgb(213, 139, 0), rgb(214, 141, 0), rgb(214, 143, 0), rgb(215, 144, 0), rgb(216, 146, 0), rgb(216, 148, 0), rgb(217, 149, 0), rgb(217, 151, 0), rgb(218, 153, 0), rgb(219, 154, 0), rgb(219, 156, 0), rgb(220, 158, 0), rgb(220, 160, 0), rgb(221, 161, 0), rgb(222, 163, 0), rgb(222, 164, 0), rgb(223, 166, 0), rgb(223, 168, 0), rgb(224, 169, 0), rgb(225, 171, 0), rgb(225, 173, 0), rgb(226, 174, 0), rgb(226, 176, 0), rgb(227, 178, 0), rgb(227, 179, 0), rgb(227, 181, 0), rgb(228, 182, 0), rgb(228, 184, 0), rgb(229, 186, 0), rgb(229, 187, 0), rgb(230, 189, 0), rgb(230, 190, 0), rgb(231, 192, 0), rgb(231, 193, 0), rgb(232, 195, 0), rgb(232, 196, 0), rgb(233, 198, 0), rgb(233, 200, 0), rgb(234, 201, 0), rgb(234, 203, 0), rgb(235, 204, 0), rgb(235, 206, 0), rgb(236, 207, 0), rgb(236, 209, 0), rgb(237, 210, 0), rgb(237, 212, 0));
}
.opinion-gradient-dot {
width: 12px;
height: 12px;
position: absolute;
top: -3px;
z-index: 10;
box-shadow: 0 0 2px 0px #00000066;
border-radius: 50%;
background-color: white;
}

View File

@ -2936,6 +2936,10 @@ Query builder
padding-right: 3px;
}
.highlight-on-hover:hover {
filter: brightness(1.1);
}
.special-tag {
animation: special-tag-color 4s infinite linear;
}

View File

@ -1935,7 +1935,9 @@ function popoverConfirm(clicked, message, placement, callback) {
var href = $clicked.attr("href");
// Load form to get new token
fetchFormDataAjax(href, function (form) {
var $form = $(form);
var $formContainer = $(form);
var $form = $formContainer.is('form') ? $formContainer : $formContainer.find('form');
$clicked.popover('destroy');
xhr({
data: $form.serialize(),
success: function (data) {
@ -4285,27 +4287,9 @@ function feedFormUpdate() {
checkSharingGroup('Feed');
}
function setContextFields() {
if (typeof showContext === "undefined") {
showContext = false;
}
var $button = $('#show_attribute_context');
if (showContext) {
$('.context').show();
$button.removeClass("btn-inverse").addClass("btn-primary");
} else {
$('.context').hide();
$button.removeClass("btn-primary").addClass("btn-inverse");
}
}
function toggleContextFields() {
if (typeof showContext === "undefined") {
showContext = false;
}
showContext = !showContext;
setContextFields();
$('.context').toggle()
$('#show_attribute_context').toggleClass("btn-inverse").toggleClass("btn-primary")
}
function checkOrphanedAttributes() {
@ -5586,9 +5570,13 @@ function loadClusterRelations(clusterId) {
}
}
function submitGenericFormInPlace(callback) {
function submitGenericFormInPlace(callback, forceApi=false) {
var $genericForm = $('.genericForm');
$.ajax({
ajaxOptions = {}
if (forceApi) {
ajaxOptions['headers'] = { Accept: "application/json" }
}
$.ajax(Object.assign({}, {
type: "POST",
url: $genericForm.attr('action'),
data: $genericForm.serialize(), // serializes the form's elements.
@ -5606,7 +5594,7 @@ function submitGenericFormInPlace(callback) {
$('#genericModal').modal();
},
error: xhrFailCallback,
});
}, ajaxOptions));
}
function openIdSelection(clicked, scope, action) {

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ misp-lib-stix2>=3.0.1.1
mixbox>=1.0.5
plyara>=2.1.1
pydeep2>=0.5.1
pymisp==2.4.185
pymisp==2.4.186
python-magic>=0.4.27
pyzmq>=25.1.1
redis>=5.0.1

View File

@ -87,7 +87,7 @@ assert event_preview["Event"]["uuid"] == event.uuid
url = f'servers/pull/{remote_server["id"]}/disable_background_processing:1'
pull_response = pymisp._check_response(pymisp._prepare_request('GET', url))
check_response(pull_response)
assert "Pull completed. 0 events pulled, 0 events could not be pulled, 0 proposals pulled, 0 sightings pulled, 0 clusters pulled." == pull_response["message"], pull_response["message"]
assert "Pull completed. 0 events pulled, 0 events could not be pulled, 0 proposals pulled, 0 sightings pulled, 0 clusters pulled, 0 analyst data pulled." == pull_response["message"], pull_response["message"]
# Test pull background
check_response(pymisp.server_pull(remote_server))