Merge remote-tracking branch 'upstream/2.4' into guides

pull/6952/head
Steve Clement 2021-02-03 14:31:38 +09:00
commit a0a66e2e16
No known key found for this signature in database
GPG Key ID: 69A20F509BE4AEE9
165 changed files with 7609 additions and 5691 deletions

View File

@ -620,7 +620,7 @@ preInstall () {
DBPASSWORD_MISP=$(cat database.php |grep -v // |grep -e password |tr -d \' |tr -d \ |tr -d , |tr -d \> |cut -f 2 -d=)
DBUSER_MISP=$(cat database.php |grep -v // |grep -e login |tr -d \' |tr -d \ |tr -d , |tr -d \> |cut -f 2 -d=)
DBNAME=$(cat database.php |grep -v // |grep -e database |tr -d \' |tr -d \ |tr -d , |tr -d \> |cut -f 2 -d=)
AUTH_KEY=$(mysql --disable-column-names -B -u $DBUSER_MISP -p"$DBPASSWORD_MISP" $DBNAME -e 'SELECT authkey FROM users WHERE role_id=1 LIMIT 1')
AUTH_KEY=$(mysql -h $DBHOST --disable-column-names -B -u $DBUSER_MISP -p"$DBPASSWORD_MISP" $DBNAME -e 'SELECT authkey FROM users WHERE role_id=1 LIMIT 1')
# Check if db exists
[[ -d "/var/lib/mysql/$DBNAME" ]] && MISP_DB_DIR_EXISTS=1 && echo "/var/lib/mysql/$DBNAME exists"
@ -1071,8 +1071,8 @@ nuke () {
sleep 10
sudo rm -rvf /usr/local/src/{misp-modules,viper,mail_to_misp,LIEF,faup}
sudo rm -rvf /var/www/MISP
sudo mysqladmin drop misp
sudo mysql -e "DROP USER misp@localhost"
sudo mysqladmin -h $DBHOST drop misp
sudo mysql -h $DBHOST -e "DROP USER misp@localhost"
}
# Final function to let the user know what happened
@ -1294,25 +1294,25 @@ prepareDB () {
debug "Setting up database"
# Kill the anonymous users
sudo mysql -e "DROP USER IF EXISTS ''@'localhost'"
sudo mysql -h $DBHOST -e "DROP USER IF EXISTS ''@'localhost'"
# Because our hostname varies we'll use some Bash magic here.
sudo mysql -e "DROP USER IF EXISTS ''@'$(hostname)'"
sudo mysql -h $DBHOST -e "DROP USER IF EXISTS ''@'$(hostname)'"
# Kill off the demo database
sudo mysql -e "DROP DATABASE IF EXISTS test"
sudo mysql -h $DBHOST -e "DROP DATABASE IF EXISTS test"
# No root remote logins
sudo mysql -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')"
sudo mysql -h $DBHOST -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')"
# Make sure that NOBODY can access the server without a password
sudo mysqladmin -u "${DBUSER_ADMIN}" password "${DBPASSWORD_ADMIN}"
sudo mysqladmin -h $DBHOST -u "${DBUSER_ADMIN}" password "${DBPASSWORD_ADMIN}"
# Make our changes take effect
sudo mysql -e "FLUSH PRIVILEGES"
sudo mysql -h $DBHOST -e "FLUSH PRIVILEGES"
sudo mysql -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE DATABASE ${DBNAME};"
sudo mysql -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE USER '${DBUSER_MISP}'@'localhost' IDENTIFIED BY '${DBPASSWORD_MISP}';"
sudo mysql -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT USAGE ON *.* to '${DBUSER_MISP}'@'localhost';"
sudo mysql -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT ALL PRIVILEGES on ${DBNAME}.* to '${DBUSER_MISP}'@'localhost';"
sudo mysql -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "FLUSH PRIVILEGES;"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE DATABASE ${DBNAME};"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE USER '${DBUSER_MISP}'@'localhost' IDENTIFIED BY '${DBPASSWORD_MISP}';"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT USAGE ON *.* to '${DBUSER_MISP}'@'localhost';"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT ALL PRIVILEGES on ${DBNAME}.* to '${DBUSER_MISP}'@'localhost';"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "FLUSH PRIVILEGES;"
# Import the empty MISP database from MYSQL.sql
${SUDO_WWW} cat ${PATH_TO_MISP}/INSTALL/MYSQL.sql | mysql -u "${DBUSER_MISP}" -p"${DBPASSWORD_MISP}" ${DBNAME}
${SUDO_WWW} cat ${PATH_TO_MISP}/INSTALL/MYSQL.sql | mysql -h $DBHOST -u "${DBUSER_MISP}" -p"${DBPASSWORD_MISP}" ${DBNAME}
fi
}
@ -1641,7 +1641,7 @@ coreCAKE () {
updateGOWNT () {
# AUTH_KEY Place holder in case we need to **curl** somehing in the future
#
$SUDO_WWW $RUN_MYSQL -- mysql -u $DBUSER_MISP -p$DBPASSWORD_MISP misp -e "SELECT authkey FROM users;" | tail -1 > /tmp/auth.key
$SUDO_WWW $RUN_MYSQL -- mysql -h $DBHOST -u $DBUSER_MISP -p$DBPASSWORD_MISP misp -e "SELECT authkey FROM users;" | tail -1 > /tmp/auth.key
AUTH_KEY=$(cat /tmp/auth.key)
rm /tmp/auth.key
@ -2306,12 +2306,12 @@ EOF
sudo systemctl restart rh-mariadb102-mariadb
scl enable rh-mariadb102 "mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'CREATE DATABASE $DBNAME;'"
scl enable rh-mariadb102 "mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT USAGE on *.* to $DBUSER_MISP@localhost IDENTIFIED by '$DBPASSWORD_MISP';\""
scl enable rh-mariadb102 "mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT ALL PRIVILEGES on $DBNAME.* to '$DBUSER_MISP'@'localhost';\""
scl enable rh-mariadb102 "mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'FLUSH PRIVILEGES;'"
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'CREATE DATABASE $DBNAME;'"
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT USAGE on *.* to $DBUSER_MISP@localhost IDENTIFIED by '$DBPASSWORD_MISP';\""
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT ALL PRIVILEGES on $DBNAME.* to '$DBUSER_MISP'@'localhost';\""
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'FLUSH PRIVILEGES;'"
$SUDO_WWW cat $PATH_TO_MISP/INSTALL/MYSQL.sql | sudo scl enable rh-mariadb102 "mysql -u $DBUSER_MISP -p$DBPASSWORD_MISP $DBNAME"
$SUDO_WWW cat $PATH_TO_MISP/INSTALL/MYSQL.sql | sudo scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_MISP -p$DBPASSWORD_MISP $DBNAME"
}
apacheConfig_RHEL () {

View File

@ -1,5 +1,5 @@
; Generated by RHash v1.4.0 on 2021-01-07 at 11:09.44
; Generated by RHash v1.3.9 on 2021-01-15 at 16:07.48
; Written by Kravchenko Aleksey (Akademgorodok) - http://rhash.sf.net/
;
; 136966 11:09.44 2021-01-07 INSTALL.sh
INSTALL.sh C1C7175A38EF3920725CF1D98B43E3EF6725D347 E9BDA1549D084EE00A3DFCFDD0E37189D2E6A0824EDB94B4B6E27E0BD6536A7A 64F1E52272BAE416EE5FB45C44EAF6E729F3A6BD3E31EE79F51F50A89C9FAB997E51DEB205E2D7A442B92341172C07BD C4FC1412E93A2042EB9300715A3BC80E24E45B65198D064F36F41C56A1F7DC030CB5172177CC26EC0475F5190D96474BF76AF2F5FDB2032197CBFE6D48287BF8
; 137197 16:07.48 2021-01-15 INSTALL.sh
INSTALL.sh AC931C9555B90E9C3B0935492DAA0E7EDC4B4394 E77AC1F6FA1B60AFAE910B86B59ACC5C33E2019D738B3E3380732FA51354D1AD C40E1D6E33EB14394C93E5C3FDFDC8DA66C2216AC0D22D1453578BD47AD052C84CB48EFDEA4F0769697A92A59FEA7E0F 8F5E59632C0B02F9CDAEE5B3B301697EA7ABFF8D1359673499689A4389A25D059625EBC2B650C114AD114A336ECA609A588FF6C6633FFDCE599F69A7717BD0F8

View File

@ -1 +1 @@
c1c7175a38ef3920725cf1d98b43e3ef6725d347 INSTALL.sh
ac931c9555b90e9c3b0935492daa0e7edc4b4394 INSTALL.sh

View File

@ -1 +1 @@
e9bda1549d084ee00a3dfcfdd0e37189d2e6a0824edb94b4b6e27e0bd6536a7a INSTALL.sh
e77ac1f6fa1b60afae910b86b59acc5c33e2019d738b3e3380732fa51354d1ad INSTALL.sh

View File

@ -1 +1 @@
64f1e52272bae416ee5fb45c44eaf6e729f3a6bd3e31ee79f51f50a89c9fab997e51deb205e2d7a442b92341172c07bd INSTALL.sh
c40e1d6e33eb14394c93e5c3fdfdc8da66c2216ac0d22d1453578bd47ad052c84cb48efdea4f0769697a92a59fea7e0f INSTALL.sh

View File

@ -1 +1 @@
c4fc1412e93a2042eb9300715a3bc80e24e45b65198d064f36f41c56a1f7dc030cb5172177cc26ec0475f5190d96474bf76af2f5fdb2032197cbfe6d48287bf8 INSTALL.sh
8f5e59632c0b02f9cdaee5b3b301697ea7abff8d1359673499689a4389a25d059625ebc2b650c114ad114a336eca609a588ff6c6633ffdce599f69a7717bd0f8 INSTALL.sh

2
PyMISP

@ -1 +1 @@
Subproject commit 3745e7153d31bb1722170b57449ce29642f53592
Subproject commit 5b97b7d0158906cd0f646a7273a3ca5b1828cd15

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":136}
{"major":2, "minor":4, "hotfix":137}

View File

@ -6,6 +6,7 @@ $config = array(
'level' => 'medium',
'salt' => '',
'cipherSeed' => '',
'require_password_confirmation' => true
//'auth'=>array('CertAuth.Certificate'), // additional authentication methods
//'auth'=>array('ShibbAuth.ApacheShibb'),
),

View File

@ -655,4 +655,15 @@ class AdminShell extends AppShell
echo $result . PHP_EOL;
}
}
public function cleanExcludedCorrelations()
{
$jobId = $this->args[0];
$this->CorrelationExclusion = ClassRegistry::init('CorrelationExclusion');
$this->CorrelationExclusion->clean($jobId);
$this->Job->id = $jobId;
$this->Job->saveField('progress', 100);
$this->Job->saveField('message', 'Job done.');
$this->Job->saveField('status', 4);
}
}

View File

@ -1,27 +1,4 @@
<?php
/**
* Application level Controller
*
* This file is application-wide controller file. You can put all
* application-wide controller-related methods here.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package app.Controller
* @since CakePHP(tm) v 0.2.9
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
// TODO GnuPG encryption has issues when keys are expired
App::uses('ConnectionManager', 'Model');
App::uses('Controller', 'Controller');
App::uses('File', 'Utility');
@ -36,31 +13,30 @@ App::uses('RequestRearrangeTool', 'Tools');
* @package app.Controller
* @link http://book.cakephp.org/2.0/en/controllers.html#the-app-controller
*
* @throws ForbiddenException // TODO Exception
* @property ACLComponent $ACL
* @property RestResponseComponent $RestResponse
* @property CRUDComponent $CRUD
* @property IndexFilterComponent $IndexFilter
* @property RateLimitComponent $RateLimit
*/
class AppController extends Controller
{
public $defaultModel = '';
public $debugMode = false;
public $helpers = array('OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
public $helpers = array('Utility', 'OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
private $__queryVersion = '120';
public $pyMispVersion = '2.4.135';
private $__queryVersion = '122';
public $pyMispVersion = '2.4.137';
public $phpmin = '7.2';
public $phprec = '7.4';
public $phptoonew = '8.0';
public $pythonmin = '3.6';
public $pythonrec = '3.7';
public $isApiAuthed = false;
private $isApiAuthed = false;
public $baseurl = '';
public $sql_dump = false;
private $isRest = null;
public $restResponsePayload = null;
// Used for _isAutomation(), a check that returns true if the controller & action combo matches an action that is a non-xml and non-json automation method
@ -72,6 +48,8 @@ class AppController extends Controller
);
protected $_legacyParams = array();
/** @var array */
public $userRole;
/** @var User */
public $User;
@ -114,14 +92,12 @@ class AppController extends Controller
public function beforeFilter()
{
$this->Auth->loginRedirect = Configure::read('MISP.baseurl') . '/users/routeafterlogin';
$this->_setupBaseurl();
$this->Auth->loginRedirect = $this->baseurl. '/users/routeafterlogin';
$customLogout = Configure::read('Plugin.CustomAuth_custom_logout');
if ($customLogout) {
$this->Auth->logoutRedirect = $customLogout;
} else {
$this->Auth->logoutRedirect = Configure::read('MISP.baseurl') . '/users/login';
}
$this->Auth->logoutRedirect = $customLogout ?: ($this->baseurl . '/users/login');
$this->__sessionMassage();
if (Configure::read('Security.allow_cors')) {
// Add CORS headers
@ -152,8 +128,8 @@ class AppController extends Controller
$this->sql_dump = intval($this->params['named']['sql']);
}
$this->_setupDatabaseConnection();
$this->_setupDebugMode();
$this->_setupDatabaseConnection();
$this->set('ajax', $this->request->is('ajax'));
$this->set('queryVersion', $this->__queryVersion);
@ -166,17 +142,19 @@ class AppController extends Controller
Configure::write('Config.language', 'eng');
}
//if fresh installation (salt empty) generate a new salt
// For fresh installation (salt empty) generate a new salt
if (!Configure::read('Security.salt')) {
$this->loadModel('Server');
$this->Server->serverSettingsSaveValue('Security.salt', $this->User->generateRandomPassword(32));
}
// Check if the instance has a UUID, if not assign one.
if (!Configure::read('MISP.uuid')) {
$this->loadModel('Server');
$this->Server->serverSettingsSaveValue('MISP.uuid', CakeText::uuid());
}
// check if Apache provides kerberos authentication data
// Check if Apache provides kerberos authentication data
$authUserFields = $this->User->describeAuthFields();
$envvar = Configure::read('ApacheSecureAuth.apacheEnv');
if ($envvar && isset($_SERVER[$envvar])) {
@ -196,10 +174,9 @@ class AppController extends Controller
}
Configure::write('CurrentController', $this->params['controller']);
Configure::write('CurrentAction', $this->params['action']);
$versionArray = $this->{$this->modelClass}->checkMISPVersion();
$versionArray = $this->User->checkMISPVersion();
$this->mispVersion = implode('.', array_values($versionArray));
$this->Security->blackHoleCallback = 'blackHole';
$this->_setupBaseurl();
// send users away that are using ancient versions of IE
// Make sure to update this if IE 20 comes out :)
@ -213,6 +190,27 @@ class AppController extends Controller
$userLoggedIn = $this->__customAuthentication($_SERVER);
}
if ($this->_isRest()) {
$jsonDecode = function ($dataToDecode) {
if (empty($dataToDecode)) {
return null;
}
try {
if (defined('JSON_THROW_ON_ERROR')) {
// JSON_THROW_ON_ERROR is supported since PHP 7.3
return json_decode($dataToDecode, true, 512, JSON_THROW_ON_ERROR);
} else {
$decoded = json_decode($dataToDecode, true);
if ($decoded === null) {
throw new UnexpectedValueException('Could not parse JSON: ' . json_last_error_msg(), json_last_error());
}
return $decoded;
}
} catch (Exception $e) {
throw new HttpException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.', 405, $e);
}
};
// Throw exception if JSON in request is invalid. Default CakePHP behaviour would just ignore that error.
$this->RequestHandler->addInputType('json', [$jsonDecode]);
$this->Security->unlockedActions = array($this->action);
}
@ -227,232 +225,68 @@ class AppController extends Controller
// REST authentication
if ($this->_isRest() || $this->_isAutomation()) {
// disable CSRF for REST access
if (array_key_exists('Security', $this->components)) {
if (isset($this->components['Security'])) {
$this->Security->csrfCheck = false;
}
// If enabled, allow passing the API key via a named parameter (for crappy legacy systems only)
$namedParamAuthkey = false;
if (Configure::read('Security.allow_unsafe_apikey_named_param') && !empty($this->params['named']['apikey'])) {
$namedParamAuthkey = $this->params['named']['apikey'];
}
// Authenticate user with authkey in Authorization HTTP header
if (!empty($_SERVER['HTTP_AUTHORIZATION']) || !empty($namedParamAuthkey)) {
$found_misp_auth_key = false;
$authentication = explode(',', $_SERVER['HTTP_AUTHORIZATION']);
if (!empty($namedParamAuthkey)) {
$authentication[] = $namedParamAuthkey;
}
$user = false;
foreach ($authentication as $auth_key) {
if (preg_match('/^[a-zA-Z0-9]{40}$/', trim($auth_key))) {
$found_misp_auth_key = true;
$temp = $this->checkAuthUser(trim($auth_key));
if ($temp) {
$user['User'] = $temp;
}
}
}
if ($found_misp_auth_key) {
if ($user) {
unset($user['User']['gpgkey']);
unset($user['User']['certif_public']);
// User found in the db, add the user info to the session
if (Configure::read('MISP.log_auth')) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$log = array(
'org' => $user['User']['Organisation']['name'],
'model' => 'User',
'model_id' => $user['User']['id'],
'email' => $user['User']['email'],
'action' => 'auth',
'title' => 'Successful authentication using API key',
'change' => 'HTTP method: ' . $_SERVER['REQUEST_METHOD'] . PHP_EOL . 'Target: ' . $this->here,
);
$this->Log->save($log);
}
$this->Session->renew();
$this->Session->write(AuthComponent::$sessionKey, $user['User']);
$this->isApiAuthed = true;
} else {
// User not authenticated correctly
// reset the session information
$redis = $this->{$this->modelClass}->setupRedis();
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . trim($auth_key))) {
$redis->set('misp:auth_fail_throttling:' . trim($auth_key), 1);
$redis->expire('misp:auth_fail_throttling:' . trim($auth_key), 3600);
$this->Session->destroy();
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$log = array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'auth_fail',
'title' => 'Failed authentication using API key (' . trim($auth_key) . ')',
'change' => null,
);
$this->Log->save($log);
}
throw new ForbiddenException('Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header.');
}
unset($user);
}
}
if ($this->Auth->user() == null) {
if ($this->__loginByAuthKey() === false || $this->Auth->user() === null) {
throw new ForbiddenException('Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header.');
}
} elseif (!$this->Session->read(AuthComponent::$sessionKey)) {
$this->_loadAuthenticationPlugins();
}
}
$this->set('externalAuthUser', $userLoggedIn);
// user must accept terms
//
// grab the base path from our base url for use in the following checks
$base_dir = parse_url($this->baseurl, PHP_URL_PATH);
// if MISP is running out of the web root already, just set this variable to blank so we don't wind up with '//' in the following if statements
if ($base_dir == '/') {
$base_dir = '';
}
$user = $this->Auth->user();
if ($user) {
Configure::write('CurrentUserId', $user['id']);
$this->__logAccess($user);
if ($this->Auth->user()) {
Configure::write('CurrentUserId', $this->Auth->user('id'));
$this->User->setMonitoring($this->Auth->user());
if (Configure::read('MISP.log_user_ips')) {
$redis = $this->{$this->modelClass}->setupRedis();
if ($redis) {
$redis->set('misp:ip_user:' . trim($_SERVER['REMOTE_ADDR']), $this->Auth->user('id'));
$redis->expire('misp:ip_user:' . trim($_SERVER['REMOTE_ADDR']), 60*60*24*30);
$redis->sadd('misp:user_ip:' . $this->Auth->user('id'), trim($_SERVER['REMOTE_ADDR']));
// Try to run updates
if ($user['Role']['perm_site_admin'] || (Configure::read('MISP.live') && !$this->_isRest())) {
$this->User->runUpdates();
}
// Put username to response header for webserver or proxy logging
if (Configure::read('Security.username_in_response_header')) {
$headerValue = $user['email'];
if (isset($user['logged_by_authkey']) && $user['logged_by_authkey']) {
$headerValue .= isset($user['authkey_id']) ? "/API/{$user['authkey_id']}" : '/API/default';
}
$this->response->header('X-Username', $headerValue);
$this->RestResponse->setHeader('X-Username', $headerValue);
}
// update script
if ($this->Auth->user('Role')['perm_site_admin'] || (Configure::read('MISP.live') && !$this->_isRest())) {
$this->{$this->modelClass}->runUpdates();
if (!$this->__verifyUser($user)) {
$this->_stop(); // just for sure
}
$user = $this->Auth->user();
if (!isset($user['force_logout']) || $user['force_logout']) {
$this->loadModel('User');
$this->User->id = $this->Auth->user('id');
$this->User->saveField('force_logout', false);
if (isset($user['logged_by_authkey']) && $user['logged_by_authkey'] && !($this->_isRest() || $this->_isAutomation())) {
throw new ForbiddenException("When user is authenticated by authkey, just REST request can be processed");
}
if ($this->Auth->user('disabled')) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$log = array(
'org' => $this->Auth->user('Organisation')['name'],
'model' => 'User',
'model_id' => $this->Auth->user('id'),
'email' => $this->Auth->user('email'),
'action' => 'auth_fail',
'title' => 'Login attempt by disabled user.',
'change' => null,
);
$this->Log->save($log);
$this->Auth->logout();
if ($this->_isRest()) {
throw new ForbiddenException('Authentication failed. Your user account has been disabled.');
} else {
$this->Flash->error('Your user account has been disabled.', array('key' => 'error'));
$this->_redirectToLogin();
}
// Put token expiration time to response header that can be processed by automation tool
if (isset($user['authkey_expiration']) && $user['authkey_expiration']) {
$expiration = date('c', $user['authkey_expiration']);
$this->response->header('X-Auth-Key-Expiration', $expiration);
$this->RestResponse->setHeader('X-Auth-Key-Expiration', $expiration);
}
$this->set('default_memory_limit', ini_get('memory_limit'));
if (isset($this->Auth->user('Role')['memory_limit'])) {
if ($this->Auth->user('Role')['memory_limit'] !== '') {
ini_set('memory_limit', $this->Auth->user('Role')['memory_limit']);
}
if (isset($user['Role']['memory_limit']) && $user['Role']['memory_limit'] !== '') {
ini_set('memory_limit', $user['Role']['memory_limit']);
}
$this->set('default_max_execution_time', ini_get('max_execution_time'));
if (isset($this->Auth->user('Role')['max_execution_time'])) {
if ($this->Auth->user('Role')['max_execution_time'] !== '') {
ini_set('max_execution_time', $this->Auth->user('Role')['max_execution_time']);
}
if (isset($user['Role']['max_execution_time']) && $user['Role']['max_execution_time'] !== '') {
ini_set('max_execution_time', $user['Role']['max_execution_time']);
}
} else {
$pre_auth_actions = array('login', 'register');
if (!empty(Configure::read('Security.email_otp_enabled'))) {
$pre_auth_actions[] = 'email_otp';
}
if ($this->params['controller'] !== 'users' || !in_array($this->params['action'], $pre_auth_actions)) {
if (!$this->request->is('ajax')) {
$this->Session->write('pre_login_requested_url', $this->here);
}
$this->_redirectToLogin();
}
}
// check if MISP is live
if ($this->Auth->user() && !Configure::read('MISP.live')) {
$role = $this->getActions();
if (!$role['perm_site_admin']) {
$message = Configure::read('MISP.maintenance_message');
if (empty($message)) {
$this->loadModel('Server');
$message = $this->Server->serverSettings['MISP']['maintenance_message']['value'];
}
if (strpos($message, '$email') && Configure::read('MISP.email')) {
$email = Configure::read('MISP.email');
$message = str_replace('$email', $email, $message);
}
$this->Flash->info($message);
$this->Auth->logout();
throw new MethodNotAllowedException($message);//todo this should pb be removed?
} else {
$this->Flash->error(__('Warning: MISP is currently disabled for all users. Enable it in Server Settings (Administration -> Server Settings -> MISP tab -> live). An update might also be in progress, you can see the progress in ') , array('params' => array('url' => $this->baseurl . '/servers/updateProgress/', 'urlName' => __('Update Progress')), 'clear' => 1));
}
}
if ($this->Session->check(AuthComponent::$sessionKey)) {
if ($this->action !== 'checkIfLoggedIn' || $this->request->params['controller'] !== 'users') {
$this->User->id = $this->Auth->user('id');
if (!$this->User->exists()) {
$message = __('Something went wrong. Your user account that you are authenticated with doesn\'t exist anymore.');
if ($this->_isRest) {
echo $this->RestResponse->throwException(
401,
$message
);
} else {
$this->Flash->info($message);
}
$this->Auth->logout();
$this->_redirectToLogin();
}
if (!empty(Configure::read('MISP.terms_file')) && !$this->Auth->user('termsaccepted') && (!in_array($this->request->here, array($base_dir.'/users/terms', $base_dir.'/users/logout', $base_dir.'/users/login', $base_dir.'/users/downloadTerms')))) {
//if ($this->_isRest()) throw new MethodNotAllowedException('You have not accepted the terms of use yet, please log in via the web interface and accept them.');
if (!$this->_isRest()) {
$this->redirect(array('controller' => 'users', 'action' => 'terms', 'admin' => false));
}
} elseif ($this->Auth->user('change_pw') && (!in_array($this->request->here, array($base_dir.'/users/terms', $base_dir.'/users/change_pw', $base_dir.'/users/logout', $base_dir.'/users/login')))) {
//if ($this->_isRest()) throw new MethodNotAllowedException('Your user account is expecting a password change, please log in via the web interface and change it before proceeding.');
if (!$this->_isRest()) {
$this->redirect(array('controller' => 'users', 'action' => 'change_pw', 'admin' => false));
}
} elseif (!$this->_isRest() && !($this->params['controller'] == 'news' && $this->params['action'] == 'index') && (!in_array($this->request->here, array($base_dir.'/users/terms', $base_dir.'/users/change_pw', $base_dir.'/users/logout', $base_dir.'/users/login')))) {
$newsread = $this->User->field('newsread', array('User.id' => $this->Auth->user('id')));
$this->loadModel('News');
$latest_news = $this->News->field('date_created', array(), 'date_created DESC');
if ($latest_news && $newsread < $latest_news) {
$this->redirect(array('controller' => 'news', 'action' => 'index', 'admin' => false));
}
}
}
}
unset($base_dir);
// We don't want to run these role checks before the user is logged in, but we want them available for every view once the user is logged on
// instead of using checkAction(), like we normally do from controllers when trying to find out about a permission flag, we can use getActions()
// getActions returns all the flags in a single SQL query
if ($this->Auth->user()) {
$this->set('mispVersion', implode('.', array($versionArray['major'], $versionArray['minor'], 0)));
$this->set('mispVersion', "{$versionArray['major']}.{$versionArray['minor']}.0");
$this->set('mispVersionFull', $this->mispVersion);
$role = $this->getActions();
$this->set('me', $this->Auth->user());
$this->set('me', $user);
$role = $user['Role'];
$this->set('isAdmin', $role['perm_admin']);
$this->set('isSiteAdmin', $role['perm_site_admin']);
$this->set('hostOrgUser', $this->Auth->user('org_id') == Configure::read('MISP.host_org_id'));
$this->set('hostOrgUser', $user['org_id'] == Configure::read('MISP.host_org_id'));
$this->set('isAclAdd', $role['perm_add']);
$this->set('isAclModify', $role['perm_modify']);
$this->set('isAclModifyOrg', $role['perm_modify_org']);
@ -475,48 +309,26 @@ class AppController extends Controller
$this->set('aclComponent', $this->ACL);
$this->userRole = $role;
$this->set('loggedInUserName', $this->__convertEmailToName($this->Auth->user('email')));
$this->set('loggedInUserName', $this->__convertEmailToName($user['email']));
$this->__accessMonitor($user);
if (
Configure::read('MISP.log_paranoid') ||
!empty(Configure::read('Security.monitored'))
) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$change = 'HTTP method: ' . $_SERVER['REQUEST_METHOD'] . PHP_EOL . 'Target: ' . $this->here;
if (
(
$this->request->is('post') ||
$this->request->is('put')
) &&
(
!empty(Configure::read('MISP.log_paranoid_include_post_body')) ||
!empty(Configure::read('Security.monitored'))
)
) {
$payload = $this->request->input();
if (!empty($payload['_Token'])) {
unset($payload['_Token']);
}
$change .= PHP_EOL . 'Request body: ' . json_encode($payload);
}
$log = array(
'org' => $this->Auth->user('Organisation')['name'],
'model' => 'User',
'model_id' => $this->Auth->user('id'),
'email' => $this->Auth->user('email'),
'action' => 'request',
'title' => 'Paranoid log entry',
'change' => $change,
);
$this->Log->save($log);
}
} else {
$pre_auth_actions = array('login', 'register', 'getGpgPublicKey');
if (!empty(Configure::read('Security.email_otp_enabled'))) {
$pre_auth_actions[] = 'email_otp';
}
if (!$this->_isControllerAction(['users' => $pre_auth_actions])) {
if (!$this->request->is('ajax')) {
$this->Session->write('pre_login_requested_url', $this->here);
}
$this->_redirectToLogin();
}
$this->set('me', false);
}
if ($this->Auth->user() && $this->_isSiteAdmin()) {
if (Configure::read('Session.defaults') == 'database') {
if (Configure::read('Session.defaults') === 'database') {
$db = ConnectionManager::getDataSource('default');
$sqlResult = $db->query('SELECT COUNT(id) AS session_count FROM cake_sessions WHERE expires < ' . time() . ';');
if (isset($sqlResult[0][0]['session_count']) && $sqlResult[0][0]['session_count'] > 1000) {
@ -545,28 +357,329 @@ class AppController extends Controller
}
}
}
$this->components['RestResponse']['sql_dump'] = $this->sql_dump;
// Notifications and homepage is not necessary for AJAX or REST requests
if ($this->Auth->user() && !$this->_isRest() && !$this->request->is('ajax')) {
if ($this->request->params['controller'] === 'users' && $this->request->params['action'] === 'dashboard') {
$notifications = $this->{$this->modelClass}->populateNotifications($this->Auth->user());
$notifications = $this->User->populateNotifications($this->Auth->user());
} else {
$notifications = $this->{$this->modelClass}->populateNotifications($this->Auth->user(), 'fast');
$notifications = $this->User->populateNotifications($this->Auth->user(), 'fast');
}
$this->set('notifications', $notifications);
$this->loadModel('UserSetting');
$homepage = $this->UserSetting->find('first', array(
'recursive' => -1,
'conditions' => array(
'UserSetting.user_id' => $this->Auth->user('id'),
'UserSetting.setting' => 'homepage'
),
'contain' => array('User.id', 'User.org_id')
));
$homepage = $this->User->UserSetting->getValueForUser($this->Auth->user('id'), 'homepage');
if (!empty($homepage)) {
$this->set('homepage', $homepage['UserSetting']['value']);
$this->set('homepage', $homepage);
}
if (version_compare(phpversion(), '8.0') >= 0) {
$this->Flash->error(__('WARNING: MISP is currently running under PHP 8.0, which is unsupported. Background jobs will fail, so please contact your administrator to run a supported PHP version (such as 7.4)'));
}
}
}
/**
* @return null|bool True if authkey was correct, False if incorrect and Null if not provided
* @throws Exception
*/
private function __loginByAuthKey()
{
if (Configure::read('Security.authkey_keep_session') && $this->Auth->user()) {
// Do not check authkey if session is establish and correct, just close session to allow multiple requests
session_write_close();
return true;
}
// If enabled, allow passing the API key via a named parameter (for crappy legacy systems only)
$namedParamAuthkey = false;
if (Configure::read('Security.allow_unsafe_apikey_named_param') && !empty($this->params['named']['apikey'])) {
$namedParamAuthkey = $this->params['named']['apikey'];
}
// Authenticate user with authkey in Authorization HTTP header
if (!empty($_SERVER['HTTP_AUTHORIZATION']) || !empty($namedParamAuthkey)) {
$foundMispAuthKey = false;
$authentication = explode(',', $_SERVER['HTTP_AUTHORIZATION']);
if (!empty($namedParamAuthkey)) {
$authentication[] = $namedParamAuthkey;
}
$user = false;
foreach ($authentication as $authKey) {
$authKey = trim($authKey);
if (preg_match('/^[a-zA-Z0-9]{40}$/', $authKey)) {
$foundMispAuthKey = true;
$temp = $this->checkAuthUser($authKey);
if ($temp) {
$user = $temp;
break;
}
}
}
if ($foundMispAuthKey) {
$authKeyToStore = substr($authKey, 0, 4)
. str_repeat('*', 32)
. substr($authKey, -4);
if ($user) {
unset($user['gpgkey']);
unset($user['certif_public']);
// 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'],
'model' => 'User',
'model_id' => $user['id'],
'email' => $user['email'],
'action' => 'auth',
'title' => "Successful authentication using API key ($authKeyToStore)",
'change' => 'HTTP method: ' . $_SERVER['REQUEST_METHOD'] . PHP_EOL . 'Target: ' . $this->here,
);
$this->Log->save($log);
}
$this->Session->renew();
$this->Session->write(AuthComponent::$sessionKey, $user);
$this->isApiAuthed = true;
return true;
} else {
// User not authenticated correctly
// reset the session information
$redis = $this->User->setupRedis();
// Do not log every fail, but just once per hour
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $authKeyToStore)) {
$redis->setex('misp:auth_fail_throttling:' . $authKeyToStore, 3600, 1);
$this->loadModel('Log');
$this->Log->create();
$log = array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'auth_fail',
'title' => "Failed authentication using API key ($authKeyToStore)",
'change' => null,
);
$this->Log->save($log);
}
$this->Session->destroy();
}
}
return false;
}
return null;
}
/**
* Check if:
* - user exists in database
* - is not disabled
* - need to force logout
* - accepted terms and conditions
* - must change password
* - reads latest news
*
* @param array $user
* @return bool
*/
private function __verifyUser(array $user)
{
// Skip these checks for 'checkIfLoggedIn' action to make that call fast
if ($this->_isControllerAction(['users' => ['checkIfLoggedIn']])) {
return true;
}
// Load last user profile modification from database
$userFromDb = $this->User->find('first', [
'conditions' => ['id' => $user['id']],
'recursive' => -1,
'fields' => ['date_modified'],
]);
// Check if user with given ID exists
if (!$userFromDb) {
$message = __('Something went wrong. Your user account that you are authenticated with doesn\'t exist anymore.');
if ($this->_isRest()) {
// TODO: Why not exception?
$response = $this->RestResponse->throwException(401, $message);
$response->send();
$this->_stop();
} else {
$this->Flash->info($message);
$this->Auth->logout();
$this->_redirectToLogin();
}
return false;
}
// Check if session data contain latest changes from db
if ((int)$user['date_modified'] < (int)$userFromDb['User']['date_modified']) {
$user = $this->_refreshAuth(); // session data are old, reload from database
}
// Check if MISP access is enabled
if (!Configure::read('MISP.live')) {
if (!$user['Role']['perm_site_admin']) {
$message = Configure::read('MISP.maintenance_message');
if (empty($message)) {
$this->loadModel('Server');
$message = $this->Server->serverSettings['MISP']['maintenance_message']['value'];
}
if (strpos($message, '$email') && Configure::read('MISP.email')) {
$email = Configure::read('MISP.email');
$message = str_replace('$email', $email, $message);
}
$this->Flash->info($message);
$this->Auth->logout();
throw new MethodNotAllowedException($message);//todo this should pb be removed?
} else {
$this->Flash->error(__('Warning: MISP is currently disabled for all users. Enable it in Server Settings (Administration -> Server Settings -> MISP tab -> live). An update might also be in progress, you can see the progress in ') , array('params' => array('url' => $this->baseurl . '/servers/updateProgress/', 'urlName' => __('Update Progress')), 'clear' => 1));
}
}
// Force logout doesn't make sense for API key authentication
if (!$this->isApiAuthed && $user['force_logout']) {
$this->User->id = $user['id'];
$this->User->saveField('force_logout', false);
$this->Auth->logout();
$this->_redirectToLogin();
return false;
}
if ($user['disabled']) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], 'Login attempt by disabled user.');
$this->Auth->logout();
if ($this->_isRest()) {
throw new ForbiddenException('Authentication failed. Your user account has been disabled.');
} else {
$this->Flash->error(__('Your user account has been disabled.'));
$this->_redirectToLogin();
}
return false;
}
// Check if auth key is not expired. Make sense when Security.authkey_keep_session is enabled.
if (isset($user['authkey_expiration']) && $user['authkey_expiration']) {
$time = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
if ($user['authkey_expiration'] < $time) {
$this->Auth->logout();
throw new ForbiddenException('Auth key is expired');
}
}
$isUserRequest = !$this->_isRest() && !$this->request->is('ajax') && !$this->_isAutomation();
// Next checks makes sense just for user direct HTTP request, so skip REST and AJAX calls
if (!$isUserRequest) {
return true;
}
// Check if user accepted terms and conditions
if (!$user['termsaccepted'] && !empty(Configure::read('MISP.terms_file')) && !$this->_isControllerAction(['users' => ['terms', 'logout', 'login', 'downloadTerms']])) {
//if ($this->_isRest()) throw new MethodNotAllowedException('You have not accepted the terms of use yet, please log in via the web interface and accept them.');
$this->redirect(array('controller' => 'users', 'action' => 'terms', 'admin' => false));
return false;
}
// Check if user must change password
if ($user['change_pw'] && !$this->_isControllerAction(['users' => ['terms', 'change_pw', 'logout', 'login']])) {
//if ($this->_isRest()) throw new MethodNotAllowedException('Your user account is expecting a password change, please log in via the web interface and change it before proceeding.');
$this->redirect(array('controller' => 'users', 'action' => 'change_pw', 'admin' => false));
return false;
}
// Check if user must read news
if (!$this->_isControllerAction(['news' => ['index'], 'users' => ['terms', 'change_pw', 'login', 'logout']])) {
$this->loadModel('News');
$latestNewsCreated = $this->News->field('date_created', array(), 'date_created DESC');
if ($latestNewsCreated && $user['newsread'] < $latestNewsCreated) {
$this->redirect(array('controller' => 'news', 'action' => 'index', 'admin' => false));
return false;
}
}
return true;
}
/**
* @param array $actionsToCheck
* @return bool
*/
private function _isControllerAction($actionsToCheck = [])
{
$controller = Inflector::variable($this->request->params['controller']);
if (!isset($actionsToCheck[$controller])) {
return false;
}
return in_array($this->action, $actionsToCheck[$controller], true);
}
/**
* User access monitoring
* @param array $user
*/
private function __logAccess(array $user)
{
$logUserIps = Configure::read('MISP.log_user_ips');
if (!$logUserIps) {
return;
}
$redis = $this->User->setupRedis();
if (!$redis) {
return;
}
$remoteAddress = trim($_SERVER['REMOTE_ADDR']);
$pipe = $redis->multi(Redis::PIPELINE);
// keep for 30 days
$pipe->setex('misp:ip_user:' . $remoteAddress, 60 * 60 * 24 * 30, $user['id']);
$pipe->sadd('misp:user_ip:' . $user['id'], $remoteAddress);
// Log key usage if enabled
if (isset($user['authkey_id']) && Configure::read('MISP.log_user_ips_authkeys')) {
// Use request time if defined
$time = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
$hashKey = date("Y-m-d", $time) . ":$remoteAddress";
$pipe->hIncrBy("misp:authkey_usage:{$user['authkey_id']}", $hashKey, 1);
// delete after one year of inactivity
$pipe->expire("misp:authkey_usage:{$user['authkey_id']}", 3600 * 24 * 365);
$pipe->set("misp:authkey_last_usage:{$user['authkey_id']}", $time);
}
$pipe->exec();
}
/**
* @param array $user
* @throws Exception
*/
private function __accessMonitor(array $user)
{
$userMonitoringEnabled = Configure::read('Security.user_monitoring_enabled');
if ($userMonitoringEnabled) {
$redis = $this->User->setupRedis();
$userMonitoringEnabled = $redis && $redis->sismember('misp:monitored_users', $user['id']);
}
if (Configure::read('MISP.log_paranoid') || $userMonitoringEnabled) {
$change = 'HTTP method: ' . $_SERVER['REQUEST_METHOD'] . PHP_EOL . 'Target: ' . $this->here;
if (
(
$this->request->is('post') ||
$this->request->is('put')
) &&
(
!empty(Configure::read('MISP.log_paranoid_include_post_body')) ||
$userMonitoringEnabled
)
) {
$payload = $this->request->input();
$change .= PHP_EOL . 'Request body: ' . $payload;
}
$this->Log = ClassRegistry::init('Log');
try {
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
} catch (Exception $e) {
// When `MISP.log_skip_db_logs_completely` is enabled, Log::createLogEntry method throws exception
}
}
}
@ -601,7 +714,7 @@ class AppController extends Controller
public function afterFilter()
{
if ($this->isApiAuthed && $this->_isRest() && $this->Session->started()) {
if ($this->isApiAuthed && $this->_isRest() && !Configure::read('Security.authkey_keep_session')) {
$this->Session->destroy();
}
}
@ -648,16 +761,17 @@ class AppController extends Controller
/*
* Sanitize the configured `MISP.baseurl` and expose it to the view as `baseurl`.
*/
protected function _setupBaseurl() {
protected function _setupBaseurl()
{
// Let us access $baseurl from all views
$baseurl = Configure::read('MISP.baseurl');
if (substr($baseurl, -1) == '/') {
if (substr($baseurl, -1) === '/') {
// if the baseurl has a trailing slash, remove it. It can lead to issues with the CSRF protection
$baseurl = rtrim($baseurl, '/');
$this->loadModel('Server');
$this->Server->serverSettingsSaveValue('MISP.baseurl', $baseurl);
}
if (trim($baseurl) == 'http://') {
if (trim($baseurl) === 'http://') {
$this->Server->serverSettingsSaveValue('MISP.baseurl', '');
}
$this->baseurl = $baseurl;
@ -683,8 +797,6 @@ class AppController extends Controller
throw new BadRequestException('The request has been black-holed');
}
public $userRole = null;
protected function _isRest()
{
return $this->IndexFilter->isRest();
@ -692,12 +804,7 @@ class AppController extends Controller
protected function _isAutomation()
{
foreach ($this->automationArray as $controllerName => $controllerActions) {
if ($this->params['controller'] == $controllerName && in_array($this->params['action'], $controllerActions)) {
return true;
}
}
return false;
return $this->IndexFilter->isApiFunction($this->params['controller'], $this->params['action']);
}
/**
@ -829,34 +936,12 @@ class AppController extends Controller
return $data;
}
// pass an action to this method for it to check the active user's access to the action
public function checkAction($action = 'perm_sync')
{
$this->loadModel('Role');
$this->Role->recursive = -1;
$role = $this->Role->findById($this->Auth->user('role_id'));
if ($role['Role'][$action]) {
return true;
}
return false;
}
// returns the role of the currently authenticated user as an array, used to set the permission variables for views in the AppController's beforeFilter() method
public function getActions()
{
$this->loadModel('Role');
$this->Role->recursive = -1;
$role = $this->Role->findById($this->Auth->user('role_id'));
return $role['Role'];
}
public function checkAuthUser($authkey)
{
if (Configure::read('Security.advanced_authkeys')) {
$this->loadModel('AuthKey');
$user = $this->AuthKey->getAuthUserByAuthKey($authkey);
} else {
$this->loadModel('User');
$user = $this->User->getAuthUserByAuthKey($authkey);
}
@ -866,22 +951,16 @@ class AppController extends Controller
if (!$user['Role']['perm_auth']) {
return false;
}
if ($user['Role']['perm_site_admin']) {
$user['siteadmin'] = true;
}
$user['logged_by_authkey'] = true;
return $user;
}
public function checkExternalAuthUser($authkey)
{
$this->loadModel('User');
$user = $this->User->getAuthUserByExternalAuth($authkey);
if (empty($user)) {
return false;
}
if ($user['Role']['perm_site_admin']) {
$user['siteadmin'] = true;
}
return $user;
}
@ -1270,7 +1349,7 @@ class AppController extends Controller
$final = $this->$scope->restSearch($user, $returnFormat, $filters, false, false, $elementCounter, $renderView);
if (!empty($renderView) && !empty($final)) {
$this->layout = false;
$final = json_decode($final, true);
$final = json_decode($final->intoString(), true);
foreach ($final as $key => $data) {
$this->set($key, $data);
}
@ -1331,4 +1410,30 @@ class AppController extends Controller
}
return false;
}
/**
* Refresh user data in session, but keep information about authkey.
* @return array User data in auth format
*/
protected function _refreshAuth()
{
$sessionUser = $this->Auth->user();
$user = $this->User->getAuthUser($sessionUser['id']);
if (!$user) {
throw new RuntimeException("User with ID {$sessionUser['id']} not exists.");
}
if (isset($sessionUser['authkey_id'])) {
$this->loadModel('AuthKey');
if (!$this->AuthKey->exists($sessionUser['authkey_id'])) {
throw new RuntimeException("Auth key with ID {$sessionUser['authkey_id']} not exists.");
}
}
foreach (['authkey_id', 'authkey_expiration', 'logged_by_authkey'] as $copy) {
if (isset($sessionUser[$copy])) {
$user[$copy] = $sessionUser[$copy];
}
}
$this->Auth->login($user);
return $user;
}
}

View File

@ -1,6 +1,9 @@
<?php
App::uses('AppController', 'Controller');
/**
* @property AuthKey $AuthKey
*/
class AuthKeysController extends AppController
{
public $components = array(
@ -23,13 +26,22 @@ class AuthKeysController extends AppController
$this->set('user_id', $id);
$conditions['AND'][] = ['AuthKey.user_id' => $id];
}
$keyUsageEnabled = Configure::read('MISP.log_user_ips') && Configure::read('MISP.log_user_ips_authkeys');
$this->CRUD->index([
'filters' => ['User.username', 'authkey', 'comment', 'User.id'],
'quickFilters' => ['authkey', 'comment'],
'contain' => ['User'],
'filters' => ['User.email', 'authkey_start', 'authkey_end', 'comment', 'User.id'],
'quickFilters' => ['comment', 'authkey_start', 'authkey_end', 'User.email'],
'contain' => ['User.id', 'User.email'],
'conditions' => $conditions,
'afterFind' => function (array $authKeys) {
'afterFind' => function (array $authKeys) use ($keyUsageEnabled) {
if ($keyUsageEnabled) {
$keyIds = Hash::extract($authKeys, "{n}.AuthKey.id");
$lastUsedById = $this->AuthKey->getLastUsageForKeys($keyIds);
}
foreach ($authKeys as &$authKey) {
if ($keyUsageEnabled) {
$lastUsed = $lastUsedById[$authKey['AuthKey']['id']];
$authKey['AuthKey']['last_used'] = $lastUsed;
}
unset($authKey['AuthKey']['authkey']);
}
return $authKeys;
@ -38,8 +50,12 @@ class AuthKeysController extends AppController
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('metaGroup', $this->_isAdmin ? 'admin' : 'globalActions');
$this->set('metaAction', 'authkeys_index');
$this->set('title_for_layout', __('Auth Keys'));
$this->set('keyUsageEnabled', $keyUsageEnabled);
$this->set('menuData', [
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
'menuItem' => 'authkeys_index',
]);
}
public function delete($id)
@ -61,18 +77,22 @@ class AuthKeysController extends AppController
public function add($user_id = false)
{
$this->set('menuData', array('menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions', 'menuItem' => 'authKeyAdd'));
$params = [
'displayOnSuccess' => 'authkey_display',
'saveModelVariable' => ['authkey_raw']
'saveModelVariable' => ['authkey_raw'],
'override' => ['authkey' => null], // do not allow to use own key, always generate random one
'afterFind' => function ($authKey) { // remove hashed key from response
unset($authKey['AuthKey']['authkey']);
return $authKey;
}
];
$selectConditions = [];
if (!$this->_isSiteAdmin()) {
$selectConditions['AND'][] = ['User.id' => $this->Auth->user('id')];
$params['override'] = ['user_id' => $this->Auth->user('id')];
$params['override']['user_id'] = $this->Auth->user('id');
} else if ($user_id) {
$selectConditions['AND'][] = ['User.id' => $user_id];
$params['override'] = ['user_id' => $user_id];
$params['override']['user_id'] = $user_id;
}
$this->CRUD->add($params);
if ($this->IndexFilter->isRest()) {
@ -86,6 +106,11 @@ class AuthKeysController extends AppController
])
];
$this->set(compact('dropdownData'));
$this->set('menuData', [
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
'menuItem' => 'authKeyAdd',
]);
$this->set('validity', Configure::read('Security.advanced_authkeys_validity'));
}
public function view($id = false)
@ -101,6 +126,15 @@ class AuthKeysController extends AppController
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
if (Configure::read('MISP.log_user_ips') && Configure::read('MISP.log_user_ips_authkeys')) {
list($keyUsage, $lastUsed, $uniqueIps) = $this->AuthKey->getKeyUsage($id);
$this->set('keyUsage', $keyUsage);
$this->set('lastUsed', $lastUsed);
$this->set('uniqueIps', $uniqueIps);
}
$this->set('title_for_layout', __('Auth Key'));
$this->set('menuData', [
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
'menuItem' => 'authKeyView',

View File

@ -30,13 +30,11 @@ class CerebratesController extends AppController
public function add()
{
$this->set('menuData', array('menuList' => 'sync', 'menuItem' => 'add_cerebrate'));
$params = [];
$this->CRUD->add($params);
if ($this->IndexFilter->isRest()) {
if ($this->restResponsePayload) {
return $this->restResponsePayload;
}
$this->set('permFlags', $this->Role->permFlags);
$this->loadModel('Organisation');
$orgs = $this->Organisation->find('list', [
@ -48,6 +46,7 @@ class CerebratesController extends AppController
'org_id' => $orgs
];
$this->set(compact('dropdownData'));
$this->set('menuData', array('menuList' => 'sync', 'menuItem' => 'add_cerebrate'));
}
public function edit($id)
@ -59,7 +58,6 @@ class CerebratesController extends AppController
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('permFlags', $this->Role->permFlags);
$this->loadModel('Organisation');
$orgs = $this->Organisation->find('list', [

View File

@ -15,13 +15,11 @@ class ACLComponent extends Component
private $__aclList = array(
'*' => array(
'blackhole' => array(),
'checkAction' => array(),
'checkAuthUser' => array(),
'checkExternalAuthUser' => array(),
'cleanModelCaches' => array(),
'debugACL' => array(),
'generateCount' => array(),
'getActions' => array(),
'pruneDuplicateUUIDs' => array(),
'queryACL' => array(),
'removeDuplicateEvents' => array(),
@ -89,6 +87,13 @@ class ACLComponent extends Component
'pull_orgs' => [],
'view' => []
],
'correlationExclusions' => [
'add' => [],
'clean' => [],
'delete' => [],
'index' => [],
'view' => []
],
'dashboards' => array(
'getForm' => array('*'),
'index' => array('*'),
@ -461,7 +466,6 @@ class ACLComponent extends Component
'admin_add' => array(),
'admin_delete' => array(),
'admin_edit' => array(),
'admin_index' => array('perm_admin'),
'admin_set_default' => array(),
'index' => array('*'),
'view' => array('*'),
@ -499,6 +503,7 @@ class ACLComponent extends Component
'postTest' => array('perm_sync'),
'previewEvent' => array(),
'previewIndex' => array(),
'compareServers' => [],
'pull' => array(),
'purgeSessions' => array(),
'push' => array(),
@ -615,6 +620,7 @@ class ACLComponent extends Component
'taxonomyMassUnhide' => array('perm_tagger'),
'toggleRequired' => array('perm_site_admin'),
'update' => array(),
'import' => [],
'view' => array('*'),
'unhideTag' => array('perm_tagger'),
'hideTag' => array('perm_tagger'),
@ -685,6 +691,7 @@ class ACLComponent extends Component
'verifyCertificate' => array(),
'verifyGPG' => array(),
'view' => array('*'),
'getGpgPublicKey' => array('*'),
),
'userSettings' => array(
'index' => array('*'),

View File

@ -2,6 +2,7 @@
class CRUDComponent extends Component
{
/** @var AppController */
public $Controller = null;
public function initialize(Controller $controller, $settings=array()) {
@ -15,7 +16,7 @@ class CRUDComponent extends Component
}
}
public function index($options)
public function index(array $options)
{
$this->prepareResponse();
if (!empty($options['quickFilters'])) {
@ -75,8 +76,6 @@ class CRUDComponent extends Component
$input[$modelName][$field] = $value;
}
}
if (isset($input[$modelName]['id'])) {
}
unset($input[$modelName]['id']);
if (!empty($params['fields'])) {
$data = [];
@ -86,20 +85,25 @@ class CRUDComponent extends Component
} else {
$data = $input;
}
if ($this->Controller->{$modelName}->save($data)) {
$data = $this->Controller->{$modelName}->find('first', [
/** @var Model $model */
$model = $this->Controller->{$modelName};
if ($model->save($data)) {
$data = $model->find('first', [
'recursive' => -1,
'conditions' => [
'id' => $this->Controller->{$modelName}->id
'id' => $model->id
]
]);
if (!empty($params['saveModelVariable'])) {
foreach ($params['saveModelVariable'] as $var) {
if (isset($this->Controller->{$modelName}->$var)) {
$data[$modelName][$var] = $this->Controller->{$modelName}->$var;
if (isset($model->$var)) {
$data[$modelName][$var] = $model->$var;
}
}
}
if (isset($params['afterFind'])) {
$data = $params['afterFind']($data);
}
$message = __('%s added.', $modelName);
if ($this->Controller->IndexFilter->isRest()) {
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
@ -111,12 +115,22 @@ class CRUDComponent extends Component
$this->Controller->render($params['displayOnSuccess']);
return;
}
$this->Controller->redirect(['action' => 'index']);
$redirect = isset($params['redirect']) ? $params['redirect'] : ['action' => 'index'];
// For AJAX requests doesn't make sense to redirect, redirect must be done on javascript side in `submitGenericFormInPlace`
if ($this->Controller->request->is('ajax')) {
$redirect = Router::url($redirect);
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData(['redirect' => $redirect], 'json');
} else {
$this->Controller->redirect($redirect);
}
}
} else {
$message = __('%s could not be added.', $modelName);
if ($this->Controller->IndexFilter->isRest()) {
$controllerName = $this->Controller->params['controller'];
$actionName = $this->Controller->params['action'];
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveFailResponse($controllerName, $actionName, false, $model->validationErrors, 'json');
} else {
$this->Controller->Flash->error($message);
}
@ -161,9 +175,10 @@ class CRUDComponent extends Component
$message = __('%s updated.', $modelName);
if ($this->Controller->IndexFilter->isRest()) {
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
return;
} else {
$this->Controller->Flash->success($message);
$this->Controller->redirect(['action' => 'index']);
$this->Controller->redirect(isset($params['redirect']) ? $params['redirect'] : ['action' => 'index']);
}
} else {
if ($this->Controller->IndexFilter->isRest()) {
@ -224,7 +239,18 @@ class CRUDComponent extends Component
if (empty($data)) {
throw new NotFoundException(__('Invalid %s.', $modelName));
}
if ($this->Controller->request->is('post') || $this->Controller->request->is('delete')) {
$validationError = null;
if (isset($params['validate'])) {
try {
$params['validate']($data);
} catch (Exception $e) {
$validationError = $e->getMessage();
if ($this->Controller->IndexFilter->isRest()) {
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveFailResponse($modelName, 'delete', $id, $validationError);
}
}
}
if ($validationError === null && $this->Controller->request->is('post') || $this->Controller->request->is('delete')) {
if (!empty($params['modelFunction'])) {
$result = $this->Controller->$modelName->{$params['modelFunction']}($id);
} else {
@ -234,36 +260,39 @@ class CRUDComponent extends Component
$message = __('%s deleted.', $modelName);
if ($this->Controller->IndexFilter->isRest()) {
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveSuccessResponse($modelName, 'delete', $id, 'json', $message);
return;
} else {
$this->Controller->Flash->success($message);
$this->Controller->redirect($this->Controller->referer());
}
}
}
$this->Controller->set('validationError', $validationError);
$this->Controller->set('id', $data[$modelName]['id']);
$this->Controller->set('data', $data);
$this->Controller->layout = 'ajax';
$this->Controller->render('/genericTemplates/delete');
}
protected function setQuickFilters($params, $query, $quickFilterFields)
protected function setQuickFilters($params, array $query, $quickFilterFields)
{
$queryConditions = [];
if (!empty($params['quickFilter']) && !empty($quickFilterFields)) {
$queryConditions = [];
$filter = '%' . strtolower($params['quickFilter']) . '%';
foreach ($quickFilterFields as $filterField) {
$queryConditions[$filterField] = $params['quickFilter'];
$queryConditions["LOWER($filterField) LIKE"] = $filter;
}
$query['conditions']['OR'][] = $queryConditions;
$query['conditions']['OR'] = $queryConditions;
}
return $query;
}
protected function setFilters($params, $query)
protected function setFilters(array $params, array $query)
{
$params = $this->massageFilters($params);
if (!empty($params['simpleFilters'])) {
foreach ($params['simpleFilters'] as $filter => $filterValue) {
// For CakePHP 2, we don't need to distinguish between simpleFilters and relatedFilters
//$params = $this->massageFilters($params);
if (!empty($params)) {
foreach ($params as $filter => $filterValue) {
if ($filter === 'quickFilter') {
continue;
}

View File

@ -6,7 +6,8 @@
class IndexFilterComponent extends Component
{
public $Controller = false;
/** @var Controller */
public $Controller;
public $isRest = null;
public function initialize(Controller $controller) {
@ -74,7 +75,7 @@ class IndexFilterComponent extends Component
}
}
}
$this->Controller->set('passedArgs', json_encode($this->Controller->passedArgs, true));
$this->Controller->set('passedArgs', json_encode($this->Controller->passedArgs));
return $data;
}
@ -85,12 +86,7 @@ class IndexFilterComponent extends Component
return $this->isRest;
}
$api = $this->isApiFunction($this->Controller->request->params['controller'], $this->Controller->request->params['action']);
if (isset($this->Controller->RequestHandler) && ($api || $this->Controller->RequestHandler->isXml() || $this->isJson() || $this->isCsv())) {
if ($this->isJson()) {
if (!empty($this->Controller->request->input()) && empty($this->Controller->request->input('json_decode'))) {
throw new MethodNotAllowedException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.');
}
}
if (isset($this->Controller->RequestHandler) && ($api || $this->isJson() || $this->Controller->RequestHandler->isXml() || $this->isCsv())) {
$this->isRest = true;
return true;
} else {
@ -99,11 +95,8 @@ class IndexFilterComponent extends Component
}
}
public function isJson($data=false)
public function isJson()
{
if ($data) {
return (json_decode($data) != null) ? true : false;
}
return $this->Controller->request->header('Accept') === 'application/json' || $this->Controller->RequestHandler->prefers() === 'json';
}
@ -117,12 +110,13 @@ class IndexFilterComponent extends Component
}
/**
* @param string $controller
* @param string $action
* @return bool
*/
public function isApiFunction($controller, $action)
{
if (isset($this->Controller->automationArray[$controller]) && in_array($action, $this->Controller->automationArray[$controller])) {
return true;
}
return false;
return isset($this->Controller->automationArray[$controller]) && in_array($action, $this->Controller->automationArray[$controller], true);
}
}

View File

@ -241,9 +241,9 @@ class RestResponseComponent extends Component
'optional' => array('type', 'source', 'timestamp', 'date', 'time')
),
'restSearch' => array(
'description' => "Search MISP sightings using a list of filter parameters and return the data in the JSON format. The search is available on an event, attribute or instance level, just select the scope via the URL (/sighting/restSearch/event vs /sighting/restSearch/attribute vs /sighting/restSearch/). id MUST be provided if context is set.",
'description' => "Search MISP sightings using a list of filter parameters and return the data in the JSON format. The search is available on an event, attribute or instance level, just select the scope via the URL (/sighting/restSearch/event vs /sighting/restSearch/attribute vs /sighting/restSearch/). id or uuid MUST be provided if context is set.",
'mandatory' => array('returnFormat'),
'optional' => array('id', 'type', 'from', 'to', 'last', 'org_id', 'source', 'includeAttribute', 'includeEvent'),
'optional' => array('id', 'uuid', 'type', 'from', 'to', 'last', 'org_id', 'source', 'includeAttribute', 'includeEvent'),
'params' => array('context')
),
),
@ -519,11 +519,12 @@ class RestResponseComponent extends Component
} else {
$type = $format;
}
$dumpSql = !empty($this->Controller->sql_dump) && Configure::read('debug') > 1;
if (!$raw) {
if (is_string($response)) {
$response = array('message' => $response);
}
if (Configure::read('debug') > 1 && !empty($this->Controller->sql_dump)) {
if ($dumpSql) {
$this->Log = ClassRegistry::init('Log');
if ($this->Controller->sql_dump === 2) {
$response = array('sql_dump' => $this->Log->getDataSource()->getLog(false, false));
@ -533,7 +534,7 @@ class RestResponseComponent extends Component
}
$response = json_encode($response, JSON_PRETTY_PRINT);
} else {
if (Configure::read('debug') > 1 && !empty($this->Controller->sql_dump)) {
if ($dumpSql) {
$this->Log = ClassRegistry::init('Log');
if ($this->Controller->sql_dump === 2) {
$response = json_encode(array('sql_dump' => $this->Log->getDataSource()->getLog(false, false)));
@ -547,7 +548,15 @@ class RestResponseComponent extends Component
}
}
}
$cakeResponse = new CakeResponse(array('body' => $response, 'status' => $code, 'type' => $type));
App::uses('TmpFileTool', 'Tools');
if ($response instanceof TmpFileTool) {
App::uses('CakeResponseTmp', 'Tools');
$cakeResponse = new CakeResponseTmp(['status' => $code, 'type' => $type]);
$cakeResponse->file($response);
} else {
$cakeResponse = new CakeResponse(array('body' => $response, 'status' => $code, 'type' => $type));
}
if (Configure::read('Security.allow_cors')) {
$headers["Access-Control-Allow-Headers"] = "Origin, Content-Type, Authorization, Accept";
@ -1842,33 +1851,36 @@ class RestResponseComponent extends Component
$field['values'][] = array('label' => h($model_name), 'value' => $i);
}
}
private function __overwriteTags($scope, $action, &$field) {
$this->{$scope} = ClassRegistry::init("Tag");
$tags = $this->{$scope}->find('list', array(
'recursive' => -1,
'fields' => array('name')
));
foreach($tags as $i => $tag) {
$tagname = htmlspecialchars($tag);
$tags[$tagname] = $tagname;
unset($tags[$i]);
private function __overwriteTags($scope, $action, &$field)
{
static $values;
if ($values === null) {
$tagModel = ClassRegistry::init("Tag");
$tags = $tagModel->find('column', array(
'fields' => array('Tag.name')
));
$values = [];
foreach ($tags as $tag) {
$tagname = htmlspecialchars($tag);
$values[$tagname] = $tagname;
}
}
$field['values'] = $tags;
$field['values'] = $values;
if ($action == 'attachTagToObject') {
$field['help'] = __('Also supports array of tags');
}
}
private function __overwriteNationality($scope, $action, &$field) {
$field['values'] = ClassRegistry::init("Organisation")->countries;
$field['values'] = ClassRegistry::init("Organisation")->getCountries();
}
private function __overwriteAction($scope, $action, &$field) {
$field['values'] = array_keys(ClassRegistry::init("Log")->actionDefinitions);
}
private function __overwriteRoleId($scope, $action, &$field) {
$this->{$scope} = ClassRegistry::init("Role");
$roles = $this->{$scope}->find('list', array(
'recursive' => -1,
$roles = $this->{$scope}->find('column', array(
'fields' => array('name')
));
$field['values'] = $roles;

View File

@ -0,0 +1,94 @@
<?php
App::uses('AppController', 'Controller');
/**
* @property AuthKey $AuthKey
*/
class CorrelationExclusionsController extends AppController
{
public $components = array(
'Security',
'CRUD',
'RequestHandler'
);
public $paginate = array(
'limit' => 60,
'order' => array(
'CorrelationExclusion.value' => 'ASC',
)
);
public function index($id = false)
{
$this->CRUD->index([
'filters' => ['value'],
'quickFilters' => ['value']
]);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('title_for_layout', __('Correlation Exclusions index'));
$this->set('menuData', [
'menuList' => 'correlationExclusions',
'menuItem' => 'index'
]);
}
public function delete($id)
{
$this->CRUD->delete($id);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
public function add($user_id = false)
{
$params = [];
$this->CRUD->add($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$dropdownData = [];
$this->set(compact('dropdownData'));
$this->set('menuData', [
'menuList' => 'correlationExclusions',
'menuItem' => 'add',
]);
}
public function view($id = false)
{
$this->CRUD->view($id);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('title_for_layout', __('Correlation Exclusion'));
$this->set('menuData', [
'menuList' => 'correlationExclusions',
'menuItem' => 'view',
]);
}
public function clean()
{
if ($this->request->is('post')) {
$this->CorrelationExclusion->cleanRouter($this->Auth->user());
$message = __('Correlations cleanup initiated, based on the exclusion rules.');
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('CorrelationExclusion', 'clean', false, false, $message);
} else {
$this->Flash->success($message);
$this->redirect($this->referer());
}
} else {
$this->set('title', __('Clean up correlations'));
$this->set('question', __('Execute the cleaning of all correlations that are at odds with the exclusion rules? This will delete all matching correlations.'));
$this->set('actionName', 'clean');;
$this->layout = 'ajax';
$this->render('/genericTemplates/confirm');
}
}
}

View File

@ -230,6 +230,7 @@ class DashboardsController extends AppController
public function saveTemplate($update = false)
{
$this->loadModel('UserSetting');
if (!empty($update)) {
$conditions = array('Dashboard.id' => $update);
if (Validation::uuid($update)) {

View File

@ -209,31 +209,30 @@ class EventReportsController extends AppController
{
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
} else {
if ($this->request->is('post')) {
$report = $this->EventReport->fetchIfAuthorized($this->Auth->user(), $reportId, 'edit', $throwErrors=true, $full=false);
$results = $this->EventReport->getComplexTypeToolResultWithReplacements($this->Auth->user(), $report);
$report['EventReport']['content'] = $results['replacementResult']['contentWithReplacements'];
$contextResults = $this->EventReport->extractWithReplacements($this->Auth->user(), $report, ['replace' => true]);
$suggestionResult = $this->EventReport->transformFreeTextIntoSuggestion($contextResults['contentWithReplacements'], $results['complexTypeToolResult']);
$errors = $this->EventReport->applySuggestions($this->Auth->user(), $report, $suggestionResult['contentWithSuggestions'], $suggestionResult['suggestionsMapping']);
if (empty($errors)) {
if (!empty($this->data['EventReport']['tag_event'])) {
$this->EventReport->attachTagsAfterReplacements($this->Auth->User(), $contextResults['replacedContext'], $report['EventReport']['event_id']);
}
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $reportId);
$data = [ 'report' => $report ];
$successMessage = __('Automatic extraction applied to Event Report %s', $reportId);
return $this->__getSuccessResponseBasedOnContext($successMessage, $data, 'applySuggestions', $reportId);
} else {
$errorMessage = __('Automatic extraction could not be applied to Event Report %s.%sReasons: %s', $reportId, PHP_EOL, json_encode($errors));
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'applySuggestions', $reportId);
}
}
$this->layout = 'ajax';
$this->set('reportId', $reportId);
$this->render('ajax/extractAllFromReport');
}
if ($this->request->is('post')) {
$report = $this->EventReport->fetchIfAuthorized($this->Auth->user(), $reportId, 'edit', $throwErrors=true, $full=false);
$results = $this->EventReport->getComplexTypeToolResultWithReplacements($this->Auth->user(), $report);
$report['EventReport']['content'] = $results['replacementResult']['contentWithReplacements'];
$contextResults = $this->EventReport->extractWithReplacements($this->Auth->user(), $report, ['replace' => true]);
$suggestionResult = $this->EventReport->transformFreeTextIntoSuggestion($contextResults['contentWithReplacements'], $results['complexTypeToolResult']);
$errors = $this->EventReport->applySuggestions($this->Auth->user(), $report, $suggestionResult['contentWithSuggestions'], $suggestionResult['suggestionsMapping']);
if (empty($errors)) {
if (!empty($this->data['EventReport']['tag_event'])) {
$this->EventReport->attachTagsAfterReplacements($this->Auth->User(), $contextResults['replacedContext'], $report['EventReport']['event_id']);
}
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $reportId);
$data = [ 'report' => $report ];
$successMessage = __('Automatic extraction applied to Event Report %s', $reportId);
return $this->__getSuccessResponseBasedOnContext($successMessage, $data, 'applySuggestions', $reportId);
} else {
$errorMessage = __('Automatic extraction could not be applied to Event Report %s.%sReasons: %s', $reportId, PHP_EOL, json_encode($errors));
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'applySuggestions', $reportId);
}
}
$this->layout = 'ajax';
$this->set('reportId', $reportId);
$this->render('ajax/extractAllFromReport');
}
public function extractFromReport($reportId)

View File

@ -136,12 +136,12 @@ class EventsController extends AppController
$includeConditions['OR'][] = array('lower(Attribute.value2) LIKE' => $i);
}
$includeIDs = array_values($this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
$includeIDs = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
'conditions' => $includeConditions,
'flatten' => true,
'event_ids' => true,
'list' => true,
)));
));
}
if (!empty($exclude)) {
@ -151,12 +151,12 @@ class EventsController extends AppController
$excludeConditions['OR'][] = array('lower(Attribute.value2) LIKE' => $e);
}
$excludeIDs = array_values($this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
$excludeIDs = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
'conditions' => $excludeConditions,
'flatten' => true,
'event_ids' => true,
'list' => true,
)));
));
}
}
// return -1 as the only value in includedIDs if both arrays are empty. This will mean that no events will be shown if there was no hit
@ -191,15 +191,13 @@ class EventsController extends AppController
$conditions = array(
'OR' => $subconditions,
);
$attributeHits = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
$result = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
'conditions' => $conditions,
'flatten' => 1,
'event_ids' => true,
'list' => true,
));
$result = array_values($attributeHits);
// we now have a list of event IDs that match on an attribute level, and the user can see it. Let's also find all of the events that match on other criteria!
// What is interesting here is that we no longer have to worry about the event's releasability. With attributes this was a different case,
// because we might run into a situation where a user can see an event but not a specific attribute
@ -234,10 +232,9 @@ class EventsController extends AppController
foreach ($values as $v) {
$subconditions[] = array('lower(name) LIKE' => $v);
}
$orgs = $this->Event->Org->find('list', array(
$orgs = $this->Event->Org->find('column', array(
'conditions' => $subconditions,
'recursive' => -1,
'fields' => array('id')
'fields' => array('Org.id')
));
$conditions = empty($result) ? [] : ['NOT' => ['id' => $result]]; // Do not include events that we already found
@ -246,11 +243,10 @@ class EventsController extends AppController
$conditions['OR'][] = array('lower(uuid) LIKE' => $v);
}
if (!empty($orgs)) {
$conditions['OR']['orgc_id'] = array_values($orgs);
$conditions['OR']['orgc_id'] = $orgs;
}
$otherEvents = $this->Event->find('list', array(
'recursive' => -1,
'fields' => array('id'),
$otherEvents = $this->Event->find('column', array(
'fields' => array('Event.id'),
'conditions' => $conditions,
));
foreach ($otherEvents as $eventId) {
@ -311,7 +307,6 @@ class EventsController extends AppController
} else {
$pieces = explode('|', $v);
}
$temp = array();
$eventidConditions = array();
foreach ($pieces as $piece) {
$piece = trim($piece);
@ -454,9 +449,9 @@ class EventsController extends AppController
$test = array();
foreach ($pieces as $piece) {
if ($piece[0] == '!') {
$this->paginate['conditions']['AND'][] = array('lower(Event.info)' . ' NOT LIKE' => '%' . strtolower(substr($piece, 1)) . '%');
$this->paginate['conditions']['AND'][] = array('lower(Event.info) NOT LIKE' => '%' . strtolower(substr($piece, 1)) . '%');
} else {
$test['OR'][] = array('lower(Event.info)' . ' LIKE' => '%' . strtolower($piece) . '%');
$test['OR'][] = array('lower(Event.info) LIKE' => '%' . strtolower($piece) . '%');
}
}
$this->paginate['conditions']['AND'][] = $test;
@ -495,17 +490,13 @@ class EventsController extends AppController
$filterString .= '!' . $piece;
continue;
}
$block = $this->Event->EventTag->find('all', array(
'conditions' => array('EventTag.tag_id' => $tagName['Tag']['id']),
'fields' => 'event_id',
'recursive' => -1,
$block = $this->Event->EventTag->find('column', array(
'conditions' => array('EventTag.tag_id' => $tagName['Tag']['id']),
'fields' => ['EventTag.event_id'],
));
if (!empty($block)) {
$sqlSubQuery = 'Event.id NOT IN (';
foreach ($block as $b) {
$sqlSubQuery .= $b['EventTag']['event_id'] . ',';
}
$tagRules['AND'][] = substr($sqlSubQuery, 0, -1) . ')';
$sqlSubQuery = 'Event.id NOT IN (' . implode(",", $block) . ')';
$tagRules['AND'][] = $sqlSubQuery;
}
if ($filterString != "") {
$filterString .= "|";
@ -532,18 +523,14 @@ class EventsController extends AppController
continue;
}
$allow = $this->Event->EventTag->find('all', array(
'conditions' => array('EventTag.tag_id' => $tagName['Tag']['id']),
'fields' => 'event_id',
'recursive' => -1,
$allow = $this->Event->EventTag->find('column', array(
'conditions' => array('EventTag.tag_id' => $tagName['Tag']['id']),
'fields' => ['EventTag.event_id'],
));
if (!empty($allow)) {
$sqlSubQuery = 'Event.id IN (';
foreach ($allow as $a) {
$setOR = true;
$sqlSubQuery .= $a['EventTag']['event_id'] . ',';
}
$tagRules['OR'][] = substr($sqlSubQuery, 0, -1) . ')';
$setOR = true;
$sqlSubQuery = 'Event.id IN ('. implode(",", $allow) . ')';
$tagRules['OR'][] = $sqlSubQuery;
}
if ($filterString != "") {
$filterString .= "|";
@ -687,22 +674,9 @@ class EventsController extends AppController
}
}
}
$this->set('passedArgs', json_encode($passedArgs));
// check each of the passed arguments whether they're a filter (could also be a sort for example) and if yes, add it to the pagination conditions
$passedArgsArray = $this->__setIndexFilterConditions($passedArgs, $urlparams);
if (!$this->_isRest()) {
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('User.email', 'EventTag'));
} else {
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('User.email'));
}
$this->set('urlparams', $urlparams);
$this->set('passedArgsArray', $passedArgsArray);
$this->paginate = Set::merge($this->paginate, array('contain' => array(
'ThreatLevel' => array(
'fields' => array(
'ThreatLevel.name'))
),
));
$this->loadModel('GalaxyCluster');
// for REST, don't use the pagination. With this, we'll escape the limit of events shown on the index.
@ -721,11 +695,13 @@ class EventsController extends AppController
if (isset($this->paginate['conditions'])) {
$rules['conditions'] = $this->paginate['conditions'];
}
if (!empty($passedArgs['searchminimal']) || !empty($passedArgs['minimal'])) {
unset($rules['contain']);
$minimal = !empty($passedArgs['searchminimal']) || !empty($passedArgs['minimal']);
if ($minimal) {
$rules['recursive'] = -1;
$rules['fields'] = array('id', 'timestamp', 'sighting_timestamp', 'published', 'uuid');
$rules['contain'] = array('Orgc.uuid');
} else {
$rules['contain'][] = 'EventTag';
}
$paginationRules = array('page', 'limit', 'sort', 'direction', 'order');
foreach ($paginationRules as $paginationRule) {
@ -733,86 +709,72 @@ class EventsController extends AppController
$rules[$paginationRule] = $passedArgs[$paginationRule];
}
}
$counting_rules = $rules;
if (!empty($counting_rules['limit'])) {
unset($counting_rules['limit']);
}
if (!empty($counting_rules['page'])) {
unset($counting_rules['page']);
}
$absolute_total = $this->Event->find('count', $counting_rules);
if (empty($rules['limit'])) {
$events = array();
$i = 1;
$continue = true;
$rules['limit'] = 20000;
while ($continue) {
while (true) {
$rules['page'] = $i;
$temp = $this->Event->find('all', $rules);
if (!empty($temp)) {
$resultCount = count($temp);
if ($resultCount !== 0) {
$events = array_merge($events, $temp);
} else {
$continue = false;
}
if ($resultCount < $rules['limit']) {
break;
}
$i += 1;
}
$absolute_total = count($events);
} else {
$events = $this->Event->find('all', $rules);
$counting_rules = $rules;
unset($counting_rules['limit']);
unset($counting_rules['page']);
$absolute_total = $this->Event->find('count', $counting_rules);
$events = $absolute_total === 0 ? [] : $this->Event->find('all', $rules);
}
$total_events = count($events);
foreach ($events as $k => $event) {
if (empty($event['SharingGroup']['name'])) {
unset($events[$k]['SharingGroup']);
if (!$minimal) {
$tagIds = [];
foreach (array_column($events, 'EventTag') as $eventTags) {
foreach (array_column($eventTags, 'tag_id') as $tagId) {
$tagIds[$tagId] = true;
}
}
}
if (empty($passedArgs['searchminimal']) && empty($passedArgs['minimal'])) {
$passes = ceil($total_events / 1000);
for ($i = 0; $i < $passes; $i++) {
$event_tag_objects = array();
$event_tag_ids = array();
$elements = 1000;
if ($i == ($passes-1)) {
$elements = ($total_events % 1000);
}
for ($j = 0; $j < $elements; $j++) {
$event_tag_ids[$events[($i*1000) + $j]['Event']['id']] = true;
}
$eventTags = $this->Event->EventTag->find('all', array(
if (!empty($tagIds)) {
$tags = $this->Event->EventTag->Tag->find('all', [
'conditions' => [
'Tag.id' => array_keys($tagIds),
'Tag.exportable' => 1,
],
'recursive' => -1,
'conditions' => array(
'EventTag.event_id' => array_keys($event_tag_ids)
),
'contain' => array(
'Tag' => array(
'conditions' => array('Tag.exportable' => 1),
'fields' => array('Tag.id', 'Tag.name', 'Tag.colour', 'Tag.is_galaxy')
)
)
));
foreach ($eventTags as $ket => $et) {
if (empty($et['Tag']['id'])) {
unset($eventTags[$ket]);
} else {
$et['EventTag']['Tag'] = $et['Tag'];
unset($et['Tag']);
if (empty($event_tag_objects[$et['EventTag']['event_id']])) {
$event_tag_objects[$et['EventTag']['event_id']] = array($et['EventTag']);
'fields' => ['Tag.id', 'Tag.name', 'Tag.colour', 'Tag.is_galaxy'],
]);
unset($tagIds);
$tags = array_column(array_column($tags, 'Tag'), null, 'id');
foreach ($events as $k => $event) {
if (empty($event['EventTag'])) {
continue;
}
foreach ($event['EventTag'] as $k2 => $et) {
if (!isset($tags[$et['tag_id']])) {
unset($events[$k]['EventTag'][$k2]); // tag not exists or is not exportable
} else {
$event_tag_objects[$et['EventTag']['event_id']][] = $et['EventTag'];
$events[$k]['EventTag'][$k2]['Tag'] = $tags[$et['tag_id']];
}
}
}
for ($j = 0; $j < $elements; $j++) {
if (!empty($event_tag_objects[$events[($i*1000) + $j]['Event']['id']])) {
$events[($i*1000) + $j]['EventTag'] = $event_tag_objects[$events[($i*1000) + $j]['Event']['id']];
} else {
$events[($i*1000) + $j]['EventTag'] = array();
}
}
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, false, false);
}
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events);
foreach ($events as $key => $event) {
$temp = $events[$key]['Event'];
if (empty($event['SharingGroup']['name'])) {
unset($event['SharingGroup']);
}
$temp = $event['Event'];
$temp['Org'] = $event['Org'];
$temp['Orgc'] = $event['Orgc'];
unset($temp['user_id']);
@ -827,39 +789,53 @@ class EventsController extends AppController
if ($this->response->type() === 'application/xml') {
$events = array('Event' => $events);
}
return $this->RestResponse->viewData($events, $this->response->type(), false, false, false, array('X-Result-Count' => $absolute_total));
} else {
foreach ($events as $key => $event) {
$event['Event']['orgc_uuid'] = $event['Orgc']['uuid'];
$events[$key] = $event['Event'];
}
return $this->RestResponse->viewData($events, $this->response->type(), false, false, false, array('X-Result-Count' => $absolute_total));
}
} else {
$events = $this->paginate();
foreach ($events as $k => $event) {
if (empty($event['SharingGroup']['name'])) {
unset($events[$k]['SharingGroup']);
}
return $this->RestResponse->viewData($events, $this->response->type(), false, false, false, ['X-Result-Count' => $absolute_total]);
}
$this->paginate['contain']['ThreatLevel'] = [
'fields' => array('ThreatLevel.name')
];
$this->paginate['contain'][] = 'EventTag';
if ($this->_isSiteAdmin()) {
$this->paginate['contain'][] = 'User.email';
}
$events = $this->paginate();
if (count($events) === 1 && isset($this->passedArgs['searchall'])) {
$this->redirect(array('controller' => 'events', 'action' => 'view', $events[0]['Event']['id']));
}
foreach ($events as $k => $event) {
if (empty($event['SharingGroup']['name'])) {
unset($events[$k]['SharingGroup']);
}
if (count($events) == 1 && isset($this->passedArgs['searchall'])) {
$this->redirect(array('controller' => 'events', 'action' => 'view', $events[0]['Event']['id']));
}
$events = $this->Event->attachTagsToEvents($events);
if (Configure::read('MISP.showCorrelationsOnIndex')) {
$events = $this->Event->attachCorrelationCountToEvents($this->Auth->user(), $events);
}
if (Configure::read('MISP.showSightingsCountOnIndex')) {
$events = $this->Event->attachSightingsCountToEvents($this->Auth->user(), $events);
}
if (Configure::read('MISP.showProposalsCountOnIndex')) {
$events = $this->Event->attachProposalsCountToEvents($this->Auth->user(), $events);
}
if (Configure::read('MISP.showDiscussionsCountOnIndex')) {
$events = $this->Event->attachDiscussionsCountToEvents($this->Auth->user(), $events);
}
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, true, false);
$this->set('events', $events);
}
$events = $this->Event->attachTagsToEvents($events);
if (Configure::read('MISP.showCorrelationsOnIndex')) {
$events = $this->Event->attachCorrelationCountToEvents($this->Auth->user(), $events);
}
if (Configure::read('MISP.showSightingsCountOnIndex')) {
$events = $this->Event->attachSightingsCountToEvents($this->Auth->user(), $events);
}
if (Configure::read('MISP.showProposalsCountOnIndex')) {
$events = $this->Event->attachProposalsCountToEvents($this->Auth->user(), $events);
}
if (Configure::read('MISP.showDiscussionsCountOnIndex')) {
$events = $this->Event->attachDiscussionsCountToEvents($this->Auth->user(), $events);
}
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, true, false);
if ($this->params['ext'] === 'csv') {
App::uses('CsvExport', 'Export');
$export = new CsvExport();
return $this->RestResponse->viewData($export->eventIndex($events), 'csv');
}
$user = $this->Auth->user();
@ -882,16 +858,17 @@ class EventsController extends AppController
$this->Flash->info(__('No GnuPG key set in your profile. To receive attributes in emails, submit your public key in your profile.'));
}
}
$this->set('events', $events);
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
$this->set('analysisLevels', $this->Event->analysisLevels);
$this->set('distributionLevels', $this->Event->distributionLevels);
$this->set('shortDist', $this->Event->shortDist);
$this->set('distributionData', $this->genDistributionGraph(-1));
if ($this->params['ext'] === 'csv') {
App::uses('CsvExport', 'Export');
$export = new CsvExport();
return $this->RestResponse->viewData($export->eventIndex($events), 'csv');
}
$this->set('urlparams', $urlparams);
$this->set('passedArgsArray', $passedArgsArray);
$this->set('passedArgs', json_encode($passedArgs));
if ($this->request->is('ajax')) {
$this->autoRender = false;
$this->layout = false;
@ -962,17 +939,10 @@ class EventsController extends AppController
}
}
$this->set('filtering', json_encode($filtering));
$tags = $this->Event->EventTag->Tag->find('all', array('recursive' => -1));
$tagNames = array();
$tagNames = $this->Event->EventTag->Tag->find('list', array('recursive' => -1, 'fields' => ['Tag.id', 'Tag.name']));
$tagJSON = array();
foreach ($tags as $k => $v) {
$tagNames[$v['Tag']['id']] = $v['Tag']['name'];
$tagJSON[] = array('id' => $v['Tag']['id'], 'value' => h($v['Tag']['name']));
}
$conditions = array();
if (!$this->_isSiteAdmin()) {
$eIds = $this->Event->fetchEventIds($this->Auth->user(), false, false, false, true);
$conditions['AND'][] = array('Event.id' => $eIds);
foreach ($tagNames as $tagId => $tagName) {
$tagJSON[] = array('id' => $tagId, 'value' => h($tagName));
}
$rules = array('published', 'eventid', 'tag', 'date', 'eventinfo', 'threatlevel', 'distribution', 'sharinggroup', 'analysis', 'attribute', 'hasproposal');
if ($this->_isSiteAdmin()) {
@ -997,7 +967,6 @@ class EventsController extends AppController
$this->set('tags', $tagNames);
$this->set('tagJSON', json_encode($tagJSON));
$this->set('rules', $rules);
$this->set('baseurl', Configure::read('MISP.baseurl'));
$this->layout = 'ajax';
}
@ -3609,8 +3578,7 @@ class EventsController extends AppController
$this->set('sgs', $sgs);
$this->set('event', $event);
$this->set('mayModify', $this->__canModifyEvent($event));
$this->set('typeList', array_keys($this->Event->Attribute->typeDefinitions));
$this->set('defaultCategories', $this->Event->Attribute->defaultCategories);
$this->set('typeDefinitions', $this->Event->Attribute->typeDefinitions);
$this->set('typeCategoryMapping', $typeCategoryMapping);
foreach ($typeCategoryMapping as $k => $v) {
$typeCategoryMapping[$k] = array_values($v);
@ -3647,7 +3615,7 @@ class EventsController extends AppController
$attribute['category'] = $attribute['default_category'];
unset($attribute['default_category']);
} else {
$attribute['category'] = $this->Event->Attribute->defaultCategories[$attribute['type']];
$attribute['category'] = $this->Event->Attribute->typeDefinitions[$attribute['type']]['default_category'];
}
$attribute['distribution'] = $distribution;
$attribute['event_id'] = $event['Event']['id'];
@ -4285,9 +4253,10 @@ class EventsController extends AppController
public function viewGraph($id)
{
// Event data are fetched by 'updateGraph', here we need just metadata.
$event = $this->Event->fetchEvent($this->Auth->user(), array(
'eventid' => $id,
'includeGranularCorrelations' => 1
'metadata' => true,
));
if (empty($event)) {
throw new MethodNotAllowedException(__('Invalid Event.'));
@ -4341,8 +4310,8 @@ class EventsController extends AppController
$grapher = new DistributionGraphTool();
$this->loadModel('Server');
$servers = $this->Server->find('list', array(
'fields' => array('name'),
$servers = $this->Server->find('column', array(
'fields' => array('Server.name'),
));
$grapher->construct($this->Event, $servers, $this->Auth->user(), $extended);
$json = $grapher->get_distributions_graph($id);
@ -4898,8 +4867,7 @@ class EventsController extends AppController
$this->set('event', array('Event' => $attribute[0]['Event']));
}
$this->set('resultArray', $resultArray);
$this->set('typeList', array_keys($this->Event->Attribute->typeDefinitions));
$this->set('defaultCategories', $this->Event->Attribute->defaultCategories);
$this->set('typeDefinitions', $this->Event->Attribute->typeDefinitions);
$this->set('typeCategoryMapping', $typeCategoryMapping);
$this->set('title', 'Enrichment Results');
$this->set('importComment', $importComment);
@ -5088,8 +5056,7 @@ class EventsController extends AppController
}
$this->set('event', $event);
$this->set('resultArray', $resultArray);
$this->set('typeList', array_keys($this->Event->Attribute->typeDefinitions));
$this->set('defaultCategories', $this->Event->Attribute->defaultCategories);
$this->set('typeDefinitions', $this->Event->Attribute->typeDefinitions);
$this->set('typeCategoryMapping', $typeCategoryMapping);
$render_name = 'resolved_attributes';
}

View File

@ -773,8 +773,10 @@ class FeedsController extends AppController
return $this->RestResponse->viewData($event, $this->response->type());
}
if (is_array($event)) {
$this->loadModel('Warninglist');
$this->Warninglist->attachWarninglistToAttributes($event['Event']['Attribute']);
if (isset($event['Event']['Attribute'])) {
$this->loadModel('Warninglist');
$this->Warninglist->attachWarninglistToAttributes($event['Event']['Attribute']);
}
$this->loadModel('Event');
$params = $this->Event->rearrangeEventForView($event, $this->passedArgs, $all);

View File

@ -66,7 +66,7 @@ class LogsController extends AppController
$conditions['AND'][] = array('created <= ' => date("Y-m-d H:i:s", $tempData[0]));
$conditions['AND'][] = array('created >= ' => date("Y-m-d H:i:s", $tempData[1]));
}
} else {
} else if ($filter !== 'limit' && $filter !== 'page') {
$data = array('OR' => $data);
$conditions = $this->Log->generic_add_filter($conditions, $data, 'Log.' . $filter);
}
@ -88,20 +88,13 @@ class LogsController extends AppController
$log_entries = $this->Log->find('all', $params);
return $this->RestResponse->viewData($log_entries, 'json');
} else {
if (!$this->userRole['perm_audit']) {
$this->redirect(array('controller' => 'events', 'action' => 'index', 'admin' => false));
}
$this->set('isSearch', 0);
$this->recursive = 0;
$validFilters = $this->Log->logMeta;
if (!$this->_isSiteAdmin()) {
$orgRestriction = $this->Auth->user('Organisation')['name'];
$conditions['Log.org'] = $orgRestriction;
$this->paginate = array(
'limit' => 60,
'conditions' => $conditions,
'order' => array('Log.id' => 'DESC')
);
$this->paginate['conditions'] = $conditions;
} else {
$validFilters = array_merge_recursive($validFilters, $this->Log->logMetaAdmin);
}
@ -117,15 +110,14 @@ class LogsController extends AppController
// Shows a minimalistic history for the currently selected event
public function event_index($id)
{
// check if the user has access to this event...
$mayModify = false;
$this->loadModel('Event');
$event = $this->Event->fetchEvent($this->Auth->user(), array(
'eventid' => $id,
'includeAllTags' => 1,
'sgReferenceOnly' => 1,
'deleted' => [0, 1],
'deleted_proposals' => 1
'deleted_proposals' => 1,
'noSightings' => true,
'noEventReports' => true,
));
if (empty($event)) {
throw new NotFoundException('Invalid event.');
@ -190,57 +182,50 @@ class LogsController extends AppController
)
);
}
// send unauthorised people away. Only site admins and users of the same org may see events that are "your org only". Everyone else can proceed for all other levels of distribution
$mineOrAdmin = true;
if (!$this->_isSiteAdmin() && $event['Event']['org_id'] != $this->Auth->user('org_id')) {
$mineOrAdmin = false;
}
$this->set('published', $event['Event']['published']);
if ($mineOrAdmin && $this->userRole['perm_modify']) {
$mayModify = true;
}
$fieldList = array('title', 'created', 'model', 'model_id', 'action', 'change', 'org', 'email');
$this->paginate = array(
'limit' => 60,
'conditions' => $conditions,
'order' => array('Log.id' => 'DESC'),
'fields' => $fieldList
);
$this->paginate['fields'] = array('title', 'created', 'model', 'model_id', 'action', 'change', 'org', 'email');
$this->paginate['conditions'] = $conditions;
$list = $this->paginate();
if (!$this->_isSiteAdmin()) {
$this->loadModel('User');
$emails = $this->User->find('list', array(
'conditions' => array('User.org_id' => $this->Auth->user('org_id')),
'fields' => array('User.id', 'User.email')
$orgEmails = $this->User->find('column', array(
'conditions' => array('User.org_id' => $this->Auth->user('org_id')),
'fields' => array('User.email')
));
foreach ($list as $k => $item) {
if (!in_array($item['Log']['email'], $emails)) {
if (!in_array($item['Log']['email'], $orgEmails)) {
$list[$k]['Log']['email'] = '';
}
}
}
if ($this->_isRest()) {
foreach ($list as $k => $item) {
$list[$k] = $item['Log'];
}
$list = array('Log' => $list);
$list = array('Log' => array_column($list, 'Log'));
return $this->RestResponse->viewData($list, $this->response->type());
} else {
$this->set('event', $event);
$this->set('list', $list);
$this->set('eventId', $id);
$this->set('mayModify', $mayModify);
}
// send unauthorised people away. Only site admins and users of the same org may see events that are "your org only". Everyone else can proceed for all other levels of distribution
$mineOrAdmin = true;
if (!$this->_isSiteAdmin() && $event['Event']['org_id'] != $this->Auth->user('org_id')) {
$mineOrAdmin = false;
}
$mayModify = false;
if ($mineOrAdmin && $this->userRole['perm_modify']) {
$mayModify = true;
}
$this->set('published', $event['Event']['published']);
$this->set('event', $event);
$this->set('list', $list);
$this->set('eventId', $id);
$this->set('mayModify', $mayModify);
}
public $helpers = array('Js' => array('Jquery'), 'Highlight');
public function admin_search($new = false)
{
if (!$this->userRole['perm_audit']) {
$this->redirect(array('controller' => 'events', 'action' => 'index', 'admin' => false));
}
$orgRestriction = null;
if ($this->_isSiteAdmin()) {
$orgRestriction = false;

View File

@ -17,24 +17,19 @@ class NewsController extends AppController
{
$this->paginate['contain'] = array('User' => array('fields' => array('User.email')));
$newsItems = $this->paginate();
$this->loadModel('User');
$currentUser = $this->User->find('first', array(
'recursive' => -1,
'conditions' => array('User.id' => $this->Auth->user('id')),
'fields' => array('User.newsread')
));
$newsread = $this->Auth->user('newsread');
foreach ($newsItems as $key => $item) {
if ($item['News']['date_created'] > $currentUser['User']['newsread']) {
if ($item['News']['date_created'] > $newsread) {
$newsItems[$key]['News']['new'] = true;
} else {
$newsItems[$key]['News']['new'] = false;
}
}
$this->User->id = $this->Auth->user('id');
//if ($this->User->exists()) {
$this->User->saveField('newsread', time());
$this->set('newsItems', $newsItems);
//}
$this->loadModel('User');
$this->User->updateField($this->Auth->user(), 'newsread', time());
}
public function add()

View File

@ -19,24 +19,24 @@ class ObjectTemplatesController extends AppController
'recursive' => -1
);
public function objectMetaChoice($event_id) {
$metas = $this->ObjectTemplate->find('list', array(
'recursive' => -1,
public function objectMetaChoice($event_id)
{
$metas = $this->ObjectTemplate->find('column', array(
'conditions' => array('ObjectTemplate.active' => 1),
'fields' => array('meta-category', 'meta-category'),
'group' => array('ObjectTemplate.meta-category'),
'order' => array('ObjectTemplate.meta-category asc')
'fields' => array('ObjectTemplate.meta-category'),
'order' => array('ObjectTemplate.meta-category asc'),
'unique' => true,
));
$items = array();
$items[] = array(
$eventId = h($event_id);
$items = [[
'name' => __('All Objects'),
'value' => $this->baseurl . "/ObjectTemplates/objectChoice/" . h($event_id) . "/" . "0"
);
foreach($metas as $meta) {
'value' => $this->baseurl . "/ObjectTemplates/objectChoice/$eventId/0"
]];
foreach ($metas as $meta) {
$items[] = array(
'name' => $meta,
'value' => $this->baseurl . "/ObjectTemplates/objectChoice/" . h($event_id) . "/" . h($meta)
'value' => $this->baseurl . "/ObjectTemplates/objectChoice/$eventId/" . h($meta)
);
}

View File

@ -808,6 +808,9 @@ class ObjectsController extends AppController
$this->MispObject->Event->insertLock($this->Auth->user(), $eventId);
}
if ($this->request->is('post') || $this->request->is('delete')) {
if (!empty($this->request->data['hard'])) {
$hard = true;
}
if ($this->__delete($object['Object']['id'], $hard)) {
$message = 'Object deleted.';
if ($this->request->is('ajax')) {

View File

@ -57,7 +57,8 @@ class OrganisationsController extends AppController
}
$this->paginate['conditions'] = $conditions;
$usersPerOrg = $this->User->getMembersCount();
$this->Organisation->addCountField('user_count', $this->User, ['User.org_id = Organisation.id']);
if ($this->_isRest()) {
unset($this->paginate['limit']);
$orgs = $this->Organisation->find('all', $this->paginate);
@ -69,12 +70,10 @@ class OrganisationsController extends AppController
$this->set('viewall', $viewAll);
$orgs = $this->paginate();
}
$this->loadModel('User');
$org_creator_ids = array();
foreach ($orgs as $k => $org) {
if (isset($usersPerOrg[$org['Organisation']['id']])) {
$orgs[$k]['Organisation']['user_count'] = $usersPerOrg[$org['Organisation']['id']];
}
if ($this->_isSiteAdmin()) {
if (!isset($org_creator_ids[$org['Organisation']['created_by']])) {
$email = $this->User->find('first', array(
@ -318,10 +317,16 @@ class OrganisationsController extends AppController
return new CakeResponse(['status' => $exists ? 200 : 404]);
}
$fields = ['id', 'name', 'date_created', 'date_modified', 'type', 'nationality', 'sector', 'contacts', 'description', 'local', 'uuid', 'restricted_to_domain', 'created_by'];
if ($this->_isRest()) {
$this->Organisation->addCountField('user_count', $this->User, ['User.org_id = Organisation.id']);
$fields[] = 'user_count';
}
$org = $this->Organisation->find('first', array(
'conditions' => $conditions,
'recursive' => -1,
'fields' => ['id', 'name', 'date_created', 'date_modified', 'type', 'nationality', 'sector', 'contacts', 'description', 'local', 'uuid', 'restricted_to_domain', 'created_by'],
'fields' => $fields,
));
if (!$org || !$this->Organisation->canSee($this->Auth->user(), $org['Organisation']['id'])) {
throw new NotFoundException(__('Invalid organisation'));
@ -342,7 +347,6 @@ class OrganisationsController extends AppController
}
if ($this->_isRest()) {
$org['Organisation']['user_count'] = $this->Organisation->User->getMembersCount($org['Organisation']['id']);
return $this->RestResponse->viewData($org, $this->response->type());
}

View File

@ -9,8 +9,6 @@ App::uses('AppController', 'Controller');
*/
class RolesController extends AppController
{
public $options = array('0' => 'Read Only', '1' => 'Manage My Own Events', '2' => 'Manage Organization Events', '3' => 'Manage & Publish Organization Events'); // FIXME move this to Role Model
public $components = array(
'Security',
'Session',
@ -28,36 +26,32 @@ class RolesController extends AppController
public function view($id=false)
{
$this->set('menuData', ['menuList' => 'globalActions', 'menuItem' => 'roles']);
$this->CRUD->view($id);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('permissionLevelName', $this->Role->premissionLevelName);
$this->set('permFlags', $this->Role->permFlags);
$this->set('menuData', ['menuList' => 'globalActions', 'menuItem' => 'roles']);
}
public function admin_add()
{
$this->set('menuData', array('menuList' => 'admin', 'menuItem' => 'addRole'));
$params = [];
$selectConditions = [];
$params = ['redirect' => ['action' => 'index', 'admin' => false]];
$this->CRUD->add($params);
if ($this->IndexFilter->isRest()) {
if ($this->restResponsePayload) {
return $this->restResponsePayload;
}
$this->set('permFlags', $this->Role->permFlags);
$dropdownData = [
'options' => $this->options
'options' => $this->Role->premissionLevelName,
];
$this->set(compact('dropdownData'));
$this->set('menuData', array('menuList' => 'admin', 'menuItem' => 'addRole'));
}
public function admin_edit($id = null)
{
if (!$this->_isSiteAdmin()) {
$this->redirect(array('controller' => 'roles', 'action' => 'index', 'admin' => false));
}
$this->Role->id = $id;
if (!$this->Role->exists() && !$this->request->is('get')) {
throw new NotFoundException(__('Invalid Role'));
@ -76,7 +70,7 @@ class RolesController extends AppController
return $this->RestResponse->viewData($role, $this->response->type());
} else {
$this->Flash->success(__('The Role has been saved'));
$this->redirect(array('action' => 'index'));
$this->redirect(array('action' => 'index', 'admin' => false));
}
} else {
if ($this->_isRest()) {
@ -94,12 +88,30 @@ class RolesController extends AppController
$this->request->data['Role']['id'] = $id;
$this->request->data = $this->Role->read(null, $id);
}
$this->set('options', $this->options);
$this->set('options', $this->Role->premissionLevelName);
$this->set('permFlags', $this->Role->permFlags);
$this->set('id', $id);
}
public function admin_index($id = false)
public function admin_delete($id = null)
{
$this->CRUD->delete($id, [
'validate' => function (array $role) {
$usersWithRole = $this->User->find('count', [
'conditions' => ['role_id' => $role['Role']['id']],
'recursive' => -1,
]);
if ($usersWithRole) {
throw new Exception(__("It is not possible to delete role that is assigned to users."));
}
}
]);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
public function index()
{
$params = [
'filters' => ['name'],
@ -108,43 +120,21 @@ class RolesController extends AppController
$this->loadModel('AdminSetting');
$default_setting = $this->AdminSetting->getSetting('default_role');
foreach ($elements as &$role) {
$role['Role']['default'] = ($role['Role']['id'] == $default_setting) ? true : false;
$role['Role']['default'] = $role['Role']['id'] == $default_setting;
}
return $elements;
}
];
//$this->paginate['fields'] = ['id', 'name'];
$this->CRUD->index($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('options', $this->Role->premissionLevelName);
$this->set('permFlags', $this->Role->permFlags);
$this->set('menuData', array('menuList' => 'globalActions', 'menuItem' => 'roles'));
}
public function admin_delete($id = null)
{
$this->CRUD->delete($id);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
}
public function index()
{
$this->recursive = 0;
if ($this->_isRest()) {
$roles = $this->Role->find('all', array(
'recursive' => -1
));
return $this->RestResponse->viewData($roles, $this->response->type());
} else {
$this->set('list', $this->paginate());
$this->set('permFlags', $this->Role->permFlags);
$this->loadModel('AdminSetting');
$this->set('default_role_id', $this->AdminSetting->getSetting('default_role'));
$this->set('options', $this->options);
}
$this->set('menuData', $this->_isAdmin() ?
['menuList' => 'admin', 'menuItem' => 'indexRole'] :
['menuList' => 'globalActions', 'menuItem' => 'roles']
);
}
public function admin_set_default($role_id = false)

View File

@ -86,10 +86,8 @@ class ServersController extends AppController
{
$urlparams = '';
$passedArgs = array();
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException('You are not authorised to do that.');
}
$server = $this->Server->find('first', array('conditions' => array('Server.id' => $id), 'recursive' => -1, 'fields' => array('Server.id', 'Server.url', 'Server.name')));
$server = $this->Server->find('first', array('conditions' => array('Server.id' => $id), 'recursive' => -1));
if (empty($server)) {
throw new NotFoundException('Invalid server ID.');
}
@ -115,15 +113,15 @@ class ServersController extends AppController
$combinedArgs['limit'] = 60;
}
try {
list($events, $total_count) = $this->Server->previewIndex($id, $this->Auth->user(), $combinedArgs);
list($events, $total_count) = $this->Server->previewIndex($server, $this->Auth->user(), $combinedArgs);
} catch (Exception $e) {
$this->Flash->error(__('Download failed.') . ' ' . $e->getMessage());
$this->redirect(array('action' => 'index'));
}
$this->loadModel('Event');
$threat_levels = $this->Event->ThreatLevel->find('all');
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
$threat_levels = $this->Event->ThreatLevel->find('list', ['fields' => ['id', 'name']]);
$this->set('threatLevels', $threat_levels);
App::uses('CustomPaginationTool', 'Tools');
$customPagination = new CustomPaginationTool();
$params = $customPagination->createPaginationRules($events, $this->passedArgs, $this->alias);
@ -150,19 +148,15 @@ class ServersController extends AppController
public function previewEvent($serverId, $eventId, $all = false)
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException('You are not authorised to do that.');
}
$server = $this->Server->find('first', array(
'conditions' => array('Server.id' => $serverId),
'recursive' => -1,
'fields' => array('Server.id', 'Server.url', 'Server.name'))
);
));
if (empty($server)) {
throw new NotFoundException('Invalid server ID.');
}
try {
$event = $this->Server->previewEvent($serverId, $eventId);
$event = $this->Server->previewEvent($server, $eventId);
} catch (NotFoundException $e) {
throw new NotFoundException(__("Event '%s' not found.", $eventId));
} catch (Exception $e) {
@ -170,6 +164,10 @@ class ServersController extends AppController
$this->redirect(array('action' => 'previewIndex', $serverId));
}
if ($this->_isRest()) {
return $this->RestResponse->viewData($event, $this->response->type());
}
$this->loadModel('Warninglist');
if (isset($event['Event']['Attribute'])) {
$this->Warninglist->attachWarninglistToAttributes($event['Event']['Attribute']);
@ -200,8 +198,17 @@ class ServersController extends AppController
$this->set($alias, $currentModel->{$variable});
}
}
$threat_levels = $this->Event->ThreatLevel->find('all');
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
$threat_levels = $this->Event->ThreatLevel->find('list', ['fields' => ['id', 'name']]);
$this->set('threatLevels', $threat_levels);
$this->set('title_for_layout', __('Remote event preview'));
}
public function compareServers()
{
list($servers, $overlap) = $this->Server->serverEventsOverlap();
$this->set('servers', $servers);
$this->set('overlap', $overlap);
$this->set('title_for_layout', __('Server overlap analysis matrix'));
}
public function filterEventIndex($id)
@ -929,27 +936,6 @@ class ServersController extends AppController
$this->render('/Elements/healthElements/settings_row');
}
private function __loadAvailableLanguages()
{
return $this->Server->loadAvailableLanguages();
}
private function __loadTagCollections()
{
return $this->Server->loadTagCollections($this->Auth->user());
}
private function __loadLocalOrgs()
{
$this->loadModel('Organisation');
$local_orgs = $this->Organisation->find('list', array(
'conditions' => array('local' => 1),
'recursive' => -1,
'fields' => array('Organisation.id', 'Organisation.name')
));
return array_replace(array(0 => __('No organisation selected.')), $local_orgs);
}
public function serverSettings($tab=false)
{
if (!$this->_isSiteAdmin()) {
@ -975,7 +961,6 @@ class ServersController extends AppController
$mixboxVersion = array(0 => __('Incorrect mixbox version installed, found $current, expecting $expected'), 1 => __('OK'));
$maecVersion = array(0 => __('Incorrect maec version installed, found $current, expecting $expected'), 1 => __('OK'));
$pymispVersion = array(0 => __('Incorrect PyMISP version installed, found $current, expecting $expected'), 1 => __('OK'));
$plyaraVersion = array(0 => __('Incorrect plyara version installed, found $current, expecting $expected'), 1 => __('OK'));
$sessionErrors = array(0 => __('OK'), 1 => __('High'), 2 => __('Alternative setting used'), 3 => __('Test failed'));
$moduleErrors = array(0 => __('OK'), 1 => __('System not enabled'), 2 => __('No modules found'));
@ -1015,8 +1000,8 @@ class ServersController extends AppController
$tabs[$result['tab']]['severity'] = $result['level'];
}
}
if (isset($result['optionsSource']) && !empty($result['optionsSource'])) {
$result['options'] = $this->{'__load' . $result['optionsSource']}();
if (isset($result['optionsSource']) && is_callable($result['optionsSource'])) {
$result['options'] = $result['optionsSource']();
}
$dumpResults[] = $result;
if ($result['tab'] == $tab) {
@ -1032,13 +1017,12 @@ class ServersController extends AppController
$diagnostic_errors = 0;
App::uses('File', 'Utility');
App::uses('Folder', 'Utility');
$additionalViewVars = array();
if ($tab == 'files') {
if ($tab === 'files') {
$files = $this->__manageFiles();
$this->set('files', $files);
}
// Only run this check on the diagnostics tab
if ($tab == 'diagnostics' || $tab == 'download' || $this->_isRest()) {
if ($tab === 'diagnostics' || $tab === 'download' || $this->_isRest()) {
$php_ini = php_ini_loaded_file();
$this->set('php_ini', $php_ini);
@ -1059,27 +1043,26 @@ class ServersController extends AppController
$this->set('commit', $gitStatus['commit']);
$this->set('latestCommit', $gitStatus['latestCommit']);
$phpSettings = array(
'max_execution_time' => array(
'explanation' => 'The maximum duration that a script can run (does not affect the background workers). A too low number will break long running scripts like comprehensive API exports',
'recommended' => 300,
'unit' => false
),
'memory_limit' => array(
'explanation' => 'The maximum memory that PHP can consume. It is recommended to raise this number since certain exports can generate a fair bit of memory usage',
'recommended' => 2048,
'unit' => 'M'
),
'upload_max_filesize' => array(
'explanation' => 'The maximum size that an uploaded file can be. It is recommended to raise this number to allow for the upload of larger samples',
'recommended' => 50,
'unit' => 'M'
),
'post_max_size' => array(
'explanation' => 'The maximum size of a POSTed message, this has to be at least the same size as the upload_max_filesize setting',
'recommended' => 50,
'unit' => 'M'
)
'max_execution_time' => array(
'explanation' => 'The maximum duration that a script can run (does not affect the background workers). A too low number will break long running scripts like comprehensive API exports',
'recommended' => 300,
'unit' => false
),
'memory_limit' => array(
'explanation' => 'The maximum memory that PHP can consume. It is recommended to raise this number since certain exports can generate a fair bit of memory usage',
'recommended' => 2048,
'unit' => 'M'
),
'upload_max_filesize' => array(
'explanation' => 'The maximum size that an uploaded file can be. It is recommended to raise this number to allow for the upload of larger samples',
'recommended' => 50,
'unit' => 'M'
),
'post_max_size' => array(
'explanation' => 'The maximum size of a POSTed message, this has to be at least the same size as the upload_max_filesize setting',
'recommended' => 50,
'unit' => 'M'
)
);
foreach ($phpSettings as $setting => $settingArray) {
@ -1133,7 +1116,9 @@ class ServersController extends AppController
$attachmentScan = ['status' => false, 'error' => $e->getMessage()];
}
$additionalViewVars = array('gpgStatus', 'sessionErrors', 'proxyStatus', 'sessionStatus', 'zmqStatus', 'stixVersion', 'cyboxVersion', 'mixboxVersion', 'maecVersion', 'stix2Version', 'pymispVersion', 'moduleStatus', 'yaraStatus', 'gpgErrors', 'proxyErrors', 'zmqErrors', 'stixOperational', 'stix', 'moduleErrors', 'moduleTypes', 'dbDiagnostics', 'dbSchemaDiagnostics', 'redisInfo', 'attachmentScan');
$view = compact('gpgStatus', 'sessionErrors', 'proxyStatus', 'sessionStatus', 'zmqStatus', 'stixVersion', 'cyboxVersion', 'mixboxVersion', 'maecVersion', 'stix2Version', 'pymispVersion', 'moduleStatus', 'yaraStatus', 'gpgErrors', 'proxyErrors', 'zmqErrors', 'stixOperational', 'stix', 'moduleErrors', 'moduleTypes', 'dbDiagnostics', 'dbSchemaDiagnostics', 'redisInfo', 'attachmentScan');
} else {
$view = [];
}
// check whether the files are writeable
$writeableDirs = $this->Server->writeableDirsDiagnostics($diagnostic_errors);
@ -1144,13 +1129,8 @@ class ServersController extends AppController
// check if the encoding is not set to utf8
$dbEncodingStatus = $this->Server->databaseEncodingDiagnostics($diagnostic_errors);
$viewVars = array(
'diagnostic_errors', 'tabs', 'tab', 'issues', 'finalSettings', 'writeableErrors', 'readableErrors', 'writeableDirs', 'writeableFiles', 'readableFiles', 'extensions', 'dbEncodingStatus'
);
$viewVars = array_merge($viewVars, $additionalViewVars);
foreach ($viewVars as $viewVar) {
$this->set($viewVar, ${$viewVar});
}
$view = array_merge($view, compact('diagnostic_errors', 'tabs', 'tab', 'issues', 'finalSettings', 'writeableErrors', 'readableErrors', 'writeableDirs', 'writeableFiles', 'readableFiles', 'extensions', 'dbEncodingStatus'));
$this->set($view);
$workerIssueCount = 4;
$worker_array = array();
@ -1200,6 +1180,7 @@ class ServersController extends AppController
$this->set('phpversion', phpversion());
$this->set('phpmin', $this->phpmin);
$this->set('phprec', $this->phprec);
$this->set('phptoonew', $this->phptoonew);
$this->set('pythonmin', $this->pythonmin);
$this->set('pythonrec', $this->pythonrec);
$this->set('pymisp', $this->pymisp);
@ -1296,47 +1277,48 @@ class ServersController extends AppController
}
}
public function idTranslator() {
// The id translation feature is limited to people from the host org
if (!$this->_isSiteAdmin() && $this->Auth->user('org_id') != Configure::read('MISP.host_org_id')) {
throw new MethodNotAllowedException(__('You don\'t have the required privileges to do that.'));
}
//We retrieve the list of remote servers that we can query
$options = array();
$options['conditions'] = array("pull" => true);
$servers = $this->Server->find('all', $options);
public function idTranslator($localId = null)
{
// We retrieve the list of remote servers that we can query
$servers = $this->Server->find('all', [
'conditions' => ['OR' => ['pull' => true, 'push' => true]],
'recursive' => -1,
'order' => ['Server.priority ASC'],
]);
// We generate the list of servers for the dropdown
$displayServers = array();
foreach($servers as $s) {
$displayServers[] = array('name' => $s['Server']['name'],
'value' => $s['Server']['id']);
foreach ($servers as $s) {
$displayServers[] = [
'name' => $s['Server']['name'],
'value' => $s['Server']['id'],
];
}
$this->set('servers', $displayServers);
if ($this->request->is('post')) {
if ($localId || $this->request->is('post')) {
if ($localId && $this->request->is('get')) {
$this->request->data['Event']['local'] = 'local';
$this->request->data['Event']['uuid'] = $localId;
}
$remote_events = array();
if(!empty($this->request->data['Event']['uuid']) && $this->request->data['Event']['local'] == "local") {
if (!empty($this->request->data['Event']['uuid']) && $this->request->data['Event']['local'] === "local") {
$local_event = $this->Event->fetchSimpleEvent($this->Auth->user(), $this->request->data['Event']['uuid']);
} else if (!empty($this->request->data['Event']['uuid']) && $this->request->data['Event']['local'] == "remote" && !empty($this->request->data['Server']['id'])) {
} else if (!empty($this->request->data['Event']['uuid']) && $this->request->data['Event']['local'] === "remote" && !empty($this->request->data['Server']['id'])) {
//We check on the remote server for any event with this id and try to find a match locally
$conditions = array('AND' => array('Server.id' => $this->request->data['Server']['id'], 'Server.pull' => true));
$remote_server = $this->Server->find('first', array('conditions' => $conditions));
if(!empty($remote_server)) {
if (!empty($remote_server)) {
try {
$remote_event = $this->Event->downloadEventFromServer($this->request->data['Event']['uuid'], $remote_server, null, true);
} catch (Exception $e) {
$error_msg = __("Issue while contacting the remote server to retrieve event information");
$this->logException($error_msg, $e);
$this->Flash->error($error_msg);
$this->Flash->error(__("Issue while contacting the remote server to retrieve event information"));
return;
}
$local_event = $this->Event->fetchSimpleEvent($this->Auth->user(), $remote_event[0]['uuid']);
//we record it to avoid re-querying the same server in the 2nd phase
if(!empty($local_event)) {
// we record it to avoid re-querying the same server in the 2nd phase
if (!empty($local_event)) {
$remote_events[] = array(
"server_id" => $remote_server['Server']['id'],
"server_name" => $remote_server['Server']['name'],
@ -1346,31 +1328,30 @@ class ServersController extends AppController
}
}
}
if(empty($local_event)) {
$this->Flash->error( __("This event could not be found or you don't have permissions to see it."));
if (empty($local_event)) {
$this->Flash->error(__("This event could not be found or you don't have permissions to see it."));
return;
} else {
$this->Flash->success(__('The event has been found.'));
}
// In the second phase, we query all configured sync servers to get their info on the event
foreach($servers as $s) {
foreach ($servers as $server) {
// We check if the server was not already contacted in phase 1
if(count($remote_events) > 0 && $remote_events[0]['server_id'] == $s['Server']['id']) {
if (count($remote_events) > 0 && $remote_events[0]['server_id'] == $server['Server']['id']) {
continue;
}
try {
$remote_event = $this->Event->downloadEventFromServer($local_event['Event']['uuid'], $s, null, true);
$remote_event = $this->Event->downloadEventFromServer($local_event['Event']['uuid'], $server, null, true);
$remote_event_id = $remote_event[0]['id'];
} catch (Exception $e) {
$this->logException("Couldn't download event from server", $e);
$remote_event_id = null;
}
$remote_events[] = array(
"server_id" => $s['Server']['id'],
"server_name" => $s['Server']['name'],
"url" => isset($remote_event_id) ? $s['Server']['url']."/events/view/".$remote_event_id : $s['Server']['url'],
"server_id" => $server['Server']['id'],
"server_name" => $server['Server']['name'],
"url" => isset($remote_event_id) ? $server['Server']['url']."/events/view/".$remote_event_id : $server['Server']['url'],
"remote_id" => isset($remote_event_id) ? $remote_event_id : false
);
}
@ -1378,6 +1359,7 @@ class ServersController extends AppController
$this->set('local_event', $local_event);
$this->set('remote_events', $remote_events);
}
$this->set('title_for_layout', __('Event ID translator'));
}
public function getSubmodulesStatus() {
@ -1428,8 +1410,8 @@ class ServersController extends AppController
$setting['value'] = $value;
}
$setting['setting'] = $setting['name'];
if (isset($setting['optionsSource']) && !empty($setting['optionsSource'])) {
$setting['options'] = $this->{'__load' . $setting['optionsSource']}();
if (isset($setting['optionsSource']) && is_callable($setting['optionsSource'])) {
$setting['options'] = $setting['optionsSource']();
}
$subGroup = explode('.', $setting['name']);
if ($subGroup[0] === 'Plugin') {
@ -1448,7 +1430,7 @@ class ServersController extends AppController
if (!isset($this->request->data['Server'])) {
$this->request->data = array('Server' => $this->request->data);
}
if (!isset($this->request->data['Server']['value'])) {
if (!isset($this->request->data['Server']['value']) || !is_scalar($this->request->data['Server']['value'])) {
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Servers', 'serverSettingsEdit', false, 'Invalid input. Expected: {"value": "new_setting"}', $this->response->type());
}
@ -1491,7 +1473,7 @@ class ServersController extends AppController
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Field updated.')), 'status'=>200, 'type' => 'json'));
}
} else {
if ($this->_isRest) {
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Servers', 'serverSettingsEdit', false, $result, $this->response->type());
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $result)), 'status'=>200, 'type' => 'json'));
@ -1627,28 +1609,31 @@ class ServersController extends AppController
public function postTest()
{
if ($this->request->is('post')) {
// Fix for PHP-FPM / Nginx / etc
// Fix via https://www.popmartian.com/tipsntricks/2015/07/14/howto-use-php-getallheaders-under-fastcgi-php-fpm-nginx-etc/
if (!function_exists('getallheaders')) {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
} else {
$headers = getallheaders();
}
$result = array();
$result['body'] = $this->request->data;
$result['headers']['Content-type'] = isset($headers['Content-type']) ? $headers['Content-type'] : 0;
$result['headers']['Accept'] = isset($headers['Accept']) ? $headers['Accept'] : 0;
$result['headers']['Authorization'] = isset($headers['Authorization']) ? 'OK' : 0;
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
} else {
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('Invalid request, expecting a POST request.');
}
// Fix for PHP-FPM / Nginx / etc
// Fix via https://www.popmartian.com/tipsntricks/2015/07/14/howto-use-php-getallheaders-under-fastcgi-php-fpm-nginx-etc/
if (!function_exists('getallheaders')) {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) === 'HTTP_') {
$headers[strtolower(str_replace('_', '-', substr($name, 5)))] = $value;
}
}
} else {
$headers = getallheaders();
$headers = array_change_key_case($headers, CASE_LOWER);
}
$result = [
'body' => $this->request->data,
'headers' => [
'Content-type' => isset($headers['content-type']) ? $headers['content-type'] : 0,
'Accept' => isset($headers['accept']) ? $headers['accept'] : 0,
'Authorization' => isset($headers['authorization']) ? 'OK' : 0,
],
];
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
}
public function getRemoteUser($id)
@ -1728,7 +1713,8 @@ class ServersController extends AppController
'version' => implode('.', $version),
'mismatch' => $mismatch,
'newer' => $newer,
'post' => isset($post) ? $post : 'too old',
'post' => isset($post) ? $post['status'] : 'too old',
'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null,
'client_certificate' => $result['client_certificate'],
)
),
@ -1838,9 +1824,19 @@ class ServersController extends AppController
$result = $this->Server->checkoutMain();
}
public function update()
public function update($branch = false)
{
if ($this->request->is('post')) {
$branch = false;
$filterData = array(
'request' => $this->request,
'named_params' => $this->params['named'],
'paramArray' => ['branch'],
'ordered_url_params' => @compact($paramArray),
'additional_delimiters' => PHP_EOL
);
$exception = false;
$settings = $this->_harvestParameters($filterData, $exception);
$status = $this->Server->getCurrentGitStatus();
$raw = array();
if (empty($status['branch'])) { // do not try to update if you are not on branch
@ -1848,7 +1844,10 @@ class ServersController extends AppController
$raw[] = $msg;
$update = $msg;
} else {
$update = $this->Server->update($status, $raw);
if ($settings === false) {
$settings = [];
}
$update = $this->Server->update($status, $raw, $settings);
}
if ($this->_isRest()) {
return $this->RestResponse->viewData(array('results' => $raw), $this->response->type());

View File

@ -268,17 +268,21 @@ class SharingGroupsController extends AppController
if (isset($this->params['named']['value'])) {
$term = '%' . strtolower($this->params['named']['value']) . '%';
$sgIds = $this->SharingGroup->SharingGroupOrg->find('list', [
'conditions' => [
'OR' => [
'Organisation.uuid LIKE' => $term,
'LOWER(Organisation.name) LIKE' => $term,
if ($this->__showOrgs()) {
$sgIds = $this->SharingGroup->SharingGroupOrg->find('column', [
'conditions' => [
'OR' => [
'Organisation.uuid LIKE' => $term,
'LOWER(Organisation.name) LIKE' => $term,
],
'SharingGroupOrg.sharing_group_id' => $authorizedSgIds,
],
'SharingGroupOrg.sharing_group_id' => $authorizedSgIds,
],
'contain' => ['Organisation'],
'fields' => ['SharingGroupOrg.sharing_group_id'],
]);
'contain' => ['Organisation'],
'fields' => ['SharingGroupOrg.sharing_group_id'],
]);
} else {
$sgIds = [];
}
$this->paginate['conditions'][]['OR'] = [
'SharingGroup.id' => $sgIds,
'SharingGroup.uuid LIKE' => $term,
@ -288,6 +292,43 @@ class SharingGroupsController extends AppController
'LOWER(Organisation.name) LIKE' => $term,
];
}
if ($this->__showOrgs() && isset($this->params['named']['searchorg'])) {
$orgs = explode('|', $this->params['named']['searchorg']);
$conditions = [];
foreach ($orgs as $org) {
$exclude = $org[0] === '!';
if ($exclude) {
$org = substr($org, 1);
}
$org = $this->SharingGroup->Organisation->fetchOrg($org);
if ($org) {
if ($exclude) {
$conditions['AND'][] = ['org_id !=' => $org['id']];
} else {
$conditions['OR'][] = ['org_id' => $org['id']];
}
}
}
$sgIds = $this->SharingGroup->SharingGroupOrg->find('column', [
'conditions' => $conditions,
'fields' => ['SharingGroupOrg.sharing_group_id'],
]);
if (empty($sgIds)) {
$sgIds = -1;
}
$this->paginate['conditions'][] = ['SharingGroup.id' => $sgIds];
}
// To allow sort sharing group by number of organisation and also show org count when user don't have permission ot see them
$this->SharingGroup->addCountField('org_count', $this->SharingGroup->SharingGroupOrg, ['SharingGroupOrg.sharing_group_id = SharingGroup.id']);
$this->paginate['fields'][] = 'SharingGroup.org_count';
if (!$this->__showOrgs()) {
unset($this->paginate['contain']['SharingGroupOrg']);
unset($this->paginate['contain']['SharingGroupServer']);
}
$result = $this->paginate();
// check if the current user can modify or delete the SG
@ -331,21 +372,30 @@ class SharingGroupsController extends AppController
if (!$this->SharingGroup->checkIfAuthorised($this->Auth->user(), $id)) {
throw new MethodNotAllowedException('Sharing group doesn\'t exist or you do not have permission to access it.');
}
$sg = $this->SharingGroup->find('first', [
'conditions' => Validation::uuid($id) ? ['SharingGroup.uuid' => $id] : ['SharingGroup.id' => $id],
'contain' => array(
'SharingGroupOrg' => array(
'Organisation' => array(
'fields' => array('id', 'name', 'uuid', 'local')
)
),
'Organisation',
'SharingGroupServer' => array(
'Server' => array(
'fields' => array('id', 'name', 'url')
)
$contain = array(
'Organisation',
'SharingGroupOrg' => array(
'Organisation' => array(
'fields' => array('id', 'name', 'uuid', 'local')
)
),
'SharingGroupServer' => array(
'Server' => array(
'fields' => array('id', 'name', 'url')
)
)
);
if (!$this->__showOrgs()) {
unset($contain['SharingGroupOrg']);
unset($contain['SharingGroupServer']);
$this->SharingGroup->addCountField('org_count', $this->SharingGroup->SharingGroupOrg, ['SharingGroupOrg.sharing_group_id = SharingGroup.id']);
}
$sg = $this->SharingGroup->find('first', [
'conditions' => Validation::uuid($id) ? ['SharingGroup.uuid' => $id] : ['SharingGroup.id' => $id],
'contain' => $contain,
]);
if (isset($sg['SharingGroupServer'])) {
foreach ($sg['SharingGroupServer'] as $key => $sgs) {
@ -577,4 +627,12 @@ class SharingGroupsController extends AppController
return $this->RestResponse->saveFailResponse('SharingGroup', $action, false, $object_type . ' could not be ' . $actionType . ' the sharing group.', $this->response->type());
}
}
/**
* @return bool
*/
private function __showOrgs()
{
return $this->Auth->user()['Role']['perm_sharing_group'] || !Configure::read('Security.hide_organisations_in_sharing_groups');
}
}

View File

@ -334,24 +334,23 @@ class SightingsController extends AppController
// Save sightings synced over, restricted to sync users
public function bulkSaveSightings($eventId = false)
{
if ($this->request->is('post')) {
if (empty($this->request->data['Sighting'])) {
$sightings = $this->request->data;
} else {
$sightings = $this->request->data['Sighting'];
}
$saved = $this->Sighting->bulkSaveSightings($eventId, $sightings, $this->Auth->user());
if (is_numeric($saved)) {
if ($saved > 0) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $saved . ' sightings added.')), 'status' => 200, 'type' => 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'success' => 'No sightings added.')), 'status' => 200, 'type' => 'json'));
}
} else {
throw new MethodNotAllowedException($saved);
}
} else {
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('This method is only accessible via POST requests.');
}
if (empty($this->request->data['Sighting'])) {
$sightings = $this->request->data;
} else {
$sightings = $this->request->data['Sighting'];
}
try {
$saved = $this->Sighting->bulkSaveSightings($eventId, $sightings, $this->Auth->user());
if ($saved > 0) {
return new CakeResponse(array('body' => json_encode(array('saved' => true, 'success' => $saved . ' sightings added.')), 'status' => 200, 'type' => 'json'));
} else {
return new CakeResponse(array('body' => json_encode(array('saved' => false, 'success' => 'No sightings added.')), 'status' => 200, 'type' => 'json'));
}
} catch (NotFoundException $e) {
throw new MethodNotAllowedException($e->getMessage());
}
}
}

View File

@ -574,9 +574,6 @@ class TagsController extends AppController
public function selectTag($id, $taxonomy_id, $scope = 'event', $filterData = '')
{
if (!$this->_isSiteAdmin() && !$this->userRole['perm_tagger']) {
throw new NotFoundException('You don\'t have permission to do that.');
}
$this->loadModel('Taxonomy');
$expanded = array();
$this->set('taxonomy_id', $taxonomy_id);
@ -629,12 +626,14 @@ class TagsController extends AppController
$expanded = $tags;
}
} elseif ($taxonomy_id === 'all') {
$conditions = [];
$conditions = [
'Tag.name NOT LIKE' => 'misp-galaxy:%',
'Tag.hide_tag' => 0,
];
if (!$this->_isSiteAdmin()) {
$conditions[] = array('Tag.org_id' => array(0, $this->Auth->user('org_id')));
$conditions[] = array('Tag.user_id' => array(0, $this->Auth->user('id')));
$conditions['Tag.org_id'] = array(0, $this->Auth->user('org_id'));
$conditions['Tag.user_id'] = array(0, $this->Auth->user('id'));
}
$conditions['Tag.hide_tag'] = 0;
$allTags = $this->Tag->find('all', array(
'conditions' => $conditions,
'recursive' => -1,
@ -643,10 +642,7 @@ class TagsController extends AppController
));
$tags = array();
foreach ($allTags as $tag) {
$isGalaxyTag = strpos($tag['Tag']['name'], 'misp-galaxy:') === 0;
if (!$isGalaxyTag) {
$tags[$tag['Tag']['id']] = $tag['Tag'];
}
$tags[$tag['Tag']['id']] = $tag['Tag'];
}
unset($allTags);
$expanded = $tags;
@ -654,45 +650,28 @@ class TagsController extends AppController
$taxonomies = $this->Taxonomy->getTaxonomy($taxonomy_id);
$tags = array();
if (!empty($taxonomies['entries'])) {
$isSiteAdmin = $this->_isSiteAdmin();
foreach ($taxonomies['entries'] as $entry) {
if (!empty($entry['existing_tag']['Tag'])) {
$tags[$entry['existing_tag']['Tag']['id']] = $entry['existing_tag']['Tag'];
$expanded[$entry['existing_tag']['Tag']['id']] = $entry['expanded'];
$tag = $entry['existing_tag']['Tag'];
if ($tag['hide_tag']) {
continue; // do not include hidden tags
}
if (!$isSiteAdmin) {
// Skip all tags that this user cannot use for tagging, determined by the org restriction on tags
if ($tag['org_id'] != '0' && $tag['org_id'] != $this->Auth->user('org_id')) {
continue;
}
if ($tag['user_id'] != '0' && $tag['user_id'] != $this->Auth->user('id')) {
continue;
}
}
$tags[$tag['id']] = $tag;
$expanded[$tag['id']] = $entry['expanded'];
}
}
}
// Unset all tags that this user cannot use for tagging, determined by the org restriction on tags
if (!$this->_isSiteAdmin()) {
$banned_tags = $this->Tag->find('list', array(
'conditions' => array(
'NOT' => array(
'Tag.org_id' => array(
0,
$this->Auth->user('org_id')
),
'Tag.user_id' => array(
0,
$this->Auth->user('id')
)
)
),
'fields' => array('Tag.id')
));
foreach ($banned_tags as $banned_tag) {
unset($tags[$banned_tag]);
unset($expanded[$banned_tag]);
}
}
$hidden_tags = $this->Tag->find('list', array(
'conditions' => array('Tag.hide_tag' => 1),
'fields' => array('Tag.id')
));
foreach ($hidden_tags as $hidden_tag) {
unset($tags[$hidden_tag]);
unset($expanded[$hidden_tag]);
}
}
}
@ -1108,18 +1087,25 @@ class TagsController extends AppController
$conditions['OR'][] = array('LOWER(Tag.name) LIKE' => $t);
}
} else {
foreach ($tag as $k => $t) {
$conditions['OR'][] = array('Tag.name' => $t);
foreach ($tag as $t) {
if (is_numeric($t)) {
$conditions['OR'][] = ['Tag.id' => $t];
} else {
$conditions['OR'][] = array('Tag.name' => $t);
}
}
}
$tags = $this->Tag->find('all', array(
'conditions' => $conditions,
'recursive' => -1
));
if (!$searchIfTagExists && empty($tags)) {
$tags = [];
foreach ($tag as $i => $tagName) {
$tags[] = ['Tag' => ['name' => $tagName], 'simulatedTag' => true];
if (!$searchIfTagExists) {
$foundTagNames = Hash::extract($tags, "{n}.Tag.name");
foreach ($tag as $tagName) {
if (!in_array($tagName, $foundTagNames, true)) {
// Tag not found, insert simulated tag
$tags[] = ['Tag' => ['name' => $tagName], 'simulatedTag' => true];
}
}
}
$this->loadModel('Taxonomy');

View File

@ -25,6 +25,15 @@ class TaxonomiesController extends AppController
public function index()
{
$this->paginate['recursive'] = -1;
if (!empty($this->passedArgs['value'])) {
$this->paginate['conditions']['id'] = $this->__search($this->passedArgs['value']);
}
if (isset($this->passedArgs['enabled'])) {
$this->paginate['conditions']['enabled'] = $this->passedArgs['enabled'] ? 1 : 0;
}
if ($this->_isRest()) {
$keepFields = array('conditions', 'contain', 'recursive', 'sort');
$searchParams = array();
@ -44,13 +53,17 @@ class TaxonomiesController extends AppController
$total += empty($predicate['TaxonomyEntry']) ? 1 : count($predicate['TaxonomyEntry']);
}
$taxonomies[$key]['total_count'] = $total;
$taxonomies[$key]['current_count'] = $this->Tag->find('count', array('conditions' => array('lower(Tag.name) LIKE ' => strtolower($taxonomy['Taxonomy']['namespace']) . ':%', 'hide_tag' => 0)));
$taxonomies[$key]['current_count'] = $this->Tag->find('count', array(
'conditions' => array('lower(Tag.name) LIKE ' => strtolower($taxonomy['Taxonomy']['namespace']) . ':%', 'hide_tag' => 0),
'recursive' => -1,
));
unset($taxonomies[$key]['TaxonomyPredicate']);
}
if ($this->_isRest()) {
return $this->RestResponse->viewData($taxonomies, $this->response->type());
} else {
$this->set('taxonomies', $taxonomies);
$this->set('passedArgsArray', $this->passedArgs);
}
}
@ -72,18 +85,18 @@ class TaxonomiesController extends AppController
}
$this->loadModel('EventTag');
$this->loadModel('AttributeTag');
$tagIds = array_column(array_column(array_column($taxonomy['entries'], 'existing_tag'), 'Tag'), 'id');
$eventCount = $this->EventTag->countForTags($tagIds, $this->Auth->user());
$attributeTags = $this->AttributeTag->countForTags($tagIds, $this->Auth->user());
foreach ($taxonomy['entries'] as $key => $value) {
$count = 0;
$count_a = 0;
if (!empty($value['existing_tag'])) {
foreach ($value['existing_tag'] as $et) {
$count = $this->EventTag->find('count', array(
'conditions' => array('EventTag.tag_id' => $et['id'])
));
$count_a = $this->AttributeTag->find('count', array(
'conditions' => array('AttributeTag.tag_id' => $et['id'])
));
}
$tagId = $value['existing_tag']['Tag']['id'];
$count = isset($eventCount[$tagId]) ? $eventCount[$tagId] : 0;
$count_a = isset($attributeTags[$tagId]) ? $attributeTags[$tagId] : 0;
}
$taxonomy['entries'][$key]['events'] = $count;
$taxonomy['entries'][$key]['attributes'] = $count_a;
@ -171,11 +184,21 @@ class TaxonomiesController extends AppController
}
}
public function import()
{
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('This endpoint requires a POST request.');
}
try {
$id = $this->Taxonomy->import($this->request->data);
return $this->view($id);
} catch (Exception $e) {
return $this->RestResponse->saveFailResponse('Taxonomy', 'import', false, $e->getMessage());
}
}
public function update()
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException(__('You don\'t have permission to do that.'));
}
$result = $this->Taxonomy->update();
$this->Log = ClassRegistry::init('Log');
$fails = 0;
@ -383,27 +406,18 @@ class TaxonomiesController extends AppController
public function taxonomyMassConfirmation($id)
{
if (!$this->_isSiteAdmin() && !$this->userRole['perm_tagger']) {
throw new NotFoundException(__('You don\'t have permission to do that.'));
}
$this->set('id', $id);
$this->render('ajax/taxonomy_mass_confirmation');
}
public function taxonomyMassHide($id)
{
if (!$this->_isSiteAdmin() && !$this->userRole['perm_tagger']) {
throw new NotFoundException(__('You don\'t have permission to do that.'));
}
$this->set('id', $id);
$this->render('ajax/taxonomy_mass_hide');
}
public function taxonomyMassUnhide($id)
{
if (!$this->_isSiteAdmin() && !$this->userRole['perm_tagger']) {
throw new NotFoundException(__('You don\'t have permission to do that.'));
}
$this->set('id', $id);
$this->render('ajax/taxonomy_mass_unhide');
}
@ -446,12 +460,47 @@ class TaxonomiesController extends AppController
} else {
return $this->RestResponse->saveFailResponse('Taxonomy', 'toggleRequired', $id, $this->validationError, $this->response->type());
}
} else {
$this->set('required', !$taxonomy['Taxonomy']['required']);
$this->set('id', $id);
$this->autoRender = false;
$this->layout = 'ajax';
$this->render('ajax/toggle_required');
}
$this->set('required', !$taxonomy['Taxonomy']['required']);
$this->set('id', $id);
$this->autoRender = false;
$this->layout = 'ajax';
$this->render('ajax/toggle_required');
}
private function __search($value)
{
$value = mb_strtolower(trim($value));
$searchTerm = "%$value%";
$taxonomyPredicateIds = $this->Taxonomy->TaxonomyPredicate->TaxonomyEntry->find('column', [
'fields' => ['TaxonomyEntry.taxonomy_predicate_id'],
'conditions' => ['OR' => [
'LOWER(value) LIKE' => $searchTerm,
'LOWER(expanded) LIKE' => $searchTerm,
]],
'unique' => true,
]);
$taxonomyIds = $this->Taxonomy->TaxonomyPredicate->find('column', [
'fields' => ['TaxonomyPredicate.taxonomy_id'],
'conditions' => ['OR' => [
'id' => $taxonomyPredicateIds,
'LOWER(value) LIKE' => $searchTerm,
'LOWER(expanded) LIKE' => $searchTerm,
]],
'unique' => true,
]);
$taxonomyIds = $this->Taxonomy->find('column', [
'fields' => ['Taxonomy.id'],
'conditions' => ['OR' => [
'id' => $taxonomyIds,
'LOWER(namespace) LIKE' => $searchTerm,
'LOWER(description) LIKE' => $searchTerm,
]],
]);
return $taxonomyIds;
}
}

View File

@ -33,7 +33,7 @@ class UsersController extends AppController
parent::beforeFilter();
// what pages are allowed for non-logged-in users
$allowedActions = array('login', 'logout');
$allowedActions = array('login', 'logout', 'getGpgPublicKey');
if(!empty(Configure::read('Security.email_otp_enabled'))) {
$allowedActions[] = 'email_otp';
}
@ -51,14 +51,6 @@ class UsersController extends AppController
if (!$this->_isSiteAdmin() && $this->Auth->user('id') != $id) {
throw new NotFoundException(__('Invalid user or not authorised.'));
}
if (!is_numeric($id) && !empty($id)) {
$userId = $this->User->find('first', array(
'conditions' => array('email' => $id),
'fields' => array('id')
));
$id = $userid['User']['id'];
}
$user = $this->User->read(null, $id);
$user = $this->User->find('first', array(
'recursive' => -1,
'conditions' => array('User.id' => $id),
@ -182,7 +174,7 @@ class UsersController extends AppController
}
if (!$abortPost) {
// What fields should be saved (allowed to be saved)
$fieldList = array('autoalert', 'gpgkey', 'certif_public', 'nids_sid', 'contactalert', 'disabled');
$fieldList = array('autoalert', 'gpgkey', 'certif_public', 'nids_sid', 'contactalert', 'disabled', 'date_modified');
if ($this->__canChangeLogin()) {
$fieldList[] = 'email';
}
@ -217,7 +209,6 @@ class UsersController extends AppController
return $this->RestResponse->viewData($this->__massageUserObject($user), $this->response->type());
} else {
$this->Flash->success(__('The profile has been updated'));
$this->_refreshAuth();
$this->redirect(array('action' => 'view', $id));
}
} else {
@ -305,7 +296,6 @@ class UsersController extends AppController
return $this->RestResponse->saveSuccessResponse('User', 'change_pw', false, $this->response->type(), $message);
}
$this->Flash->success($message);
$this->_refreshAuth();
$this->redirect(array('action' => 'view', $id));
} else {
$message = __('The password could not be updated. Make sure you meet the minimum password length / complexity requirements.');
@ -915,8 +905,8 @@ class UsersController extends AppController
if (isset($this->request->data['User']['role_id']) && !array_key_exists($this->request->data['User']['role_id'], $syncRoles)) {
$this->request->data['User']['server_id'] = 0;
}
$fields = array();
$blockedFields = array('id', 'invited_by');
$fields = [];
$blockedFields = array('id', 'invited_by', 'date_modified');
if (!$this->_isSiteAdmin()) {
$blockedFields[] = 'org_id';
}
@ -967,11 +957,15 @@ class UsersController extends AppController
throw new Exception('You are not authorised to assign that role to a user.');
}
}
if (!empty($fields) && $this->User->save($this->request->data, true, $fields)) {
$fields[] = 'date_modified'; // time will be inserted in `beforeSave` action
if ($this->User->save($this->request->data, true, $fields)) {
// newValues to array
$fieldsNewValues = array();
foreach ($fields as $field) {
if ($field != 'confirm_password') {
if ($field === 'date_modified') {
continue;
}
if ($field !== 'confirm_password') {
$newValue = $this->data['User'][$field];
if (gettype($newValue) == 'array') {
$newValueStr = '';
@ -1014,7 +1008,6 @@ class UsersController extends AppController
return $this->RestResponse->viewData($user, $this->response->type());
} else {
$this->Flash->success(__('The user has been saved'));
$this->_refreshAuth(); // in case we modify ourselves
$this->redirect(array('action' => 'index'));
}
} else {
@ -1122,6 +1115,7 @@ class UsersController extends AppController
public function login()
{
$oldHash = false;
if ($this->request->is('post') || $this->request->is('put')) {
$this->Bruteforce = ClassRegistry::init('Bruteforce');
if (!empty($this->request->data['User']['email'])) {
@ -1130,6 +1124,17 @@ class UsersController extends AppController
throw new ForbiddenException('You have reached the maximum number of login attempts. Please wait ' . $expire . ' seconds and try again.');
}
}
// Check the length of the user's authkey match old format. This can be removed in future.
$userPass = $this->User->find('first', [
'conditions' => ['User.email' => $this->request->data['User']['email']],
'fields' => ['User.password'],
'recursive' => -1,
]);
if (!empty($userPass) && strlen($userPass['User']['password']) === 40) {
$oldHash = true;
unset($this->Auth->authenticate['Form']['passwordHasher']); // use default password hasher
$this->Auth->constructAuthenticate();
}
}
if ($this->request->is('post') && Configure::read('Security.email_otp_enabled')) {
$user = $this->Auth->identify($this->request, $this->response);
@ -1142,6 +1147,12 @@ class UsersController extends AppController
$this->set('formLoginEnabled', $formLoginEnabled);
if ($this->Auth->login()) {
if ($oldHash) {
// Convert old style password hash to blowfish
$passwordToSave = $this->request->data['User']['password'];
// Password is converted to hashed form automatically
$this->User->save(['id' => $this->Auth->user('id'), 'password' => $passwordToSave], false, ['password']);
}
$this->_postlogin();
} else {
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
@ -1252,16 +1263,9 @@ class UsersController extends AppController
// Events list
$url = $this->Session->consume('pre_login_requested_url');
if (empty($url)) {
$homepage = $this->User->UserSetting->find('first', array(
'recursive' => -1,
'conditions' => array(
'UserSetting.user_id' => $this->Auth->user('id'),
'UserSetting.setting' => 'homepage'
),
'contain' => array('User.id', 'User.org_id')
));
$homepage = $this->User->UserSetting->getValueForUser($this->Auth->user('id'), 'homepage');
if (!empty($homepage)) {
$url = $homepage['UserSetting']['value']['path'];
$url = $homepage['path'];
} else {
$url = array('controller' => 'events', 'action' => 'index');
}
@ -1309,7 +1313,6 @@ class UsersController extends AppController
}
if (!$this->_isRest()) {
$this->Flash->success(__('New authkey generated.', true));
$this->_refreshAuth();
$this->redirect($this->referer());
} else {
return $this->RestResponse->saveSuccessResponse('User', 'resetauthkey', $id, $this->response->type(), 'Authkey updated: ' . $newkey);
@ -1436,9 +1439,7 @@ class UsersController extends AppController
public function terms()
{
if ($this->request->is('post') || $this->request->is('put')) {
$this->User->id = $this->Auth->user('id');
$this->User->saveField('termsaccepted', true);
$this->_refreshAuth(); // refresh auth info
$this->User->updateField($this->Auth->user(), 'termsaccepted', true);
$this->Flash->success(__('You accepted the Terms and Conditions.'));
$this->redirect(array('action' => 'routeafterlogin'));
}
@ -2278,18 +2279,6 @@ class UsersController extends AppController
$this->set('users', $user_results);
}
// Refreshes the Auth session with new/updated data
protected function _refreshAuth()
{
$oldUser = $this->Auth->user();
$newUser = $this->User->find('first', array('conditions' => array('User.id' => $oldUser['id']), 'recursive' => -1,'contain' => array('Organisation', 'Role')));
// Rearrange it a bit to match the Auth object created during the login
$newUser['User']['Role'] = $newUser['Role'];
$newUser['User']['Organisation'] = $newUser['Organisation'];
unset($newUser['Organisation'], $newUser['Role']);
$this->Auth->login($newUser['User']);
}
public function searchGpgKey($email = false)
{
if (!$email) {
@ -2317,6 +2306,26 @@ class UsersController extends AppController
return new CakeResponse(array('body' => $key));
}
public function getGpgPublicKey()
{
if (!Configure::read("MISP.download_gpg_from_homedir")) {
throw new MethodNotAllowedException("Downloading GPG public key from homedir is not allowed.");
}
$key = $this->User->getGpgPublicKey();
if (!$key) {
throw new NotFoundException("Public key not found.");
}
list($fingeprint, $publicKey) = $key;
$response = new CakeResponse(array(
'body' => $publicKey,
'type' => 'text/plain',
));
$response->download($fingeprint . '.asc');
return $response;
}
public function checkIfLoggedIn()
{
return new CakeResponse(array('body'=> 'OK','status' => 200));

View File

@ -0,0 +1,40 @@
<?php
class CakeResponseTmp extends CakeResponse
{
public function file($path, $options = array())
{
if ($path instanceof TmpFileTool) {
$this->header('Content-Length', $path->size());
$this->_clearBuffer();
$this->_file = $path;
} else {
parent::file($path, $options);
}
}
/**
* @param File|TmpFileTool $file
* @param array $range
* @return bool
* @throws Exception
*/
protected function _sendFile($file, $range)
{
if ($file instanceof TmpFileTool) {
set_time_limit(0);
session_write_close();
foreach ($file->intoChunks() as $chunk) {
if (!$this->_isActive()) {
$file->close();
return false;
}
echo $chunk;
$this->_flushBuffer();
}
return true;
} else {
return parent::_sendFile($file, $range);
}
}
}

View File

@ -177,7 +177,7 @@ class ComplexTypeTool
unset($input);
$iocArray = [];
foreach ($tmpFile->csv($delimiter) as $row) {
foreach ($tmpFile->intoParsedCsv($delimiter) as $row) {
if (!empty($row[0][0]) && $row[0][0] === '#') { // Comment
continue;
}
@ -234,6 +234,7 @@ class ComplexTypeTool
if (isset($resultArray[$typeArray['value']])) {
continue;
}
$typeArray['original_value'] = $ioc;
$resultArray[$typeArray['value']] = $typeArray;
}
return array_values($resultArray);

View File

@ -26,7 +26,15 @@
private function __expandEvent($id)
{
$event = $this->__eventModel->fetchEvent($this->__user, array('eventid' => $id, 'flatten' => 0, 'includeTagRelations' => 1, 'includeGalaxy' => 1, 'includeGranularCorrelations' => 1));
$event = $this->__eventModel->fetchEvent($this->__user, array(
'eventid' => $id,
'flatten' => 0,
'includeTagRelations' => 1,
'includeGalaxy' => 1,
'includeGranularCorrelations' => 1,
'noSightings' => true,
'sgReferenceOnly' => true,
));
if (empty($event)) {
return $this->__json;
}

View File

@ -1,12 +1,15 @@
<?php
class DistributionGraphTool
{
private $__user = false;
/** @var array */
private $__user;
private $__json = array();
/** @var Event */
private $__eventModel;
/** @var Organisation */
private $__organisationModel;
/** @var array */
private $__serverList;
public function construct(Event $eventModel, array $servers, array $user, $extended_view=0)
{
@ -76,27 +79,26 @@ class DistributionGraphTool
$this->__addAdditionalDistributionInfo(3, "All other communities"); // add current community
// connected
$servers = $this->__serverList;
$this->__addAdditionalDistributionInfo(2, "This community"); // add current community
foreach ($servers as $server) {
foreach ($this->__serverList as $server) {
$this->__addAdditionalDistributionInfo(2, $server);
}
// community
$orgs = $this->__organisationModel->find('list', array(
'fields' => array('name'),
'conditions' => array('local' => true)
$orgConditions = $this->__organisationModel->createConditions($this->__user);
$orgConditions['local'] = true;
$orgConditions['id !='] = $this->__user['Organisation']['id'];
$orgs = $this->__organisationModel->find('column', array(
'fields' => ['name'],
'conditions' => $orgConditions,
));
$thisOrg = $this->__user['Organisation']['name'];
$this->__addAdditionalDistributionInfo(1, $thisOrg); // add current community
foreach ($orgs as $org) {
if ($thisOrg != $org) {
$this->__addAdditionalDistributionInfo(1, $org);
}
foreach ($orgs as $orgName) {
$this->__addAdditionalDistributionInfo(1, $orgName);
}
// org only
$thisOrg = $this->__user['Organisation']['name'];
$this->__addAdditionalDistributionInfo(0, $thisOrg); // add current community
}

View File

@ -46,7 +46,7 @@ class GpgTool
*/
public function searchGpgKey($search)
{
$uri = 'https://pgp.circl.lu/pks/lookup?search=' . urlencode($search) . '&op=index&fingerprint=on&options=mr';
$uri = 'https://openpgp.circl.lu/pks/lookup?search=' . urlencode($search) . '&op=index&fingerprint=on&options=mr';
$response = $this->keyServerLookup($uri);
if ($response->code == 404) {
return array(); // no keys found
@ -63,7 +63,7 @@ class GpgTool
*/
public function fetchGpgKey($fingerprint)
{
$uri = 'https://pgp.circl.lu/pks/lookup?search=0x' . urlencode($fingerprint) . '&op=get&options=mr';
$uri = 'https://openpgp.circl.lu/pks/lookup?search=0x' . urlencode($fingerprint) . '&op=get&options=mr';
$response = $this->keyServerLookup($uri);
if ($response->code == 404) {
return null; // key with given fingerprint not found

View File

@ -0,0 +1,132 @@
<?php
App::uses('HttpSocketResponse', 'Network/Http');
App::uses('HttpSocket', 'Network/Http');
class HttpClientJsonException extends Exception
{
/** @var HttpSocketResponse */
private $response;
public function __construct($message, HttpSocketResponseExtended $response, Throwable $previous = null)
{
$this->response = $response;
parent::__construct($message, 0, $previous);
}
/**
* @return HttpSocketResponse
*/
public function getResponse()
{
return $this->response;
}
}
class HttpSocketResponseExtended extends HttpSocketResponse
{
/**
* @param string $message
* @throws SocketException
*/
public function parseResponse($message)
{
parent::parseResponse($message);
$contentEncoding = $this->getHeader('Content-Encoding');
if ($contentEncoding === 'gzip' && function_exists('gzdecode')) {
$this->body = gzdecode($this->body);
if ($this->body === false) {
throw new SocketException("Response should be gzip encoded, but gzip decoding failed.");
}
} else if ($contentEncoding === 'br' && function_exists('brotli_uncompress')) {
$this->body = brotli_uncompress($this->body);
if ($this->body === false) {
throw new SocketException("Response should be brotli encoded, but brotli decoding failed.");
}
} else if ($contentEncoding) {
throw new SocketException("Remote server returns unsupported content encoding '$contentEncoding'");
}
}
/**
* Decodes JSON string and throws exception if string is not valid JSON.
*
* @return array
* @throws HttpClientJsonException
*/
public function json()
{
try {
if (defined('JSON_THROW_ON_ERROR')) {
// JSON_THROW_ON_ERROR is supported since PHP 7.3
$decoded = json_decode($this->body, true, 512, JSON_THROW_ON_ERROR);
} else {
$decoded = json_decode($this->body, true);
if ($decoded === null) {
throw new UnexpectedValueException('Could not parse JSON: ' . json_last_error_msg(), json_last_error());
}
}
return $decoded;
} catch (Exception $e) {
throw new HttpClientJsonException('Could not parse response as JSON.', $this, $e);
}
}
}
/**
* Supports response compression and also decodes response as JSON
*/
class HttpSocketExtended extends HttpSocket
{
public $responseClass = 'HttpSocketResponseExtended';
public function __construct($config = array())
{
parent::__construct($config);
if (isset($config['compress']) && $config['compress']) {
$acceptEncoding = $this->acceptedEncodings();
if (!empty($acceptEncoding)) {
$this->config['request']['header']['Accept-Encoding'] = implode(', ', $this->acceptedEncodings());
}
}
}
/**
* @param array $request
* @return HttpSocketResponseExtended
*/
public function request($request = array())
{
// Reset last error
$this->lastError = [];
/** @var HttpSocketResponseExtended $response */
$response = parent::request($request);
if ($response === false) {
throw new InvalidArgumentException("Invalid argument provided.");
}
// Convert connection timeout to SocketException
if (!empty($this->lastError)) {
throw new SocketException($this->lastError['msg']);
}
return $response;
}
/**
* Returns accepted content encodings (compression algorithms)
* @return string[]
*/
private function acceptedEncodings()
{
$supportedEncoding = [];
// Enable brotli compressed responses if PHP has 'brotli_uncompress' method
if (function_exists('brotli_uncompress')) {
$supportedEncoding[] = 'br';
}
// Enable gzipped responses if PHP has 'gzdecode' method
if (function_exists('gzdecode')) {
$supportedEncoding[] = 'gzip';
}
return $supportedEncoding;
}
}

View File

@ -2,10 +2,17 @@
class SyncTool
{
// take a server as parameter and return a HttpSocket object using the ssl options defined in the server settings
/**
* Take a server as parameter and return a HttpSocket object using the ssl options defined in the server settings
* @param array|null $server
* @param false $timeout
* @param string $model
* @return HttpSocketExtended
* @throws Exception
*/
public function setupHttpSocket($server = null, $timeout = false, $model = 'Server')
{
$params = array();
$params = ['compress' => true];
if (!empty($server)) {
if (!empty($server[$model]['cert_file'])) {
$params['ssl_cafile'] = APP . "files" . DS . "certs" . DS . $server[$model]['id'] . '.pem';
@ -33,12 +40,12 @@ class SyncTool
public function setupHttpSocketFeed($feed = null)
{
return $this->setupHttpSocket();
return $this->createHttpSocket(['compress' => true]);
}
/**
* @param array $params
* @return HttpSocket
* @return HttpSocketExtended
* @throws Exception
*/
public function createHttpSocket($params = array())
@ -52,8 +59,8 @@ class SyncTool
$params['ssl_cafile'] = $caPath;
}
App::uses('HttpSocket', 'Network/Http');
$HttpSocket = new HttpSocket($params);
App::uses('HttpSocketExtended', 'Tools');
$HttpSocket = new HttpSocketExtended($params);
$proxy = Configure::read('Proxy');
if (empty($params['skip_proxy']) && isset($proxy['host']) && !empty($proxy['host'])) {
$HttpSocket->configProxy($proxy['host'], $proxy['port'], $proxy['method'], $proxy['user'], $proxy['password']);

View File

@ -68,7 +68,7 @@ class TmpFileTool
}
/**
* Get one line from file parsed as CSV.
* Returns generator of parsed CSV line from file.
*
* @param string $delimiter
* @param string $enclosure
@ -76,7 +76,7 @@ class TmpFileTool
* @return Generator
* @throws Exception
*/
public function csv($delimiter = ',', $enclosure = '"', $escape = "\\")
public function intoParsedCsv($delimiter = ',', $enclosure = '"', $escape = "\\")
{
$this->rewind();
$line = 0;
@ -88,15 +88,16 @@ class TmpFileTool
$line++;
yield $result;
}
fclose($this->tmpfile);
$this->tmpfile = null;
$this->close();
}
/**
* Returns generator of line from file.
*
* @return Generator
* @throws Exception
*/
public function lines()
public function intoLines()
{
$this->rewind();
while (!feof($this->tmpfile)) {
@ -106,24 +107,64 @@ class TmpFileTool
}
yield $result;
}
fclose($this->tmpfile);
$this->tmpfile = null;
$this->close();
}
/**
* @param int $chunkSize In bytes
* @return Generator
* @throws Exception
*/
public function intoChunks($chunkSize = 8192)
{
$this->rewind();
while (!feof($this->tmpfile)) {
$result = fread($this->tmpfile, $chunkSize);
if ($result === false) {
throw new Exception('Could not read from temporary file.');
}
yield $result;
}
$this->close();
}
/**
* @return string
* @throws Exception
*/
public function finish()
public function intoString()
{
$this->rewind();
$final = stream_get_contents($this->tmpfile);
if ($final === false) {
$string = stream_get_contents($this->tmpfile);
if ($string === false) {
throw new Exception('Could not read from temporary file.');
}
fclose($this->tmpfile);
$this->tmpfile = null;
return $final;
$this->close();
return $string;
}
/**
* Pass data to output.
*
* @throws Exception
*/
public function intoOutput()
{
$this->rewind();
if (fpassthru($this->tmpfile) === false) {
throw new Exception('Could not pass temporary file to output.');
}
$this->close();
}
/**
* @return int
* @throws Exception
*/
public function size()
{
$this->isOpen();
return fstat($this->tmpfile)['size'];
}
/**
@ -132,7 +173,30 @@ class TmpFileTool
*/
public function __toString()
{
return $this->finish();
return $this->intoString();
}
/**
* @return bool
*/
public function close()
{
if ($this->tmpfile) {
$result = fclose($this->tmpfile);
$this->tmpfile = null;
return $result;
}
return true;
}
/**
* @throws Exception
*/
private function isOpen()
{
if ($this->tmpfile === null) {
throw new Exception('Temporary file is already closed.');
}
}
/**
@ -142,6 +206,7 @@ class TmpFileTool
*/
private function rewind()
{
$this->isOpen();
if (fseek($this->tmpfile, 0) === -1) {
throw new Exception('Could not seek to start of temporary file.');
}

View File

@ -55,6 +55,7 @@ class AppModel extends Model
parent::__construct($id, $table, $ds);
$this->name = get_class($this);
$this->findMethods['column'] = true;
}
// deprecated, use $db_changes
@ -87,7 +88,7 @@ class AppModel extends Model
45 => false, 46 => false, 47 => false, 48 => false, 49 => false, 50 => false,
51 => false, 52 => false, 53 => false, 54 => false, 55 => false, 56 => false,
57 => false, 58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
63 => true, 64 => false
63 => true, 64 => false, 65 => false
);
public $advanced_updates_description = array(
@ -1557,6 +1558,15 @@ class AppModel extends Model
KEY `org_id` (`org_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
break;
case 65:
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `correlation_exclusions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value` text NOT NULL,
`from_json` tinyint(1) default 0,
PRIMARY KEY (`id`),
INDEX `value` (`value`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
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;';
@ -1627,10 +1637,8 @@ class AppModel extends Model
break;
default:
return false;
break;
}
$now = new DateTime();
// switch MISP instance live to false
if ($liveOff) {
$this->Server = Classregistry::init('Server');
@ -1643,7 +1651,7 @@ class AppModel extends Model
$this->__setUpdateProgress(0, $total_update_count, $command);
$str_index_array = array();
foreach($indexArray as $toIndex) {
$str_index_array[] = __('Indexing ') . sprintf('%s -> %s', $toIndex[0], $toIndex[1]);
$str_index_array[] = __('Indexing %s -> %s', $toIndex[0], $toIndex[1]);
}
$this->__setUpdateCmdMessages(array_merge($sqlArray, $str_index_array));
$flagStop = false;
@ -1679,10 +1687,10 @@ class AppModel extends Model
'email' => 'SYSTEM',
'action' => 'update_database',
'user_id' => 0,
'title' => __('Successfuly executed the SQL query for ') . $command,
'title' => __('Successfully executed the SQL query for ') . $command,
'change' => sprintf(__('The executed SQL query was: %s'), $sql)
));
$this->__setUpdateResMessages($i, sprintf(__('Successfuly executed the SQL query for %s'), $command));
$this->__setUpdateResMessages($i, sprintf(__('Successfully executed the SQL query for %s'), $command));
} catch (Exception $e) {
$errorMessage = $e->getMessage();
$this->Log->create();
@ -1736,14 +1744,13 @@ class AppModel extends Model
}
}
}
$this->__setUpdateProgress(count($sqlArray)+count($indexArray), false);
$this->__setUpdateProgress(count($sqlArray) + count($indexArray), false);
}
if ($clean) {
$this->cleanCacheFiles();
}
if ($liveOff) {
$liveSetting = 'MISP.live';
$this->Server->serverSettingsSaveValue($liveSetting, true);
$this->Server->serverSettingsSaveValue('MISP.live', true);
}
if (!$flagStop && $errorCount == 0) {
$this->__postUpdate($command);
@ -2132,11 +2139,20 @@ class AppModel extends Model
}
}
if ($requiresLogout) {
$this->updateDatabase('destroyAllSessions');
$this->refreshSessions();
}
return true;
}
/**
* Update date_modified for all users, this will ensure that all users will refresh their session data.
*/
private function refreshSessions()
{
$this->User = ClassRegistry::init('User');
$this->User->updateAll(['date_modified' => time()]);
}
private function __setUpdateProgress($current, $total=false, $toward_db_version=false)
{
$updateProgress = $this->getUpdateProgress();
@ -2735,7 +2751,7 @@ class AppModel extends Model
{
static $versionArray;
if ($versionArray === null) {
$file = new File(ROOT . DS . 'VERSION.json', true);
$file = new File(ROOT . DS . 'VERSION.json');
$versionArray = $this->jsonDecode($file->read());
$file->close();
}
@ -3011,6 +3027,66 @@ class AppModel extends Model
}
}
/**
* Find method that allows to fetch just one column from database.
* @param $state
* @param $query
* @param array $results
* @return array
* @throws Exception
*/
protected function _findColumn($state, $query, $results = array())
{
if ($state === 'before') {
if (count($query['fields']) === 1) {
if (strpos($query['fields'][0], '.') === false) {
$query['fields'][0] = $this->alias . '.' . $query['fields'][0];
}
$query['column'] = $query['fields'][0];
if (isset($query['unique']) && $query['unique']) {
$query['fields'] = array("DISTINCT {$query['fields'][0]}");
} else {
$query['fields'] = array($query['fields'][0]);
}
} else {
throw new Exception("Invalid number of column, expected one, " . count($query['fields']) . " given");
}
if (!isset($query['recursive'])) {
$query['recursive'] = -1;
}
return $query;
}
// Faster version of `Hash::extract`
foreach (explode('.', $query['column']) as $part) {
$results = array_column($results, $part);
}
return $results;
}
/**
* @param string $field
* @param AppModel $model
* @param array $conditions
*/
public function addCountField($field, AppModel $model, array $conditions)
{
$db = $this->getDataSource();
$subQuery = $db->buildStatement(
array(
'fields' => ['COUNT(*)'],
'table' => $db->fullTableName($model),
'alias' => $model->alias,
'conditions' => $conditions,
),
$model
);
$this->virtualFields[$field] = $subQuery;
}
/**
* Log exception with backtrace and with nested exceptions.
*

File diff suppressed because it is too large Load Diff

View File

@ -42,22 +42,38 @@ class AuthKey extends AppModel
$this->data['AuthKey']['authkey_end'] = substr($authkey, -4);
$this->data['AuthKey']['authkey_raw'] = $authkey;
$this->authkey_raw = $authkey;
$validity = Configure::read('Security.advanced_authkeys_validity');
if (empty($this->data['AuthKey']['expiration'])) {
$this->data['AuthKey']['expiration'] = 0;
$this->data['AuthKey']['expiration'] = $validity ? strtotime("+$validity days") : 0;
} else {
$this->data['AuthKey']['expiration'] = strtotime($this->data['AuthKey']['expiration']);
$expiration = is_numeric($this->data['AuthKey']['expiration']) ?
(int)$this->data['AuthKey']['expiration'] :
strtotime($this->data['AuthKey']['expiration']);
if ($expiration === false) {
$this->invalidate('expiration', __('Expiration must be in YYYY-MM-DD format.'));
}
if ($validity && $expiration > strtotime("+$validity days")) {
$this->invalidate('expiration', __('Maximal key validity is %s days.', $validity));
}
$this->data['AuthKey']['expiration'] = $expiration;
}
}
return true;
}
/**
* @param string $authkey
* @return array|false
*/
public function getAuthUserByAuthKey($authkey)
{
$start = substr($authkey, 0, 4);
$end = substr($authkey, -4);
$existing_authkeys = $this->find('all', [
'recursive' => -1,
'fields' => ['authkey', 'user_id'],
'fields' => ['id', 'authkey', 'user_id', 'expiration'],
'conditions' => [
'OR' => [
'expiration >' => time(),
@ -70,7 +86,12 @@ class AuthKey extends AppModel
$passwordHasher = $this->getHasher();
foreach ($existing_authkeys as $existing_authkey) {
if ($passwordHasher->check($authkey, $existing_authkey['AuthKey']['authkey'])) {
return $this->User->getAuthUser($existing_authkey['AuthKey']['user_id']);
$user = $this->User->getAuthUser($existing_authkey['AuthKey']['user_id']);
if ($user) {
$user['authkey_id'] = $existing_authkey['AuthKey']['id'];
$user['authkey_expiration'] = $existing_authkey['AuthKey']['expiration'];
}
return $user;
}
}
return false;
@ -105,6 +126,67 @@ class AuthKey extends AppModel
}
}
/**
* @param int $id
* @return array
* @throws Exception
*/
public function getKeyUsage($id)
{
$redis = $this->setupRedisWithException();
$data = $redis->hGetAll("misp:authkey_usage:$id");
$output = [];
$uniqueIps = [];
foreach ($data as $key => $count) {
list($date, $ip) = explode(':', $key);
$uniqueIps[$ip] = true;
if (isset($output[$date])) {
$output[$date] += $count;
} else {
$output[$date] = $count;
}
}
// Data from redis are not sorted
ksort($output);
$lastUsage = $redis->get("misp:authkey_last_usage:$id");
$lastUsage = $lastUsage === false ? null : (int)$lastUsage;
return [$output, $lastUsage, count($uniqueIps)];
}
/**
* @param array $ids
* @return array<DateTime|null>
* @throws Exception
*/
public function getLastUsageForKeys(array $ids)
{
$redis = $this->setupRedisWithException();
$keys = array_map(function($id) {
return "misp:authkey_last_usage:$id";
}, $ids);
$lastUsages = $redis->mget($keys);
$output = [];
foreach (array_values($ids) as $i => $id) {
$output[$id] = $lastUsages[$i] === false ? null : (int)$lastUsages[$i];
}
return $output;
}
/**
* When key is deleted, update after `date_modified` for user that was assigned to that key, so session data
* will be realoaded and canceled.
* @see AppController::_refreshAuth
*/
public function afterDelete()
{
parent::afterDelete();
$userId = $this->data['AuthKey']['user_id'];
$this->User->updateAll(['date_modified' => time()], ['User.id' => $userId]);
}
/**
* @return AbstractPasswordHasher
*/

View File

@ -0,0 +1,108 @@
<?php
App::uses('AppModel', 'Model');
App::uses('RandomTool', 'Tools');
class CorrelationExclusion extends AppModel
{
public $recursive = -1;
public $key = 'misp:correlation_exclusions';
public $actsAs = array(
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full'),
'Containable',
);
public function afterSave($created, $options = array())
{
$this->cacheValues();
}
public function afterDelete()
{
$this->cacheValues();
}
public function cacheValues()
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$redis->del($this->key);
$exclusions = $this->find('list', [
'recursive' => -1,
'fields' => ['id', 'value']
]);
$redis->sAddArray($this->key, $exclusions);
}
public function cleanRouter($user)
{
if (Configure::read('MISP.background_jobs')) {
$this->Job = ClassRegistry::init('Job');
$this->Job->create();
$data = [
'worker' => 'default',
'job_type' => 'clean_correlation_exclusions',
'job_input' => '',
'status' => 0,
'retries' => 0,
'org' => $user['Organisation']['name'],
'message' => __('Cleaning up excluded correlations.'),
];
$this->Job->save($data);
$jobId = $this->Job->id;
$process_id = CakeResque::enqueue(
'default',
'AdminShell',
['cleanExcludedCorrelations', $jobId],
true
);
$this->Job->saveField('process_id', $process_id);
$message = __('Cleanup queued for background execution.');
} else {
$this->clean();
}
}
public function clean($jobId = false)
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$this->Correlation = ClassRegistry::init('Correlation');
$exclusions = $redis->sMembers($this->key);
$conditions = [];
$exclusions = array_chunk($exclusions, 100);
if ($jobId) {
$this->Job = ClassRegistry::init('Job');
$this->Job->id = $jobId;
}
foreach ($exclusions as $exclusion_chunk) {
$i = 0;
foreach ($exclusion_chunk as $exclusion) {
$i += 1;
if (!empty($exclusion)) {
if ($exclusion[0] === '%' || substr($exclusion, -1) === '%') {
$conditions['OR'][] = ['Correlation.value LIKE' => $exclusion];
} else {
$conditions['OR']['Correlation.value'][] = $exclusion;
}
}
if (!empty($conditions)) {
$this->Correlation->deleteAll($conditions);
}
if ($i % 100 === 0) {
$this->Job->saveProgress($jobId, 'Chunk ' . $i . '/' . $total, $i * 100 / $total);
}
}
}
}
}

View File

@ -11,6 +11,7 @@ App::uses('TmpFileTool', 'Tools');
* @property ShadowAttribute $ShadowAttribute
* @property EventTag $EventTag
* @property SharingGroup $SharingGroup
* @property ThreatLevel $ThreatLevel
*/
class Event extends AppModel
{
@ -62,120 +63,7 @@ class Event extends AppModel
public $shortDist = array(0 => 'Organisation', 1 => 'Community', 2 => 'Connected', 3 => 'All', 4 => ' sharing Group');
public $export_types = array(
'json' => array(
'extension' => '.json',
'type' => 'JSON',
'scope' => 'Event',
'requiresPublished' => 0,
'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'json'),
'description' => 'Click this to download all events and attributes that you have access to in MISP JSON format.',
),
'xml' => array(
'extension' => '.xml',
'type' => 'XML',
'scope' => 'Event',
'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'xml'),
'requiresPublished' => 0,
'description' => 'Click this to download all events and attributes that you have access to in MISP XML format.',
),
'csv_sig' => array(
'extension' => '.csv',
'type' => 'CSV_Sig',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('published' => 1, 'to_ids' => 1, 'returnFormat' => 'csv'),
'description' => 'Click this to download all attributes that are indicators and that you have access to <small>(except file attachments)</small> in CSV format.',
),
'csv_all' => array(
'extension' => '.csv',
'type' => 'CSV_All',
'scope' => 'Event',
'requiresPublished' => 0,
'params' => array('ignore' => 1, 'returnFormat' => 'csv'),
'description' => 'Click this to download all attributes that you have access to <small>(except file attachments)</small> in CSV format.',
),
'suricata' => array(
'extension' => '.rules',
'type' => 'Suricata',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'suricata'),
'description' => 'Click this to download all network related attributes that you have access to under the Suricata rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.',
),
'snort' => array(
'extension' => '.rules',
'type' => 'Snort',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'snort'),
'description' => 'Click this to download all network related attributes that you have access to under the Snort rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.',
),
'bro' => array(
'extension' => '.intel',
'type' => 'Bro',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'bro'),
'description' => 'Click this to download all network related attributes that you have access to under the Bro rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.',
),
'stix' => array(
'extension' => '.xml',
'type' => 'STIX',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'stix', 'includeAttachments' => 1),
'description' => 'Click this to download a STIX document containing the STIX version of all events and attributes that you have access to.'
),
'stix-json' => array(
'extension' => '.json',
'type' => 'STIX',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'stix', 'includeAttachments' => 1),
'description' => 'Click this to download a STIX document containing the STIX version of all events and attributes that you have access to.'
),
'stix2' => array(
'extension' => '.json',
'type' => 'STIX2',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'stix2', 'includeAttachments' => 1),
'description' => 'Click this to download a STIX2 document containing the STIX2 version of all events and attributes that you have access to.'
),
'rpz' => array(
'extension' => '.txt',
'type' => 'RPZ',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'rpz'),
'description' => 'Click this to download an RPZ Zone file generated from all ip-src/ip-dst, hostname, domain attributes. This can be useful for DNS level firewalling. Only published events and attributes marked as IDS Signature are exported.'
),
'text' => array(
'extension' => '.txt',
'type' => 'TEXT',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'text', 'includeAttachments' => 1),
'description' => 'Click on one of the buttons below to download all the attributes with the matching type. This list can be used to feed forensic software when searching for susipicious files. Only published events and attributes marked as IDS Signature are exported.'
),
'yara' => array(
'extension' => '.yara',
'type' => 'Yara',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'yara'),
'description' => 'Click this to download Yara rules generated from all relevant attributes.'
),
'yara-json' => array(
'extension' => '.json',
'type' => 'Yara',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'yara-json'),
'description' => 'Click this to download Yara rules generated from all relevant attributes. Rules are returned in a JSON format with information about origin (generated or parsed) and validity.'
),
);
public $export_types = [];
public $validFormats = array(
'attack' => array('html', 'AttackExport', 'html'),
@ -654,10 +542,10 @@ class Event extends AppModel
}
}
public function attachtagsToEvents($events)
public function attachTagsToEvents(array $events)
{
$tagsToFetch = array();
foreach ($events as $k => $event) {
foreach ($events as $event) {
if (!empty($event['EventTag'])) {
foreach ($event['EventTag'] as $et) {
$tagsToFetch[$et['tag_id']] = $et['tag_id'];
@ -669,11 +557,11 @@ class Event extends AppModel
'recursive' => -1,
'order' => false
));
$tags = Set::combine($tags, '{n}.Tag.id', '{n}');
$tags = array_column(array_column($tags, 'Tag'), null, 'id');
foreach ($events as $k => $event) {
if (!empty($event['EventTag'])) {
foreach ($event['EventTag'] as $k2 => $et) {
$events[$k]['EventTag'][$k2]['Tag'] = $tags[$et['tag_id']]['Tag'];
$events[$k]['EventTag'][$k2]['Tag'] = $tags[$et['tag_id']];
}
}
}
@ -688,7 +576,7 @@ class Event extends AppModel
$sgids = array(-1);
}
$this->Correlation = ClassRegistry::init('Correlation');
$eventIds = Set::extract('/Event/id', $events);
$eventIds = array_column(array_column($events, 'Event'), 'id');
$conditionsCorrelation = $this->__buildEventConditionsCorrelation($user, $eventIds, $sgids);
$correlations = $this->Correlation->find('all', array(
'fields' => array('Correlation.1_event_id', 'count(distinct(Correlation.event_id)) as count'),
@ -698,14 +586,14 @@ class Event extends AppModel
));
$correlations = Hash::combine($correlations, '{n}.Correlation.1_event_id', '{n}.0.count');
foreach ($events as &$event) {
$event['Event']['correlation_count'] = (isset($correlations[$event['Event']['id']])) ? $correlations[$event['Event']['id']] : 0;
$event['Event']['correlation_count'] = isset($correlations[$event['Event']['id']]) ? $correlations[$event['Event']['id']] : 0;
}
return $events;
}
public function attachSightingsCountToEvents($user, $events)
{
$eventIds = Set::extract('/Event/id', $events);
$eventIds = array_column(array_column($events, 'Event'), 'id');
$this->Sighting = ClassRegistry::init('Sighting');
$sightings = $this->Sighting->find('all', array(
'fields' => array('Sighting.event_id', 'count(distinct(Sighting.id)) as count'),
@ -715,14 +603,14 @@ class Event extends AppModel
));
$sightings = Hash::combine($sightings, '{n}.Sighting.event_id', '{n}.0.count');
foreach ($events as $key => $event) {
$events[$key]['Event']['sightings_count'] = (isset($sightings[$event['Event']['id']])) ? $sightings[$event['Event']['id']] : 0;
$events[$key]['Event']['sightings_count'] = isset($sightings[$event['Event']['id']]) ? $sightings[$event['Event']['id']] : 0;
}
return $events;
}
public function attachProposalsCountToEvents($user, $events)
{
$eventIds = Set::extract('/Event/id', $events);
$eventIds = array_column(array_column($events, 'Event'), 'id');
$proposals = $this->ShadowAttribute->find('all', array(
'fields' => array('ShadowAttribute.event_id', 'count(distinct(ShadowAttribute.id)) as count'),
'conditions' => array('event_id' => $eventIds, 'deleted' => 0),
@ -731,14 +619,14 @@ class Event extends AppModel
));
$proposals = Hash::combine($proposals, '{n}.ShadowAttribute.event_id', '{n}.0.count');
foreach ($events as $key => $event) {
$events[$key]['Event']['proposals_count'] = (isset($proposals[$event['Event']['id']])) ? $proposals[$event['Event']['id']] : 0;
$events[$key]['Event']['proposals_count'] = isset($proposals[$event['Event']['id']]) ? $proposals[$event['Event']['id']] : 0;
}
return $events;
}
public function attachDiscussionsCountToEvents($user, $events)
{
$eventIds = Set::extract('/Event/id', $events);
$eventIds = array_column(array_column($events, 'Event'), 'id');
$this->Thread = ClassRegistry::init('Thread');
$threads = $this->Thread->find('list', array(
'conditions' => array('Thread.event_id' => $eventIds),
@ -843,18 +731,16 @@ class Event extends AppModel
// ii. Atttibute has a distribution between 1-3 (community only, connected communities, all orgs)
// iii. Attribute has a sharing group that the user is accessible to view
$conditionsCorrelation = $this->__buildEventConditionsCorrelation($user, $eventId, $sgids);
$correlations = $this->Correlation->find('list', array(
'fields' => array('Correlation.event_id', 'Correlation.event_id'),
'conditions' => $conditionsCorrelation,
'recursive' => 0,
'group' => 'Correlation.event_id',
'order' => array('Correlation.event_id DESC')));
$relatedEventIds = $this->Correlation->find('column', array(
'fields' => array('Correlation.event_id'),
'conditions' => $conditionsCorrelation,
'unique' => true,
));
if (empty($correlations)) {
if (empty($relatedEventIds)) {
return [];
}
$relatedEventIds = array_values($correlations);
// now look up the event data for these attributes
$conditions = $this->createEventConditions($user);
$conditions['AND'][] = array('Event.id' => $relatedEventIds);
@ -1088,7 +974,7 @@ class Event extends AppModel
public function uploadEventToServer($event, $server, $HttpSocket = null, $scope = 'events')
{
$this->Server = ClassRegistry::init('Server');
$push = $this->Server->checkVersionCompatibility($server['Server']['id'], false, $HttpSocket);
$push = $this->Server->checkVersionCompatibility($server, false, $HttpSocket);
if ($scope === 'events' && empty($push['canPush'])) {
return 'The remote user is not a sync user - the upload of the event has been blocked.';
} elseif ($scope === 'sightings' && empty($push['canPush']) && empty($push['canSight'])) {
@ -1188,14 +1074,18 @@ class Event extends AppModel
if (is_numeric($event)) {
return $event;
}
$url = $server['Server']['url'];
$HttpSocket = $this->setupHttpSocket($server, $HttpSocket);
$request = $this->setupSyncRequest($server);
if ($scope === 'sightings') {
$scope .= '/bulkSaveSightings';
$urlPath = $event['Event']['uuid'];
}
$url = $server['Server']['url'];
$uri = $url . '/' . $scope . $this->__getLastUrlPathComponent($urlPath);
if ($scope === 'event') {
// After creating or editing event, it is not necessary to fetch full event
$uri .= '/metadata:1';
}
$data = json_encode($event);
if (!empty(Configure::read('Security.sync_audit'))) {
$pushLogEntry = sprintf(
@ -1467,40 +1357,32 @@ class Event extends AppModel
* @param int $eventId
* @param array $server
* @param null|HttpSocket $HttpSocket
* @param boolean $metadataOnly, if True, we only retrieve the metadata
* without attributes and attachments which is much faster
* @param boolean $metadataOnly, if True, we only retrieve the metadata, without attributes and attachments which is much faster
* @return array
* @throws Exception
*/
public function downloadEventFromServer($eventId, $server, $HttpSocket=null, $metadataOnly=false)
public function downloadEventFromServer($eventId, $server, HttpSocket $HttpSocket=null, $metadataOnly=false)
{
$url = $server['Server']['url'];
$HttpSocket = $this->setupHttpSocket($server, $HttpSocket);
$request = $this->setupSyncRequest($server);
if ($metadataOnly) {
$uri = $url . '/events/index';
$data = ['eventid' => $eventId];
$data = json_encode($data);
$data = json_encode(['eventid' => $eventId]);
$response = $HttpSocket->post($uri, $data, $request);
} else {
$uri = $url . '/events/view/' . $eventId . '/deleted[]:0/deleted[]:1/excludeGalaxy:1';
if (!empty($server['Server']['internal'])) {
$uri = $uri . '/excludeLocalTags:1';
}
$response = $HttpSocket->get($uri, $data = '', $request);
$response = $HttpSocket->get($uri, [], $request);
}
if ($response === false) {
throw new Exception("Could not reach '$uri'.");
} else if (!$response->isOk()) {
if (!$response->isOk()) {
throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
}
$event = json_decode($response->body, true);
if ($event === null) {
throw new Exception('Could not parse event JSON: ' . json_last_error_msg(), json_last_error());
}
return $event;
return $this->jsonDecode($response->body);
}
public function quickDelete($event)
@ -1803,11 +1685,10 @@ class Event extends AppModel
{
$conditions = $this->createEventConditions($user);
$conditions['AND'][] = $params['conditions'];
$results = array_values($this->find('list', array(
$results = $this->find('column', array(
'conditions' => $conditions,
'recursive' => -1,
'fields' => array('Event.id')
)));
));
return $results;
}
@ -1883,9 +1764,9 @@ class Event extends AppModel
if ($list) {
$params = array(
'conditions' => $conditions,
'recursive' => -1,
'fields' => ['Event.id'],
);
$results = array_values($this->find('list', $params));
$results = $this->find('column', $params);
} else {
$params = array(
'conditions' => $conditions,
@ -2588,6 +2469,12 @@ class Event extends AppModel
$event['Event']['extensionEvents'][$eventMeta['id']] = $eventMeta;
$thingsToMerge = array('Attribute', 'Object', 'ShadowAttribute', 'Galaxy');
foreach ($thingsToMerge as $thingToMerge) {
if (!isset($event[$thingToMerge])) {
$event[$thingToMerge] = [];
}
if (!isset($extensionEvent[$thingToMerge])) {
$extensionEvent[$thingToMerge] = [];
}
$event[$thingToMerge] = array_merge($event[$thingToMerge], $extensionEvent[$thingToMerge]);
}
// Merge event reports if requested
@ -2698,7 +2585,7 @@ class Event extends AppModel
$existingOrg = $this->Orgc->find('first', array(
'recursive' => -1,
'conditions' => array('Orgc.name' => $org),
'fields' => array('Orgc.name', 'Orgc.id')
'fields' => array('Orgc.id')
));
if (empty($existingOrg)) {
$params['org']['OR'][$k] = -1;
@ -2715,7 +2602,7 @@ class Event extends AppModel
$existingOrg = $this->Orgc->find('first', array(
'recursive' => -1,
'conditions' => array('Orgc.name' => $org),
'fields' => array('Orgc.name', 'Orgc.id')
'fields' => array('Orgc.id')
));
if (!empty($existingOrg)) {
$temp[] = $existingOrg['Orgc']['id'];
@ -4205,7 +4092,7 @@ class Event extends AppModel
if (isset($data['Event']['EventReport'])) {
foreach ($data['Event']['EventReport'] as $i => $report) {
$nothingToChange = false;
$result = $this->EventReport->editReport($user, $report, $this->id, true, $nothingToChange);
$result = $this->EventReport->editReport($user, ['EventReport' => $report], $this->id, true, $nothingToChange);
if (!empty($result)) {
$validationErrors['EventReport'][] = $result;
}
@ -6150,8 +6037,8 @@ class Event extends AppModel
}
}
}
$data[$dataType . 'Tag'] = array_values($data[$dataType . 'Tag']);
}
$data[$dataType . 'Tag'] = array_values($data[$dataType . 'Tag']);
return $data;
}
@ -6995,7 +6882,7 @@ class Event extends AppModel
unset($result);
unset($temp);
$tmpfile->write($exportTool->footer($exportToolParams));
return $tmpfile->finish();
return $tmpfile;
}
/*

View File

@ -161,6 +161,8 @@ class EventReport extends AppModel
$errors[] = __('Event Report not found.');
return $errors;
}
} else {
$report['EventReport']['id'] = $existingReport['EventReport']['id'];
}
if ($fromPull) {
@ -521,7 +523,8 @@ class EventReport extends AppModel
return $errors;
}
public function applySuggestions($user, $report, $contentWithSuggestions, $suggestionsMapping) {
public function applySuggestions(array $user, array $report, $contentWithSuggestions, array $suggestionsMapping)
{
$errors = [];
$replacedContent = $contentWithSuggestions;
$success = 0;
@ -546,10 +549,10 @@ class EventReport extends AppModel
return $errors;
}
public function applySuggestionsInText($contentWithSuggestions, $attribute, $value)
public function applySuggestionsInText($contentWithSuggestions, array $attribute, $value)
{
$textToBeReplaced = sprintf('@[suggestion](%s)', $value);
$textToInject = sprintf('@[attribute](%s)', $attribute['Attribute']['uuid']);
$textToBeReplaced = "@[suggestion]($value)";
$textToInject = "@[attribute]({$attribute['Attribute']['uuid']})";
$replacedContent = str_replace($textToBeReplaced, $textToInject, $contentWithSuggestions);
return $replacedContent;
}
@ -642,25 +645,36 @@ class EventReport extends AppModel
];
}
public function transformFreeTextIntoSuggestion($content, $complexTypeToolResult)
public function transformFreeTextIntoSuggestion($content, array $complexTypeToolResult)
{
$replacedContent = $content;
$suggestionsMapping = [];
$typeToCategoryMapping = $this->Event->Attribute->typeToCategoryMapping();
foreach ($complexTypeToolResult as $i => $complexTypeToolEntry) {
// Sort by original value string length, longest values first
usort($complexTypeToolResult, function ($a, $b) {
$strlenA = strlen($a['original_value']);
$strlenB = strlen($b['original_value']);
if ($strlenA === $strlenB) {
return 0;
}
return ($strlenA < $strlenB) ? 1 : -1;
});
$suggestionsMapping = [];
foreach ($complexTypeToolResult as $complexTypeToolEntry) {
$textToBeReplaced = $complexTypeToolEntry['value'];
$textToInject = sprintf('@[suggestion](%s)', $textToBeReplaced);
$textToInject = "@[suggestion]($textToBeReplaced)";
$suggestionsMapping[$textToBeReplaced] = [
'category' => $typeToCategoryMapping[$complexTypeToolEntry['default_type']][0],
'type' => $complexTypeToolEntry['default_type'],
'value' => $textToBeReplaced,
'to_ids' => $complexTypeToolEntry['to_ids'],
];
$replacedContent = str_replace($textToBeReplaced, $textToInject, $replacedContent);
$replacedContent = str_replace($complexTypeToolEntry['original_value'], $textToInject, $replacedContent);
}
return [
'contentWithSuggestions' => $replacedContent,
'suggestionsMapping' => $suggestionsMapping
'suggestionsMapping' => $suggestionsMapping,
];
}
@ -674,21 +688,17 @@ class EventReport extends AppModel
return $complexTypeToolResult;
}
public function getComplexTypeToolResultFromReport($content)
public function getComplexTypeToolResultWithReplacements(array $user, array $report)
{
App::uses('ComplexTypeTool', 'Tools');
$complexTypeTool = new ComplexTypeTool();
$this->Warninglist = ClassRegistry::init('Warninglist');
$complexTypeTool->setTLDs($this->Warninglist->fetchTLDLists());
$complexTypeToolResult = $complexTypeTool->checkComplexRouter($content, 'freetext');
return $complexTypeToolResult;
}
public function getComplexTypeToolResultWithReplacements($user, $report)
{
$complexTypeToolResult = $this->getComplexTypeToolResultFromReport($report['EventReport']['content']);
$complexTypeToolResult = $complexTypeTool->checkFreeText($report['EventReport']['content']);
$replacementResult = $this->transformFreeTextIntoReplacement($user, $report, $complexTypeToolResult);
$complexTypeToolResult = $this->getComplexTypeToolResultFromReport($replacementResult['contentWithReplacements']);
$complexTypeToolResult = $complexTypeTool->checkFreeText($replacementResult['contentWithReplacements']);
return [
'complexTypeToolResult' => $complexTypeToolResult,
'replacementResult' => $replacementResult,
@ -700,6 +710,7 @@ class EventReport extends AppModel
*
* @param array $user
* @param array $report
* @param array $options
* @return array
*/
public function extractWithReplacements(array $user, array $report, array $options = [])
@ -713,7 +724,6 @@ class EventReport extends AppModel
'attack' => true,
];
$options = array_merge($baseOptions, $options);
$originalContent = $report['EventReport']['content'];
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
$mitreAttackGalaxyId = $this->GalaxyCluster->Galaxy->getMitreAttackGalaxyId();
$clusterContain = ['Tag'];
@ -734,17 +744,21 @@ class EventReport extends AppModel
'contain' => $clusterContain
]);
$originalContent = $report['EventReport']['content'];
// Remove all existing event report markers
$content = preg_replace("/@\[(attribute|tag|galaxymatrix)]\([^)]*\)/", '', $originalContent);
if ($options['tags']) {
$this->Tag = ClassRegistry::init('Tag');
$tags = $this->Tag->fetchUsableTags($user);
foreach ($tags as $i => $tag) {
foreach ($tags as $tag) {
$tagName = $tag['Tag']['name'];
$found = $this->isValidReplacementTag($originalContent, $tagName);
$found = $this->isValidReplacementTag($content, $tagName);
if ($found) {
$replacedContext[$tagName][$tagName] = $tag['Tag'];
} else {
$tagNameUpper = strtoupper($tagName);
$found = $this->isValidReplacementTag($originalContent, $tagNameUpper);
$found = $this->isValidReplacementTag($content, $tagNameUpper);
if ($found) {
$replacedContext[$tagNameUpper][$tagName] = $tag['Tag'];
}
@ -752,10 +766,10 @@ class EventReport extends AppModel
}
}
foreach ($clusters as $i => $cluster) {
foreach ($clusters as $cluster) {
$cluster['GalaxyCluster']['colour'] = '#0088cc';
$tagName = $cluster['GalaxyCluster']['tag_name'];
$found = $this->isValidReplacementTag($originalContent, $tagName);
$found = $this->isValidReplacementTag($content, $tagName);
if ($found) {
$replacedContext[$tagName][$tagName] = $cluster['GalaxyCluster'];
}
@ -765,10 +779,10 @@ class EventReport extends AppModel
$replacedContext[$cluster['GalaxyCluster']['value']][$tagName] = $cluster['GalaxyCluster'];
}
if ($options['synonyms']) {
foreach ($cluster['GalaxyElement'] as $j => $element) {
foreach ($cluster['GalaxyElement'] as $element) {
if (strlen($element['value']) >= $options['synonyms_min_characters']) {
$toSearch = ' ' . $element['value'] . ' ';
$found = strpos($originalContent, $toSearch) !== false;
$found = strpos($content, $toSearch) !== false;
if ($found) {
$replacedContext[$element['value']][$tagName] = $cluster['GalaxyCluster'];
}
@ -783,22 +797,22 @@ class EventReport extends AppModel
'conditions' => ['GalaxyCluster.galaxy_id' => $mitreAttackGalaxyId],
'contain' => $clusterContain
]);
foreach ($attackClusters as $i => $cluster) {
foreach ($attackClusters as $cluster) {
$cluster['GalaxyCluster']['colour'] = '#0088cc';
$tagName = $cluster['GalaxyCluster']['tag_name'];
$toSearch = ' ' . $cluster['GalaxyCluster']['value'] . ' ';
$found = strpos($originalContent, $toSearch) !== false;
$found = strpos($content, $toSearch) !== false;
if ($found) {
$replacedContext[$cluster['GalaxyCluster']['value']][$tagName] = $cluster['GalaxyCluster'];
} else {
$clusterParts = explode(' - ', $cluster['GalaxyCluster']['value'], 2);
$toSearch = ' ' . $clusterParts[0] . ' ';
$found = strpos($originalContent, $toSearch) !== false;
$found = strpos($content, $toSearch) !== false;
if ($found) {
$replacedContext[$clusterParts[0]][$tagName] = $cluster['GalaxyCluster'];
} else {
} else if (isset($clusterParts[1])) {
$toSearch = ' ' . $clusterParts[1] . ' ';
$found = strpos($originalContent, $toSearch) !== false;
$found = strpos($content, $toSearch) !== false;
if ($found) {
$replacedContext[$clusterParts[1]][$tagName] = $cluster['GalaxyCluster'];
}
@ -810,14 +824,32 @@ class EventReport extends AppModel
'replacedContext' => $replacedContext
];
if ($options['replace']) {
// Sort by original value string length, longest values first
uksort($replacedContext, function ($a, $b) {
$strlenA = strlen($a);
$strlenB = strlen($b);
if ($strlenA === $strlenB) {
return 0;
}
return ($strlenA < $strlenB) ? 1 : -1;
});
$content = $originalContent;
$secondPassReplace = [];
// Replace in two pass to prevent double replace
$id = 0;
foreach ($replacedContext as $rawText => $replacements) {
// Replace with first one until a better strategy is found
reset($replacements);
$replacement = key($replacements);
$textToInject = sprintf('@[tag](%s)', $replacement);
$content = str_replace($rawText, $textToInject, $content);
++$id;
$content = str_replace($rawText, "@[mark]($id)", $content);
$secondPassReplace[$id] = "@[tag]($replacement)";
}
$content = preg_replace_callback("/@\[mark]\(([^)]*)\)/", function ($matches) use ($secondPassReplace) {
return $secondPassReplace[$matches[1]];
}, $content);
$toReturn['contentWithReplacements'] = $content;
}
return $toReturn;
@ -835,7 +867,6 @@ class EventReport extends AppModel
'event_id' => $event_id,
'url' => $url
];
$module = $this->isFetchURLModuleEnabled();
if (!empty($module)) {
$result = $this->Module->queryModuleServer($modulePayload, false);
if (empty($result['results'][0]['values'][0])) {
@ -853,7 +884,7 @@ class EventReport extends AppModel
}
/**
* findValidReplacementTag Search if tagName is in content and is not wrapped in a tag reference
* findValidReplacementTag Search if tagName is in content
*
* @param string $content
* @param string $tagName
@ -861,25 +892,8 @@ class EventReport extends AppModel
*/
private function isValidReplacementTag($content, $tagName)
{
$lastIndex = 0;
$allIndices = [];
$toSearch = strpos($tagName, ':') === false ? ' ' . $tagName . ' ' : $tagName;
while (($lastIndex = strpos($content, $toSearch, $lastIndex)) !== false) {
$allIndices[] = $lastIndex;
$lastIndex = $lastIndex + strlen($toSearch);
}
if (empty($allIndices)) {
return false;
} else {
$wrapper = '@[tag](';
foreach ($allIndices as $i => $index) {
$stringBeforeTag = substr($content, $index - strlen($wrapper), strlen($wrapper));
if ($stringBeforeTag != $wrapper) {
return true;
}
}
return false;
}
return strpos($content, $toSearch) !== false;
}
public function attachTagsAfterReplacements($user, $replacedContext, $eventId)

View File

@ -180,7 +180,7 @@ class Feed extends AppModel
$tmpFile->write(trim($data));
unset($data);
return $tmpFile->csv();
return $tmpFile->intoParsedCsv();
}
/**
@ -364,18 +364,18 @@ class Feed extends AppModel
$redisResultToAttributePosition = [];
foreach ($attributes as $k => $attribute) {
if (in_array($attribute['type'], $this->Attribute->nonCorrelatingTypes)) {
if (in_array($attribute['type'], $this->Attribute->nonCorrelatingTypes, true)) {
continue; // attribute type is not correlateable
}
if (!empty($attribute['disable_correlation'])) {
continue; // attribute correlation is disabled
}
if (in_array($attribute['type'], $compositeTypes)) {
if (in_array($attribute['type'], $compositeTypes, true)) {
list($value1, $value2) = explode('|', $attribute['value']);
$parts = [$value1];
if (!in_array($attribute['type'], $this->Attribute->primaryOnlyCorrelatingTypes)) {
if (!in_array($attribute['type'], $this->Attribute->primaryOnlyCorrelatingTypes, true)) {
$parts[] = $value2;
}
} else {
@ -442,12 +442,17 @@ class Feed extends AppModel
if (!isset($event[$scope][$sourceId])) {
$event[$scope][$sourceId] = $source[$scope];
}
$attributePosition = $redisResultToAttributePosition[$hitIds[$k]];
$attributes[$attributePosition][$scope][] = $source[$scope];
$alreadyAttached = isset($attributes[$attributePosition][$scope]) &&
in_array($sourceId, array_column($attributes[$attributePosition][$scope], 'id'));
if (!$alreadyAttached) {
$attributes[$attributePosition][$scope][] = $source[$scope];
}
$sourceHasHit = true;
}
}
// Append also exact MISP feed event UUID
// Append also exact MISP feed or server event UUID
// TODO: This can be optimised in future to do that in one pass
if ($sourceHasHit && ($scope === 'Server' || $source[$scope]['source_format'] === 'misp')) {
$pipe = $redis->multi(Redis::PIPELINE);
@ -475,7 +480,9 @@ class Feed extends AppModel
$attributePosition = $eventUuidHitPosition[$sourceHitPos];
foreach ($attributes[$attributePosition][$scope] as $tempKey => $tempFeed) {
if ($tempFeed['id'] == $feedId) {
$attributes[$attributePosition][$scope][$tempKey]['event_uuids'][] = $eventUuid;
if (empty($attributes[$attributePosition][$scope][$tempKey]['event_uuids']) || !in_array($eventUuid, $attributes[$attributePosition][$scope][$tempKey]['event_uuids'])) {
$attributes[$attributePosition][$scope][$tempKey]['event_uuids'][] = $eventUuid;
}
break;
}
}
@ -598,11 +605,6 @@ class Feed extends AppModel
)
);
// Enable gzipped responses if PHP has 'gzdecode' method
if (function_exists('gzdecode')) {
$result['header']['Accept-Encoding'] = 'gzip';
}
$commit = $this->checkMIPSCommit();
if ($commit) {
$result['header']['commit'] = $commit;
@ -1759,30 +1761,22 @@ class Feed extends AppModel
$request = $this->__createFeedRequest($feed['Feed']['headers']);
if ($followRedirect) {
$response = $this->getFollowRedirect($HttpSocket, $uri, $request);
} else {
$response = $HttpSocket->get($uri, array(), $request);
try {
if ($followRedirect) {
$response = $this->getFollowRedirect($HttpSocket, $uri, $request);
} else {
$response = $HttpSocket->get($uri, array(), $request);
}
} catch (Exception $e) {
throw new Exception("Fetching the '$uri' failed with exception: {$e->getMessage()}", 0, $e);
}
if ($response === false) {
throw new Exception("Could not reach '$uri'.");
} else if ($response->code != 200) { // intentionally !=
if ($response->code != 200) { // intentionally !=
throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
}
$data = $response->body;
$contentEncoding = $response->getHeader('Content-Encoding');
if ($contentEncoding === 'gzip') {
$data = gzdecode($data);
if ($data === false) {
throw new Exception("Fetching the '$uri' failed, response should be gzip encoded, but gzip decoding failed.");
}
} else if ($contentEncoding) {
throw new Exception("Fetching the '$uri' failed, because remote server returns unsupported content encoding '$contentEncoding'");
}
$contentType = $response->getHeader('Content-Type');
if ($contentType === 'application/zip') {
$zipFile = new File($this->tempFileName());

View File

@ -1,5 +1,7 @@
<?php
App::uses('AppModel', 'Model');
App::uses('TmpFileTool', 'Tools');
class GalaxyCluster extends AppModel
{
public $useTable = 'galaxy_clusters';
@ -867,8 +869,18 @@ class GalaxyCluster extends AppModel
return $tags;
}
/**
* @param string $name
* @param array $user
* @return array|mixed
*/
public function getCluster($name, $user)
{
$isGalaxyTag = strpos($name, 'misp-galaxy:') === 0;
if (!$isGalaxyTag) {
return null;
}
if (isset($this->__clusterCache[$name])) {
return $this->__clusterCache[$name];
}
@ -1104,8 +1116,8 @@ class GalaxyCluster extends AppModel
);
}
$tmpfile = tmpfile();
fwrite($tmpfile, $exportTool->header($exportToolParams));
$tmpfile = new TmpFileTool();
$tmpfile->write($exportTool->header($exportToolParams));
$loop = false;
if (empty($params['limit'])) {
$memory_in_mb = $this->convert_to_memory_limit_to_mb(ini_get('memory_limit'));
@ -1115,48 +1127,32 @@ class GalaxyCluster extends AppModel
$params['page'] = 1;
}
$this->__iteratedFetch($user, $params, $loop, $tmpfile, $exportTool, $exportToolParams, $elementCounter);
fwrite($tmpfile, $exportTool->footer($exportToolParams));
fseek($tmpfile, 0);
if (fstat($tmpfile)['size']) {
$final = fread($tmpfile, fstat($tmpfile)['size']);
} else {
$final = '';
}
fclose($tmpfile);
return $final;
$tmpfile->write($exportTool->footer($exportToolParams));
return $tmpfile;
}
private function __iteratedFetch($user, &$params, &$loop, &$tmpfile, $exportTool, $exportToolParams, &$elementCounter = 0)
private function __iteratedFetch($user, $params, $loop, TmpFileTool $tmpfile, $exportTool, $exportToolParams, &$elementCounter = 0)
{
$continue = true;
while ($continue) {
$temp = '';
$elementCounter = 0;
$separator = $exportTool->separator($exportToolParams);
do {
$results = $this->fetchGalaxyClusters($user, $params, $full=$params['full']);
if (empty($results)) {
$loop = false;
return true;
break; // nothing found, skip rest
}
if ($elementCounter !== 0 && !empty($results)) {
$temp .= $exportTool->separator($exportToolParams);
$resultCount = count($results);
$elementCounter += $resultCount;
foreach ($results as $cluster) {
$handlerResult = $exportTool->handler($cluster, $exportToolParams);
if ($handlerResult !== '') {
$tmpfile->writeWithSeparator($handlerResult, $separator);
}
}
if ($resultCount < $params['limit']) {
break;
}
$params['page'] += 1;
$i = 0;
foreach ($results as $cluster) {
$elementCounter++;
$handlerResult = $exportTool->handler($cluster, $exportToolParams);
$temp .= $handlerResult;
if ($handlerResult !== '') {
if ($i != count($results) -1) {
$temp .= $exportTool->separator($exportToolParams);
}
}
$i++;
}
if (!$loop) {
$continue = false;
}
fwrite($tmpfile, $temp);
}
} while ($loop);
return true;
}
@ -1407,9 +1403,9 @@ class GalaxyCluster extends AppModel
{
$clusterTagNames = [];
foreach ($events as $event) {
foreach ($event['EventTag'] as $k2 => $eventTag) {
foreach ($event['EventTag'] as $eventTag) {
if ($eventTag['Tag']['is_galaxy']) {
$clusterTagNames[] = strtolower($eventTag['Tag']['name']);
$clusterTagNames[strtolower($eventTag['Tag']['name'])] = true;
}
}
}
@ -1419,7 +1415,7 @@ class GalaxyCluster extends AppModel
}
$options = [
'conditions' => ['LOWER(GalaxyCluster.tag_name)' => $clusterTagNames],
'conditions' => ['LOWER(GalaxyCluster.tag_name)' => array_keys($clusterTagNames)],
];
if (!$fetchFullCluster) {
$options['contain'] = ['Galaxy'];
@ -1564,7 +1560,7 @@ class GalaxyCluster extends AppModel
{
$this->Server = ClassRegistry::init('Server');
$this->Log = ClassRegistry::init('Log');
$push = $this->Server->checkVersionCompatibility($server['Server']['id'], false, $HttpSocket);
$push = $this->Server->checkVersionCompatibility($server, false, $HttpSocket);
if (empty($push['canPush']) && empty($push['canPushGalaxyCluster'])) {
return __('The remote user does not have the permission to manipulate galaxies - the upload of the galaxy clusters has been blocked.');
}
@ -1837,12 +1833,12 @@ class GalaxyCluster extends AppModel
* - string <full> pull everything
* - string <update> pull updates of cluster present locally
* - string <pull_relevant_clusters> pull clusters based on tags present locally
* @return void The number of pulled clusters
* @return int The number of pulled clusters
*/
public function pullGalaxyClusters(array $user, array $server, $technique = 'full')
{
$this->Server = ClassRegistry::init('Server');
$compatible = $this->Server->checkVersionCompatibility($server['Server']['id'], $user)['supportEditOfGalaxyCluster'];
$compatible = $this->Server->checkVersionCompatibility($server, $user)['supportEditOfGalaxyCluster'];
if (!$compatible) {
return 0;
}
@ -1875,12 +1871,11 @@ class GalaxyCluster extends AppModel
$clusterIds = $this->Server->getElligibleClusterIdsFromServerForPull($server, $HttpSocket=null, $onlyUpdateLocalCluster=true, $elligibleClusters=$localClustersToUpdate);
} elseif ("pull_relevant_clusters" === $technique) {
// Fetch all local custom cluster tags then fetch their corresponding clusters on the remote end
$tagNames = $this->Tag->find('list', array(
$tagNames = $this->Tag->find('column', array(
'conditions' => array(
'Tag.is_custom_galaxy' => true
),
'fields' => array('Tag.name'),
'recursive' => -1,
));
$clusterUUIDs = array();
$re = '/^misp-galaxy:[^:="]+="(?<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})"$/m';

View File

@ -336,6 +336,11 @@ class Log extends AppModel
$elasticSearchClient->pushDocument($logIndex, "log", $data);
}
// Do not save request action logs to syslog, because they contain no information
if ($data['Log']['action'] === 'request') {
return true;
}
// write to syslogd as well if enabled
if ($this->syslog === null) {
if (Configure::read('Security.syslog')) {

View File

@ -81,6 +81,8 @@ class MispObject extends AppModel
)
);
private $__objectDuplicationCheckCache = [];
public function buildFilterConditions(&$params)
{
$conditions = [];
@ -302,26 +304,38 @@ class MispObject extends AppModel
{
$newObjectAttributes = array();
$existingObjectAttributes = array();
foreach ($object['Attribute'] as $attribute) {
if (isset($object['Object']['Attribute'])) {
$attributeArray = $object['Object']['Attribute'];
} else {
$attributeArray = $object['Attribute'];
}
foreach ($attributeArray as $attribute) {
if ($attribute['type'] === 'malware-sample') {
if (strpos($attribute['value'], '|') === false && !empty($attribute['data'])) {
$attribute['value'] = $attribute['value'] . '|' . md5(base64_decode($attribute['data']));
}
}
$newObjectAttributes[] = hash(
'sha256',
$attribute['object_relation'] . $attribute['category'] . $attribute['type'] . $attribute['value']
$attribute['object_relation'] . $attribute['category'] . $attribute['type'] . $this->data['Attribute']['value'] = $this->Attribute->modifyBeforeValidation($attribute['type'], $attribute['value'])
);
}
$newObjectAttributeCount = count($newObjectAttributes);
$existingObjects = $this->find('all', array(
'recursive' => -1,
'contain' => array(
'Attribute' => array(
'fields' => array('value', 'type', 'category', 'object_relation'),
'conditions' => array('Attribute.deleted' => 0)
)
),
'fields' => array('template_uuid'),
'conditions' => array('template_uuid' => $object['Object']['template_uuid'], 'Object.deleted' => 0, 'event_id' => $eventId)
));
if (!isset($this->__objectDuplicationCheckCache[$object['Object']['template_uuid']])) {
$this->__objectDuplicationCheckCache[$object['Object']['template_uuid']] = $this->find('all', array(
'recursive' => -1,
'contain' => array(
'Attribute' => array(
'fields' => array('value', 'type', 'category', 'object_relation'),
'conditions' => array('Attribute.deleted' => 0)
)
),
'fields' => array('template_uuid'),
'conditions' => array('template_uuid' => $object['Object']['template_uuid'], 'Object.deleted' => 0, 'event_id' => $eventId)
));
}
$oldObjects = array();
foreach ($existingObjects as $k => $existingObject) {
foreach ($this->__objectDuplicationCheckCache[$object['Object']['template_uuid']] as $k => $existingObject) {
$temp = array();
if (!empty($existingObject['Attribute']) && $newObjectAttributeCount == count($existingObject['Attribute'])) {
foreach ($existingObject['Attribute'] as $existingAttribute) {
@ -884,6 +898,23 @@ class MispObject extends AppModel
if (!isset($object['Object'])) {
$object = array('Object' => $object);
}
if (!empty($object['Object']['breakOnDuplicate'])) {
$duplicate = $this->checkForDuplicateObjects($object, $eventId);
if ($duplicate) {
$log->create();
$log->save(array(
'org' => $user['Organisation']['name'],
'model' => 'Object',
'model_id' => 0,
'email' => $user['email'],
'action' => 'add',
'user_id' => $user['id'],
'title' => 'Object dropped due to it being a duplicate and breakOnDuplicate being requested for Event ' . $eventId,
'change' => 'Duplicate object found.',
));
}
return true;
}
if (empty($log)) {
$log = ClassRegistry::init('Log');
}
@ -1443,7 +1474,7 @@ class MispObject extends AppModel
}
$this->__iteratedFetch($user, $params, $loop, $tmpfile, $exportTool, $exportToolParams, $elementCounter);
$tmpfile->write($exportTool->footer($exportToolParams));
return $tmpfile->finish();
return $tmpfile;
}
private function __iteratedFetch($user, &$params, &$loop, TmpFileTool $tmpfile, $exportTool, $exportToolParams, &$elementCounter = 0)

View File

@ -486,6 +486,7 @@ class Organisation extends AppModel
/**
* Hide organisation view from users if they haven't yet contributed data and Security.hide_organisation_index_from_users is enabled
*
* @see Organisation::canSee if you want to check multiple orgs
* @param array $user
* @param int $orgId
* @return bool
@ -521,6 +522,43 @@ class Organisation extends AppModel
return true;
}
/**
* Create conditions for fetching orgs based on user permission.
* @see Organisation::canSee if you want to check just one org
* @param array $user
* @return array|array[]
*/
public function createConditions(array $user)
{
if (!$user['Role']['perm_sharing_group'] && Configure::read('Security.hide_organisation_index_from_users')) {
$allowedOrgs = [$user['org_id']];
$eventConditions = $this->Event->createEventConditions($user);
$orgsWithEvent = $this->Event->find('column', [
'fields' => ['Event.orgc_id'],
'conditions' => $eventConditions,
'unique' => true,
]);
$allowedOrgs = array_merge($allowedOrgs, $orgsWithEvent);
$proposalConditions = $this->Event->ShadowAttribute->buildConditions($user);
// Do not check orgs that we already can see
$proposalConditions['AND'][]['NOT'] = ['ShadowAttribute.org_id' => $allowedOrgs];
$orgsWithProposal = $this->Event->ShadowAttribute->find('column', [
'fields' => ['ShadowAttribute.org_id'],
'conditions' => $proposalConditions,
'contain' => ['Event', 'Attribute'],
'unique' => true,
'order' => false,
]);
$allowedOrgs = array_merge($allowedOrgs, $orgsWithProposal);
return ['AND' => ['id' => $allowedOrgs]];
}
return [];
}
private function getCountryGalaxyCluster()
{
static $list;

View File

@ -1,8 +1,12 @@
<?php
App::uses('AppModel', 'Model');
/**
* @property User $User
*/
class Role extends AppModel
{
public $recursive = -1;
public $validate = array(
'valueNotEmpty' => array(
'rule' => array('valueNotEmpty'),
@ -232,6 +236,18 @@ class Role extends AppModel
return true;
}
public function afterSave($created, $options = array())
{
// After role change, update `date_modified` field for all user with this role to apply this change to already
// logged users.
if (!$created && !empty($this->data)) {
$roleId = $this->data['Role']['id'];
$this->User->updateAll(['date_modified' => time()], ['role_id' => $roleId]);
}
parent::afterSave($created, $options);
}
public function afterFind($results, $primary = false)
{
foreach ($results as $key => $val) {

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,8 @@ App::uses('ComplexTypeTool', 'Tools');
/**
* @property Event $Event
* @property Attribute $Attribute
* @property-read array $typeDefinitions
* @property-read array $categoryDefinitions
*/
class ShadowAttribute extends AppModel
{
@ -73,9 +75,6 @@ class ShadowAttribute extends AppModel
'attachment'
);
// definitions of categories
public $categoryDefinitions;
public $order = array("ShadowAttribute.event_id" => "DESC", "ShadowAttribute.type" => "ASC");
public $validate = array(
@ -149,11 +148,22 @@ class ShadowAttribute extends AppModel
)
);
public function __construct($id = false, $table = null, $ds = null)
public function __isset($name)
{
parent::__construct($id, $table, $ds);
$this->categoryDefinitions = $this->Attribute->categoryDefinitions;
$this->typeDefinitions = $this->Attribute->typeDefinitions;
if ($name === 'typeDefinitions' || $name === 'categoryDefinitions') {
return true;
}
return parent::__isset($name);
}
public function __get($name)
{
if ($name === 'categoryDefinitions') {
return $this->Attribute->categoryDefinitions;
} else if ($name === 'typeDefinitions') {
return $this->Attribute->typeDefinitions;
}
return parent::__get($name);
}
// The Associations below have been created with all possible keys, those that are not needed can be removed
@ -354,7 +364,7 @@ class ShadowAttribute extends AppModel
{
$category = $this->data['ShadowAttribute']['category'];
if (isset($this->categoryDefinitions[$category]['types'])) {
return in_array($fields['type'], $this->categoryDefinitions[$category]['types']);
return in_array($fields['type'], $this->categoryDefinitions[$category]['types'], true);
}
return false;
}
@ -481,13 +491,13 @@ class ShadowAttribute extends AppModel
*/
public function getEventContributors($eventId)
{
$orgs = $this->find('all', array(
'fields' => array('DISTINCT(ShadowAttribute.org_id)'),
$orgIds = $this->find('column', array(
'fields' => array('ShadowAttribute.org_id'),
'conditions' => array('event_id' => $eventId),
'recursive' => -1,
'unique' => true,
'order' => false
));
if (empty($orgs)) {
if (empty($orgIds)) {
return [];
}
@ -495,8 +505,8 @@ class ShadowAttribute extends AppModel
return $this->Organisation->find('list', array(
'recursive' => -1,
'fields' => array('id', 'name'),
'conditions' => array('Organisation.id' => Hash::extract($orgs, "{n}.ShadowAttribute.org_id")))
);
'conditions' => array('Organisation.id' => $orgIds)
));
}
/**

View File

@ -111,13 +111,6 @@ class SharingGroup extends AppModel
return false;
}
public function fetchAllAuthorisedForServer($server)
{
$sgs = $this->SharingGroupOrg->fetchAllAuthorised($server['RemoteOrg']['id']);
$sgs = array_merge($sgs, $this->SharingGroupServer->fetchAllSGsForServer($server['Server']['id']));
return $sgs;
}
/**
* Returns a list of all sharing groups that the user is allowed to see.
* Scope can be:
@ -145,11 +138,10 @@ class SharingGroup extends AppModel
}
if ($user['Role']['perm_site_admin']) {
$ids = array_values($this->find('list', array(
'recursive' => -1,
$ids = $this->find('column', array(
'fields' => array('id'),
'conditions' => $conditions
)));
));
} else {
$ids = array_unique(array_merge(
$this->SharingGroupServer->fetchAllAuthorised(),
@ -204,13 +196,20 @@ class SharingGroup extends AppModel
} elseif ($scope === 'distribution_graph') {
// Specific scope that fetch just necessary information for distribution graph
// @see DistributionGraphTool
$canSeeOrgs = $user['Role']['perm_sharing_group'] || !Configure::read('Security.hide_organisations_in_sharing_groups');
$sgs = $this->find('all', array(
'contain' => ['SharingGroupOrg' => ['org_id']],
'contain' => $canSeeOrgs ? ['SharingGroupOrg' => ['org_id']] : [],
'conditions' => $conditions,
'fields' => ['SharingGroup.id', 'SharingGroup.name', 'SharingGroup.org_id'],
'order' => 'SharingGroup.name ASC'
));
return $this->appendOrgsAndServers($sgs, ['id', 'name'], []);
if ($canSeeOrgs) {
return $this->appendOrgsAndServers($sgs, ['id', 'name'], []);
}
foreach ($sgs as &$sg) {
$sg['SharingGroupOrg'] = [];
}
return $sgs;
} elseif ($scope === 'name') {
$sgs = $this->find('list', array(
'recursive' => -1,
@ -241,8 +240,10 @@ class SharingGroup extends AppModel
{
$orgsToFetch = [];
$serverToFetch = [];
foreach($sharingGroups as $sg) {
$orgsToFetch[$sg['SharingGroup']['org_id']] = true;
foreach ($sharingGroups as $sg) {
if (isset($sg['SharingGroup']['org_id'])) {
$orgsToFetch[$sg['SharingGroup']['org_id']] = true;
}
if (isset($sg['SharingGroupOrg'])) {
foreach ($sg['SharingGroupOrg'] as $sgo) {
$orgsToFetch[$sgo['org_id']] = true;
@ -283,7 +284,7 @@ class SharingGroup extends AppModel
}
foreach ($sharingGroups as &$sg) {
if (isset($orgsById[$sg['SharingGroup']['org_id']])) {
if (isset($sg['SharingGroup']['org_id']) && isset($orgsById[$sg['SharingGroup']['org_id']])) {
$sg['Organisation'] = $orgsById[$sg['SharingGroup']['org_id']];
}
@ -699,7 +700,7 @@ class SharingGroup extends AppModel
if ($force) {
$sgids = $existingSG['SharingGroup']['id'];
$editedSG = $existingSG['SharingGroup'];
$attributes = array('name', 'releasability', 'description', 'created', 'modified', 'active');
$attributes = ['name', 'releasability', 'description', 'created', 'modified', 'active', 'roaming'];
foreach ($attributes as $a) {
if (isset($sg[$a])) {
$editedSG[$a] = $sg[$a];

View File

@ -78,12 +78,11 @@ class SharingGroupOrg extends AppModel
*/
public function fetchAllAuthorised($org_id)
{
$sgs = $this->find('list', array(
$sgs = $this->find('column', array(
'conditions' => array('org_id' => $org_id),
'recursive' => -1,
'fields' => array('sharing_group_id'),
'fields' => array('SharingGroupOrg.sharing_group_id'),
));
return array_values($sgs);
return $sgs;
}
// pass a sharing group ID and an organisation ID, returns true if it has a matching attached organisation object

View File

@ -87,12 +87,11 @@ class SharingGroupServer extends AppModel
// This basically lists all SGs that allow everyone on the instance to see events tagged with it
public function fetchAllAuthorised()
{
$sgs = $this->find('list', array(
$sgs = $this->find('column', array(
'conditions' => array('all_orgs' => 1, 'server_id' => 0),
'recursive' => -1,
'fields' => array('sharing_group_id'),
'fields' => array('SharingGroupServer.sharing_group_id'),
));
return array_values($sgs);
return $sgs;
}
// pass a sharing group ID, returns true if it has an attached server object with "all_orgs" ticked
@ -108,20 +107,4 @@ class SharingGroupServer extends AppModel
}
return false;
}
public function fetchAllSGsForServer($server_id)
{
$sgs = $this->find('all', array(
'recursive' => -1,
'conditions' => array('server_id' => $server_id)
));
if (empty($sgs)) {
return array();
}
$sgids = array();
foreach ($sgs as $temp) {
$sgids[] = $temp[$this->alias]['id'];
}
return $sgids;
}
}

View File

@ -14,7 +14,8 @@ class Sighting extends AppModel
// Possible values of `Plugin.Sightings_policy` setting
const SIGHTING_POLICY_EVENT_OWNER = 0,
SIGHTING_POLICY_SIGHTING_REPORTER = 1,
SIGHTING_POLICY_EVERYONE = 2;
SIGHTING_POLICY_EVERYONE = 2,
SIGHTING_POLICY_HOST_ORG = 3; // the same as SIGHTING_POLICY_EVENT_OWNER, but also sightings from host org are visible
private $orgCache = [];
@ -124,23 +125,54 @@ class Sighting extends AppModel
}
}
public function captureSighting($sighting, $attribute_id, $event_id, $user)
/**
* @param array $sightings
* @param int $attributeId
* @param int $eventId
* @param array $user
* @return bool
*/
public function captureSightings(array $sightings, $attributeId, $eventId, array $user)
{
$org_id = 0;
if (!empty($sighting['Organisation'])) {
$org_id = $this->Organisation->captureOrg($sighting['Organisation'], $user);
}
if (isset($sighting['id'])) {
// Since sightings are immutable (it is not possible to change it from web interface), we can check
// if sighting with given uuid already exists and skip them
$existingSighting = $this->existing($sightings);
// Fetch existing organisations in bulk
$existingOrganisations = $this->existingOrganisations($sightings);
$toSave = [];
foreach ($sightings as $sighting) {
if (isset($existingSighting[$sighting['uuid']])) {
continue; // already exists, skip
}
$orgId = 0;
if (isset($sighting['Organisation'])) {
if (isset($existingOrganisations[$sighting['Organisation']['uuid']])) {
$orgId = $existingOrganisations[$sighting['Organisation']['uuid']];
} else {
$orgId = $this->Organisation->captureOrg($sighting['Organisation'], $user);
}
}
unset($sighting['id']);
$sighting['org_id'] = $orgId;
$sighting['event_id'] = $eventId;
$sighting['attribute_id'] = $attributeId;
$toSave[] = $sighting;
}
$sighting['org_id'] = $org_id;
$sighting['event_id'] = $event_id;
$sighting['attribute_id'] = $attribute_id;
$this->create();
return $this->save($sighting);
return $this->saveMany($toSave);
}
public function getSighting($id, $user)
/**
* @param int $id
* @param array $user
* @param bool $withEvent
* @return array
*/
public function getSighting($id, array $user, $withEvent = true)
{
$sighting = $this->find('first', array(
'recursive' => -1,
@ -149,10 +181,7 @@ class Sighting extends AppModel
'fields' => array('Attribute.value', 'Attribute.id', 'Attribute.uuid', 'Attribute.type', 'Attribute.category', 'Attribute.to_ids')
),
'Event' => array(
'fields' => array('Event.id', 'Event.uuid', 'Event.orgc_id', 'Event.org_id', 'Event.info'),
'Orgc' => array(
'fields' => array('Orgc.name')
)
'fields' => $withEvent ? ['Event.id', 'Event.uuid', 'Event.orgc_id', 'Event.org_id', 'Event.info'] : ['Event.org_id'],
)
),
'conditions' => array('Sighting.id' => $id)
@ -161,11 +190,7 @@ class Sighting extends AppModel
return array();
}
if (!isset($event)) {
$event = array('Event' => $sighting['Event']);
}
$ownEvent = $user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id'];
$ownEvent = $user['Role']['perm_site_admin'] || $sighting['Event']['org_id'] == $user['org_id'];
if (!$ownEvent) {
$sightingPolicy = $this->sightingsPolicy();
// if sighting policy == 0 then return false if the sighting doesn't belong to the user
@ -180,27 +205,30 @@ class Sighting extends AppModel
return array();
}
}
}
$anonymise = Configure::read('Plugin.Sightings_anonymise');
if ($anonymise) {
if ($sighting['Sighting']['org_id'] != $user['org_id']) {
unset($sighting['Sighting']['org_id']);
unset($sighting['Organisation']);
else if ($sightingPolicy === self::SIGHTING_POLICY_HOST_ORG) {
if ($sighting['Sighting']['org_id'] != $user['org_id'] || $sighting['Sighting']['org_id'] != Configure::read('MISP.host_org_id')) {
return array();
}
}
}
// rearrange it to match the event format of fetchevent
if (isset($sighting['Organisation'])) {
$sighting['Sighting']['Organisation'] = $sighting['Organisation'];
unset($sighting['Organisation']);
// Put event organisation name from cache
if ($withEvent) {
$sighting['Event']['Orgc']['name'] = $this->getOrganisationById($sighting['Event']['orgc_id'])['name'];
}
$anonymise = Configure::read('Plugin.Sightings_anonymise');
if ($anonymise && $sighting['Sighting']['org_id'] != $user['org_id']) {
unset($sighting['Sighting']['org_id']);
}
// rearrange it to match the event format of fetchevent
$result = array(
'Sighting' => $sighting['Sighting']
);
$result['Sighting']['Event'] = $sighting['Event'];
$result['Sighting']['Attribute'] = $sighting['Attribute'];
if (!empty($sighting['Organisation'])) {
$result['Sighting']['Organisation'] = $sighting['Organisation'];
if ($withEvent) {
$result['Sighting']['Event'] = $sighting['Event'];
}
$result['Sighting']['Attribute'] = $sighting['Attribute'];
return $result;
}
@ -223,6 +251,8 @@ class Sighting extends AppModel
$sightingsPolicy = $this->sightingsPolicy();
if ($sightingsPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$conditions['Sighting.org_id'] = $user['org_id'];
} else if ($sightingsPolicy === self::SIGHTING_POLICY_HOST_ORG) {
$conditions['Sighting.org_id'] = [$user['org_id'], Configure::read('MISP.host_org_id')];
}
// TODO: Currently, we dont support `SIGHTING_POLICY_SIGHTING_REPORTER` for tags
$sparklineData = [];
@ -269,6 +299,8 @@ class Sighting extends AppModel
if (!$this->isReporter($attribute['Event']['id'], $user['org_id'])) {
continue; // skip attribute
}
} else if ($sightingsPolicy === self::SIGHTING_POLICY_HOST_ORG) {
$attributeConditions['Sighting.org_id'] = [$user['org_id'], Configure::read('MISP.host_org_id')];
}
}
$conditions['OR'][] = $attributeConditions;
@ -290,19 +322,21 @@ class Sighting extends AppModel
return ['data' => [], 'csv' => []];
}
$sightingPolicy = $this->sightingsPolicy();
$sightingsPolicy = $this->sightingsPolicy();
$conditions = [];
foreach ($events as $event) {
$eventCondition = ['Sighting.event_id' => $event['Event']['id']];
$ownEvent = $user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id'];
if (!$ownEvent) {
if ($sightingPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
if ($sightingsPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$eventCondition['Sighting.org_id'] = $user['org_id'];
} else if ($sightingPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
} else if ($sightingsPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
if (!$this->isReporter($event['Event']['id'], $user['org_id'])) {
continue;
}
} else if ($sightingsPolicy === self::SIGHTING_POLICY_HOST_ORG) {
$eventCondition['Sighting.org_id'] = [$user['org_id'], Configure::read('MISP.host_org_id')];
}
}
$conditions['OR'][] = $eventCondition;
@ -368,7 +402,7 @@ class Sighting extends AppModel
'contain' => [ucfirst($context) . 'Tag'],
'conditions' => $conditions,
'fields' => [ucfirst($context) . 'Tag.tag_id', 'date', 'sighting_count'],
'group' => [ucfirst($context) . 'Tag.id', 'date'],
'group' => [ucfirst($context) . 'Tag.tag_id', 'date'],
'order' => ['date'], // from oldest
]);
unset($this->virtualFields['date'], $this->virtualFields['sighting_count']);
@ -517,13 +551,15 @@ class Sighting extends AppModel
$ownEvent = $user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id'];
if (!$ownEvent) {
$sightingPolicy = $this->sightingsPolicy();
if ($sightingPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$sightingsPolicy = $this->sightingsPolicy();
if ($sightingsPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$conditions['Sighting.org_id'] = $user['org_id'];
} elseif ($sightingPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
} elseif ($sightingsPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
if (!$this->isReporter($event['Event']['id'], $user['org_id'])) {
return array();
}
} else if ($sightingsPolicy === self::SIGHTING_POLICY_HOST_ORG) {
$conditions['Sighting.org_id'] = [$user['org_id'], Configure::read('MISP.host_org_id')];
}
}
if ($extraConditions !== false) {
@ -659,6 +695,10 @@ class Sighting extends AppModel
return $result;
}
/**
* @return bool
* @deprecated
*/
public function addUuids()
{
$sightings = $this->find('all', array(
@ -743,10 +783,14 @@ class Sighting extends AppModel
return $sightings; // site admin can see all sightings, do not limit him
}
$sightingsPolicy = $this->sightingsPolicy();
if ($sightingsPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
if ($sightingsPolicy === self::SIGHTING_POLICY_EVENT_OWNER || $sightingsPolicy === self::SIGHTING_POLICY_HOST_ORG) {
$userOrgId = $user['org_id'];
$allowedOrgs = [$userOrgId];
if ($sightingsPolicy === self::SIGHTING_POLICY_HOST_ORG) {
$allowedOrgs[] = Configure::read('MISP.host_org_id');
}
foreach ($sightings as $k => $sighting) {
if ($eventOwnerOrgIdList[$sighting['Sighting']['event_id']] !== $userOrgId && $sighting['Sighting']['org_id'] !== $userOrgId) {
if ($eventOwnerOrgIdList[$sighting['Sighting']['event_id']] !== $userOrgId && !in_array($sighting['Sighting']['org_id'], $allowedOrgs)) {
unset($sightings[$k]);
}
}
@ -789,9 +833,9 @@ class Sighting extends AppModel
if (isset($filters['context']) && !in_array($filters['context'], $allowedContext, true)) {
throw new MethodNotAllowedException(__('Invalid context.'));
}
// ensure that an id is provided if context is set
if (!empty($filters['context']) && !isset($filters['id'])) {
throw new MethodNotAllowedException(__('An id must be provided if the context is set.'));
// ensure that an id or uuid is provided if context is set
if (!empty($filters['context']) && !(isset($filters['id']) || isset($filters['uuid'])) ) {
throw new MethodNotAllowedException(__('An ID or UUID must be provided if the context is set.'));
}
if (!isset($this->validFormats[$returnFormat][1])) {
@ -811,7 +855,9 @@ class Sighting extends AppModel
} else {
$timeCondition = '30d';
}
$conditions = $this->Attribute->setTimestampConditions($timeCondition, array(), $scope = 'Sighting.date_sighting');
$contain = [];
$conditions = $this->Attribute->setTimestampConditions($timeCondition, [], $scope = 'Sighting.date_sighting');
if (isset($filters['type'])) {
$conditions['Sighting.type'] = $filters['type'];
@ -824,7 +870,11 @@ class Sighting extends AppModel
}
foreach ($filters['org_id'] as $k => $org_id) {
if (Validation::uuid($org_id)) {
$org = $this->Organisation->find('first', array('conditions' => array('Organisation.uuid' => $org_id), 'recursive' => -1, 'fields' => array('Organisation.id')));
$org = $this->Organisation->find('first', array(
'conditions' => array('Organisation.uuid' => $org_id),
'recursive' => -1,
'fields' => array('Organisation.id'),
));
if (empty($org)) {
$filters['org_id'][$k] = -1;
} else {
@ -847,56 +897,37 @@ class Sighting extends AppModel
}
}
// fetch sightings matching the query
$sightings = $this->find('list', array(
'recursive' => -1,
'conditions' => $conditions,
'fields' => array('id'),
));
$sightings = array_values($sightings);
$filters['requested_attributes'] = array('id', 'attribute_id', 'event_id', 'org_id', 'date_sighting', 'uuid', 'source', 'type');
// apply ACL and sighting policies
$allowedSightings = array();
$additional_attribute_added = false;
$additional_event_added = false;
foreach ($sightings as $sid) {
$sight = $this->getSighting($sid, $user);
if (!empty($sight)) {
$sight['Sighting']['value'] = $sight['Sighting']['Attribute']['value'];
// by default, do not include event and attribute
if (!isset($filters['includeAttribute']) || !$filters['includeAttribute']) {
unset($sight["Sighting"]["Attribute"]);
} else if (!$additional_attribute_added) {
$filters['requested_attributes'] = array_merge($filters['requested_attributes'], array('attribute_uuid', 'attribute_type', 'attribute_category', 'attribute_to_ids', 'attribute_value'));
$additional_attribute_added = true;
}
if (!isset($filters['includeEvent']) || !$filters['includeEvent']) {
unset($sight["Sighting"]["Event"]);
} else if (!$additional_event_added) {
$filters['requested_attributes'] = array_merge($filters['requested_attributes'], array('event_uuid', 'event_orgc_id', 'event_org_id', 'event_info', 'event_Orgc_name'));
$additional_event_added = true;
}
if (!empty($sight)) {
array_push($allowedSightings, $sight);
}
if (!empty($filters['uuid'])) {
if ($filters['context'] === 'attribute') {
$conditions['Attribute.uuid'] = $filters['uuid'];
$contain[] = 'Attribute';
} elseif ($filters['context'] === 'event') {
$conditions['Event.uuid'] = $filters['uuid'];
$contain[] = 'Event';
}
}
$params = array(
'conditions' => array(), //result already filtered
);
// fetch sightings matching the query
$sightingIds = $this->find('column', [
'conditions' => $conditions,
'fields' => ['Sighting.id'],
'contain' => $contain,
]);
if (!isset($this->validFormats[$returnFormat])) {
// this is where the new code path for the export modules will go
throw new NotFoundException('Invalid export format.');
$includeAttribute = isset($filters['includeAttribute']) && $filters['includeAttribute'];
$includeEvent = isset($filters['includeEvent']) && $filters['includeEvent'];
$requestedAttributes = ['id', 'attribute_id', 'event_id', 'org_id', 'date_sighting', 'uuid', 'source', 'type'];
if ($includeAttribute) {
$requestedAttributes = array_merge($requestedAttributes, ['attribute_uuid', 'attribute_type', 'attribute_category', 'attribute_to_ids', 'attribute_value']);
}
if ($includeEvent) {
$requestedAttributes = array_merge($requestedAttributes, ['event_uuid', 'event_orgc_id', 'event_org_id', 'event_info', 'event_Orgc_name']);
}
$filters['requested_attributes'] = $requestedAttributes;
$exportToolParams = array(
'user' => $user,
'params' => $params,
'params' => ['conditions' => []], //result already filtered
'returnFormat' => $returnFormat,
'scope' => 'Sighting',
'filters' => $filters
@ -904,57 +935,110 @@ class Sighting extends AppModel
$tmpfile = new TmpFileTool();
$tmpfile->write($exportTool->header($exportToolParams));
$separator = $exportTool->separator($exportToolParams);
$temp = '';
$i = 0;
foreach ($allowedSightings as $sighting) {
$temp .= $exportTool->handler($sighting, $exportToolParams);
if ($temp !== '') {
if ($i != count($allowedSightings) -1) {
$temp .= $exportTool->separator($exportToolParams);
foreach ($sightingIds as $sightingId) {
// apply ACL and sighting policies
$sighting = $this->getSighting($sightingId, $user, $includeEvent);
if (!empty($sighting)) {
$sighting['Sighting']['value'] = $sighting['Sighting']['Attribute']['value'];
if (!$includeAttribute) {
unset($sighting['Sighting']['Attribute']);
}
$tmpfile->writeWithSeparator($exportTool->handler($sighting, $exportToolParams), $separator);
}
$i++;
}
$tmpfile->write($temp);
$tmpfile->write($exportTool->footer($exportToolParams));
return $tmpfile->finish();
return $tmpfile;
}
/**
* @param int|string $eventId Event ID or UUID
* @param array $sightings
* @param array $user
* @param null $passAlong
* @return int|string Number of saved sightings or error message as string
* @param int|null $passAlong Server ID
* @return int Number of saved sightings
* @throws Exception
*/
public function bulkSaveSightings($eventId, $sightings, $user, $passAlong = null)
public function bulkSaveSightings($eventId, array $sightings, array $user, $passAlong = null)
{
$event = $this->Event->fetchSimpleEvent($user, $eventId);
if (empty($event)) {
return 'Event not found or not accessible by this user.';
throw new NotFoundException('Event not found or not accessible by this user.');
}
$saved = 0;
// Since sightings are immutable (it is not possible to change it from web interface), we can check
// if sighting with given uuid already exists and skip them
$existingSighting = $this->existing($sightings);
// Fetch existing organisations in bulk
$existingOrganisations = $this->existingOrganisations($sightings);
// Fetch attributes IDs and event IDs
$attributesToTransform = $this->Attribute->fetchAttributesSimple($user, [
'conditions' => ['Attribute.uuid' => array_unique(array_column($sightings, 'attribute_uuid'))],
'fields' => ['Attribute.id', 'Attribute.uuid', 'Attribute.event_id'],
]);
$attributes = [];
foreach ($attributesToTransform as $attribute) {
$attributes[$attribute['Attribute']['uuid']] = [$attribute['Attribute']['id'], $attribute['Attribute']['event_id']];
}
$toSave = [];
foreach ($sightings as $s) {
if (isset($existingSighting[$s['uuid']])) {
continue; // sighting already exists
}
if (!isset($attributes[$s['attribute_uuid']])) {
continue; // attribute doesn't exists or user don't have permission to access it
}
list($attributeId, $eventId) = $attributes[$s['attribute_uuid']];
if ($s['type'] === '2') {
// remove existing expiration by the same org if it exists
$this->deleteAll(array(
'Sighting.org_id' => $user['org_id'],
'Sighting.type' => 2,
'Sighting.attribute_id' => $attributeId,
));
}
$saveOnBehalfOf = false;
if ($user['Role']['perm_sync']) {
if (isset($s['org_id'])) {
if ($s['org_id'] != 0 && !empty($s['Organisation'])) {
$saveOnBehalfOf = $this->Event->Orgc->captureOrg($s['Organisation'], $user);
if (isset($existingOrganisations[$s['Organisation']['uuid']])) {
$saveOnBehalfOf = $existingOrganisations[$s['Organisation']['uuid']];
} else {
$saveOnBehalfOf = $this->Organisation->captureOrg($s['Organisation'], $user);
}
} else {
$saveOnBehalfOf = 0;
}
}
}
$result = $this->saveSightings($s['attribute_uuid'], false, $s['date_sighting'], $user, $s['type'], $s['source'], $s['uuid'], false, $saveOnBehalfOf);
if (is_numeric($result)) {
$saved += $result;
}
$toSave[] = [
'attribute_id' => $attributeId,
'event_id' => $eventId,
'org_id' => $saveOnBehalfOf === false ? $user['org_id'] : $saveOnBehalfOf,
'date_sighting' => $s['date_sighting'],
'type' => $s['type'],
'source' => $s['source'],
'uuid' => $s['uuid'],
];
}
if ($saved > 0) {
if (empty($toSave)) {
return 0;
}
if ($this->saveMany($toSave)) {
$this->Event->publishRouter($event['Event']['id'], $passAlong, $user, 'sightings');
return count($toSave);
} else {
return 0;
}
return $saved;
}
public function pullSightings($user, $server)
@ -1005,6 +1089,36 @@ class Sighting extends AppModel
return strtotime("-$rangeInDays days");
}
/**
* @param array $sightings
* @return array Existing sightings UUID in key
*/
private function existing(array $sightings)
{
$existingSighting = $this->find('column', [
'fields' => ['Sighting.uuid'],
'conditions' => ['uuid' => array_column($sightings, 'uuid')],
]);
// Move UUID to array key
return array_flip($existingSighting);
}
/**
* @param array $sightings
* @return array Organisation UUID => Organisation ID
*/
private function existingOrganisations(array $sightings)
{
$organisations = array_column($sightings, 'Organisation');
if (empty($organisations)) {
return [];
}
return $this->Organisation->find('list', [
'fields' => ['Organisation.uuid', 'Organisation.id'],
'conditions' => ['Organisation.uuid' => array_unique(array_column($organisations, 'uuid'))],
]);
}
/**
* Sighting reporters setting
* If the event has any sightings for the user's org, then the user is a sighting reporter for the event too.

View File

@ -466,13 +466,12 @@ class Tag extends AppModel
public function getTagsByName($tag_names, $containTagConnectors = true)
{
$contain = array('EventTag', 'AttributeTag');
$tag_params = array(
'recursive' => -1,
'conditions' => array('name' => $tag_names)
'recursive' => -1,
'conditions' => array('name' => $tag_names)
);
if ($containTagConnectors) {
$tag_params['contain'] = $contain;
$tag_params['contain'] = array('EventTag', 'AttributeTag');
}
$tags_temp = $this->find('all', $tag_params);
$tags = array();
@ -482,16 +481,19 @@ class Tag extends AppModel
return $tags;
}
/**
* @param string $namespace
* @param bool $containTagConnectors
* @return array Uppercase tag name in key
*/
public function getTagsForNamespace($namespace, $containTagConnectors = true)
{
$contain = array('EventTag', 'AttributeTag');
$tag_params = array(
'recursive' => -1,
'conditions' => array('UPPER(name) LIKE' => strtoupper($namespace) . '%'),
'recursive' => -1,
'conditions' => array('LOWER(name) LIKE' => strtolower($namespace) . '%'),
);
if ($containTagConnectors) {
$tag_params['contain'] = $contain;
$tag_params['contain'] = array('EventTag', 'AttributeTag');
}
$tags_temp = $this->find('all', $tag_params);
$tags = array();
@ -514,13 +516,11 @@ class Tag extends AppModel
}
$id = $tag['Tag']['id'];
}
$event_ids = $this->EventTag->find('list', array(
'recursive' => -1,
$event_ids = $this->EventTag->find('column', array(
'conditions' => array('EventTag.tag_id' => $id),
'fields' => array('EventTag.event_id', 'EventTag.event_id'),
'order' => array('EventTag.event_id')
'fields' => array('EventTag.event_id'),
));
$params = array('conditions' => array('Event.id' => array_values($event_ids)));
$params = array('conditions' => array('Event.id' => $event_ids));
$events = $this->EventTag->Event->fetchSimpleEvents($user, $params, true);
foreach ($events as $k => $event) {
$event['Event']['Orgc'] = $event['Orgc'];

View File

@ -1,6 +1,9 @@
<?php
App::uses('AppModel', 'Model');
/**
* @property TaxonomyPredicate $TaxonomyPredicate
*/
class Taxonomy extends AppModel
{
public $useTable = 'taxonomies';
@ -49,9 +52,9 @@ class Taxonomy extends AppModel
if (!$file->exists()) {
continue;
}
$vocab = json_decode($file->read(), true);
$file->close();
if ($vocab === null) {
try {
$vocab = $this->jsonDecode($file->read());
} catch (Exception $e) {
$updated['fails'][] = array('namespace' => $dir, 'fail' => "File machinetag.json is not valid JSON.");
continue;
}
@ -91,17 +94,55 @@ class Taxonomy extends AppModel
return $updated;
}
private function __updateVocab($vocab, $current, $skipUpdateFields = array())
/**
* @param array $vocab
* @return int Taxonomy ID
* @throws Exception
*/
public function import(array $vocab)
{
foreach (['namespace', 'description', 'predicates'] as $requiredField) {
if (!isset($vocab[$requiredField])) {
throw new Exception("Required field '$requiredField' not provided.");
}
}
if (!is_array($vocab['predicates'])) {
throw new Exception("Field 'predicates' must be array.");
}
if (isset($vocab['values']) && !is_array($vocab['values'])) {
throw new Exception("Field 'values' must be array.");
}
if (!isset($vocab['version'])) {
$vocab['version'] = 1;
}
$current = $this->find('first', array(
'conditions' => array('namespace' => $vocab['namespace']),
'recursive' => -1,
'fields' => array('version', 'enabled', 'namespace')
));
$result = $this->__updateVocab($vocab, $current);
if (is_array($result)) {
throw new Exception('Could not save taxonomy because of validation errors: ' . json_encode($result));
}
return (int)$result;
}
private function __updateVocab(array $vocab, $current, array $skipUpdateFields = [])
{
$enabled = 0;
$taxonomy = array();
if (!empty($current)) {
if ($current['Taxonomy']['enabled']) {
$enabled = 1;
}
$this->deleteAll(array('Taxonomy.namespace' => $current['Taxonomy']['namespace']));
$this->deleteAll(['Taxonomy.namespace' => $current['Taxonomy']['namespace']]);
}
$taxonomy['Taxonomy'] = array('namespace' => $vocab['namespace'], 'description' => $vocab['description'], 'version' => $vocab['version'], 'exclusive' => !empty($vocab['exclusive']), 'enabled' => $enabled);
$taxonomy = ['Taxonomy' => [
'namespace' => $vocab['namespace'],
'description' => $vocab['description'],
'version' => $vocab['version'],
'exclusive' => !empty($vocab['exclusive']),
'enabled' => $enabled,
]];
$predicateLookup = array();
foreach ($vocab['predicates'] as $k => $predicate) {
$taxonomy['Taxonomy']['TaxonomyPredicate'][$k] = $predicate;
@ -109,14 +150,15 @@ class Taxonomy extends AppModel
}
if (!empty($vocab['values'])) {
foreach ($vocab['values'] as $value) {
if (empty($taxonomy['Taxonomy']['TaxonomyPredicate'][$predicateLookup[$value['predicate']]]['TaxonomyEntry'])) {
$taxonomy['Taxonomy']['TaxonomyPredicate'][$predicateLookup[$value['predicate']]]['TaxonomyEntry'] = $value['entry'];
$predicatePosition = $predicateLookup[$value['predicate']];
if (empty($taxonomy['Taxonomy']['TaxonomyPredicate'][$predicatePosition]['TaxonomyEntry'])) {
$taxonomy['Taxonomy']['TaxonomyPredicate'][$predicatePosition]['TaxonomyEntry'] = $value['entry'];
} else {
$taxonomy['Taxonomy']['TaxonomyPredicate'][$predicateLookup[$value['predicate']]]['TaxonomyEntry'] = array_merge($taxonomy['Taxonomy']['TaxonomyPredicate'][$predicateLookup[$value['predicate']]]['TaxonomyEntry'], $value['entry']);
$taxonomy['Taxonomy']['TaxonomyPredicate'][$predicatePosition]['TaxonomyEntry'] = array_merge($taxonomy['Taxonomy']['TaxonomyPredicate'][$predicatePosition]['TaxonomyEntry'], $value['entry']);
}
}
}
$result = $this->saveAssociated($taxonomy, array('deep' => true));
$result = $this->saveAssociated($taxonomy, ['deep' => true]);
if ($result) {
$this->__updateTags($this->id, $skipUpdateFields);
return $this->id;
@ -175,7 +217,7 @@ class Taxonomy extends AppModel
if ($filter) {
$namespaceLength = strlen($taxonomy['Taxonomy']['namespace']);
foreach ($entries as $k => $entry) {
if (strpos(substr(strtoupper($entry['tag']), $namespaceLength), strtoupper($filter)) === false) {
if (strpos(substr(mb_strtolower($entry['tag']), $namespaceLength), mb_strtolower($filter)) === false) {
unset($entries[$k]);
}
}
@ -189,8 +231,7 @@ class Taxonomy extends AppModel
public function getAllTaxonomyTags($inverse = false, $user = false, $full = false)
{
$this->Tag = ClassRegistry::init('Tag');
$taxonomyIdList = $this->find('list', array('fields' => array('id')));
$taxonomyIdList = array_keys($taxonomyIdList);
$taxonomyIdList = $this->find('column', array('fields' => array('Taxonomy.id')));
$allTaxonomyTags = array();
foreach ($taxonomyIdList as $taxonomy) {
$allTaxonomyTags = array_merge($allTaxonomyTags, array_keys($this->getTaxonomyTags($taxonomy, true)));
@ -284,8 +325,8 @@ class Taxonomy extends AppModel
if (empty($taxonomy)) {
return false;
}
$tag_names = Hash::extract($taxonomy, 'entries.{n}.tag');
$tags = $this->Tag->getTagsByName($tag_names, false);
$tagNames = array_column($taxonomy['entries'], 'tag');
$tags = $this->Tag->getTagsByName($tagNames, false);
if (isset($taxonomy['entries'])) {
foreach ($taxonomy['entries'] as $key => $temp) {
if (isset($tags[strtoupper($temp['tag'])])) {
@ -307,13 +348,12 @@ class Taxonomy extends AppModel
private function __updateTags($id, $skipUpdateFields = array())
{
$this->Tag = ClassRegistry::init('Tag');
App::uses('ColourPaletteTool', 'Tools');
$paletteTool = new ColourPaletteTool();
$taxonomy = $this->__getTaxonomy($id, array('full' => true));
$colours = $paletteTool->generatePaletteFromString($taxonomy['Taxonomy']['namespace'], count($taxonomy['entries']));
$this->Tag = ClassRegistry::init('Tag');
$tags = $this->Tag->getTagsForNamespace($taxonomy['Taxonomy']['namespace']);
$tags = $this->Tag->getTagsForNamespace($taxonomy['Taxonomy']['namespace'], false);
foreach ($taxonomy['entries'] as $k => $entry) {
if (isset($tags[strtoupper($entry['tag'])])) {
$temp = $tags[strtoupper($entry['tag'])];
@ -501,12 +541,9 @@ class Taxonomy extends AppModel
return $taxonomies;
}
public function getTaxonomyForTag($tagName, $metaOnly = false, $fullTaxonomy = False)
public function getTaxonomyForTag($tagName, $metaOnly = false, $fullTaxonomy = false)
{
if (preg_match('/^[^:="]+:[^:="]+="[^:="]+"$/i', $tagName)) {
$temp = explode(':', $tagName);
$pieces = array_merge(array($temp[0]), explode('=', $temp[1]));
$pieces[2] = trim($pieces[2], '"');
if (preg_match('/^([^:="]+):([^:="]+)="([^:="]+)"$/i', $tagName, $matches)) {
$contain = array(
'TaxonomyPredicate' => array(
'TaxonomyEntry' => array()
@ -514,15 +551,15 @@ class Taxonomy extends AppModel
);
if (!$fullTaxonomy) {
$contain['TaxonomyPredicate']['conditions'] = array(
'LOWER(TaxonomyPredicate.value)' => strtolower($pieces[1])
'LOWER(TaxonomyPredicate.value)' => mb_strtolower($matches[2]),
);
$contain['TaxonomyPredicate']['TaxonomyEntry']['conditions'] = array(
'LOWER(TaxonomyEntry.value)' => strtolower($pieces[2])
'LOWER(TaxonomyEntry.value)' => mb_strtolower($matches[3]),
);
}
$taxonomy = $this->find('first', array(
'recursive' => -1,
'conditions' => array('LOWER(Taxonomy.namespace)' => strtolower($pieces[0])),
'conditions' => array('LOWER(Taxonomy.namespace)' => mb_strtolower($matches[1])),
'contain' => $contain
));
if ($metaOnly && !empty($taxonomy)) {
@ -534,12 +571,12 @@ class Taxonomy extends AppModel
$contain = array('TaxonomyPredicate' => array());
if (!$fullTaxonomy) {
$contain['TaxonomyPredicate']['conditions'] = array(
'LOWER(TaxonomyPredicate.value)' => strtolower($pieces[1])
'LOWER(TaxonomyPredicate.value)' => mb_strtolower($pieces[1])
);
}
$taxonomy = $this->find('first', array(
'recursive' => -1,
'conditions' => array('LOWER(Taxonomy.namespace)' => strtolower($pieces[0])),
'conditions' => array('LOWER(Taxonomy.namespace)' => mb_strtolower($pieces[0])),
'contain' => $contain
));
if ($metaOnly && !empty($taxonomy)) {

View File

@ -1,6 +1,9 @@
<?php
App::uses('AppModel', 'Model');
/**
* @property TaxonomyEntry $TaxonomyEntry
*/
class TaxonomyPredicate extends AppModel
{
public $useTable = 'taxonomy_predicates';

View File

@ -641,39 +641,50 @@ class User extends AppModel
// get the current user and rearrange it to be in the same format as in the auth component
public function getAuthUser($id)
{
$user = $this->getUserById($id);
if (empty($user)) {
return $user;
if (empty($id)) {
throw new InvalidArgumentException('Invalid user ID.');
}
return $this->rearrangeToAuthForm($user);
$conditions = ['User.id' => $id];
return $this->getAuthUserByConditions($conditions);
}
// get the current user and rearrange it to be in the same format as in the auth component
public function getAuthUserByAuthkey($id)
public function getAuthUserByAuthkey($authkey)
{
$conditions = array('User.authkey' => $id);
$user = $this->find('first', array('conditions' => $conditions, 'recursive' => -1,'contain' => array('Organisation', 'Role', 'Server')));
if (empty($user)) {
return $user;
if (empty($authkey)) {
throw new InvalidArgumentException('Invalid user auth key.');
}
return $this->rearrangeToAuthForm($user);
$conditions = array('User.authkey' => $authkey);
return $this->getAuthUserByConditions($conditions);
}
public function getAuthUserByExternalAuth($auth_key)
{
if (empty($auth_key)) {
throw new InvalidArgumentException('Invalid user external auth key.');
}
$conditions = array(
'User.external_auth_key' => $auth_key,
'User.external_auth_required' => true
);
$user = $this->find('first', array(
return $this->getAuthUserByConditions($conditions);
}
/**
* @param array $conditions
* @return array|null
*/
private function getAuthUserByConditions(array $conditions)
{
$user = $this->find('first', [
'conditions' => $conditions,
'recursive' => -1,
'contain' => array(
'contain' => [
'Organisation',
'Role',
'Server'
)
));
'Server',
],
]);
if (empty($user)) {
return $user;
}
@ -696,9 +707,6 @@ class User extends AppModel
$user['User']['Role'] = $user['Role'];
$user['User']['Organisation'] = $user['Organisation'];
$user['User']['Server'] = $user['Server'];
if (isset($user['UserSetting'])) {
$user['User']['UserSetting'] = $user['UserSetting'];
}
return $user['User'];
}
@ -880,38 +888,6 @@ class User extends AppModel
return $fields;
}
public function getMembersCount($org_id = false)
{
// for Organizations List
$conditions = array();
$findType = 'all';
if ($org_id !== false) {
$findType = 'first';
$conditions = array('User.org_id' => $org_id);
}
$fields = array('org_id', 'COUNT(User.id) AS num_members');
$params = array(
'fields' => $fields,
'recursive' => -1,
'group' => array('org_id'),
'order' => array('org_id'),
'conditions' => $conditions
);
$orgs = $this->find($findType, $params);
if (empty($orgs)) {
return 0;
}
if ($org_id !== false) {
return $orgs[0]['num_members'];
} else {
$usersPerOrg = [];
foreach ($orgs as $key => $value) {
$usersPerOrg[$value['User']['org_id']] = $value[0]['num_members'];
}
return $usersPerOrg;
}
}
public function findAdminsResponsibleForUser($user)
{
$admin = $this->find('first', array(
@ -967,7 +943,7 @@ class User extends AppModel
if ($result) {
$this->id = $user['User']['id'];
$this->saveField('password', $password);
$this->saveField('change_pw', '1');
$this->updateField($user['User'], 'change_pw', 1);
if ($simpleReturn) {
return true;
} else {
@ -983,10 +959,9 @@ class User extends AppModel
public function getOrgAdminsForOrg($org_id, $excludeUserId = false)
{
$adminRoles = $this->Role->find('list', array(
'recursive' => -1,
$adminRoles = $this->Role->find('column', array(
'conditions' => array('perm_admin' => 1),
'fields' => array('Role.id', 'Role.id')
'fields' => array('Role.id')
));
$conditions = array(
'User.org_id' => $org_id,
@ -1142,7 +1117,7 @@ class User extends AppModel
if (empty(Configure::read('Security.advanced_authkeys'))) {
$oldKey = $this->data['User']['authkey'];
$newkey = $this->generateAuthKey();
$this->saveField('authkey', $newkey);
$this->updateField($updatedUser['User'], 'authkey', $newkey);
$this->extralog(
$user,
'reset_auth_key',
@ -1212,6 +1187,27 @@ class User extends AppModel
$syslog->write('notice', "$description -- $action" . (empty($fieldResult) ? '' : ' -- ' . $result['Log']['change']));
}
/**
* @return array|null
* @throws Exception
*/
public function getGpgPublicKey()
{
$email = Configure::read('GnuPG.email');
if (!$email) {
throw new Exception("Configuration option 'GnuPG.email' is not set, public key cannot be exported.");
}
$cryptGpg = $this->initializeGpg();
$fingerprint = $cryptGpg->getFingerprint($email);
if (!$fingerprint) {
return null;
}
$publicKey = $cryptGpg->exportPublicKey($fingerprint);
return array($fingerprint, $publicKey);
}
public function getOrgActivity($orgId, $params=array())
{
$conditions = array();
@ -1265,24 +1261,6 @@ class User extends AppModel
return $data;
}
/*
* Set the monitoring flag in Configure for the current user
* Reads the state from redis
*/
public function setMonitoring($user)
{
if (
!empty(Configure::read('Security.user_monitoring_enabled'))
) {
$redis = $this->setupRedis();
if (!empty($redis->sismember('misp:monitored_users', $user['id']))) {
Configure::write('Security.monitored', 1);
return true;
}
}
Configure::write('Security.monitored', 0);
}
public function registerUser($added_by, $registration, $org_id, $role_id) {
$user = array(
'email' => $registration['data']['email'],
@ -1382,7 +1360,7 @@ class User extends AppModel
$name => $value,
], true, ['id', $name, 'date_modified']);
if (!$success) {
throw new RuntimeException("Could not save field `$name` with value `$value` for user `{$user['id']}`.");
throw new RuntimeException("Could not save setting $name for user {$user['id']}.");
}
}

View File

@ -201,34 +201,33 @@ class UserSetting extends AppModel
public function getDefaulRestSearchParameters($user)
{
$setting = $this->find('first', array(
'recursive' => -1,
'conditions' => array(
'UserSetting.user_id' => $user['id'],
'UserSetting.setting' => 'default_restsearch_parameters'
)
));
$parameters = array();
if (!empty($setting)) {
$parameters = $setting['UserSetting']['value'];
}
return $parameters;
return $this->getValueForUser($user['id'], 'default_restsearch_parameters') ?: [];
}
public function getTagNumericalValueOverride($userId)
{
$setting = $this->find('first', array(
'recursive' => -1,
'conditions' => array(
'UserSetting.user_id' => $userId,
'UserSetting.setting' => 'tag_numerical_value_override'
)
));
$parameters = array();
if (!empty($setting)) {
$parameters = $setting['UserSetting']['value'];
}
return $parameters;
return $this->getValueForUser($userId, 'tag_numerical_value_override') ?: [];
}
/**
* @param int $userId
* @param string $setting
* @return mixed|null
*/
public function getValueForUser($userId, $setting)
{
$output = $this->find('first', array(
'recursive' => -1,
'fields' => ['value'],
'conditions' => array(
'UserSetting.user_id' => $userId,
'UserSetting.setting' => $setting,
)
));
if ($output) {
return $output['UserSetting']['value'];
}
return null;
}
/*

View File

@ -304,8 +304,7 @@ class Warninglist extends AppModel
if ($id && $warninglist['Warninglist']['id'] != $id) {
continue;
}
$entries = $this->WarninglistEntry->find('list', array(
'recursive' => -1,
$entries = $this->WarninglistEntry->find('column', array(
'conditions' => array('warninglist_id' => $warninglist['Warninglist']['id']),
'fields' => array('value')
));
@ -390,11 +389,10 @@ class Warninglist extends AppModel
if ($redis !== false && $redis->exists('misp:warninglist_entries_cache:' . $id)) {
return $redis->sMembers('misp:warninglist_entries_cache:' . $id);
} else {
$entries = array_values($this->WarninglistEntry->find('list', array(
'recursive' => -1,
$entries = $this->WarninglistEntry->find('column', array(
'conditions' => array('warninglist_id' => $id),
'fields' => array('value')
)));
'fields' => array('WarninglistEntry.value')
));
$this->cacheWarninglistEntries($entries, $id);
return $entries;
}
@ -482,7 +480,7 @@ class Warninglist extends AppModel
if ($object['to_ids'] || $this->showForAll) {
foreach ($warninglists as $list) {
if (in_array('ALL', $list['types']) || in_array($object['type'], $list['types'])) {
if (in_array('ALL', $list['types'], true) || in_array($object['type'], $list['types'], true)) {
$result = $this->__checkValue($this->getFilteredEntries($list), $object['value'], $object['type'], $list['Warninglist']['type']);
if ($result !== false) {
$object['warnings'][] = array(
@ -677,14 +675,13 @@ class Warninglist extends AppModel
*/
public function fetchTLDLists()
{
$tldLists = $this->find('list', array(
$tldLists = $this->find('column', array(
'conditions' => array('Warninglist.name' => $this->__tlds),
'recursive' => -1,
'fields' => array('Warninglist.id', 'Warninglist.id')
'fields' => array('Warninglist.id')
));
$tlds = array();
if (!empty($tldLists)) {
$tlds = $this->WarninglistEntry->find('list', array(
$tlds = $this->WarninglistEntry->find('column', array(
'conditions' => array('WarninglistEntry.warninglist_id' => $tldLists),
'fields' => array('WarninglistEntry.value')
));
@ -692,7 +689,7 @@ class Warninglist extends AppModel
$tlds[$key] = strtolower($value);
}
}
if (!in_array('onion', $tlds)) {
if (!in_array('onion', $tlds, true)) {
$tlds[] = 'onion';
}
return $tlds;
@ -710,7 +707,7 @@ class Warninglist extends AppModel
}
foreach ($warninglists as $warninglist) {
if (in_array('ALL', $warninglist['types']) || in_array($attribute['type'], $warninglist['types'])) {
if (in_array('ALL', $warninglist['types'], true) || in_array($attribute['type'], $warninglist['types'], true)) {
$result = $this->__checkValue($this->getFilteredEntries($warninglist), $attribute['value'], $attribute['type'], $warninglist['Warninglist']['type']);
if ($result !== false) {
return false;

View File

@ -145,6 +145,7 @@ class ApacheShibbAuthenticate extends BaseAuthenticate
* @param string $org
* @param array $user
* @return int
* @throws Exception
*/
private function checkOrganization($org, $user)
{
@ -192,13 +193,13 @@ class ApacheShibbAuthenticate extends BaseAuthenticate
foreach ($groupList as $group) {
// TODO: Can be optimized inverting the search group and using only array_key_exists
if (array_key_exists($group, $groupRoleMatching)) { //In case there is an group not defined in the config.php file
CakeLog::write('info', "User group ${group} found.");
CakeLog::write('info', "User group $group found.");
$roleVal = $groupRoleMatching[$group];
if ($roleVal <= $roleId || $roleId == -1) {
$roleId = $roleVal;
$roleChanged = true;
}
CakeLog::write('info', "User role ${roleId} assigned.");
CakeLog::write('info', "User role $roleId assigned.");
}
}
return array($roleChanged, $roleId);
@ -217,7 +218,8 @@ class ApacheShibbAuthenticate extends BaseAuthenticate
private function updateUserRole($roleChanged, array $user, $roleId, User $userModel)
{
if ($roleChanged && $user['role_id'] != $roleId) {
CakeLog::write('warning', "User role changed from ${user['role_id']} to $roleId.");
$message = "User role changed from ${user['role_id']} to $roleId for user ${user['email']} (${user['id']}).";
CakeLog::write('warning', $message);
$userModel->updateField($user, 'role_id', $roleId);
}
return $user;
@ -233,7 +235,8 @@ class ApacheShibbAuthenticate extends BaseAuthenticate
private function updateUserOrg($orgId, array $user, User $userModel)
{
if ($user['org_id'] != $orgId) {
CakeLog::write('warning', "User organisation $orgId changed.");
$message = "User organisation changed from ${user['org_id']} to $orgId for user ${user['email']} (${user['id']}).";
CakeLog::write('warning', $message);
$user['org_id'] = $orgId; // Different role either increase or decrease permissions
$userModel->updateField($user, 'org_id', $orgId);
}

View File

@ -4,13 +4,6 @@
<div class="pagination">
<ul>
<?php
$this->Paginator->options(array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()',
));
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));

View File

@ -4,13 +4,6 @@
<div class="pagination">
<ul>
<?php
$this->Paginator->options(array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()',
));
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));

View File

@ -29,13 +29,6 @@
<div class="pagination">
<ul>
<?php
$this->Paginator->options(array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()',
));
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
@ -115,6 +108,13 @@
}
?>
</table>
<?php
// Generate form for adding sighting just once, generation for every attribute is surprisingly too slow
echo $this->Form->create('Sighting', ['id' => 'SightingForm', 'url' => $baseurl . '/sightings/add/', 'style' => 'display:none;']);
echo $this->Form->input('id', ['label' => false, 'type' => 'number']);
echo $this->Form->input('type', ['label' => false]);
echo $this->Form->end();
?>
<p>
<?php
echo $this->Paginator->counter(array(

View File

@ -1,5 +1,4 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Add auth key'),
@ -13,11 +12,12 @@ echo $this->element('genericElements/Form/genericForm', [
],
[
'field' => 'comment',
'label' => __('Comment'),
'class' => 'span6'
],
[
'field' => 'expiration',
'label' => 'Expiration',
'label' => __('Expiration (%s)', $validity ? __('keep empty for maximal validity of %s days', $validity) : __('keep empty for indefinite')),
'class' => 'datepicker span6',
'placeholder' => "YYYY-MM-DD",
'type' => 'text'

View File

@ -28,8 +28,7 @@
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
'searchKey' => 'quickFilter',
]
]
],
@ -43,9 +42,12 @@
'name' => __('User'),
'sort' => 'User.email',
'data_path' => 'User.email',
'element' => empty($user_id) ? 'links' : 'generic_field',
'url' => $baseurl . '/users/view',
'url_params_data_paths' => ['User.id'],
],
[
'name' => __('Auth key'),
'name' => __('Auth Key'),
'sort' => 'AuthKey.authkey_start',
'element' => 'authkey',
'data_path' => 'AuthKey',
@ -56,6 +58,12 @@
'data_path' => 'AuthKey.expiration',
'element' => 'expiration'
],
[
'name' => ('Last used'),
'data_path' => 'AuthKey.last_used',
'element' => 'datetime',
'requirements' => $keyUsageEnabled,
],
[
'name' => __('Comment'),
'sort' => 'AuthKey.comment',
@ -66,6 +74,14 @@
'description' => empty($ajax) ? __('A list of API keys bound to a user.') : false,
'pull' => 'right',
'actions' => [
[
'url' => $baseurl . '/auth_keys/view',
'url_params_data_paths' => array(
'AuthKey.id'
),
'icon' => 'eye',
'dbclickAction' => true
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/authKeys/delete/[onclick_params_data_path]\');',
@ -80,19 +96,14 @@
]);
echo '</div>';
if (empty($ajax)) {
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => $metaGroup, 'menuItem' => $this->action));
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}
?>
<script type="text/javascript">
var passedArgsArray = <?php echo $passedArgs; ?>;
$(document).ready(function() {
$(function() {
$('#quickFilterButton').click(function() {
runIndexQuickFilter();
});
$('#quickFilterField').on('keypress', function (e) {
if(e.which === 13) {
runIndexQuickFilter();
}
});
});
</script>

View File

@ -1,4 +1,17 @@
<?php
$keyUsageCsv = null;
if (isset($keyUsage)) {
$todayString = date('Y-m-d');
$today = strtotime($todayString);
$startDate = key($keyUsage); // oldest date for sparkline
$startDate = strtotime($startDate) - (3600 * 24 * 3);
$keyUsageCsv = 'Date,Close\n';
for ($date = $startDate; $date <= $today; $date += (3600 * 24)) {
$dateAsString = date('Y-m-d', $date);
$keyUsageCsv .= $dateAsString . ',' . (isset($keyUsage[$dateAsString]) ? $keyUsage[$dateAsString] : 0) . '\n';
}
}
echo $this->element(
'genericElements/SingleViews/single_view',
[
@ -14,10 +27,21 @@ echo $this->element(
'path' => 'AuthKey.uuid',
],
[
'key' => __('Auth key'),
'key' => __('Auth Key'),
'path' => 'AuthKey',
'type' => 'authkey'
],
[
'key' => __('User'),
'path' => 'User.id',
'pathName' => 'User.email',
'model' => 'users',
'type' => 'model'
],
[
'key' => __('Comment'),
'path' => 'AuthKey.comment'
],
[
'key' => __('Created'),
'path' => 'AuthKey.created',
@ -29,18 +53,24 @@ echo $this->element(
'type' => 'expiration'
],
[
'key' => __('User'),
'path' => 'User.id',
'pathName' => 'User.email',
'model' => 'users',
'type' => 'model'
'key' => __('Key usage'),
'type' => 'sparkline',
'path' => 'AuthKey.id',
'csv' => [
'data' => $keyUsageCsv,
],
'requirement' => isset($keyUsage),
],
[
'key' => __('Comment'),
'path' => 'AuthKey.comment'
'key' => __('Last used'),
'raw' => $lastUsed ? date('Y-m-d H:i:s', $lastUsed) : __('Not used yet'),
'requirement' => isset($keyUsage),
],
[
'key' => __('Unique IPs'),
'raw' => $uniqueIps,
'requirement' => isset($keyUsage),
]
],
'children' => [
]
]
);

View File

@ -0,0 +1,20 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Add Correlation Exclusion Entry'),
'description' => __('If you wish to exclude certain entries from being correlated on, simply add an entry here.'),
'fields' => [
[
'field' => 'value',
'label' => __('Value'),
'class' => 'span6',
]
],
'submit' => [
'action' => $this->request->params['action']
]
]
]);
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}

View File

@ -0,0 +1,99 @@
<?php
echo sprintf('<div%s>', empty($ajax) ? ' class="index"' : '');
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'data' => $data,
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'simple',
'children' => [
'data' => [
'type' => 'simple',
'text' => __('Add correlation exclusion entry'),
'class' => 'btn btn-primary',
'onClick' => 'openGenericModal',
'onClickParams' => [
sprintf(
'%s/correlation_exclusions/add',
$baseurl
)
]
]
]
],
[
'type' => 'simple',
'children' => [
'data' => [
'type' => 'simple',
'text' => __('Clean up correlations'),
'class' => 'btn btn-primary',
'onClick' => 'openGenericModal',
'onClickParams' => [
sprintf(
'%s/correlation_exclusions/clean',
$baseurl
)
]
]
]
],
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'searchKey' => 'quickFilter',
]
]
],
'fields' => [
[
'name' => '#',
'sort' => 'CorrelationExclusion.id',
'data_path' => 'CorrelationExclusion.id',
'class' => 'short'
],
[
'name' => 'Value',
'sort' => 'CorrelationExclusion.value',
'data_path' => 'CorrelationExclusion.value',
],
[
'name' => 'JSON source',
'sort' => 'CorrelationExclusion.from_json',
'data_path' => 'CorrelationExclusion.from_json',
'element' => 'boolean',
'class' => 'short'
]
],
'title' => empty($ajax) ? __('Correlation Exclusions Index') : false,
'description' => empty($ajax) ? __('A list of values to exclude from the correlation engine.') : false,
'pull' => 'right',
'actions' => [
[
'onclick' => sprintf(
'openGenericModal(\'%s/correlation_exclusions/delete/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'CorrelationExclusion.id',
'icon' => 'trash',
'title' => __('Delete correlation exclusion entry'),
]
]
]
]);
echo '</div>';
if (empty($ajax)) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}
?>
<script type="text/javascript">
var passedArgsArray = <?php echo $passedArgs; ?>;
$(function() {
$('#quickFilterButton').click(function() {
runIndexQuickFilter();
});
});
</script>

View File

@ -3,13 +3,6 @@
<div class="pagination">
<ul>
<?php
$this->Paginator->options(array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()',
));
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));

View File

@ -24,13 +24,6 @@ if (isset($sightingsData['data'][$objectId])) {
}
?>
<td class="shortish">
<span id="sightingForm_<?= $objectId ?>">
<?php
echo $this->Form->create('Sighting', array('id' => 'Sighting_' . $objectId, 'url' => $baseurl . '/sightings/add/' . $objectId, 'style' => 'display:none;'));
echo $this->Form->input('type', array('label' => false, 'id' => 'Sighting_' . $objectId . '_type'));
echo $this->Form->end();
?>
</span>
<?php
if ($isAclSighting):
?>

View File

@ -76,7 +76,7 @@
);
if (!empty($tag['Tag']['id'])) {
$span_tag = sprintf(
'<a href="%s" style="%s" class="%s" title="%s">%s</a>',
'<a href="%s" style="%s" class="%s" title="%s" data-tag-id="%s">%s</a>',
sprintf(
'%s%s%s',
$baseurl,
@ -86,6 +86,7 @@
$aStyle,
$aClass,
$aText,
h($tag['Tag']['id']),
isset($aTextModified) ? $aTextModified : $aText
);
} else {

View File

@ -36,14 +36,12 @@
$url = array_merge(array('controller' => 'events', 'action' => 'viewEventAttributes', $event['Event']['id']), $params);
$this->Paginator->options(array(
'url' => $url,
'update' => '#attributes_div',
'evalScripts' => true,
'before' => '$(".loading").show()',
'complete' => '$(".loading").hide()',
'data-paginator' => '#attributes_div',
));
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 60, 'separator' => '', 'tag' => 'li', 'currentClass' => 'red', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
$paginatorLinks = $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
$paginatorLinks .= $this->Paginator->numbers(array('modulus' => 60, 'separator' => '', 'tag' => 'li', 'currentClass' => 'red', 'currentTag' => 'span'));
$paginatorLinks .= $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $paginatorLinks;
?>
<li class="all <?php if ($all) echo 'disabled'; ?>">
<?php
@ -185,6 +183,13 @@
}
?>
</table>
<?php
// Generate form for adding sighting just once, generation for every attribute is surprisingly too slow
echo $this->Form->create('Sighting', ['id' => 'SightingForm', 'url' => $baseurl . '/sightings/add/', 'style' => 'display:none;']);
echo $this->Form->input('id', ['label' => false, 'type' => 'number']);
echo $this->Form->input('type', ['label' => false]);
echo $this->Form->end();
?>
</div>
<?php if ($emptyEvent && (empty($attributeFilter) || $attributeFilter === 'all') && !$filtered): ?>
<div class="background-red bold" style="padding: 2px 5px">
@ -201,18 +206,7 @@ attributes or the appropriate distribution level. If you think there is a mistak
<?php endif;?>
<div class="pagination">
<ul>
<?php
$this->Paginator->options(array(
'url' => $url,
'update' => '#attributes_div',
'evalScripts' => true,
'before' => '$(".loading").show()',
'complete' => '$(".loading").hide()',
));
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 60, 'separator' => '', 'tag' => 'li', 'currentClass' => 'red', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
?>
<?= $paginatorLinks ?>
<li class="all <?php if ($all) echo 'disabled'; ?>">
<?php
if ($all):
@ -299,6 +293,3 @@ attributes or the appropriate distribution level. If you think there is a mistak
filterAttributes('value', '<?php echo h($event['Event']['id']); ?>');
});
</script>
<?php
echo $this->Js->writeBuffer();
?>

View File

@ -2,7 +2,7 @@
<div id="shortcutsListContainer" class="<?php echo $debugMode ? 'hidden': ''; ?>">
<div id="triangle"></div>
<div id="shortcutsList">
<span> <?php echo __('Keyboard shortcuts for this page'); ?>:</span><br />
<span> <?php echo __('Keyboard shortcuts for this page'); ?>:</span><br>
<div id="shortcuts"><?php echo __('none'); ?></div>
</div>
</div>
@ -11,22 +11,24 @@
<div class="pull-left footerText" style="float:left;position:absolute;padding-top:12px;z-index:2;">
<?php
$gpgpath = ROOT.DS.APP_DIR.DS.WEBROOT_DIR.DS.'gpg.asc';
if (file_exists($gpgpath) && (is_file($gpgpath) || is_link($gpgpath))){ ?>
<span>Download: <?php echo $this->Html->link(__('GnuPG key'), $this->webroot.'gpg.asc');?></span>
if (Configure::read("MISP.download_gpg_from_homedir")) { ?>
<span>Download: <?= $this->Html->link(__('PGP public key'), array('controller' => 'users', 'action' => 'getGpgPublicKey')) ?></span>
<?php } else if (file_exists($gpgpath) && (is_file($gpgpath) || is_link($gpgpath))) { ?>
<span>Download: <?php echo $this->Html->link(__('PGP public key'), $this->webroot.'gpg.asc');?></span>
<?php } else { ?>
<span><?php echo __('Could not locate the GnuPG public key.');?></span>
<span><?php echo __('Could not locate the PGP public key.');?></span>
<?php }
if (Configure::read('SMIME.enabled')):
$smimepath = ROOT.DS.APP_DIR.DS.WEBROOT_DIR.DS.'public_certificate.pem';
if (file_exists($smimepath) && (is_file($smimepath) || is_link($gpgpath))){ ?>
<span>Download: <?php echo $this->Html->link('SMIME certificate', $this->webroot.'public_certificate.pem');?></span>
if (file_exists($smimepath) && (is_file($smimepath) || is_link($gpgpath))) { ?>
<span>Download: <?php echo $this->Html->link(__('S/MIME certificate'), $this->webroot.'public_certificate.pem');?></span>
<?php } else { ?>
<span><?php echo __('Could not locate SMIME certificate.');?></span>
<span><?php echo __('Could not locate S/MIME certificate.');?></span>
<?php }
endif;
?>
</div>
<div class = "footerText footerCenterText">
<div class="footerText footerCenterText">
<span><?php echo h(Configure::read('MISP.footermidleft')); ?> Powered by <a href="https://github.com/MISP/MISP">MISP <?php if (isset($me['id'])) echo h($mispVersionFull);?></a> <?php echo h(Configure::read('MISP.footermidright')); ?> - <?php echo date("Y-m-d H:i:s"); ?></span>
</div>
<div class="pull-right" style="position:relative;padding-top:9px;z-index:2;">

View File

@ -45,32 +45,32 @@ function extractDatetimePart(text) {
}
}
$(document).ready(function() {
var sliders_container = "#bothSeenSliderContainer"
$(function() {
var $sliders_container = $("#bothSeenSliderContainer");
var inputs_container = $('<div class="input-group input-daterange"></div>');
// create separate date and time input
var date_div_fs = $('<div class="input clear larger-input-field" style="margin-left: 10px;"></div>').append(
var date_div_fs = $('<div class="input clear" style="width: 257px"></div>').append(
$('<label><?php echo __('First seen date') . '<span class="fas fa-calendar label-icon"></span>'; ?><input id="date_fs" type="text" style="width: 240px;"></input></label>')
);
$(inputs_container).append(date_div_fs);
var date_div_ls = $('<div class="input text larger-input-field"></div>').append(
var date_div_ls = $('<div class="input text" style="width: 257px"></div>').append(
$('<label><?php echo __('Last seen date') . '<span class="fas fa-calendar label-icon"></span>'; ?><input id="date_ls" type="text" style="width: 240px;"></input></label>')
);
$(inputs_container).append(date_div_ls);
$(sliders_container).append(inputs_container);
$sliders_container.append(inputs_container);
var time_div_fs = $('<div class="input clear larger-input-field" style="margin-left: 10px;"></div>').append(
var time_div_fs = $('<div class="input clear" style="width: 257px"></div>').append(
$('<label><?php echo __('First seen time') . '<span class="fas fa-clock label-icon"></span>'; ?><input id="time_fs" type="text" style="width: 240px; text-align: center; margin-bottom: 0px" placeholder="HH:MM:SS.ssssss+TT:TT"></input></label>'),
$('<span class="apply_css_arrow"></span>').text('<?php echo __('Expected format: HH:MM:SS.ssssss+TT:TT') ?>')
);
$(sliders_container).append(time_div_fs);
var time_div_ls = $('<div class="input larger-input-field"></div>').append(
$sliders_container.append(time_div_fs);
var time_div_ls = $('<div class="input" style="width: 257px"></div>').append(
$('<label><?php echo __('Last seen time') . '<span class="fas fa-clock label-icon"></span>'; ?><input id="time_ls" type="text" style="width: 240px; text-align: center; margin-bottom: 0px" placeholder="HH:MM:SS.ssssss+TT:TT"></input></label>'),
$('<span class="apply_css_arrow"></span>').text('<?php echo __('Expected format: HH:MM:SS.ssssss+TT:TT') ?>')
);
$(sliders_container).append(time_div_ls);
$sliders_container.append(time_div_ls);
$('#'+controller+'FirstSeen').closest('form').submit(function( event ) {
$('#'+controller+'FirstSeen').closest('form').submit(function() {
reflect_change_on_form();
});

View File

@ -32,7 +32,7 @@
foreach ($cluster_fields as $cluster_field) {
$key = sprintf('<span class="blue bold">%s</span>', Inflector::humanize(h($cluster_field['key'])));
if (is_array($cluster_field['value'])) {
if ($cluster_field['key'] == 'refs') {
if ($cluster_field['key'] === 'refs') {
$value = array();
foreach ($cluster_field['value'] as $k => $v) {
$v_name = $v;
@ -41,25 +41,25 @@
}
$value[$k] = '<a href="' . h($v) . '" title="' . h($v) . '">' . h($v_name) . '</a>';
}
$value_contents = nl2br(implode("\n", $value));
} else if($cluster_field['key'] == 'country') {
$value_contents = implode("<br>", $value);
} else if ($cluster_field['key'] === 'country') {
$value = array();
foreach ($cluster_field['value'] as $k => $v) {
$value[] = $this->Icon->countryFlag($v) . '&nbsp;' . h($v);
}
$value_contents = nl2br(implode("\n", $value));
$value_contents = implode("<br>", $value);
} else {
$value_contents = nl2br(h(implode("\n", $cluster_field['value'])));
$value_contents = nl2br(h(implode("\n", $cluster_field['value'])), false);
}
} else {
if ($cluster_field['key'] == 'source' && filter_var($cluster_field['value'], FILTER_VALIDATE_URL)) {
$value_contents = '<a href="' . h($cluster_field['value']) . '">' . h($cluster_field['value']) . '</a>';;
if ($cluster_field['key'] === 'source' && filter_var($cluster_field['value'], FILTER_VALIDATE_URL)) {
$value_contents = '<a href="' . h($cluster_field['value']) . '">' . h($cluster_field['value']) . '</a>';
} else {
$value_contents = h($cluster_field['value']);
}
}
$value = sprintf('<span class="black">%s</span>', $value_contents);
$popover_data .= sprintf('<span>%s: %s</span><br />', $key, $value);
$popover_data .= "<span>$key: $value</span><br>";
}
echo sprintf(
'<div class="large-left-margin">%s %s %s %s</div>',
@ -135,17 +135,3 @@
);
}
}
?>
<script type="text/javascript">
$(document).ready(function () {
$('<?= isset($rowId) ? '#'.$rowId : '' ?> .expandable')
.on('click', function() {
loadClusterRelations($(this).data('clusterid'));
})
.popover({
html: true,
trigger: 'hover'
});
});
</script>

View File

@ -129,7 +129,7 @@
);
} else {
echo sprintf(
'<div class="%s">%s<fieldset><legend>%s</legend>%s<div class="clear" style="padding-bottom:10px;">%s</div>%s</fieldset>%s%s%s</div>',
'<div class="%s">%s<fieldset><legend>%s</legend>%s<div class="clear">%s</div>%s</fieldset>%s%s%s</div>',
empty($data['skip_side_menu']) ? 'form' : 'menuless-form',
$formCreate,
empty($data['title']) ? h(Inflector::humanize($this->request->params['action'])) . ' ' . $modelForForm : h($data['title']),
@ -144,7 +144,7 @@
?>
<script type="text/javascript">
var fieldsArray = <?php echo json_encode($fieldsArrayForPersistence); ?>;
$(document).ready(function() {
$(function() {
popoverStartup();
});
</script>

View File

@ -43,9 +43,19 @@
}
$rules_raw = implode('<br />', $rules_raw);
}
$classes = ['fa'];
$classes[] = !empty(Hash::extract($row, $field['data_path'])[0]) ? 'fa-check' : 'fa-times';
if (!empty($field['colors'])) {
$classes[] = !empty(Hash::extract($row, $field['data_path'])[0]) ? 'green' : 'grey';
} else {
$classes[] = 'black';
}
echo sprintf(
'<i class="black fa fa-%s" role="img" aria-label="%s"></i>%s',
(!empty(Hash::extract($row, $field['data_path'])[0])) ? 'check' : 'times',
'<i class="%s" role="img" aria-label="%s"></i>%s',
implode(' ', $classes),
(!empty(Hash::extract($row, $field['data_path'])[0])) ? __('Yes') : __('No'),
empty($rules_raw) ? '' :
sprintf(
@ -55,4 +65,4 @@
__('Rules')
)
);
?>

View File

@ -20,9 +20,9 @@
$title = __('Expired at %s', date('Y-m-d H:i:s', $data));
$data = '<span class="red bold" title="' . $title . '">' . __('Expired') . '</span>';
} else {
$diffInDays = floor(($data - time()) / 3600 * 24);
$diffInDays = floor(($data - time()) / (3600 * 24));
$class = $diffInDays <= 14 ? 'text-warning bold' : 'text-success';
$title = __n('Will expire at %s day', 'Will expire at %s days', $diffInDays, $diffInDays);
$title = __n('Will expire in %s day', 'Will expire in %s days', $diffInDays, $diffInDays);
$data = '<span class="' . $class . '" title="' . $title . '">' . date('Y-m-d H:i:s', $data) . '</span>';
}
}

View File

@ -19,11 +19,20 @@
} else {
$header_data = h($header['name']);
}
}
$classes = [];
if (!empty($header['sort'])) {
$classes[] = 'pagination_link';
}
if (!empty($header['rotate_header'])) {
$classes[] = 'rotate';
$header_data = "<div><span>$header_data</span></div>";
}
$headersHtml .= sprintf(
'<th%s>%s</th>',
!empty($header['sort']) ? ' class="pagination_link"' : '',
'<th%s%s>%s</th>',
!empty($classes) ? ' class="' . implode(' ', $classes) .'"' : '',
!empty($header['header_title']) ? ' title="' . h($header['header_title']) . '"' : '',
$header_data
);
}

View File

@ -38,10 +38,21 @@
$skipPagination = isset($data['skip_pagination']) ? $data['skip_pagination'] : 0;
if (!$skipPagination) {
$paginationData = !empty($data['paginatorOptions']) ? $data['paginatorOptions'] : array();
echo $this->element('/genericElements/IndexTable/pagination', array('paginationOptions' => $paginationData));
echo $this->element('/genericElements/IndexTable/pagination_links');
if ($ajax && isset($containerId)) {
$paginationData['data-paginator'] = "#{$containerId}_content";
}
$this->Paginator->options($paginationData);
$paginatonLinks = $this->element('/genericElements/IndexTable/pagination_links');
echo $paginatonLinks;
}
$hasSearch = false;
if (!empty($data['top_bar'])) {
foreach ($data['top_bar']['children'] as $child) {
if (isset($child['type']) && $child['type'] === 'search') {
$hasSearch = true;
break;
}
}
echo $this->element('/genericElements/ListTopBar/scaffold', array('data' => $data['top_bar']));
}
$rows = '';
@ -89,48 +100,27 @@
echo '</div>';
if (!$skipPagination) {
echo $this->element('/genericElements/IndexTable/pagination_counter', $paginationData);
echo $this->element('/genericElements/IndexTable/pagination_links');
echo $paginatonLinks;
}
$url = $baseurl . '/' . $this->params['controller'] . '/' . $this->params['action'];
?>
<script type="text/javascript">
var passedArgsArray = <?= isset($passedArgs) ? $passedArgs : '[]'; ?>;
<?php
var passedArgsArray = <?= isset($passedArgs) ? $passedArgs : '{}'; ?>;
var url = "<?= $url ?>";
<?php if ($hasSearch): ?>
$(function() {
<?php
if (isset($containerId)) {
echo 'var target = "#' . $containerId . '_content";';
}
?>
var url = "<?= $url ?>";
$(function() {
?>
$('#quickFilterButton').click(function() {
if (typeof(target) !== 'undefined') {
runIndexQuickFilter(passedArgsArray, url, target);
runIndexQuickFilterFixed(passedArgsArray, url, target);
} else {
runIndexQuickFilter(passedArgsArray, url);
runIndexQuickFilterFixed(passedArgsArray, url);
}
});
});
var ajax = <?= $ajax ? 'true' : 'false' ?>;
if (ajax && typeof(target) !== 'undefined') {
$(target + ' .pagination_link a').on('click', function() {
$.ajax({
beforeSend: function () {
$(".loading").show();
},
success: function (data) {
$(target).html(data);
},
error: function() {
showMessage('fail', 'Could not fetch the requested data.');
},
complete: function() {
$(".loading").hide();
},
type: "get",
url: $(this).attr('href')
});
return false;
});
}
<?php endif; ?>
</script>

View File

@ -1,12 +0,0 @@
<?php
$options = array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()'
);
if (!empty($paginationOptions)) {
$options = array_merge($options, $paginationOptions);
}
echo $this->Paginator->options($options);
?>

View File

@ -1,8 +1,3 @@
<?php
echo sprintf(
'<p>%s</p>',
$this->Paginator->counter(array(
'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
))
);
?>
<p><?= $this->Paginator->counter(array(
'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
)); ?></p>

View File

@ -477,6 +477,24 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
}
break;
case 'correlationExclusions':
if ($menuItem === 'view') {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'view',
'text' => __('View Correlation Exclusion')
));
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index',
'url' => $baseurl . '/correlation_exclusions/index',
'text' => __('List Correlation Exclusions')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'add',
'url' => $baseurl . '/correlation_exclusions/add',
'text' => __('Add Correlation Exclusion')
));
break;
case 'warninglist':
if ($menuItem === 'view') {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
@ -734,6 +752,10 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'url' => $baseurl . '/servers/add',
'text' => __('New Servers')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/servers/compareServers',
'text' => __('Server overlap analysis matrix'),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/communities/index',
'text' => __('List Communities'),
@ -762,7 +784,7 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
if ($menuItem === 'id_translator') {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'text' => __('Event ID translator'),
'url' => '/servers/id_translator',
'url' => '/servers/idTranslator',
'element_id' => 'id_translator'
));
}
@ -933,7 +955,7 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'indexRole',
'url' => $baseurl . '/admin/roles/index',
'url' => $baseurl . '/roles/index',
'text' => __('List Roles')
));
if ($isSiteAdmin) {

View File

@ -20,9 +20,9 @@
$title = __('Expired at %s', date('Y-m-d H:i:s', $data));
$data = '<span class="red bold" title="' . $title . '">' . __('Expired') . '</span>';
} else {
$diffInDays = floor(($data - time()) / 3600 * 24);
$diffInDays = floor(($data - time()) / (3600 * 24));
$class = $diffInDays <= 14 ? 'text-warning bold' : 'text-success';
$title = __n('Will expire at %s day', 'Will expire at %s days', $diffInDays, $diffInDays);
$title = __n('Will expire in %s day', 'Will expire in %s days', $diffInDays, $diffInDays);
$data = '<span class="' . $class . '" title="' . $title . '">' . date('Y-m-d H:i:s', $data) . '</span>';
}
}

View File

@ -1,5 +1,5 @@
<?php
if (!empty($field['raw'])) {
if (isset($field['raw'])) {
$string = $field['raw'];
} else {
$value = Hash::extract($data, $field['path']);

View File

@ -0,0 +1,14 @@
<?php
$elementId = Hash::extract($data, $field['path'])[0];
if (!empty($field['csv_data_path'])) {
$csv = Hash::extract($data, $field['csv_data_path']);
if (!empty($csv)) {
$csv = $csv[0];
}
} else {
$csv = $field['csv']['data'];
}
if (!empty($csv)) {
$scope = empty($field['csv']['scope']) ? '' : $field['csv']['scope'];
echo $this->element('sparkline', array('scope' => $scope, 'id' => $elementId, 'csv' => $csv));
}

View File

@ -30,6 +30,10 @@
$listElements = '';
if (!empty($fields)) {
foreach ($fields as $field) {
if (isset($field['requirement']) && !$field['requirement']) {
continue;
}
if (empty($field['type'])) {
$field['type'] = 'generic';
}

View File

@ -17,7 +17,7 @@
'disable_search_threshold' => 10,
'allow_single_deselect' => true,
),
'multiple' => 0,
'multiple' => 'multiple',
'select_threshold' => 7, // threshold above which pills will be replace by a select (unused if multiple is > 1)
'functionName' => '', // function to be called on submit
'submitButtonText' => 'Submit',
@ -104,6 +104,12 @@ function setupChosen(id, redrawChosen) {
if (redrawChosen) {
redrawChosenWithTemplate($elem, $chosenContainer);
}
if ($elem.prop('multiple')) {
$elem.filter('[autofocus]').trigger('chosen:open');
} else {
$elem.filter('[autofocus]').trigger('chosen:activate');
}
}
function redrawChosenWithTemplate($select, $chosenContainer, eventType) {
@ -113,11 +119,12 @@ function redrawChosenWithTemplate($select, $chosenContainer, eventType) {
} else {
$chosenContainer.find('.generic-picker-wrapper-warning-text').hide(0)
var $matches;
if (eventType == 'chosen:picked' || eventType == 'change') {
if (eventType === 'chosen:picked' || eventType === 'change') {
$matches = $chosenContainer.find('.chosen-single > span, .search-choice > span');
} else {
$matches = $chosenContainer.find('.chosen-results .active-result');
}
var templates = options_templates[$select.attr('id')];
$matches.each(function() {
var $item = $(this);
var index = $item.data('option-array-index');
@ -131,8 +138,7 @@ function redrawChosenWithTemplate($select, $chosenContainer, eventType) {
return temp === text;
});
}
var template = options_templates[$select.attr('id')][$option.val()];
var res = "";
var template = templates[$option.val()];
if (template !== undefined && template !== '') {
$item.html(template);
}
@ -228,10 +234,10 @@ function submitFunction(clicked, callback) {
$flag_addPills = false;
?>
<?php if ($use_select): ?>
<select id="<?php echo $select_id; ?>" style="height: 100px; margin-bottom: 0px;" <?php echo h($this->GenericPicker->add_select_params($defaults)); ?>>
<select id="<?php echo $select_id; ?>" autofocus style="height: 100px; margin-bottom: 0px;" <?= $this->GenericPicker->add_select_params($defaults); ?>>
<option></option>
<?php
foreach ($items as $k => $param) {
foreach ($items as $param) {
if (isset($param['isPill']) && $param['isPill']) {
$flag_addPills = true;
continue;
@ -256,7 +262,7 @@ function submitFunction(clicked, callback) {
<?php if ($flag_addPills): // add forced pills ?>
<ul class="nav nav-pills">
<?php
foreach ($items as $k => $param) {
foreach ($items as $param) {
if (isset($param['isPill']) && $param['isPill']) {
echo $this->GenericPicker->add_pill($param, $defaults);
if (isset($param['additionalData'])) {
@ -270,16 +276,16 @@ function submitFunction(clicked, callback) {
<?php endif; ?>
<script>
$(document).ready(function() {
$(function() {
setupChosen("<?php echo $select_id; ?>", <?php echo ($defaults['flag_redraw_chosen'] === true ? 'true' : 'false') ?>);
});
</script>
<?php elseif (count($items) > 0): ?>
<ul class="nav nav-pills">
<select id="<?php echo $select_id; ?>" style="display: none;" <?php echo h($this->GenericPicker->add_select_params($defaults)); ?>></select>
<select id="<?php echo $select_id; ?>" autofocus style="display: none;" <?php echo h($this->GenericPicker->add_select_params($defaults)); ?>></select>
<?php
foreach ($items as $k => $param) {
foreach ($items as $param) {
echo $this->GenericPicker->add_pill($param, $defaults);
if (isset($param['additionalData'])) {
$additionalData = $param['additionalData'];
@ -297,9 +303,10 @@ function submitFunction(clicked, callback) {
<script>
if (options_templates === undefined) {
var options_templates = {};
var options_additionalData = {}
var options_additionalData = {};
}
options_templates['<?php echo $select_id; ?>'] = <?php echo json_encode($option_templates); ?>;
options_additionalData['<?php echo $select_id; ?>'] = <?php echo json_encode($options_additionalData); ?>;
// Keep as string, it is faster than parsing as JS
options_templates['<?php echo $select_id; ?>'] = JSON.parse('<?= addslashes(json_encode($option_templates, JSON_UNESCAPED_UNICODE)); ?>');
options_additionalData['<?php echo $select_id; ?>'] = JSON.parse('<?= addslashes(json_encode($options_additionalData, JSON_UNESCAPED_UNICODE)); ?>');
</script>
</div>

View File

@ -1,10 +1,4 @@
<?php
$this->Paginator->options(array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()',
));
$title = sprintf('<h2>%s</h2>', $alias);
if (!empty($description)) {
$description = sprintf('<p>%s</p>', Inflector::humanize($description));

View File

@ -1,10 +1,4 @@
<?php
$this->Paginator->options(array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()',
));
$title = sprintf('<h2>%s index</h2>', Inflector::humanize($controller));
if (!empty($description)) {
$description = sprintf('<p>%s</p>', Inflector::humanize($description));

View File

@ -164,7 +164,11 @@
array(
'text' => __('List Noticelists'),
'url' => $baseurl . '/noticelists/index'
)
),
[
'text' => __('List Correlation Exclusions'),
'url' => $baseurl . '/correlation_exclusions/index'
]
)
),
array(
@ -370,7 +374,7 @@
),
array(
'text' => __('List Roles'),
'url' => $baseurl . '/admin/roles/index'
'url' => $baseurl . '/roles/index'
),
array(
'text' => __('Add Roles'),
@ -464,7 +468,7 @@
(!empty($homepage['path']) && $homepage['path'] === $this->here) ? 'orange' : '',
__('Set the current page as your home page in MISP'),
__('Set the current page as your home page in MISP'),
$this->here
h($this->here)
)
),
array(

View File

@ -72,7 +72,7 @@
<h3><?php echo __('Writeable Directories and files');?></h3>
<p><?php echo __('The following directories and files have to be writeable for MISP to function properly. Make sure that the apache user has write privileges for the directories below.');?></p>
<p><b><?php echo __('Directories');?></b></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
foreach ($writeableDirs as $dir => $error) {
$colour = 'green';
@ -87,7 +87,7 @@
</div>
<br />
<p><b><?php echo __('Writeable Files');?></b></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
foreach ($writeableFiles as $file => $error) {
$colour = 'green';
@ -101,7 +101,7 @@
?>
</div>
<p><b><?php echo __('Readable Files');?></b></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
foreach ($readableFiles as $file => $error) {
$colour = 'green';
@ -131,14 +131,18 @@
}
$phpversions[$source]['phpcolour'] = 'green';
$phpversions[$source]['phptext'] = __('Up to date');
if (version_compare($phpversions[$source]['phpversion'], $phprec) < 1) {
if (version_compare($phpversions[$source]['phpversion'], $phprec) < 0) {
$phpversions[$source]['phpcolour'] = 'orange';
$phpversions[$source]['phptext'] = __('Update highly recommended');
if (version_compare($phpversions[$source]['phpversion'], $phpmin) < 1) {
if (version_compare($phpversions[$source]['phpversion'], $phpmin) < 0) {
$phpversions[$source]['phpcolour'] = 'red';
$phpversions[$source]['phptext'] = __('Version unsupported, update ASAP');
}
}
if (version_compare($phpversions[$source]['phpversion'], $phptoonew) >= 0) {
$phpversions[$source]['phpcolour'] = 'red';
$phpversions[$source]['phptext'] = __('Version unsupported, 8.x support not available yet.');
}
}
if (version_compare($phpversion, $phprec) < 1) {
$phpcolour = 'orange';
@ -250,7 +254,7 @@
)); ?>
</div>
<h3><?= __("Redis info") ?></h3>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<b><?= __('PHP extension version') ?>:</b> <?= $redisInfo['extensionVersion'] ?: ('<span class="red bold">' . __('Not installed.') . '</span>') ?><br>
<?php if ($redisInfo['connection']): ?>
<b><?= __('Redis version') ?>:</b> <?= $redisInfo['redis_version'] ?><br>
@ -264,7 +268,7 @@
</div>
<h3><?php echo __('Advanced attachment handler');?></h3>
<?php echo __('The advanced attachment tools are used by the add attachment functionality to extract additional data about the uploaded sample.');?>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
if (empty($advanced_attachments)):
?>
@ -281,7 +285,7 @@
?>
</div>
<h3><?= __('Attachment scan module') ?></h3>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php if ($attachmentScan['status']): ?>
<b>Status:</b> <span class="green bold"><?= __('OK') ?></span><br>
<b>Software</b>: <?= implode(", ", $attachmentScan['software']) ?>
@ -300,7 +304,7 @@
<b>STIX2</b>: <?php echo $stix['stix2']['expected'];?><br />
<b>PyMISP</b>: <?php echo $stix['pymisp']['expected'];?><br />
<?php echo __('Other versions might work but are not tested / recommended.');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
$colour = 'green';
$testReadError = false;
@ -341,7 +345,7 @@
</div>
<h3><?php echo __('Yara');?></h3>
<p><?php echo __('This tool tests whether plyara, the library used by the yara export tool is installed or not.');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
$colour = 'green';
$message = __('OK');
@ -355,7 +359,7 @@
<h3><?php echo __('GnuPG');?></h3>
<p><?php echo __('This tool tests whether your GnuPG is set up correctly or not.');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
$message = $gpgErrors[$gpgStatus['status']];
$color = $gpgStatus['status'] === 0 ? 'green' : 'red';
@ -367,7 +371,7 @@
</div>
<h3><?php echo __('ZeroMQ');?></h3>
<p><?php echo __('This tool tests whether the ZeroMQ extension is installed and functional.');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
$colour = 'green';
$message = $zmqErrors[$zmqStatus];
@ -384,7 +388,7 @@
</div>
<h3><?php echo __('Proxy');?></h3>
<p><?php echo __('This tool tests whether your HTTP proxy settings are correct.');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
$colour = 'green';
$message = $proxyErrors[$proxyStatus];
@ -399,7 +403,7 @@
<?php
foreach ($moduleTypes as $type):
?>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
$colour = 'red';
if (isset($moduleErrors[$moduleStatus[$type]])) {
@ -418,7 +422,7 @@
?>
<h3><?php echo __('Session table');?></h3>
<p><?php echo __('This tool checks how large your database\'s session table is. <br />Sessions in CakePHP rely on PHP\'s garbage collection for clean-up and in certain distributions this can be disabled by default resulting in an ever growing cake session table. <br />If you are affected by this, just click the clean session table button below.');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php
$colour = 'green';
$message = $sessionErrors[$sessionStatus];
@ -460,7 +464,7 @@
?>
<h3><?php echo __('Orphaned attributes');?></h3>
<p><?php echo __('In some rare cases attributes can remain in the database after an event is deleted becoming orphaned attributes. This means that they do not belong to any event, which can cause issues with the correlation engine (known cases include event deletion directly in the database without cleaning up the attributes and situations involving a race condition with an event deletion happening before all attributes are synchronised over).');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php echo __('Orphaned attributes');?>…<span id="orphanedAttributeCount"><span style="color:orange;"><?php echo __('Run the test below');?></span></span>
</div><br />
<span class="btn btn-inverse" role="button" tabindex="0" aria-label="<?php echo __('Check for orphaned attribute');?>" title="<?php echo __('Check for orphaned attributes');?>" style="padding-top:1px;padding-bottom:1px;" onClick="checkOrphanedAttributes();"><?php echo __('Check for orphaned attributes');?></span><br /><br />
@ -474,7 +478,7 @@
<span class="btn btn-inverse" style="padding-top:1px;padding-bottom:1px;" onClick="location.href = '<?php echo $baseurl; ?>/pages/display/administration';"><?php echo __('Legacy Administrative Tools');?></span>
<h3><?php echo __('Verify bad link on attachments');?></h3>
<p><?php echo __('Verify each attachment referenced in database is accessible on filesystem.');?></p>
<div style="background-color:#f7f7f9;width:400px;">
<div class="diagnostics-box">
<?php echo __('Non existing attachments referenced in Database');?>…<span id="orphanedFileCount"><span style="color:orange;"><?php echo __('Run the test below');?></span></span>
</div><br>
<span class="btn btn-inverse" role="button" tabindex="0" aria-label="<?php echo __('Check bad link on attachments');?>" title="<?php echo __('Check bad link on attachments');?>" style="padding-top:1px;padding-bottom:1px;" onClick="checkAttachments();"><?php echo __('Check bad link on attachments');?></span>
@ -484,7 +488,7 @@
</div>
<script>
$(document).ready(function() {
$(function() {
updateSubModulesStatus();
$('#refreshSubmoduleStatus').click(function() { updateSubModulesStatus(); });
$('#updateAllJson').click(function() { updateAllJson(); });

View File

@ -98,7 +98,7 @@
</li>
</ul>
<?php elseif($canEdit && !empty($editRedirect)): ?>
<a id="saveMarkdownButton" type="button" class="btn btn-primary" href="<?= h($editRedirect) ?>" target="_blank">
<a type="button" class="btn btn-primary" href="<?= h($editRedirect) ?>#splitscreen" target="_blank">
<i class="<?= $this->FontAwesome->getClass('edit') ?>"></i>
<?= __('Edit report') ?>
</a>
@ -221,4 +221,4 @@
if (!empty($additionalMarkdownElements)) {
echo $this->element($additionalMarkdownElements['path'], $additionalMarkdownElements['variables']);
}
?>
?>

Some files were not shown because too many files have changed in this diff Show More