From 570dc7e91ebcd0f5a901720dab90a2c97745173b Mon Sep 17 00:00:00 2001
From: Alexandre Dulaunoy
Date: Sun, 23 Apr 2023 17:47:43 +0200
Subject: [PATCH 01/22] chg: [misp-galaxy] updated to the latest version
---
app/files/misp-galaxy | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/files/misp-galaxy b/app/files/misp-galaxy
index ccc8f0f80..3c6c204f0 160000
--- a/app/files/misp-galaxy
+++ b/app/files/misp-galaxy
@@ -1 +1 @@
-Subproject commit ccc8f0f8018fece44f3afd18375894b16948da88
+Subproject commit 3c6c204f01682a27b3b573f7213bb3d3d9f284be
From 7327539a682792af285a70c556af3c1e7ead3c77 Mon Sep 17 00:00:00 2001
From: Alexandre Dulaunoy
Date: Mon, 24 Apr 2023 09:02:37 +0200
Subject: [PATCH 02/22] chg: [misp-galaxy] updated
---
app/files/misp-galaxy | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/files/misp-galaxy b/app/files/misp-galaxy
index 3c6c204f0..79b80b086 160000
--- a/app/files/misp-galaxy
+++ b/app/files/misp-galaxy
@@ -1 +1 @@
-Subproject commit 3c6c204f01682a27b3b573f7213bb3d3d9f284be
+Subproject commit 79b80b0869e57dc14861c0c30792f64a243ecb3b
From c3f7077abe111154017b52fac504f293ea41a471 Mon Sep 17 00:00:00 2001
From: Sascha Rommelfangen
Date: Mon, 24 Apr 2023 14:07:19 +0200
Subject: [PATCH 03/22] removed cogsec, domain not renewed
---
app/files/community-metadata/defaults.json | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/app/files/community-metadata/defaults.json b/app/files/community-metadata/defaults.json
index 67ab31d65..6e800b672 100644
--- a/app/files/community-metadata/defaults.json
+++ b/app/files/community-metadata/defaults.json
@@ -78,21 +78,6 @@
"misp_project_vetted": true,
"scope_of_data_to_be_shared": "Cybersecurity Threat Intelligence including indicators, threat intelligence information, reports, contextual threat actor information or financial fraud information."
},
- {
- "name": "Cognitive Security Collaborative",
- "uuid": "1ea46a83-cd51-40f5-a375-104e0acd6729",
- "org_uuid": "5e2dd31a-3bcc-45e8-ba7e-2ab890d945c8",
- "org_name": "Cogsec Collab",
- "description": "The Cognitive Security Collaborative operates as a sharing community dedicated to information operations.",
- "url": "https://www.cogsec-collab.org",
- "sector": "undefined",
- "nationality": "International",
- "type": "Vetted Information Sharing Community",
- "email": "misp@cogsec-collab.org",
- "pgp_key": "\r\n\r\n-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nmQGNBF55bdcBDAC6+Fcey+0GcUw4iP4j15+/FylnvGa4wl8MRkYR5XryJn+n/O4s\r\nZbNCKpxwUA7lb2prn37lWMX7LswjvoxfmCTKi78UY1YH7Fqg3JG2PsV9Lw7uYnzC\r\nAImyAflzDpewo+eCF1aknvcbcbGkYFwdQ/37UfG/BkwCDQQGrBZ5EtL6CYXXNX/P\r\nX+4vYv23AVuchHvxeyW2dPLL3A6t3Mx8pZQBdN1cGZ1QAtE9IN0Yn2y+rMsNpDG4\r\ncOQ6bRqmue2I8JEB4AsQcufcqx69imBvBERsIZEyGZekLjmiuqDKI9Gti2VKZe/t\r\nxdl++gjplq6OAkdzXDGsMNtwxSk21IBrugAXK6K+4RPiMrPpBh81VGzBe2PRKUwT\r\nAZi06KZdaZudehvzIMLsNP5Aeep4+GXxoZ7Yrka/08SIv7SN5XY4o6xkli658Z+l\r\n8WAj2JiI684D/TK5MlvcBDQk1yKdDI2iC4eTFLkJ2PiDToUDT+vACrcnevstU+c8\r\nrNPFbvbB1DUIIo8AEQEAAbQ5Q29nbml0aXZlIFNlY3VyaXR5IENvbGxhYm9yYXRp\r\ndmUgPG1pc3BAY29nc2VjLWNvbGxhYi5vcmc+iQHUBBMBCAA+FiEEm65FjZ6Jbfp9\r\nCN50hA2Itf18R2cFAl55bdcCGwMFCQlmAYAFCwkIBwIGFQoJCAsCBBYCAwECHgEC\r\nF4AACgkQhA2Itf18R2e/ewv7BuCpmNIR0YOJld8RqrS4g5MV6eKJUuTRYUOxDyw9\r\nvgdpdvM1FgHPZ7pJcsijKQ+S+dL7ADmEbsCLWe1UhcwbnVRxJ0T+1yxRf6ONQA0/\r\ntRLmrcF4j6JCkl01irWRnYxMI1w1ABOQj4/J7BcTCzbYUdnxSuWhcZBqcsYIHf8J\r\nHnfbVd7OIML/80IRZbRXn1ST6OeXK9RpzqO7bnfPGnd506dt8sfHCWRidUSv2max\r\nrsi9xSyXeSKSNPQFVBgYnMVwBVUGIaWTnt7Ly4I8Bs5P9NWUpLYrRgYLMbDzLWaD\r\nxX7qNQjAKkNCx9k7qQN0Ck9YqeUIuJQPq2doGuLKnqjJBXizsXbAFqcKitQz7WV2\r\nPUsN/QUguVyZbhy7oJELlWDiDWxS6EwpU+q0SODHjCFKoUXvWFkk9bz1K4/kLDFO\r\nOdTABp7i65nJst5b3pVXimoTKqW7JRyCUWz3aaaqjWSTPKP2GmQbxOwM86rgmnGX\r\nqq8Ces6LQw6zGw08ubDDotEKuQGNBF55bdcBDACbmsVMV7azLYys6iMXTLVERasT\r\nUnw8FpKADA2uDgQme5o3CjeFtBBkgBNe8zdOEEslggETVmntp4n6woQzOknDHNx/\r\nVMliUaGuIYgmC8hTDTF269fdRTpKMrcwu2aBEUpHpG7Xvz91HIr213FTwU0LLq0g\r\n+DefSlwdcMPJiCUqshLw8q/D3qVg/VYVen5li55RQBBFLgYYNgag3WnSejE41uqz\r\nvt40FZ4C88Pj0I3f+PRtfHHeXTZehUjs3+W4jn1fLWNmbIScmIhwp/Vqh8R7JHf2\r\n69UGgWr4cOaLGh6C2Io+TVJ+Sq7TMt47qB6eO53Vr2nyizXTxjrmAWqjw3OLc8QX\r\nWsjbpTMqUaPisnCpog/3SqnE4Fe2rQYkroQao6dRL3FrmgvnyhLgjUtjk6fAfx1+\r\nH6fQFH/JJGCNefG9AWo41Er3oHGoV0yqlI697uk0QGdx/848hc0gXLrus82bw+BI\r\nx36ycevxkpmfvzC8lew/vLEB7t/jqXH2H9Qqtm0AEQEAAYkBvAQYAQgAJhYhBJuu\r\nRY2eiW36fQjedIQNiLX9fEdnBQJeeW3XAhsMBQkJZgGAAAoJEIQNiLX9fEdnmYsM\r\nAJzX6MCYoGPED1VXMoPXVS9s7V7hv+0Q4SKcoUxqROwA0wb3NwvdnzO/WAQlzIIj\r\ny1Sk9VX8qZkATN7+nti8jfhKnlMVqAXFFg9fMsq68WlTzHiyGm06DnM2DXBvdLRT\r\nwbcm5H4Ly1/bCFww6Spbxo3zScrSCeRrIHHGOHEzr/vhcZavRDpFmdpTCD6ID7oG\r\nw5jR6GdSCpvBT6Lq7M2xe6cVw/A9z5tE3cIf75uikKfch8HFVV2l1B9XLJVpvhqv\r\nYf+kUa7l7VP893yyTyf9G6SSaS77VKlHxn+OQ9AX+wdgSpD5SgVkvRFXejXw8oIZ\r\nBeTNYTvYYgV75ApnvT+hyeirGDCRRiTiuva0ijd71PzTRk+5Ad80rav1Jy864dUt\r\nDcSklY5T+wjJf7kb/3nIE5vqO/3YkJxdDTvZM23T+IZsCvamQ5pyyp+bP3HTAZkr\r\no6oiGFXbv5OF6/wkUG6vQ5w1RCUQVLfrM6Dh675dx/sdI+p0JMt6BlvlRUJSofu0\r\nWw==\r\n=4aXp\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n",
- "misp_project_vetted": true,
- "scope_of_data_to_be_shared": "Information Operation Threat Intelligence including disinformation, indicators, threat intelligence information, reports, contextual threat actor information or financial fraud information."
- },
{
"name": "COVID-19 MISP community",
"uuid": "5e59659e-8e24-4e5d-b3fa-2ba744b7dd05",
From c442c27dd5e6a72c7eb020ec755d6c29fcb572ac Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:21:15 +0200
Subject: [PATCH 04/22] new: [setting] added a new setting to remove email
addresses from widgets that would otherwise display it
- anonymise the widgets on demand
---
app/Model/Server.php | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/app/Model/Server.php b/app/Model/Server.php
index cc850d4ca..b0d69eea5 100644
--- a/app/Model/Server.php
+++ b/app/Model/Server.php
@@ -6584,7 +6584,15 @@ class Server extends AppModel
'type' => 'boolean',
'null' => true,
'cli_only' => true
- ]
+ ],
+ 'disclose_user_emails' => array(
+ 'level' => 0,
+ 'description' => __('Enable this setting to allow for the user e-mail addresses to be shown to non site-admin users. Keep in mind that in broad communities this can be abused.'),
+ 'value' => false,
+ 'test' => 'testBool',
+ 'type' => 'boolean',
+ 'null' => true
+ ),
),
'SecureAuth' => array(
'branch' => 1,
From 984be50b759b16e7d2cd77595d8778a6e35103d6 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:22:26 +0200
Subject: [PATCH 05/22] fix: [trending tags widget] reworked
- added day based time_window option
- much more perforant / memory friendly
---
app/Lib/Dashboard/TrendingTagsWidget.php | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/app/Lib/Dashboard/TrendingTagsWidget.php b/app/Lib/Dashboard/TrendingTagsWidget.php
index cf50cf707..b3d6f6048 100644
--- a/app/Lib/Dashboard/TrendingTagsWidget.php
+++ b/app/Lib/Dashboard/TrendingTagsWidget.php
@@ -7,7 +7,7 @@ class TrendingTagsWidget
public $width = 3;
public $height = 4;
public $params = array(
- 'time_window' => 'The time window, going back in seconds, that should be included.',
+ 'time_window' => 'The time window, going back in seconds, that should be included. (allows for filtering by days - example: 5d. -1 Will fetch all historic data)',
'exclude' => 'List of substrings to exclude tags by - for example "sofacy" would exclude any tag containing sofacy.',
'include' => 'List of substrings to include tags by - for example "sofacy" would include any tag containing sofacy.',
'threshold' => 'Limits the number of displayed tags. Default: 10',
@@ -16,23 +16,26 @@ class TrendingTagsWidget
);
public $placeholder =
'{
- "time_window": "86400",
+ "time_window": "7d",
"threshold": 15,
"exclude": ["tlp:", "pap:"],
"include": ["misp-galaxy:", "my-internal-taxonomy"],
"filter_event_tags": ["misp-galaxy:threat-actor="APT 29"],
}';
public $description = 'Widget showing the trending tags over the past x seconds, along with the possibility to include/exclude tags.';
- public $cacheLifetime = 600;
+ public $cacheLifetime = 3;
public function handler($user, $options = array())
{
/** @var Event $eventModel */
$eventModel = ClassRegistry::init('Event');
$threshold = empty($options['threshold']) ? 10 : $options['threshold'];
- $params = [
- 'timestamp' => time() - (empty($options['time_window']) ? 8640000 : $options['time_window']),
- ];
+ $time_window = empty($options['time_window']) ? (7 * 24 * 60 * 60) : $options['time_window'];
+ if (is_string($time_window) && substr($time_window, -1) === 'd') {
+ $time_window = ((int)substr($time_window, 0, -1)) * 24 * 60 * 60;
+ }
+ $params = $time_window === -1 ? [] : ['timestamp' => time() - $time_window];
+
if (!empty($options['filter_event_tags'])) {
$params['event_tags'] = $options['filter_event_tags'];
}
@@ -48,6 +51,7 @@ class TrendingTagsWidget
$events = $eventModel->fetchEvent($user, [
'eventid' => $eventIds,
'order' => 'Event.timestamp',
+ 'metadata' => 1
]);
foreach ($events as $event) {
@@ -111,7 +115,6 @@ class TrendingTagsWidget
}
}
-
return $data;
}
From e5cd4155793ca5eec8eedcdc59fafc65aadea792 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:23:32 +0200
Subject: [PATCH 06/22] new: [usagedata widget] upgraded
- allows for filtering based on organisation metadata
- shows changes in current month
- fixed several invalid statistics
- moved all individual statistics to separate functions for readability
- removed permission restriction - the data is only showing aggregates
---
app/Lib/Dashboard/UsageDataWidget.php | 400 +++++++++++++++++++++++---
1 file changed, 359 insertions(+), 41 deletions(-)
diff --git a/app/Lib/Dashboard/UsageDataWidget.php b/app/Lib/Dashboard/UsageDataWidget.php
index caebbae05..37781039b 100644
--- a/app/Lib/Dashboard/UsageDataWidget.php
+++ b/app/Lib/Dashboard/UsageDataWidget.php
@@ -5,40 +5,82 @@ class UsageDataWidget
public $render = 'SimpleList';
public $width = 2;
public $height = 5;
- public $params = array();
public $description = 'Shows usage data / statistics.';
public $cacheLifetime = false;
public $autoRefreshDelay = 3;
+ public $params = [
+ 'filter' => 'A list of filters by organisation meta information (nationality, sector, type, name, uuid) to include. (dictionary, prepending values with ! uses them as a negation)',
+ ];
+ private $User = null;
+ private $Event = null;
+ private $Correlation = null;
+ private $Thread = null;
+ private $AuthKey = null;
+
+ private $validFilterKeys = [
+ 'nationality',
+ 'sector',
+ 'type',
+ 'name',
+ 'uuid'
+ ];
+
+ private $validFields = [
+ 'Events',
+ 'Attributes',
+ 'Attributes / event',
+ 'Correlations',
+ 'Active proposals',
+ 'Users',
+ 'Users with PGP keys',
+ 'Organisations',
+ 'Local organisations',
+ 'Event creator orgs',
+ 'Average users / org',
+ 'Discussion threads',
+ 'Discussion posts'
+ ];
public function handler($user, $options = array()){
$this->User = ClassRegistry::init('User');
-
- $orgsCount = $this->User->Organisation->find('count');
- $localOrgsParams['conditions']['Organisation.local'] = 1;
- $localOrgsCount = $this->User->Organisation->find('count', $localOrgsParams);
-
- $thisMonth = strtotime('first day of this month');
$this->Event = ClassRegistry::init('Event');
- $eventsCount = $this->Event->find('count', array('recursive' => -1));
- $eventsCountMonth = $this->Event->find('count', array('conditions' => array('Event.timestamp >' => $thisMonth), 'recursive' => -1));
-
- $this->Attribute = ClassRegistry::init('Attribute');
- $attributesCount = $this->Attribute->find('count', array('conditions' => array('Attribute.deleted' => 0), 'recursive' => -1));
- $attributesCountMonth = $this->Attribute->find('count', array('conditions' => array('Attribute.timestamp >' => $thisMonth, 'Attribute.deleted' => 0), 'recursive' => -1));
- $attributesPerEvent = round($attributesCount / $eventsCount);
-
- $this->Correlation = ClassRegistry::init('Correlation');
- $correlationsCount = $this->Correlation->find('count', array('recursive' => -1)) / 2;
-
- $proposalsCount = $this->Event->ShadowAttribute->find('count', array('recursive' => -1, 'conditions' => array('deleted' => 0)));
-
- $usersCount = $this->User->find('count', array('recursive' => -1));
- $usersCountPgp = $this->User->find('count', array('recursive' => -1, 'conditions' => array('User.gpgkey !=' => '')));
- $usersCountPgpPercentage = round(100* ($usersCountPgp / $usersCount), 1);
- $contributingOrgsCount = $this->Event->find('count', array('recursive' => -1, 'group' => array('Event.orgc_id')));
- $averageUsersPerOrg = round($usersCount / $localOrgsCount, 1);
-
$this->Thread = ClassRegistry::init('Thread');
+ $this->Correlation = ClassRegistry::init('Correlation');
+ $thisMonth = strtotime('first day of this month');
+ $orgConditions = [];
+ $orgIdList = null;
+ if (!empty($options['filter']) && is_array($options['filter'])) {
+ foreach ($this->validFilterKeys as $filterKey) {
+ if (!empty($options['filter'][$filterKey])) {
+ if (!is_array($options['filter'][$filterKey])) {
+ $options['filter'][$filterKey] = [$options['filter'][$filterKey]];
+ }
+ $tempConditionBucket = [];
+ foreach ($options['filter'][$filterKey] as $value) {
+ if ($value[0] === '!') {
+ $tempConditionBucket['Organisation.' . $filterKey . ' NOT IN'][] = mb_substr($value, 1);
+ } else {
+ $tempConditionBucket['Organisation.' . $filterKey . ' IN'][] = $value;
+ }
+ }
+ if (!empty($tempConditionBucket)) {
+ $orgConditions[] = $tempConditionBucket;
+ }
+ }
+ }
+ $orgIdList = $this->User->Organisation->find('column', [
+ 'recursive' => -1,
+ 'conditions' => $orgConditions,
+ 'fields' => ['Organisation.id']
+ ]);
+ }
+ $eventsCount = $this->getEventsCount($orgConditions, $orgIdList, $thisMonth);
+ $attributesCount = $this->getAttributesCount($orgConditions, $orgIdList, $thisMonth);
+ $usersCount = $this->getUsersCount($orgConditions, $orgIdList, $thisMonth);
+ $usersCountPgp = $this->getUsersCountPgp($orgConditions, $orgIdList, $thisMonth);
+ $localOrgsCount = $this->getLocalOrgsCount($orgConditions, $orgIdList, $thisMonth);
+
+
$threadCount = $this->Thread->find('count', array('conditions' => array('Thread.post_count >' => 0), 'recursive' => -1));
$threadCountMonth = $this->Thread->find('count', array('conditions' => array('Thread.date_created >' => date("Y-m-d H:i:s", $thisMonth), 'Thread.post_count >' => 0), 'recursive' => -1));
@@ -47,21 +89,69 @@ class UsageDataWidget
//Monhtly data is not added to the widget at the moment, could optionally add these later and give user choice?
- $statistics = array(
- array('title' => 'Events', 'value' => $eventsCount),
- array('title' => 'Attributes', 'value' => $attributesCount),
- array('title' => 'Attributes / event', 'value' => $attributesPerEvent),
- array('title' => 'Correlations', 'value' => $correlationsCount),
- array('title' => 'Active proposals', 'value' => $proposalsCount),
- array('title' => 'Users', 'value' => $usersCount),
- array('title' => 'Users with PGP keys', 'value' => $usersCountPgp . ' (' . $usersCountPgpPercentage . '%)'),
- array('title' => 'Organisations', 'value' => $orgsCount),
- array('title' => 'Local organisations', 'value' => $localOrgsCount),
- array('title' => 'Event creator orgs', 'value' => $contributingOrgsCount),
- array('title' => 'Average users / org', 'value' => $averageUsersPerOrg),
- array('title' => 'Discussions threads', 'value' => $threadCount),
- array('title' => 'Discussion posts', 'value' => $postCount)
- );
+ $statistics = [
+ 'Events' => [
+ 'title' => 'Events',
+ 'value' => $eventsCount,
+ 'change' => $this->getEventsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Attributes' => [
+ 'title' => 'Attributes',
+ 'value' => $attributesCount,
+ 'change' => $this->getAttributesCountMonth($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Attributes / event' => [
+ 'title' => 'Attributes / event',
+ 'value' => $eventsCount ? round($attributesCount / $eventsCount) : 0
+ ],
+ 'Correlations' => [
+ 'title' => 'Correlations',
+ 'value' => $this->getCorrelationsCount($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Active proposals' => [
+ 'title' => 'Active proposals',
+ 'value' => $this->getProposalsCount($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Users' => [
+ 'title' => 'Users',
+ 'value' => $usersCount,
+ 'change' => $this->getUsersCountMonth($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Users with PGP keys' => [
+ 'title' => 'Users with PGP keys',
+ 'value' => sprintf(
+ '%s (%s %%)',
+ $usersCountPgp,
+ $usersCount ? round(100* ($usersCountPgp / $usersCount), 1) : 0
+ )
+ ],
+ 'Organisations' => [
+ 'title' => 'Organisations',
+ 'value' => $this->getOrgsCount($orgConditions, $orgIdList, $thisMonth),
+ 'change' => $this->getOrgsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Local organisations' => [
+ 'title' => 'Local organisations',
+ 'value' => $localOrgsCount,
+ 'change' => $this->getLocalOrgsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Event creator orgs' => [
+ 'title' => 'Event creator orgs', 'value' => $this->getContributingOrgsCount($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Average users / org' => [
+ 'title' => 'Average users / org', 'value' => round($usersCount / $localOrgsCount, 1)
+ ],
+ 'Discussion threads' => [
+ 'title' => 'Discussions threads',
+ 'value' => $this->getThreadsCount($orgConditions, $orgIdList, $thisMonth),
+ 'change' => $this->getThreadsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ ],
+ 'Discussion posts' => [
+ 'title' => 'Discussion posts',
+ 'value' => $this->getPostsCount($orgConditions, $orgIdList, $thisMonth),
+ 'change' => $this->getPostsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ ]
+ ];
if(!empty(Configure::read('Security.advanced_authkeys'))){
$this->AuthKey = ClassRegistry::init('AuthKey');
$authkeysCount = $this->AuthKey->find('count', array('recursive' => -1));
@@ -70,6 +160,233 @@ class UsageDataWidget
return $statistics;
}
+ private function getEventsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = [];
+ if (!empty($orgIdList)) {
+ $conditions['AND'][] = ['Event.orgc_id IN' => $orgIdList];
+ }
+ return $this->Event->find('count', [
+ 'recursive' => -1,
+ 'conditions' => $conditions
+ ]);
+ }
+
+ private function getCorrelationsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = [];
+ if (!empty($orgIdList)) {
+ $conditions['AND']['OR'][] = ['Correlation.org_id IN' => $orgIdList];
+ $conditions['AND']['OR'][] = ['Correlation.1_org_id IN' => $orgIdList];
+ }
+ return $this->Correlation->find('count', [
+ 'recursive' => -1,
+ 'conditions' => $conditions
+ ]);
+ }
+
+ private function getEventsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = ['Event.timestamp >' => $thisMonth];
+ if (!empty($orgIdList)) {
+ $conditions['AND'][] = ['Event.orgc_id IN' => $orgIdList];
+ }
+ return $this->Event->find('count', [
+ 'conditions' => $conditions,
+ 'recursive' => -1
+ ]);
+ }
+
+ private function getAttributesCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = ['Attribute.deleted' => 0];
+ if (!empty($orgIdList)) {
+ $conditions['AND'][] = ['Event.orgc_id IN' => $orgIdList];
+ }
+ return $this->Event->Attribute->find('count', [
+ 'conditions' => $conditions,
+ 'contain' => ['Event'],
+ 'recursive' => -1
+ ]);
+ }
+
+ private function getAttributesCountMonth($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = ['Attribute.timestamp >' => $thisMonth, 'Attribute.deleted' => 0];
+ if (!empty($orgIdList)) {
+ $conditions['AND'][] = ['Event.orgc_id IN' => $orgIdList];
+ }
+ return $this->Event->Attribute->find('count', [
+ 'conditions' => $conditions,
+ 'contain' => 'Event.orgc_id',
+ 'recursive' => -1
+ ]);
+ }
+
+ private function getOrgsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ return $this->User->Organisation->find('count', [
+ 'conditions' => [
+ 'AND' => $orgConditions
+ ]
+ ]);
+ }
+
+ private function getOrgsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ {
+ $datetime = new DateTime();
+ $datetime->setTimestamp($thisMonth);
+ $thisMonth = $datetime->format('Y-m-d H:i:s');
+ return $this->User->Organisation->find('count', [
+ 'conditions' => [
+ 'AND' => $orgConditions,
+ 'Organisation.date_created >' => $thisMonth
+ ]
+ ]);
+ }
+
+ private function getLocalOrgsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ return $this->User->Organisation->find('count', [
+ 'conditions' => [
+ 'Organisation.local' => 1,
+ 'AND' => $orgConditions
+ ]
+ ]);
+ }
+
+ private function getLocalOrgsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ {
+ $datetime = new DateTime();
+ $datetime->setTimestamp($thisMonth);
+ $thisMonth = $datetime->format('Y-m-d H:i:s');
+ return $this->User->Organisation->find('count', [
+ 'conditions' => [
+ 'Organisation.local' => 1,
+ 'AND' => $orgConditions,
+ 'Organisation.date_created >' => $thisMonth
+ ]
+ ]);
+ }
+
+ private function getProposalsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = ['deleted' => 0];
+ if (!empty($orgIdList)) {
+ $conditions['ShadowAttribute.org_id IN'] = $orgIdList;
+ }
+ return $this->Event->ShadowAttribute->find('count', [
+ 'recursive' => -1,
+ 'conditions' => $conditions
+ ]);
+ }
+
+ private function getUsersCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = [];
+ if (!empty($orgIdList)) {
+ $conditions['User.org_id IN'] = $orgIdList;
+ }
+ return $this->User->find('count', [
+ 'recursive' => -1,
+ 'conditions' => $conditions
+ ]);
+ }
+
+ private function getUsersCountMonth($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = ['User.date_created >' => $thisMonth];
+ if (!empty($orgIdList)) {
+ $conditions['User.org_id IN'] = $orgIdList;
+ }
+ return $this->User->find('count', [
+ 'recursive' => -1,
+ 'conditions' => $conditions
+ ]);
+ }
+
+ private function getUsersCountPgp($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = ['User.gpgkey !=' => ''];
+ if (!empty($orgIdList)) {
+ $conditions['User.org_id IN'] = $orgIdList;
+ }
+ return $this->User->find('count', [
+ 'recursive' => -1,
+ 'conditions' => $conditions
+ ]);
+ }
+
+ private function getContributingOrgsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = [];
+ if ($orgConditions) {
+ $conditions['AND'][] = ['Event.orgc_id IN' => $orgIdList];
+ }
+ return $this->Event->find('count', [
+ 'recursive' => -1,
+ 'group' => ['Event.orgc_id'],
+ 'conditions' => $conditions
+ ]);
+ }
+
+ private function getThreadsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = ['Thread.post_count >' => 0];
+ if ($orgConditions) {
+ $conditions['AND'][] = ['Thread.org_id IN' => $orgIdList];
+ }
+ return $this->Thread->find('count', [
+ 'conditions' => $conditions,
+ 'recursive' => -1
+ ]);
+ }
+
+ private function getThreadsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = [
+ 'Thread.post_count >' => 0,
+ 'Thread.date_created >=' => $thisMonth
+ ];
+ if ($orgConditions) {
+ $conditions['AND'][] = ['Thread.org_id IN' => $orgIdList];
+ }
+ return $this->Thread->find('count', [
+ 'conditions' => $conditions,
+ 'recursive' => -1
+ ]);
+ }
+
+ private function getPostsCount($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = [];
+ if ($orgConditions) {
+ $conditions['AND'][] = ['User.org_id IN' => $orgIdList];
+ }
+ return $this->Thread->Post->find('count', [
+ 'conditions' => $conditions,
+ 'contain' => ['User.org_id'],
+ 'recursive' => -1
+ ]);
+ }
+
+ private function getPostsCountMonth($orgConditions, $orgIdList, $thisMonth)
+ {
+ $conditions = [
+ 'Post.date_created >=' => $thisMonth
+ ];
+ if ($orgConditions) {
+ $conditions['AND'][] = ['User.org_id IN' => $orgIdList];
+ }
+ return $this->Thread->Post->find('count', [
+ 'conditions' => $conditions,
+ 'contain' => ['User.org_id'],
+ 'recursive' => -1
+ ]);
+ }
+
+
+/* There is nothing sensitive in here.
public function checkPermissions($user)
{
if (empty($user['Role']['perm_site_admin'])) {
@@ -77,4 +394,5 @@ class UsageDataWidget
}
return true;
}
+*/
}
From 4578232ed147bcf2c6780d6bec9729e70e6bd364 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:26:29 +0200
Subject: [PATCH 07/22] chg: [widget UI] various improvements
---
app/View/Elements/dashboard/Widgets/BarChart.ctp | 9 +++++++--
app/View/Elements/dashboard/Widgets/Index.ctp | 2 +-
app/View/Elements/dashboard/Widgets/SimpleList.ctp | 14 ++++++++++++--
3 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/app/View/Elements/dashboard/Widgets/BarChart.ctp b/app/View/Elements/dashboard/Widgets/BarChart.ctp
index 80e73351e..a2748fde3 100644
--- a/app/View/Elements/dashboard/Widgets/BarChart.ctp
+++ b/app/View/Elements/dashboard/Widgets/BarChart.ctp
@@ -15,10 +15,15 @@
if (!empty($data['logarithmic'])) {
$value = $data['logarithmic'][$entry];
}
+ $shortlabel = $entry;
+ if (mb_strlen($shortlabel) > 30) {
+ $shortlabel = mb_substr($shortlabel, 0, 30) . '...';
+ }
echo sprintf(
- '%s | %s |
',
- 'text-align:right;width:33%;white-space:nowrap;',
+ '%s | %s |
',
+ 'text-align:right;width:35em;white-space:nowrap;',
h($entry),
+ h($shortlabel),
'width:100%',
sprintf(
'%s%s
',
diff --git a/app/View/Elements/dashboard/Widgets/Index.ctp b/app/View/Elements/dashboard/Widgets/Index.ctp
index d7a432423..dedb30890 100644
--- a/app/View/Elements/dashboard/Widgets/Index.ctp
+++ b/app/View/Elements/dashboard/Widgets/Index.ctp
@@ -2,10 +2,10 @@
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'data' => $data['data'],
+ 'description' => empty($data['description']) ? false : $data['description'],
'top_bar' => [],
'fields' => $data['fields'],
'title' => false,
- 'description' => false,
'pull' => 'right',
'skip_pagination' => true,
'actions' => []
diff --git a/app/View/Elements/dashboard/Widgets/SimpleList.ctp b/app/View/Elements/dashboard/Widgets/SimpleList.ctp
index 7a44710f3..525f8ebdc 100644
--- a/app/View/Elements/dashboard/Widgets/SimpleList.ctp
+++ b/app/View/Elements/dashboard/Widgets/SimpleList.ctp
@@ -18,12 +18,22 @@
$element['value'] = h($element['value']);
}
}
+ $change = '';
+ if (!empty($element['change'])) {
+ $change = (int)$element['change'];
+ if ($change > 0) {
+ $change = ' (+' . $change . ')';
+ } else {
+ $change = ' (-' . $change . ')';
+ }
+ }
echo sprintf(
- '%s: %s%s
',
+ '%s: %s%s%s
',
h($element['title']),
empty($element['class']) ? 'blue' : h($element['class']),
!isset($element['value']) ? '' : $element['value'],
- empty($element['html']) ? '' : $element['html']
+ empty($element['html']) ? '' : $element['html'],
+ $change
);
}
}
From 96652cc7819fc011bc2cd08c19989d96fdb460ad Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:27:19 +0200
Subject: [PATCH 08/22] new: [widgets] Widget to list latest joined orgs
- filter by org metadata / timeframe
---
app/Lib/Dashboard/NewOrgsWidget.php | 157 ++++++++++++++++++++++++++++
1 file changed, 157 insertions(+)
create mode 100644 app/Lib/Dashboard/NewOrgsWidget.php
diff --git a/app/Lib/Dashboard/NewOrgsWidget.php b/app/Lib/Dashboard/NewOrgsWidget.php
new file mode 100644
index 000000000..881796d9c
--- /dev/null
+++ b/app/Lib/Dashboard/NewOrgsWidget.php
@@ -0,0 +1,157 @@
+ 'Maximum number of joining organisations shown. (integer, defaults to 10 if not set)',
+ 'filter' => 'A list of filters by organisation meta information (nationality, sector, type, name, uuid) to include. (dictionary, prepending values with ! uses them as a negation)',
+ 'days' => 'How many days back should the list go - for example, setting 7 will only show the organisations that were added in the past 7 days. (integer)',
+ 'month' => 'Which organisations have been added this month? (boolean)',
+ 'year' => 'Which organisations have been added this year? (boolean)',
+ 'local' => 'Should the list only show local organisations? (boolean or list of booleans, defaults to 1. To get both sets, use [0,1])',
+ 'fields' => 'Which fields should be displayed, by default all are selected. Pass a list with the following options: [id, uuid, name, sector, type, nationality, creation_date]'
+ ];
+ private $validFilterKeys = [
+ 'nationality',
+ 'sector',
+ 'type',
+ 'name',
+ 'uuid'
+ ];
+
+ public $placeholder =
+ '{
+ "limit": 5,
+ "filter": {
+ "nationality": [
+ "Hungary",
+ "Russia",
+ "North Korea"
+ ]
+ },
+ "month": true
+}';
+
+ private $Organisation = null;
+
+ private function timeConditions($options)
+ {
+ $limit = empty($options['limit']) ? 10 : $options['limit'];
+ if (!empty($options['days'])) {
+ $condition = strtotime(sprintf("-%s days", $options['days']));
+ $this->tableDescription = __('The %d newest organisations created in the past %d days', $limit, (int)$options['days']);
+ } else if (!empty($options['month'])) {
+ $condition = strtotime('first day of this month 00:00:00', time());
+ $this->tableDescription = __('The %d newest organisations created during the current month', $limit);
+ } else if (!empty($options['year'])) {
+ $condition = strtotime('first day of this year 00:00:00', time());
+ $this->tableDescription = __('The %d newest organisations created during the current year', $limit);
+ } else {
+ $this->tableDescription = __('The %d newest organisations created', $limit);
+ return null;
+ }
+ $datetime = new DateTime();
+ $datetime->setTimestamp($condition);
+ return $datetime->format('Y-m-d H:i:s');
+ }
+
+ public function handler($user, $options = array())
+ {
+ $this->Organisation = ClassRegistry::init('Organisation');
+ $field_options = [
+ 'id' => [
+ 'name' => '#',
+ 'url' => Configure::read('MISP.baseurl') . '/organisations/view',
+ 'element' => 'links',
+ 'data_path' => 'Organisation.id',
+ 'url_params_data_paths' => 'Organisation.id'
+ ],
+ 'date_created' => [
+ 'name' => 'Creation date',
+ 'data_path' => 'Organisation.date_created'
+ ],
+ 'name' => [
+ 'name' => 'Name',
+ 'data_path' => 'Organisation.name',
+ ],
+ 'uuid' => [
+ 'name' => 'UUID',
+ 'data_path' => 'Organisation.uuid',
+ ],
+ 'sector' => [
+ 'name' => 'Sector',
+ 'data_path' => 'Organisation.sector',
+ ],
+ 'nationality' => [
+ 'name' => 'Nationality',
+ 'data_path' => 'Organisation.nationality',
+ ],
+ 'type' => [
+ 'name' => 'Type',
+ 'data_path' => 'Organisation.type',
+ ]
+ ];
+ $params = [
+ 'conditions' => [
+ 'AND' => ['Organisation.local' => !isset($options['local']) ? 1 : $options['local']]
+ ],
+ 'limit' => 10,
+ 'recursive' => -1
+ ];
+ if (!empty($options['filter']) && is_array($options['filter'])) {
+ foreach ($this->validFilterKeys as $filterKey) {
+ if (!empty($options['filter'][$filterKey])) {
+ if (!is_array($options['filter'][$filterKey])) {
+ $options['filter'][$filterKey] = [$options['filter'][$filterKey]];
+ }
+ $tempConditionBucket = [];
+ foreach ($options['filter'][$filterKey] as $value) {
+ if ($value[0] === '!') {
+ $tempConditionBucket['Organisation.' . $filterKey . ' NOT IN'][] = mb_substr($value, 1);
+ } else {
+ $tempConditionBucket['Organisation.' . $filterKey . ' IN'][] = $value;
+ }
+ }
+ if (!empty($tempConditionBucket)) {
+ $params['conditions']['AND'][] = $tempConditionBucket;
+ }
+ }
+ }
+ }
+ $timeConditions = $this->timeConditions($options);
+ if ($timeConditions) {
+ $params['conditions']['AND'][] = ['Organisation.date_created >=' => $timeConditions];
+ }
+ if (isset($options['fields'])) {
+ $fields = [];
+ foreach ($options['fields'] as $field) {
+ if (isset($field_options[$field])) {
+ $fields[$field] = $field_options[$field];
+ }
+ }
+ } else {
+ $fields = $field_options;
+ }
+ $data = $this->Organisation->find('all', [
+ 'recursive' => -1,
+ 'conditions' => $params['conditions'],
+ 'limit' => isset($options['limit']) ? (int)$options['limit'] : 10,
+ 'fields' => array_keys($fields),
+ 'order' => 'Organisation.date_created DESC'
+ ]);
+
+ return [
+ 'data' => $data,
+ 'fields' => $fields,
+ 'description' => $this->tableDescription
+ ];
+ }
+}
From 2048b546de5e629d24c07e48ae623480a781d83a Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:28:01 +0200
Subject: [PATCH 09/22] new: [widget] Widget to show latest users
- filter by org metadata, etc
---
app/Lib/Dashboard/NewUsersWidget.php | 171 +++++++++++++++++++++++++++
1 file changed, 171 insertions(+)
create mode 100644 app/Lib/Dashboard/NewUsersWidget.php
diff --git a/app/Lib/Dashboard/NewUsersWidget.php b/app/Lib/Dashboard/NewUsersWidget.php
new file mode 100644
index 000000000..f41d43570
--- /dev/null
+++ b/app/Lib/Dashboard/NewUsersWidget.php
@@ -0,0 +1,171 @@
+ 'Maximum number of joining users shown. (integer, defaults to 10 if not set)',
+ 'filter' => 'A list of filters for the organisations (nationality, sector, type, name, uuid) to include. (dictionary, prepending values with ! uses them as a negation)',
+ 'days' => 'How many days back should the list go - for example, setting 7 will only show the organisations that were added in the past 7 days. (integer)',
+ 'month' => 'Which organisations have been added this month? (boolean)',
+ 'year' => 'Which organisations have been added this year? (boolean)',
+ 'fields' => 'Which fields should be displayed, by default all are selected. Pass a list with the following options: [id, email, Organisation.name, Role.name, date_created]'
+ ];
+ private $validFilterKeys = [
+ 'id',
+ 'email',
+ 'Organisation.name',
+ 'Role.name',
+ 'date_created'
+ ];
+
+ public $placeholder =
+ '{
+ "limit": 10,
+ "filter": {
+ "Organisation.name": [
+ "!FSB",
+ "!GRU",
+ "!Kaspersky"
+ ],
+ "email": [
+ "!andras.iklody@circl.lu"
+ ],
+ "Role.name": [
+ "Publisher",
+ "User"
+ ]
+ },
+ "year": true
+}';
+
+ private $User = null;
+
+ private function timeConditions($options)
+ {
+ $limit = empty($options['limit']) ? 10 : $options['limit'];
+ if (!empty($options['days'])) {
+ $condition = strtotime(sprintf("-%s days", $options['days']));
+ $this->tableDescription = __('The %d newest users created in the past %d days', $limit, (int)$options['days']);
+ } else if (!empty($options['month'])) {
+ $condition = strtotime('first day of this month 00:00:00', time());
+ $this->tableDescription = __('The %d newest users created during the current month', $limit);
+ } else if (!empty($options['year'])) {
+ $condition = strtotime('first day of this year 00:00:00', time());
+ $this->tableDescription = __('The %d newest users created during the current year', $limit);
+ } else {
+ $this->tableDescription = __('The %d newest users created', $limit);
+ return null;
+ }
+ return $condition;
+ }
+
+ public function handler($user, $options = array())
+ {
+ $this->User = ClassRegistry::init('User');
+ $field_options = [
+ 'id' => [
+ 'name' => '#',
+ 'url' => empty($user['Role']['perm_site_admin']) ? null : Configure::read('MISP.baseurl') . '/admin/users/view',
+ 'element' => 'links',
+ 'data_path' => 'User.id',
+ 'url_params_data_paths' => 'User.id'
+ ],
+ 'date_created' => [
+ 'name' => 'Creation date',
+ 'data_path' => 'User.date_created'
+ ],
+ 'email' => [
+ 'name' => 'E-mail',
+ 'data_path' => 'User.email',
+ ],
+ 'Organisation.name' => [
+ 'name' => 'Organisation',
+ 'data_path' => 'Organisation.name',
+ ],
+ 'Role.name' => [
+ 'name' => 'Role',
+ 'data_path' => 'Role.name',
+ ]
+ ];
+ $params = [
+ 'conditions' => [],
+ 'limit' => 10,
+ 'recursive' => -1
+ ];
+ if (!empty($options['filter']) && is_array($options['filter'])) {
+ foreach ($this->validFilterKeys as $filterKey) {
+ if (!empty($options['filter'][$filterKey])) {
+ if (!is_array($options['filter'][$filterKey])) {
+ $options['filter'][$filterKey] = [$options['filter'][$filterKey]];
+ }
+ $tempConditionBucket = [];
+ foreach ($options['filter'][$filterKey] as $value) {
+ $filterName = strpos($filterKey, '.') ? $filterKey : 'User.' . $filterKey;
+ if ($value[0] === '!') {
+ $tempConditionBucket[$filterName . ' NOT IN'][] = mb_substr($value, 1);
+ } else {
+ $tempConditionBucket[$filterName . ' IN'][] = $value;
+ }
+ }
+ if (!empty($tempConditionBucket)) {
+ $params['conditions']['AND'][] = $tempConditionBucket;
+ }
+ }
+ }
+ }
+ $timeConditions = $this->timeConditions($options);
+ if ($timeConditions) {
+ $params['conditions']['AND'][] = ['User.date_created >=' => $timeConditions];
+ }
+ if (isset($options['fields'])) {
+ $fields = [];
+ foreach ($options['fields'] as $field) {
+ if (isset($field_options[$field])) {
+ $fields[$field] = $field_options[$field];
+ }
+ }
+ } else {
+ $fields = $field_options;
+ }
+
+ // redact e-mails for non site admins unless specifically allowed
+ if (
+ empty($user['Role']['perm_site_admin']) &&
+ !Configure::read('Security.disclose_user_emails') &&
+ isset($fields['email'])
+ ) {
+ unset($fields['email']);
+ }
+ $data = $this->User->find('all', [
+ 'recursive' => -1,
+ 'contain' => ['Organisation.name', 'Role.name'],
+ 'conditions' => $params['conditions'],
+ 'limit' => isset($options['limit']) ? $options['limit'] : 10,
+ 'fields' => array_keys($fields),
+ 'order' => 'User.date_created DESC'
+ ]);
+
+ foreach ($data as &$u) {
+ if (empty($u['User']['date_created'])) {
+ continue;
+ }
+ $tempDate = new DateTime();
+ $tempDate->setTimestamp($u['User']['date_created']);
+ $u['User']['date_created'] = $tempDate->format('Y-m-d H:i:s');
+ }
+
+ return [
+ 'data' => $data,
+ 'fields' => $fields,
+ 'description' => $this->tableDescription
+ ];
+ }
+}
From 9224fc46f2aa79eb408ea0b741b67ff9234342d6 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:28:56 +0200
Subject: [PATCH 10/22] new: [widget] added a widget to monitor contribution
counts per org
- filterable
---
.../OrgContributionToplistWidget.php | 107 ++++++++++++++++++
1 file changed, 107 insertions(+)
create mode 100644 app/Lib/Dashboard/OrgContributionToplistWidget.php
diff --git a/app/Lib/Dashboard/OrgContributionToplistWidget.php b/app/Lib/Dashboard/OrgContributionToplistWidget.php
new file mode 100644
index 000000000..ed7079fe3
--- /dev/null
+++ b/app/Lib/Dashboard/OrgContributionToplistWidget.php
@@ -0,0 +1,107 @@
+ 'How many days back should the list go - for example, setting 7 will only show contributions in the past 7 days. (integer)',
+ 'month' => 'Who contributed most this month? (boolean)',
+ 'year' => 'Which contributed most this year? (boolean)',
+ 'filter' => 'A list of filters by organisation meta information (nationality, sector, type, name, uuid, local (- expects a boolean or a list of boolean values)) to include. (dictionary, prepending values with ! uses them as a negation)',
+ 'limit' => 'Limits the number of displayed tags. Default: 10'
+ ];
+ public $cacheLifetime = null;
+ public $autoRefreshDelay = false;
+ private $validFilterKeys = [
+ 'nationality',
+ 'sector',
+ 'type',
+ 'name',
+ 'uuid'
+ ];
+ public $placeholder =
+'{
+ "days": "7d",
+ "threshold": 15,
+ "filter": {
+ "sector": "Financial"
+ }
+}';
+ private $Org = null;
+ private $Event = null;
+
+
+ private function timeConditions($options)
+ {
+ $limit = empty($options['limit']) ? 10 : $options['limit'];
+ if (!empty($options['days'])) {
+ $condition = strtotime(sprintf("-%s days", $options['days']));
+ } else if (!empty($options['month'])) {
+ $condition = strtotime('first day of this month 00:00:00', time());
+ } else if (!empty($options['year'])) {
+ $condition = strtotime('first day of this year 00:00:00', time());
+ } else {
+ return null;
+ }
+ return $condition;
+ }
+
+
+ public function handler($user, $options = array())
+ {
+ $params = ['conditions' => []];
+ $timeConditions = $this->timeConditions($options);
+ if ($timeConditions) {
+ $params['conditions']['AND'][] = ['Event.timestamp >=' => $timeConditions];
+ }
+ if (!empty($options['filter']) && is_array($options['filter'])) {
+ foreach ($this->validFilterKeys as $filterKey) {
+ if (!empty($options['filter'][$filterKey])) {
+ if (!is_array($options['filter'][$filterKey])) {
+ $options['filter'][$filterKey] = [$options['filter'][$filterKey]];
+ }
+ $tempConditionBucket = [];
+ foreach ($options['filter'][$filterKey] as $value) {
+ if ($value[0] === '!') {
+ $tempConditionBucket['Organisation.' . $filterKey . ' NOT IN'][] = mb_substr($value, 1);
+ } else {
+ $tempConditionBucket['Organisation.' . $filterKey . ' IN'][] = $value;
+ }
+ }
+ if (!empty($tempConditionBucket)) {
+ $params['conditions']['AND'][] = $tempConditionBucket;
+ }
+ }
+ }
+ }
+ if (isset($options['filter']['local'])) {
+ $params['conditions']['AND']['local'] = $options['filter']['local'];
+ }
+
+ $this->Org = ClassRegistry::init('Organisation');
+ $org_ids = $this->Org->find('list', [
+ 'fields' => ['Organisation.id', 'Organisation.name'],
+ 'conditions' => $params['conditions']
+ ]);
+ $conditions = ['Event.orgc_id IN' => array_keys($org_ids)];
+ $this->Event = ClassRegistry::init('Event');
+ $this->Event->virtualFields['frequency'] = 0;
+ $orgs = $this->Event->find('all', [
+ 'recursive' => -1,
+ 'fields' => ['orgc_id', 'count(Event.orgc_id) as Event__frequency'],
+ 'group' => ['orgc_id'],
+ 'conditions' => $conditions,
+ 'order' => 'count(Event.orgc_id) desc',
+ 'limit' => empty($options['limit']) ? 10 : $options['limit']
+ ]);
+ $results = [];
+ foreach($orgs as $org) {
+ $results[$org_ids[$org['Event']['orgc_id']]] = $org['Event']['frequency'];
+ }
+ return ['data' => $results];
+ }
+}
+?>
From f6fabd2db05adbc0e91f669412550cf340863f93 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:29:33 +0200
Subject: [PATCH 11/22] new: [widget] User contribution widget
- filterable
---
.../UserContributionToplistWidget.php | 115 ++++++++++++++++++
1 file changed, 115 insertions(+)
create mode 100644 app/Lib/Dashboard/UserContributionToplistWidget.php
diff --git a/app/Lib/Dashboard/UserContributionToplistWidget.php b/app/Lib/Dashboard/UserContributionToplistWidget.php
new file mode 100644
index 000000000..586d0ee2b
--- /dev/null
+++ b/app/Lib/Dashboard/UserContributionToplistWidget.php
@@ -0,0 +1,115 @@
+ 'How many days back should the list go - for example, setting 7 will only show contributions in the past 7 days. (integer)',
+ 'month' => 'Who contributed most this month? (boolean)',
+ 'year' => 'Which contributed most this year? (boolean)',
+ 'filter' => 'A list of filters by organisation meta information (nationality, sector, type, name, uuid, local (- expects a boolean or a list of boolean values)) to include. (dictionary, prepending values with ! uses them as a negation)',
+ 'limit' => 'Limits the number of displayed tags. Default: 10'
+ ];
+ public $cacheLifetime = null;
+ public $autoRefreshDelay = false;
+ private $validFilterKeys = [
+ 'nationality',
+ 'sector',
+ 'type',
+ 'name',
+ 'uuid'
+ ];
+ public $placeholder =
+'{
+ "days": "7d",
+ "threshold": 15,
+ "filter": {
+ "sector": "Financial"
+ }
+}';
+ private $Org = null;
+ private $Event = null;
+
+
+ private function timeConditions($options)
+ {
+ $limit = empty($options['limit']) ? 10 : $options['limit'];
+ if (!empty($options['days'])) {
+ $condition = strtotime(sprintf("-%s days", $options['days']));
+ } else if (!empty($options['month'])) {
+ $condition = strtotime('first day of this month 00:00:00', time());
+ } else if (!empty($options['year'])) {
+ $condition = strtotime('first day of this year 00:00:00', time());
+ } else {
+ return null;
+ }
+ return $condition;
+ }
+
+
+ public function handler($user, $options = array())
+ {
+ $params = ['conditions' => []];
+ $timeConditions = $this->timeConditions($options);
+ if ($timeConditions) {
+ $params['conditions']['AND'][] = ['Event.timestamp >=' => $timeConditions];
+ }
+ if (!empty($options['filter']) && is_array($options['filter'])) {
+ foreach ($this->validFilterKeys as $filterKey) {
+ if (!empty($options['filter'][$filterKey])) {
+ if (!is_array($options['filter'][$filterKey])) {
+ $options['filter'][$filterKey] = [$options['filter'][$filterKey]];
+ }
+ $tempConditionBucket = [];
+ foreach ($options['filter'][$filterKey] as $value) {
+ if ($value[0] === '!') {
+ $tempConditionBucket['Organisation.' . $filterKey . ' NOT IN'][] = mb_substr($value, 1);
+ } else {
+ $tempConditionBucket['Organisation.' . $filterKey . ' IN'][] = $value;
+ }
+ }
+ if (!empty($tempConditionBucket)) {
+ $params['conditions']['AND'][] = $tempConditionBucket;
+ }
+ }
+ }
+ }
+ if (isset($options['filter']['local'])) {
+ $params['conditions']['AND']['local'] = $options['filter']['local'];
+ }
+
+ $this->Org = ClassRegistry::init('Organisation');
+ $org_ids = $this->Org->find('list', [
+ 'fields' => ['Organisation.id', 'Organisation.name'],
+ 'conditions' => $params['conditions']
+ ]);
+ $userConditions = [];
+ if (!empty($org_ids)) {
+ $userConditions = ['User.org_id IN' => array_keys($org_ids)];
+ }
+ $user_ids = $this->Org->User->find('list', [
+ 'fields' => ['User.id', 'User.email'],
+ 'conditions' => $userConditions
+ ]);
+ $conditions = empty($user_ids) ? [] : ['Event.user_id IN' => array_keys($user_ids)];
+ $this->Event = ClassRegistry::init('Event');
+ $this->Event->virtualFields['frequency'] = 0;
+ $users = $this->Event->find('all', [
+ 'recursive' => -1,
+ 'fields' => ['user_id', 'count(Event.user_id) as Event__frequency'],
+ 'group' => ['user_id'],
+ 'conditions' => $conditions,
+ 'order' => 'count(Event.user_id) desc',
+ 'limit' => empty($options['limit']) ? 10 : $options['limit']
+ ]);
+ $results = [];
+ foreach($users as $user) {
+ $results[$user_ids[$user['Event']['user_id']]] = $user['Event']['frequency'];
+ }
+ return ['data' => $results];
+ }
+}
+?>
From cc9ab78fe4656ea26156974a612862118a47b25d Mon Sep 17 00:00:00 2001
From: iglocska
Date: Fri, 5 May 2023 14:29:59 +0200
Subject: [PATCH 12/22] new: [widget] monitor the trending attribute values
- filter by timeframe among other filters
---
.../Dashboard/TrendingAttributesWidget.php | 136 ++++++++++++++++++
1 file changed, 136 insertions(+)
create mode 100644 app/Lib/Dashboard/TrendingAttributesWidget.php
diff --git a/app/Lib/Dashboard/TrendingAttributesWidget.php b/app/Lib/Dashboard/TrendingAttributesWidget.php
new file mode 100644
index 000000000..e8faff0a6
--- /dev/null
+++ b/app/Lib/Dashboard/TrendingAttributesWidget.php
@@ -0,0 +1,136 @@
+ 'The time window, going back in seconds, that should be included. (allows for filtering by days - example: 5d. -1 Will fetch all historic data)',
+ 'exclude' => 'List of values to exclude - for example "8.8.8.8".',
+ 'threshold' => 'Limits the number of displayed attribute values. Default: 10',
+ 'type' => 'List of Attribute types to include',
+ 'category' => 'List of Attribute categories to exclude',
+ 'to_ids' => 'A list of to_ids settings accepted for the data displayed ([0], [1], [0,1])',
+ 'org_filter' => 'List of organisation filters to exclude events by, based on organisation meta-data (Organisation.sector, Organisation.type, Organisation.nationality). Pre-pending a value with a "!" negates it.'
+ );
+ private $validOrgFilters = [
+ 'sector',
+ 'type',
+ 'national',
+ 'uuid',
+ 'local'
+ ];
+ public $placeholder =
+ '{
+ "time_window": "7d",
+ "threshold": 15,
+ "org_filter": {
+ "sector": ["Financial"]
+ }
+}';
+ public $description = 'Widget showing the trending tags over the past x seconds, along with the possibility to include/exclude tags.';
+ public $cacheLifetime = 3;
+
+ private function getOrgList($options)
+ {
+ $organisationModel = ClassRegistry::init('Organisation');
+ if (!empty($options['org_filter']) && is_array($options['org_filter'])) {
+ foreach ($this->validOrgFilters as $filterKey) {
+ if (!empty($options['org_filter'][$filterKey])) {
+ if ($filterKey === 'local') {
+ $tempConditionBucket['Organisation.local'] = $options['org_filter']['local'];
+ } else {
+ if (!is_array($options['org_filter'][$filterKey])) {
+ $options['org_filter'][$filterKey] = [$options['org_filter'][$filterKey]];
+ }
+ $tempConditionBucket = [];
+ foreach ($options['org_filter'][$filterKey] as $value) {
+ if ($value[0] === '!') {
+ $tempConditionBucket['Organisation.' . $filterKey . ' NOT IN'][] = mb_substr($value, 1);
+ } else {
+ $tempConditionBucket['Organisation.' . $filterKey . ' IN'][] = $value;
+ }
+ }
+ }
+ if (!empty($tempConditionBucket)) {
+ $orgConditions[] = $tempConditionBucket;
+ }
+ }
+ }
+ return $organisationModel->find('column', [
+ 'recursive' => -1,
+ 'conditions' => $orgConditions,
+ 'fields' => ['Organisation.id']
+ ]);
+ }
+ }
+
+ public function handler($user, $options = array())
+ {
+ /** @var Event $eventModel */
+ $attributeModel = ClassRegistry::init('Attribute');
+ $threshold = empty($options['threshold']) ? 10 : $options['threshold'];
+ $time_window = empty($options['time_window']) ? (7 * 24 * 60 * 60) : (int)$options['time_window'];
+ if (is_string($time_window) && substr($time_window, -1) === 'd') {
+ $time_window = ((int)substr($time_window, 0, -1)) * 24 * 60 * 60;
+ }
+ $conditions = $time_window === -1 ? [] : ['timestamp >=' => time() - $time_window];
+ $conditions['deleted'] = 0;
+ $conditionsToParse = ['type', 'category', 'to_ids'];
+ foreach ($conditionsToParse as $parsedCondition) {
+ if (!empty($options[$parsedCondition])) {
+ $conditions[$parsedCondition] = $options[$parsedCondition];
+ }
+ }
+ if (!empty($options['exclude'])) {
+ $conditions['value1 NOT IN'] = $options['exclude'];
+ }
+ if (!empty($options['org_filter'])) {
+ $conditions['Event.orgc_id IN'] = $this->getOrgList($options);
+ if (empty($conditions['Event.orgc_id IN'])) {
+ $conditions['Event.orgc_id IN'] = [-1];
+ }
+ }
+ $attributeModel->virtualFields['frequency'] = 0;
+ if (!empty($user['Role']['perm_site_admin'])) {
+ $values = $attributeModel->find('all', [
+ 'recursive' => -1,
+ 'fields' => ['value1', 'count(Attribute.value1) as Attribute__frequency'],
+ 'group' => ['value1'],
+ 'conditions' => $conditions,
+ 'contain' => ['Event.orgc_id'],
+ 'order' => 'count(Attribute.value1) desc',
+ 'limit' => empty($options['threshold']) ? 10 : $options['threshold']
+ ]);
+ } else {
+ $conditions['AND'][] = [
+ 'OR' => [
+ 'Event.orgc_id' => $user['org_id'],
+
+ ]
+ ];
+ $values = $attributeModel->find('all', [
+ 'recursive' => -1,
+ 'fields' => ['value1', 'count(Attribute.value1) as Attribute__frequency', 'distribution', 'sharing_group_id'],
+ 'group' => 'value1',
+ 'contain' => [
+ 'Event.org_id',
+ 'Event.distribution',
+ 'Event.sharing_group_id',
+ 'Object.distribution',
+ 'Object.sharing_group_id'
+ ],
+ 'conditions' => $conditions,
+ 'order' => 'count(Attribute.value1) desc',
+ 'limit' => empty($options['threshold']) ? 10 : $options['threshold']
+ ]);
+ }
+ $data = [];
+ foreach ($values as $value) {
+ $data[$value['Attribute']['value1']] = $value['Attribute']['frequency'];
+ }
+ return ['data' => $data];
+ }
+}
From 9e763ba0e519fd9a7e0de66275aa43a2686caba2 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 13:39:31 +0200
Subject: [PATCH 13/22] new: [auth] log api key usage in redis
- lightweight per day slice of api key use
- built as a ranked set in redis for the dashboards
---
app/Controller/AppController.php | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php
index 064a06862..0c6e50e35 100755
--- a/app/Controller/AppController.php
+++ b/app/Controller/AppController.php
@@ -417,16 +417,19 @@ class AppController extends Controller
}
}
if ($foundMispAuthKey) {
- $authKeyToStore = substr($authKey, 0, 4)
+ $start = substr($authKey, 0, 4);
+ $end = substr($authKey, -4);
+ $authKeyToStore = $start
. str_repeat('*', 32)
- . substr($authKey, -4);
+ . $end;
+ $this->__logApiKeyUse($start . $end);
if ($user) {
// User found in the db, add the user info to the session
if (Configure::read('MISP.log_auth')) {
$this->loadModel('Log');
$this->Log->create();
$log = array(
- 'org' => $user['Organisation']['name'],
+ 'org' => $user['Organisation']['0000000000000000000000000000000000000000name'],
'model' => 'User',
'model_id' => $user['id'],
'email' => $user['email'],
@@ -642,6 +645,15 @@ class AppController extends Controller
return in_array($this->request->params['action'], $actionsToCheck[$controller], true);
}
+ private function __logApiKeyUse($apikey)
+ {
+ $redis = $this->User->setupRedis();
+ if (!$redis) {
+ return;
+ }
+ $redis->zIncrBy('misp:authkey_log:' . date("Ymd"), 1, $apikey);
+ }
+
/**
* User access monitoring
* @param array $user
From a60202d9d1cb6b99920a0d5538db1b22dad3add0 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 13:41:44 +0200
Subject: [PATCH 14/22] fix: [junk removed] removed accidentally inserted
characters
- fell asleep on the keyboard?
---
app/Controller/AppController.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php
index 0c6e50e35..00ff8bf28 100755
--- a/app/Controller/AppController.php
+++ b/app/Controller/AppController.php
@@ -429,7 +429,7 @@ class AppController extends Controller
$this->loadModel('Log');
$this->Log->create();
$log = array(
- 'org' => $user['Organisation']['0000000000000000000000000000000000000000name'],
+ 'org' => $user['Organisation']['name'],
'model' => 'User',
'model_id' => $user['id'],
'email' => $user['email'],
From 712321eb81f05b065bd10f72e97af0ee1c8cd7c8 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 14:04:32 +0200
Subject: [PATCH 15/22] new: [dashboard templates] show which modules will be
visible to the given user
---
app/Controller/DashboardsController.php | 11 +++++++
.../IndexTable/Fields/allow_deny_list.ctp | 31 +++++++++++++++++++
2 files changed, 42 insertions(+)
create mode 100644 app/View/Elements/genericElements/IndexTable/Fields/allow_deny_list.ctp
diff --git a/app/Controller/DashboardsController.php b/app/Controller/DashboardsController.php
index a974be4c4..50fb894b4 100644
--- a/app/Controller/DashboardsController.php
+++ b/app/Controller/DashboardsController.php
@@ -316,6 +316,8 @@ class DashboardsController extends AppController
public function listTemplates()
{
$conditions = array();
+ // load all widgets for internal use, won't be displayed to the user. Thus we circumvent the ACL on it.
+ $accessible_widgets = array_keys($this->Dashboard->loadAllWidgets($this->Auth->user()));
if (!$this->_isSiteAdmin()) {
$permission_flags = array();
foreach ($this->Auth->user('Role') as $perm => $value) {
@@ -394,6 +396,15 @@ class DashboardsController extends AppController
}
$element['Dashboard']['widgets'] = array_keys($widgets);
sort($element['Dashboard']['widgets']);
+ $temp = [];
+ foreach ($element['Dashboard']['widgets'] as $widget) {
+ if (in_array($widget, $accessible_widgets)) {
+ $temp['allow'][] = $widget;
+ } else {
+ $temp['deny'][] = $widget;
+ }
+ }
+ $element['Dashboard']['widgets'] = $temp;
if ($element['Dashboard']['user_id'] != $this->Auth->user('id')) {
$element['User']['email'] = '';
}
diff --git a/app/View/Elements/genericElements/IndexTable/Fields/allow_deny_list.ctp b/app/View/Elements/genericElements/IndexTable/Fields/allow_deny_list.ctp
new file mode 100644
index 000000000..78b0cbd03
--- /dev/null
+++ b/app/View/Elements/genericElements/IndexTable/Fields/allow_deny_list.ctp
@@ -0,0 +1,31 @@
+ [
+ 'name' => __('Allowed'),
+ 'color' => 'green'
+ ],
+ 'deny' => [
+ 'name' => __('Denied'),
+ 'color' => 'red'
+ ]
+ ];
+ foreach ($setup as $state => $settings) {
+ if (!empty($data[$state])) {
+ echo sprintf(
+ '%s
',
+ $settings['color'],
+ $settings['name']
+ );
+ foreach ($data[$state] as $k => $element) {
+ $data[$state][$k] = sprintf(
+ '%s',
+ $settings['color'],
+ h($element)
+ );
+ }
+ echo implode('
', $data[$state]);
+ }
+ }
+
+?>
From aff872aeeea58f358dde9ec54a5f2b601544b63b Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 14:05:11 +0200
Subject: [PATCH 16/22] chg: [usage widget] removed autorefresh
---
app/Lib/Dashboard/UsageDataWidget.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Lib/Dashboard/UsageDataWidget.php b/app/Lib/Dashboard/UsageDataWidget.php
index 37781039b..3bddb2531 100644
--- a/app/Lib/Dashboard/UsageDataWidget.php
+++ b/app/Lib/Dashboard/UsageDataWidget.php
@@ -7,7 +7,7 @@ class UsageDataWidget
public $height = 5;
public $description = 'Shows usage data / statistics.';
public $cacheLifetime = false;
- public $autoRefreshDelay = 3;
+ public $autoRefreshDelay = false;
public $params = [
'filter' => 'A list of filters by organisation meta information (nationality, sector, type, name, uuid) to include. (dictionary, prepending values with ! uses them as a negation)',
];
From b7bc526e1630f5bf1360927c166ac10d7015ca1e Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 14:05:34 +0200
Subject: [PATCH 17/22] chg: [usercontribution widget] added permission check
for Security.disclose_user_emails
---
app/Lib/Dashboard/UserContributionToplistWidget.php | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/app/Lib/Dashboard/UserContributionToplistWidget.php b/app/Lib/Dashboard/UserContributionToplistWidget.php
index 586d0ee2b..b9ea08c50 100644
--- a/app/Lib/Dashboard/UserContributionToplistWidget.php
+++ b/app/Lib/Dashboard/UserContributionToplistWidget.php
@@ -111,5 +111,13 @@ class UserContributionToplistWidget
}
return ['data' => $results];
}
+
+ public function checkPermissions($user)
+ {
+ if (empty(Configure::read('Security.disclose_user_emails')) && empty($user['Role']['perm_site_admin'])) {
+ return false;
+ }
+ return true;
+ }
}
?>
From 6e39da801eddf145db84be9b531056ee5fccdcd4 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 14:06:35 +0200
Subject: [PATCH 18/22] new: [organisation usage widget (map)] added world map
listing the countries / counts for each country of users
---
app/Lib/Dashboard/OrganisationMapWidget.php | 250 ++++++++++++++++++++
1 file changed, 250 insertions(+)
create mode 100644 app/Lib/Dashboard/OrganisationMapWidget.php
diff --git a/app/Lib/Dashboard/OrganisationMapWidget.php b/app/Lib/Dashboard/OrganisationMapWidget.php
new file mode 100644
index 000000000..77605aa85
--- /dev/null
+++ b/app/Lib/Dashboard/OrganisationMapWidget.php
@@ -0,0 +1,250 @@
+ 'A list of filters by organisation meta information (sector, type, local (- expects a boolean or a list of boolean values)) to include. (dictionary, prepending values with ! uses them as a negation)',
+ 'limit' => 'Limits the number of displayed tags. Default: 10'
+ ];
+ public $cacheLifetime = null;
+ public $autoRefreshDelay = false;
+ private $validFilterKeys = [
+ 'sector',
+ 'type',
+ 'local'
+ ];
+ public $placeholder =
+'{
+ "type": "Member",
+ "local": [0,1]
+}';
+ private $Organisation = null;
+
+ public $countryCodes = array(
+ 'Afghanistan' => 'AF',
+ 'Albania' => 'AL',
+ 'Algeria' => 'DZ',
+ 'Angola' => 'AO',
+ 'Argentina' => 'AR',
+ 'Armenia' => 'AM',
+ 'Australia' => 'AU',
+ 'Austria' => 'AT',
+ 'Azerbaijan' => 'AZ',
+ 'Bahamas' => 'BS',
+ 'Bangladesh' => 'BD',
+ 'Belarus' => 'BY',
+ 'Belgium' => 'BE',
+ 'Belize' => 'BZ',
+ 'Benin' => 'BJ',
+ 'Bhutan' => 'BT',
+ 'Bolivia' => 'BO',
+ 'Bosnia and Herz.' => 'BA',
+ 'Botswana' => 'BW',
+ 'Brazil' => 'BR',
+ 'Brunei' => 'BN',
+ 'Bulgaria' => 'BG',
+ 'Burkina Faso' => 'BF',
+ 'Burundi' => 'BI',
+ 'Cambodia' => 'KH',
+ 'Cameroon' => 'CM',
+ 'Canada' => 'CA',
+ 'Central African Rep.' => 'CF',
+ 'Chad' => 'TD',
+ 'Chile' => 'CL',
+ 'China' => 'CN',
+ 'Colombia' => 'CO',
+ 'Congo' => 'CG',
+ 'Costa Rica' => 'CR',
+ 'Croatia' => 'HR',
+ 'Cuba' => 'CU',
+ 'Cyprus' => 'CY',
+ 'Czech Rep.' => 'CZ',
+ 'Côte d\'Ivoire' => 'CI',
+ 'Dem. Rep. Congo' => 'CD',
+ 'Dem. Rep. Korea' => 'KP',
+ 'Denmark' => 'DK',
+ 'Djibouti' => 'DJ',
+ 'Dominican Rep.' => 'DO',
+ 'Ecuador' => 'EC',
+ 'Egypt' => 'EG',
+ 'El Salvador' => 'SV',
+ 'Eq. Guinea' => 'GQ',
+ 'Eritrea' => 'ER',
+ 'Estonia' => 'EE',
+ 'Ethiopia' => 'ET',
+ 'Falkland Is.' => 'FK',
+ 'Fiji' => 'FJ',
+ 'Finland' => 'FI',
+ 'Fr. S. Antarctic Lands' => 'TF',
+ 'France' => 'FR',
+ 'Gabon' => 'GA',
+ 'Gambia' => 'GM',
+ 'Georgia' => 'GE',
+ 'Germany' => 'DE',
+ 'Ghana' => 'GH',
+ 'Greece' => 'GR',
+ 'Greenland' => 'GL',
+ 'Guatemala' => 'GT',
+ 'Guinea' => 'GN',
+ 'Guinea-Bissau' => 'GW',
+ 'Guyana' => 'GY',
+ 'Haiti' => 'HT',
+ 'Honduras' => 'HN',
+ 'Hungary' => 'HU',
+ 'Iceland' => 'IS',
+ 'India' => 'IN',
+ 'Indonesia' => 'ID',
+ 'Iran' => 'IR',
+ 'Iraq' => 'IQ',
+ 'Ireland' => 'IE',
+ 'Israel' => 'IL',
+ 'Italy' => 'IT',
+ 'Jamaica' => 'JM',
+ 'Japan' => 'JP',
+ 'Jordan' => 'JO',
+ 'Kazakhstan' => 'KZ',
+ 'Kenya' => 'KE',
+ 'Korea' => 'KR',
+ 'Kuwait' => 'KW',
+ 'Kyrgyzstan' => 'KG',
+ 'Lao PDR' => 'LA',
+ 'Latvia' => 'LV',
+ 'Lebanon' => 'LB',
+ 'Lesotho' => 'LS',
+ 'Liberia' => 'LR',
+ 'Libya' => 'LY',
+ 'Lithuania' => 'LT',
+ 'Luxembourg' => 'LU',
+ 'Macedonia' => 'MK',
+ 'Madagascar' => 'MG',
+ 'Mainland China' => 'CN',
+ 'Malawi' => 'MW',
+ 'Malaysia' => 'MY',
+ 'Mali' => 'ML',
+ 'Mauritania' => 'MR',
+ 'Mexico' => 'MX',
+ 'Moldova' => 'MD',
+ 'Mongolia' => 'MN',
+ 'Montenegro' => 'ME',
+ 'Morocco' => 'MA',
+ 'Mozamb' => 'MZ',
+ 'Myanmar' => 'MM',
+ 'Namibia' => 'NA',
+ 'Nepal' => 'NP',
+ 'Netherlands' => 'NL',
+ 'New Caledonia' => 'NC',
+ 'New Zealand' => 'NZ',
+ 'Nicaragua' => 'NI',
+ 'Niger' => 'NE',
+ 'Nigeria' => 'NG',
+ 'Norway' => 'NO',
+ 'Oman' => 'OM',
+ 'Pakistan' => 'PK',
+ 'Palestine' => 'PS',
+ 'Panama' => 'PA',
+ 'Papua New Guinea' => 'PG',
+ 'Paraguay' => 'PY',
+ 'Peru' => 'PE',
+ 'Philippines' => 'PH',
+ 'Poland' => 'PL',
+ 'Portugal' => 'PT',
+ 'Puerto Rico' => 'PR',
+ 'Qatar' => 'QA',
+ 'Romania' => 'RO',
+ 'Russia' => 'RU',
+ 'Rwanda' => 'RW',
+ 'S. Sudan' => 'SS',
+ 'Saudi Arabia' => 'SA',
+ 'Senegal' => 'SN',
+ 'Serbia' => 'RS',
+ 'Sierra Leone' => 'SL',
+ 'Slovakia' => 'SK',
+ 'Slovenia' => 'SI',
+ 'Solomon Is.' => 'SB',
+ 'Somalia' => 'SO',
+ 'South Africa' => 'ZA',
+ 'Spain' => 'ES',
+ 'Sri Lanka' => 'LK',
+ 'Sudan' => 'SD',
+ 'Suriname' => 'SR',
+ 'Swaziland' => 'SZ',
+ 'Sweden' => 'SE',
+ 'Switzerland' => 'CH',
+ 'Syria' => 'SY',
+ 'Taiwan' => 'TW',
+ 'Tajikistan' => 'TJ',
+ 'Tanzania' => 'TZ',
+ 'Thailand' => 'TH',
+ 'Timor-Leste' => 'TL',
+ 'Togo' => 'TG',
+ 'Trinidad and Tobago' => 'TT',
+ 'Tunisia' => 'TN',
+ 'Turkey' => 'TR',
+ 'Turkmenistan' => 'TM',
+ 'Uganda' => 'UG',
+ 'Ukraine' => 'UA',
+ 'United Arab Emirates' => 'AE',
+ 'United Kingdom' => 'GB',
+ 'United States' => 'US',
+ 'Uruguay' => 'UY',
+ 'Uzbekistan' => 'UZ',
+ 'Vanuatu' => 'VU',
+ 'Venezuela' => 'VE',
+ 'Vietnam' => 'VN',
+ 'W. Sahara' => 'EH',
+ 'Yemen' => 'YE',
+ 'Zambia' => 'ZM',
+ 'Zimbabwe' => 'ZW'
+ );
+
+ public function handler($user, $options = array())
+ {
+ $params = [
+ 'conditions' => [
+ 'Nationality !=' => ''
+ ]
+ ];
+ if (!empty($options['filter']) && is_array($options['filter'])) {
+ foreach ($this->validFilterKeys as $filterKey) {
+ if (!empty($options['filter'][$filterKey])) {
+ if (!is_array($options['filter'][$filterKey])) {
+ $options['filter'][$filterKey] = [$options['filter'][$filterKey]];
+ }
+ $tempConditionBucket = [];
+ foreach ($options['filter'][$filterKey] as $value) {
+ if ($value[0] === '!') {
+ $tempConditionBucket['Organisation.' . $filterKey . ' NOT IN'][] = mb_substr($value, 1);
+ } else {
+ $tempConditionBucket['Organisation.' . $filterKey . ' IN'][] = $value;
+ }
+ }
+ if (!empty($tempConditionBucket)) {
+ $params['conditions']['AND'][] = $tempConditionBucket;
+ }
+ }
+ }
+ }
+ $this->Organisation = ClassRegistry::init('Organisation');
+ $orgs = $this->Organisation->find('all', [
+ 'recursive' => -1,
+ 'fields' => ['Organisation.nationality', 'COUNT(Organisation.nationality) AS frequency'],
+ 'conditions' => $params['conditions'],
+ 'group' => ['Organisation.nationality']
+ ]);
+ $results = ['data' => [], 'scope' => 'Organisations'];
+ foreach($orgs as $org) {
+ $country = $org['Organisation']['nationality'];
+ $count = $org['0']['frequency'];
+ if (isset($this->countryCodes[$country])) {
+ $countryCode = $this->countryCodes[$country];
+ $results['data'][$countryCode] = $count;
+ }
+ }
+ return $results;
+ }
+}
+?>
From e205d79dacdc184fae839a2398ec99dbefa860bd Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 14:09:27 +0200
Subject: [PATCH 19/22] new: [widget] login widget added for admins
- who logged into the instance via the UI in the past x days / current month / current year, and how frequently?
---
app/Lib/Dashboard/LoginsWidget.php | 88 ++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 app/Lib/Dashboard/LoginsWidget.php
diff --git a/app/Lib/Dashboard/LoginsWidget.php b/app/Lib/Dashboard/LoginsWidget.php
new file mode 100644
index 000000000..7280d6863
--- /dev/null
+++ b/app/Lib/Dashboard/LoginsWidget.php
@@ -0,0 +1,88 @@
+ 'A list of filters by organisation meta information (sector, type, nationality, id, uuid) to include. (dictionary, prepending values with ! uses them as a negation)',
+ 'limit' => 'Limits the number of displayed APIkeys. (-1 will list all) Default: -1',
+ 'days' => 'How many days back should the list go - for example, setting 7 will only show contributions in the past 7 days. (integer)',
+ 'month' => 'Who contributed most this month? (boolean)',
+ 'year' => 'Which contributed most this year? (boolean)',
+ ];
+ public $description = 'Basic widget showing some server statistics in regards to MISP.';
+ public $cacheLifetime = 10;
+ public $autoRefreshDelay = null;
+ private $User = null;
+ private $Log = null;
+
+
+ private function getDates($options)
+ {
+ if (!empty($options['days'])) {
+ $begin = date('Y-m-d H:i:s', strtotime(sprintf("-%s days", $options['days'])));
+ } else if (!empty($options['month'])) {
+ $begin = date('Y-m-d H:i:s', strtotime('first day of this month 00:00:00', time()));
+ } else if (!empty($options['year'])) {
+ $begin = date('Y-m-d', strtotime('first day of this year 00:00:00', time()));
+ } else {
+ $begin = date('Y-m-d H:i:s', strtotime('-7 days', time()));
+ }
+ return $begin ? ['Log.created >=' => $begin] : [];
+ }
+
+ public function handler($user, $options = array())
+ {
+ $this->User = ClassRegistry::init('User');
+ $this->Log = ClassRegistry::init('Log');
+ $conditions = $this->getDates($options);
+ $conditions['Log.action'] = 'login';
+ $this->Log->Behaviors->load('Containable');
+ $this->Log->bindModel([
+ 'belongsTo' => [
+ 'User'
+ ]
+ ]);
+ $this->Log->virtualFields['count'] = 0;
+ $this->Log->virtualFields['email'] = '';
+ $logs = $this->Log->find('all', [
+ 'recursive' => -1,
+ 'conditions' => $conditions,
+ 'fields' => ['Log.user_id', 'COUNT(Log.id) AS Log__count', 'User.email AS Log__email'],
+ 'contain' => ['User'],
+ 'group' => ['Log.user_id']
+ ]);
+ $counts = [];
+ $emails = [];
+ foreach ($logs as $log) {
+ $counts[$log['Log']['user_id']] = $log['Log']['count'];
+ $emails[$log['Log']['user_id']] = $log['Log']['email'];
+ }
+ $results = [];
+ arsort($counts);
+ $baseurl = empty(Configure::read('MISP.external_baseurl')) ? h(Configure::read('MISP.baseurl')) : Configure::read('MISP.external_baseurl');
+ foreach ($counts as $user_id => $count) {
+ $results[] = [
+ 'html_title' => sprintf(
+ '%s',
+ h($baseurl),
+ h($user_id),
+ h($emails[$user_id])
+ ),
+ 'value' => $count
+ ];
+ }
+ return $results;
+ }
+
+ public function checkPermissions($user)
+ {
+ if (empty($user['Role']['perm_site_admin'])) {
+ return false;
+ }
+ return true;
+ }
+}
From c1ad695a9fb80e56aeb9d315349830af5074ddea Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 14:10:21 +0200
Subject: [PATCH 20/22] chg: [list dashboard templates] view updated with the
relevant changes to show allowed/denied widgets in a given template
---
app/View/Dashboards/list_templates.ctp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/View/Dashboards/list_templates.ctp b/app/View/Dashboards/list_templates.ctp
index edd37d9cf..0f9aaf5a0 100644
--- a/app/View/Dashboards/list_templates.ctp
+++ b/app/View/Dashboards/list_templates.ctp
@@ -46,7 +46,7 @@
array(
'name' => __('Widgets Used'),
'data_path' => 'Dashboard.widgets',
- 'element' => 'list'
+ 'element' => 'allow_deny_list'
),
array(
'name' => __('Selectable'),
From c702f5366d9d8b70f8681a469e9e96f89b0668e0 Mon Sep 17 00:00:00 2001
From: iglocska
Date: Tue, 16 May 2023 14:11:02 +0200
Subject: [PATCH 21/22] chg: [dashboard widget UI] made some changes to
accomodate the new widgets
---
app/View/Elements/dashboard/Widgets/SimpleList.ctp | 7 ++++++-
app/View/Elements/dashboard/Widgets/WorldMap.ctp | 1 -
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/View/Elements/dashboard/Widgets/SimpleList.ctp b/app/View/Elements/dashboard/Widgets/SimpleList.ctp
index 525f8ebdc..255b7a9eb 100644
--- a/app/View/Elements/dashboard/Widgets/SimpleList.ctp
+++ b/app/View/Elements/dashboard/Widgets/SimpleList.ctp
@@ -27,9 +27,14 @@
$change = ' (-' . $change . ')';
}
}
+ if (!empty($element['html_title'])) {
+ $title = $element['html_title'];
+ } else {
+ $title = h($element['title']);
+ }
echo sprintf(
'%s: %s%s%s
',
- h($element['title']),
+ $title,
empty($element['class']) ? 'blue' : h($element['class']),
!isset($element['value']) ? '' : $element['value'],
empty($element['html']) ? '' : $element['html'],
diff --git a/app/View/Elements/dashboard/Widgets/WorldMap.ctp b/app/View/Elements/dashboard/Widgets/WorldMap.ctp
index 2123a4b34..a89d4d978 100644
--- a/app/View/Elements/dashboard/Widgets/WorldMap.ctp
+++ b/app/View/Elements/dashboard/Widgets/WorldMap.ctp
@@ -20,7 +20,6 @@
), true);
}
?>
-