Merge branch 'develop' into 2.4

pull/9678/head v2.4.189
iglocska 2024-04-05 14:42:18 +02:00
commit 5817075607
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
68 changed files with 4524 additions and 3740 deletions

2
PyMISP

@ -1 +1 @@
Subproject commit 8a2e52ac7ee2318623eb9f817cf999cc9734afb1
Subproject commit 60aa6b9a0fce69507776429fcaaf3e0e3962a36c

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":188}
{"major":2, "minor":4, "hotfix":189}

View File

@ -1576,7 +1576,7 @@ class AttributesController extends AppController
// Force index for performance reasons see #3321
if (isset($filters['value'])) {
$this->paginate['forceIndexHint'] = '(value1, value2)';
$this->paginate['forceIndexHint'] = 'value1, value2';
}
$this->paginate['conditions'] = $params['conditions'];

View File

@ -646,6 +646,12 @@ class ACLComponent extends Component
'removeOrg' => array('perm_sharing_group'),
'view' => array('*'),
),
'sightingBlocklists' => [
'index' => [],
'add' => [],
'delete' => [],
'edit' => []
],
'sightings' => array(
'add' => array('perm_sighting'),
'restSearch' => array('*'),

View File

@ -671,9 +671,10 @@ class RestResponseComponent extends Component
}
if ($response instanceof TmpFileTool) {
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
$requestEtag = $this->requestEtag();
if ($requestEtag !== null) {
$etag = '"' . $response->hash('sha1') . '"';
if ($_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
if ($requestEtag === $etag) {
return new CakeResponse(['status' => 304]);
}
$headers['ETag'] = $etag;
@ -689,9 +690,10 @@ class RestResponseComponent extends Component
}
} else {
// Check if resource was changed when `If-None-Match` header is send and return 304 Not Modified
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
$requestEtag = $this->requestEtag();
if ($requestEtag !== null) {
$etag = '"' . sha1($response) . '"';
if ($_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
if ($requestEtag === $etag) {
return new CakeResponse(['status' => 304]);
}
// Generate etag just when HTTP_IF_NONE_MATCH is set
@ -724,6 +726,25 @@ class RestResponseComponent extends Component
return $cakeResponse;
}
/**
* Return etag from If-None-Match HTTP request header without compression marks added by Apache
* @return string|null
*/
private function requestEtag()
{
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
// Remove compression marks that adds Apache for compressed content
$requestEtag = $_SERVER['HTTP_IF_NONE_MATCH'];
$etagWithoutQuotes = trim($requestEtag, '"');
$dashPos = strrpos($etagWithoutQuotes, '-');
if ($dashPos && in_array(substr($etagWithoutQuotes, $dashPos + 1), ['br', 'gzip'], true)) {
return '"' . substr($etagWithoutQuotes, 0, $dashPos) . '"';
}
return $requestEtag;
}
return null;
}
/**
* @param string $response
* @return string Signature as base64 encoded string

View File

@ -1416,7 +1416,8 @@ class EventsController extends AppController
$exception = false;
$warningTagConflicts = array();
$filters = $this->_harvestParameters($filterData, $exception);
$analystData = $this->Event->attachAnalystData($event['Event']);
$event['Event'] = array_merge($event['Event'], $analystData);
$emptyEvent = (empty($event['Object']) && empty($event['Attribute']));
$this->set('emptyEvent', $emptyEvent);
@ -1490,7 +1491,6 @@ class EventsController extends AppController
$containsProposals = true;
}
}
foreach ($event['Object'] as $k => $object) {
$modDate = date("Y-m-d", $object['timestamp']);
$modificationMap[$modDate] = !isset($modificationMap[$modDate])? 1 : $modificationMap[$modDate] + 1;
@ -1522,7 +1522,6 @@ class EventsController extends AppController
}
}
}
if ($containsProposals && $this->__canPublishEvent($event, $user)) {
$mess = $this->Session->read('Message');
if (empty($mess)) {
@ -1696,8 +1695,8 @@ class EventsController extends AppController
}
$namedParams = $this->request->params['named'];
$conditions['includeAnalystData'] = true;
if ($this->_isRest()) {
$conditions['includeAnalystData'] = true;
$conditions['includeAttachments'] = isset($namedParams['includeAttachments']) ? $namedParams['includeAttachments'] : true;
} else {
$conditions['includeAllTags'] = true;
@ -2414,7 +2413,7 @@ class EventsController extends AppController
if (isset($this->params['named']['distribution'])) {
$distribution = intval($this->params['named']['distribution']);
if (!array_key_exists($distribution, $distributionLevels)) {
throw new MethodNotAllowedException(__('Wrong distribution level'));
throw new BadRequestException(__('Wrong distribution level'));
}
} else {
$distribution = $initialDistribution;
@ -2422,11 +2421,11 @@ class EventsController extends AppController
$sharingGroupId = null;
if ($distribution == 4) {
if (!isset($this->params['named']['sharing_group_id'])) {
throw new MethodNotAllowedException(__('The sharing group id is needed when the distribution is set to 4 ("Sharing group").'));
throw new BadRequestException(__('The sharing group id is needed when the distribution is set to 4 ("Sharing group").'));
}
$sharingGroupId = intval($this->params['named']['sharing_group_id']);
if (!array_key_exists($sharingGroupId, $sgs)) {
throw new MethodNotAllowedException(__('Please select a valid sharing group id.'));
throw new BadRequestException(__('Please select a valid sharing group id.'));
}
}
$clusterDistribution = $initialDistribution;
@ -2436,15 +2435,15 @@ class EventsController extends AppController
if (isset($this->params['name']['cluster_distribution'])) {
$clusterDistribution = intval($this->params['named']['cluster_distribution']);
if (!array_key_exists($clusterDistribution, $distributionLevels)) {
throw new MethodNotAllowedException(__('Wrong cluster distribution level'));
throw new BadRequestException(__('Wrong cluster distribution level'));
}
if ($clusterDistribution == 4) {
if (!isset($this->params['named']['cluster_sharing_group_id'])) {
throw new MethodNotAllowedException(__('The cluster sharing group id is needed when the cluster distribution is set to 4 ("Sharing group").'));
throw new BadRequestException(__('The cluster sharing group id is needed when the cluster distribution is set to 4 ("Sharing group").'));
}
$clusterSharingGroupId = intval($this->params['named']['cluster_sharing_group_id']);
if (!array_key_exists($clusterSharingGroupId, $sgs)) {
throw new MethodNotAllowedException(__('Please select a valid cluster sharing group id.'));
throw new BadRequestException(__('Please select a valid cluster sharing group id.'));
}
}
}
@ -2476,8 +2475,8 @@ class EventsController extends AppController
} else {
return $this->RestResponse->saveFailResponse('Events', 'upload_stix', false, $result, $this->response->type());
}
} else {
$original_file = !empty($this->data['Event']['original_file']) ? $this->data['Event']['stix']['name'] : '';
} else { // not REST request
$originalFile = !empty($this->data['Event']['original_file']) ? $this->data['Event']['stix']['name'] : '';
if (isset($this->data['Event']['stix']) && $this->data['Event']['stix']['size'] > 0 && is_uploaded_file($this->data['Event']['stix']['tmp_name'])) {
$filePath = FileAccessTool::createTempFile();
if (!move_uploaded_file($this->data['Event']['stix']['tmp_name'], $filePath)) {
@ -2490,12 +2489,12 @@ class EventsController extends AppController
$this->Auth->user(),
$filePath,
$stix_version,
$original_file,
$originalFile,
$this->data['Event']['publish'],
$this->data['Event']['distribution'],
$this->data['Event']['sharing_group_id'] ?? null,
$this->data['Event']['galaxies_handling'],
$this->data['Event']['cluster_distribution'],
$this->data['Event']['galaxies_handling'] ?? false,
$this->data['Event']['cluster_distribution'] ?? 0,
$this->data['Event']['cluster_sharing_group_id'] ?? null,
$debug
);
@ -4862,16 +4861,18 @@ class EventsController extends AppController
public function updateGraph($id, $type = 'event')
{
$user = $this->_closeSession();
$validTools = array('event', 'galaxy', 'tag');
if (!in_array($type, $validTools, true)) {
throw new MethodNotAllowedException(__('Invalid type.'));
}
$this->loadModel('Taxonomy');
$this->loadModel('GalaxyCluster');
App::uses('CorrelationGraphTool', 'Tools');
$grapher = new CorrelationGraphTool();
$data = $this->request->is('post') ? $this->request->data : array();
$grapher->construct($this->Event, $this->Taxonomy, $this->GalaxyCluster, $user, $data);
$grapher = new CorrelationGraphTool($this->Event, $this->Taxonomy, $this->GalaxyCluster, $user, $data);
$json = $grapher->buildGraphJson($id, $type);
array_walk_recursive($json, function (&$item, $key) {
if (!mb_detect_encoding($item, 'utf-8', true)) {

View File

@ -0,0 +1,47 @@
<?php
App::uses('AppController', 'Controller');
class SightingBlocklistsController extends AppController
{
public $components = array('Session', 'RequestHandler', 'BlockList');
public function beforeFilter()
{
parent::beforeFilter();
if (!$this->_isSiteAdmin()) {
$this->redirect('/');
}
if (Configure::check('MISP.enableSightingBlocklisting') && !Configure::read('MISP.enableSightingBlocklisting') !== false) {
$this->Flash->info(__('Sighting BlockListing is not currently enabled on this instance.'));
$this->redirect('/');
}
}
public $paginate = array(
'limit' => 60,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user van view/page.
'order' => array(
'SightingBlocklist.created' => 'DESC'
),
);
public function index()
{
return $this->BlockList->index($this->_isRest());
}
public function add()
{
return $this->BlockList->add($this->_isRest());
}
public function edit($id)
{
return $this->BlockList->edit($this->_isRest(), $id);
}
public function delete($id)
{
return $this->BlockList->delete($this->_isRest(), $id);
}
}

View File

@ -191,4 +191,3 @@ class AchievementsWidget
return $result;
}
}
?>

View File

@ -35,4 +35,3 @@ class AttackWidget
return $data;
}
}
?>

View File

@ -137,4 +137,3 @@ class OrgContributionToplistWidget
return ['data' => $results];
}
}
?>

View File

@ -78,4 +78,3 @@ class OrganisationListWidget
return ['data' => $results];
}
}
?>

View File

@ -85,4 +85,3 @@ class OrganisationMapWidget
return $results;
}
}
?>

View File

@ -19,4 +19,3 @@ class OrgsContributorLastMonthWidget extends OrgsContributorsGeneric
return count($results) > 0;
}
}
?>

View File

@ -45,4 +45,3 @@ class OrgsContributorsGeneric
return $result;
}
}
?>

View File

@ -32,4 +32,3 @@ class OrgsUsingMitreWidget extends OrgsContributorsGeneric
return count($events) > 0;
}
}
?>

View File

@ -25,4 +25,3 @@ class OrgsUsingObjectsWidget extends OrgsContributorsGeneric
return count($eventsIds) > 0;
}
}
?>

View File

@ -145,4 +145,3 @@ class UserContributionToplistWidget
return true;
}
}
?>

View File

@ -556,7 +556,12 @@ class AttributeValidationTool
if (!is_numeric($value) || $value < 0 || $value > 10) {
return __('The value has to be a number between 0 and 10.');
}
return true;*/
return true;*/
case 'integer':
if (is_int($value)) {
return true;
}
return __('The value has to be an integer value.');
case 'iban':
case 'bic':
case 'btc':

View File

@ -1,4 +1,6 @@
<?php
App::uses('OrgImgHelper', 'View/Helper');
class CorrelationGraphTool
{
private $__lookupTables = array();
@ -9,20 +11,24 @@
/** @var Taxonomy */
private $__taxonomyModel;
private $__galaxyClusterModel = false;
private $__user = false;
private $__json = array();
/** @var User */
private $__user;
/** @var array */
private $data;
private $orgImgHelper;
public function construct(Event $eventModel, $taxonomyModel, $galaxyClusterModel, $user, $json)
public function __construct(Event $eventModel, $taxonomyModel, $galaxyClusterModel, array $user, array $data)
{
$this->__eventModel = $eventModel;
$this->__taxonomyModel = $taxonomyModel;
$this->__galaxyClusterModel = $galaxyClusterModel;
$this->__user = $user;
$this->__json = $json;
$this->data = $data;
$this->__lookupTables = array(
'analysisLevels' => $this->__eventModel->analysisLevels,
'distributionLevels' => $this->__eventModel->Attribute->distributionLevels
);
'analysisLevels' => $eventModel->analysisLevels,
'distributionLevels' => $eventModel->Attribute->distributionLevels
);
$this->orgImgHelper = new OrgImgHelper(new View());
return true;
}
@ -38,7 +44,7 @@
'sgReferenceOnly' => true,
));
if (empty($event)) {
return $this->__json;
return $this->data;
}
$this->cleanLinks();
$event[0]['Event']['Orgc'] = $event[0]['Orgc'];
@ -75,7 +81,7 @@
public function buildGraphJson($id, $type = 'event', $action = 'create')
{
if ($action == 'delete') {
return $this->__json;
return $this->data;
}
switch ($type) {
case 'event':
@ -88,13 +94,13 @@
$this->__expandTag($id);
break;
}
return $this->__json;
return $this->data;
}
private function __deleteObject($id)
{
$this->cleanLinks();
return $this->__json;
return $this->data;
}
private function __handleObjects($objects, $anchor_id, $full = false)
@ -193,8 +199,8 @@
private function __expandGalaxy($id)
{
if (!empty($this->__json['nodes'])) {
foreach ($this->__json['nodes'] as $k => $node) {
if (!empty($this->data['nodes'])) {
foreach ($this->data['nodes'] as $k => $node) {
if ($node['type'] == 'galaxy' && $node['id'] == $id) {
$current_galaxy_id = $k;
$tag_name = $node['tag_name'];
@ -205,7 +211,7 @@
$current_galaxy_id = $this->__addGalaxy($id);
}
$this->cleanLinks();
$events = $this->__eventModel->EventTag->Tag->fetchSimpleEventsForTag($this->__json['nodes'][$current_galaxy_id]['tag_name'], $this->__user, true);
$events = $this->__eventModel->EventTag->Tag->fetchSimpleEventsForTag($this->data['nodes'][$current_galaxy_id]['tag_name'], $this->__user, true);
foreach ($events as $event) {
$current_event_id = $this->__createNode('event', $event);
$this->__addLink($current_event_id, $current_galaxy_id);
@ -229,7 +235,7 @@
{
$link = $this->graphJsonContainsLink($from_id, $to_id);
if ($link === false) {
$this->__json['links'][] = array('source' => $from_id, 'target' => $to_id, 'linkDistance' => $linkDistance);
$this->data['links'][] = array('source' => $from_id, 'target' => $to_id, 'linkDistance' => $linkDistance);
}
}
@ -240,7 +246,7 @@
if ($from_uuid == $to_uuid) {
return false;
}
foreach ($this->__json['nodes'] as $k => $node) {
foreach ($this->data['nodes'] as $k => $node) {
if ($node['uuid'] === $from_uuid) {
$from_id = $k;
}
@ -277,10 +283,9 @@
);
break;
case 'event':
if ($this->orgImgExists($data['Orgc']['name'])) {
$image = Configure::read('MISP.baseurl') . '/img/orgs/' . h($data['Orgc']['name']) . '.png';
} else {
$image = Configure::read('MISP.baseurl') . '/img/orgs/MISP.png';
$orgImage = $this->orgImgHelper->getOrgLogoAsBase64($data['Orgc']);
if ($orgImage === null) {
$orgImage = Configure::read('MISP.baseurl') . '/img/misp-org.png';
}
$node = array(
'unique_id' => 'event-' . $data['id'],
@ -289,7 +294,7 @@
'id' => $data['id'],
'expanded' => $expand,
'uuid' => $data['uuid'],
'image' => $image,
'image' => $orgImage,
'info' => $data['info'],
'org' => $data['Orgc']['name'],
'analysis' => $this->__lookupTables['analysisLevels'][$data['analysis']],
@ -345,11 +350,11 @@
);
break;
}
$this->__json['nodes'][] = $node;
$current_id = count($this->__json['nodes'])-1;
$this->data['nodes'][] = $node;
$current_id = count($this->data['nodes'])-1;
} else {
if ($expand) {
$this->__json['nodes'][$current_id]['expanded'] = 1;
$this->data['nodes'][$current_id]['expanded'] = 1;
}
}
return $current_id;
@ -357,11 +362,11 @@
public function cleanLinks()
{
if (isset($this->__json['nodes']) && isset($this->__json['links'])) {
if (isset($this->data['nodes']) && isset($this->data['links'])) {
$links = array();
foreach ($this->__json['links'] as $link) {
foreach ($this->data['links'] as $link) {
$temp = array();
foreach ($this->__json['nodes'] as $k => $node) {
foreach ($this->data['nodes'] as $k => $node) {
if ($link['source'] == $node) {
$temp['source'] = $k;
}
@ -372,32 +377,24 @@
$temp['linkDistance'] = $link['linkDistance'];
$links[] = $temp;
}
$this->__json['links'] = $links;
$this->data['links'] = $links;
} else {
if (!isset($this->__json['links'])) {
$this->__json['links'] = array();
if (!isset($this->data['links'])) {
$this->data['links'] = array();
}
if (!isset($this->__json['nodes'])) {
$this->__json['nodes'] = array();
if (!isset($this->data['nodes'])) {
$this->data['nodes'] = array();
}
}
return true;
}
public function orgImgExists($org)
{
if (file_exists(APP . 'webroot' . DS . 'img' . DS . 'orgs' . DS . $org . '.png')) {
return true;
}
return false;
}
public function graphJsonContains($type, $element)
{
if (!isset($this->__json['nodes'])) {
if (!isset($this->data['nodes'])) {
return false;
}
foreach ($this->__json['nodes'] as $k => $node) {
foreach ($this->data['nodes'] as $k => $node) {
if ($type == 'event' && $node['type'] == 'event' && $node['id'] == $element['id']) {
return $k;
}
@ -418,10 +415,10 @@
}
public function graphJsonContainsLink($id1, $id2)
{
if (!isset($this->__json['links'])) {
if (!isset($this->data['links'])) {
return false;
}
foreach ($this->__json['links'] as $k => $link) {
foreach ($this->data['links'] as $k => $link) {
if (($link['source'] == $id1 && $link['target'] == $id2) || ($link['source'] == $id2 && $link['target'] == $id1)) {
return $k;
}

View File

@ -7,7 +7,7 @@ class CurlClient extends HttpSocketExtended
private $ch;
/** @var int */
private $timeout = 30;
private $timeout = 10800;
/** @var string|null */
private $caFile;
@ -38,6 +38,8 @@ class CurlClient extends HttpSocketExtended
{
if (isset($params['timeout'])) {
$this->timeout = $params['timeout'];
} else {
$this->timeout = Configure::check('MISP.curl_request_timeout') ? Configure::read('MISP.curl_request_timeout') : 10800;
}
if (isset($params['ssl_cafile'])) {
$this->caFile = $params['ssl_cafile'];
@ -271,16 +273,6 @@ class CurlClient extends HttpSocketExtended
*/
private function constructResponse($body, array $headers, $code)
{
if (isset($responseHeaders['content-encoding']) && $responseHeaders['content-encoding'] === 'zstd') {
if (!function_exists('zstd_uncompress')) {
throw new SocketException('Response is zstd encoded, but PHP do not support zstd decoding.');
}
$body = zstd_uncompress($body);
if ($body === false) {
throw new SocketException('Could not decode zstd encoded response.');
}
}
$response = new HttpSocketResponseExtended();
$response->code = $code;
$response->body = $body;
@ -319,7 +311,7 @@ class CurlClient extends HttpSocketExtended
CURLOPT_RETURNTRANSFER => true, // Should cURL return or print out the data? (true = return, false = print)
CURLOPT_HEADER => false, // Include header in result?
CURLOPT_TIMEOUT => $this->timeout, // Timeout in seconds
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP, // be sure that only HTTP and HTTPS protocols are enabled,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP, // be sure that only HTTP and HTTPS protocols are enabled
];
if ($this->caFile) {
@ -335,7 +327,7 @@ class CurlClient extends HttpSocketExtended
}
if ($this->compress) {
$options[CURLOPT_ACCEPT_ENCODING] = $this->supportedEncodings();
$options[CURLOPT_ACCEPT_ENCODING] = ''; // empty string means all encodings supported by curl
}
if ($this->allowSelfSigned) {
@ -352,25 +344,4 @@ class CurlClient extends HttpSocketExtended
return $options;
}
/**
* @return string
*/
private function supportedEncodings()
{
$encodings = [];
// zstd is not supported by curl itself, but add support if PHP zstd extension is installed
if (function_exists('zstd_uncompress')) {
$encodings[] = 'zstd';
}
// brotli and gzip is supported by curl itself if it is compiled with these features
$info = curl_version();
if (defined('CURL_VERSION_BROTLI') && $info['features'] & CURL_VERSION_BROTLI) {
$encodings[] = 'br';
}
if ($info['features'] & CURL_VERSION_LIBZ) {
$encodings[] = 'gzip, deflate';
}
return implode(', ', $encodings);
}
}

View File

@ -114,6 +114,10 @@ class HttpSocketResponseExtended extends HttpSocketResponse
*/
public function json()
{
if (strlen($this->body) === 0) {
throw new HttpSocketJsonException('Could not parse empty response as JSON.', $this);
}
try {
return JsonTool::decode($this->body);
} catch (Exception $e) {

View File

@ -304,12 +304,24 @@ class ServerSyncTool
*/
public function fetchSightingsForEvents(array $eventUuids)
{
return $this->post('/sightings/restSearch/event', [
$SightingBlocklist = ClassRegistry::init('SightingBlocklist');
$blocked_sightings = $SightingBlocklist->find('column', [
'recursive' => -1,
'fields' => ['org_uuid']
]);
foreach ($blocked_sightings as $k => $uuid) {
$blocked_sightings[$k] = '!' . $uuid;
}
$postParams = [
'returnFormat' => 'json',
'last' => 0, // fetch all
'includeUuid' => true,
'uuid' => $eventUuids,
])->json()['response'];
];
if (!empty($blocked_sightings)) {
$postParams['org_id'] = $blocked_sightings;
}
return $this->post('/sightings/restSearch/event', $postParams)->json()['response'];
}
/**

View File

@ -82,7 +82,9 @@ class SyncTool
}
$params['ssl_crypto_method'] = $version;
}
if (!isset($params['timeout'])) {
$params['timeout'] = Configure::check('MISP.curl_request_timeout') ? Configure::read('MISP.curl_request_timeout') : 10800;
}
if (function_exists('curl_init')) {
App::uses('CurlClient', 'Tools');
$HttpSocket = new CurlClient($params);

View File

@ -185,7 +185,7 @@ class AnalystData extends AppModel
public function getEditableFields(): array
{
return array_merge(self::BASE_EDITABLE_FIELDS, static::EDITABLE_FIELDS);
return array_merge(self::BASE_EDITABLE_FIELDS, $this->EDITABLE_FIELDS);
}
/**
@ -377,6 +377,8 @@ class AnalystData extends AppModel
return $analystData;
}
$this->fetchedUUIDFromRecursion[$analystData['uuid']] = true;
$this->Note = ClassRegistry::init('Note');
$this->Opinion = ClassRegistry::init('Opinion');
$paramsNote = [
'recursive' => -1,

View File

@ -91,7 +91,7 @@ class AppModel extends Model
105 => false, 106 => false, 107 => false, 108 => false, 109 => false, 110 => false,
111 => false, 112 => false, 113 => true, 114 => false, 115 => false, 116 => false,
117 => false, 118 => false, 119 => false, 120 => false, 121 => false, 122 => false,
123 => false,
123 => false, 124 => false,
);
const ADVANCED_UPDATES_DESCRIPTION = array(
@ -2164,6 +2164,18 @@ class AppModel extends Model
$sqlArray[] = 'ALTER TABLE `opinions` MODIFY `modified` datetime NOT NULL;';
$sqlArray[] = 'ALTER TABLE `relationships` MODIFY `modified` datetime NOT NULL;';
break;
case 124:
$sqlArray[] = 'CREATE TABLE IF NOT EXISTS `sighting_blocklists` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`org_uuid` varchar(40) COLLATE utf8_bin NOT NULL,
`created` datetime NOT NULL,
`org_name` varchar(255) COLLATE utf8_bin NOT NULL,
`comment` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci,
PRIMARY KEY (`id`),
INDEX `org_uuid` (`org_uuid`),
INDEX `org_name` (`org_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;';
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ class AnalystDataBehavior extends ModelBehavior
private $__current_type = null;
private $__valid_sharing_groups = null;
protected $__valid_sharing_groups = null;
public function setup(Model $Model, $settings = array()) {
// We want to know whether we're a Note, Opinion or Relationship
@ -76,7 +76,7 @@ class AnalystDataBehavior extends ModelBehavior
]);
$results = [];
foreach ($temp as $result) {
$results[$result[$type]['object_uuid']] = $result;
$results[$result[$type]['object_uuid']][$type][] = $result[$type];
}
return $results;
}

View File

@ -31,17 +31,44 @@ class AnalystDataParentBehavior extends ModelBehavior
$temp = $this->{$type}->fetchForUuid($object['uuid'], $this->__currentUser);
if (!empty($temp)) {
foreach ($temp as $k => $temp_element) {
if (in_array($type, ['Note', 'Opinion', 'Relationship'])) {
$temp_element[$type] = $this->{$type}->fetchChildNotesAndOpinions($this->__currentUser, $temp_element[$type], 1);
}
$data[$type][] = $temp_element[$type];
}
}
}
// include inbound relationship
$data['RelationshipInbound'] = Hash::extract($this->Relationship->getInboundRelationships($this->__currentUser, $model->alias, $object['uuid']), '{n}.Relationship');
return $data;
}
public function fetchAnalystDataBulk(Model $model, array $uuids, array $types = ['Note', 'Opinion', 'Relationship']) {
$uuids = array_chunk($uuids, 100000);
if (empty($this->__currentUser)) {
$user_id = Configure::read('CurrentUserId');
$this->User = ClassRegistry::init('User');
if ($user_id) {
$this->__currentUser = $this->User->getAuthUser($user_id);
}
}
$results = [];
foreach ($uuids as $uuid_chunk) {
foreach ($types as $type) {
$this->{$type} = ClassRegistry::init($type);
$this->{$type}->fetchRecursive = !empty($model->includeAnalystDataRecursive);
$temp = $this->{$type}->fetchForUuids($uuid_chunk, $this->__currentUser);
$results = array_merge($results, $temp);
}
}
return $results;
}
public function attachAnalystDataBulk(Model $model, array $objects, array $types = ['Note', 'Opinion', 'Relationship'])
{
$uuids = [];
$objects = array_chunk($objects, 10000);
$objects = array_chunk($objects, 100000, true);
if (empty($this->__currentUser)) {
$user_id = Configure::read('CurrentUserId');
$this->User = ClassRegistry::init('User');

View File

@ -177,6 +177,10 @@ class MysqlExtended extends Mysql
*/
public function insertMulti($table, $fields, $values)
{
if (empty($values)) {
return true;
}
$table = $this->fullTableName($table);
$holder = substr(str_repeat('?,', count($fields)), 0, -1);
$fields = implode(',', array_map([$this, 'name'], $fields));

View File

@ -5587,6 +5587,7 @@ class Event extends AppModel
$passedArgs['page'] = 0;
}
$params = $customPagination->applyRulesOnArray($objects, $passedArgs, 'events', 'category');
$objects = $this->attachAnalystDataToViewObjects($objects);
foreach ($objects as $k => $object) {
if (isset($referencedByArray[$object['uuid']])) {
foreach ($referencedByArray[$object['uuid']] as $objectType => $references) {
@ -5599,6 +5600,44 @@ class Event extends AppModel
return $params;
}
// take a list of paginated, rearranged objects from the event view generation's viewUI() function
// collect all attribute and object uuids from the object list
// fetch the related analyst data and inject them back into the object list
public function attachAnalystDataToViewObjects($objects)
{
$attribute_notes = [];
$object_notes = [];
foreach ($objects as $k => $object) {
if ($object['objectType'] === 'object') {
$object_notes[] = $object['uuid'];
foreach ($object['Attribute'] as $a) {
$attribute_notes[] = $a['uuid'];
}
} else if ($object['objectType'] === 'attribute') {
$attribute_notes[] = $object['uuid'];
}
}
$attribute_notes = $this->Attribute->fetchAnalystDataBulk($attribute_notes);
$object_notes = $this->Object->fetchAnalystDataBulk($object_notes);
foreach ($objects as $k => $object) {
if ($object['objectType'] === 'object') {
if (!empty($object_notes[$object['uuid']])) {
$objects[$k] = array_merge($object, $object_notes[$object['uuid']]);
}
foreach ($object['Attribute'] as $k2 => $a) {
if (!empty($attribute_notes[$a['uuid']])) {
$objects[$k]['Attribute'][$k2] = array_merge($a, $attribute_notes[$a['uuid']]);
}
}
} else if ($object['objectType'] === 'attribute') {
if (!empty($attribute_notes[$object['uuid']])) {
$objects[$k] = array_merge($object, $attribute_notes[$object['uuid']]);
}
}
}
return $objects;
}
// pass along a json from the server filter rules
// returns a conditions set to be merged into pagination / event fetch / etc
public function filterRulesToConditions($rules)
@ -6072,7 +6111,7 @@ class Event extends AppModel
/**
* @param string $stixVersion
* @param string $file
* @param string $file Path to STIX file
* @param int $distribution
* @param int|null $sharingGroupId
* @param bool $galaxiesAsTags
@ -6130,6 +6169,7 @@ class Event extends AppModel
try {
$stdout = ProcessTool::execute($shellCommand, null, true);
} catch (ProcessException $e) {
$this->logException("Could not import $stixVersion file $file", $e);
$stdout = $e->stdout();
}

View File

@ -197,7 +197,7 @@ class GalaxyCluster extends AppModel
*/
public function arrangeData($cluster)
{
$models = array('Galaxy', 'SharingGroup', 'GalaxyElement', 'GalaxyClusterRelation', 'Org', 'Orgc', 'TargetingClusterRelation', 'Note', 'Opinion', 'Relationship');
$models = array('Galaxy', 'SharingGroup', 'GalaxyElement', 'GalaxyClusterRelation', 'Org', 'Orgc', 'TargetingClusterRelation', 'Note', 'Opinion', 'Relationship', 'RelationshipInbound');
foreach ($models as $model) {
if (isset($cluster[$model])) {
$cluster['GalaxyCluster'][$model] = $cluster[$model];

View File

@ -49,7 +49,7 @@ class Relationship extends AnalystData
}
}
foreach ($results as $i => $v) {
if (!empty($v[$this->alias]['related_object_type']) && !empty($v[$this->alias]['related_object_uuid'])) {
if (!empty($v[$this->alias]['related_object_type']) && !empty($v[$this->alias]['related_object_uuid']) && empty($results[$i][$this->alias]['related_object'])) {
$results[$i][$this->alias]['related_object'] = $this->getRelatedElement($this->__currentUser, $v[$this->alias]['related_object_type'], $v[$this->alias]['related_object_uuid']);
}
}
@ -146,4 +146,41 @@ class Relationship extends AnalystData
}
return $data;
}
public function getInboundRelationships(array $user, $object_type, $object_uuid): array
{
$conditions = [
'related_object_type' => $object_type,
'related_object_uuid' => $object_uuid,
];
$type = 'Relationship';
if (empty($user['Role']['perm_site_admin'])) {
if ($this->__valid_sharing_groups === null) {
$this->__valid_sharing_groups = $this->SharingGroup->authorizedIds($user, true);
}
$conditions['AND'][] = [
'OR' => [
$type . '.orgc_uuid' => $user['Organisation']['uuid'],
$type . '.org_uuid' => $user['Organisation']['uuid'],
$type . '.distribution IN' => [1, 2, 3],
'AND' => [
$type . '.distribution' => 4,
$type . '.sharing_group_id IN' => $this->__valid_sharing_groups
]
]
];
}
$inboundRelations = $this->find('all', [
'recursive' => -1,
'conditions' => $conditions,
'contain' => ['Org', 'Orgc', 'SharingGroup'],
]);
foreach ($inboundRelations as $i => $relationship) {
$relationship = $relationship['Relationship'];
$inboundRelations[$i]['Relationship']['related_object'] = $this->getRelatedElement($this->__currentUser, $relationship['object_type'], $relationship['object_uuid']);
}
return $inboundRelations;
}
}

View File

@ -583,7 +583,7 @@ class Server extends AppModel
try {
$this->__checkIfPulledEventExistsAndAddOrUpdate($event, $eventId, $successes, $fails, $eventModel, $serverSync->server(), $user, $jobId, $force, $response);
} catch (Exception $e) {
$title = __('Pulling an event (#%s) from Server #%s has failed. The sync process was not interrupted.', $eventId, $serverSync->server()['id']);
$title = __('Pulling an event (#%s) from Server #%s has failed. The sync process was not interrupted.', $eventId, $serverSync->serverId());
$this->loadLog()->createLogEntry(
$user,
'error',
@ -5123,6 +5123,14 @@ class Server extends AppModel
'type' => 'numeric',
'null' => true
),
'curl_request_timeout' => [
'level' => 1,
'description' => __('Control the timeout of curl requests issued by MISP (during synchronisation, feed fetching, etc.'),
'value' => 10800,
'test' => 'testForNumeric',
'type' => 'numeric',
'null' => true
],
'disable_sighting_loading' => [
'level' => 1,
'description' => __('If an instance has an extremely high number of sightings, including the sightings in the search algorithms can bring an instance to a grinding halt. Enable this setting to temporarily disable the search until the issue is remedied. This setting will also disable sightings from being attached via /events/view API calls.'),
@ -5714,6 +5722,13 @@ class Server extends AppModel
'type' => 'boolean',
'test' => 'testBool'
),
'enableSightingBlocklisting' => array(
'level' => 1,
'description' => __('Blocklisting organisation UUIDs to prevent the creation of any sightings created by the blocklisted organisation.'),
'value' => true,
'type' => 'boolean',
'test' => 'testBool'
),
'log_client_ip' => array(
'level' => 1,
'description' => __('If enabled, all log entries will include the IP address of the user.'),

View File

@ -25,6 +25,8 @@ class Sighting extends AppModel
public $recursive = -1;
private $__blockedOrgs = null;
public $actsAs = array(
'Containable',
);
@ -72,6 +74,16 @@ class Sighting extends AppModel
} else {
$this->data['Sighting']['uuid'] = strtolower($this->data['Sighting']['uuid']);
}
if ($this->__blockedOrgs === null) {
$SightingBlocklist = ClassRegistry::init('SightingBlocklist');
$this->__blockedOrgs = $SightingBlocklist->find('column', [
'recursive' => -1,
'fields' => ['org_uuid']
]);
}
if (!empty($this->data['Sighting']['org_uuid']) && in_array($this->data['Sighting']['org_uuid'], $this->__blockedOrgs)) {
return false;
}
return true;
}
@ -428,18 +440,33 @@ class Sighting extends AppModel
return ['Sighting.attribute_id' => array_column(array_column($attributes, 'Attribute'), 'id')];
}
$hostOrgId = Configure::read('MISP.host_org_id');
// Merge attributes by Event ID
$userOrgId = $user['org_id'];
$conditions = [];
$attributesByEventId = [];
foreach ($attributes as $attribute) {
$attributeConditions = ['Sighting.attribute_id' => $attribute['Attribute']['id']];
$ownEvent = $attribute['Event']['org_id'] == $userOrgId;
if (!$ownEvent) {
$eventId = $attribute['Event']['id'];
if (isset($attributesByEventId[$eventId])) {
$attributesByEventId[$eventId]['ids'][] = $attribute['Attribute']['id'];
} else {
$ownEvent = $attribute['Event']['org_id'] == $userOrgId;
$attributesByEventId[$eventId] = [
'ids' => [$attribute['Attribute']['id']],
'ownEvent' => $ownEvent,
];
}
}
// Create conditions for merged attributes
$hostOrgId = Configure::read('MISP.host_org_id');
$conditions = [];
foreach ($attributesByEventId as $eventId => $eventAttributes) {
$attributeConditions = ['Sighting.attribute_id' => $eventAttributes['ids']];
if (!$eventAttributes['ownEvent']) {
if ($sightingsPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$attributeConditions['Sighting.org_id'] = $userOrgId;
} else if ($sightingsPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
if (!$this->isReporter($attribute['Event']['id'], $userOrgId)) {
continue; // skip attribute
if (!$this->isReporter($eventId, $userOrgId)) {
continue; // skip event
}
} else if ($sightingsPolicy === self::SIGHTING_POLICY_HOST_ORG) {
$attributeConditions['Sighting.org_id'] = [$userOrgId, $hostOrgId];
@ -1086,20 +1113,30 @@ class Sighting extends AppModel
$filters['org_id'] = array($filters['org_id']);
}
foreach ($filters['org_id'] as $k => $org_id) {
$negation = false;
if (is_string($org_id) && $org_id[0] === '!') {
$negation = true;
$org_id = substr($org_id, 1);
}
if (Validation::uuid($org_id)) {
$org = $this->Organisation->find('first', array(
'conditions' => array('Organisation.uuid' => $org_id),
'recursive' => -1,
'fields' => array('Organisation.id'),
));
if (empty($org)) {
$filters['org_id'][$k] = -1;
} else {
$filters['org_id'][$k] = $org['Organisation']['id'];
if (!empty($org)) {
$temp = $org['Organisation']['id'];
}
}
if ($negation) {
$conditions['Sighting.org_id'][] = $temp;
} else {
$conditions['Sighting.org_id NOT IN'][] = $temp;
}
if (empty($conditions['Sighting.org_id']) && empty($conditions['Sighting.org_id NOT IN'])) {
$conditions['Sighting.org_id'] = -1;
}
}
$conditions['Sighting.org_id'] = $filters['org_id'];
}
if (isset($filters['source'])) {
@ -1241,6 +1278,8 @@ class Sighting extends AppModel
if (!isset($attributes[$s['attribute_uuid']])) {
continue; // attribute doesn't exists or user don't have permission to access it
}
$existingSighting[$s['uuid']] = true; // just to be sure that there are no sigthings with duplicated UUID
list($attributeId, $eventId) = $attributes[$s['attribute_uuid']];
if ($s['type'] === '2') {
@ -1256,11 +1295,8 @@ class Sighting extends AppModel
if ($user['Role']['perm_sync']) {
if (isset($s['org_id'])) {
if ($s['org_id'] != 0 && !empty($s['Organisation'])) {
if (isset($existingOrganisations[$s['Organisation']['uuid']])) {
$saveOnBehalfOf = $existingOrganisations[$s['Organisation']['uuid']];
} else {
$saveOnBehalfOf = $this->Organisation->captureOrg($s['Organisation'], $user);
}
$saveOnBehalfOf = $existingOrganisations[$s['Organisation']['uuid']] ??
$this->Organisation->captureOrg($s['Organisation'], $user);
} else {
$saveOnBehalfOf = 0;
}
@ -1282,8 +1318,8 @@ class Sighting extends AppModel
}
if ($this->saveMany($toSave)) {
$existingUuids = array_column($toSave, 'uuid');
$this->Event->publishSightingsRouter($event['Event']['id'], $user, $passAlong, $existingUuids);
$sightingsUuidsToPush = array_column($toSave, 'uuid');
$this->Event->publishSightingsRouter($event['Event']['id'], $user, $passAlong, $sightingsUuidsToPush);
return count($toSave);
}
@ -1389,20 +1425,17 @@ class Sighting extends AppModel
$this->logException("Could not fetch event IDs from server {$serverSync->server()['Server']['name']}", $e);
return 0;
}
// Remove events from list that do not have published sightings.
foreach ($remoteEvents as $k => $remoteEvent) {
if ($remoteEvent['sighting_timestamp'] == 0) {
unset($remoteEvents[$k]);
}
}
// Downloads sightings just from events that exists locally and remote sighting_timestamp is newer than local.
$localEvents = $this->Event->find('list', [
'fields' => ['Event.uuid', 'Event.sighting_timestamp'],
'conditions' => (count($remoteEvents) > 10000) ? [] : ['Event.uuid' => array_column($remoteEvents, 'uuid')],
]);
$eventUuids = [];
foreach ($remoteEvents as $remoteEvent) {
if (isset($localEvents[$remoteEvent['uuid']]) && $localEvents[$remoteEvent['uuid']] < $remoteEvent['sighting_timestamp']) {
@ -1410,7 +1443,6 @@ class Sighting extends AppModel
}
}
unset($remoteEvents, $localEvents);
if (empty($eventUuids)) {
return 0;
}
@ -1445,10 +1477,9 @@ class Sighting extends AppModel
try {
$sightings = $serverSync->fetchSightingsForEvents($chunk);
} catch (Exception $e) {
$this->logException("Failed to download sightings from {$serverSync->server()['Server']['name']}.", $e);
$this->logException("Failed to download sightings from remote server {$serverSync->server()['Server']['name']}.", $e);
continue;
}
$sightingsToSave = [];
foreach ($sightings as $sighting) {
$sighting = $sighting['Sighting'];

View File

@ -0,0 +1,167 @@
<?php
App::uses('AppModel', 'Model');
class SightingBlocklist extends AppModel
{
public $useTable = 'sighting_blocklists';
public $recursive = -1;
public $actsAs = [
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full'),
'Containable',
];
public $blocklistFields = ['org_uuid', 'comment', 'org_name'];
public $blocklistTarget = 'org';
private $blockedCache = [];
public $validate = array(
'org_uuid' => array(
'unique' => array(
'rule' => 'isUnique',
'message' => 'Organisation already blocklisted.'
),
'uuid' => array(
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
),
)
);
public function beforeValidate($options = array())
{
parent::beforeValidate();
if (empty($this->data['OrgBlocklist']['id'])) {
$this->data['OrgBlocklist']['date_created'] = date('Y-m-d H:i:s');
}
return true;
}
public function afterDelete()
{
parent::afterDelete();
if (!empty($this->data['OrgBlocklist']['org_uuid'])) {
$this->cleanupBlockedCount($this->data['OrgBlocklist']['org_uuid']);
}
}
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $result) {
if (isset($result['OrgBlocklist']['org_uuid'])) {
$results[$k]['OrgBlocklist']['blocked_data'] = $this->getBlockedData($result['OrgBlocklist']['org_uuid']);
}
}
return $results;
}
/**
* @param array $eventArray
*/
public function removeBlockedEvents(array &$eventArray)
{
$blocklistHits = $this->find('column', array(
'conditions' => array('OrgBlocklist.org_uuid' => array_unique(array_column($eventArray, 'orgc_uuid'))),
'fields' => array('OrgBlocklist.org_uuid'),
));
if (empty($blocklistHits)) {
return;
}
$blocklistHits = array_flip($blocklistHits);
foreach ($eventArray as $k => $event) {
if (isset($blocklistHits[$event['orgc_uuid']])) {
unset($eventArray[$k]);
}
}
}
/**
* @param int|string $orgIdOrUuid Organisation ID or UUID
* @return bool
*/
public function isBlocked($orgIdOrUuid)
{
if (isset($this->blockedCache[$orgIdOrUuid])) {
return $this->blockedCache[$orgIdOrUuid];
}
if (is_numeric($orgIdOrUuid)) {
$orgUuid = $this->getUUIDFromID($orgIdOrUuid);
} else {
$orgUuid = $orgIdOrUuid;
}
$isBlocked = $this->hasAny(['OrgBlocklist.org_uuid' => $orgUuid]);
$this->blockedCache[$orgIdOrUuid] = $isBlocked;
return $isBlocked;
}
private function getUUIDFromID($orgID)
{
$this->Organisation = ClassRegistry::init('Organisation');
$orgUuid = $this->Organisation->find('first', [
'conditions' => ['Organisation.id' => $orgID],
'fields' => ['Organisation.uuid'],
'recursive' => -1,
]);
if (empty($orgUuid)) {
return false; // org not found by ID, so it is not blocked
}
$orgUuid = $orgUuid['Organisation']['uuid'];
return $orgUuid;
}
public function saveEventBlocked($orgIdOrUUID)
{
if (is_numeric($orgIdOrUUID)) {
$orgcUUID = $this->getUUIDFromID($orgIdOrUUID);
} else {
$orgcUUID = $orgIdOrUUID;
}
$lastBlockTime = time();
$redisKeyBlockAmount = "misp:blocklist_blocked_amount:{$orgcUUID}";
$redisKeyBlockLastTime = "misp:blocklist_blocked_last_time:{$orgcUUID}";
$redis = RedisTool::init();
if ($redis !== false) {
$pipe = $redis->multi(Redis::PIPELINE)
->incr($redisKeyBlockAmount)
->set($redisKeyBlockLastTime, $lastBlockTime);
$pipe->exec();
}
}
private function cleanupBlockedCount($orgcUUID)
{
$redisKeyBlockAmount = "misp:blocklist_blocked_amount:{$orgcUUID}";
$redisKeyBlockLastTime = "misp:blocklist_blocked_last_time:{$orgcUUID}";
$redis = RedisTool::init();
if ($redis !== false) {
$pipe = $redis->multi(Redis::PIPELINE)
->del($redisKeyBlockAmount)
->del($redisKeyBlockLastTime);
$pipe->exec();
}
}
public function getBlockedData($orgcUUID)
{
$redisKeyBlockAmount = "misp:blocklist_blocked_amount:{$orgcUUID}";
$redisKeyBlockLastTime = "misp:blocklist_blocked_last_time:{$orgcUUID}";
$blockData = [
'blocked_amount' => false,
'blocked_last_time' => false,
];
$redis = RedisTool::init();
if ($redis !== false) {
$blockData['blocked_amount'] = $redis->get($redisKeyBlockAmount);
$blockData['blocked_last_time'] = $redis->get($redisKeyBlockLastTime);
}
return $blockData;
}
}

View File

@ -13,7 +13,7 @@ App::uses('Oidc', 'OidcAuth.Lib');
* - OidcAuth.organisation_property (default: `organization`)
* - OidcAuth.organisation_uuid_property (default: `organization_uuid`)
* - OidcAuth.roles_property (default: `roles`)
* - OidcAuth.default_org - organisation ID, UUID or name if organsation is not provided by OIDC
* - OidcAuth.default_org - organisation ID, UUID or name if organisation is not provided by OIDC
* - OidcAuth.unblock (boolean, default: false)
* - OidcAuth.offline_access (boolean, default: false)
* - OidcAuth.check_user_validity (integer, default `0`)

View File

@ -227,13 +227,13 @@ class Oidc
$roleProperty = $this->getConfig('roles_property', 'roles');
$roles = $claims->{$roleProperty} ?? $oidc->requestUserInfo($roleProperty);
if ($roles === null) {
$this->log($user['email'], "Role property `$roleProperty` is missing in claims.");
$this->log($user['email'], "Role property `$roleProperty` is missing in claims.", LOG_ERR);
return false;
}
$roleId = $this->getUserRole($roles, $user['email']);
if ($roleId === null) {
$this->log($user['email'], 'No role was assigned.');
$this->log($user['email'], 'No role was assigned.', LOG_WARNING);
return false;
}
@ -244,14 +244,20 @@ class Oidc
// Check user org
$organisationProperty = $this->getConfig('organisation_property', 'organization');
$organisationName = $claims->{$organisationProperty} ?? $this->getConfig('default_org');
$organisationName = $claims->{$organisationProperty} ?? null;
$organisationUuidProperty = $this->getConfig('organisation_uuid_property', 'organization_uuid');
$organisationUuid = $claims->{$organisationUuidProperty} ?? null;
$organisationId = $this->checkOrganization($organisationName, $organisationUuid, $user['email']);
if (!$organisationId) {
return false;
$defaultOrganisationId = $this->defaultOrganisationId();
if ($defaultOrganisationId) {
$organisationId = $defaultOrganisationId;
} else {
$this->log($user['email'], 'No organisation was assigned.', LOG_WARNING);
return false;
}
}
if ($update && $user['org_id'] != $organisationId) {
@ -406,11 +412,11 @@ class Oidc
]);
if (empty($orgAux)) {
if (is_numeric($defaultOrgName)) {
$this->log(null, "Could not find default organisation with ID `$defaultOrgName`.");
$this->log(null, "Could not find default organisation with ID `$defaultOrgName`.", LOG_ERR);
} else if (Validation::uuid($defaultOrgName)) {
$this->log(null, "Could not find default organisation with UUID `$defaultOrgName`.");
$this->log(null, "Could not find default organisation with UUID `$defaultOrgName`.", LOG_ERR);
} else {
$this->log(null, "Could not find default organisation with name `$defaultOrgName`.");
$this->log(null, "Could not find default organisation with name `$defaultOrgName`.", LOG_ERR);
}
return false;
}

View File

@ -13,6 +13,7 @@ $fields = [
'notes_path' => $modelSelection . '.Note',
'opinions_path' => $modelSelection . '.Opinion',
'relationships_path' => $modelSelection . '.Relationship',
'relationshipsInbound_path' => $modelSelection . '.RelationshipInbound',
],
[
'key' => __('Note Type'),
@ -126,7 +127,8 @@ $options = [
'shortDist' => $shortDist,
'notes' => $data[$modelSelection]['Note'] ?? [],
'opinions' => $data[$modelSelection]['Opinion'] ?? [],
'relationships' => $data[$modelSelection]['Relationship'] ?? [],
'relationships_outbound' => $data[$modelSelection]['Relationship'] ?? [],
'relationships_inbound' => $data[$modelSelection]['RelationshipInbound'] ?? [],
];
echo $this->element('genericElements/assetLoader', [

View File

@ -74,8 +74,9 @@
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
$relationshipsInbound = !empty($object['RelationshipInbound']) ? $object['RelationshipInbound'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships_outbound' => $relationships, 'relationships_inbound' => $relationshipsInbound],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
@ -90,12 +91,14 @@
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
$relationshipsInbound = !empty($object['RelationshipInbound']) ? $object['RelationshipInbound'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
'relationshipsInbound' => $relationshipsInbound,
]);
?>
</td>

View File

@ -37,7 +37,7 @@ $objectId = intval($object['id']);
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships_outbound' => $relationships, 'relationships_inbound' => $relationshipsInbound],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
@ -52,12 +52,14 @@ $objectId = intval($object['id']);
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
$relationshipsInbound = !empty($object['RelationshipInbound']) ? $object['RelationshipInbound'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
'relationshipsInbound' => $relationshipsInbound,
]);
?>
</td>

View File

@ -51,8 +51,9 @@
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
$relationshipsInbound = !empty($object['RelationshipInbound']) ? $object['RelationshipInbound'] : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships_outbound' => $relationships, 'relationships_inbound' => $relationshipsInbound],
'object_uuid' => $object['uuid'],
'object_type' => 'Attribute'
]);
@ -72,12 +73,14 @@
$notes = !empty($object['Note']) ? $object['Note'] : [];
$opinions = !empty($object['Opinion']) ? $object['Opinion'] : [];
$relationships = !empty($object['Relationship']) ? $object['Relationship'] : [];
$relationshipsInbound = !empty($object['RelationshipInbound']) ? $object['RelationshipInbound'] : [];
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $object['uuid'],
'object_type' => 'Attribute',
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
'relationshipsInbound' => $relationshipsInbound,
]);
?>
</td>

View File

@ -3,10 +3,11 @@ $seed = mt_rand();
$notes = $analyst_data['notes'] ?? [];
$opinions = $analyst_data['opinions'] ?? [];
$relationships = $analyst_data['relationships'] ?? [];
$relationships_outbound = $analyst_data['relationships_outbound'] ?? [];
$relationships_inbound = $analyst_data['relationships_inbound'] ?? [];
$notesOpinions = array_merge($notes, $opinions);
$notesOpinionsRelationships = array_merge($notesOpinions, $relationships);
$notesOpinionsRelationships = array_merge($notesOpinions, $relationships_outbound);
if(!function_exists("countNotes")) {
function countNotes($notesOpinions) {
@ -37,17 +38,25 @@ if(!function_exists("countNotes")) {
}
$counts = countNotes($notesOpinions);
$notesOpinionCount = $counts['notesOpinions'];
$relationshipsCount = count($relationships);
$relationshipsOutboundCount = count($relationships_outbound);
$relationshipsInboundCount = count($relationships_inbound);
$allCounts = [
'notesOpinions' => $counts['notesOpinions'],
'relationships_outbound' => $relationshipsOutboundCount,
'relationships_inbound' => $relationshipsInboundCount,
]
?>
<?php if (empty($notesOpinions) && empty($relationshipsCount)): ?>
<?php if (empty($notesOpinions) && empty($relationshipsOutboundCount) && empty($relationshipsInboundCount)): ?>
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer node-opener-<?= $seed ?>" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?php else: ?>
<span class="label label-info useCursorPointer node-opener-<?= $seed ?> highlight-on-hover">
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?= $notesOpinionCount; ?>
<i class="<?= $this->FontAwesome->getClass('project-diagram') ?> useCursorPointer" title="<?= __('Relationships for this UUID') ?>"></i>
<?= $relationshipsCount; ?>
<i class="<?= $this->FontAwesome->getClass('arrow-up') ?> useCursorPointer" title="<?= __('Outbound Relationships from this UUID') ?>"></i>
<?= $relationshipsOutboundCount; ?>
<i class="<?= $this->FontAwesome->getClass('arrow-down') ?> useCursorPointer" title="<?= __('Inbound Relationships to this UUID') ?>"></i>
<?= $relationshipsInboundCount; ?>
</span>
<?php endif; ?>
@ -78,9 +87,11 @@ $(document).ready(function() {
'seed' => $seed,
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
'relationships_outbound' => $relationships_outbound,
'relationships_inbound' => $relationships_inbound,
'object_type' => $object_type,
'object_uuid' => $object_uuid,
'shortDist' => $shortDist,
'allCounts' => $allCounts,
]);
?>

View File

@ -7,7 +7,8 @@
$notes = !empty($notes) ? $notes : [];
$opinions = !empty($opinions) ? $opinions : [];
$relationships = !empty($relationships) ? $relationships : [];
$relationshipsOutbound = !empty($relationships_outbound) ? $relationships_outbound : [];
$relationshipsInbound = !empty($relationships_inbound) ? $relationships_inbound : [];
$related_objects = [
'Attribute' => [],
@ -20,14 +21,19 @@
'Opinion' => [],
'SharingGroup' => [],
];
foreach ($relationships as $relationship) {
foreach ($relationshipsOutbound as $relationship) {
if (!empty($relationship['related_object'][$relationship['related_object_type']])) {
$related_objects[$relationship['related_object_type']][$relationship['related_object_uuid']] = $relationship['related_object'][$relationship['related_object_type']];
}
}
foreach ($relationshipsInbound as $relationship) {
if (!empty($relationship['related_object'][$relationship['object_type']])) {
$related_objects[$relationship['object_type']][$relationship['object_uuid']] = $relationship['related_object'][$relationship['object_type']];
}
}
$notesOpinions = array_merge($notes, $opinions);
$notesOpinionsRelationships = array_merge($notesOpinions, $relationships);
$notesOpinionsRelationships = array_merge($notesOpinions, $relationshipsOutbound, $relationshipsInbound);
?>
<script>
@ -37,14 +43,14 @@ if (!window.shortDist) {
}
var renderedNotes<?= $seed ?> = null
function renderNotes(notes, relationship_related_object) {
function renderNotes(notes, relationship_related_object, emptyMessage='<?= __('Empty') ?>', isInbound=false) {
var renderedNotesArray = []
if (notes.length == 0) {
var emptyHtml = '<span style="text-align: center; color: #777;"><?= __('No notes for this UUID.') ?></span>'
var emptyHtml = '<span style="text-align: center; color: #777;">' + emptyMessage + '</span>'
renderedNotesArray.push(emptyHtml)
} else {
notes.forEach(function(note) {
var noteHtml = renderNote(note, relationship_related_object)
var noteHtml = renderNote(note, relationship_related_object, isInbound)
if (note.Opinion && note.Opinion.length > 0) { // The notes has more notes attached
noteHtml += replyNoteTemplate({notes_html: renderNotes(note.Opinion, relationship_related_object), })
@ -62,7 +68,7 @@ function renderNotes(notes, relationship_related_object) {
return renderedNotesArray.join('')
}
function renderNote(note, relationship_related_object) {
function renderNote(note, relationship_related_object, isInbound=false) {
note.modified_relative = note.modified ? moment(note.modified).fromNow() : note.modified
note.created_relative = note.created ? moment(note.created).fromNow() : note.created
note.modified = note.modified ? (new Date(note.modified)).toLocaleString() : note.modified
@ -78,7 +84,10 @@ function renderNote(note, relationship_related_object) {
note.opinion_text = (note.opinion >= 81) ? '<?= __("Strongly Agree") ?>' : ((note.opinion >= 61) ? '<?= __("Agree") ?>' : ((note.opinion >= 41) ? '<?= __("Neutral") ?>' : ((note.opinion >= 21) ? '<?= __("Disagree") ?>' : '<?= __("Strongly Disagree") ?>')))
note.content = opinionTemplate(note)
} else if (note.note_type == 2) {
note.content = renderRelationshipEntryFromType(note, relationship_related_object)
note.content = renderRelationshipEntryFromType(note, relationship_related_object, isInbound)
}
if (isInbound) {
note._canEdit = false;
}
var noteHtml = baseNoteTemplate(note)
return noteHtml
@ -96,7 +105,15 @@ function getURLFromRelationship(note) {
return '#'
}
function renderRelationshipEntryFromType(note, relationship_related_object) {
function renderRelationshipEntryFromType(note, relationship_related_object, isInbound=false) {
if (isInbound) { // reverse related_object_* with object_* to preserve the same code
var tmp_uuid = note.object_uuid
var tmp_type = note.object_type
note.object_uuid = note.related_object_uuid
note.related_object_uuid = tmp_uuid
note.object_type = note.related_object_type
note.related_object_type = tmp_type
}
var contentHtml = ''
var template = doT.template('\
<span style="border: 1px solid #ddd !important; border-radius: 3px; padding: 0.25rem;"> \
@ -166,7 +183,13 @@ function renderRelationshipEntryFromType(note, relationship_related_object) {
}
note.url = getURLFromRelationship(note)
contentHtml = template(note)
return relationshipDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
var full = ''
if (isInbound) {
full = relationshipInboundDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
} else {
full = relationshipDefaultEntryTemplate({content: contentHtml, relationship_type: note.relationship_type, comment: note.comment})
}
return full
}
var noteFilteringTemplate = '\
@ -250,6 +273,7 @@ var opinionTemplate = doT.template('\
var relationshipDefaultEntryTemplate = doT.template('\
<div style="max-width: 40vw; margin: 0.5rem 0 0.5rem 0.25rem;"> \
<div style="display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;"> \
<i class="far fa-dot-circle" style="font-size: 1.25em; color: #555; margin-right: 0.25em;"></i> \
<i class="<?= $this->FontAwesome->getClass('minus') ?>" style="font-size: 1.5em; color: #555"></i> \
<span style="text-wrap: nowrap; padding: 0 0.25rem; border: 2px solid #555; border-radius: 0.25rem; max-width: 20rem; overflow-x: hidden; text-overflow: ellipsis;"> \
{{? it.relationship_type }} \
@ -259,7 +283,29 @@ var relationshipDefaultEntryTemplate = doT.template('\
{{?}} \
</span> \
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
<div style="margin-left: 0.5rem;">{{=it.content}}</div> \
<div style="margin-left: 0.25rem;">{{=it.content}}</div> \
</div> \
{{? it.comment }} \
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
{{!it.comment}} \
</div> \
{{?}} \
</div> \
')
var relationshipInboundDefaultEntryTemplate = doT.template('\
<div style="max-width: 40vw; margin: 0.5rem 0 0.5rem 0.25rem;"> \
<div style="display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;"> \
<div style="margin-right: 0.25rem;">{{=it.content}}</div> \
<i class="<?= $this->FontAwesome->getClass('minus') ?>" style="font-size: 1.5em; color: #555"></i> \
<span style="text-wrap: nowrap; padding: 0 0.25rem; border: 2px solid #555; border-radius: 0.25rem; max-width: 20rem; overflow-x: hidden; text-overflow: ellipsis;"> \
{{? it.relationship_type }} \
{{!it.relationship_type}} \
{{??}} \
<i style="font-weight: lighter; color: #999;"> - empty -</i> \
{{?}} \
</span> \
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
<i class="far fa-dot-circle" style="font-size: 1.25em; color: #555; margin-left: 0.25em;"></i> \
</div> \
{{? it.comment }} \
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
@ -363,7 +409,8 @@ function fetchMoreNotes(clicked, noteType, uuid) {
(function() {
var notes = <?= json_encode($notesOpinions) ?>;
var relationships = <?= json_encode($relationships) ?>;
var relationships = <?= json_encode($relationshipsOutbound) ?>;
var relationships_inbound = <?= json_encode($relationshipsInbound) ?>;
var relationship_related_object = <?= json_encode($related_objects) ?>;
var container_id = false
<?php if (isset($container_id)): ?>
@ -373,16 +420,20 @@ function fetchMoreNotes(clicked, noteType, uuid) {
var nodeContainerTemplate = doT.template('\
<div> \
<ul class="nav nav-tabs" style="margin-bottom: 10px;"> \
<li class="active"><a href="#notes-<?= $seed ?>" data-toggle="tab"><?= __('Notes & Opinions') ?></a></li> \
<li><a href="#relationships-<?= $seed ?>" data-toggle="tab"><?= __('Relationships') ?></a></li> \
<li class="active"><a href="#notes-<?= $seed ?>" data-toggle="tab"><i class="<?= $this->FontAwesome->getClass('sticky-note') ?>"></i> <?= __('Notes & Opinions') ?> <span class="label label-secondary"><?= $allCounts['notesOpinions'] ?></span></a></li> \
<li><a href="#relationships-outbound-<?= $seed ?>" data-toggle="tab"><i class="<?= $this->FontAwesome->getClass('arrow-up') ?>"></i> <?= __('Outbound Relationships') ?> <span class="label label-secondary"><?= $allCounts['relationships_outbound'] ?></span></a></li> \
<li><a href="#relationships-inbound-<?= $seed ?>" data-toggle="tab"><i class="<?= $this->FontAwesome->getClass('arrow-down') ?>"></i> <?= __('Inbound Relationships') ?> <span class="label label-secondary"><?= $allCounts['relationships_inbound'] ?></span></a></li> \
</ul> \
<div class="tab-content" style="padding: 0.25rem; max-width: 1200px; min-width: 400px;"> \
<div id="notes-<?= $seed ?>" class="tab-pane active"> \
' + noteFilteringTemplate + ' \
<div style="display: flex; flex-direction: column; gap: 0.5rem;" class="all-notes">{{=it.content_notes}}</div>\
</div> \
<div id="relationships-<?= $seed ?>" class="tab-pane"> \
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships}}</div>\
<div id="relationships-outbound-<?= $seed ?>" class="tab-pane"> \
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships_outbound}}</div>\
</div> \
<div id="relationships-inbound-<?= $seed ?>" class="tab-pane"> \
<div style="display: flex; flex-direction: column; gap: 0.5rem;">{{=it.content_relationships_inbound}}</div>\
</div> \
</div> \
</div> \
@ -391,8 +442,9 @@ function fetchMoreNotes(clicked, noteType, uuid) {
function renderAllNotesWithForm(relationship_related_object) {
var buttonContainer = '<div id="add-button-container" style="margin-top: 0.5rem;">' + addNoteButton + addOpinionButton + '</div>'
renderedNotes<?= $seed ?> = nodeContainerTemplate({
content_notes: renderNotes(notes.filter(function(note) { return note.note_type != 2}), relationship_related_object) + buttonContainer,
content_relationships: renderNotes(relationships, relationship_related_object) + addRelationshipButton,
content_notes: renderNotes(notes.filter(function(note) { return note.note_type != 2}), relationship_related_object, '<?= __('No notes for this UUID.') ?>') + buttonContainer,
content_relationships_outbound: renderNotes(relationships, relationship_related_object, '<?= __('No relationship from this UUID') ?>') + addRelationshipButton,
content_relationships_inbound: renderNotes(relationships_inbound, relationship_related_object, '<?= __('No element are referencing this UUID') ?>', true),
})
if (container_id) {
$('#' + container_id).html(renderedNotes<?= $seed ?>)

View File

@ -7,10 +7,12 @@
$notes = Hash::extract($row, $field['notes_data_path'] ?? 'Note');
$opinions = Hash::extract($row, $field['opinions_data_path'] ?? 'Opnion');
$relationships = Hash::extract($row, $field['relationships_data_path'] ?? 'Relationship');
$relationshipsInbound = Hash::extract($row, $field['relationships_inbound_data_path'] ?? 'RelationshipInbound');
echo $this->element('genericElements/shortUuidWithNotes', [
'uuid' => $uuid,
'object_type' => $field['object_type'],
'notes' => $notes,
'opinions' => $opinions,
'relationships' => $relationships,
'relationshipsInbound' => $relationshipsInbound,
]);

View File

@ -1119,6 +1119,7 @@ $divider = '<li class="divider"></li>';
'url' => $baseurl . '/servers/eventBlockRule',
'text' => __('Event Block Rules')
));
echo $divider;
if (Configure::read('MISP.enableEventBlocklisting') !== false) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'eventBlocklistsAdd',
@ -1130,6 +1131,7 @@ $divider = '<li class="divider"></li>';
'url' => $baseurl . '/eventBlocklists',
'text' => __('Manage Event Blocklists')
));
echo $divider;
}
if (!Configure::check('MISP.enableOrgBlocklisting') || Configure::read('MISP.enableOrgBlocklisting') !== false) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
@ -1142,6 +1144,20 @@ $divider = '<li class="divider"></li>';
'url' => $baseurl . '/orgBlocklists',
'text' => __('Manage Org Blocklists')
));
echo $divider;
}
if (!Configure::check('MISP.enableSightingBlocklisting') || Configure::read('MISP.enableSightingBlocklisting') !== false) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'sightingBlocklistsAdd',
'url' => $baseurl . '/sightingBlocklists/add',
'text' => __('Blocklists Sightings')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'sightingBlocklists',
'url' => $baseurl . '/sightingBlocklists',
'text' => __('Manage Sighting Blocklists')
));
echo $divider;
}
}
break;

View File

@ -9,11 +9,13 @@
$field['notes_path'] = !empty($field['notes_path']) ? $field['notes_path'] : 'Note';
$field['opinions_path'] = !empty($field['opinions_path']) ? $field['opinions_path'] : 'Opinion';
$field['relationships_path'] = !empty($field['relationships_path']) ? $field['relationships_path'] : 'Relationship';
$field['relationshipsInbound_path'] = !empty($field['relationshipsInbound_path']) ? $field['relationshipsInbound_path'] : 'RelationshipInbound';
$notes = !empty($field['notes']) ? $field['notes'] : Hash::extract($data, $field['notes_path']);
$opinions = !empty($field['opinions']) ? $field['opinions'] : Hash::extract($data, $field['opinions_path']);
$relationships = !empty($field['relationships']) ? $field['relationships'] : Hash::extract($data, $field['relationships_path']);
$relationshipsInbound = !empty($field['relationshipsInbound']) ? $field['relationshipsInbound'] : Hash::extract($data, $field['relationshipsInbound_path']);
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships],
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships_outbound' => $relationships, 'relationships_inbound' => $relationshipsInbound],
'object_uuid' => $uuid,
'object_type' => $field['object_type']
]);

View File

@ -8,8 +8,9 @@
$notes = !empty($notes) ? $notes : [];
$opinions = !empty($opinions) ? $opinions : [];
$relationships = !empty($relationships) ? $relationships : [];
$relationshipsInbound = !empty($relationshipsInbound) ? $relationshipsInbound : [];
echo $this->element('genericElements/Analyst_data/generic', [
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships' => $relationships,],
'analyst_data' => ['notes' => $notes, 'opinions' => $opinions, 'relationships_outbound' => $relationships, 'relationships_inbound' => $relationshipsInbound,],
'object_uuid' => $uuid,
'object_type' => $object_type
]);

View File

@ -81,6 +81,7 @@
'notes_data_path' => 'Note',
'opinions_data_path' => 'Opinion',
'relationships_data_path' => 'Relationship',
'relationships_inbound_data_path' => 'RelationshipInbound',
'element' => 'shortUUIDWithNotes',
'object_type' => 'EventReport',
),

View File

@ -60,6 +60,7 @@
'notes_data_path' => 'Note',
'opinions_data_path' => 'Opinion',
'relationships_data_path' => 'Relationship',
'relationships_inbound_data_path' => 'RelationshipInbound',
'element' => 'shortUUIDWithNotes',
'object_type' => 'EventReport',
),

View File

@ -133,8 +133,7 @@ $(function(){
checkSharingGroup('Event');
});
checkSharingGroup('Event');
});
$(function(){
$('#EventGalaxiesHandling').change(function() {
if ($(this).val() == 0) {
$('#ClusterDistribution').show();
@ -146,8 +145,7 @@ $(function(){
$('#ClusterSGContainer').hide();
}
}).change();
});
$(function(){
$('#EventClusterDistribution').change(function() {
if ($(this).val() == 4 && $('#EventGalaxiesHandling').val() == 0) {
$('#ClusterSGContainer').show();

View File

@ -66,6 +66,7 @@ $table_data[] = [
'notes_path' => 'GalaxyCluster.Note',
'opinions_path' => 'GalaxyCluster.Opinion',
'relationships_path' => 'GalaxyCluster.Relationship',
'relationshipsInbound_path' => 'GalaxyCluster.RelationshipInbound',
]
],
];
@ -176,7 +177,8 @@ $options = [
'shortDist' => $shortDist,
'notes' => $cluster['GalaxyCluster']['Note'] ?? [],
'opinions' => $cluster['GalaxyCluster']['Opinion'] ?? [],
'relationships' => $cluster['GalaxyCluster']['Relationship'] ?? [],
'relationships_outbound' => $cluster['GalaxyCluster']['Relationship'] ?? [],
'relationships_inbound' => $cluster['GalaxyCluster']['RelationshipInbound'] ?? [],
];
echo $this->element('genericElements/Analyst_data/thread', $options);

View File

@ -46,6 +46,19 @@ class OrgImgHelper extends AppHelper
return $this->getOrgImg($options, true, !$withLink);
}
/**
* @param array $organisation
* @return string|null
*/
public function getOrgLogoAsBase64(array $organisation)
{
$orgImgName = $this->findOrgImage($organisation);
if ($orgImgName) {
return $this->_View->Image->base64(self::IMG_PATH . $orgImgName);
}
return null;
}
/**
* @deprecated
*/

View File

@ -0,0 +1,40 @@
<?php
echo $this->element(
'/genericElements/SideMenu/side_menu',
['menuList' => 'admin', 'menuItem' => 'sightingBlocklistsAdd']
);
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Add Sighting Blocklist Entries'),
'description' => __('Blocklisting an organisation prevents the creation of any sighting by that organisation on this instance as well as syncing of that organisation\'s sightings to this instance. It does not prevent a local user of the blocklisted organisation from logging in and editing or viewing data. <br/>Paste a list of all the organisation UUIDs that you want to add to the blocklist below (one per line).'),
'fields' => [
[
'field' => 'uuids',
'label' => __('UUIDs'),
'div' => 'input clear',
'class' => 'input-xxlarge',
'type' => 'textarea',
'placeholder' => __('Enter a single or a list of UUIDs'),
],
[
'field' => 'org_name',
'label' => __('Organisation name'),
'class' => 'input-xxlarge',
'placeholder' => __('(Optional) The organisation name that the organisation is associated with')
],
[
'field' => 'comment',
'label' => __('Comment'),
'type' => 'textarea',
'div' => 'input clear',
'class' => 'input-xxlarge',
'placeholder' => __('(Optional) Any comments you would like to add regarding this (or these) entries.')
],
],
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);

View File

@ -0,0 +1,41 @@
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'admin', 'menuItem' => 'sightingBlocklistsAdd'));
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Edit Sighting Blocklist Entries'),
'description' => __('Blocklisting an organisation prevents the creation of any sighting by that organisation on this instance as well as syncing of that organisation\'s sightings to this instance. It does not prevent a local user of the blocklisted organisation from logging in and editing or viewing data. <br/>Paste a list of all the organisation UUIDs that you want to add to the blocklist below (one per line).'),
'fields' => [
[
'field' => 'org_uuid',
'label' => __('UUIDs'),
'default' => $blockEntry['SightingBlocklist']['org_uuid'],
'div' => 'input clear',
'class' => 'input-xxlarge',
'type' => 'textarea',
'disabled' => true,
'placeholder' => __('Enter a single or a list of UUIDs')
],
[
'field' => 'org_name',
'label' => __('Organisation name'),
'default' => $blockEntry['SightingBlocklist']['org_name'],
'class' => 'input-xxlarge',
'placeholder' => __('(Optional) The organisation name that the organisation is associated with')
],
[
'field' => 'comment',
'label' => __('Comment'),
'default' => $blockEntry['SightingBlocklist']['comment'],
'type' => 'textarea',
'div' => 'input clear',
'class' => 'input-xxlarge',
'placeholder' => __('(Optional) Any comments you would like to add regarding this (or these) entries.')
],
],
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);

View File

@ -0,0 +1,70 @@
<?php
$this->set('menuData', ['menuList' => 'admin', 'menuItem' => 'sightingBlocklists']);
echo $this->element('genericElements/IndexTable/scaffold', [
'scaffold_data' => [
'data' => [
'data' => $response,
'fields' => [
[
'name' => 'Id',
'sort' => 'SightingBlocklist.id',
'data_path' => 'SightingBlocklist.id'
],
[
'name' => 'Organisation name',
'sort' => 'SightingBlocklist.org_name',
'data_path' => 'SightingBlocklist.org_name'
],
[
'name' => 'UUID',
'sort' => 'SightingBlocklist.org_uuid',
'data_path' => 'SightingBlocklist.org_uuid'
],
[
'name' => 'Created',
'sort' => 'SightingBlocklist.created',
'data_path' => 'SightingBlocklist.created',
'element' => 'datetime'
],
[
'name' => 'Comment',
'sort' => 'SightingBlocklist.comment',
'data_path' => 'SightingBlocklist.comment',
'class' => 'bitwider'
],
[
'name' => 'Blocked amount',
'sort' => 'SightingBlocklist.blocked_data.blocked_amount',
'data_path' => 'SightingBlocklist.blocked_data.blocked_amount',
],
[
'name' => 'Blocked last time ',
'sort' => 'SightingBlocklist.blocked_data.blocked_last_time',
'data_path' => 'SightingBlocklist.blocked_data.blocked_last_time',
'element' => 'datetime'
],
],
'title' => empty($ajax) ? __('Sighting Blocklists') : false,
'pull' => 'right',
'actions' => [
[
'url' => $baseurl . '/org_blocklists/edit',
'url_params_data_paths' => array(
'SightingBlocklist.id'
),
'icon' => 'edit',
'title' => 'Edit Blocklist',
],
[
'url' => $baseurl . '/org_blocklists/delete',
'url_params_data_paths' => array(
'SightingBlocklist.id'
),
'icon' => 'trash',
'title' => 'Delete Blocklist',
]
]
]
]
]);

@ -1 +1 @@
Subproject commit e18e5c16c6451c9a15ba94f9f0d50c97c13dc808
Subproject commit c953d8ee5d1ec43bf8a418f957ad6f920fe1c0e8

@ -1 +1 @@
Subproject commit 8ccd583d217624c322a6927bcbdb7fe412a2e855
Subproject commit dc52c10844cbed9e2f39f0429665b4f9b1caef3e

@ -1 +1 @@
Subproject commit f531a2cdce0e1ff2bcc879d57d2872f6318fb5cf
Subproject commit 6f344fe8e813c2d512d725f07699003ec7548430

@ -1 +1 @@
Subproject commit 5f580a3bb5aec4341787719b4ded294c1bd9321a
Subproject commit 55e0f57d5d2953417e4084fe311a80bae99270f5

@ -1 +1 @@
Subproject commit d5c586c1bdbc84f3b7b6d0d1a4185c3b23f61bbb
Subproject commit 7c69f4f987ee753901e952071556d5ad0fafd115

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

View File

@ -7776,6 +7776,63 @@
"extra": ""
}
],
"sighting_blocklists": [
{
"column_name": "id",
"is_nullable": "NO",
"data_type": "int",
"character_maximum_length": null,
"numeric_precision": "10",
"collation_name": null,
"column_type": "int(11)",
"column_default": null,
"extra": "auto_increment"
},
{
"column_name": "org_uuid",
"is_nullable": "NO",
"data_type": "varchar",
"character_maximum_length": "40",
"numeric_precision": null,
"collation_name": "utf8mb3_bin",
"column_type": "varchar(40)",
"column_default": null,
"extra": ""
},
{
"column_name": "created",
"is_nullable": "NO",
"data_type": "datetime",
"character_maximum_length": null,
"numeric_precision": null,
"collation_name": null,
"column_type": "datetime",
"column_default": null,
"extra": ""
},
{
"column_name": "org_name",
"is_nullable": "NO",
"data_type": "varchar",
"character_maximum_length": "255",
"numeric_precision": null,
"collation_name": "utf8mb3_bin",
"column_type": "varchar(255)",
"column_default": null,
"extra": ""
},
{
"column_name": "comment",
"is_nullable": "YES",
"data_type": "text",
"character_maximum_length": "65535",
"numeric_precision": null,
"collation_name": "utf8mb3_unicode_ci",
"column_type": "text",
"column_default": "NULL",
"extra": ""
}
],
"system_settings": [
{
"column_name": "setting",
@ -10426,6 +10483,11 @@
"type": false,
"uuid": true
},
"sighting_blocklists": {
"id": true,
"org_name": false,
"org_uuid": false
},
"system_settings": {
"setting": true
},
@ -10541,5 +10603,5 @@
"uuid": false
}
},
"db_version": "123"
"db_version": "124"
}

View File

@ -411,7 +411,9 @@ apacheConfig_RHEL7 () {
#sudo sed -i "s/SetHandler/\#SetHandler/g" /etc/httpd/conf.d/misp.ssl.conf
sudo rm /etc/httpd/conf.d/ssl.conf
sudo chmod 644 /etc/httpd/conf.d/misp.ssl.conf
sudo sed -i '/Listen 443/!s/Listen 80/a Listen 443/' /etc/httpd/conf/httpd.conf
if ! grep -x "Listen 443" /etc/httpd/conf/httpd.conf; then
sudo sed -i '/Listen 80/a Listen 443' /etc/httpd/conf/httpd.conf
fi
# If a valid SSL certificate is not already created for the server, create a self-signed certificate:
echo "The Common Name used below will be: ${OPENSSL_CN}"

View File

@ -452,7 +452,9 @@ apacheConfig_RHEL8 () {
#sudo sed -i "s/SetHandler/\#SetHandler/g" /etc/httpd/conf.d/misp.ssl.conf
sudo rm /etc/httpd/conf.d/ssl.conf
sudo chmod 644 /etc/httpd/conf.d/misp.ssl.conf
sudo sed -i '/Listen 443/!s/Listen 80/a Listen 443/' /etc/httpd/conf/httpd.conf
if ! grep -x "Listen 443" /etc/httpd/conf/httpd.conf; then
sudo sed -i '/Listen 80/a Listen 443' /etc/httpd/conf/httpd.conf
fi
# If a valid SSL certificate is not already created for the server, create a self-signed certificate:
echo "The Common Name used below will be: ${OPENSSL_CN}"

View File

@ -5,18 +5,24 @@ For the time being both background jobs backends will be supported, but we plan
The new backend requires [Supervisor](http://supervisord.org/) and some extra PHP packages.
**This guide is intended for Ubuntu/Debian systems**
**This guide is intended for Ubuntu/Debian and RHEL systems, make sure you execute the version for your distribution**
## Install requirements
Run on your MISP instance the following commands.
1. Install **Supervisord**:
- Ubuntu / Debian
```
sudo apt install supervisor -y
```
- RHEL
```
sudo dnf install -y supervisor
```
2. Install required PHP packages:
- Ubuntu / Debian
```
cd /var/www/MISP/app
sudo -u www-data php composer.phar require --with-all-dependencies supervisorphp/supervisor:^4.0 \
@ -25,9 +31,14 @@ Run on your MISP instance the following commands.
php-http/message-factory \
lstrojny/fxmlrpc
```
- RHEL
```
sudo -u apache sh -c "cd /var/www/MISP/app;php composer.phar require --with-all-dependencies supervisorphp/supervisor:^4.0 guzzlehttp/guzzle php-http/message php-http/message-factory lstrojny/fxmlrpc"
```
3. Add the following settings at the bottom of the **Supervisord** conf file, usually located in:
- Ubuntu / Debian
`/etc/supervisor/supervisord.conf`
```
[inet_http_server]
@ -35,9 +46,11 @@ Run on your MISP instance the following commands.
username=supervisor
password=PWD_CHANGE_ME
```
- RHEL (same content as above, just different config file path)
`/etc/supervisord.conf`
4. Use the following configuration as a template for the services, usually located in:
- Ubuntu / Debian
`/etc/supervisor/conf.d/misp-workers.conf`
```
[group:misp-workers]
@ -107,6 +120,42 @@ Run on your MISP instance the following commands.
stdout_logfile=/var/www/MISP/app/tmp/logs/misp-workers.log
user=www-data
```
- RHEL. Same file content as above except for user which should be apache, find and replace www-data -> apache. Filepath is also different, see below:
`/etc/supervisord.d/misp-workers.ini`
## Make SELinux happy
***These steps are only relevant for systems with SELinux enabled (typically RHEL)!!!*** Create and install an SELinux module to run new misp-workers as httpd_t, this will make sure the workers diagnostics page works. If you get some message there saying you are not running the workers with correct user, so it can't get the status, SELinux is potentially the cause:
1. Install required packages
```
sudo dnf install -y selinux-policy-devel setools-console
```
2. Create and move to temp dir where we will create the required files
```
mkdir /tmp/misp-modules-supervisord
cd /tmp/misp-modules-supervisord
```
3. Create file and add content to
`misp-modules-supervisord.te`
```
policy_module(misp-workers-httpd, 1.0)
require{
type unconfined_service_t, httpd_sys_script_exec_t, httpd_t;
}
domtrans_pattern(unconfined_service_t, httpd_sys_script_exec_t, httpd_t);
allow httpd_t httpd_sys_script_exec_t:file entrypoint;
```
4. Make and install module
```
make -f /usr/share/selinux/devel/Makefile misp-modules-supervisord.pp
sudo semodule -i misp-modules-supervisord.pp
```
5. Restart **Supervisord** to load the changes:
```
sudo systemctl restart supervisord
```
## MISP Config
1. Go to your **MISP** instances `Server Settings & Maintenance` page, and then to the new [SimpleBackgroundJobs]((https://localhost/servers/serverSettings/SimpleBackgroundJobs)) tab.
@ -118,9 +167,14 @@ Run on your MISP instance the following commands.
4. Verify Redis and other settings are correct and then set `SimpleBackgroundJobs.enabled` to `true`.
5. Restart **Supervisord** to load the changes:
- Ubuntu / Debian
```
sudo service supervisor restart
```
- RHEL
```
sudo systemctl restart supervisord
```
6. Check **Supervisord** workers are running:
```

View File

@ -16,12 +16,14 @@ curl -sS --compressed https://stixproject.github.io/documentation/idioms/c2-indi
python ./../app/files/scripts/stix2misp.py test-stix1.xml 1 1 ./../app/files/scripts/synonymsToTagNames.json | python3 -c 'import sys, json; data = json.load(sys.stdin); print(data); sys.exit(0 if data["success"] == 1 else 1)'
rm -f ./../app/files/scripts/tmp/{test-stix1.xml,test-stix1.xml.json}
# Test converting stix2 to MISP format
# Test converting STIX2 to MISP format
curl -sS --compressed https://raw.githubusercontent.com/oasis-open/cti-stix2-json-schemas/master/examples/indicator-for-c2-ip-address.json > ./../app/files/scripts/tmp/test-stix2.json
python ./../app/files/scripts/stix2/stix2misp.py -i ./../app/files/scripts/tmp/test-stix2.json --distribution 1 | python3 -c 'import sys, json; data = json.load(sys.stdin); print(data); sys.exit(0 if data["success"] == 1 else 1)'
python3 -c 'import sys, json; json.load(sys.stdin)' < ./../app/files/scripts/tmp/test-stix2.json.out
rm -f ./../app/files/scripts/tmp/{test-stix2.json,test-stix2.json.stix2}
# Test converting MISP to STIX2
cp event.json /tmp/
python ./../app/files/scripts/stix2/misp2stix2.py -i /tmp/event.json | python3 -c 'import sys, json; data = json.load(sys.stdin); print(data); sys.exit(0 if data["success"] == 1 else 1)'
python3 -c 'import sys, json; json.load(sys.stdin)' < /tmp/event.json.out
rm -f /tmp/{event.json,event.json.out}

View File

@ -923,6 +923,9 @@ class TestComprehensive(unittest.TestCase):
self.assertTrue(created_user.autoalert, created_user)
self.admin_misp_connector.delete_user(created_user)
def test_attribute_search(self):
request(self.admin_misp_connector, "GET", "/attributes/search/value:8.8.8.8.json")
def test_search_snort_suricata(self):
event = create_simple_event()
event.add_attribute('ip-src', '8.8.8.8', to_ids=True)