Merge branch 'develop' of github.com:MISP/MISP into develop

pull/9594/head
Christian Studer 2024-02-21 11:45:49 +01:00
commit e29924b55d
No known key found for this signature in database
GPG Key ID: 6BBED1B63A6D639F
84 changed files with 9951 additions and 3465 deletions

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,327 @@
<?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 = [
'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,7 +33,7 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName');
private $__queryVersion = '158';
private $__queryVersion = '159';
public $pyMispVersion = '2.4.185';
public $phpmin = '7.2';
public $phprec = '7.4';
@ -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'));
@ -2692,7 +2696,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 +4377,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

@ -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;

1028
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,7 @@ 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,
);
const ADVANCED_UPDATES_DESCRIPTION = array(
@ -2013,6 +2016,145 @@ 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 '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 +4193,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 +4238,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 ?
@ -7948,6 +8029,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) {

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

@ -0,0 +1,26 @@
<?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 $validate = array(
);
public function beforeValidate($options = array())
{
parent::beforeValidate();
return true;
}
}

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

@ -0,0 +1,26 @@
<?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 $validate = array(
);
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'];
}
/**

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

@ -0,0 +1,146 @@
<?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;
public $validate = array(
);
/** @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,

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

@ -0,0 +1,228 @@
<?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,
],
[
'field' => 'related_object_uuid',
'class' => 'span4',
],
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,104 @@
<?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' => 'Edit',
'url' => '/analyst_data_blocklists/edit',
'url_params_data_paths' => array(
'AnalystDataBlocklist.id'
),
'icon' => 'edit',
),
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 ?>">
<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 baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid
} else if (note.related_object_type == 'Object') {
return 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 note') ?>"><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

@ -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,17 @@
<?php
$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

@ -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

@ -50,7 +50,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 +107,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 +159,25 @@ 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/assetLoader', [
'js' => ['doT', 'moment.min'],
'css' => ['analyst-data',],
]);
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,35 @@
<?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");
}
$fileContent = base64_encode(FileAccessTool::readFromFile($imagePath));
$base64 = "data:$mime;base64,$fileContent";
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

@ -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

@ -17,9 +17,9 @@
</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;" />
<img src="<?= $this->Image->base64(APP . 'files/img/custom/' . Configure::read('MISP.home_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

View File

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

@ -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

@ -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))