MISP/app/Controller/AttributesController.php

3009 lines
138 KiB
PHP

<?php
App::uses('AppController', 'Controller');
App::uses('Folder', 'Utility');
App::uses('File', 'Utility');
App::uses('AttachmentTool', 'Tools');
/**
* @property Attribute $Attribute
*/
class AttributesController extends AppController
{
public $components = array('RequestHandler');
public $paginate = [
'limit' => 60,
'maxLimit' => 9999,
'conditions' => array('AND' => array('Attribute.deleted' => 0)),
'order' => 'Attribute.event_id DESC',
'recursive' => -1,
'contain' => array(
'Event' => array(
'fields' => array('Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.info', 'Event.user_id', 'Event.date'),
),
'AttributeTag',
'Object' => array(
'fields' => array('Object.id', 'Object.distribution', 'Object.sharing_group_id')
),
'SharingGroup' => ['fields' => ['SharingGroup.name']],
),
];
public function beforeFilter()
{
parent::beforeFilter();
$this->Auth->allow('restSearch');
$this->Auth->allow('returnAttributes');
$this->Auth->allow('downloadAttachment');
$this->Auth->allow('text');
$this->Auth->allow('rpz');
$this->Auth->allow('bro');
// permit reuse of CSRF tokens on the search page.
if ('search' === $this->request->params['action']) {
$this->Security->csrfCheck = false;
}
$this->Security->unlockedActions[] = 'getMassEditForm';
$this->Security->unlockedActions[] = 'search';
if ($this->request->action === 'add_attachment') {
$this->Security->unlockedFields = array('values');
}
// convert uuid to id if present in the url and overwrite id field
if (isset($this->request->params->query['uuid'])) {
$params = array(
'conditions' => array('Attribute.uuid' => $this->request->params->query['uuid']),
'recursive' => 0,
'fields' => 'Attribute.id'
);
$result = $this->Attribute->find('first', $params);
if (isset($result['Attribute']) && isset($result['Attribute']['id'])) {
$id = $result['Attribute']['id'];
$this->params->addParams(array('pass' => array($id))); // FIXME find better way to change id variable if uuid is found. params->url and params->here is not modified accordingly now
}
}
if ($this->request->action === 'viewPicture') {
$this->Security->doNotGenerateToken = true;
}
}
public function index()
{
$user = $this->Auth->user();
$this->paginate['conditions']['AND'][] = $this->Attribute->buildConditions($user);
$attributes = $this->paginate();
if ($this->_isRest()) {
$attributes = array_column($attributes, 'Attribute');
return $this->RestResponse->viewData($attributes, $this->response->type());
}
$this->Attribute->attachTagsToAttributes($attributes, ['includeAllTags' => true]);
$orgTable = $this->Attribute->Event->Orgc->find('all', [
'fields' => ['Orgc.id', 'Orgc.name', 'Orgc.uuid'],
]);
$orgTable = Hash::combine($orgTable, '{n}.Orgc.id', '{n}.Orgc');
$sgids = $this->Attribute->SharingGroup->authorizedIds($user);
foreach ($attributes as &$attribute) {
if (isset($orgTable[$attribute['Event']['orgc_id']])) {
$attribute['Event']['Orgc'] = $orgTable[$attribute['Event']['orgc_id']];
}
$temp = $this->Attribute->Correlation->getRelatedAttributes(
$user,
$sgids,
$attribute['Attribute'],
[],
true
);
foreach ($temp as &$t) {
$t['info'] = $t['Event']['info'];
$t['org_id'] = $t['Event']['org_id'];
$t['date'] = $t['Event']['date'];
}
$attribute['Event']['RelatedAttribute'][$attribute['Attribute']['id']] = $temp;
}
list($attributes, $sightingsData) = $this->__searchUI($attributes, $user);
$this->set('isSearch', 0);
$this->set('sightingsData', $sightingsData);
$this->set('orgTable', array_column($orgTable, 'name', 'id'));
$this->set('shortDist', $this->Attribute->shortDist);
$this->set('attributes', $attributes);
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
$this->set('distributionLevels', $this->Attribute->distributionLevels);
}
public function add($eventId = false)
{
if ($this->request->is('get') && $this->_isRest()) {
return $this->RestResponse->describe('Attributes', 'add', false, $this->response->type());
}
if ($eventId === false) {
throw new MethodNotAllowedException(__('No event ID set.'));
}
$event = $this->Attribute->Event->fetchSimpleEvent($this->Auth->user(), $eventId, ['contain' => ['Orgc']]);
if (!$event) {
throw new NotFoundException(__('Invalid event'));
}
if (!$this->__canModifyEvent($event)) {
throw new ForbiddenException(__('You do not have permission to do that.'));
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $event['Event']['id']);
}
if ($this->request->is('ajax')) {
$this->set('ajax', true);
$this->layout = false;
} else {
$this->set('ajax', false);
}
if ($this->request->is('post')) {
if ($this->request->is('ajax')) {
$this->autoRender = false;
}
if (!isset($this->request->data['Attribute'])) {
$this->request->data = array('Attribute' => $this->request->data);
}
if (isset($this->request->data['Attribute']['distribution']) && $this->request->data['Attribute']['distribution'] == 4) {
if (!$this->__canUseSharingGroup($this->request->data['Attribute']['sharing_group_id'])) {
throw new ForbiddenException(__('Invalid Sharing Group or not authorised.'));
}
}
//
// multiple attributes in batch import
//
if (!empty($this->request->data['Attribute']['batch_import']) || (!empty($this->request->data['Attribute']['value']) && is_array($this->request->data['Attribute']['value']))) {
$attributes = array();
if (is_array($this->request->data['Attribute']['value'])) {
$values = $this->request->data['Attribute']['value'];
} else {
$values = explode("\n", rtrim($this->request->data['Attribute']['value'], "\n"));
}
$temp = $this->request->data['Attribute'];
foreach ($values as $value) {
$temp['value'] = $value;
$attributes[] = $temp;
}
} else {
$attributes = $this->request->data['Attribute'];
}
if (!isset($attributes[0])) {
$attributes = array(0 => $attributes);
}
$fails = [];
$successes = 0;
$attributeCount = count($attributes);
$insertedIds = array();
foreach ($attributes as $k => $attribute) {
$validationErrors = array();
$this->Attribute->captureAttribute($attribute, $event['Event']['id'], $this->Auth->user(), false, false, $event, $validationErrors, $this->params['named']);
if (empty($validationErrors)) {
$insertedIds[] = $this->Attribute->id;
$successes++;
} else {
$fails["attribute_" . $k] = $validationErrors;
}
}
if ($successes !== 0) {
$this->Attribute->Event->unpublishEvent($event);
}
if ($this->_isRest()) {
if ($successes !== 0) {
$attributes = $this->Attribute->find('all', array(
'recursive' => -1,
'conditions' => array('Attribute.id' => $insertedIds),
'contain' => array(
'AttributeTag' => array(
'Tag' => array('fields' => array('Tag.id', 'Tag.name', 'Tag.colour', 'Tag.numerical_value'))
)
)
));
if (count($attributes) === 1) {
$attributes = $attributes[0];
} else {
$result = array('Attribute' => array());
foreach ($attributes as $attribute) {
$temp = $attribute['Attribute'];
if (!empty($attribute['AttributeTag'])) {
foreach ($attribute['AttributeTag'] as $at) {
$temp['Tag'][] = $at['Tag'];
}
}
$result['Attribute'][] = $temp;
}
$attributes = $result;
unset($result);
}
return $this->RestResponse->viewData($attributes, $this->response->type(), $fails);
} else {
if ($attributeCount === 1) {
return $this->RestResponse->saveFailResponse('Attributes', 'add', false, $fails["attribute_0"], $this->response->type());
} else {
return $this->RestResponse->saveFailResponse('Attributes', 'add', false, $fails, $this->response->type());
}
}
} else {
if (empty($fails)) {
$message = __('Attributes saved.');
} else {
if ($attributeCount > 1) {
$flashErrorMessage = [];
foreach ($attributes as $k => $attribute) {
if (isset($fails["attribute_$k"])) {
$reason = '';
foreach ($fails["attribute_" . $k] as $failKey => $failData) {
$reason = $failKey . ': ' . $failData[0];
}
$flashErrorMessage[] = '<span class="red bold">' . h($attribute["value"]) . '</span> (' . h($reason) . ')';
} else {
$flashErrorMessage[] = '<span class="green bold">' . h($attribute["value"]) . '</span>';
}
}
$flashErrorMessage = implode('<br>', $flashErrorMessage);
$this->Session->write('flashErrorMessage', $flashErrorMessage);
if ($successes === 0) {
$message = __('Attributes could not be saved. Click $flashErrorMessage for more info');
} else {
$message = __('Attributes saved, however, %s attributes could not be saved. Click $flashErrorMessage for more info', count($fails));
}
} else {
$message = __('Attribute could not be saved.');
}
}
if ($this->request->is('ajax')) {
if (!empty($successes)) {
$data = ['saved' => true, 'success' => $message];
} else {
$message = $attributeCount > 1 ? $message : $this->Attribute->validationErrors;
$data = ['saved' => false, 'errors' => $message];
if (!empty($flashErrorMessage)) {
$data['full_errors'] = $flashErrorMessage;
}
}
return $this->RestResponse->viewData($data, 'json');
}
if (empty($fails)) {
$this->Flash->success($message);
} else {
$this->Flash->error($message);
}
if ($successes > 0) {
$this->redirect(array('controller' => 'events', 'action' => 'view', $event['Event']['id']));
}
}
}
// combobox for types
$types = $this->Attribute->getNonAttachmentTypes();
$types = $this->_arrayToValuesIndexArray($types);
$this->set('types', $types);
// combobox for categories
$categories = array_keys($this->Attribute->categoryDefinitions);
$categories = $this->_arrayToValuesIndexArray($categories);
$this->set('categories', $categories);
$this->__common();
$this->set('title_for_layout', __('Add attribute'));
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
$this->set('event', $event);
$this->set('action', $this->request->action);
}
public function download($id = null)
{
$conditions = $this->__idToConditions($id);
$conditions['Attribute.type'] = array('attachment', 'malware-sample');
$attributes = $this->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => $conditions, 'flatten' => true));
if (empty($attributes)) {
throw new UnauthorizedException(__('Attribute does not exist or you do not have the permission to download this attribute.'));
}
return $this->__downloadAttachment($attributes[0]['Attribute']);
}
private function __downloadAttachment(array $attribute)
{
$file = $this->Attribute->getAttachmentFile($attribute);
if ('attachment' === $attribute['type']) {
$filename = $attribute['value'];
$fileExt = pathinfo($filename, PATHINFO_EXTENSION);
$filename = substr($filename, 0, strlen($filename) - strlen($fileExt) - 1);
} elseif ('malware-sample' === $attribute['type']) {
$filenameHash = explode('|', $attribute['value']);
$filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\'));
$fileExt = "zip";
} else {
throw new NotFoundException(__('Attribute not an attachment or malware-sample'));
}
$download_attachments_on_load = Configure::check('MISP.download_attachments_on_load') ? Configure::read('MISP.download_attachments_on_load') : true;
return $this->RestResponse->sendFile($file, $fileExt, $download_attachments_on_load, $filename . '.' . $fileExt);
}
public function add_attachment($eventId = null)
{
$event = $this->Attribute->Event->fetchSimpleEvent($this->Auth->user(), $eventId, ['contain' => ['Orgc']]);
if (empty($event)) {
throw new NotFoundException(__('Invalid Event.'));
}
if (!$this->__canModifyEvent($event)) {
throw new ForbiddenException(__('You do not have permission to do that.'));
}
if ($this->request->is('post')) {
if (isset($this->request->data['Attribute']['distribution']) && $this->request->data['Attribute']['distribution'] == 4) {
if (!$this->__canUseSharingGroup($this->request->data['Attribute']['sharing_group_id'])) {
throw new ForbiddenException(__('Invalid Sharing Group or not authorised.'));
}
}
$fails = array();
$success = 0;
foreach ($this->request->data['Attribute']['values'] as $value) {
// Check if there were problems with the file upload
// only keep the last part of the filename, this should prevent directory attacks
$filename = basename($value['name']);
$tmpfile = new File($value['tmp_name']);
if ((isset($value['error']) && $value['error'] == 0) ||
(!empty($value['tmp_name']) && $value['tmp_name'] != 'none')
) {
if (!is_uploaded_file($tmpfile->path)) {
throw new InternalErrorException(__('PHP says file was not uploaded. Are you attacking me?'));
}
} else {
$fails[] = $filename;
continue;
}
if ($this->request->data['Attribute']['malware']) {
if ($this->request->data['Attribute']['advanced']) {
$result = $this->Attribute->advancedAddMalwareSample(
$event['Event']['id'],
$this->request->data['Attribute'],
$filename,
$tmpfile
);
} else {
$result = $this->Attribute->simpleAddMalwareSample(
$event['Event']['id'],
$this->request->data['Attribute'],
$filename,
$tmpfile
);
}
if ($result) {
$success++;
} else {
$fails[] = $filename;
}
if (!empty($result)) {
foreach ($result['Object'] as $object) {
$object['distribution'] = $this->request->data['Attribute']['distribution'];
if (!empty($this->request->data['sharing_group_id'])) {
$object['sharing_group_id'] = $this->request->data['Attribute']['sharing_group_id'];
}
foreach ($object['Attribute'] as $ka => $attribute) {
$object['Attribute'][$ka]['distribution'] = 5;
}
$this->Attribute->Object->captureObject(array('Object' => $object), $event['Event']['id'], $this->Auth->user());
}
if (!empty($result['ObjectReference'])) {
foreach ($result['ObjectReference'] as $reference) {
$this->Attribute->Object->ObjectReference->smartSave($reference, $event['Event']['id']);
}
}
}
} else {
$attribute = array(
'Attribute' => array(
'value' => $filename,
'category' => $this->request->data['Attribute']['category'],
'type' => 'attachment',
'event_id' => $event['Event']['id'],
'data_raw' => $tmpfile->read(),
'comment' => $this->request->data['Attribute']['comment'],
'to_ids' => 0,
'distribution' => $this->request->data['Attribute']['distribution'],
'sharing_group_id' => isset($this->request->data['Attribute']['sharing_group_id']) ? $this->request->data['Attribute']['sharing_group_id'] : 0,
)
);
$this->Attribute->create();
$r = $this->Attribute->save($attribute);
if ($r == false) {
$fails[] = $filename;
} else {
$success++;
}
}
}
$message = __n('The attachment have been uploaded.', 'The attachments have been uploaded.', $success);
if (!empty($fails)) {
$message = __('Some of the attachments failed to upload. The failed files were: %s - This can be caused by the attachments already existing in the event.', implode(', ', $fails));
}
if (empty($success)) {
if (empty($fails)) {
$message = __('The attachment(s) could not be saved. Please contact your administrator.');
}
} else {
$this->Attribute->Event->unpublishEvent($event);
}
if (empty($success) && !empty($fails)) {
$this->Flash->error($message);
} else {
$this->Flash->success($message);
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $event['Event']['id']);
}
$this->redirect(array('controller' => 'events', 'action' => 'view', $event['Event']['id']));
} else {
// set the event_id in the form
$this->request->data['Attribute']['event_id'] = $event['Event']['id'];
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $event['Event']['id']);
}
// Filter categories that contains attachment type
$selectedCategories = array();
foreach ($this->Attribute->categoryDefinitions as $category => $values) {
foreach ($values['types'] as $type) {
if ($this->Attribute->typeIsAttachment($type)) {
$selectedCategories[] = $category;
break;
}
}
}
// Create list of categories that should be marked as malware sample by default
$isMalwareSampleCategory = [];
foreach ($selectedCategories as $category) {
$possibleMalwareSample = false;
foreach ($this->Attribute->categoryDefinitions[$category]['types'] as $type) {
if ($this->Attribute->typeIsMalware($type)) {
$possibleMalwareSample = true;
break;
}
}
$isMalwareSampleCategory[$category] = $possibleMalwareSample;
}
$categories = $this->_arrayToValuesIndexArray($selectedCategories);
$this->set('categories', $categories);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
$this->set('isMalwareSampleCategory', $isMalwareSampleCategory);
$this->set('advancedExtractionAvailable', $this->Attribute->isAdvancedExtractionAvailable());
$this->__common();
$this->set('title_for_layout', __('Add attachment'));
$this->set('event', $event);
}
private function __common()
{
$distributionData = $this->Attribute->fetchDistributionData($this->Auth->user());
$this->set('sharingGroups', $distributionData['sgs']);
$this->set('distributionLevels', $distributionData['levels']);
$this->set('initialDistribution', $distributionData['initial']);
$this->set('fieldDesc', $this->__fieldDesc());
$this->set('nonCorrelatingTypes', Attribute::NON_CORRELATING_TYPES);
$this->loadModel('Noticelist');
$notice_list_triggers = $this->Noticelist->getTriggerData();
$this->set('notice_list_triggers', json_encode($notice_list_triggers));
}
/**
* @return array|array[]
*/
private function __fieldDesc()
{
$fieldDesc = ['category' => [], 'type' => [], 'distribution' => []];
foreach ($this->Attribute->categoryDefinitions as $key => $value) {
$fieldDesc['category'][$key] = isset($value['formdesc']) ? $value['formdesc'] : $value['desc'];
}
foreach ($this->Attribute->typeDefinitions as $key => $value) {
$fieldDesc['type'][$key] = isset($value['formdesc']) ? $value['formdesc'] : $value['desc'];
}
foreach ($this->Attribute->distributionLevels as $key => $value) {
$fieldDesc['distribution'][$key] = $this->Attribute->distributionDescriptions[$key]['formdesc'];
}
return $fieldDesc;
}
// Imports the CSV threatConnect file to multiple attributes
public function add_threatconnect($eventId = null)
{
if ($this->request->is('post')) {
$this->loadModel('Event');
$this->Event->id = $eventId;
$this->Event->recursive = -1;
$this->Event->read();
if (!$this->__canModifyEvent($this->Event->data)) {
throw new ForbiddenException(__('You do not have permission to do that.'));
}
//
// File upload
//
// Check if there were problems with the file upload
$tmpfile = new File($this->request->data['Attribute']['value']['tmp_name']);
if ((isset($this->request->data['Attribute']['value']['error']) && $this->request->data['Attribute']['value']['error'] == 0) ||
(!empty($this->request->data['Attribute']['value']['tmp_name']) && $this->request->data['Attribute']['value']['tmp_name'] != 'none')
) {
if (!is_uploaded_file($tmpfile->path)) {
throw new InternalErrorException(__('PHP says file was not uploaded. Are you attacking me?'));
}
} else {
$this->Flash->error(__('There was a problem to upload the file.', true), 'default', array(), 'error');
$this->redirect(array('controller' => 'attributes', 'action' => 'add_threatconnect', $this->request->data['Attribute']['event_id']));
}
// verify mime type
$file_info = $tmpfile->info();
if ($file_info['mime'] != 'text/plain') {
$this->Flash->error('File not in CSV format.', 'default', array(), 'error');
$this->redirect(array('controller' => 'attributes', 'action' => 'add_threatconnect', $this->request->data['Attribute']['event_id']));
}
// parse uploaded csv file
$filename = $tmpfile->path;
$header = null;
$entries = array();
if (($handle = fopen($filename, 'r')) !== false) {
while (($row = fgetcsv($handle, 0, ',', '"')) !== false) {
if (!$header) {
$header = $row;
} else {
$entries[] = array_combine($header, $row);
}
}
fclose($handle);
}
// verify header of the file (first row)
$required_headers = array('Type', 'Value', 'Confidence', 'Description', 'Source');
// TODO i18n
if (count(array_intersect($header, $required_headers)) != count($required_headers)) {
$this->Flash->error('Incorrect ThreatConnect headers. The minimum required headers are: '.implode(',', $required_headers), 'default', array(), 'error');
$this->redirect(array('controller' => 'attributes', 'action' => 'add_threatconnect', $this->request->data['Attribute']['event_id']));
}
//
// import attributes
//
$attributes = array(); // array with all the attributes we're going to save
foreach ($entries as $entry) {
$attribute = array();
$attribute['event_id'] = $this->request->data['Attribute']['event_id'];
$attribute['value'] = $entry['Value'];
$attribute['to_ids'] = ($entry['Confidence'] > 51) ? 1 : 0; // To IDS if high confidence
$attribute['comment'] = $entry['Description'];
$attribute['distribution'] = '3'; // 'All communities'
if (Configure::read('MISP.default_attribute_distribution') != null) {
if (Configure::read('MISP.default_attribute_distribution') === 'event') {
$attribute['distribution'] = $this->Event->data['Event']['distribution'];
} else {
$attribute['distribution'] = Configure::read('MISP.default_attribute_distribution');
}
}
switch ($entry['Type']) {
case 'Address':
$attribute['category'] = 'Network activity';
$attribute['type'] = 'ip-dst';
break;
case 'Host':
$attribute['category'] = 'Network activity';
$attribute['type'] = 'domain';
break;
case 'EmailAddress':
$attribute['category'] = 'Payload delivery';
$attribute['type'] = 'email-src';
break;
case 'File':
$attribute['category'] = 'Artifacts dropped';
$attribute['value'] = strtolower($attribute['value']);
if (preg_match("#^[0-9a-f]{32}$#", $attribute['value'])) {
$attribute['type'] = 'md5';
} elseif (preg_match("#^[0-9a-f]{40}$#", $attribute['value'])) {
$attribute['type'] = 'sha1';
} elseif (preg_match("#^[0-9a-f]{64}$#", $attribute['value'])) {
$attribute['type'] = 'sha256';
} else {
// do not keep attributes that do not have a match
$attribute=null;
}
break;
case 'URL':
$attribute['category'] = 'Network activity';
$attribute['type'] = 'url';
break;
default:
// do not keep attributes that do not have a match
$attribute=null;
}
// add attribute to the array that will be saved
if ($attribute) {
$attributes[] = $attribute;
}
}
//
// import source info:
//
// 1/ iterate over all the sources, unique
// 2/ add uniques as 'Internal reference'
// 3/ if url format -> 'link'
// else 'comment'
$references = array();
foreach ($entries as $entry) {
if (empty($entry['Source'])) {
continue;
}
$references[$entry['Source']] = true;
}
$references = array_keys($references);
// generate the Attributes
foreach ($references as $reference) {
$attribute = array();
$attribute['event_id'] = $this->request->data['Attribute']['event_id'];
$attribute['category'] = 'Internal reference';
if (preg_match('#^(http|ftp)(s)?\:\/\/((([a-z|0-9|\-]{1,25})(\.)?){2,7})($|/.*$)#i', $reference)) {
$attribute['type'] = 'link';
} else {
$attribute['type'] = 'comment';
}
$attribute['value'] = $reference;
$attribute['distribution'] = 3; // 'All communities'
// add attribute to the array that will be saved
$attributes[] = $attribute;
}
//
// finally save all the attributes at once, and continue if there are validation errors
//
$results = array('successes' => 0, 'fails' => 0);
foreach ($attributes as $attribute) {
$this->Attribute->create();
$result = $this->Attribute->save($attribute);
if (!$result) {
$results['fails']++;
} else {
$results['successes']++;
}
}
// data imported (with or without errors)
// remove the published flag from the event
$this->loadModel('Event');
$this->Event->id = $this->request->data['Attribute']['event_id'];
$this->Event->saveField('published', 0);
// everything is done, now redirect to event view
$message = __('The ThreatConnect data has been imported.');
if ($results['successes'] != 0) {
$flashType = 'success';
$temp = sprintf(__('%s entries imported.'), $results['successes']);
$message .= ' ' . $temp;
}
if ($results['fails'] != 0) {
$temp = sprintf(__('%s entries could not be imported.'), $results['fails']);
$message .= ' ' . $temp;
}
$this->Flash->{empty($flashType) ? 'error' : $flashType}($message);
$this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Attribute']['event_id']));
} else {
// set the event_id in the form
$this->request->data['Attribute']['event_id'] = $eventId;
}
// form not submitted, show page
$this->loadModel('Event');
$events = $this->Event->findById($eventId);
$this->set('published', $events['Event']['published']);
}
public function edit($id = null)
{
if ($this->request->is('get') && $this->_isRest()) {
return $this->RestResponse->describe('Attributes', 'edit', false, $this->response->type());
}
$attribute = $this->__fetchAttribute($id);
if (empty($attribute)) {
throw new NotFoundException('Invalid attribute');
}
$this->Attribute->data = $attribute;
if ($this->Attribute->data['Attribute']['deleted']) {
throw new NotFoundException(__('Invalid attribute'));
}
$this->Attribute->id = $attribute['Attribute']['id'];
if (!$this->__canModifyEvent($attribute)) {
$message = __('You do not have permission to do that.');
if ($this->_isRest()) {
throw new ForbiddenException($message);
} else {
$this->Flash->error($message);
$this->redirect(array('controller' => 'events', 'action' => 'index'));
}
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $this->Attribute->data['Attribute']['event_id']);
}
$eventId = $this->Attribute->data['Attribute']['event_id'];
if ($this->request->is('post') || $this->request->is('put')) {
if (!isset($this->request->data['Attribute'])) {
$this->request->data = array('Attribute' => $this->request->data);
}
if (isset($this->request->data['Attribute']['distribution']) && $this->request->data['Attribute']['distribution'] == 4) {
if (!$this->__canUseSharingGroup($this->request->data['Attribute']['sharing_group_id'])) {
throw new ForbiddenException(__('Invalid Sharing Group or not authorised.'));
}
}
$dateObj = new DateTime();
$existingAttribute = $this->Attribute->find('first', [
'conditions' => [
'Attribute.uuid' => $this->Attribute->data['Attribute']['uuid']
],
'recursive' => -1
]
);
// check if the attribute has a timestamp already set (from a previous instance that is trying to edit via synchronisation)
// check which attribute is newer
if (count($existingAttribute) && !$existingAttribute['Attribute']['deleted']) {
$this->request->data['Attribute']['id'] = $existingAttribute['Attribute']['id'];
$this->request->data['Attribute']['event_id'] = $existingAttribute['Attribute']['event_id'];
$this->request->data['Attribute']['object_id'] = $existingAttribute['Attribute']['object_id'];
$this->request->data['Attribute']['uuid'] = $existingAttribute['Attribute']['uuid'];
$skipTimeCheck = false;
if (!isset($this->request->data['Attribute']['timestamp'])) {
$this->request->data['Attribute']['timestamp'] = $dateObj->getTimestamp();
$skipTimeCheck = true;
}
if ($skipTimeCheck || $this->request->data['Attribute']['timestamp'] > $existingAttribute['Attribute']['timestamp']) {
$recoverFields = array('value', 'to_ids', 'distribution', 'category', 'type', 'comment', 'first_seen', 'last_seen');
foreach ($recoverFields as $rF) {
if (!isset($this->request->data['Attribute'][$rF])) {
$this->request->data['Attribute'][$rF] = $existingAttribute['Attribute'][$rF];
}
}
// carry on with adding this attribute - Don't forget! if orgc!=user org, create shadow attribute, not attribute!
} else {
// the old one is newer or the same, replace the request's attribute with the old one
throw new MethodNotAllowedException(__('Attribute could not be saved: Attribute in the request not newer than the local copy.'));
}
} else {
if ($this->_isRest() || $this->response->type() === 'application/json') {
throw new NotFoundException(__('Invalid attribute.'));
} else {
$this->Flash->error(__('Invalid attribute.'));
$this->redirect(array('controller' => 'events', 'action' => 'index'));
}
}
if ($existingAttribute['Attribute']['object_id']) {
$result = $this->Attribute->save($this->request->data, array('fieldList' => Attribute::EDITABLE_FIELDS));
if ($result) {
$this->Attribute->AttributeTag->handleAttributeTags($this->Auth->user(), $this->request->data['Attribute'], $attribute['Event']['id'], $capture=true);
}
$this->Attribute->Object->updateTimestamp($existingAttribute['Attribute']['object_id']);
} else {
$result = $this->Attribute->save($this->request->data, array('fieldList' => Attribute::EDITABLE_FIELDS));
if ($result) {
$this->Attribute->AttributeTag->handleAttributeTags($this->Auth->user(), $this->request->data['Attribute'], $attribute['Event']['id'], $capture=true);
}
if ($this->request->is('ajax')) {
$this->autoRender = false;
if ($result) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Attribute updated.')),'status' => 200, 'type' => 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Could not update attribute, reason: ' . json_encode($this->Attribute->validationErrors))),'status' => 200, 'type' => 'json'));
}
}
}
if ($result) {
$this->Flash->success(__('The attribute has been saved'));
// remove the published flag from the event
$this->Attribute->Event->unpublishEvent($eventId, false, $dateObj->getTimestamp());
if (!empty($this->Attribute->data['Attribute']['object_id'])) {
$this->Attribute->Object->updateTimestamp($this->Attribute->data['Attribute']['object_id'], $dateObj->getTimestamp());
}
if ($this->_isRest()) {
$saved_attribute = $this->Attribute->find('first', array(
'conditions' => array('id' => $this->Attribute->id),
'recursive' => -1,
'contain' => array('AttributeTag' => array('Tag'))
));
if ($this->response->type() === 'application/json') {
$type = 'json';
} else {
$type = 'xml';
}
App::uses(strtoupper($type) . 'ConverterTool', 'Tools');
$tool = strtoupper($type) . 'ConverterTool';
$converter = new $tool();
$saved_attribute = $converter->convertAttribute($saved_attribute, true);
return $this->RestResponse->viewData($saved_attribute, $type);
} else {
$this->redirect(array('controller' => 'events', 'action' => 'view', $eventId));
}
} else {
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Attributes', 'edit', false, $this->Attribute->validationErrors);
} else {
if (!CakeSession::read('Message.flash')) {
$this->Flash->error(__('The attribute could not be saved. Please, try again.'));
} else {
$this->request->data = $this->Attribute->read(null, $id);
}
}
}
} else {
$this->request->data = $this->Attribute->find('first', [
'recursive' => -1,
'conditions' => ['Attribute.id' => $id]
]);
}
$this->set('attribute', $this->request->data);
if (!empty($this->request->data['Attribute']['object_id'])) {
$this->set('objectAttribute', true);
} else {
$this->set('objectAttribute', false);
}
// enabling / disabling the distribution field in the edit view based on whether user's org == orgc in the event
$this->set('event', $attribute); // Attribute contains 'Event' field
// needed for RBAC
// combobox for types
$isAttachment = $this->Attribute->typeIsAttachment($attribute['Attribute']['type']);
$this->set('attachment', $isAttachment);
if ($isAttachment) {
$types = [$attribute['Attribute']['type'] => $attribute['Attribute']['type']];
} else {
$types = $this->Attribute->getNonAttachmentTypes();
$types = $this->_arrayToValuesIndexArray($types);
}
$this->set('types', $types);
$this->__common();
$this->set('title_for_layout', __('Edit attribute'));
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$categoryDefinitions = $this->Attribute->categoryDefinitions;
$categories = array_keys($categoryDefinitions);
$categories = $this->_arrayToValuesIndexArray($categories);
if (!empty($this->request->data['Attribute']['object_id'])) {
foreach ($categoryDefinitions as $k => $v) {
if (!in_array($this->request->data['Attribute']['type'], $v['types'])) {
unset($categoryDefinitions[$k]);
}
}
foreach ($categories as $k => $v) {
if (!isset($categoryDefinitions[$k])) {
unset($categories[$k]);
}
}
}
$this->set('categories', $categories);
$this->set('categoryDefinitions', $categoryDefinitions);
$this->set('action', $this->action);
$this->render('add');
}
// ajax edit - post a single edited field and this method will attempt to save it and return a json with the validation errors if they occur.
public function editField($id)
{
$attribute = $this->Attribute->fetchAttributeSimple($this->Auth->user(), [
'conditions' => ['Attribute.id' => $id],
]);
if (empty($attribute)) {
return new CakeResponse(array('body'=> json_encode(array('fail' => false, 'errors' => 'Invalid attribute')), 'status' => 200, 'type' => 'json'));
}
if (!$this->__canModifyEvent($attribute)) {
return new CakeResponse(array('body' => json_encode(array('fail' => false, 'errors' => 'You do not have permission to do that')), 'status' => 200, 'type' => 'json'));
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $attribute['Attribute']['event_id']);
}
if (empty($this->request->data['Attribute'])) {
$this->request->data = array('Attribute' => $this->request->data);
if (empty($this->request->data['Attribute'])) {
throw new MethodNotAllowedException(__('Invalid input.'));
}
}
$validFields = array('value', 'category', 'type', 'comment', 'to_ids', 'distribution', 'first_seen', 'last_seen');
$changed = false;
foreach ($this->request->data['Attribute'] as $changedKey => $changedField) {
if (!in_array($changedKey, $validFields, true)) {
throw new MethodNotAllowedException(__('Invalid field.'));
}
if ($attribute['Attribute'][$changedKey] == $changedField) {
return new CakeResponse(array('body'=> json_encode(array('errors'=> array('value' => 'nochange'))), 'status'=>200, 'type' => 'json'));
}
$attribute['Attribute'][$changedKey] = $changedField;
$changed = true;
}
if (!$changed) {
return new CakeResponse(array('body'=> json_encode(array('errors'=> array('value' => 'nochange'))), 'status'=>200, 'type' => 'json'));
}
$time = time();
$attribute['Attribute']['timestamp'] = $time;
if ($this->Attribute->save($attribute)) {
$this->Attribute->Event->unpublishEvent($attribute['Attribute']['event_id'], false, $time);
if ($attribute['Attribute']['object_id'] != 0) {
$this->Attribute->Object->updateTimestamp($attribute['Attribute']['object_id'], $time);
}
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Field updated.', 'check_publish' => true)), 'status'=>200, 'type' => 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $this->Attribute->validationErrors)), 'status'=>200, 'type' => 'json'));
}
}
public function view($id)
{
if ($this->request->is('head')) { // Just check if attribute exists
$attribute = $this->Attribute->fetchAttributesSimple($this->Auth->user(), [
'conditions' => $this->__idToConditions($id),
'fields' => ['Attribute.id'],
]);
return new CakeResponse(['status' => $attribute ? 200 : 404]);
}
$attribute = $this->__fetchAttribute($id);
if (empty($attribute)) {
throw new MethodNotAllowedException(__('Invalid attribute'));
}
if ($this->_isRest()) {
if (isset($attribute['AttributeTag'])) {
foreach ($attribute['AttributeTag'] as $k => $tag) {
$attribute['Attribute']['Tag'][$k] = $tag['Tag'];
}
}
unset($attribute['Attribute']['value1']);
unset($attribute['Attribute']['value2']);
$this->set('Attribute', $attribute['Attribute']);
$this->set('_serialize', array('Attribute'));
} else {
$this->redirect('/events/view/' . $attribute['Attribute']['event_id']);
}
}
public function viewPicture($id, $thumbnail=false)
{
$user = $this->_closeSession();
$conditions = $this->__idToConditions($id);
$conditions['Attribute.type'] = 'attachment';
if ($this->_isRest()) {
$options = array(
'conditions' => $conditions,
'includeAllTags' => false,
'includeAttributeUuid' => true,
'flatten' => true,
'deleted' => [0, 1],
'withAttachments' => true,
);
$attribute = $this->Attribute->fetchAttributes($user, $options);
if (empty($attribute)) {
throw new MethodNotAllowedException('Invalid attribute');
}
$attribute = $attribute[0];
if (!$this->Attribute->isImage($attribute['Attribute'])) {
throw new NotFoundException("Attribute is not an image.");
}
return $this->RestResponse->viewData($attribute['Attribute']['data'], $this->response->type());
}
$attribute = $this->Attribute->fetchAttributeSimple($user, [
'conditions' => $conditions,
'fields' => ['Attribute.id', 'Attribute.event_id', 'Attribute.type', 'Attribute.value'],
]);
if (empty($attribute)) {
throw new MethodNotAllowedException('Invalid attribute');
}
if (!$this->Attribute->isImage($attribute['Attribute'])) {
throw new NotFoundException("Attribute is not an image.");
}
if ($thumbnail) {
$extension = $thumbnail === 'webp' ? 'webp' : 'png';
$maxWidth = $this->request->params['named']['width'] ?? null;
$maxHeight = $this->request->params['named']['height'] ?? null;
$imageData = $this->Attribute->getThumbnail($attribute, $extension, $maxWidth, $maxHeight);
} else {
$imageData = $this->Attribute->getPictureData($attribute);
$extension = strtolower(pathinfo($attribute['Attribute']['value'], PATHINFO_EXTENSION));
}
if ($imageData instanceof File) {
return $this->RestResponse->sendFile($imageData, $extension);
}
$this->response->body($imageData);
$this->response->type($extension);
return $this->response;
}
public function delete($id, $hard = false)
{
if (isset($this->params['named']['hard'])) {
$hard = $this->params['named']['hard'];
}
if (isset($this->request->data['hard'])) {
$hard = $this->request->data['hard'];
}
$conditions = $this->__idToConditions($id);
if (!$hard) {
$conditions['deleted'] = 0;
}
$attribute = $this->Attribute->find('first', array(
'conditions' => $conditions,
'recursive' => -1,
'fields' => array('id', 'event_id'),
));
if (empty($attribute)) {
throw new NotFoundException('Invalid attribute');
}
$this->set('id', $attribute['Attribute']['id']);
if ($this->request->is('ajax')) {
if ($this->request->is('post')) {
if ($this->Attribute->deleteAttribute($attribute['Attribute']['id'], $this->Auth->user(), $hard)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Attribute deleted.')), 'status'=>200, 'type' => 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Attribute was not deleted.')), 'status'=>200, 'type' => 'json'));
}
} else {
$this->set('hard', $hard);
$this->set('event_id', $attribute['Attribute']['event_id']);
$this->render('ajax/attributeConfirmationForm');
}
} else {
if (!$this->request->is('post') && !$this->request->is('delete')) {
throw new MethodNotAllowedException(__('This function is only accessible via POST requests.'));
}
if ($this->Attribute->deleteAttribute($attribute['Attribute']['id'], $this->Auth->user(), $hard)) {
if ($this->_isRest() || $this->response->type() === 'application/json') {
$this->set('message', 'Attribute deleted.');
$this->set('_serialize', array('message'));
} else {
$this->Flash->success(__('Attribute deleted'));
$this->redirect($this->referer());
}
} else {
if ($this->_isRest() || $this->response->type() === 'application/json') {
throw new Exception(__('Attribute was not deleted'));
} else {
$this->Flash->error(__('Attribute was not deleted'));
$this->redirect(array('action' => 'index'));
}
$this->Flash->success(__('Attribute deleted'));
}
}
}
public function restore($id = null)
{
$attribute = $this->Attribute->find('first', array(
'conditions' => array('Attribute.id' => $id),
'recursive' => -1,
'fields' => array('Attribute.id', 'Attribute.event_id'),
'contain' => array(
'Event' => array(
'fields' => array('Event.orgc_id')
)
)
));
if (empty($attribute) || !$this->__canModifyEvent($attribute)) {
if ($this->request->is('ajax')) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Attribute')), 'type' => 'json', 'status'=>200));
} else {
throw new MethodNotAllowedException(__('Invalid Attribute'));
}
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $attribute['Attribute']['event_id']);
}
if ($this->request->is('ajax')) {
if ($this->request->is('post')) {
$result = $this->Attribute->restore($id, $this->Auth->user());
if ($result === true) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Attribute restored.')), 'type' => 'json' ,'status'=>200));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $result)), 'type' => 'json', 'status'=>200));
}
} else {
$this->set('id', $id);
$this->set('event_id', $attribute['Attribute']['event_id']);
$this->render('ajax/attributeRestorationForm');
}
} else {
if (!$this->request->is('post') && !$this->_isRest()) {
throw new MethodNotAllowedException();
}
if ($this->Attribute->restore($id, $this->Auth->user())) {
$this->redirect(array('action' => 'view', $id));
} else {
throw new NotFoundException(__('Could not restore the attribute'));
}
}
}
public function deleteSelected($eventId = false, $hard = false)
{
if ($this->request->is('get')) {
return $this->RestResponse->describe('Attributes', 'deleteSelected', false, $this->response->type());
} else if (!$this->request->is('post')) {
throw new MethodNotAllowedException(__('This function is only accessible via POST requests.'));
}
// get a json object with a list of attribute IDs to be deleted
// check each of them and return a json object with the successful deletes and the failed ones.
if ($this->_isRest()) {
if (empty($this->request->data['Attribute'])) {
$this->request->data['Attribute'] = $this->request->data;
}
if (isset($this->request->data['Attribute']['id'])) {
$ids = $this->request->data['Attribute']['id'];
} else {
$ids = $this->request->data['Attribute'];
}
if (empty($eventId) && isset($this->request->data['Attribute']['event_id']) && is_numeric($this->request->data['Attribute']['event_id'])) {
$eventId = $this->request->data['Attribute']['event_id'];
}
} else {
$ids = $this->_jsonDecode($this->request->data['Attribute']['ids_delete']);
}
if (empty($ids)) {
throw new NotFoundException(__('No matching attributes found.'));
}
if (empty($eventId)) {
throw new MethodNotAllowedException(__('No event ID set.'));
}
if (!$this->_isSiteAdmin()) {
$event = $this->Attribute->Event->find('first', [
'conditions' => ['id' => $eventId],
'recursive' => -1,
'fields' => ['id', 'orgc_id', 'user_id'],
]);
if (!$event) {
throw new NotFoundException(__('Invalid event'));
}
if (!$this->__canModifyEvent($event)) {
throw new ForbiddenException(__('You do not have permission to do that.'));
}
}
$conditions = ['id' => $ids, 'event_id' => $eventId];
if ($ids === 'all') {
unset($conditions['id']);
}
if ($hard || ($this->_isRest() && empty($this->request->data['Attribute']['allow_hard_delete']))) {
$conditions['deleted'] = 0;
}
// find all attributes from the ID list that also match the provided event ID.
$attributes = $this->Attribute->find('list', [
'conditions' => $conditions,
'fields' => ['id', 'deleted'],
]);
if (empty($attributes)) {
throw new NotFoundException(__('No matching attributes found.'));
}
if ($ids === 'all') {
$ids = array_keys($attributes);
}
$user = $this->_closeSession();
$successes = [];
foreach ($attributes as $attributeId => $deleted) {
if ($this->Attribute->deleteAttribute($attributeId, $user, $hard || $deleted == 1)) {
$successes[] = $attributeId;
}
}
$fails = array_diff($ids, $successes);
if (empty($fails) && count($successes) > 0) {
$message = __n('%s attribute deleted.', '%s attributes deleted', count($successes), count($successes));
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('Attributes', 'deleteSelected', $eventId, false, $message);
}
return $this->RestResponse->viewData(['saved' => true, 'success' => $message], 'json');
} else {
$message = count($successes) . ' attribute' . (count($successes) != 1 ? 's' : '') . ' deleted, but ' . count($fails) . ' attribute' . (count($fails) != 1 ? 's' : '') . ' could not be deleted.';
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Attributes', 'deleteSelected', false, $message);
}
return $this->RestResponse->viewData(['saved' => false, 'errors' => $message], 'json');
}
}
public function getMassEditForm($eventId)
{
if (!$this->request->is('ajax') || !$this->request->is('post')) {
throw new MethodNotAllowedException(__('This method can only be accessed via AJAX and POST.'));
}
if (!isset($eventId)) {
throw new MethodNotAllowedException(__('No event ID provided.'));
}
$event = $this->Attribute->Event->fetchSimpleEvent($this->Auth->user(), $eventId, array(
'fields' => array('id', 'orgc_id', 'org_id', 'user_id', 'published', 'timestamp')
));
if (empty($event)) {
throw new NotFoundException(__('Invalid event'));
}
if (!$this->__canModifyEvent($event)) {
throw new ForbiddenException(__('You are not authorized to edit this event.'));
}
$selectedAttributeIds = $this->_jsonDecode($this->request->data['selected_ids']);
if (empty($selectedAttributeIds)) {
throw new MethodNotAllowedException(__('No attributes selected'));
}
$attributes = $this->Attribute->fetchAttributes($this->Auth->user(), [
'conditions' => ['Attribute.id' => $selectedAttributeIds, 'Attribute.event_id' => $event['Event']['id']],
'flatten' => true,
]);
// tags to remove
$tags = $this->Attribute->AttributeTag->getAttributesTags($attributes);
$tagItemsRemove = array();
foreach ($tags as $tag) {
$tagName = $tag['name'];
$tagItemsRemove[] = array(
'name' => $tagName,
'value' => $tag['id'],
'template' => array(
'name' => array(
'name' => $tagName,
'label' => array(
'background' => isset($tag['colour']) ? $tag['colour'] : '#ffffff'
)
),
)
);
}
unset($tags);
// clusters to remove
$clusters = $this->Attribute->AttributeTag->getAttributesClusters($this->Auth->user(), $attributes);
$clusterItemsRemove = array();
foreach ($clusters as $cluster) {
$name = $cluster['value'];
$optionName = $cluster['value'];
$synom = $cluster['synonyms_string'] !== '' ? " ({$cluster['synonyms_string']})" : '';
$optionName .= $synom;
$temp = array(
'name' => $optionName,
'value' => $cluster['id'],
'template' => array(
'name' => $name,
'infoExtra' => $cluster['description']
)
);
if ($cluster['synonyms_string'] !== '') {
$temp['infoContextual'] = __('Synonyms: ') . $cluster['synonyms_string'];
}
$clusterItemsRemove[] = $temp;
}
// clusters to add
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
$clusters = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array(
'fields' => array('value', 'id'),
'conditions' => array('published' => true)
));
$clusterItemsAdd = array();
foreach ($clusters as $cluster) {
$clusterItemsAdd[] = array(
'name' => $cluster['GalaxyCluster']['value'],
'value' => $cluster['GalaxyCluster']['id']
);
}
$tags = $this->Attribute->AttributeTag->Tag->fetchUsableTags($this->Auth->user(), false);
$tagItemsAdd = array();
foreach ($tags as $tag) {
$tagName = $tag['Tag']['name'];
$tagItemsAdd[] = array(
'name' => $tagName,
'value' => $tag['Tag']['id'],
'template' => array(
'name' => array(
'name' => $tagName,
'label' => array(
'background' => isset($tag['Tag']['colour']) ? $tag['Tag']['colour'] : '#ffffff'
)
),
)
);
}
$this->layout = false;
$this->set('id', $eventId);
$this->set('selectedAttributeIds', $selectedAttributeIds);
$this->set('sgs', $this->Attribute->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', true));
$this->set('distributionLevels', $this->Attribute->distributionLevels);
$this->set('distributionDescriptions', $this->Attribute->distributionDescriptions);
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('tagItemsRemove', $tagItemsRemove);
$this->set('tagItemsAdd', $tagItemsAdd);
$this->set('clusterItemsAdd', $clusterItemsAdd);
$this->set('clusterItemsRemove', $clusterItemsRemove);
$this->set('options', array( // set chosen (select picker) options
'multiple' => -1,
'autofocus' => false,
'disabledSubmitButton' => true,
'flag_redraw_chosen' => true,
'select_options' => array(
'additionalData' => array(
'event_id' => $eventId,
),
),
));
$this->render('ajax/attributeEditMassForm');
}
public function editSelected($eventId)
{
$this->request->allowMethod(['post']);
$event = $this->Attribute->Event->find('first', array(
'conditions' => array('id' => $eventId),
'recursive' => -1,
'fields' => array('id', 'orgc_id', 'org_id', 'user_id', 'published', 'timestamp', 'uuid')
));
if (!$event) {
throw new NotFoundException(__('Invalid event'));
}
if (!$this->__canModifyEvent($event)) {
throw new ForbiddenException(__('You are not authorized to edit this event.'));
}
$requestData = $this->request->data['Attribute'];
$attribute_ids = $this->_jsonDecode($requestData['attribute_ids']);
$attributes = $this->Attribute->find('all', array(
'conditions' => array(
'id' => $attribute_ids,
'event_id' => $eventId,
),
'recursive' => -1,
));
$tags_ids_remove = json_decode($requestData['tags_ids_remove']);
$tags_ids_add = json_decode($requestData['tags_ids_add']);
$clusters_ids_remove = json_decode($requestData['clusters_ids_remove']);
$clusters_ids_add = json_decode($requestData['clusters_ids_add']);
$changeInTagOrCluster = ($tags_ids_remove !== null && count($tags_ids_remove) > 0)
|| ($tags_ids_add === null || count($tags_ids_add) > 0)
|| ($clusters_ids_remove === null || count($clusters_ids_remove) > 0)
|| ($clusters_ids_add === null || count($clusters_ids_add) > 0);
$changeInAttribute = ($requestData['to_ids'] != 2) || ($requestData['distribution'] != 6) || ($requestData['comment'] != null) || ($requestData['disable_correlation'] != 2);
if (!$changeInAttribute && !$changeInTagOrCluster) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true)), 'status' => 200, 'type' => 'json'));
}
if ($requestData['to_ids'] != 2) {
$toIds = $requestData['to_ids'] == 0 ? false : true;
foreach ($attributes as $key => $attribute) {
$attributes[$key]['Attribute']['to_ids'] = $toIds;
}
}
if ($requestData['distribution'] != 6) {
foreach ($attributes as $key => $attribute) {
$attributes[$key]['Attribute']['distribution'] = $requestData['distribution'];
}
if ($requestData['distribution'] == 4) {
$sharingGroupId = $requestData['sharing_group_id'];
if (!$this->__canUseSharingGroup($sharingGroupId)) {
throw new ForbiddenException(__('Invalid Sharing Group or not authorised.'));
}
foreach ($attributes as $key => $attribute) {
$attributes[$key]['Attribute']['sharing_group_id'] = $sharingGroupId;
}
} else {
foreach ($attributes as $key => $attribute) {
$attributes[$key]['Attribute']['sharing_group_id'] = 0;
}
}
}
if ($requestData['comment'] != null) {
foreach ($attributes as $key => $attribute) {
$attributes[$key]['Attribute']['comment'] = $requestData['comment'];
}
}
if ($requestData['disable_correlation'] != 2) {
$disableCorrelation = $requestData['disable_correlation'] === '0' ? false : true;
foreach ($attributes as $key => $attribute) {
$attributes[$key]['Attribute']['disable_correlation'] = $disableCorrelation;
}
}
$timestamp = time();
foreach ($attributes as $key => $attribute) {
$attributes[$key]['Attribute']['timestamp'] = $timestamp;
}
if ($changeInAttribute) {
if ($requestData['is_proposal']) { // create ShadowAttributes instead
$shadowAttributes = array();
foreach ($attributes as $attribute) {
$shadowAttribute['ShadowAttribute'] = $attribute['Attribute'];
unset($shadowAttribute['ShadowAttribute']['id']);
$shadowAttribute['ShadowAttribute']['email'] = $this->Auth->user('email');
$shadowAttribute['ShadowAttribute']['org_id'] = $this->Auth->user('org_id');
$shadowAttribute['ShadowAttribute']['event_uuid'] = $event['Event']['uuid'];
$shadowAttribute['ShadowAttribute']['event_org_id'] = $event['Event']['org_id'];
$shadowAttribute['ShadowAttribute']['old_id'] = $attribute['Attribute']['id'];
$shadowAttributes[] = $shadowAttribute;
}
$saveSuccess = $this->Attribute->Event->ShadowAttribute->saveMany($shadowAttributes);
} else {
$saveSuccess = $this->Attribute->saveMany($attributes);
}
if ($saveSuccess) {
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $event['Event']['id']);
}
$this->Attribute->Event->unpublishEvent($event, false, $timestamp);
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'validationErrors' => $this->Attribute->validationErrors)), 'status' => 200, 'type' => 'json'));
}
}
// apply changes in tag/cluster
foreach ($attributes as $attribute) {
foreach ($tags_ids_remove as $tag_id) {
$this->removeTag($attribute['Attribute']['id'], $tag_id);
}
foreach ($tags_ids_add as $tag_id) {
$this->addTag($attribute['Attribute']['id'], $tag_id);
}
$this->Galaxy = ClassRegistry::init('Galaxy');
foreach ($clusters_ids_remove as $cluster_id) {
$this->Galaxy->detachCluster($this->Auth->user(), 'attribute', $attribute['Attribute']['id'], $cluster_id);
}
foreach ($clusters_ids_add as $cluster_id) {
$this->Galaxy->attachCluster($this->Auth->user(), 'attribute', $attribute['Attribute']['id'], $cluster_id);
}
}
return new CakeResponse(array('body'=> json_encode(array('saved' => true)), 'status' => 200, 'type' => 'json'));
}
private function __getSearchFilters(&$exception)
{
if (isset($this->request->data['Attribute'])) {
$this->request->data = $this->request->data['Attribute'];
}
$checkForEmpty = array('value', 'tags', 'uuid', 'org', 'type', 'category', 'first_seen', 'last_seen');
foreach ($checkForEmpty as $field) {
if (empty($this->request->data[$field]) || $this->request->data[$field] === 'ALL') {
unset($this->request->data[$field]);
}
}
if (empty($this->request->data['to_ids'])) {
unset($this->request->data['to_ids']);
$this->request->data['ignore'] = 1;
}
$paramArray = array('value' , 'type', 'category', 'org', 'tags', 'from', 'to', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'event_timestamp', 'threat_level_id', 'includeEventTags', 'first_seen', 'last_seen');
$filterData = array(
'request' => $this->request,
'named_params' => $this->request->params['named'],
'paramArray' => $paramArray,
'additional_delimiters' => PHP_EOL
);
$exception = false;
$filters = $this->_harvestParameters($filterData, $exception);
if (!empty($filters['uuid'])) {
if (!is_array($filters['uuid'])) {
$filters['uuid'] = array($filters['uuid']);
}
$uuid = array();
$ids = array();
foreach ($filters['uuid'] as $k => $filter) {
if ($filter[0] === '!') {
$filter = substr($filter, 1);
}
if (Validation::uuid($filter)) {
$uuid[] = $filters['uuid'][$k];
} else {
$ids[] = $filters['uuid'][$k];
}
}
if (empty($uuid)) {
unset($filters['uuid']);
} else {
$filters['uuid'] = $uuid;
}
if (!empty($ids)) {
$filters['eventid'] = $ids;
}
}
return $filters;
}
public function search($continue = false)
{
$user = $this->Auth->user();
$exception = null;
$filters = $this->__getSearchFilters($exception);
if ($this->request->is('post') || !empty($this->request->params['named']['tags'])) {
if ($filters === false) {
return $exception;
}
$this->Session->write('search_attributes_filters', json_encode($filters));
} elseif ($continue === 'results') {
$filters = $this->Session->read('search_attributes_filters');
$filters = empty($filters) ? [] : $this->_jsonDecode($filters);
} else {
$types = $this->_arrayToValuesIndexArray(array_keys($this->Attribute->typeDefinitions));
ksort($types);
$this->set('types', array_merge(['ALL' => 'ALL'], $types));
// combobox for categories
$categories = array_merge(['ALL' => 'ALL'], $this->_arrayToValuesIndexArray(array_keys($this->Attribute->categoryDefinitions)));
$this->set('categories', $categories);
$categoryDefinition = $this->Attribute->categoryDefinitions;
$categoryDefinition = array_merge(["ALL" => ['types' => array_keys($this->Attribute->typeDefinitions), 'formdesc' => '']], $categoryDefinition);
foreach ($categoryDefinition as &$def) {
$def['types'] = array_merge(['ALL'], $def['types']);
}
$this->set('categoryDefinitions', $categoryDefinition);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('fieldDesc', $this->__fieldDesc());
$this->Session->write('search_attributes_filters', null);
}
if (!empty($filters)) {
$filters['includeCorrelations'] = 1;
$params = $this->Attribute->restSearch($user, 'json', $filters, true);
if (!isset($params['conditions']['Attribute.deleted'])) {
$params['conditions']['Attribute.deleted'] = 0;
}
// Force index for performance reasons see #3321
if (isset($filters['value'])) {
$this->paginate['forceIndexHint'] = '(value1, value2)';
}
$this->paginate['conditions'] = $params['conditions'];
$attributes = $this->paginate();
$this->Attribute->attachTagsToAttributes($attributes, ['includeAllTags' => true]);
$orgTable = $this->Attribute->Event->Orgc->find('all', [
'fields' => ['Orgc.id', 'Orgc.name', 'Orgc.uuid'],
]);
$orgTable = array_column(array_column($orgTable, 'Orgc'), null, 'id');
$sgids = $this->Attribute->SharingGroup->authorizedIds($user);
foreach ($attributes as &$attribute) {
if (isset($orgTable[$attribute['Event']['orgc_id']])) {
$attribute['Event']['Orgc'] = $orgTable[$attribute['Event']['orgc_id']];
}
if (isset($orgTable[$attribute['Event']['org_id']])) {
$attribute['Event']['Org'] = $orgTable[$attribute['Event']['org_id']];
}
if (isset($filters['includeCorrelations'])) {
$temp = $this->Attribute->Correlation->getRelatedAttributes(
$user,
$sgids,
$attribute['Attribute'],
[],
true
);
foreach ($temp as &$t) {
$t['info'] = $t['Event']['info'];
$t['org_id'] = $t['Event']['org_id'];
$t['date'] = $t['Event']['date'];
}
$attribute['Event']['RelatedAttribute'][$attribute['Attribute']['id']] = $temp;
}
}
if ($this->_isRest()) {
return $this->RestResponse->viewData($attributes, $this->response->type());
}
list($attributes, $sightingsData) = $this->__searchUI($attributes, $user);
$this->set('sightingsData', $sightingsData);
if (isset($filters['tags']) && !empty($filters['tags'])) {
// if the tag is passed by ID - show its name in the view
$this->loadModel('Tag');
if (!is_array($filters['tags'])) {
$filters['tags'] = array($filters['tags']);
}
foreach ($filters['tags'] as &$v) {
if (!is_numeric($v))
continue;
$tag = $this->Tag->find('first', [
'conditions' => ['Tag.id' => $v],
'fields' => ['name'],
'recursive' => -1
]);
if (!empty($tag)) {
$v = $tag['Tag']['name'];
}
}
}
$this->set('orgTable', array_column($orgTable, 'name', 'id'));
$this->set('filters', $filters);
$this->set('attributes', $attributes);
$this->set('isSearch', 1);
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('shortDist', $this->Attribute->shortDist);
$this->set('distributionLevels', $this->Attribute->distributionLevels);
$this->render('index');
}
if (isset($attributeTags)) {
$this->set('attributeTags', $attributeTags);
}
}
/**
* @param array $attributes
* @param array $user
* @return array|array[]
*/
private function __searchUI(array $attributes, array $user)
{
if (empty($attributes)) {
return [[], []];
}
$this->Feed = ClassRegistry::init('Feed');
$this->loadModel('Sighting');
$this->loadModel('AttachmentScan');
$galaxyTags = [];
foreach ($attributes as &$attribute) {
if ($this->Attribute->isImage($attribute['Attribute'])) {
if (extension_loaded('gd')) {
// if extension is loaded, the data is not passed to the view because it is asynchronously fetched
$attribute['Attribute']['image'] = true; // tell the view that it is an image despite not having the actual data
} else {
$attribute['Attribute']['image'] = $this->Attribute->base64EncodeAttachment($attribute['Attribute']);
}
}
if ($attribute['Attribute']['type'] === 'attachment' && $this->AttachmentScan->isEnabled()) {
$infected = $this->AttachmentScan->isInfected(AttachmentScan::TYPE_ATTRIBUTE, $attribute['Attribute']['id']);
$attribute['Attribute']['infected'] = $infected;
}
if ($attribute['Attribute']['distribution'] == 4) {
$attribute['Attribute']['SharingGroup'] = $attribute['SharingGroup'];
}
$attribute['Attribute']['AttributeTag'] = $attribute['AttributeTag'];
foreach ($attribute['Attribute']['AttributeTag'] as $at) {
if ($at['Tag']['is_galaxy']) {
$galaxyTags[$at['Tag']['id']] = $at['Tag']['name'];
}
}
unset($attribute['AttributeTag']);
}
unset($attribute);
// Fetch galaxy clusters in one query
if (!empty($galaxyTags)) {
$this->loadModel('GalaxyCluster');
$clusters = $this->GalaxyCluster->getClustersByTags($galaxyTags, $user, true, false);
$clusters = array_column(array_column($clusters, 'GalaxyCluster'), null, 'tag_id');
} else {
$clusters = [];
}
// `attachFeedCorrelations` method expects different attribute format, so we need to transform that, then process
// and then take information back to original attribute structure.
$fakeEventArray = [];
$attributesWithFeedCorrelations = $this->Feed->attachFeedCorrelations(array_column($attributes, 'Attribute'), $user, $fakeEventArray);
foreach ($attributes as $k => $attribute) {
// Assign galaxies
$galaxies = [];
foreach ($attribute['Attribute']['AttributeTag'] as $k2 => $attributeTag) {
if (!isset($clusters[$attributeTag['Tag']['id']])) {
continue;
}
$cluster = $clusters[$attributeTag['Tag']['id']];
$galaxyId = $cluster['Galaxy']['id'];
$cluster['local'] = $attributeTag['local'] ?? false;
$cluster['attribute_tag_id'] = $attributeTag['id'];
if (isset($attribute['Attribute']['Galaxy'][$galaxyId])) {
unset($cluster['Galaxy']);
$galaxies[$galaxyId]['GalaxyCluster'][] = $cluster;
} else {
$galaxies[$galaxyId] = $cluster['Galaxy'];
unset($cluster['Galaxy']);
$galaxies[$galaxyId]['GalaxyCluster'] = [$cluster];
}
unset($attributes[$k]['Attribute']['AttributeTag'][$k2]); // remove galaxy tag
}
$attributes[$k]['Attribute']['Galaxy'] = array_values($galaxies);
if (isset($attributesWithFeedCorrelations[$k]['Feed'])) {
$attributes[$k]['Attribute']['Feed'] = $attributesWithFeedCorrelations[$k]['Feed'];
}
}
$sightingsData = $this->Sighting->attributesStatistics($attributes, $user);
return [$attributes, $sightingsData];
}
public function checkComposites()
{
if (!self::_isAdmin()) {
throw new NotFoundException();
}
$this->set('fails', $this->Attribute->checkComposites());
}
public function downloadAttachment($key='download', $id)
{
if ($key != null && $key != 'download') {
$user = $this->_checkAuthUser($key);
} else {
if (!$this->Auth->user()) {
throw new UnauthorizedException(__('You are not authorized. Please send the Authorization header with your auth key along with an Accept header for application/xml.'));
}
$user = $this->Auth->user();
}
// if the user is authorised to use the api key then user will be populated with the user's account
// in addition we also set a flag indicating whether the user is a site admin or not.
if (!$user) {
throw new UnauthorizedException(__('This authentication key is not authorized to be used for exports. Contact your administrator.'));
}
$conditions = $this->__idToConditions($id);
$conditions['Attribute.type'] = array('attachment', 'malware-sample');
$attributes = $this->Attribute->fetchAttributes($user, array('conditions' => $conditions, 'flatten' => true));
if (empty($attributes)) {
throw new UnauthorizedException(__('Attribute does not exist or you do not have the permission to download this attribute.'));
}
return $this->__downloadAttachment($attributes[0]['Attribute']);
}
// returns an XML with attributes that belong to an event. The type of attributes to be returned can be restricted by type using the 3rd parameter.
// Similar to the restSearch, this parameter can be chained with '&&' and negations are accepted too. For example filename&&!filename|md5 would return all filenames that don't have an md5
// The usage of returnAttributes is the following: [MISP-url]/attributes/returnAttributes/<API-key>/<event_id>/<type>/<signature flag>
// The signature flag is off by default, enabling it will only return attributes that have the to_ids flag set to true.
public function returnAttributes()
{
//$key='download', $id, $type = null, $sigOnly = false
$this->_legacyAPIRemap(array(
'paramArray' => array(
'key', 'id', 'type', 'sigOnly'
),
'request' => $this->request,
'named_params' => $this->params['named'],
'ordered_url_params' => func_get_args(),
'injectedParams' => array(
'returnFormat' => 'xml'
),
'alias' => array(
'id' => 'eventid'
)
));
if (!empty($this->_legacyParams['sigOnly'])) {
$this->_legacyParams['to_ids'] = 1;
} else {
$this->_legacyParams['to_ids'] = [0,1];
}
if (!empty($this->_legacyParams['type']) && $this->_legacyParams['type'] === 'all') {
unset($this->_legacyParams['type']);
}
if (!empty($this->_legacyParams['type']) && $this->_legacyParams['type'] === 'all') {
unset($this->_legacyParams['type']);
}
if ($this->response->type() === 'application/json') {
$this->_legacyParams['returnFormat'] = 'json';
}
return $this->restSearch();
}
public function text()
{
$this->_legacyAPIRemap(array(
'paramArray' => array(
'key', 'type', 'tags', 'eventId', 'allowNonIDS', 'from', 'to', 'last', 'enforceWarninglist', 'allowNotPublished'
),
'request' => $this->request,
'named_params' => $this->params['named'],
'ordered_url_params' => func_get_args(),
'injectedParams' => array(
'returnFormat' => 'text'
),
'alias' => array(
'eventId' => 'eventid'
)
));
if (!empty($this->_legacyParams['allowNonIDS'])) {
$this->_legacyParams['to_ids'] = [0,1];
}
if (!empty($this->_legacyParams['allowNotPublished'])) {
$this->_legacyParams['published'] = [0,1];
}
if (!empty($this->_legacyParams['type']) && $this->_legacyParams['type'] === 'all') {
unset($this->_legacyParams['type']);
}
return $this->restSearch();
}
public function rpz()
{
$this->_legacyAPIRemap(array(
'paramArray' => array(
'key', 'tags', 'eventid', 'from', 'to', 'policy', 'walled_garden', 'ns',
'email', 'serial', 'refresh', 'retry', 'expiry', 'minimum_ttl', 'ttl',
'enforceWarninglist', 'ns_alt'
),
'request' => $this->request,
'named_params' => $this->params['named'],
'ordered_url_params' => func_get_args(),
'injectedParams' => array(
'returnFormat' => 'rpz'
)
));
return $this->restSearch();
}
public function bro($key = 'download', $type = 'all', $tags = false, $eventId = false, $from = false, $to = false, $last = false, $enforceWarninglist = false)
{
if ($this->request->is('post')) {
if ($this->request->input('json_decode', true)) {
$data = $this->request->input('json_decode', true);
} else {
$data = $this->request->data;
}
if (!empty($data) && !isset($data['request'])) {
$data = array('request' => $data);
}
$paramArray = array('type', 'tags', 'eventId', 'from', 'to', 'last', 'enforceWarninglist');
foreach ($paramArray as $p) {
if (isset($data['request'][$p])) {
${$p} = $data['request'][$p];
}
}
}
$simpleFalse = array('type', 'tags', 'eventId', 'from', 'to', 'last', 'enforceWarninglist');
foreach ($simpleFalse as $sF) {
if (!is_array(${$sF}) && (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false')) {
${$sF} = false;
}
}
if ($type === 'null' || $type === '0' || $type === 'false') {
$type = 'all';
}
if ($from) {
$from = $this->Attribute->Event->dateFieldCheck($from);
}
if ($to) {
$to = $this->Attribute->Event->dateFieldCheck($to);
}
if ($last) {
$last = $this->Attribute->Event->resolveTimeDelta($last);
}
if ($key != 'download') {
// check if the key is valid -> search for users based on key
$user = $this->_checkAuthUser($key);
if (!$user) {
throw new UnauthorizedException(__('This authentication key is not authorized to be used for exports. Contact your administrator.'));
}
} else {
if (!$this->Auth->user('id')) {
throw new UnauthorizedException(__('You have to be logged in to do that.'));
}
}
$filename = 'misp.' . $type . '.intel';
if ($eventId) {
$filename = 'misp.' . $type . '.event_' . $eventId . '.intel';
}
$responseFile = implode(PHP_EOL, $this->Attribute->bro($this->Auth->user(), $type, $tags, $eventId, $from, $to, $last, $enforceWarninglist)) . PHP_EOL;
$this->response->body($responseFile);
$this->response->type('txt');
$this->response->download($filename);
return $this->response;
}
public function reportValidationIssuesAttributes($eventId = false)
{
// search for validation problems in the attributes
$this->set('result', $this->Attribute->reportValidationIssuesAttributes($eventId));
}
public function generateCorrelation()
{
if ($this->request->is('post')) {
if (!Configure::read('MISP.background_jobs')) {
$k = $this->Attribute->Correlation->generateCorrelation();
$message = __('All done. %s attributes processed.', $k);
if ($this->_isRest()) {
return $this->RestResponse->successResponse(0, $message);
}
$this->Flash->success($message);
$this->redirect(array('controller' => 'pages', 'action' => 'display', 'administration'));
} else {
/** @var Job $job */
$job = ClassRegistry::init('Job');
$jobId = $job->createJob(
'SYSTEM',
Job::WORKER_DEFAULT,
'generate correlation',
'All attributes',
'Job created.'
);
$this->Attribute->getBackgroundJobsTool()->enqueue(
BackgroundJobsTool::DEFAULT_QUEUE,
BackgroundJobsTool::CMD_ADMIN,
[
'jobGenerateCorrelation',
$jobId
],
true,
$jobId
);
$this->Flash->success(__('Job queued. You can view the progress if you navigate to the active jobs view (Administration -> Jobs).'));
$this->redirect(Router::url($this->referer(), true));
}
} else {
$this->render('ajax/recorrelationConfirmation');
}
}
public function fetchViewValue($id, $field = null)
{
$user = $this->_closeSession();
$validFields = ['value', 'comment', 'type', 'category', 'to_ids', 'distribution', 'timestamp', 'first_seen', 'last_seen'];
if (!isset($field) || !in_array($field, $validFields, true)) {
throw new MethodNotAllowedException(__('Invalid field requested.'));
}
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException(__('This function can only be accessed via AJAX.'));
}
$fieldsToFetch = ['id', $field];
if ($field === 'value') {
$fieldsToFetch[] = 'to_ids'; // for warninglist
$fieldsToFetch[] = 'type'; // for view
$fieldsToFetch[] = 'category'; // for view
}
$params = array(
'conditions' => array('Attribute.id' => $id),
'fields' => $fieldsToFetch,
'contain' => ['Event'],
'flatten' => 1,
);
$attribute = $this->Attribute->fetchAttributes($user, $params);
if (empty($attribute)) {
throw new NotFoundException(__('Invalid attribute'));
}
$attribute = $attribute[0];
$result = $attribute['Attribute'][$field];
if ($field === 'distribution') {
$result = $this->Attribute->shortDist[$result];
} elseif ($field === 'to_ids') {
$result = $result == 0 ? 'No' : 'Yes';
} elseif ($field === 'value') {
$this->loadModel('Warninglist');
$attribute['Attribute'] = $this->Warninglist->checkForWarning($attribute['Attribute']);
}
$this->set('value', $result);
$this->set('object', $attribute);
$this->set('field', $field);
$this->layout = false;
$this->render('ajax/attributeViewFieldForm');
}
public function fetchEditForm($id, $field = null)
{
$validFields = array('value', 'comment', 'type', 'category', 'to_ids', 'distribution', 'first_seen', 'last_seen');
if (!isset($field) || !in_array($field, $validFields)) {
throw new MethodNotAllowedException(__('Invalid field requested.'));
}
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException(__('This function can only be accessed via AJAX.'));
}
$fields = array('id', 'distribution', 'event_id');
if ($field == 'category' || $field == 'type') {
$fields[] = 'type';
$fields[] = 'category';
} else {
$fields[] = $field;
}
$params = array(
'conditions' => array('Attribute.id' => $id),
'fields' => $fields,
'flatten' => 1,
'contain' => array(
'Event' => array(
'fields' => array('distribution', 'id', 'user_id', 'orgc_id'),
)
)
);
$attribute = $this->Attribute->fetchAttributes($this->Auth->user(), $params);
if (empty($attribute)) {
throw new NotFoundException(__('Invalid attribute'));
}
$attribute = $attribute[0];
if (!$this->__canModifyEvent($attribute)) {
throw new ForbiddenException(__('You do not have permission to do that'));
}
$this->layout = false;
if ($field === 'distribution') {
$distributionLevels = $this->Attribute->shortDist;
unset($distributionLevels[4]);
$this->set('distributionLevels', $distributionLevels);
} elseif ($field === 'category') {
$typeCategory = array();
foreach ($this->Attribute->categoryDefinitions as $k => $category) {
foreach ($category['types'] as $type) {
$typeCategory[$type][] = $k;
}
}
$this->set('typeCategory', $typeCategory);
} elseif ($field === 'type') {
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
}
$this->set('object', $attribute['Attribute']);
$fieldURL = ucfirst($field);
$this->render('ajax/attributeEdit' . $fieldURL . 'Form');
}
public function attributeReplace($id)
{
$event = $this->Attribute->Event->find('first', array(
'conditions' => array('Event.id' => $id),
'fields' => array('id', 'orgc_id', 'distribution', 'user_id'),
'recursive' => -1
));
if (empty($event) || !$this->__canModifyEvent($event)) {
throw new MethodNotAllowedException(__('Event not found or you don\'t have permissions to create attributes'));
}
$this->set('event_id', $id);
if ($this->request->is('get')) {
$this->layout = false;
$this->request->data['Attribute']['event_id'] = $id;
// combobox for types
$types = array_keys($this->Attribute->typeDefinitions);
$types = $this->_arrayToValuesIndexArray($types);
$this->set('types', $types);
// combobox for categories
$categories = array_keys($this->Attribute->categoryDefinitions);
$categories = $this->_arrayToValuesIndexArray($categories);
$this->set('categories', $categories);
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
} elseif ($this->request->is('post')) {
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException(__('This action can only be accessed via AJAX.'));
}
$newValues = explode(PHP_EOL, $this->request->data['Attribute']['value']);
$category = $this->request->data['Attribute']['category'];
$type = $this->request->data['Attribute']['type'];
$to_ids = $this->request->data['Attribute']['to_ids'];
$oldAttributes = $this->Attribute->find('all', array(
'conditions' => array(
'event_id' => $id,
'category' => $category,
'type' => $type,
),
'fields' => array('id', 'event_id', 'category', 'type', 'value'),
'recursive' => -1,
));
$results = array('untouched' => count($oldAttributes), 'created' => 0, 'deleted' => 0, 'createdFail' => 0, 'deletedFail' => 0);
$newValues = array_map('trim', $newValues);
foreach ($newValues as $value) {
$found = false;
foreach ($oldAttributes as $old) {
if ($value == $old['Attribute']['value']) {
$found = true;
}
}
if (!$found) {
$attribute = array(
'value' => $value,
'event_id' => $id,
'category' => $category,
'type' => $type,
'distribution' => $event['Event']['distribution'],
'to_ids' => $to_ids,
);
$this->Attribute->create();
if ($this->Attribute->save(array('Attribute' => $attribute))) {
$results['created']++;
} else {
$results['createdFail']++;
}
}
}
foreach ($oldAttributes as $old) {
if (!in_array($old['Attribute']['value'], $newValues)) {
if ($this->Attribute->delete($old['Attribute']['id'])) {
$results['deleted']++;
$results['untouched']--;
} else {
$results['deletedFail']++;
}
}
}
$message = '';
$success = true;
if (($results['created'] > 0 || $results['deleted'] > 0) && $results['createdFail'] == 0 && $results['deletedFail'] == 0) {
$message .= 'Update completed without any issues.';
$this->Attribute->Event->unpublishEvent($id);
} else {
$message .= 'Update completed with some errors.';
$success = false;
}
if ($results['created']) {
$message .= $results['created'] . ' attribute' . $this->__checkCountForOne($results['created']) . ' created. ';
}
if ($results['createdFail']) {
$message .= $results['createdFail'] . ' attribute' . $this->__checkCountForOne($results['createdFail']) . ' could not be created. ';
}
if ($results['deleted']) {
$message .= $results['deleted'] . ' attribute' . $this->__checkCountForOne($results['deleted']) . ' deleted.';
}
if ($results['deletedFail']) {
$message .= $results['deletedFail'] . ' attribute' . $this->__checkCountForOne($results['deletedFail']) . ' could not be deleted. ';
}
$message .= $results['untouched'] . ' attributes left untouched. ';
$this->autoRender = false;
$this->layout = false;
if ($success) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $message)), 'status'=>200, 'type' => 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => $message)), 'status'=>200, 'type' => 'json'));
}
}
}
private function __checkCountForOne($number)
{
if ($number != 1) {
return 's';
}
return '';
}
// download a sample by passing along an md5
public function downloadSample($hash=false, $allSamples=false, $eventID=false)
{
if (!$this->userRole['perm_auth']) {
throw new MethodNotAllowedException(__('This functionality requires API key access.'));
}
$error = false;
if ($this->response->type() === 'application/json') {
$data = $this->request->input('json_decode', true);
} elseif ($this->response->type() === 'application/xml') {
$data = $this->request->data;
} else {
throw new BadRequestException(__('This action is for the API only. Please refer to the automation page for information on how to use it.'));
}
if (!$hash && isset($data['request']['hash'])) {
$hash = $data['request']['hash'];
}
if (!$allSamples && isset($data['request']['allSamples'])) {
$allSamples = $data['request']['allSamples'];
}
if (!$eventID && isset($data['request']['eventID'])) {
$eventID = $data['request']['eventID'];
}
if (!$eventID && !$hash) {
throw new MethodNotAllowedException(__('No hash or event ID received. You need to set at least one of the two.'));
}
if (!$hash) {
$allSamples = true;
}
$simpleFalse = array('hash', 'allSamples', 'eventID');
foreach ($simpleFalse as $sF) {
if (!is_array(${$sF}) && (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false')) {
${$sF} = false;
}
}
// valid combinations of settings are:
// hash
// eventID + all samples
// hash + eventID
// hash + eventID + all samples
$searchConditions = array();
$types = array();
if ($hash) {
$validTypes = $this->Attribute->resolveHashType($hash);
if ($allSamples) {
if (empty($validTypes)) {
$error = 'Invalid hash format (valid options are ' . implode(', ', array_keys(Attribute::FILE_HASH_TYPES)) . ')';
} else {
foreach ($validTypes as $t) {
if ($t === 'md5') {
$types = array_merge($types, array('malware-sample', 'filename|md5', 'md5'));
} else {
$types = array_merge($types, array('filename|' . $t, $t));
}
}
}
if (empty($error)) {
$event_ids = $this->Attribute->find('column', array(
'recursive' => -1,
'contain' => array('Event'),
'fields' => array('Event.id'),
'conditions' => array(
'OR' => array(
'AND' => array(
'LOWER(Attribute.value1) LIKE' => strtolower($hash),
'Attribute.value2' => '',
),
'LOWER(Attribute.value2) LIKE' => strtolower($hash)
)
),
));
$searchConditions = array(
'AND' => array('Event.id' => $event_ids)
);
if (empty($event_ids)) {
$error = 'No hits with the given parameters.';
}
}
} else {
if (!in_array('md5', $validTypes)) {
$error = 'Only MD5 hashes can be used to fetch malware samples at this point in time.';
}
if (empty($error)) {
$searchConditions = array('AND' => array('LOWER(Attribute.value2) LIKE' => strtolower($hash)));
}
}
}
if (!empty($eventID)) {
$searchConditions['AND'][] = array('Event.id' => $eventID);
}
if (empty($error)) {
$attributes = $this->Attribute->fetchAttributes(
$this->Auth->user(),
array(
'fields' => array('Attribute.event_id', 'Attribute.id', 'Attribute.value1', 'Attribute.value2', 'Event.info'),
'conditions' => array(
'AND' => array(
$searchConditions,
array('Attribute.type' => 'malware-sample')
)
),
'contain' => array('Event'),
'flatten' => 1
)
);
if (empty($attributes)) {
$error = 'No hits with the given parameters.';
}
$results = array();
foreach ($attributes as $attribute) {
$found = false;
foreach ($results as $previous) {
if ($previous['md5'] == $attribute['Attribute']['value2']) {
$found = true;
}
}
if (!$found) {
$results[] = array(
'md5' => $attribute['Attribute']['value2'],
'base64' => $this->Attribute->base64EncodeAttachment($attribute['Attribute']),
'filename' => $attribute['Attribute']['value1'],
'attribute_id' => $attribute['Attribute']['id'],
'event_id' => $attribute['Attribute']['event_id'],
'event_info' => $attribute['Event']['info'],
);
}
}
if ($error) {
$this->set('message', $error);
$this->set('_serialize', array('message'));
} else {
$this->set('result', $results);
$this->set('_serialize', array('result'));
}
} else {
$this->set('message', $error);
$this->set('_serialize', array('message'));
}
}
public function pruneOrphanedAttributes()
{
if (!$this->_isSiteAdmin() || !$this->request->is('post')) {
throw new MethodNotAllowedException(__('You are not authorised to do that.'));
}
$events = array_keys($this->Attribute->Event->find('list'));
$orphans = $this->Attribute->find('list', array('conditions' => array('Attribute.event_id !=' => $events)));
if (count($orphans) > 0) {
$this->Attribute->deleteAll(array('Attribute.event_id !=' => $events), false, true);
}
$this->Flash->success('Removed ' . count($orphans) . ' attribute(s).');
$this->redirect(Router::url($this->referer(), true));
}
public function checkOrphanedAttributes()
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException(__('You are not authorised to do that.'));
}
$this->loadModel('Attribute');
$events = array_keys($this->Attribute->Event->find('list'));
$orphans = $this->Attribute->find('list', array('conditions' => array('Attribute.event_id !=' => $events)));
return new CakeResponse(array('body'=> count($orphans), 'status'=>200, 'type' => 'json'));
}
public function updateAttributeValues($script)
{
if (!$this->_isSiteAdmin() || !$this->request->is('post')) {
throw new MethodNotAllowedException(__('You are not authorised to do that.'));
}
switch ($script) {
case 'urlSanitisation':
$replaceConditions = array(
array('search' => 'UPPER(Attribute.value1) LIKE', 'from' => 'HXXP', 'to' => 'http', 'ci' => true, 'condition' => 'startsWith'),
array('search' => 'Attribute.value1 LIKE', 'from' => '[.]', 'to' => '.', 'ci' => false, 'condition' => 'contains'),
);
break;
default:
throw new Exception(__('Invalid script.'));
}
$counter = 0;
foreach ($replaceConditions as $rC) {
$searchPattern = '';
if (in_array($rC['condition'], array('endsWith', 'contains'))) {
$searchPattern .= '%';
}
$searchPattern .= $rC['from'];
if (in_array($rC['condition'], array('startsWith', 'contains'))) {
$searchPattern .= '%';
}
$attributes = $this->Attribute->find('all', array('conditions' => array($rC['search'] => $searchPattern), 'recursive' => -1));
foreach ($attributes as $attribute) {
$regex = '/';
if (!in_array($rC['condition'], array('startsWith', 'contains'))) {
$regex .= '^';
}
$regex .= $rC['from'];
if (!in_array($rC['condition'], array('endsWith', 'contains'))) {
$regex .= '$';
}
$regex .= '/';
if ($rC['ci']) {
$regex .= 'i';
}
$attribute['Attribute']['value'] = preg_replace($regex, $rC['to'], $attribute['Attribute']['value']);
$this->Attribute->save($attribute);
$counter++;
}
}
$this->Flash->success('Updated ' . $counter . ' attribute(s).');
$this->redirect('/pages/display/administration');
}
public function hoverEnrichment($id, $persistent = false)
{
$attribute = $this->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id), 'flatten' => 1, 'includeEventTags' => 1));
if (empty($attribute)) {
throw new NotFoundException(__('Invalid Attribute'));
}
$this->loadModel('Module');
$modules = $this->Module->getEnabledModules($this->Auth->user());
$validTypes = array();
if (isset($modules['hover_type'][$attribute[0]['Attribute']['type']])) {
$validTypes = $modules['hover_type'][$attribute[0]['Attribute']['type']];
}
$resultArray = array();
foreach ($validTypes as $type) {
$options = array();
$found = false;
foreach ($modules['modules'] as $temp) {
if ($temp['name'] === $type) {
$found = true;
$format = isset($temp['mispattributes']['format']) ? $temp['mispattributes']['format'] : 'simplified';
if (isset($temp['meta']['config'])) {
foreach ($temp['meta']['config'] as $conf) {
$options[$conf] = Configure::read('Plugin.Enrichment_' . $type . '_' . $conf);
}
}
break;
}
}
if (!$found) {
throw new MethodNotAllowedException(__('No valid enrichment options found for this attribute.'));
}
$data = array('module' => $type);
if ($persistent) {
$data['persistent'] = 1;
}
if (!empty($options)) {
$data['config'] = $options;
}
if ($format == 'misp_standard') {
$data['attribute'] = in_array('value', $attribute) ? $attribute : $attribute[0]['Attribute'];
} else {
$data[$attribute[0]['Attribute']['type']] = $attribute[0]['Attribute']['value'];
}
$result = $this->Module->queryModuleServer($data, true, 'Enrichment', false, $attribute[0]);
if ($result) {
if (!is_array($result)) {
$resultArray[$type] = ['error' => $result];
continue;
}
} else {
// TODO: i18n?
$resultArray[$type] = ['error' => 'Enrichment service not reachable.'];
continue;
}
$current_result = array();
if (isset($result['results']['Object'])) {
if (!empty($result['results']['Object'])) {
$objects = array();
foreach ($result['results']['Object'] as $object) {
if (isset($object['Attribute']) && !empty($object['Attribute'])) {
$object_attributes = array();
foreach($object['Attribute'] as $object_attribute) {
$object_attributes[] = [
'object_relation' => $object_attribute['object_relation'],
'value' => $object_attribute['value'],
'type' => $object_attribute['type'],
];
}
$objects[] = array('name' => $object['name'], 'Attribute' => $object_attributes);
}
}
$current_result['Object'] = $objects;
}
unset($result['results']['Object']);
}
if (isset($result['results']['Attribute'])) {
if (!empty($result['results']['Attribute'])) {
$attributes = array();
foreach($result['results']['Attribute'] as $result_attribute) {
$attributes[] = array('type' => $result_attribute['type'], 'value' => $result_attribute['value']);
}
$current_result['Attribute'] = $attributes;
}
unset($result['results']['Attribute']);
}
$resultArray[$type] = $current_result;
if (!empty($result['results'])) {
foreach ($result['results'] as $r) {
if (is_array($r['values']) && !empty($r['values'])) {
$tempArray = array();
foreach ($r['values'] as $k => $v) {
if (is_array($v)) {
$v = 'Array returned';
}
$tempArray[$k] = $v;
}
$resultArray[$type][] = array($type => $tempArray);
} elseif ($r['values'] == null) {
$resultArray[$type][] = array($type => 'No result');
} else {
$resultArray[$type][] = array($type => $r['values']);
}
}
}
}
$this->set('persistent', $persistent);
$this->set('results', $resultArray);
$this->layout = false;
$this->render('ajax/hover_enrichment');
}
public function describeTypes()
{
$result = array();
foreach ($this->Attribute->typeDefinitions as $key => $value) {
$result['sane_defaults'][$key] = array('default_category' => $value['default_category'], 'to_ids' => $value['to_ids']);
}
$result['types'] = array_keys($this->Attribute->typeDefinitions);
$result['categories'] = array_keys($this->Attribute->categoryDefinitions);
foreach ($this->Attribute->categoryDefinitions as $cat => $data) {
$result['category_type_mappings'][$cat] = $data['types'];
}
return $this->RestResponse->viewData(['result' => $result], 'json');
}
public function attributeStatistics($type = 'type', $percentage = false)
{
$validTypes = array('type', 'category');
if (!in_array($type, $validTypes)) {
throw new MethodNotAllowedException(__('Invalid type requested.'));
}
$totalAttributes = $this->Attribute->find('count', array());
$attributes = $this->Attribute->find('all', array(
'recursive' => -1,
'fields' => array($type, 'COUNT(id) as attribute_count'),
'group' => array($type),
'order' => ''
));
$results = array();
foreach ($attributes as $attribute) {
if ($percentage) {
$results[$attribute['Attribute'][$type]] = round(100 * $attribute[0]['attribute_count'] / $totalAttributes, 3) . '%';
} else {
$results[$attribute['Attribute'][$type]] = $attribute[0]['attribute_count'];
}
}
ksort($results);
return $this->RestResponse->viewData($results, 'json');
}
public function addTag($id = false, $tag_id = false)
{
$rearrangeRules = array(
'request' => false,
'Attribute' => false,
'tag_id' => 'tag',
'attribute_id' => 'attribute',
'id' => 'attribute'
);
$RearrangeTool = new RequestRearrangeTool();
$this->request->data = $RearrangeTool->rearrangeArray($this->request->data, $rearrangeRules);
$local = empty($this->request->params['named']['local']) ? 0 : 1;
if (!$this->request->is('post')) {
if ($id === false) {
throw new NotFoundException(__('Invalid attribute'));
}
$this->set('local', $local);
$this->set('object_id', $id);
$this->set('scope', 'Attribute');
$this->layout = false;
$this->autoRender = false;
$this->render('/Events/add_tag');
} else {
if ($id === false) {
if (!isset($this->request->data['attribute'])) {
throw new NotFoundException(__('Invalid attribute'));
}
$id = $this->request->data['attribute'];
}
if ($id === 'selected') {
if (!isset($this->request->data['attribute_ids'])) {
throw new NotFoundException(__('Invalid attribute'));
}
$idList = json_decode($this->request->data['attribute_ids'], true);
}
if ($tag_id === false) {
if (!isset($this->request->data['tag'])) {
throw new NotFoundException(__('Invalid tag'));
}
$tag_id = $this->request->data['tag'];
}
if (!is_numeric($tag_id)) {
if (preg_match('/^collection_[0-9]+$/i', $tag_id)) {
$tagChoice = explode('_', $tag_id)[1];
$this->loadModel('TagCollection');
$tagCollection = $this->TagCollection->fetchTagCollection($this->Auth->user(), array('conditions' => array('TagCollection.id' => $tagChoice)));
if (empty($tagCollection)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag Collection.')), 'status'=>200, 'type' => 'json'));
}
$tag_id_list = array_column($tagCollection[0]['TagCollectionTag'], 'tag_id');
} else {
// try to parse json array
$tag_ids = json_decode($tag_id);
if ($tag_ids !== null) { // can decode json
$tag_id_list = array();
foreach ($tag_ids as $tag_id) {
if (preg_match('/^collection_[0-9]+$/i', $tag_id)) {
$tagChoice = explode('_', $tag_id)[1];
$this->loadModel('TagCollection');
$tagCollection = $this->TagCollection->fetchTagCollection($this->Auth->user(), array('conditions' => array('TagCollection.id' => $tagChoice)));
if (empty($tagCollection)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag Collection.')), 'status'=>200, 'type' => 'json'));
}
$tag_id_list = array_column($tagCollection[0]['TagCollectionTag'], 'tag_id');
} else {
$tag_id_list[] = $tag_id;
}
}
} else {
$tagId = $this->Attribute->AttributeTag->Tag->lookupTagIdForUser($this->Auth->user(), trim($tag_id));
if (empty($tagId)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status'=>200, 'type' => 'json'));
}
$tag_id = $tagId;
}
}
}
if (!isset($idList)) {
$idList = array($id);
}
if (empty($tag_id_list)) {
$tag_id_list = array($tag_id);
}
$conditions = $this->Attribute->AttributeTag->Tag->createConditions($this->Auth->user());
$conditions['Tag.id'] = $tag_id_list;
$tags = $this->Attribute->AttributeTag->Tag->find('list', array(
'conditions' => $conditions,
'fields' => ['Tag.id', 'Tag.name'],
));
$success = 0;
$fails = 0;
$this->Taxonomy = ClassRegistry::init('Taxonomy');
foreach ($idList as $id) {
$attribute = $this->Attribute->fetchAttributeSimple($this->Auth->user(), [
'conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0),
]);
if (empty($attribute)) {
throw new NotFoundException(__('Invalid attribute'));
}
if (!$this->__canModifyTag($attribute, $local)) {
$fails++;
continue;
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $attribute['Event']['id']);
}
$changeTimestamp = false;
foreach ($tag_id_list as $tag_id) {
if (!isset($tags[$tag_id])) {
// Tag not found or user don't have permission to add it.
$fails++;
continue;
}
$tagName = $tags[$tag_id];
$found = $this->Attribute->AttributeTag->hasAny([
'attribute_id' => $id,
'tag_id' => $tag_id,
]);
if ($found) {
// Tag is already assigned to given attribute.
$fails++;
continue;
}
$tagsOnAttribute = $this->Attribute->AttributeTag->find('column', array(
'conditions' => array(
'AttributeTag.attribute_id' => $id,
'AttributeTag.local' => $local,
),
'contain' => 'Tag',
'fields' => array('Tag.name'),
));
$exclusiveTestPassed = $this->Taxonomy->checkIfNewTagIsAllowedByTaxonomy($tagName, $tagsOnAttribute);
if (!$exclusiveTestPassed) {
$fails++;
continue;
}
$this->Attribute->AttributeTag->create();
if ($this->Attribute->AttributeTag->save(array('attribute_id' => $id, 'tag_id' => $tag_id, 'event_id' => $attribute['Event']['id'], 'local' => $local))) {
if (!$local) {
$changeTimestamp = true;
}
$log = ClassRegistry::init('Log');
$log->createLogEntry(
$this->Auth->user(),
'tag',
'Attribute',
$id,
sprintf(
'Attached%s tag (%s) "%s" to attribute (%s)',
$local ? ' local' : '',
$tag_id,
$tagName,
$id
),
sprintf(
'Attribute (%s) tagged as Tag (%s)%s',
$id,
$tag_id,
$local ? ' locally' : ''
)
);
$success++;
} else {
$fails++;
}
}
if ($changeTimestamp) {
$this->Attribute->touch($attribute);
}
}
if ($fails === 0) {
$message = __n('Tag added.', '%s tags added', $success, $success);
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $message, 'check_publish' => true)), 'status' => 200, 'type' => 'json'));
} else {
$message = __n('Tag could not be added.', '%s tags could not be added.', $fails, $fails);
if ($success > 0) {
$message .= __n(' However, %s tag was added.', ' However, %s tags were added.', $success, $success);
}
return new CakeResponse(array('body' => json_encode(array('saved' => false, 'errors' => $message)), 'status' => 200, 'type' => 'json'));
}
}
}
public function removeTag($id = false, $tag_id = false)
{
if (!$this->request->is('post')) {
$attribute = $this->Attribute->fetchAttributeSimple($this->Auth->user(), [
'conditions' => $this->__idToConditions($id)
]);
if (!$attribute) {
throw new NotFoundException(__('Invalid attribute'));
}
$attributeTag = $this->Attribute->AttributeTag->find('first', array(
'conditions' => array(
'attribute_id' => $attribute['Attribute']['id'],
'tag_id' => $tag_id,
),
'contain' => ['Tag'],
'recursive' => -1,
));
if (!$attributeTag) {
throw new NotFoundException(__('Invalid tag.'));
}
$this->set('is_local', $attributeTag['AttributeTag']['local']);
$this->set('tag', $attributeTag);
$this->set('id', $attribute['Attribute']['id']);
$this->set('tag_id', $tag_id);
$this->set('model', 'Attribute');
$this->set('model_name', $attribute['Attribute']['id']);
$this->render('ajax/tagRemoveConfirmation');
} else {
$rearrangeRules = array(
'request' => false,
'Attribute' => false,
'tag_id' => 'tag',
'attribute_id' => 'attribute',
'id' => 'attribute'
);
$RearrangeTool = new RequestRearrangeTool();
$this->request->data = $RearrangeTool->rearrangeArray($this->request->data, $rearrangeRules);
if ($id === false) {
if (!isset($this->request->data['attribute'])) {
throw new NotFoundException(__('Invalid attribute'));
}
$id = $this->request->data['attribute'];
}
if ($tag_id === false) {
if (!isset($this->request->data['tag'])) {
throw new NotFoundException(__('Invalid tag'));
}
$tag_id = $this->request->data['tag'];
}
$attribute = $this->Attribute->find('first', [
'recursive' => -1,
'conditions' => ['Attribute.id' => $id],
'fields' => ['Attribute.deleted', 'Attribute.event_id', 'Attribute.id', 'Attribute.object_id', 'Event.orgc_id', 'Event.user_id'],
'contain' => ['Event'],
]);
if (empty($attribute)) {
throw new NotFoundException(__('Invalid attribute'));
}
if ($attribute['Attribute']['deleted']) {
throw new NotFoundException(__('Invalid attribute'));
}
if (empty($tag_id)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status' => 200, 'type' => 'json'));
}
if (!is_numeric($tag_id)) {
$tag = $this->Attribute->AttributeTag->Tag->find('first', array('recursive' => -1, 'conditions' => array('LOWER(Tag.name) LIKE' => strtolower(trim($tag_id)))));
if (empty($tag)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status' => 200, 'type' => 'json'));
}
$tag_id = $tag['Tag']['id'];
}
if (!is_numeric($id)) {
$id = $this->request->data['Attribute']['id'];
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $attribute['Attribute']['event_id']);
}
$attributeTag = $this->Attribute->AttributeTag->find('first', array(
'conditions' => array(
'attribute_id' => $id,
'tag_id' => $tag_id
),
'recursive' => -1,
));
// org should allow to (un)tag too, so that an event that gets pushed can be (un)tagged locally by the owning org
if (!$this->__canModifyTag($attribute, !empty($attributeTag['AttributeTag']['local']))) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'You do not have permission to do that.')), 'status' => 200, 'type' => 'json'));
}
if (empty($attributeTag)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid attribute - tag combination.')), 'status' => 200, 'type' => 'json'));
}
$tag = $this->Attribute->AttributeTag->Tag->find('first', array(
'conditions' => array('Tag.id' => $tag_id),
'recursive' => -1,
'fields' => array('Tag.name')
));
if ($this->Attribute->AttributeTag->delete($attributeTag['AttributeTag']['id'])) {
if (empty($attributeTag['AttributeTag']['local'])) {
$this->Attribute->touch($attribute);
}
$log = ClassRegistry::init('Log');
$log->createLogEntry($this->Auth->user(), 'tag', 'Attribute', $id, 'Removed tag (' . $tag_id . ') "' . $tag['Tag']['name'] . '" from attribute (' . $id . ')', 'Attribute (' . $id . ') untagged of Tag (' . $tag_id . ')');
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Tag removed.', 'check_publish' => empty($attributeTag['AttributeTag']['local']))), 'status' => 200, 'type'=> 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Tag could not be removed.')), 'status' => 200, 'type' => 'json'));
}
}
}
public function toggleCorrelation($id)
{
if (!$this->_isSiteAdmin() && !Configure::read('MISP.allow_disabling_correlation')) {
throw new MethodNotAllowedException(__('Disabling the correlation is not permitted on this instance.'));
}
$attribute = $this->Attribute->find('first', array(
'conditions' => array('Attribute.id' => $id),
'recursive' => -1,
'contain' => array('Event')
));
if (empty($attribute)) {
throw new NotFoundException(__('Invalid Attribute.'));
}
if (!$this->__canModifyEvent($attribute)) {
throw new ForbiddenException(__('You do not have permission to do that.'));
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $attribute['Event']['id']);
}
if ($this->request->is('post')) {
if ($attribute['Attribute']['disable_correlation']) {
$attribute['Attribute']['disable_correlation'] = 0;
} else {
$attribute['Attribute']['disable_correlation'] = 1;
}
$this->Attribute->save($attribute, ['parentEvent' => $attribute]);
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('attributes', 'toggleCorrelation', $id, false, 'Correlation ' . ($attribute['Attribute']['disable_correlation'] ? 'disabled' : 'enabled') . '.');
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => ('Correlation ' . ($attribute['Attribute']['disable_correlation'] ? 'disabled' : 'enabled')), 'check_publish' => true)), 'status'=>200, 'type' => 'json'));
}
} else {
$this->set('attribute', $attribute);
$this->render('ajax/toggle_correlation');
}
}
public function toggleToIDS($id)
{
return $this->fetchEditForm($id, 'to_ids');
}
public function checkAttachments()
{
$attributes = $this->Attribute->find(
'all',
array(
'conditions' => array('Attribute.type' => array('attachment', 'malware-sample')),
'contain' => ['Event.orgc_id', 'Event.org_id'],
'recursive' => -1
)
);
$counter = 0;
$attachmentTool = new AttachmentTool();
$results = [];
foreach ($attributes as $attribute) {
$exists = $attachmentTool->exists($attribute['Attribute']['event_id'], $attribute['Attribute']['id']);
if (!$exists) {
$results['affectedEvents'][$attribute['Attribute']['event_id']] = $attribute['Attribute']['event_id'];
$results['affectedAttributes'][] = $attribute['Attribute']['id'];
foreach (['orgc', 'org'] as $type) {
if (empty($results['affectedOrgs'][$type][$attribute['Event'][$type . '_id']])) {
$results['affectedOrgs'][$type][$attribute['Event'][$type . '_id']] = 0;
} else {
$results['affectedOrgs'][$type][$attribute['Event'][$type . '_id']] += 1;
}
}
$counter++;
}
}
if (!empty($results)) {
$results['affectedEvents'] = array_values($results['affectedEvents']);
rsort($results['affectedEvents']);
rsort($results['affectedAttributes']);
foreach (['orgc', 'org'] as $type) {
arsort($results['affectedOrgs'][$type]);
}
}
file_put_contents(APP . '/tmp/logs/missing_attachments.log', json_encode($results, JSON_PRETTY_PRINT));
return new CakeResponse(array('body' => $counter, 'status' => 200));
}
public function exportSearch($type = false)
{
$filters = $this->Session->read('search_attributes_filters');
if ($filters === null) {
throw new NotFoundException('No search to export.');
}
if (empty($type)) {
$exports = array_keys($this->Attribute->validFormats);
$this->set('exports', $exports);
$this->render('ajax/exportSearch');
} else {
$filters = $this->_jsonDecode($filters);
$final = $this->Attribute->restSearch($this->Auth->user(), $type, $filters);
$responseType = $this->Attribute->validFormats[$type][0];
return $this->RestResponse->viewData($final, $responseType, false, true, 'search.' . $type . '.' . $responseType);
}
}
/**
* @param int|string $id Attribute ID or UUID
* @return array
* @throws Exception
*/
private function __fetchAttribute($id)
{
$options = array(
'conditions' => $this->__idToConditions($id),
'contain' => array(
'Event',
),
'withAttachments' => $this->_isRest(),
'flatten' => true,
'includeAllTags' => false,
'includeAttributeUuid' => true,
'limit' => 1,
);
$attributes = $this->Attribute->fetchAttributes($this->Auth->user(), $options);
if (!empty($attributes)) {
return $attributes[0];
} else {
return null;
}
}
/**
* @param int|string $id Attribute ID or UUID
* @return array
*/
private function __idToConditions($id)
{
if (is_numeric($id)) {
$conditions = array('Attribute.id' => $id);
} elseif (Validation::uuid($id)) {
$conditions = array('Attribute.uuid' => $id);
} else {
throw new NotFoundException(__('Invalid attribute ID.'));
}
return $conditions;
}
/**
* @param int $sharingGroupId
* @return bool
*/
private function __canUseSharingGroup($sharingGroupId)
{
$sg = $this->Attribute->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', true, $sharingGroupId);
return !empty($sg);
}
}