diff --git a/.gitignore b/.gitignore index a84039004..5cf0333ac 100755 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ app/Lib/EventWarning/Custom/* /app/tmp/cached_exports/sha256/* /app/tmp/cached_exports/bro/* /app/Plugin/CakeResque +/app/Plugin/DebugKit .gnupg .smime *.swp diff --git a/INSTALL/MYSQL.sql b/INSTALL/MYSQL.sql index 9afec4086..150d9aaa5 100644 --- a/INSTALL/MYSQL.sql +++ b/INSTALL/MYSQL.sql @@ -88,6 +88,12 @@ CREATE TABLE IF NOT EXISTS `attribute_tags` ( INDEX `tag_id` (`tag_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- ------------------------------------------------------- + +-- +-- Table structure for table `auth_keys` +-- + CREATE TABLE IF NOT EXISTS `auth_keys` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uuid` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL, @@ -98,6 +104,8 @@ CREATE TABLE IF NOT EXISTS `auth_keys` ( `expiration` int(10) unsigned NOT NULL, `user_id` int(10) unsigned NOT NULL, `comment` text COLLATE utf8mb4_unicode_ci, + `allowed_ips` text COLLATE utf8mb4_unicode_ci, + `unique_ips` text COLLATE utf8mb4_unicode_ci, PRIMARY KEY (`id`), KEY `authkey_start` (`authkey_start`), KEY `authkey_end` (`authkey_end`), diff --git a/app/Controller/AuthKeysController.php b/app/Controller/AuthKeysController.php index dbcb0ed91..ce77a2b33 100644 --- a/app/Controller/AuthKeysController.php +++ b/app/Controller/AuthKeysController.php @@ -238,6 +238,30 @@ class AuthKeysController extends AppController ]); } + public function pin($id, $ip) { + if ($this->request->is('post')) { + // find entry, to confirm user is authorized + $conditions = $this->__prepareConditions(); + $conditions['AND'][]['AuthKey.id'] = $id; + $authKey = $this->AuthKey->find( + 'first', + ['conditions' => $conditions, + 'recursive'=> 1 + ] + ); + // update the key with the source IP + if ($authKey) { + $authKey['AuthKey']['allowed_ips'] = $ip; + $this->AuthKey->save($authKey, ['fieldList' => ['allowed_ips']]); + $this->Flash->success(__('IP address set as allowed source for the Key.')); + } else { + $this->Flash->error(__('Failed to set IP as source')); + } + } + $this->redirect($this->referer()); + // $this->redirect(['controller' => 'auth_keys', 'view' => 'index']); + } + /** * Return conditions according to current user permission. * @return array diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index 013610ad3..36152778e 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -72,8 +72,9 @@ class ACLComponent extends Component 'add' => ['AND' => ['perm_auth', 'not_read_only_authkey']], 'delete' => ['AND' => ['perm_auth', 'not_read_only_authkey']], 'edit' => ['AND' => ['perm_auth', 'not_read_only_authkey']], + 'pin' => ['AND' => ['perm_auth', 'not_read_only_authkey']], 'index' => ['perm_auth'], - 'view' => ['perm_auth'] + 'view' => ['perm_auth'], ], 'cerebrates' => [ 'add' => [], diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index dda95b94c..2ddc40ff8 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -84,7 +84,7 @@ class AppModel extends Model 87 => false, 88 => false, 89 => false, 90 => false, 91 => false, 92 => false, 93 => false, 94 => false, 95 => true, 96 => false, 97 => true, 98 => false, 99 => false, 100 => false, 101 => false, 102 => false, 103 => false, 104 => false, - 105 => false, 106 => false + 105 => false, 106 => false, 107 => false ); const ADVANCED_UPDATES_DESCRIPTION = array( @@ -1944,6 +1944,9 @@ class AppModel extends Model case 106: $sqlArray[] = "ALTER TABLE `taxii_servers` MODIFY `baseurl` varchar(191) NOT NULL;"; break; + case 107: + $sqlArray[] = "ALTER TABLE `auth_keys` ADD `unique_ips` text COLLATE utf8mb4_unicode_ci"; + break; case 'fixNonEmptySharingGroupID': $sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; $sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; diff --git a/app/Model/AuthKey.php b/app/Model/AuthKey.php index e1caa4e10..b177180ff 100644 --- a/app/Model/AuthKey.php +++ b/app/Model/AuthKey.php @@ -104,6 +104,12 @@ class AuthKey extends AppModel if (isset($val['AuthKey']['allowed_ips'])) { $results[$key]['AuthKey']['allowed_ips'] = JsonTool::decode($val['AuthKey']['allowed_ips']); } + if (isset($val['AuthKey']['unique_ips'])) { + $results[$key]['AuthKey']['unique_ips'] = JsonTool::decode($val['AuthKey']['unique_ips']); + } else { + $results[$key]['AuthKey']['unique_ips'] = []; + } + } return $results; } @@ -117,6 +123,13 @@ class AuthKey extends AppModel $this->data['AuthKey']['allowed_ips'] = JsonTool::encode($this->data['AuthKey']['allowed_ips']); } } + if (isset($this->data['AuthKey']['unique_ips'])) { + if (empty($this->data['AuthKey']['unique_ips'])) { + $this->data['AuthKey']['unique_ips'] = null; + } else { + $this->data['AuthKey']['unique_ips'] = JsonTool::encode($this->data['AuthKey']['unique_ips']); + } + } return true; } @@ -162,12 +175,27 @@ class AuthKey extends AppModel $possibleAuthkeys = $this->find('all', [ 'recursive' => -1, - 'fields' => ['id', 'authkey', 'user_id', 'expiration', 'allowed_ips', 'read_only'], + 'fields' => ['id', 'authkey', 'user_id', 'expiration', 'allowed_ips', 'read_only', 'unique_ips'], 'conditions' => $conditions, ]); $passwordHasher = $this->getHasher(); foreach ($possibleAuthkeys as $possibleAuthkey) { - if ($passwordHasher->check($authkey, $possibleAuthkey['AuthKey']['authkey'])) { + if ($passwordHasher->check($authkey, $possibleAuthkey['AuthKey']['authkey'])) { // valid authkey + // store IP in db if not there yet + $remote_ip = $this->_remoteIp(); + $update_db_ip = true; + if (in_array($remote_ip, $possibleAuthkey['AuthKey']['unique_ips'])) { + $update_db_ip = false; // IP already seen, skip saving in DB + } else { // first time this IP is seen for this API key + $possibleAuthkey['AuthKey']['unique_ips'][] = $remote_ip; + } + if ($update_db_ip) { + // prevent double entries due to race condition + $possibleAuthkey['AuthKey']['unique_ips'] = array_unique($possibleAuthkey['AuthKey']['unique_ips']); + // save in db + $this->save($possibleAuthkey, ['fieldList' => ['unique_ips']]); + } + // fetch user $user = $this->User->getAuthUser($possibleAuthkey['AuthKey']['user_id']); if ($user) { $user = $this->setUserData($user, $possibleAuthkey); diff --git a/app/Model/Sighting.php b/app/Model/Sighting.php index 5fe2eb61f..2562b404d 100644 --- a/app/Model/Sighting.php +++ b/app/Model/Sighting.php @@ -786,7 +786,7 @@ class Sighting extends AppModel foreach ($values as $value) { foreach (array('value1', 'value2') as $field) { $conditions['OR'][] = array( - 'LOWER(Attribute.' . $field . ') LIKE' => strtolower($value) + 'Attribute.' . $field => $value ); } } diff --git a/app/View/AuthKeys/index.ctp b/app/View/AuthKeys/index.ctp index 0fc6e0dd8..37110217d 100644 --- a/app/View/AuthKeys/index.ctp +++ b/app/View/AuthKeys/index.ctp @@ -73,6 +73,11 @@ 'name' => __('Allowed IPs'), 'data_path' => 'AuthKey.allowed_ips', ], + [ + 'name' => __('Seen IPs'), + 'data_path' => 'AuthKey.unique_ips', + 'element' => 'authkey_pin', + ] ], 'title' => empty($ajax) ? __('Authentication key Index') : false, 'description' => empty($ajax) ? __('A list of API keys bound to a user.') : false, diff --git a/app/View/AuthKeys/view.ctp b/app/View/AuthKeys/view.ctp index ff9c8262d..106063b27 100644 --- a/app/View/AuthKeys/view.ctp +++ b/app/View/AuthKeys/view.ctp @@ -48,7 +48,7 @@ echo $this->element('genericElements/SingleViews/single_view', [ 'type' => 'custom', 'function' => function (array $data) { if (is_array($data['AuthKey']['allowed_ips'])) { - return implode("
", array_map('h', $data['AuthKey']['allowed_ips'])); + return implode("
", array_map('h', $data['AuthKey']['allowed_ips'])); } return __('All'); } @@ -83,9 +83,9 @@ echo $this->element('genericElements/SingleViews/single_view', [ 'requirement' => isset($keyUsage), ], [ - 'key' => __('Unique IPs'), - 'raw' => $uniqueIps, - 'requirement' => isset($keyUsage), + 'key' => __('Seen IPs'), + 'path' => 'AuthKey.unique_ips', + 'type' => 'authkey_pin' ] ], ]); diff --git a/app/View/Elements/genericElements/IndexTable/Fields/authkey_pin.ctp b/app/View/Elements/genericElements/IndexTable/Fields/authkey_pin.ctp new file mode 100644 index 000000000..42bd93e5b --- /dev/null +++ b/app/View/Elements/genericElements/IndexTable/Fields/authkey_pin.ctp @@ -0,0 +1,27 @@ + $ip) { + $data_ip['ip'] = $ip; + $action = ['class' => 'modal-open', + 'url' => $baseurl. "/authKeys/pin/" . h($row['AuthKey']['id']) . '/' . h($ip), + 'icon' => 'thumbtack', + 'postLink' => true, + 'postLinkConfirm' => __('Use this as only possible source IP?'), + 'title' => __('Use this IP')]; + $form = $this->Form->postLink( + '', + $action['url'], + array( + 'class' => $this->FontAwesome->getClass($action['icon']) . ' ' . (empty($action['class']) ? '' : h($action['class'])), + 'title' => empty($action['title']) ? '' : h($action['title']), + 'aria-label' => empty($action['title']) ? '' : h($action['title']), + ), + $action['postLinkConfirm'] + ) . ' '; + $result[$key] = h($ip) . " " . $form; + } + + $result = implode('
', $result); + echo $result; +?> diff --git a/app/View/Elements/genericElements/SingleViews/Fields/authkey_pinField.ctp b/app/View/Elements/genericElements/SingleViews/Fields/authkey_pinField.ctp new file mode 100644 index 000000000..dcff3b254 --- /dev/null +++ b/app/View/Elements/genericElements/SingleViews/Fields/authkey_pinField.ctp @@ -0,0 +1,27 @@ + $ip) { + $data_ip['ip'] = $ip; + $action = ['class' => 'modal-open', + 'url' => $baseurl. "/authKeys/pin/" . h($data['AuthKey']['id']) . '/' . h($ip), + 'icon' => 'thumbtack', + 'postLink' => true, + 'postLinkConfirm' => __('Use this as only possible source IP?'), + 'title' => __('Use this IP')]; + $form = $this->Form->postLink( + '', + $action['url'], + array( + 'class' => $this->FontAwesome->getClass($action['icon']) . ' ' . (empty($action['class']) ? '' : h($action['class'])), + 'title' => empty($action['title']) ? '' : h($action['title']), + 'aria-label' => empty($action['title']) ? '' : h($action['title']), + ), + $action['postLinkConfirm'] + ) . ' '; + $result[$key] = h($ip) . " " . $form; + } + + $result = implode('
', $result); + echo $result; +?>