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

pull/9594/head
Christian Studer 2024-02-23 22:14:10 +01:00
commit 71d1d5fc4a
No known key found for this signature in database
GPG Key ID: 6BBED1B63A6D639F
24 changed files with 297 additions and 96 deletions

View File

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

View File

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

View File

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

View File

@ -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.");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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('<div style="max-width: 960px;"><label>%s:</label><div id="related-object-container">%s</div></div>', __('Related Object'), __('- No UUID provided -'))
]

View File

@ -43,7 +43,7 @@ $relationshipsCount = count($relationships);
<?php if (empty($notesOpinions) && empty($relationshipsCount)): ?>
<i class="<?= $this->FontAwesome->getClass('sticky-note') ?> useCursorPointer node-opener-<?= $seed ?>" title="<?= __('Notes and opinions for this UUID') ?>"></i>
<?php else: ?>
<span class="label label-info useCursorPointer node-opener-<?= $seed ?>">
<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>

View File

@ -89,9 +89,9 @@ function getURLFromRelationship(note) {
if (note.related_object_type == 'Event') {
return baseurl + '/events/view/' + note.related_object_uuid
} else if (note.related_object_type == 'Attribute') {
return baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid
return note?.attribute?.event_id ? baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid : '#'
} else if (note.related_object_type == 'Object') {
return baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid
return note?.object?.event_id ? baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid : '#'
}
return '#'
}
@ -100,9 +100,9 @@ function renderRelationshipEntryFromType(note, relationship_related_object) {
var contentHtml = ''
var template = doT.template('\
<span style="border: 1px solid #ddd !important; border-radius: 3px; padding: 0.25rem;"> \
<span class="ellipsis-overflow" style="max-width: 12em;">{{=it.related_object_type}}</span> \
<span class="ellipsis-overflow" style="max-width: 12em;">{{!it.related_object_type}}</span> \
:: \
<span class="ellipsis-overflow" style="max-width: 12em;">{{=it.related_object_uuid}}</span> \
<span class="ellipsis-overflow" style="max-width: 12em;">{{!it.related_object_uuid}}</span> \
</span> \
')
var templateEvent = doT.template('\
@ -110,16 +110,16 @@ function renderRelationshipEntryFromType(note, relationship_related_object) {
<span class="bold"> \
<span class="attr-type"><span><i class="<?= $this->FontAwesome->getClass('envelope') ?>"></i></span></span> \
<span class=""><span class="attr-value"> \
<span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{=it.urlEvent}}" target="_blank">{{=it.content}}</a></span> \
<span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.urlEvent}}" target="_blank">{{!it.content}}</a></span> \
</span></span> \
</span> \
</span> \
')
if (note.related_object_type == 'Event' && relationship_related_object.Event[note.related_object_uuid]) {
note.event = relationship_related_object.Event[note.related_object_uuid]
template = doT.template(templateEvent({content: '{{=it.event.info}}', urlEvent: '{{=it.url}}'}))
template = doT.template(templateEvent({content: '{{!it.event.info}}', urlEvent: '{{!it.url}}'}))
} else if (note.related_object_type == 'Attribute' && relationship_related_object.Attribute[note.related_object_uuid]) {
var event = templateEvent({content: '{{=it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{=it.attribute.event_id}}'})
var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'})
note.attribute = relationship_related_object.Attribute[note.related_object_uuid]
if (note.attribute.object_relation !== undefined && note.attribute.object_relation !== null) {
template = doT.template('\
@ -128,27 +128,27 @@ function renderRelationshipEntryFromType(note, relationship_related_object) {
<span class="misp-element-wrapper object"> \
<span class="bold"> \
<span class="obj-type"> \
<span class="object-name" title="<?= __('Object') ?>">{{=it.attribute.Object.name}}</span> \
↦ <span class="object-attribute-type" title="<?= __('Object Relation') ?>">{{=it.attribute.object_relation}}</span> \
<span class="object-name" title="<?= __('Object') ?>">{{!it.attribute.Object.name}}</span> \
↦ <span class="object-attribute-type" title="<?= __('Object Relation') ?>">{{!it.attribute.object_relation}}</span> \
</span> \
<span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{=it.url}}" target="_blank">{{=it.attribute.value}}</a></span></span> \
<span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.attribute.value}}</a></span></span> \
</span> \
')
} else if (relationship_related_object.Attribute[note.related_object_uuid]) {
var event = templateEvent({content: '{{=it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{=it.attribute.event_id}}'})
var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'})
template = doT.template('\
' + event + ' \
<b>↦</b> \
<span class="misp-element-wrapper attribute"> \
<span class="bold"> \
<span class="attr-type"><span title="<?= __('Attribute') ?>">{{=it.attribute.type}}</span></span> \
<span class="blue"><span class="attr-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{=it.url}}" target="_blank">{{=it.attribute.value}}</a></span></span></span> \
<span class="attr-type"><span title="<?= __('Attribute') ?>">{{!it.attribute.type}}</span></span> \
<span class="blue"><span class="attr-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.attribute.value}}</a></span></span></span> \
</span> \
</span> \
')
}
} else if (note.related_object_type == 'Object') {
var event = templateEvent({content: '{{=it.object.Event.info}}', urlEvent: baseurl + '/events/view/{{=it.object.event_id}}'})
var event = templateEvent({content: '{{!it.object.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.object.event_id}}'})
note.object = relationship_related_object.Object[note.related_object_uuid]
template = doT.template('\
' + event + ' \
@ -157,9 +157,9 @@ function renderRelationshipEntryFromType(note, relationship_related_object) {
<span class="bold"> \
<span class="obj-type"> \
<i class="<?= $this->FontAwesome->getClass('cubes') ?>" title="<?= __('Object') ?>" style="margin: 0 0 0 0.25rem;"></i> \
<span>{{=it.object.name}}</span> \
<span>{{!it.object.name}}</span> \
</span> \
<span class="blue"><span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{=it.url}}" target="_blank">{{=it.object.id}}</a></span></span></span> \
<span class="blue"><span class="obj-value"><span class="ellipsis-overflow" style="max-width: 12em;"><a href="{{!it.url}}" target="_blank">{{!it.object.id}}</a></span></span></span> \
</span> \
</span> \
')
@ -178,40 +178,40 @@ var noteFilteringTemplate = '\
'
var baseNoteTemplate = doT.template('\
<div id="{{=it.note_type_name}}-{{=it.id}}" \
<div id="{{!it.note_type_name}}-{{!it.id}}" \
class="analyst-note" \
style="display: flex; flex-direction: row; align-items: center; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 1px 5px -2px rgb(0 0 0 / 0.5); border-radius: 0.25rem; padding: 0.25rem; margin-bottom: 0.0rem; background-color: #fff; transition: ease-out opacity 0.5s;" \
data-org-uuid="{{=it.orgc_uuid}}" \
data-org-uuid="{{!it.orgc_uuid}}" \
> \
<div style="flex-grow: 1;"> \
<div style="display: flex; flex-direction: column;"> \
<div style="display: flex; min-width: 250px; gap: 0.5rem;"> \
<img src="<?= $baseurl ?>/img/orgs/{{=it.Orgc.id}}.png" width="20" height="20" class="orgImg" style="width: 20px; height: 20px;" onerror="this.remove()" alt="Organisation logo"></object> \
<img src="<?= $baseurl ?>/img/orgs/{{!it.Orgc.id}}.png" width="20" height="20" class="orgImg" style="width: 20px; height: 20px;" onerror="this.remove()" alt="Organisation logo"></object> \
<span style="margin-left: 0rem; margin-right: 0.5rem;"> \
<span>{{=it.Orgc.name}}</span> \
<span>{{!it.Orgc.name}}</span> \
<i class="<?= $this->FontAwesome->getClass('angle-right') ?>" style="color: #999; margin: 0 0.25rem;"></i> \
<b>{{=it.authors}}</b> \
<b>{{!it.authors}}</b> \
</span> \
<span style="display: inline-block; font-weight: lighter; color: #999">{{=it.modified_relative}} • {{=it.modified}}</span> \
<span style="margin-left: 0.5rem; flex-grow: 1; text-align: right; color: {{=it.distribution_color}}"> \
<span style="display: inline-block; font-weight: lighter; color: #999">{{!it.modified_relative}} • {{!it.modified}}</span> \
<span style="margin-left: 0.5rem; flex-grow: 1; text-align: right; color: {{!it.distribution_color}}"> \
{{? it.distribution == 4 }} \
<a href="<?= $baseurl ?>/sharingGroups/view/{{=it.SharingGroup.id}}" target="_blank">{{=it.distribution_text}}</a> \
<a href="<?= $baseurl ?>/sharingGroups/view/{{!it.SharingGroup.id}}" target="_blank">{{!it.distribution_text}}</a> \
{{??}} \
{{=it.distribution_text}} \
{{!it.distribution_text}} \
{{?}} \
</span> \
<span class="action-button-container" style="margin-left: auto; display: flex; gap: 0.2rem;"> \
{{? 1 == <?= $me['Role']['perm_modify'] ? 1 : 0 ?> }} \
<span role="button" onclick="addOpinion(this, \'{{=it.uuid}}\', \'{{=it.note_type_name}}\')" title="<?= __('Add an opinion to this note') ?>"><i class="<?= $this->FontAwesome->getClass('gavel') ?> useCursorPointer"></i></span> \
<span role="button" onclick="addOpinion(this, \'{{!it.uuid}}\', \'{{!it.note_type_name}}\')" title="<?= __('Add an opinion to this note') ?>"><i class="<?= $this->FontAwesome->getClass('gavel') ?> useCursorPointer"></i></span> \
{{?}} \
{{? 1 == <?= $me['Role']['perm_modify'] ? 1 : 0 ?> }} \
<span role="button" onclick="addNote(this, \'{{=it.uuid}}\', \'{{=it.note_type_name}}\')" title="<?= __('Add a note to this note') ?>"><i class="<?= $this->FontAwesome->getClass('comment-alt') ?> useCursorPointer"></i></span> \
<span role="button" onclick="addNote(this, \'{{!it.uuid}}\', \'{{!it.note_type_name}}\')" title="<?= __('Add a note to this note') ?>"><i class="<?= $this->FontAwesome->getClass('comment-alt') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._canEdit }} \
<span role="button" onclick="editNote(this, {{=it.id}}, \'{{=it.note_type_name}}\')" title="<?= __('Edit this note') ?>"><i class="<?= $this->FontAwesome->getClass('edit') ?> useCursorPointer"></i></span> \
<span role="button" onclick="editNote(this, {{!it.id}}, \'{{!it.note_type_name}}\')" title="<?= __('Edit this note') ?>"><i class="<?= $this->FontAwesome->getClass('edit') ?> useCursorPointer"></i></span> \
{{?}} \
{{? it._canEdit }} \
<span role="button" onclick="deleteNote(this, {{=it.id}})" title="<?= __('Delete this note') ?>" href="<?= $baseurl . $URL_DELETE ?>{{=it.note_type_name}}/{{=it.id}}"><i class="<?= $this->FontAwesome->getClass('trash') ?> useCursorPointer"></i></span> \
<span role="button" onclick="deleteNote(this, {{!it.id}})" title="<?= __('Delete this note') ?>" href="<?= $baseurl . $URL_DELETE ?>{{!it.note_type_name}}/{{!it.id}}"><i class="<?= $this->FontAwesome->getClass('trash') ?> useCursorPointer"></i></span> \
{{?}} \
</span> \
</div> \
@ -222,7 +222,7 @@ var baseNoteTemplate = doT.template('\
')
var analystTemplate = doT.template('\
<div style="max-width: 40vw; margin-top: 0.5rem; font-size:"> \
{{=it.note}} \
{{!it.note}} \
</div> \
')
var opinionGradient = '\
@ -233,17 +233,17 @@ var opinionGradient = '\
</div> \
'
var opinionTemplate = doT.template('\
<div style="margin: 0.75rem 0 0.25rem 0; display: flex; flex-direction: row;" title="<?= __('Opinion:') ?> {{=it.opinion}} /100"> \
<div style="margin: 0.75rem 0 0.25rem 0; display: flex; flex-direction: row;" title="<?= __('Opinion:') ?> {{!it.opinion}} /100"> \
' + opinionGradient + ' \
<span style="line-height: 1em; margin-left: 0.25rem; margin-top: -3px;"> \
<b style="margin-left: 0.5rem; color: {{=it.opinion_color}}">{{=it.opinion_text}}</b> \
<b style="margin-left: 0.25rem; color: {{=it.opinion_color}}">{{=it.opinion}}</b> \
<b style="margin-left: 0.5rem; color: {{!it.opinion_color}}">{{!it.opinion_text}}</b> \
<b style="margin-left: 0.25rem; color: {{!it.opinion_color}}">{{!it.opinion}}</b> \
<span style="font-size: 0.7em; font-weight: lighter; color: #999">/100</span> \
</span> \
</div> \
{{? it.comment }} \
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
{{=it.comment}} \
{{!it.comment}} \
</div> \
{{?}} \
')
@ -253,17 +253,17 @@ var relationshipDefaultEntryTemplate = doT.template('\
<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}} \
{{!it.relationship_type}} \
{{??}} \
<i style="font-weight: lighter; color: #999;"> - empty -</i> \
{{?}} \
</span> \
<i class="<?= $this->FontAwesome->getClass('long-arrow-alt-right') ?>" style="font-size: 1.5em; color: #555"></i> \
<div style="margin-left: 0.5rem;">{{=it.content}}</div> \
<div style="margin-left: 0.5rem;">{{!it.content}}</div> \
</div> \
{{? it.comment }} \
<div style="max-width: 40vw; margin: 0.5rem 0 0 0.5rem; position: relative;" class="v-bar-text-opinion"> \
{{=it.comment}} \
{{!it.comment}} \
</div> \
{{?}} \
</div> \
@ -284,7 +284,7 @@ var maxDepthReachedTemplate = doT.template('\
<div> \
<span style="font-weight: lighter; color: #999;"> \
- Max depth reached, there is at least one entry remaining - \
<a href="<?= $baseurl ?>/analystData/view/{{=it.note.note_type_name}}/{{=it.note.id}}" target="_blank"> \
<a href="<?= $baseurl ?>/analystData/view/{{!it.note.note_type_name}}/{{!it.note.id}}" target="_blank"> \
<i class="<?= $this->FontAwesome->getClass('search') ?>"></i> \
<?= __('View entry') ?> \
</a> \
@ -292,7 +292,7 @@ var maxDepthReachedTemplate = doT.template('\
</div> \
<div> \
<span> \
<a onclick="fetchMoreNotes(this, \'{{=it.note.note_type_name}}\', \'{{=it.note.uuid}}\')" target="_blank" class="useCursorPointer"> \
<a onclick="fetchMoreNotes(this, \'{{!it.note.note_type_name}}\', \'{{!it.note.uuid}}\')" target="_blank" class="useCursorPointer"> \
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> \
<?= __('Load more notes') ?> \
</a> \

View File

@ -1,4 +1,9 @@
<?php
echo $this->element('genericElements/assetLoader', [
'js' => ['doT', 'moment.min'],
'css' => ['analyst-data',],
]);
$table_data = array();
$table_data[] = array('key' => __('ID'), 'value' => $report['EventReport']['id']);
$table_data[] = [

View File

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

View File

@ -1,4 +1,9 @@
<?php
echo $this->element('genericElements/assetLoader', [
'js' => ['doT', 'moment.min'],
'css' => ['analyst-data',],
]);
$extendedFromHtml = '';
if (!empty($cluster['GalaxyCluster']['extended_from'])) {
$element = $this->element('genericElements/IndexTable/Fields/links', array(
@ -174,10 +179,6 @@ $options = [
'relationships' => $cluster['GalaxyCluster']['Relationship'] ?? [],
];
echo $this->element('genericElements/assetLoader', [
'js' => ['doT', 'moment.min'],
'css' => ['analyst-data',],
]);
echo $this->element('genericElements/Analyst_data/thread', $options);
?>

View File

@ -27,8 +27,15 @@ class ImageHelper extends AppHelper
throw new InvalidArgumentException("Only SVG and PNG images are supported");
}
$fileContent = base64_encode(FileAccessTool::readFromFile($imagePath));
$base64 = "data:$mime;base64,$fileContent";
try {
$fileContent = FileAccessTool::readFromFile($imagePath);
} catch (Exception $e) {
CakeLog::warning($e);
return 'data:null'; // in case file doesn't exists or is not readable
}
$fileContentEncoded = base64_encode($fileContent);
$base64 = "data:$mime;base64,$fileContentEncoded";
return $this->imageCache[$imagePath] = $base64;
}

View File

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

View File

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

@ -1 +1 @@
Subproject commit b8b8b7445754ea3cbc84e2d0b434ecd08740ef95
Subproject commit dd3037ee7f31c1f43a3ad3aaaa6cccfa232dc530

View File

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

View File

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

View File

@ -10547,5 +10547,5 @@
"uuid": false
}
},
"db_version": "122"
"db_version": "123"
}