diff --git a/app/Controller/AnalystDataController.php b/app/Controller/AnalystDataController.php index ef7b305fb..9659555d8 100644 --- a/app/Controller/AnalystDataController.php +++ b/app/Controller/AnalystDataController.php @@ -80,6 +80,7 @@ class AnalystDataController extends AppController $this->set('id', $id); $conditions = $this->AnalystData->buildConditions($this->Auth->user()); $params = [ + 'fields' => $this->AnalystData->getEditableFields(), 'conditions' => $conditions, 'afterFind' => function(array $analystData): array { $canEdit = $this->ACL->canEditAnalystData($this->Auth->user(), $analystData, $this->modelSelection); @@ -89,7 +90,7 @@ class AnalystDataController extends AppController return $analystData; }, 'beforeSave' => function(array $analystData): array { - $analystData[$this->modelSelection]['modified'] = date ('Y-m-d H:i:s'); + $analystData[$this->modelSelection]['modified'] = date('Y-m-d H:i:s'); return $analystData; } ]; diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index c65a98721..466c767ae 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -2408,14 +2408,14 @@ class EventsController extends AppController } if (isset($this->params['named']['distribution'])) { $distribution = intval($this->params['named']['distribution']); - if (array_key_exists($distribution, $distributionLevels)) { - $initialDistribution = $distribution; - } else { + if (!array_key_exists($distribution, $distributionLevels)) { throw new MethodNotAllowedException(__('Wrong distribution level')); } + } else { + $distribution = $initialDistribution; } $sharingGroupId = null; - if ($initialDistribution == 4) { + if ($distribution == 4) { if (!isset($this->params['named']['sharing_group_id'])) { throw new MethodNotAllowedException(__('The sharing group id is needed when the distribution is set to 4 ("Sharing group").')); } @@ -2424,8 +2424,25 @@ class EventsController extends AppController throw new MethodNotAllowedException(__('Please select a valid sharing group id.')); } } + $clusterDistribution = $initialDistribution; + $clusterSharingGroupId = null; if (isset($this->params['named']['galaxies_as_tags'])) { $galaxies_as_tags = $this->params['named']['galaxies_as_tags']; + if (isset($this->params['name']['cluster_distribution'])) { + $clusterDistribution = intval($this->params['named']['cluster_distribution']); + if (!array_key_exists($clusterDistribution, $distributionLevels)) { + throw new MethodNotAllowedException(__('Wrong cluster distribution level')); + } + if ($clusterDistribution == 4) { + if (!isset($this->params['named']['cluster_sharing_group_id'])) { + throw new MethodNotAllowedException(__('The cluster sharing group id is needed when the cluster distribution is set to 4 ("Sharing group").')); + } + $clusterSharingGroupId = intval($this->params['named']['cluster_sharing_group_id']); + if (!array_key_exists($clusterSharingGroupId, $sgs)) { + throw new MethodNotAllowedException(__('Please select a valid cluster sharing group id.')); + } + } + } } if (isset($this->params['named']['debugging'])) { $debug = $this->params['named']['debugging']; @@ -2437,9 +2454,11 @@ class EventsController extends AppController $stix_version, 'uploaded_stix_file.' . ($stix_version == '1' ? 'xml' : 'json'), $publish, - $initialDistribution, + $distribution, $sharingGroupId, $galaxies_as_tags, + $clusterDistribution, + $clusterSharingGroupId, $debug ); if (is_numeric($result)) { @@ -2471,6 +2490,8 @@ class EventsController extends AppController $this->data['Event']['distribution'], $this->data['Event']['sharing_group_id'] ?? null, $this->data['Event']['galaxies_handling'], + $this->data['Event']['cluster_distribution'], + $this->data['Event']['cluster_sharing_group_id'] ?? null, $debug ); if (is_numeric($result)) { diff --git a/app/Controller/OrganisationsController.php b/app/Controller/OrganisationsController.php index 2150a5b59..fd0f98931 100644 --- a/app/Controller/OrganisationsController.php +++ b/app/Controller/OrganisationsController.php @@ -481,8 +481,8 @@ class OrganisationsController extends AppController $extension = pathinfo($logo['name'], PATHINFO_EXTENSION); $filename = $orgId . '.' . ($extension === 'svg' ? 'svg' : 'png'); - if ($logo['size'] > 250*1024) { - $this->Flash->error(__('This organisation logo is too large, maximum file size allowed is 250kB.')); + if ($logo['size'] > 250 * 1024) { + $this->Flash->error(__('This organisation logo is too large, maximum file size allowed is 250 kB.')); return false; } @@ -492,10 +492,12 @@ class OrganisationsController extends AppController } $imgMime = mime_content_type($logo['tmp_name']); - if ($extension === 'png' && !exif_imagetype($logo['tmp_name'])) { + if ($extension === 'png' && (function_exists('exif_imagetype') && !exif_imagetype($logo['tmp_name']))) { $this->Flash->error(__('This is not a valid PNG image.')); return false; - } else if ($extension === 'svg' && !($imgMime === 'image/svg+xml' || $imgMime === 'image/svg')) { + } + + if ($extension === 'svg' && !($imgMime === 'image/svg+xml' || $imgMime === 'image/svg')) { $this->Flash->error(__('This is not a valid SVG image.')); return false; } diff --git a/app/Lib/Tools/ProcessTool.php b/app/Lib/Tools/ProcessTool.php index b0003938b..30b28e18d 100644 --- a/app/Lib/Tools/ProcessTool.php +++ b/app/Lib/Tools/ProcessTool.php @@ -55,8 +55,19 @@ class ProcessTool if ($logToFile) { self::logMessage('Running command ' . implode(' ', $command)); } - - $process = proc_open($command, $descriptorSpec, $pipes, $cwd); + if (version_compare(phpversion(), '7.4.0', '<')) { + $temp = []; + foreach ($command as $k => $part) { + if ($k >= 1) { + $part = escapeshellarg($part); + } + $temp[] = $part; + } + $command_stringified = implode(' ', $temp); + $process = proc_open($command_stringified, $descriptorSpec, $pipes, $cwd); + } else { + $process = proc_open($command, $descriptorSpec, $pipes, $cwd); + } if (!$process) { $commandForException = self::commandFormat($command); throw new Exception("Command '$commandForException' could be started."); diff --git a/app/Model/AnalystData.php b/app/Model/AnalystData.php index f7023afe4..42ff8e268 100644 --- a/app/Model/AnalystData.php +++ b/app/Model/AnalystData.php @@ -36,6 +36,15 @@ class AnalystData extends AppModel 'Relationship', ]; + protected const BASE_EDITABLE_FIELDS = [ + 'language', + 'authors', + 'modified', + 'distribution', + 'sharing_group_id', + ]; + protected $EDITABLE_FIELDS = []; + /** @var object|null */ protected $Note; /** @var object|null */ @@ -126,7 +135,7 @@ class AnalystData extends AppModel public function beforeValidate($options = array()) { - parent::beforeValidate(); + parent::beforeValidate($options); if (empty($this->id) && empty($this->data[$this->current_type]['uuid'])) { $this->data[$this->current_type]['uuid'] = CakeText::uuid(); } @@ -142,6 +151,25 @@ class AnalystData extends AppModel return true; } + public function beforeSave($options = []) + { + parent::beforeSave($options); + if (empty($this->data[$this->current_type]['created'])) { + $this->data[$this->current_type]['created'] = (new DateTime())->format('Y-m-d H:i:s'); + } + if (empty($this->data[$this->current_type]['modified'])) { + $this->data[$this->current_type]['modified'] = (new DateTime())->format('Y-m-d H:i:s'); + } + $this->data[$this->current_type]['modified'] = (new DateTime($this->data[$this->current_type]['modified'], new DateTimeZone('UTC')))->format('Y-m-d H:i:s'); + $this->data[$this->current_type]['created'] = (new DateTime($this->data[$this->current_type]['created'], new DateTimeZone('UTC')))->format('Y-m-d H:i:s'); + return true; + } + + public function getEditableFields(): array + { + return array_merge(self::BASE_EDITABLE_FIELDS, $this->EDITABLE_FIELDS); + } + /** * Checks if user can modify given analyst data * diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 3077fa34e..48582c699 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -91,6 +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, ); const ADVANCED_UPDATES_DESCRIPTION = array( @@ -2155,6 +2156,14 @@ class AppModel extends Model UNIQUE KEY `unique_element` (`element_uuid`, `collection_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; break; + case 123: + $sqlArray[] = 'ALTER TABLE `notes` MODIFY `created` datetime NOT NULL'; + $sqlArray[] = 'ALTER TABLE `opinions` MODIFY `created` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `relationships` MODIFY `created` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `notes` MODIFY `modified` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `opinions` MODIFY `modified` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `relationships` MODIFY `modified` datetime NOT NULL;'; + break; case 'fixNonEmptySharingGroupID': $sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; $sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; diff --git a/app/Model/Event.php b/app/Model/Event.php index 834db06b0..342337a45 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -5995,15 +5995,17 @@ class Event extends AppModel * @param int $distribution * @param int|null $sharingGroupId * @param bool $galaxiesAsTags + * @param int $clusterDistribution + * @param int|null $clusterSharingGroupId * @param bool $debug * @return int|string|array * @throws JsonException * @throws InvalidArgumentException * @throws Exception */ - public function upload_stix(array $user, $file, $stixVersion, $originalFile, $publish, $distribution, $sharingGroupId, $galaxiesAsTags, $debug = false) + public function upload_stix(array $user, $file, $stixVersion, $originalFile, $publish, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $debug = false) { - $decoded = $this->convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $debug); + $decoded = $this->convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $user['Organisation']['uuid'], $debug); if (!empty($decoded['success'])) { $data = JsonTool::decodeArray($decoded['converted']); @@ -6067,11 +6069,14 @@ class Event extends AppModel * @param int $distribution * @param int|null $sharingGroupId * @param bool $galaxiesAsTags + * @param int $clusterDistribution + * @param int|null $clusterSharingGroupId + * @param string $orgUuid * @param bool $debug * @return array * @throws Exception */ - private function convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $debug) + private function convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $orgUuid, $debug) { $scriptDir = APP . 'files' . DS . 'scripts'; if ($stixVersion === '2' || $stixVersion === '2.0' || $stixVersion === '2.1') { @@ -6082,12 +6087,18 @@ class Event extends AppModel $scriptFile, '-i', $file, '--distribution', $distribution, + '--org_uuid', $orgUuid ]; if ($distribution == 4) { array_push($shellCommand, '--sharing_group_id', $sharingGroupId); } if ($galaxiesAsTags) { $shellCommand[] = '--galaxies_as_tags'; + } else { + array_push($shell_command, '--cluster_distribution', $clusterDistribution); + if ($clusterDistribution == 4) { + array_push($shell_command, '--cluster_sharing_group_id', $clusterSharingGroupId); + } } if ($debug) { $shellCommand[] = '--debug'; diff --git a/app/Model/Note.php b/app/Model/Note.php index 9f41df961..481a3711c 100644 --- a/app/Model/Note.php +++ b/app/Model/Note.php @@ -15,8 +15,11 @@ class Note extends AnalystData public $current_type = 'Note'; public $current_type_id = 0; - public $validate = array( - ); + public const EDITABLE_FIELDS = [ + 'note', + ]; + + public $validate = []; public function beforeValidate($options = array()) { diff --git a/app/Model/Opinion.php b/app/Model/Opinion.php index 33b65f29c..8a7c589d2 100644 --- a/app/Model/Opinion.php +++ b/app/Model/Opinion.php @@ -15,8 +15,12 @@ class Opinion extends AnalystData public $current_type = 'Opinion'; public $current_type_id = 1; - public $validate = array( - ); + public const EDITABLE_FIELDS = [ + 'opinion', + 'comment', + ]; + + public $validate = []; public function beforeValidate($options = array()) { diff --git a/app/Model/Relationship.php b/app/Model/Relationship.php index ea73cd777..b469e24a5 100644 --- a/app/Model/Relationship.php +++ b/app/Model/Relationship.php @@ -15,8 +15,11 @@ class Relationship extends AnalystData public $current_type = 'Relationship'; public $current_type_id = 2; - public $validate = array( - ); + protected $EDITABLE_FIELDS = [ + 'relationship_type', + ]; + + public $validate = []; /** @var object|null */ protected $Event; diff --git a/app/Plugin/AadAuth/README.md b/app/Plugin/AadAuth/README.md index d5e9447eb..07e49e486 100755 --- a/app/Plugin/AadAuth/README.md +++ b/app/Plugin/AadAuth/README.md @@ -1,16 +1,16 @@ -# Configure Azure AD to use SIngle SignOn for MISP +# Configure Azure AD to use Single Sign-On (SSO) for MISP -This plugin enables authentication with an Azure Active Directory server. Under the hood it uses oAuth2. There are still a number of rough edges but in general the plugin works. +This plugin enables authentication with an Azure Active Directory (now called [Entra-ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id)) server. Under the hood it uses oAuth2. There are still a number of rough edges but in general the plugin works. It supports verification if a user has the proper ‘MISP AD’ groups. Users should already exist in MISP. Future enhancement could include auto-create users ## Azure AD — App Registration Configuration -In Azure, add a new App Registration. Select Web and set the Redirect URI to your MISP server login page `https://misp.yourdomain.com/users/login`. The MISP instance does not need to be publicly accessible if it is reachable by your browser. The redirect URI that you specify here must be the same as used in the MISP configuration. +In Azure, add a new App Registration. Select Web and set the Redirect URI to your MISP server login page `https://misp.yourdomain.com/users/login`. The MISP instance does not need to be publicly accessible if it is reachable by your browser. The redirect URI that you specify here must be the same as used in the MISP configuration (including `/users/login`). You can add as many redirect URIs as needed, meaning you can have multiple MISP servers use the same Azure App. ![AppReg Configuration](.images/Picture29.png) -On the Overview page of the new MISP App Registration capture the following inforamtion. +On the Overview page of the new MISP App Registration capture the following information. - [x] Application (client) ID - [x] Directory (tenant) ID @@ -44,7 +44,7 @@ Create the following groups in Azure AD, these can be called anything you like f Make a name of your groups, we'll need these later. - [x] Misp Users -- [x] Misp ORG Admins +- [x] Misp Org Admins - [x] Misp Site Admins ## Enable the AAD Plugin for MISP @@ -122,7 +122,7 @@ Scroll down to near the bottom of the page and add in the following configuratio ), ``` -Add the information we made a note of earlier when creating the `App Registation` and optionally the Azure AD groups you created. +Add the information we made a note of earlier when creating the `App Registration` and optionally the Azure AD groups you created. ![AadAuth.configuration](.images/Picture38.png) @@ -139,4 +139,12 @@ Additionally, it is recommended to set the following settings in the MISP config * `MISP.disable_user_login_change => true`: Removes the ability of users to change their username (email), except for site admins. * `MISP.disable_user_password_change => true`: Removes the ability of users to change their own password. -This way users will not be able to change their passwords and by-pass the AAD login flow. \ No newline at end of file +This way users will not be able to change their passwords and by-pass the AAD login flow. + +# Create users via the MISP REST API + +Because users already need to exist in MISP before they can authenticate with AAD it can be useful to provision them in an automated fashion. This can be done by creating the users via the MISP REST API. The below `curl` command provides an example on how to do this. Note that you need an API key. + +``` +curl -k -d '{"email":"newuser@mycompany.com", "role_id":"3", "org_id":"1", "enable_password":"1", "change_pw":"0"}' -H "Authorization: API_KEY" -H "Accept: application/json" -H "Content-type: application/json" -X POST htps://misp.mycompany.com/admin/users/add +``` diff --git a/app/View/AnalystData/add.ctp b/app/View/AnalystData/add.ctp index de7ae5437..9d6eaf389 100644 --- a/app/View/AnalystData/add.ctp +++ b/app/View/AnalystData/add.ctp @@ -89,10 +89,12 @@ if ($modelSelection === 'Note') { 'options' => $dropdownData['valid_targets'], 'type' => 'dropdown', 'stayInLine' => 1, + 'disabled' => !empty($this->data[$modelSelection]['related_object_type']), ], [ 'field' => 'related_object_uuid', 'class' => 'span4', + 'disabled' => !empty($this->data[$modelSelection]['related_object_uuid']), ], sprintf('
- Html->image('custom/' . h(Configure::read('MISP.welcome_logo')), array('alt' => __('Logo'), 'onerror' => "this.style.display='none';")); ?> + + + |
@@ -16,8 +18,8 @@
?>
-
-
+
+
@@ -82,7 +84,9 @@
?>
|
- Html->image('custom/' . h(Configure::read('MISP.welcome_logo2')), array('alt' => 'Logo2', 'onerror' => "this.style.display='none';")); ?> + + + |