new: [internal] Show auth key usage in key view page

pull/6581/head
Jakub Onderka 2020-11-28 16:46:00 +01:00
parent d7c027fe91
commit ee8a495d89
8 changed files with 148 additions and 40 deletions

View File

@ -236,26 +236,11 @@ class AppController extends Controller
}
}
$userMonitoringEnabled = false;
if ($this->Auth->user()) {
$user = $this->Auth->user();
Configure::write('CurrentUserId', $user['id']);
$this->User->setMonitoring($user);
if (Configure::read('MISP.log_user_ips')) {
$redis = $this->{$this->modelClass}->setupRedis();
if ($redis) {
$remoteAddress = trim($_SERVER['REMOTE_ADDR']);
$redis->set('misp:ip_user:' . $remoteAddress, $user['id']);
$redis->expire('misp:ip_user:' . $remoteAddress, 60 * 60 * 24 * 30); // 30 days
$redis->sadd('misp:user_ip:' . $user['id'], $remoteAddress);
// Allow to log key usage
if (isset($user['authkey_id']) && Configure::read('MISP.log_user_ips_auth')) {
$key = "misp:authkey:{$user['authkey_id']}";
$hashKey = date("Y-m-d") . ":$remoteAddress";
$redis->hIncrBy($key, $hashKey, 1);
}
}
}
$userMonitoringEnabled = $this->__accessMonitor($user);
// update script
if ($user['Role']['perm_site_admin'] || (Configure::read('MISP.live') && !$this->_isRest())) {
$this->{$this->modelClass}->runUpdates();
@ -325,7 +310,7 @@ class AppController extends Controller
if (
Configure::read('MISP.log_paranoid') ||
!empty(Configure::read('Security.monitored'))
$userMonitoringEnabled
) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
@ -337,7 +322,7 @@ class AppController extends Controller
) &&
(
!empty(Configure::read('MISP.log_paranoid_include_post_body')) ||
!empty(Configure::read('Security.monitored'))
$userMonitoringEnabled
)
) {
$payload = $this->request->input();
@ -635,6 +620,53 @@ class AppController extends Controller
return in_array($this->action, $actionsToCheck[$controller], true);
}
/**
* User access monitoring
* @param array $user
* @return bool True is user monitoring is enabled
*/
private function __accessMonitor(array $user)
{
$userMonitoringEnabled = Configure::read('Security.user_monitoring_enabled');
$logUserIps = Configure::read('MISP.log_user_ips');
if (!$userMonitoringEnabled && !$logUserIps) {
return false;
}
$redis = $this->User->setupRedis();
if (!$redis) {
return false;
}
if ($logUserIps) {
$remoteAddress = trim($_SERVER['REMOTE_ADDR']);
$pipe = $redis->multi(Redis::PIPELINE);
// keep for 30 days
$pipe->setex('misp:ip_user:' . $remoteAddress, 60 * 60 * 24 * 30, $user['id']);
$pipe->sadd('misp:user_ip:' . $user['id'], $remoteAddress);
// Log key usage if enabled
if (isset($user['authkey_id']) && Configure::read('MISP.log_user_ips_auth')) {
// Use request time if defined
$time = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
$hashKey = date("Y-m-d", $time) . ":$remoteAddress";
$pipe->hIncrBy("misp:authkey_usage:{$user['authkey_id']}", $hashKey, 1);
// delete after one year of inactivity
$pipe->expire("misp:authkey_usage:{$user['authkey_id']}", 3600 * 24 * 365);
$pipe->set("misp:authkey_last_usage:{$user['authkey_id']}", $time);
}
$pipe->exec();
}
// Return true for the current user if monitoring of this user is enabled in Redis.
if ($userMonitoringEnabled && $redis->sismember('misp:monitored_users', $user['id'])) {
return true;
}
return false;
}
private function __rateLimitCheck()
{
$info = array();

View File

@ -1,6 +1,9 @@
<?php
App::uses('AppController', 'Controller');
/**
* @property AuthKey $AuthKey
*/
class AuthKeysController extends AppController
{
public $components = array(
@ -26,10 +29,15 @@ class AuthKeysController extends AppController
$this->CRUD->index([
'filters' => ['User.username', 'authkey', 'comment', 'User.id'],
'quickFilters' => ['authkey', 'comment'],
'contain' => ['User'],
'contain' => ['User.id', 'User.email'],
'conditions' => $conditions,
'afterFind' => function (array $authKeys) {
$keyUsageEnabled = Configure::read('MISP.log_user_ips') && Configure::read('MISP.log_user_ips_auth');
foreach ($authKeys as &$authKey) {
if ($keyUsageEnabled) {
$lastUsed = $this->AuthKey->getKeyUsage($authKey['AuthKey']['id'])[1];
$key['AuthKey']['last_used'] = $lastUsed ? $lastUsed->format('c') : null;
}
unset($authKey['AuthKey']['authkey']);
}
return $authKeys;
@ -101,6 +109,13 @@ class AuthKeysController extends AppController
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
if (Configure::read('MISP.log_user_ips') && Configure::read('MISP.log_user_ips_auth')) {
list($keyUsage, $lastUsed) = $this->AuthKey->getKeyUsage($id);
$this->set('keyUsage', $keyUsage);
$this->set('lastUsed', $lastUsed);
}
$this->set('menuData', [
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
'menuItem' => 'authKeyView',

View File

@ -109,6 +109,34 @@ class AuthKey extends AppModel
}
}
/**
* @param int $id
* @return array
* @throws Exception
*/
public function getKeyUsage($id)
{
$redis = $this->setupRedisWithException();
$data = $redis->hGetAll("misp:authkey_usage:$id");
$output = [];
foreach ($data as $key => $count) {
list($date, $ip) = explode(':', $key);
if (isset($output[$date])) {
$output[$date] += $count;
} else {
$output[$date] = $count;
}
}
// Data from redis are not sorted
ksort($output);
$lastUsage = $redis->get("misp:authkey_last_usage:$id");
$lastUsage = $lastUsage === false ? null : new DateTime("@$lastUsage");
return [$output, $lastUsage];
}
/**
* @return AbstractPasswordHasher
*/

View File

@ -1294,24 +1294,6 @@ class User extends AppModel
return $data;
}
/*
* Set the monitoring flag in Configure for the current user
* Reads the state from redis
*/
public function setMonitoring($user)
{
if (
!empty(Configure::read('Security.user_monitoring_enabled'))
) {
$redis = $this->setupRedis();
if (!empty($redis->sismember('misp:monitored_users', $user['id']))) {
Configure::write('Security.monitored', 1);
return true;
}
}
Configure::write('Security.monitored', 0);
}
public function registerUser($added_by, $registration, $org_id, $role_id) {
$user = array(
'email' => $registration['data']['email'],

View File

@ -66,6 +66,14 @@
'description' => empty($ajax) ? __('A list of API keys bound to a user.') : false,
'pull' => 'right',
'actions' => [
[
'url' => $baseurl . '/auth_keys/view',
'url_params_data_paths' => array(
'AuthKey.id'
),
'icon' => 'eye',
'dbclickAction' => true
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/authKeys/delete/[onclick_params_data_path]\');',

View File

@ -1,4 +1,17 @@
<?php
$keyUsageCsv = null;
if (isset($keyUsage)) {
$todayString = date('Y-m-d');
$today = strtotime($todayString);
$startDate = key($keyUsage); // oldest date for sparkline
$startDate = strtotime($startDate) - (3600 * 24 * 3);
$keyUsageCsv = 'Date,Close\n';
for ($date = $startDate; $date <= $today; $date += (3600 * 24)) {
$dateAsString = date('Y-m-d', $date);
$keyUsageCsv .= $dateAsString . ',' . (isset($keyUsage[$dateAsString]) ? $keyUsage[$dateAsString] : 0) . '\n';
}
}
echo $this->element(
'genericElements/SingleViews/single_view',
[
@ -38,9 +51,21 @@ echo $this->element(
[
'key' => __('Comment'),
'path' => 'AuthKey.comment'
],
[
'key' => __('Key usage'),
'type' => 'sparkline',
'path' => 'AuthKey.id',
'csv' => [
'data' => $keyUsageCsv,
],
'requirement' => isset($keyUsage),
],
[
'key' => __('Last used'),
'raw' => $lastUsed ? $lastUsed->format('Y-m-d H:i:s') : __('Not used yet'),
'requirement' => isset($keyUsage),
]
],
'children' => [
]
]
);

View File

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

View File

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