diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index 55cf99b5f..4b258080c 100755 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -79,6 +79,7 @@ class AppController extends Controller ), 'Security', 'ACL', + 'CompressedRequestHandler', 'RestResponse', 'Flash', 'Toolbox', diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index 73c481740..8b1364a0c 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -262,7 +262,9 @@ class ACLComponent extends Component 'viewEventAttributes' => array('*'), 'viewGraph' => array('*'), 'viewGalaxyMatrix' => array('*'), - 'xml' => array('*') + 'xml' => array('*'), + 'addEventLock' => ['perm_auth'], + 'removeEventLock' => ['perm_auth'], ), 'favouriteTags' => array( 'toggle' => array('*'), @@ -495,7 +497,7 @@ class ACLComponent extends Component 'getSubmodulesStatus' => array(), 'getSubmoduleQuickUpdateForm' => array(), 'getWorkers' => array(), - 'getVersion' => array('*'), + 'getVersion' => array('perm_auth'), 'idTranslator' => ['OR' => [ 'host_org_user', 'perm_site_admin', diff --git a/app/Controller/Component/CompressedRequestHandlerComponent.php b/app/Controller/Component/CompressedRequestHandlerComponent.php new file mode 100644 index 000000000..969303257 --- /dev/null +++ b/app/Controller/Component/CompressedRequestHandlerComponent.php @@ -0,0 +1,145 @@ +request->setInput($this->decodeBrotliEncodedContent($controller)); + } else if ($contentEncoding === 'gzip') { + $controller->request->setInput($this->decodeGzipEncodedContent($controller)); + } else { + throw new MethodNotAllowedException("Unsupported content encoding '$contentEncoding'."); + } + } + } + + /** + * @return array + */ + public function supportedEncodings() + { + $supportedEncodings = []; + if (function_exists('gzdecode') || function_exists('inflate_init')) { + $supportedEncodings[] = 'gzip'; + } + if (function_exists('brotli_uncompress') || function_exists('brotli_uncompress_init')) { + $supportedEncodings[] = 'br'; + } + return $supportedEncodings; + } + + /** + * @return string + * @throws Exception + * @see CakeRequest::_readInput() + */ + private function decodeGzipEncodedContent(Controller $controller) + { + if (function_exists('inflate_init')) { + // Decompress data on the fly if supported + $resource = inflate_init(ZLIB_ENCODING_GZIP); + if ($resource === false) { + throw new Exception('GZIP incremental uncompress init failed.'); + } + $uncompressed = ''; + foreach ($this->streamInput() as $data) { + $uncompressedChunk = inflate_add($resource, $data); + if ($uncompressedChunk === false) { + throw new MethodNotAllowedException('Invalid compressed data.'); + } + $uncompressed .= $uncompressedChunk; + if (strlen($uncompressed) > self::MAX_SIZE) { + throw new Exception("Uncompressed data are bigger than is limit."); + } + } + $uncompressedChunk = inflate_add($resource, '', ZLIB_FINISH); + if ($uncompressedChunk === false) { + throw new MethodNotAllowedException('Invalid compressed data.'); + } + return $uncompressed . $uncompressedChunk; + + } else if (function_exists('gzdecode')) { + $decoded = gzdecode($controller->request->input(), self::MAX_SIZE); + if ($decoded === false) { + throw new MethodNotAllowedException('Invalid compressed data.'); + } + if (strlen($decoded) >= self::MAX_SIZE) { + throw new Exception("Uncompressed data are bigger than is limit."); + } + return $decoded; + } else { + throw new MethodNotAllowedException("This server doesn't support GZIP compressed requests."); + } + } + + /** + * @param Controller $controller + * @return string + * @throws Exception + * @see CakeRequest::_readInput() + */ + private function decodeBrotliEncodedContent(Controller $controller) + { + if (function_exists('brotli_uncompress_init')) { + // Decompress data on the fly if supported + $resource = brotli_uncompress_init(); + if ($resource === false) { + throw new Exception('Brotli incremental uncompress init failed.'); + } + $uncompressed = ''; + foreach ($this->streamInput() as $data) { + $uncompressedChunk = brotli_uncompress_add($resource, $data, BROTLI_PROCESS); + if ($uncompressedChunk === false) { + throw new MethodNotAllowedException('Invalid compressed data.'); + } + $uncompressed .= $uncompressedChunk; + if (strlen($uncompressed) > self::MAX_SIZE) { + throw new Exception("Uncompressed data are bigger than is limit."); + } + } + $uncompressedChunk = brotli_uncompress_add($resource, '', BROTLI_FINISH); + if ($uncompressedChunk === false) { + throw new MethodNotAllowedException('Invalid compressed data.'); + } + return $uncompressed . $uncompressedChunk; + + } else if (function_exists('brotli_uncompress')) { + $decoded = brotli_uncompress($controller->request->input(), self::MAX_SIZE); + if ($decoded === false) { + throw new MethodNotAllowedException('Invalid compressed data.'); + } + if (strlen($decoded) >= self::MAX_SIZE) { + throw new Exception("Uncompressed data are bigger than is limit."); + } + return $decoded; + } else { + throw new MethodNotAllowedException("This server doesn't support brotli compressed requests."); + } + } + + /** + * @param int $chunkSize + * @return Generator + * @throws Exception + */ + private function streamInput($chunkSize = 8192) + { + $fh = fopen('php://input', 'rb'); + if ($fh === false) { + throw new Exception("Could not open PHP input for reading."); + } + while (!feof($fh)) { + $data = fread($fh, $chunkSize); + if ($data === false) { + throw new Exception("Could not read PHP input."); + } + yield $data; + } + fclose($fh); + } +} diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 1f11acf7a..0c5377b08 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -5312,9 +5312,43 @@ class EventsController extends AppController } } + public function addEventLock($id) + { + if (!$this->request->is('post')) { + throw new MethodNotAllowedException('This endpoint requires a POST request.'); + } + + $event = $this->Event->fetchSimpleEvent($this->Auth->User(), $id); + if (empty($event)) { + throw new MethodNotAllowedException(__('Invalid Event')); + } + if (!$this->__canModifyEvent($event)) { + throw new UnauthorizedException(__('You do not have permission to do that.')); + } + + $this->loadModel('EventLock'); + $lockId = $this->EventLock->insertLockApi($event['Event']['id'], $this->Auth->user()); + return $this->RestResponse->viewData(['lock_id' => $lockId], $this->response->type()); + } + + public function removeEventLock($id, $lockId) + { + if (!$this->request->is('post')) { + throw new MethodNotAllowedException('This endpoint requires a POST request.'); + } + + $event = $this->Event->fetchSimpleEvent($this->Auth->User(), $id); + if (empty($event)) { + throw new MethodNotAllowedException(__('Invalid Event')); + } + + $this->loadModel('EventLock'); + $deleted = $this->EventLock->deleteApiLock($event['Event']['id'], $lockId, $this->Auth->user()); + return $this->RestResponse->viewData(['deleted' => $deleted], $this->response->type()); + } + public function checkLocks($id) { - $this->loadModel('EventLock'); $event = $this->Event->find('first', array( 'recursive' => -1, 'conditions' => array('Event.id' => $id), @@ -5322,29 +5356,34 @@ class EventsController extends AppController )); $locks = array(); if (!empty($event) && ($event['Event']['orgc_id'] == $this->Auth->user('org_id') || $this->_isSiteAdmin())) { + $this->loadModel('EventLock'); $locks = $this->EventLock->checkLock($this->Auth->user(), $id); } if (!empty($locks)) { $temp = $locks; $locks = array(); foreach ($temp as $t) { - if ($t['User']['id'] !== $this->Auth->user('id')) { + if ($t['type'] === 'user' && $t['User']['id'] !== $this->Auth->user('id')) { if (!$this->_isSiteAdmin() && $t['User']['org_id'] != $this->Auth->user('org_id')) { - continue; + $locks[] = __('another user'); + } else { + $locks[] = $t['User']['email']; } - $locks[] = $t['User']['email']; + } else if ($t['type'] === 'job') { + $locks[] = __('background job'); + } else if ($t['type'] === 'api') { + $locks[] = __('external tool'); } } } - // TODO: i18n - if (!empty($locks)) { - $message = sprintf('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks)); - $this->set('message', $message); - $this->layout = false; - $this->render('/Events/ajax/event_lock'); - } else { + if (empty($locks)) { return $this->RestResponse->viewData('', $this->response->type(), false, true); } + + $message = __('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks)); + $this->set('message', $message); + $this->layout = false; + $this->render('/Events/ajax/event_lock'); } public function getEditStrategy($id) diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php index 8609a3b00..b1c9c8972 100644 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -2,6 +2,7 @@ App::uses('AppController', 'Controller'); App::uses('Xml', 'Utility'); App::uses('AttachmentTool', 'Tools'); +App::uses('SecurityAudit', 'Tools'); /** * @property Server $Server @@ -938,253 +939,254 @@ class ServersController extends AppController public function serverSettings($tab=false) { - if (!$this->_isSiteAdmin()) { - throw new MethodNotAllowedException(); + if (!$this->request->is('get')) { + throw new MethodNotAllowedException('Just GET method is allowed.'); } - if ($this->request->is('Get')) { - $tabs = array( - 'MISP' => array('count' => 0, 'errors' => 0, 'severity' => 5), - 'Encryption' => array('count' => 0, 'errors' => 0, 'severity' => 5), - 'Proxy' => array('count' => 0, 'errors' => 0, 'severity' => 5), - 'Security' => array('count' => 0, 'errors' => 0, 'severity' => 5), - 'Plugin' => array('count' => 0, 'errors' => 0, 'severity' => 5) - ); - $writeableErrors = array(0 => __('OK'), 1 => __('not found'), 2 => __('is not writeable')); - $readableErrors = array(0 => __('OK'), 1 => __('not readable')); - $gpgErrors = array(0 => __('OK'), 1 => __('FAIL: settings not set'), 2 => __('FAIL: Failed to load GnuPG'), 3 => __('FAIL: Issues with the key/passphrase'), 4 => __('FAIL: sign failed')); - $proxyErrors = array(0 => __('OK'), 1 => __('not configured (so not tested)'), 2 => __('Getting URL via proxy failed')); - $zmqErrors = array(0 => __('OK'), 1 => __('not enabled (so not tested)'), 2 => __('Python ZeroMQ library not installed correctly.'), 3 => __('ZeroMQ script not running.')); - $stixOperational = array(0 => __('Some of the libraries related to STIX are not installed. Make sure that all libraries listed below are correctly installed.'), 1 => __('OK')); - $stixVersion = array(0 => __('Incorrect STIX version installed, found $current, expecting $expected'), 1 => __('OK')); - $stix2Version = array(0 => __('Incorrect STIX2 version installed, found $current, expecting $expected'), 1 => __('OK')); - $cyboxVersion = array(0 => __('Incorrect CyBox version installed, found $current, expecting $expected'), 1 => __('OK')); - $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')); - $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')); + $tabs = array( + 'MISP' => array('count' => 0, 'errors' => 0, 'severity' => 5), + 'Encryption' => array('count' => 0, 'errors' => 0, 'severity' => 5), + 'Proxy' => array('count' => 0, 'errors' => 0, 'severity' => 5), + 'Security' => array('count' => 0, 'errors' => 0, 'severity' => 5), + 'Plugin' => array('count' => 0, 'errors' => 0, 'severity' => 5) + ); + $writeableErrors = array(0 => __('OK'), 1 => __('not found'), 2 => __('is not writeable')); + $readableErrors = array(0 => __('OK'), 1 => __('not readable')); + $gpgErrors = array(0 => __('OK'), 1 => __('FAIL: settings not set'), 2 => __('FAIL: Failed to load GnuPG'), 3 => __('FAIL: Issues with the key/passphrase'), 4 => __('FAIL: sign failed')); + $proxyErrors = array(0 => __('OK'), 1 => __('not configured (so not tested)'), 2 => __('Getting URL via proxy failed')); + $zmqErrors = array(0 => __('OK'), 1 => __('not enabled (so not tested)'), 2 => __('Python ZeroMQ library not installed correctly.'), 3 => __('ZeroMQ script not running.')); + $stixOperational = array(0 => __('Some of the libraries related to STIX are not installed. Make sure that all libraries listed below are correctly installed.'), 1 => __('OK')); + $stixVersion = array(0 => __('Incorrect STIX version installed, found $current, expecting $expected'), 1 => __('OK')); + $stix2Version = array(0 => __('Incorrect STIX2 version installed, found $current, expecting $expected'), 1 => __('OK')); + $cyboxVersion = array(0 => __('Incorrect CyBox version installed, found $current, expecting $expected'), 1 => __('OK')); + $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')); + $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')); - $finalSettings = $this->Server->serverSettingsRead(); - $issues = array( - 'errors' => array( - 0 => array( - 'value' => 0, - 'description' => __('MISP will not operate correctly or will be unsecure until these issues are resolved.') - ), - 1 => array( - 'value' => 0, - 'description' => __('Some of the features of MISP cannot be utilised until these issues are resolved.') - ), - 2 => array( - 'value' => 0, - 'description' => __('There are some optional tweaks that could be done to improve the looks of your MISP instance.') - ), + $finalSettings = $this->Server->serverSettingsRead(); + $issues = array( + 'errors' => array( + 0 => array( + 'value' => 0, + 'description' => __('MISP will not operate correctly or will be unsecure until these issues are resolved.') ), - 'deprecated' => array(), - 'overallHealth' => 3, - ); - $dumpResults = array(); - $tempArray = array(); - foreach ($finalSettings as $k => $result) { - if ($result['level'] == 3) { - $issues['deprecated']++; + 1 => array( + 'value' => 0, + 'description' => __('Some of the features of MISP cannot be utilised until these issues are resolved.') + ), + 2 => array( + 'value' => 0, + 'description' => __('There are some optional tweaks that could be done to improve the looks of your MISP instance.') + ), + ), + 'deprecated' => array(), + 'overallHealth' => 3, + ); + $dumpResults = array(); + $tempArray = array(); + foreach ($finalSettings as $k => $result) { + if ($result['level'] == 3) { + $issues['deprecated']++; + } + $tabs[$result['tab']]['count']++; + if (isset($result['error']) && $result['level'] < 3) { + $issues['errors'][$result['level']]['value']++; + if ($result['level'] < $issues['overallHealth']) { + $issues['overallHealth'] = $result['level']; } - $tabs[$result['tab']]['count']++; - if (isset($result['error']) && $result['level'] < 3) { - $issues['errors'][$result['level']]['value']++; - if ($result['level'] < $issues['overallHealth']) { - $issues['overallHealth'] = $result['level']; - } - $tabs[$result['tab']]['errors']++; - if ($result['level'] < $tabs[$result['tab']]['severity']) { - $tabs[$result['tab']]['severity'] = $result['level']; - } - } - if (isset($result['optionsSource']) && is_callable($result['optionsSource'])) { - $result['options'] = $result['optionsSource'](); - } - $dumpResults[] = $result; - if ($result['tab'] == $tab) { - if (isset($result['subGroup'])) { - $tempArray[$result['subGroup']][] = $result; - } else { - $tempArray['general'][] = $result; - } + $tabs[$result['tab']]['errors']++; + if ($result['level'] < $tabs[$result['tab']]['severity']) { + $tabs[$result['tab']]['severity'] = $result['level']; } } - $finalSettings = $tempArray; - // Diagnostics portion - $diagnostic_errors = 0; - App::uses('File', 'Utility'); - App::uses('Folder', 'Utility'); - if ($tab === 'files') { - $files = $this->__manageFiles(); - $this->set('files', $files); + if (isset($result['optionsSource']) && is_callable($result['optionsSource'])) { + $result['options'] = $result['optionsSource'](); } - // Only run this check on the diagnostics tab - if ($tab === 'diagnostics' || $tab === 'download' || $this->_isRest()) { - $php_ini = php_ini_loaded_file(); - $this->set('php_ini', $php_ini); - - $attachmentTool = new AttachmentTool(); - try { - $advanced_attachments = $attachmentTool->checkAdvancedExtractionStatus($this->Server->getPythonVersion()); - } catch (Exception $e) { - $this->log($e->getMessage(), LOG_NOTICE); - $advanced_attachments = false; + $dumpResults[] = $result; + if ($result['tab'] == $tab) { + if (isset($result['subGroup'])) { + $tempArray[$result['subGroup']][] = $result; + } else { + $tempArray['general'][] = $result; } - - $this->set('advanced_attachments', $advanced_attachments); - // check if the current version of MISP is outdated or not - $version = $this->__checkVersion(); - $this->set('version', $version); - $gitStatus = $this->Server->getCurrentGitStatus(); - $this->set('branch', $gitStatus['branch']); - $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' => 'seconds', - ), - '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' => 'MB' - ), - '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' => 'MB' - ), - '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' => 'MB' - ) - ); - - foreach ($phpSettings as $setting => $settingArray) { - $phpSettings[$setting]['value'] = $this->Server->getIniSetting($setting); - if ($phpSettings[$setting]['value'] && $settingArray['unit'] && $settingArray['unit'] === 'MB') { - // convert basic unit to M - $phpSettings[$setting]['value'] = (int) floor($phpSettings[$setting]['value'] / 1024 / 1024); - } - } - $this->set('phpSettings', $phpSettings); - - if ($version && (!$version['upToDate'] || $version['upToDate'] == 'older')) { - $diagnostic_errors++; - } - - // check if the STIX and Cybox libraries are working and the correct version using the test script stixtest.py - $stix = $this->Server->stixDiagnostics($diagnostic_errors, $stixVersion, $cyboxVersion, $mixboxVersion, $maecVersion, $stix2Version, $pymispVersion); - - $yaraStatus = $this->Server->yaraDiagnostics($diagnostic_errors); - - // if GnuPG is set up in the settings, try to encrypt a test message - $gpgStatus = $this->Server->gpgDiagnostics($diagnostic_errors); - - // if the message queue pub/sub is enabled, check whether the extension works - $zmqStatus = $this->Server->zmqDiagnostics($diagnostic_errors); - - // if Proxy is set up in the settings, try to connect to a test URL - $proxyStatus = $this->Server->proxyDiagnostics($diagnostic_errors); - - // get the DB diagnostics - $dbDiagnostics = $this->Server->dbSpaceUsage(); - $dbSchemaDiagnostics = $this->Server->dbSchemaDiagnostic(); - - $redisInfo = $this->Server->redisInfo(); - - $moduleTypes = array('Enrichment', 'Import', 'Export', 'Cortex'); - foreach ($moduleTypes as $type) { - $moduleStatus[$type] = $this->Server->moduleDiagnostics($diagnostic_errors, $type); - } - - // check the size of the session table - $sessionCount = 0; - $sessionStatus = $this->Server->sessionDiagnostics($diagnostic_errors, $sessionCount); - $this->set('sessionCount', $sessionCount); - - $this->loadModel('AttachmentScan'); - try { - $attachmentScan = ['status' => true, 'software' => $this->AttachmentScan->diagnostic()]; - } catch (Exception $e) { - $attachmentScan = ['status' => false, 'error' => $e->getMessage()]; - } - - $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); - $writeableFiles = $this->Server->writeableFilesDiagnostics($diagnostic_errors); - $readableFiles = $this->Server->readableFilesDiagnostics($diagnostic_errors); - $extensions = $this->Server->extensionDiagnostics(); - - // check if the encoding is not set to utf8 - $dbEncodingStatus = $this->Server->databaseEncodingDiagnostics($diagnostic_errors); - - $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(); - if (Configure::read('MISP.background_jobs')) { - $workerIssueCount = 0; - $worker_array = $this->Server->workerDiagnostics($workerIssueCount); - } - $this->set('worker_array', $worker_array); - if ($tab == 'download' || $this->_isRest()) { - foreach ($dumpResults as $key => $dr) { - unset($dumpResults[$key]['description']); - } - $dump = array( - 'version' => $version, - 'phpSettings' => $phpSettings, - 'gpgStatus' => $gpgErrors[$gpgStatus['status']], - 'proxyStatus' => $proxyErrors[$proxyStatus], - 'zmqStatus' => $zmqStatus, - 'stix' => $stix, - 'moduleStatus' => $moduleStatus, - 'writeableDirs' => $writeableDirs, - 'writeableFiles' => $writeableFiles, - 'readableFiles' => $readableFiles, - 'dbDiagnostics' => $dbDiagnostics, - 'dbSchemaDiagnostics' => $dbSchemaDiagnostics, - 'redisInfo' => $redisInfo, - 'finalSettings' => $dumpResults, - 'extensions' => $extensions, - 'workers' => $worker_array - ); - foreach ($dump['finalSettings'] as $k => $v) { - if (!empty($v['redacted'])) { - $dump['finalSettings'][$k]['value'] = '*****'; - } - } - $this->response->body(json_encode($dump, JSON_PRETTY_PRINT)); - $this->response->type('json'); - $this->response->download('MISP.report.json'); - return $this->response; - } - - $priorities = array(0 => 'Critical', 1 => 'Recommended', 2 => 'Optional', 3 => 'Deprecated'); - $this->set('priorities', $priorities); - $this->set('workerIssueCount', $workerIssueCount); - $priorityErrorColours = array(0 => 'red', 1 => 'yellow', 2 => 'green'); - $this->set('priorityErrorColours', $priorityErrorColours); - $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); } + $finalSettings = $tempArray; + // Diagnostics portion + $diagnostic_errors = 0; + App::uses('File', 'Utility'); + App::uses('Folder', 'Utility'); + if ($tab === 'files') { + $files = $this->Server->grabFiles(); + $this->set('files', $files); + } + // Only run this check on the diagnostics tab + if ($tab === 'diagnostics' || $tab === 'download' || $this->_isRest()) { + $php_ini = php_ini_loaded_file(); + $this->set('php_ini', $php_ini); + + $attachmentTool = new AttachmentTool(); + try { + $advanced_attachments = $attachmentTool->checkAdvancedExtractionStatus($this->Server->getPythonVersion()); + } catch (Exception $e) { + $this->log($e->getMessage(), LOG_NOTICE); + $advanced_attachments = false; + } + + $this->set('advanced_attachments', $advanced_attachments); + // check if the current version of MISP is outdated or not + $version = $this->__checkVersion(); + $this->set('version', $version); + $gitStatus = $this->Server->getCurrentGitStatus(); + $this->set('branch', $gitStatus['branch']); + $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' => 'seconds', + ), + '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' => 'MB' + ), + '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' => 'MB' + ), + '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' => 'MB' + ) + ); + + foreach ($phpSettings as $setting => $settingArray) { + $phpSettings[$setting]['value'] = $this->Server->getIniSetting($setting); + if ($phpSettings[$setting]['value'] && $settingArray['unit'] && $settingArray['unit'] === 'MB') { + // convert basic unit to M + $phpSettings[$setting]['value'] = (int) floor($phpSettings[$setting]['value'] / 1024 / 1024); + } + } + $this->set('phpSettings', $phpSettings); + + if ($version && (!$version['upToDate'] || $version['upToDate'] == 'older')) { + $diagnostic_errors++; + } + + // check if the STIX and Cybox libraries are working and the correct version using the test script stixtest.py + $stix = $this->Server->stixDiagnostics($diagnostic_errors, $stixVersion, $cyboxVersion, $mixboxVersion, $maecVersion, $stix2Version, $pymispVersion); + + $yaraStatus = $this->Server->yaraDiagnostics($diagnostic_errors); + + // if GnuPG is set up in the settings, try to encrypt a test message + $gpgStatus = $this->Server->gpgDiagnostics($diagnostic_errors); + + // if the message queue pub/sub is enabled, check whether the extension works + $zmqStatus = $this->Server->zmqDiagnostics($diagnostic_errors); + + // if Proxy is set up in the settings, try to connect to a test URL + $proxyStatus = $this->Server->proxyDiagnostics($diagnostic_errors); + + // get the DB diagnostics + $dbDiagnostics = $this->Server->dbSpaceUsage(); + $dbSchemaDiagnostics = $this->Server->dbSchemaDiagnostic(); + + $redisInfo = $this->Server->redisInfo(); + + $moduleTypes = array('Enrichment', 'Import', 'Export', 'Cortex'); + foreach ($moduleTypes as $type) { + $moduleStatus[$type] = $this->Server->moduleDiagnostics($diagnostic_errors, $type); + } + + // check the size of the session table + $sessionCount = 0; + $sessionStatus = $this->Server->sessionDiagnostics($diagnostic_errors, $sessionCount); + $this->set('sessionCount', $sessionCount); + + $this->loadModel('AttachmentScan'); + try { + $attachmentScan = ['status' => true, 'software' => $this->AttachmentScan->diagnostic()]; + } catch (Exception $e) { + $attachmentScan = ['status' => false, 'error' => $e->getMessage()]; + } + + $securityAudit = (new SecurityAudit())->run($this->Server); + + $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', 'securityAudit'); + } else { + $view = []; + } + + // check whether the files are writeable + $writeableDirs = $this->Server->writeableDirsDiagnostics($diagnostic_errors); + $writeableFiles = $this->Server->writeableFilesDiagnostics($diagnostic_errors); + $readableFiles = $this->Server->readableFilesDiagnostics($diagnostic_errors); + $extensions = $this->Server->extensionDiagnostics(); + + // check if the encoding is not set to utf8 + $dbEncodingStatus = $this->Server->databaseEncodingDiagnostics($diagnostic_errors); + + $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(); + if (Configure::read('MISP.background_jobs')) { + $workerIssueCount = 0; + $worker_array = $this->Server->workerDiagnostics($workerIssueCount); + } + $this->set('worker_array', $worker_array); + if ($tab === 'download' || $this->_isRest()) { + foreach ($dumpResults as $key => $dr) { + unset($dumpResults[$key]['description']); + } + $dump = array( + 'version' => $version, + 'phpSettings' => $phpSettings, + 'gpgStatus' => $gpgErrors[$gpgStatus['status']], + 'proxyStatus' => $proxyErrors[$proxyStatus], + 'zmqStatus' => $zmqStatus, + 'stix' => $stix, + 'moduleStatus' => $moduleStatus, + 'writeableDirs' => $writeableDirs, + 'writeableFiles' => $writeableFiles, + 'readableFiles' => $readableFiles, + 'dbDiagnostics' => $dbDiagnostics, + 'dbSchemaDiagnostics' => $dbSchemaDiagnostics, + 'redisInfo' => $redisInfo, + 'finalSettings' => $dumpResults, + 'extensions' => $extensions, + 'workers' => $worker_array + ); + foreach ($dump['finalSettings'] as $k => $v) { + if (!empty($v['redacted'])) { + $dump['finalSettings'][$k]['value'] = '*****'; + } + } + $this->response->body(json_encode($dump, JSON_PRETTY_PRINT)); + $this->response->type('json'); + $this->response->download('MISP.report.json'); + return $this->response; + } + + $priorities = array(0 => 'Critical', 1 => 'Recommended', 2 => 'Optional', 3 => 'Deprecated'); + $this->set('priorities', $priorities); + $this->set('workerIssueCount', $workerIssueCount); + $priorityErrorColours = array(0 => 'red', 1 => 'yellow', 2 => 'green'); + $this->set('priorityErrorColours', $priorityErrorColours); + $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); } public function startWorker($type) @@ -1250,9 +1252,6 @@ class ServersController extends AppController private function __checkVersion() { - if (!$this->_isSiteAdmin()) { - throw new MethodNotAllowedException(); - } App::uses('SyncTool', 'Tools'); $syncTool = new SyncTool(); try { @@ -1365,10 +1364,8 @@ class ServersController extends AppController $this->set('title_for_layout', __('Event ID translator')); } - public function getSubmodulesStatus() { - if (!$this->_isSiteAdmin()) { - throw new MethodNotAllowedException(); - } + public function getSubmodulesStatus() + { $this->set('submodules', $this->Server->getSubmodulesGitStatus()); $this->render('ajax/submoduleStatus'); } @@ -1387,9 +1384,6 @@ class ServersController extends AppController public function serverSettingsEdit($setting_name, $id = false, $forceSave = false) { - if (!$this->_isSiteAdmin()) { - throw new MethodNotAllowedException(); - } if (!isset($setting_name)) { throw new MethodNotAllowedException(); } @@ -1521,15 +1515,6 @@ class ServersController extends AppController $this->redirect(array('controller' => 'servers', 'action' => 'serverSettings', 'workers')); } - private function __manageFiles() - { - if (!$this->_isSiteAdmin()) { - throw new MethodNotAllowedException(); - } - $files = $this->Server->grabFiles(); - return $files; - } - public function deleteFile($type, $filename) { if (!$this->_isSiteAdmin()) { @@ -1703,23 +1688,17 @@ class ServersController extends AppController $result['status'] = 8; return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json')); } - return new CakeResponse( - array( - 'body'=> json_encode( - array( - 'status' => 1, - 'local_version' => implode('.', $local_version), - 'version' => implode('.', $version), - 'mismatch' => $mismatch, - 'newer' => $newer, - 'post' => isset($post) ? $post['status'] : 'too old', - 'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null, - 'client_certificate' => $result['client_certificate'], - ) - ), - 'type' => 'json' - ) - ); + return $this->RestResponse->viewData([ + 'status' => 1, + 'local_version' => implode('.', $local_version), + 'version' => implode('.', $version), + 'mismatch' => $mismatch, + 'newer' => $newer, + 'post' => isset($post) ? $post['status'] : 'too old', + 'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null, + 'request_encoding' => isset($result['info']['request_encoding']) ? $result['info']['request_encoding'] : null, + 'client_certificate' => $result['client_certificate'], + ], 'json'); } else { $result['status'] = 3; } @@ -1799,17 +1778,15 @@ class ServersController extends AppController public function getVersion() { - if (!$this->userRole['perm_auth']) { - throw new MethodNotAllowedException('This action requires API access.'); - } $versionArray = $this->Server->checkMISPVersion(); - $this->set('response', array( + $response = [ 'version' => $versionArray['major'] . '.' . $versionArray['minor'] . '.' . $versionArray['hotfix'], 'perm_sync' => $this->userRole['perm_sync'], 'perm_sighting' => $this->userRole['perm_sighting'], 'perm_galaxy_editor' => $this->userRole['perm_galaxy_editor'], - )); - $this->set('_serialize', 'response'); + 'request_encoding' => $this->CompressedRequestHandler->supportedEncodings(), + ]; + return $this->RestResponse->viewData($response, $this->response->type()); } public function getPyMISPVersion() diff --git a/app/Controller/TaxonomiesController.php b/app/Controller/TaxonomiesController.php index 6b9be18d8..95f611b09 100644 --- a/app/Controller/TaxonomiesController.php +++ b/app/Controller/TaxonomiesController.php @@ -101,14 +101,15 @@ class TaxonomiesController extends AppController $params = $customPagination->applyRulesOnArray($taxonomy['entries'], $params, 'taxonomies'); if ($this->_isRest()) { return $this->RestResponse->viewData($taxonomy, $this->response->type()); - } else { - $this->set('entries', $taxonomy['entries']); - $this->set('urlparams', $urlparams); - $this->set('passedArgs', json_encode($passedArgs)); - $this->set('passedArgsArray', $passedArgs); - $this->set('taxonomy', $taxonomy['Taxonomy']); - $this->set('id', $id); } + + $this->set('entries', $taxonomy['entries']); + $this->set('urlparams', $urlparams); + $this->set('passedArgs', json_encode($passedArgs)); + $this->set('passedArgsArray', $passedArgs); + $this->set('taxonomy', $taxonomy['Taxonomy']); + $this->set('id', $id); + $this->set('title_for_layout', __('%s Taxonomy Library', h(strtoupper($taxonomy['Taxonomy']['namespace'])))); } public function enable($id) diff --git a/app/Lib/Tools/SecurityAudit.php b/app/Lib/Tools/SecurityAudit.php new file mode 100644 index 000000000..f97aaeed7 --- /dev/null +++ b/app/Lib/Tools/SecurityAudit.php @@ -0,0 +1,505 @@ +config['password']; + if (empty($databasePassword)) { + $output['Database'][] = ['error', __('Database password not set.')]; + } else if (strlen($databasePassword) < self::STRONG_PASSWORD_LENGTH) { + $output['Database'][] = ['warning', __('Database password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)]; + } + + $passwordPolicyLength = Configure::read('Security.password_policy_length') ?: $server->serverSettings['Security']['password_policy_length']['value']; + if ($passwordPolicyLength < 8) { + $output['Password'][] = ['error', __('Minimum password length is set to %s, that too short.', $passwordPolicyLength)]; + } elseif ($passwordPolicyLength < 12) { + $output['Password'][] = ['warning', __('Minimum password length is set to %s, consider raising to at least 12 characters.', $passwordPolicyLength)]; + } + + if (empty(Configure::read('Security.require_password_confirmation'))) { + $output['Password'][] = [ + 'warning', + __('Password confirmation is not enabled. %s', $server->serverSettings['Security']['require_password_confirmation']['description']), + ]; + } + if (!empty(Configure::read('Security.auth')) && !Configure::read('Security.auth_enforced')) { + $output['Login'][] = [ + 'hint', + __('External authentication is enabled, but local accounts will still work. You can disable logging with local accounts by setting `Security.auth_enforced` to `true`.'), + ]; + } + + if (empty(Configure::read('Security.disable_browser_cache'))) { + $output['Browser'][] = [ + 'warning', + __('Browser cache is enabled. Attacker can obtain sensitive data from user cache. You can disable cache by setting `Security.disable_browser_cache` to `false`.'), + ]; + } + if (empty(Configure::read('Security.check_sec_fetch_site_header'))) { + $output['Browser'][] = [ + 'warning', + __('MISP server is not checking `Sec-Fetch` HTTP headers. This is protection against CSRF for moder browsers. You can enable this checks by setting `Security.check_sec_fetch_site_header` to `true`.'), + ]; + } + if (Configure::read('Security.disable_form_security')) { + $output['Browser'][] = ['error', __('Disabling form security is never a good idea.')]; + } + + if (empty(Configure::read('Security.advanced_authkeys'))) { + $output['Auth Key'][] = ['warning', __('Consider enabling Advanced Auth Keys, that provides higher security.')]; + } + if (Configure::read('Security.allow_unsafe_apikey_named_param')) { + $output['Auth Key'][] = ['error', __('It is possible to pass API key in URL, so the key can be logged by proxies.')]; + } + if (empty(Configure::read('Security.do_not_log_authkeys'))) { + $output['Auth Key'][] = ['warning', __('Auth Key logging is not disabled. Auth Keys in cleartext can be visible in Audit log.')]; + } + + $salt = Configure::read('Security.salt'); + if (empty($salt)) { + $output['Security salt'][] = ['error', __('Salt is not set.')]; + } else if (strlen($salt) < 32) { + $output['Security salt'][] = ['warning', __('Salt is too short, should contains at least 32 characters.')]; + } else if ($salt === "Rooraenietu8Eeyoemail($output); + + if (!Configure::read('Security.hide_organisation_index_from_users')) { + $output['MISP'][] = [ + 'hint', + __('Any user can see list of all organisations. You can disable that by setting `Security.hide_organisation_index_from_users` to `true`. %s', $server->serverSettings['Security']['hide_organisation_index_from_users']['description']), + ]; + } + if (!Configure::read('Security.hide_organisations_in_sharing_groups')) { + $output['MISP'][] = [ + 'hint', + __('Any user can see list of all organisations in sharing group that user can see. You can disable that by setting `Security.hide_organisations_in_sharing_groups` to `true`. %s', $server->serverSettings['Security']['hide_organisations_in_sharing_groups']['description']), + ]; + } + + $this->feeds($output); + $this->remoteServers($output); + + try { + $cakeVersion = $this->getCakeVersion(); + if (version_compare($cakeVersion, '2.10.21', '<')) { + $output['Dependencies'][] = ['warning', __('CakePHP version %s is outdated.', $cakeVersion)]; + } + } catch (RuntimeException $e) {} + + if (version_compare(PHP_VERSION, '7.3.0', '<')) { + $output['PHP'][] = [ + 'warning', + __('PHP version %s is not supported anymore. It can be still supported by your distribution. ', PHP_VERSION), + 'https://www.php.net/supported-versions.php' + ]; + } else if (version_compare(PHP_VERSION, '7.4.0', '<')) { + $output['PHP'][] = [ + 'hint', + __('PHP version 7.3 will be not supported after 6 Dec 2021. Event after that date can be still supported by your distribution.'), + 'https://www.php.net/supported-versions.php' + ]; + } + if (ini_get('session.use_strict_mode') != 1) { + $output['PHP'][] = [ + 'warning', + __('Session strict mode is disable.'), + 'https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode', + ]; + } + if (empty(ini_get('session.cookie_httponly'))) { + $output['PHP'][] = ['error', __('Session cookie is not set as HTTP only. Session cookie can be access from JavaScript.')]; + } + if (!in_array(strtolower(ini_get('session.cookie_samesite')), ['strict', 'lax'])) { + $output['PHP'][] = [ + 'error', + __('Session cookie SameSite parameter is not defined or set to None.'), + 'https://developer.mozilla.org/en-us/docs/Web/HTTP/Headers/Set-Cookie/SameSite', + ]; + } + $sidLength = ini_get('session.sid_length'); + if ($sidLength !== false && $sidLength < 32) { + $output['PHP'][] = [ + 'warning', + __('Session ID length is set to %s, at least 32 is recommended.', $sidLength), + 'https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length', + ]; + } + $sidBits = ini_get('session.sid_bits_per_character'); + if ($sidBits !== false && $sidBits <= 4) { + $output['PHP'][] = [ + 'warning', + __('Session ID bit per character is set to %s, at least 5 is recommended.', $sidBits), + 'https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character', + ]; + } + + $this->system($server, $output); + + return $output; + } + + private function feeds(array &$output) + { + /** @var Feed $feed */ + $feed = ClassRegistry::init('Feed'); + $enabledFeeds = $feed->find('list', [ + 'conditions' => [ + 'input_source' => 'network', + 'OR' => [ + 'enabled' => true, + 'caching_enabled' => true, + ] + ], + 'fields' => ['name', 'url'], + ]); + foreach ($enabledFeeds as $feedName => $feedUrl) { + if (substr($feedUrl, 0, strlen('http://')) === 'http://') { + $output['Feeds'][] = ['warning', __('Feed %s uses insecure (HTTP) connection.', $feedName)]; + } + } + } + + private function remoteServers(array &$output) + { + /** @var Server $server */ + $server = ClassRegistry::init('Server'); + $enabledServers = $server->find('all', [ + 'conditions' => ['OR' => [ + 'push' => true, + 'pull' => true, + 'push_sightings' => true, + 'caching_enabled' => true, + ]], + 'fields' => ['id', 'name', 'url', 'self_signed', 'cert_file', 'client_cert_file'], + ]); + foreach ($enabledServers as $enabledServer) { + if (substr($enabledServer['Server']['url'], 0, strlen('http://')) === 'http://') { + $output['Remote servers'][] = ['warning', __('Server %s uses insecure (HTTP) connection.', $enabledServer['Server']['name'])]; + } else if ($enabledServer['Server']['self_signed']) { + $output['Remote servers'][] = ['warning', __('Server %s uses self signed certificate. This is considered insecure.', $enabledServer['Server']['name'])]; + } + + try { + $parsed = SyncTool::getServerClientCertificateInfo($enabledServer); + if (isset($parsed['public_key_size_ok']) && !$parsed['public_key_size_ok']) { + $algo = $parsed['public_key_type'] . " " . $parsed['public_key_size']; + $output['Remote servers'][] = ['warning', __('Server %s uses weak client certificate (%s).', $enabledServer['Server']['name'], $algo)]; + } + } catch (Exception $e) {} + + try { + $parsed = SyncTool::getServerCaCertificateInfo($enabledServer); + if (isset($parsed['public_key_size_ok']) && !$parsed['public_key_size_ok']) { + $algo = $parsed['public_key_type'] . " " . $parsed['public_key_size']; + $output['Remote servers'][] = ['warning', __('Server %s uses weak CA certificate (%s).', $enabledServer['Server']['name'], $algo)]; + } + } catch (Exception $e) {} + } + } + + private function email(array &$output) + { + $canSignPgp = Configure::read('GnuPG.sign'); + $canSignSmime = Configure::read('SMIME.enabled') && + !empty(Configure::read('SMIME.cert_public_sign')) && + !empty(Configure::read('SMIME.key_sign')); + + if (!$canSignPgp && !$canSignSmime) { + $output['Email'][] = [ + 'warning', + __('Email signing (PGP or S/MIME) is not enabled.') + ]; + } + + if ($canSignPgp) { + $gpgKeyPassword = Configure::read('GnuPG.password'); + if (empty($gpgKeyPassword)) { + $output['Email'][] = ['error', __('PGP private key password is empty.')]; + } else if (strlen($gpgKeyPassword) < self::STRONG_PASSWORD_LENGTH) { + $output['Email'][] = ['warning', __('PGP private key password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)]; + } + } + + if (!Configure::read('GnuPG.bodyonlyencrypted')) { + $output['Email'][] = [ + 'hint', + __('Full email body with all event information will be send even without encryption.') + ]; + } + + if ($canSignPgp && !Configure::read('GnuPG.obscure_subject')) { + $output['Email'][] = [ + 'hint', + __('Even for encrypted emails, email subject will be send unencrypted. You can change that behaviour by setting `GnuPG.obscure_subject` to `true`.'), + ]; + } + + App::uses('CakeEmail', 'Network/Email'); + $email = new CakeEmail(); + $emailConfig = $email->config(); + if ($emailConfig['transport'] === 'Smtp' && $emailConfig['port'] == 25 && !$emailConfig['tls']) { + $output['Email'][] = [ + 'warning', + __('STARTTLS is not enabled.'), + 'https://en.wikipedia.org/wiki/Opportunistic_TLS', + ]; + } + } + + private function system(Server $server, array &$output) + { + $kernelBuildTime = $this->getKernelBuild(); + if ($kernelBuildTime) { + $diff = (new DateTime())->diff($kernelBuildTime); + $diffDays = $diff->format('a'); + if ($diffDays > 300) { + $output['System'][] = [ + 'warning', + __('Kernel build time is since %s days ago. This usually means that the system kernel is not updated.', $diffDays), + ]; + } + } + + // uptime + try { + $since = $this->execute(['uptime', '-s']); + $since = new DateTime($since); + $diff = (new DateTime())->diff($since); + $diffDays = $diff->format('a'); + if ($diffDays > 100) { + $output['System'][] = [ + 'warning', + __('Uptime of this server is %s days. This usually means that the system kernel is outdated.', $diffDays), + ]; + } + } catch (Exception $e) { + } + + // Python version + try { + $pythonVersion = $this->execute([$server->getPythonVersion(), '-V']); + $parts = explode(' ', $pythonVersion); + if ($parts[0] !== 'Python') { + throw new Exception("Invalid python version response: $pythonVersion"); + } + + if (version_compare($parts[1], '3.6', '<')) { + $output['System'][] = [ + 'warning', + __('You are using Python %s. This version is not supported anymore, but it can be still supported by your distribution.'), + 'https://endoflife.date/python', + ]; + } else if (version_compare($parts[1], '3.7', '<')) { + $output['System'][] = [ + 'hint', + __('You are using Python %s. This version will be not supported after 23 Dec 2021, but it can be still supported by your distribution.'), + 'https://endoflife.date/python', + ]; + } + } catch (Exception $e) { + } + + $ubuntuVersion = $this->getUbuntuVersion(); + if ($ubuntuVersion) { + if (in_array($ubuntuVersion, ['14.04', '19.10'])) { + $output['System'][] = [ + 'warning', + __('You are using Ubuntu %s. This version doesn\'t have security support anymore.', $ubuntuVersion), + 'https://endoflife.date/ubuntu', + ]; + } else if (in_array($ubuntuVersion, ['16.04'])) { + $output['System'][] = [ + 'hint', + __('You are using Ubuntu %s. This version will be not supported after 02 Apr 2021.', $ubuntuVersion), + 'https://endoflife.date/ubuntu', + ]; + } + } + } + + private function getKernelBuild() + { + if (!php_uname('s') !== 'Linux') { + return false; + } + + $version = php_uname('v'); + if (substr($version, 0, 7) !== '#1 SMP ') { + return false; + } + $buildTime = substr($version, 7); + return new DateTime($buildTime); + } + + private function getUbuntuVersion() + { + if (!php_uname('s') !== 'Linux') { + return false; + } + if (!is_readable('/etc/os-release')) { + return false; + } + $content = file_get_contents('/etc/os-release'); + if ($content === false) { + return false; + } + $parsed = parse_ini_string($content); + if ($parsed === false) { + return false; + } + if (!isset($parsed['NAME'])) { + return false; + } + if ($parsed['NAME'] !== 'Ubuntu') { + return false; + } + if (!isset($parsed['VERSION_ID'])) { + return false; + } + return $parsed['VERSION_ID']; + } + + /** + * @return string + */ + private function getCakeVersion() + { + $filePath = APP . 'Lib/cakephp/lib/Cake/VERSION.txt'; + $version = file_get_contents($filePath); + if (!$version) { + throw new RuntimeException("Could not open CakePHP version file '$filePath'."); + } + foreach (explode("\n", $version) as $line) { + if ($line[0] === '/') { + continue; + } + return trim($line); + } + throw new RuntimeException("CakePHP version not found in file '$filePath'."); + } + + private function execute(array $command) + { + $descriptorspec = [ + 1 => ["pipe", "w"], // stdout + 2 => ["pipe", "w"], // stderr + ]; + + $command = implode(' ', $command); + $process = proc_open($command, $descriptorspec, $pipes); + if (!$process) { + throw new Exception("Command '$command' could be started."); + } + + $stdout = stream_get_contents($pipes[1]); + if ($stdout === false) { + throw new Exception("Could not get STDOUT of command."); + } + fclose($pipes[1]); + + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + + $returnCode = proc_close($process); + if ($returnCode !== 0) { + throw new Exception("Command '$command' return error code $returnCode. STDERR: '$stderr', STDOUT: '$stdout'"); + } + + return $stdout; + } +} diff --git a/app/Lib/Tools/SyncTool.php b/app/Lib/Tools/SyncTool.php index 4f3d87974..b59a192b6 100644 --- a/app/Lib/Tools/SyncTool.php +++ b/app/Lib/Tools/SyncTool.php @@ -92,6 +92,35 @@ class SyncTool return self::getClientCertificateInfo($certificateContent); } + /** + * @param array $server + * @return array|void + * @throws Exception + */ + public static function getServerCaCertificateInfo(array $server) + { + if (!$server['Server']['cert_file']) { + return; + } + + $caCertificate = new File(APP . "files" . DS . "certs" . DS . $server['Server']['id'] . '.pem'); + if (!$caCertificate->exists()) { + throw new Exception("Certificate file '{$caCertificate->pwd()}' doesn't exists."); + } + + $certificateContent = $caCertificate->read(); + if ($certificateContent === false) { + throw new Exception("Could not read '{$caCertificate->pwd()}' file with certificate."); + } + + $certificate = openssl_x509_read($certificateContent); + if (!$certificate) { + throw new Exception("Couldn't read certificate: " . openssl_error_string()); + } + + return self::parseCertificate($certificate); + } + /** * @param string $certificateContent PEM encoded certificate and private key. * @return array @@ -101,11 +130,11 @@ class SyncTool { $certificate = openssl_x509_read($certificateContent); if (!$certificate) { - throw new Exception("Could't parse certificate: " . openssl_error_string()); + throw new Exception("Couldn't read certificate: " . openssl_error_string()); } $privateKey = openssl_pkey_get_private($certificateContent); if (!$privateKey) { - throw new Exception("Could't get private key from certificate: " . openssl_error_string()); + throw new Exception("Couldn't get private key from certificate: " . openssl_error_string()); } $verify = openssl_x509_check_private_key($certificate, $privateKey); if (!$verify) { @@ -123,7 +152,7 @@ class SyncTool { $parsed = openssl_x509_parse($certificate); if (!$parsed) { - throw new Exception("Could't get parse X.509 certificate: " . openssl_error_string()); + throw new Exception("Couldn't get parse X.509 certificate: " . openssl_error_string()); } $currentTime = new DateTime(); $output = [ diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index a94a38fd9..7c9076944 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -4073,6 +4073,10 @@ class Attribute extends AppModel $attribute['distribution'] = 5; } } + if (isset($attribute['Sighting']) && !empty($attribute['Sighting'])) { + $this->Sighting = ClassRegistry::init('Sighting'); + $this->Sighting->captureSightings($attribute['Sighting'], $attribute['id'], $eventId, $user); + } $fieldList = $this->editableFields; if (empty($existingAttribute)) { $addableFieldList = array('event_id', 'type', 'uuid'); diff --git a/app/Model/Event.php b/app/Model/Event.php index e728ae28e..296ceec5d 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -3640,9 +3640,6 @@ class Event extends AppModel // Low level function to add an Event based on an Event $data array public function _add(array &$data, $fromXml, array $user, $org_id = 0, $passAlong = null, $fromPull = false, $jobId = null, &$created_id = 0, &$validationErrors = array()) { - if ($jobId) { - App::uses('AuthComponent', 'Controller/Component'); - } if (Configure::read('MISP.enableEventBlocklisting') !== false && isset($data['Event']['uuid'])) { $this->EventBlocklist = ClassRegistry::init('EventBlocklist'); if ($this->EventBlocklist->isBlocked($data['Event']['uuid'])) { @@ -3821,6 +3818,12 @@ class Event extends AppModel ); $saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList['Event'])); if ($saveResult) { + if ($jobId) { + /** @var EventLock $eventLock */ + $eventLock = ClassRegistry::init('EventLock'); + $eventLock->insertLockBackgroundJob($this->id, $jobId); + } + if ($passAlong) { if ($server['Server']['publish_without_email'] == 0) { $st = "enabled"; @@ -3950,6 +3953,10 @@ class Event extends AppModel } } } + if ($jobId) { + $eventLock->deleteBackgroundJobLock($this->id, $jobId); + } + return true; } else { $validationErrors['Event'] = $this->validationErrors; @@ -4074,6 +4081,11 @@ class Event extends AppModel $saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList)); $this->Log = ClassRegistry::init('Log'); if ($saveResult) { + if ($jobId) { + /** @var EventLock $eventLock */ + $eventLock = ClassRegistry::init('EventLock'); + $eventLock->insertLockBackgroundJob($data['Event']['id'], $jobId); + } $validationErrors = array(); if (isset($data['Event']['Attribute'])) { $data['Event']['Attribute'] = array_values($data['Event']['Attribute']); @@ -4201,6 +4213,9 @@ class Event extends AppModel } $this->publish($existingEvent['Event']['id']); } + if ($jobId) { + $eventLock->deleteBackgroundJobLock($data['Event']['id'], $jobId); + } return true; } return $this->validationErrors; @@ -4830,32 +4845,25 @@ class Event extends AppModel return false; } - public function removeOlder(&$eventArray, $scope = 'events') + public function removeOlder(array &$events, $scope = 'events') { - if ($scope === 'sightings' ) { - $field = 'sighting_timestamp'; - } else { - $field = 'timestamp'; - } - $uuidsToCheck = array(); - foreach ($eventArray as $k => &$event) { - $uuidsToCheck[$event['uuid']] = $k; - } - $localEvents = array(); - $temp = $this->find('all', array('recursive' => -1, 'fields' => array('Event.uuid', 'Event.' . $field, 'Event.locked'))); - foreach ($temp as $e) { - $localEvents[$e['Event']['uuid']] = array($field => $e['Event'][$field], 'locked' => $e['Event']['locked']); - } - foreach ($uuidsToCheck as $uuid => $eventArrayId) { + $field = $scope === 'sightings' ? 'sighting_timestamp' : 'timestamp'; + $localEvents = $this->find('all', [ + 'recursive' => -1, + 'fields' => ['Event.uuid', 'Event.' . $field, 'Event.locked'], + ]); + $localEvents = array_column(array_column($localEvents, 'Event'), null, 'uuid'); + foreach ($events as $k => $event) { // remove all events for the sighting sync if the remote is not aware of the new field yet - if (!isset($eventArray[$eventArrayId][$field])) { - unset($eventArray[$eventArrayId]); + if (!isset($event[$field])) { + unset($events[$k]); } else { + $uuid = $event['uuid']; if (isset($localEvents[$uuid]) - && ($localEvents[$uuid][$field] >= $eventArray[$eventArrayId][$field] + && ($localEvents[$uuid][$field] >= $event[$field] || ($scope === 'events' && !$localEvents[$uuid]['locked']))) { - unset($eventArray[$eventArrayId]); + unset($events[$k]); } } } @@ -6122,6 +6130,10 @@ class Event extends AppModel $ontheflyattributes = array(); $i = 0; if ($jobId) { + /** @var EventLock $eventLock */ + $eventLock = ClassRegistry::init('EventLock'); + $eventLock->insertLockBackgroundJob($event['Event']['id'], $jobId); + $this->Job = ClassRegistry::init('Job'); $total = count($attributeSources); } @@ -6229,6 +6241,7 @@ class Event extends AppModel $message = $saved . ' ' . $messageScopeSaved . ' created' . $emailResult . '.'; } if ($jobId) { + $eventLock->deleteBackgroundJobLock($event['Event']['id'], $jobId); $this->Job->saveStatus($jobId, true, __('Processing complete. %s', $message)); } if (!empty($returnRawResults)) { diff --git a/app/Model/EventLock.php b/app/Model/EventLock.php index a0fd2807a..790c2b801 100644 --- a/app/Model/EventLock.php +++ b/app/Model/EventLock.php @@ -1,65 +1,132 @@ array( - 'className' => 'User', - 'foreignKey' => 'user_id', - ) - ); - - - public $validate = array( - ); - - public function beforeValidate($options = array()) + /** + * @param array $user + * @param int $eventId + * @return bool True if insert was successful. + */ + public function insertLock(array $user, $eventId) { - parent::beforeValidate(); - return true; + return $this->insertLockToRedis("misp:event_lock:$eventId:user:{$user['id']}", [ + 'type' => 'user', + 'timestamp' => time(), + 'User' => [ + 'id' => $user['id'], + 'org_id' => $user['org_id'], + 'email' => $user['email'], + ] + ]); } - public function insertLock($user, $eventId) + /** + * @param int $eventId + * @param int $jobId + * @return bool True if insert was successful. + */ + public function insertLockBackgroundJob($eventId, $jobId) { - $date = new DateTime(); - $lock = array( - 'timestamp' => $date->getTimestamp(), + return $this->insertLockToRedis("misp:event_lock:$eventId:job:$jobId", [ + 'type' => 'job', + 'timestamp' => time(), + 'job_id' => $jobId, + ]); + } + + /** + * @param int $eventId + * @return int|null Lock ID + */ + public function insertLockApi($eventId, array $user) + { + $rand = mt_rand(); + if ($this->insertLockToRedis("misp:event_lock:$eventId:api:{$user['id']}:$rand", [ + 'type' => 'api', 'user_id' => $user['id'], - 'event_id' => $eventId - ); - $this->deleteAll(array('user_id' => $user['id'])); - $this->create(); - return $this->save($lock); + 'timestamp' => time(), + ])) { + return $rand; + } + return null; } - public function checkLock($user, $eventId) + /** + * @param int $eventId + * @param int $jobId + * @return null + */ + public function deleteBackgroundJobLock($eventId, $jobId) { - $this->cleanupLock($user, $eventId); - $locks = $this->find('all', array( - 'recursive' => -1, - 'contain' => array('User.email', 'User.org_id', 'User.id'), - 'conditions' => array( - 'event_id' => $eventId - ) - )); - return $locks; + try { + $redis = $this->setupRedisWithException(); + } catch (Exception $e) { + return false; + } + + $deleted = $redis->del("misp:event_lock:$eventId:job:$jobId"); + return $deleted > 0; } - // If a lock has been active for 15 minutes, delete it - public function cleanupLock() + /** + * @param string $key + * @param array $data + * @return bool + */ + private function insertLockToRedis($key, array $data) { - $date = new DateTime(); - $timestamp = $date->getTimestamp(); - $timestamp -= 900; - $this->deleteAll(array('timestamp <' => $timestamp)); - return true; + try { + $redis = $this->setupRedisWithException(); + } catch (Exception $e) { + return false; + } + + return $redis->setex($key, self::DEFAULT_TTL, json_encode($data)); + } + + /** + * @param int $eventId + * @param int $lockId + * @return bool + */ + public function deleteApiLock($eventId, $lockId, array $user) + { + try { + $redis = $this->setupRedisWithException(); + } catch (Exception $e) { + return false; + } + + $deleted = $redis->del("misp:event_lock:$eventId:api:{$user['id']}:$lockId"); + return $deleted > 0; + } + + /** + * @param array $user + * @param int $eventId + * @return array[] + * @throws JsonException + */ + public function checkLock(array $user, $eventId) + { + try { + $redis = $this->setupRedisWithException(); + } catch (Exception $e) { + return []; + } + + $keys = $redis->keys("misp:event_lock:$eventId:*"); + if (empty($keys)) { + return []; + } + + return array_map(function ($value) { + return $this->jsonDecode($value); + }, $redis->mget($keys)); } } diff --git a/app/Model/Feed.php b/app/Model/Feed.php index 8bbfb36a2..4d9cdb8af 100644 --- a/app/Model/Feed.php +++ b/app/Model/Feed.php @@ -997,7 +997,7 @@ class Feed extends AppModel * @return bool * @throws Exception */ - public function saveFreetextFeedData($feed, $data, $user, $jobId = false) + public function saveFreetextFeedData(array $feed, array $data, array $user, $jobId = false) { $this->Event = ClassRegistry::init('Event'); @@ -1040,25 +1040,14 @@ class Feed extends AppModel } } if ($feed['Feed']['fixed_event']) { - $existsAttributes = $this->Event->Attribute->find('all', array( + $existsAttributesValueToId = $this->Event->Attribute->find('list', array( 'conditions' => array( 'Attribute.deleted' => 0, 'Attribute.event_id' => $event['Event']['id'] ), 'recursive' => -1, - 'fields' => array('id', 'value1', 'value2') + 'fields' => array('value', 'id') )); - $existsAttributesValueToId = array(); - foreach ($existsAttributes as $t) { - if (!empty($t['Attribute']['value2'])) { - $value = $t['Attribute']['value1'] . '|' . $t['Attribute']['value2']; - } else { - $value = $t['Attribute']['value1']; - } - // Since event values are unique, it is OK to put value into key - $existsAttributesValueToId[$value] = $t['Attribute']['id']; - } - unset($existsAttributes); // Create event diff. After this cycle, `$data` will contains just attributes that do not exists in current // event and in `$existsAttributesValueToId` will contains just attributes that do not exists in current feed. @@ -1098,7 +1087,6 @@ class Feed extends AppModel return true; } - $data = array_values($data); $uniqueValues = array(); foreach ($data as $key => $value) { if (isset($uniqueValues[$value['value']])) { diff --git a/app/Model/Post.php b/app/Model/Post.php index ea3315a57..aba8994c5 100644 --- a/app/Model/Post.php +++ b/app/Model/Post.php @@ -1,8 +1,10 @@ User->findById($user_id); @@ -38,7 +40,7 @@ class Post extends AppModel 'status' => 0, 'retries' => 0, 'org_id' => $user['User']['org_id'], - 'message' => 'Sending..', + 'message' => 'Sending...', ); $job->save($data); $jobId = $job->id; @@ -51,50 +53,90 @@ class Post extends AppModel $job->saveField('process_id', $process_id); return true; } else { - $result = $this->sendPostsEmail($user_id, $post_id, $event_id, $title, $message); - return $result; + return $this->sendPostsEmail($user_id, $post_id, $event_id, $title, $message); } } - public function sendPostsEmail($user_id, $post_id, $event_id, $title, $message) + /** + * @param int $userId + * @param int $postId + * @param int|null $eventId + * @param string $title + * @param string $message + * @return bool + * @throws Exception + */ + public function sendPostsEmail($userId, $postId, $eventId, $title, $message) { - // fetch the post - $post = $this->read(null, $post_id); - $this->User = ClassRegistry::init('User'); + $post = $this->find('first', [ + 'recursive' => -1, + 'conditions' => ['id' => $postId], + 'fields' => ['id', 'thread_id'], + ]); + if (empty($post)) { + throw new Exception("Post with ID $postId not found."); + } + + $userFields = ['id', 'email', 'gpgkey', 'certif_public', 'disabled']; // If the post belongs to an event, E-mail all users in the org that have contactalert set - if ($event_id) { + if ($eventId) { $this->Event = ClassRegistry::init('Event'); - ; - $event = $this->Event->read(null, $event_id); - //Insert extra field here: alertOrg or something, then foreach all the org members - //limit this array to users with contactalerts turned on! - $orgMembers = array(); - $this->User->recursive = -1; - $temp = $this->User->findAllByOrgId($event['Event']['org_id'], array('email', 'gpgkey', 'certif_public', 'contactalert', 'id')); - foreach ($temp as $tempElement) { - if ($tempElement['User']['id'] != $user_id && ($tempElement['User']['contactalert'] || $tempElement['User']['id'] == $event['Event']['user_id'])) { - array_push($orgMembers, $tempElement); - } + $event = $this->Event->find('first', [ + 'recursive' => -1, + 'conditions' => ['id' => $eventId], + 'fields' => ['id', 'org_id', 'user_id'], + ]); + if (empty($event)) { + throw new Exception("Event with ID $eventId not found."); } + // Insert extra field here: alertOrg or something, then foreach all the org members + // limit this array to users with contactalerts turned on! + $orgMembers = $this->User->find('all', [ + 'recursive' => -1, + 'conditions' => [ + 'org_id' => $event['Event']['org_id'], + 'disabled' => 0, + 'NOT' => ['id' => $userId], // do not send to post creator + 'OR' => [ // send just to users with contactalert or to event creator + 'contactalert' => 1, + 'id' => $event['Event']['user_id'], + ], + ], + 'fields' => $userFields, + ]); } else { // Not an event: E-mail the user that started the thread $thread = $this->Thread->read(null, $post['Post']['thread_id']); - if ($thread['Thread']['user_id'] == $user_id) { + if ($thread['Thread']['user_id'] == $userId) { $orgMembers = array(); } else { - $orgMembers = $this->User->findAllById($thread['Thread']['user_id'], array('email', 'gpgkey', 'certif_public', 'contactalert', 'id')); + $orgMembers = $this->User->find('all', [ + 'recursive' => -1, + 'fields' => $userFields, + 'conditions' => [ + 'id' => $thread['Thread']['user_id'], + 'disabled' => 0, + ] + ]); } } // Add all users who posted in this thread - $temp = $this->findAllByThreadId($post['Post']['thread_id'], array('user_id')); - foreach ($temp as $tempElement) { - $user = $this->User->findById($tempElement['Post']['user_id'], array('email', 'gpgkey', 'certif_public', 'contactalert', 'id')); - if (!empty($user) && $user['User']['id'] != $user_id && !in_array($user, $orgMembers)) { - array_push($orgMembers, $user); - } - } + $excludeUsers = Hash::extract($orgMembers, '{n}.User.id'); + $excludeUsers[] = $userId; + $temp = $this->find('all', [ + 'recursive' => -1, + 'fields' => ['Post.id'], + 'conditions' => [ + 'Post.thread_id' => $post['Post']['thread_id'], + 'User.disabled' => 0, + 'NOT' => ['User.id' => $excludeUsers] + ], + 'contain' => ['User' => ['fields' => $userFields]], + 'group' => ['User.id'], // remove duplicates + ]); + $orgMembers = array_merge($orgMembers, $temp); // The mail body, h() is NOT needed as we are sending plain-text mails. $body = ""; @@ -118,11 +160,14 @@ class Post extends AppModel $bodyDetail .= "The following message was added: \n"; $bodyDetail .= "\n"; $bodyDetail .= $message . "\n"; + $tplColorString = Configure::read('MISP.email_subject_TLP_string') ?: "tlp:amber"; $subject = "[" . Configure::read('MISP.org') . " MISP] New post in discussion " . $post['Post']['thread_id'] . " - " . strtoupper($tplColorString); foreach ($orgMembers as $recipient) { $this->User->sendEmail($recipient, $bodyDetail, $body, $subject); } + + return true; } public function findPageNr($id, $context = 'thread', $post_id = false) diff --git a/app/Model/Server.php b/app/Model/Server.php index d868cc129..0f0cde2ad 100644 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -3767,17 +3767,14 @@ class Server extends AppModel return true; } - public function getLatestGitRemote() - { - return exec('timeout 3 git ls-remote https://github.com/MISP/MISP | head -1 | sed "s/HEAD//"'); - } - public function getCurrentGitStatus() { + $latestCommit = exec('timeout 3 git ls-remote https://github.com/MISP/MISP | head -1 | sed "s/HEAD//"'); + $status = array(); $status['commit'] = exec('git rev-parse HEAD'); $status['branch'] = $this->getCurrentBranch(); - $status['latestCommit'] = $this->getLatestGitremote(); + $status['latestCommit'] = $latestCommit; return $status; } diff --git a/app/Model/ServerTag.php b/app/Model/ServerTag.php deleted file mode 100644 index 06e001647..000000000 --- a/app/Model/ServerTag.php +++ /dev/null @@ -1,43 +0,0 @@ - array( - 'valueNotEmpty' => array( - 'rule' => array('valueNotEmpty'), - ), - ), - 'tag_id' => array( - 'valueNotEmpty' => array( - 'rule' => array('valueNotEmpty'), - ), - ), - ); - - public $belongsTo = array( - 'Server', - 'Tag' - ); - - public function attachTagToServer($server_id, $tag_id) - { - $existingAssociation = $this->find('first', array( - 'recursive' => -1, - 'conditions' => array( - 'tag_id' => $tag_id, - 'server_id' => $server_id - ) - )); - if (empty($existingAssociation)) { - $this->create(); - if (!$this->save(array('server_id' => $server_id, 'tag_id' => $tag_id))) { - return false; - } - } - return true; - } -} diff --git a/app/Model/Sighting.php b/app/Model/Sighting.php index 03c833c55..534cbda92 100644 --- a/app/Model/Sighting.php +++ b/app/Model/Sighting.php @@ -154,6 +154,8 @@ class Sighting extends AppModel } else { $orgId = $this->Organisation->captureOrg($sighting['Organisation'], $user); } + } else if (isset($user['org_id'])) { + $orgId = $user['org_id']; } unset($sighting['id']); diff --git a/app/Model/User.php b/app/Model/User.php index 4395d88c0..e8f70527d 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -7,6 +7,8 @@ App::uses('SendEmail', 'Tools'); /** * @property Log $Log + * @property Organisation $Organisation + * @property Role $Role */ class User extends AppModel { diff --git a/app/Plugin/OidcAuth/Controller/Component/Auth/OidcAuthenticate.php b/app/Plugin/OidcAuth/Controller/Component/Auth/OidcAuthenticate.php new file mode 100644 index 000000000..2b8cb2cb4 --- /dev/null +++ b/app/Plugin/OidcAuth/Controller/Component/Auth/OidcAuthenticate.php @@ -0,0 +1,244 @@ +prepareClient(); + + if (!$oidc->authenticate()) { + throw new Exception("OIDC authentication was not successful."); + } + + $mispUsername = $oidc->requestUserInfo('email'); + $this->log($mispUsername, "Trying login"); + + $verifiedClaims = $oidc->getVerifiedClaims(); + $organisationProperty = $this->getConfig('organisation_property', 'organization'); + if (property_exists($verifiedClaims, $organisationProperty)) { + $organisationName = $verifiedClaims->{$organisationProperty}; + } else { + $organisationName = $this->getConfig('default_org'); + } + + $roles = []; + $roleProperty = $this->getConfig('roles_property', 'roles'); + if (property_exists($verifiedClaims, $roleProperty)) { + $roles = $verifiedClaims->{$roleProperty}; + } + + $this->settings['fields'] = ['username' => 'email']; + $user = $this->_findUser($mispUsername); + + $organisationId = $this->checkOrganization($organisationName, $user, $mispUsername); + if (!$organisationId) { + return false; + } + + $roleId = $this->getUserRole($roles, $mispUsername); + if ($roleId === null) { + $this->log($mispUsername, 'No role was assigned.'); + return false; + } + + if ($user) { + $this->log($mispUsername, 'Found in database.'); + + if ($user['org_id'] != $organisationId) { + $user['org_id'] = $organisationId; + $this->userModel()->updateField($user, 'org_id', $organisationId); + $this->log($mispUsername, "User organisation changed from ${user['org_id']} to $organisationId."); + } + + if ($user['role_id'] != $roleId) { + $user['role_id'] = $roleId; + $this->userModel()->updateField($user, 'role_id', $roleId); + $this->log($mispUsername, "User role changed from ${user['role_id']} to $roleId."); + } + + $this->log($mispUsername, 'Logged in.'); + return $user; + } + + $this->log($mispUsername, 'Not found in database.'); + + $userData = [ + 'email' => $mispUsername, + 'org_id' => $organisationId, + 'newsread' => time(), + 'role_id' => $roleId, + 'change_pw' => 0, + 'date_created' => time(), + ]; + + if (!$this->userModel()->save($userData)) { + throw new RuntimeException("Could not save user `$mispUsername` to database."); + } + + $this->log($mispUsername, "Saved in database with ID {$this->userModel()->id}"); + $this->log($mispUsername, 'Logged in.'); + return $this->_findUser($mispUsername); + } + + /** + * @return OpenIDConnectClient + */ + private function prepareClient() + { + $providerUrl = $this->getConfig('provider_url'); + if (!filter_var($providerUrl, FILTER_VALIDATE_URL)) { + throw new RuntimeException("Config option `OidcAuth.provider_url` must be valid URL."); + } + + // OpenIDConnectClient will append well-know path, so if well-know path is already part of the url, remove it + $wellKnownPosition = strpos($providerUrl, '/.well-known/'); + if ($wellKnownPosition !== false) { + $providerUrl = substr($providerUrl, 0, $wellKnownPosition); + } + + $clientId = $this->getConfig('client_id'); + $clientSecret = $this->getConfig('client_secret'); + + $oidc = new OpenIDConnectClient($providerUrl, $clientId, $clientSecret); + $oidc->setRedirectURL(Configure::read('MISP.baseurl') . '/users/login'); + return $oidc; + } + + /** + * @param string $org + * @param array|null $user + * @param string $mispUsername + * @return int + * @throws Exception + */ + private function checkOrganization($org, $user, $mispUsername) + { + if (empty($org)) { + $this->log($mispUsername, "Organisation name not provided."); + return false; + } + + $orgIsUuid = Validation::uuid($org); + + $orgAux = $this->userModel()->Organisation->find('first', [ + 'fields' => ['Organisation.id'], + 'conditions' => $orgIsUuid ? ['uuid' => mb_strtolower($org)] : ['name' => $org], + ]); + if (empty($orgAux)) { + if ($orgIsUuid) { + $this->log($mispUsername, "Could not found organisation with UUID `$org`."); + return false; + } + + $orgUserId = 1; // By default created by the admin + if ($user) { + $orgUserId = $user['id']; + } + $orgId = $this->userModel()->Organisation->createOrgFromName($org, $orgUserId, true); + $this->log($mispUsername, "User organisation `$org` created with ID $orgId."); + } else { + $orgId = $orgAux['Organisation']['id']; + $this->log($mispUsername, "User organisation `$org` found with ID $orgId."); + } + return $orgId; + } + + /** + * @param array $roles Role list provided by OIDC + * @param string $mispUsername + * @return int|null Role ID or null if no role matches + */ + private function getUserRole(array $roles, $mispUsername) + { + $roleMapper = $this->getConfig('role_mapper'); + if (!is_array($roleMapper)) { + throw new RuntimeException("Config option `OidcAuth.role_mapper` must be array."); + } + + $roleNameToId = $this->userModel()->Role->find('list', [ + 'fields' => ['Role.name', 'Role.id'], + ]); + $roleNameToId = array_change_key_case($roleNameToId); // normalize role names to lowercase + + $userRole = null; + foreach ($roles as $role) { + if (isset($roleMapper[$role])) { + $roleId = $roleMapper[$role]; + if (!is_numeric($roleId)) { + $roleId = mb_strtolower($roleId); + if (isset($roleNameToId[$roleId])) { + $roleId = $roleNameToId[$roleId]; + } else { + $this->log($mispUsername, "MISP Role with name `$roleId` not found, skipping."); + continue; + } + } + if ($userRole === null || $roleId < $userRole) { // role with lower ID wins + $userRole = $roleId; + } + } + } + + return $userRole; + } + + /** + * @param string $config + * @param mixed|null $default + * @return mixed + */ + private function getConfig($config, $default = null) + { + $value = Configure::read("OidcAuth.$config"); + if (empty($value)) { + if ($default === null) { + throw new RuntimeException("Config option `OidcAuth.$config` is not set."); + } + return $default; + } + return $value; + } + + /** + * @param string $username + * @param string $message + */ + private function log($username, $message) + { + CakeLog::info("OIDC: User `$username` – $message"); + } + + /** + * @return User + */ + private function userModel() + { + if (isset($this->userModel)) { + return $this->userModel; + } + + $this->userModel = ClassRegistry::init($this->settings['userModel']); + return $this->userModel; + } +} diff --git a/app/View/Elements/eventattributetoolbar.ctp b/app/View/Elements/eventattributetoolbar.ctp index 22e8c5552..736a68391 100644 --- a/app/View/Elements/eventattributetoolbar.ctp +++ b/app/View/Elements/eventattributetoolbar.ctp @@ -51,7 +51,7 @@ 'id' => 'create-button', 'title' => $possibleAction === 'attribute' ? __('Add attribute') : __('Add proposal'), 'fa-icon' => 'plus', - //'onClick' => 'clickCreateButton', + 'class' => 'last', 'onClick' => 'openGenericModal', 'onClickParams' => array('/' . $possibleAction . 's/add/' . h($event['Event']['id'])) ), diff --git a/app/View/Elements/healthElements/diagnostics.ctp b/app/View/Elements/healthElements/diagnostics.ctp index 855bd1815..404b249c2 100644 --- a/app/View/Elements/healthElements/diagnostics.ctp +++ b/app/View/Elements/healthElements/diagnostics.ctp @@ -115,6 +115,38 @@ ?> +

+ + + + + + + + + + + $errors): foreach ($errors as $error): list($level, $message) = $error; ?> + + + + + + + +
+ + + + + + + +
+ +

- -
-
- - - - - -
-
- - Form->create('Server', array('id' => 'removeTag_' . h($tag['Tag']['id']), 'url' => $baseurl . '/servers/removeTag/' . h($server['Server']['id']) . '/' . h($tag['Tag']['id']), 'style' => 'margin:0px;')); - ?> -
x
- Form->end(); - ?> - -   - -
-
- -
- - - -   - -
- diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index a6a76138c..90c19a505 100644 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -540,7 +540,7 @@ diff --git a/app/View/Layouts/default.ctp b/app/View/Layouts/default.ctp index 6a05c310c..3b79002e4 100644 --- a/app/View/Layouts/default.ctp +++ b/app/View/Layouts/default.ctp @@ -15,7 +15,6 @@ 'jquery-ui', 'chosen.min', 'main', - 'jquery-jvectormap-2.0.5', array('print', array('media' => 'print')) ); if (Configure::read('MISP.custom_css')) { @@ -121,10 +120,6 @@ - if ($('.alert').text().indexOf("$flashErrorMessage") >= 0) { - var flashMessageLink = 'here'; - $('.alert').html(($('.alert').html().replace("$flashErrorMessage", flashMessageLink))); - } }); diff --git a/app/View/Layouts/flash.ctp b/app/View/Layouts/flash.ctp deleted file mode 100644 index 566086d0f..000000000 --- a/app/View/Layouts/flash.ctp +++ /dev/null @@ -1,36 +0,0 @@ - - - - -Html->charset(); ?> -<?php echo $page_title; ?> - - - - - - -

- - diff --git a/app/View/Layouts/graph.ctp b/app/View/Layouts/graph.ctp deleted file mode 100644 index 0af1405d6..000000000 --- a/app/View/Layouts/graph.ctp +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - Html->charset(); ?> - - <?php echo $title_for_layout, ' - MISP'; ?> - - Html->meta('icon'); - //echo $this->Html->css('roboto'); - echo $this->Html->css('bootstrap'); // see http://twitter.github.io/bootstrap/base-css.html - echo $this->Html->css('bootstrap-datepicker'); - echo $this->Html->css('bootstrap-timepicker'); - echo $this->Html->css('bootstrap-colorpicker'); - echo $this->Html->css('main'); - echo $this->Html->css('print', 'stylesheet', array('media' => 'print')); - - echo $this->fetch('meta'); - echo $this->fetch('css'); - echo $this->fetch('script'); - - echo $this->Html->script('jquery'); // Include jQuery library - ?> - - - -
-
- element('global_menu'); - $padding_top = 10; - if ($debugMode == 'debugOff') $padding_top = 50; - ?> -
- Session->flash('email'); - $flash[] = $this->Session->flash(); - $flash[] = $this->Session->flash('gpg'); - $flash[] = $this->Session->flash('error'); - $flash[] = $this->Session->flash('auth'); - foreach ($flash as $f) { - if ($f) { - echo $f; - $has_flash = true; - continue; - } - } - ?> -
- -
- fetch('content'); ?> -
-
- element('footer'); - echo $this->element('sql_dump'); - echo $this->Html->script('bootstrap'); - echo $this->Html->script('bootstrap-timepicker'); - echo $this->Html->script('bootstrap-datepicker'); - echo $this->Html->script('bootstrap-colorpicker'); - echo $this->Html->script('misp.js?' . $queryVersion); - echo $this->Html->script('keyboard-shortcuts.js?' . $queryVersion); - ?> -
-
-
-
-
-
-
-
-
-
- - - - - diff --git a/app/View/Taxonomies/view.ctp b/app/View/Taxonomies/view.ctp index d89bf545f..ca4f51337 100644 --- a/app/View/Taxonomies/view.ctp +++ b/app/View/Taxonomies/view.ctp @@ -1,5 +1,5 @@
-

+

'. __('Yes') . '  ' : '' . __('No') . '  '; @@ -76,12 +76,12 @@ echo $this->element('genericElements/viewMetaTable', ['table_data' => $tableData -   +   @@ -91,7 +91,7 @@ echo $this->element('genericElements/viewMetaTable', ['table_data' => $tableData - '> + "> element('genericElements/viewMetaTable', ['table_data' => $tableData - '> + "> element('genericElements/viewMetaTable', ['table_data' => $tableData $url = $baseurl . '/events/index/searchtag:' . h($item['existing_tag']['Tag']['id']); if ($isAclTagger) $url = $baseurl . '/tags/edit/' . h($item['existing_tag']['Tag']['id']); ?> - + Html->link('', array('controller' => 'tags', 'action' => 'viewGraph', $item['existing_tag']['Tag']['id']), array('class' => 'fa fa-share-alt black', 'title' => __('View correlation graph'), 'aria-label' => __('View correlation graph'))); diff --git a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Black-webfont.eot deleted file mode 100644 index bae10a28a..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Black-webfont.svg deleted file mode 100644 index e1d6178e0..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Black-webfont.ttf deleted file mode 100644 index 7acbe9390..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Black-webfont.woff deleted file mode 100644 index 2bbbfbf34..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Black-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.eot deleted file mode 100644 index 6e811a66d..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.svg deleted file mode 100644 index 810986695..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.ttf deleted file mode 100644 index ded49e8b5..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.woff deleted file mode 100644 index 382c2a85b..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BlackItalic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.eot deleted file mode 100644 index c4ec7ebe2..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.svg deleted file mode 100644 index f731abac7..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.ttf deleted file mode 100644 index cd29fc5ef..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.woff deleted file mode 100644 index 3c5926be7..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Bold-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.eot deleted file mode 100644 index 14062404d..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.svg deleted file mode 100644 index fc9e5cb09..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.ttf deleted file mode 100644 index dc5a1d393..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.woff deleted file mode 100644 index 10c696255..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldCondensed-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.eot deleted file mode 100644 index e9f04b304..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.svg deleted file mode 100644 index dc39022a7..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.ttf deleted file mode 100644 index 268545422..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.woff deleted file mode 100644 index 7e1daf415..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldCondensedItalic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.eot deleted file mode 100644 index ba54c8a26..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.svg deleted file mode 100644 index 3ee9e9229..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.ttf deleted file mode 100644 index 536d4cae8..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.woff deleted file mode 100644 index baf277b3e..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-BoldItalic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.eot deleted file mode 100644 index 4eea4985a..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.svg deleted file mode 100644 index a62941c4d..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.ttf deleted file mode 100644 index dfc0f9485..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.woff deleted file mode 100644 index 306e23add..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Condensed-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.eot deleted file mode 100644 index b6534ea22..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.svg deleted file mode 100644 index 12d5a638c..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.ttf deleted file mode 100644 index 02fdc4eb0..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.woff deleted file mode 100644 index 5e79ca7d2..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-CondensedItalic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.eot deleted file mode 100644 index 8049a4d3c..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.svg deleted file mode 100644 index be11e0833..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.ttf deleted file mode 100644 index e25340415..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.woff deleted file mode 100644 index f5436e548..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Italic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Light-webfont.eot deleted file mode 100644 index a8b0d4322..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Light-webfont.svg deleted file mode 100644 index a46503535..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Light-webfont.ttf deleted file mode 100644 index 7b44118b5..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Light-webfont.woff deleted file mode 100644 index 64264d88b..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Light-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.eot deleted file mode 100644 index 81545b0e4..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.svg deleted file mode 100644 index 5f0668dd9..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.ttf deleted file mode 100644 index 445afbcdc..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.woff deleted file mode 100644 index 752e9fbd2..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-LightItalic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.eot deleted file mode 100644 index cc69239e4..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.svg deleted file mode 100644 index 930d33ad7..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.ttf deleted file mode 100644 index 71ba85ddd..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.woff deleted file mode 100644 index f2cce18d9..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Medium-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.eot deleted file mode 100644 index fd8482ec2..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.svg deleted file mode 100644 index 5ec4d4bf1..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.ttf deleted file mode 100644 index f84d5da6d..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.woff deleted file mode 100644 index 52727a162..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-MediumItalic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.eot deleted file mode 100644 index feb9b46f8..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.svg deleted file mode 100644 index e4f21cc07..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.ttf deleted file mode 100644 index b732d26a5..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.woff deleted file mode 100644 index 18bdd9bcc..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Regular-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.eot deleted file mode 100644 index 33aeb8448..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.svg deleted file mode 100644 index c585627c8..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.ttf deleted file mode 100644 index 571805d34..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.woff deleted file mode 100644 index 59e9333ee..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-Thin-webfont.woff and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.eot b/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.eot deleted file mode 100644 index 123aae82e..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.eot and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.svg b/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.svg deleted file mode 100644 index 90469b364..000000000 --- a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Font data copyright Google 2011 -Designer : Google -Foundry URL : Googlecom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.ttf b/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.ttf deleted file mode 100644 index b305b7b8a..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.ttf and /dev/null differ diff --git a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.woff b/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.woff deleted file mode 100644 index 4ab4991e9..000000000 Binary files a/app/webroot/css/fonts/roboto/Roboto-ThinItalic-webfont.woff and /dev/null differ diff --git a/app/webroot/css/main.css b/app/webroot/css/main.css index 065fa13ed..ea37dab5e 100644 --- a/app/webroot/css/main.css +++ b/app/webroot/css/main.css @@ -30,6 +30,11 @@ form button.btn[disabled] { cursor: not-allowed; } +.btn-group > .btn.last { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + /*.close{ float:none; }*/ @@ -114,7 +119,6 @@ pre { border-left: none; border-right: 6px solid #fff; border-bottom: 6px solid transparent; - left: 10px; top: 1px; left: -6px; } @@ -1423,7 +1427,6 @@ span.success { border: 1px solid #ddd !important; border-bottom:0px !important; line-height:14px !important; - margin-right:0px !important; margin-bottom:1px !important; float:right; } @@ -1449,7 +1452,6 @@ span.success { width:34px; padding-left:3px; margin:0px !important; - margin-bottom:1px !important; border: 1px solid #ddd !important; border-bottom:0px !important; margin-right:200px !important; @@ -1755,7 +1757,6 @@ span.success { line-height:40px; background-color: #FFFFFF; padding:0px !important; - padding-right:0px !important; border: 1px dashed black !important; -webkit-radius: 8px; -moz-border-radius: 8px; @@ -2190,7 +2191,8 @@ table.table.table-striped tr.deleted_row td { } .popover { - max-width:33%; + max-width: 33%; + color: #333333; } .checkbox input[type=checkbox] { diff --git a/app/webroot/css/roboto.css b/app/webroot/css/roboto.css deleted file mode 100644 index ed70e6010..000000000 --- a/app/webroot/css/roboto.css +++ /dev/null @@ -1,191 +0,0 @@ -@font-face { - font-family: 'Roboto'; - src: url('fonts/roboto/Roboto-Regular-webfont.eot'); - src: url('fonts/roboto/Roboto-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Regular-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Regular-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Regular-webfont.svg#RobotoRegular') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoItalic'; - src: url('fonts/roboto/Roboto-Italic-webfont.eot'); - src: url('fonts/roboto/Roboto-Italic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Italic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Italic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Italic-webfont.svg#RobotoItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoBold'; - src: url('fonts/roboto/Roboto-Bold-webfont.eot'); - src: url('fonts/roboto/Roboto-Bold-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Bold-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Bold-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Bold-webfont.svg#RobotoBold') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoBoldItalic'; - src: url('fonts/roboto/Roboto-BoldItalic-webfont.eot'); - src: url('fonts/roboto/Roboto-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('vRoboto-BoldItalic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-BoldItalic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-BoldItalic-webfont.svg#RobotoBoldItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoCondensed'; - src: url('fonts/roboto/Roboto-Condensed-webfont.eot'); - src: url('fonts/roboto/Roboto-Condensed-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Condensed-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Condensed-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Condensed-webfont.svg#RobotoCondensed') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoCondensedItalic'; - src: url('fonts/roboto/Roboto-CondensedItalic-webfont.eot'); - src: url('fonts/roboto/Roboto-CondensedItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-CondensedItalic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-CondensedItalic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-CondensedItalic-webfont.svg#RobotoCondensedItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoBoldCondensed'; - src: url('fonts/roboto/Roboto-BoldCondensed-webfont.eot'); - src: url('fonts/roboto/Roboto-BoldCondensed-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-BoldCondensed-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-BoldCondensed-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-BoldCondensed-webfont.svg#RobotoBoldCondensed') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoBoldCondensedItalic'; - src: url('fonts/roboto/Roboto-BoldCondensedItalic-webfont.eot'); - src: url('fonts/roboto/Roboto-BoldCondensedItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-BoldCondensedItalic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-BoldCondensedItalic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-BoldCondensedItalic-webfont.svg#RobotoBoldCondensedItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoThin'; - src: url('fonts/roboto/Roboto-Thin-webfont.eot'); - src: url('fonts/roboto/Roboto-Thin-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Thin-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Thin-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Thin-webfont.svg#RobotoThin') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoThinItalic'; - src: url('fonts/roboto/Roboto-ThinItalic-webfont.eot'); - src: url('fonts/roboto/Roboto-ThinItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-ThinItalic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-ThinItalic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-ThinItalic-webfont.svg#RobotoThinItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoLight'; - src: url('fonts/roboto/Roboto-Light-webfont.eot'); - src: url('fonts/roboto/Roboto-Light-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Light-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Light-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Light-webfont.svg#RobotoLight') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoLightItalic'; - src: url('fonts/roboto/Roboto-LightItalic-webfont.eot'); - src: url('fonts/roboto/Roboto-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-LightItalic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-LightItalic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-LightItalic-webfont.svg#RobotoLightItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoMedium'; - src: url('fonts/roboto/Roboto-Medium-webfont.eot'); - src: url('fonts/roboto/Roboto-Medium-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Medium-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Medium-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Medium-webfont.svg#RobotoMedium') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoMediumItalic'; - src: url('fonts/roboto/Roboto-MediumItalic-webfont.eot'); - src: url('fonts/roboto/Roboto-MediumItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-MediumItalic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-MediumItalic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-MediumItalic-webfont.svg#RobotoMediumItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoBlack'; - src: url('fonts/roboto/Roboto-Black-webfont.eot'); - src: url('fonts/roboto/Roboto-Black-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-Black-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-Black-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-Black-webfont.svg#RobotoBlack') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'RobotoBlackItalic'; - src: url('fonts/roboto/Roboto-BlackItalic-webfont.eot'); - src: url('fonts/roboto/Roboto-BlackItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/roboto/Roboto-BlackItalic-webfont.woff') format('woff'), - url('fonts/roboto/Roboto-BlackItalic-webfont.ttf') format('truetype'), - url('fonts/roboto/Roboto-BlackItalic-webfont.svg#RobotoBlackItalic') format('svg'); - font-weight: normal; - font-style: normal; - -} diff --git a/app/webroot/js/misp.js b/app/webroot/js/misp.js index e064c51cd..ae93ac1be 100644 --- a/app/webroot/js/misp.js +++ b/app/webroot/js/misp.js @@ -895,8 +895,13 @@ function toggleAllTaxonomyCheckboxes() { } function attributeListAnyAttributeCheckBoxesChecked() { - if ($('.select_attribute:checked').length > 0) $('.mass-select').removeClass('hidden'); - else $('.mass-select').addClass('hidden'); + if ($('.select_attribute:checked').length > 0) { + $('.mass-select').removeClass('hidden'); + $('#create-button').removeClass('last'); + } else { + $('.mass-select').addClass('hidden'); + $('#create-button').addClass('last'); + } } function listCheckboxesChecked() { @@ -1203,12 +1208,6 @@ function redirectAddObject(templateId, additionalData) { window.location = baseurl + '/objects/add/' + eventId + '/' + templateId; } -function clickCreateButton(event, type) { - var destination = 'attributes'; - if (type == 'Proposal') destination = 'shadow_attributes'; - simplePopup(baseurl + "/" + destination + "/add/" + event); -} - function openGenericModal(url, modalData, callback) { $.ajax({ type: "get", @@ -4925,6 +4924,11 @@ $(document).ready(function() { html: true, trigger: 'hover' }); + + if ($('.alert').text().indexOf("$flashErrorMessage") >= 0) { + var flashMessageLink = 'here'; + $('.alert').html(($('.alert').html().replace("$flashErrorMessage", flashMessageLink))); + } }); $(document.body).on("click", ".correlation-expand-button", function() { @@ -5016,16 +5020,14 @@ $(document.body).on('click', 'a[data-paginator]', function (e) { }); }); -function queryEventLock(event_id, user_org_id) { +function queryEventLock(event_id) { if (tabIsActive) { $.ajax({ url: baseurl + "/events/checkLocks/" + event_id, type: "get", success: function(data, statusText, xhr) { if (xhr.status == 200) { - if ($('#event_lock_warning').length != 0) { - $('#event_lock_warning').remove(); - } + $('#event_lock_warning').remove(); if (data != '') { $('#main-view-container').append(data); } @@ -5033,7 +5035,7 @@ function queryEventLock(event_id, user_org_id) { } }); } - setTimeout(function() { queryEventLock(event_id, user_org_id); }, 5000); + setTimeout(function() { queryEventLock(event_id); }, 5000); } function checkIfLoggedIn() {