mirror of https://github.com/MISP/MISP
add: migrate authkeys
parent
ac82a238ca
commit
5890b8c60f
|
@ -0,0 +1,287 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\ORM\Locator\LocatorAwareTrait;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Utility\Hash;
|
||||
|
||||
class AuthKeysController extends AppController
|
||||
{
|
||||
use LocatorAwareTrait;
|
||||
|
||||
public $paginate = [
|
||||
'limit' => 60,
|
||||
'maxLimit' => 9999,
|
||||
'order' => [
|
||||
'Authkey.id' => 'DESC'
|
||||
],
|
||||
];
|
||||
|
||||
public function index($user_id = false)
|
||||
{
|
||||
$conditions = $this->__prepareConditions();
|
||||
$canCreateAuthkey = $this->__canCreateAuthKeyForUser($user_id);
|
||||
if ($user_id) {
|
||||
$this->set('user_id', $user_id);
|
||||
$conditions['AND'][] = ['AuthKeys.user_id' => $user_id];
|
||||
}
|
||||
$this->set('canCreateAuthkey', $canCreateAuthkey);
|
||||
$keyUsageEnabled = Configure::read('MISP.log_user_ips') && Configure::read('MISP.log_user_ips_authkeys');
|
||||
$this->CRUD->index([
|
||||
'filters' => ['Users.email', 'authkey_start', 'authkey_end', 'comment', 'Users.id'],
|
||||
'quickFilters' => ['comment', 'authkey_start', 'authkey_end', 'Users.email'],
|
||||
'conditions' => $conditions,
|
||||
'contain' => ['Users' => ['fields' => ['id', 'email']]],
|
||||
'afterFind' => function ($authKeys) use ($keyUsageEnabled) {
|
||||
if ($keyUsageEnabled) {
|
||||
$keyIds = Hash::extract($authKeys, "{n}.AuthKey.id");
|
||||
$lastUsedById = $this->AuthKey->getLastUsageForKeys($keyIds);
|
||||
}
|
||||
$authKeys = $authKeys->toArray();
|
||||
foreach ($authKeys as &$authKey) {
|
||||
if ($keyUsageEnabled) {
|
||||
$lastUsed = $lastUsedById[$authKey['id']];
|
||||
$authKey['last_used'] = $lastUsed;
|
||||
}
|
||||
}
|
||||
return $authKeys;
|
||||
}
|
||||
]);
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('title_for_layout', __('Auth Keys'));
|
||||
$this->set('advancedEnabled', !empty(Configure::read('Security.advanced_authkeys')));
|
||||
$this->set('keyUsageEnabled', $keyUsageEnabled);
|
||||
$this->set('menuData', [
|
||||
'menuList' => $this->isSiteAdmin() ? 'admin' : 'globalActions',
|
||||
'menuItem' => 'authkeys_index',
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
if (!$this->__canEditAuthKey($id)) {
|
||||
throw new MethodNotAllowedException(__('Invalid user or insufficient privileges to interact with an authkey for the given user.'));
|
||||
}
|
||||
$this->CRUD->delete($id, [
|
||||
'conditions' => $this->__prepareConditions(),
|
||||
'contain' => ['User'],
|
||||
]);
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
if (!$this->__canEditAuthKey($id)) {
|
||||
throw new MethodNotAllowedException(__('Invalid user or insufficient privileges to interact with an authkey for the given user.'));
|
||||
}
|
||||
$this->CRUD->edit($id, [
|
||||
'conditions' => $this->__prepareConditions(),
|
||||
'afterFind' => function (\App\Model\Entity\AuthKey $authKey) {
|
||||
return $authKey;
|
||||
},
|
||||
'fields' => ['comment', 'allowed_ips', 'expiration', 'read_only'],
|
||||
'contain' => ['Users' => ['fields' => ['id', 'org_id']]]
|
||||
]);
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('dropdownData', [
|
||||
'user' => $this->Users->find('list', [
|
||||
'sort' => ['username' => 'asc'],
|
||||
'conditions' => ['id' => $this->entity['user_id']],
|
||||
])
|
||||
]);
|
||||
$this->set('menuData', [
|
||||
'menuList' => $this->isSiteAdmin() ? 'admin' : 'globalActions',
|
||||
'menuItem' => 'authKeyAdd',
|
||||
]);
|
||||
$this->set('edit', true);
|
||||
$this->set('validity', Configure::read('Security.advanced_authkeys_validity'));
|
||||
$this->set('title_for_layout', __('Edit auth key'));
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function add($user_id = false)
|
||||
{
|
||||
$loggedUser = $this->ACL->getUser();
|
||||
$options = $this->request->getParam('user_id');
|
||||
if (!empty($params['user_id'])) {
|
||||
$user_id = $options['user_id'];
|
||||
}
|
||||
$params = [
|
||||
'displayOnSuccess' => 'authkey_display',
|
||||
'override' => ['authkey' => null], // do not allow to use own key, always generate random one
|
||||
'afterFind' => function (array $authKey, array $savedData) { // remove hashed key from response
|
||||
unset($authKey['authkey']);
|
||||
$authKey['authkey_raw'] = $savedData['authkey_raw'];
|
||||
return $authKey;
|
||||
}
|
||||
];
|
||||
if ($user_id === 'me' || $user_id === false) {
|
||||
$user_id = $loggedUser->id;
|
||||
}
|
||||
$selectConditions = [];
|
||||
if ($user_id) {
|
||||
if ($this->__canCreateAuthKeyForUser($user_id)) {
|
||||
$selectConditions['AND'][] = ['Users.id' => $user_id];
|
||||
$params['override']['user_id'] = $user_id;
|
||||
} else {
|
||||
throw new MethodNotAllowedException(__('Invalid user or insufficient privileges to interact with an authkey for the given user.'));
|
||||
}
|
||||
} else {
|
||||
$selectConditions['AND'][] = ['Users.id' => $loggedUser->id];
|
||||
$params['override']['user_id'] = $loggedUser->id;
|
||||
}
|
||||
$this->CRUD->add($params);
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$dropdownData = [
|
||||
'user' => $this->AuthKeys->Users->find('list', [
|
||||
'sort' => ['username' => 'asc'],
|
||||
'conditions' => $selectConditions,
|
||||
])
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->set('title_for_layout', __('Add auth key'));
|
||||
$this->set('menuData', [
|
||||
'menuList' => $this->isSiteAdmin() ? 'admin' : 'globalActions',
|
||||
'menuItem' => 'authKeyAdd',
|
||||
]);
|
||||
$this->set('validity', Configure::read('Security.advanced_authkeys_validity'));
|
||||
}
|
||||
|
||||
public function view($id = false)
|
||||
{
|
||||
$this->CRUD->view($id, [
|
||||
'contain' => ['Users' => ['fields' => ['id', 'email']]],
|
||||
'conditions' => $this->__prepareConditions(),
|
||||
'afterFind' => function (\App\Model\Entity\AuthKey $authKey) {
|
||||
return $authKey;
|
||||
}
|
||||
]);
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
|
||||
if (Configure::read('MISP.log_user_ips') && Configure::read('MISP.log_user_ips_authkeys')) {
|
||||
list($keyUsage, $lastUsed, $uniqueIps) = $this->AuthKey->getKeyUsage($id);
|
||||
$this->set('keyUsage', $keyUsage);
|
||||
$this->set('lastUsed', $lastUsed);
|
||||
$this->set('uniqueIps', $uniqueIps);
|
||||
}
|
||||
|
||||
$this->set('title_for_layout', __('Auth key'));
|
||||
$this->set('menuData', [
|
||||
'menuList' => $this->isSiteAdmin() ? 'admin' : 'globalActions',
|
||||
'menuItem' => 'authKeyView',
|
||||
]);
|
||||
}
|
||||
|
||||
public function pin($id, $ip)
|
||||
{
|
||||
if (!$this->__canEditAuthKey($id)) {
|
||||
throw new MethodNotAllowedException(__('Invalid user or insufficient privileges to interact with an authkey for the given user.'));
|
||||
}
|
||||
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['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
|
||||
*/
|
||||
private function __prepareConditions()
|
||||
{
|
||||
$user = $this->ACL->getUser();
|
||||
if ($user['Role']['perm_site_admin']) {
|
||||
$conditions = []; // site admin can see/edit all keys
|
||||
} else if ($user['Role']['perm_admin']) {
|
||||
$conditions['AND'][]['org_id'] = $user['org_id']; // org admin can see his/her user org auth keys
|
||||
} else {
|
||||
$conditions['AND'][]['id'] = $user['id'];
|
||||
}
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
private function __canCreateAuthKeyForUser($user_id)
|
||||
{
|
||||
$loggedUser = $this->ACL->getUser();
|
||||
if (!$user_id)
|
||||
return true;
|
||||
if ($this->isAdmin) {
|
||||
if ($this->isSiteAdmin()) {
|
||||
return true; // site admin is OK for all
|
||||
} else {
|
||||
// org admin only for non-admin users and themselves
|
||||
$user = $this->AuthKey->User->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'User.id' => $user_id,
|
||||
'User.disabled' => false
|
||||
],
|
||||
'fields' => ['User.id', 'User.org_id', 'User.disabled'],
|
||||
'contain' => [
|
||||
'Role' => [
|
||||
'fields' => [
|
||||
'Role.perm_site_admin', 'Role.perm_admin', 'Role.perm_auth'
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
if (
|
||||
$user['Role']['perm_site_admin'] ||
|
||||
($user['Role']['perm_admin'] && $user['User']['id'] !== $loggedUser->id) ||
|
||||
!$user['Role']['perm_auth']
|
||||
) {
|
||||
// no create/edit for site_admin or other org admin
|
||||
return false;
|
||||
} else {
|
||||
// ok for themselves or users
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// user for themselves
|
||||
return (int)$user_id === (int)$loggedUser->id;
|
||||
}
|
||||
}
|
||||
|
||||
private function __canEditAuthKey($key_id)
|
||||
{
|
||||
$user_id = $this->AuthKeys->find('column', [
|
||||
'fields' => ['user_id'],
|
||||
'conditions' => [
|
||||
'id' => $key_id
|
||||
]
|
||||
]);
|
||||
return $this->__canCreateAuthKeyForUser($user_id);
|
||||
}
|
||||
}
|
|
@ -3,10 +3,8 @@
|
|||
namespace App\Model\Entity;
|
||||
|
||||
use App\Model\Entity\AppModel;
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
class AuthKey extends AppModel
|
||||
{
|
||||
|
||||
protected $_hidden = ['authkey'];
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\Validation\Validator;
|
||||
use ArrayObject;
|
||||
use Cake\Auth\DefaultPasswordHasher;
|
||||
use Cake\Datasource\EntityInterface;
|
||||
use Cake\Event\Event;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\Auth\DefaultPasswordHasher;
|
||||
use Cake\Utility\Security;
|
||||
use ArrayObject;
|
||||
use Cake\Validation\Validator;
|
||||
|
||||
class AuthKeysTable extends AppTable
|
||||
{
|
||||
|
@ -19,7 +19,12 @@ class AuthKeysTable extends AppTable
|
|||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->belongsTo(
|
||||
'Users'
|
||||
'Users',
|
||||
[
|
||||
'dependent' => false,
|
||||
'cascadeCallbacks' => false,
|
||||
'propertyName' => 'User'
|
||||
]
|
||||
);
|
||||
$this->setDisplayField('comment');
|
||||
}
|
||||
|
@ -65,14 +70,16 @@ class AuthKeysTable extends AppTable
|
|||
}
|
||||
$start = substr($authkey, 0, 4);
|
||||
$end = substr($authkey, -4);
|
||||
$candidates = $this->find()->where([
|
||||
$candidates = $this->find()->where(
|
||||
[
|
||||
'authkey_start' => $start,
|
||||
'authkey_end' => $end,
|
||||
'OR' => [
|
||||
'expiration' => 0,
|
||||
'expiration >' => time()
|
||||
]
|
||||
]);
|
||||
]
|
||||
);
|
||||
if (!empty($candidates)) {
|
||||
foreach ($candidates as $candidate) {
|
||||
if ((new DefaultPasswordHasher())->check($authkey, $candidate['authkey'])) {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'title' => isset($edit) ? __('Edit auth key') : __('Add auth key'),
|
||||
'description' => __('Auth keys are used for API access. A user can have more than one authkey, so if you would like to use separate keys per tool that queries MISP, add additional keys. Use the comment field to make identifying your keys easier.'),
|
||||
'fields' => [
|
||||
[
|
||||
'field' => 'user_id',
|
||||
'label' => __('User'),
|
||||
'options' => $dropdownData['user'],
|
||||
'type' => 'dropdown',
|
||||
'class' => 'span6'
|
||||
],
|
||||
[
|
||||
'field' => 'comment',
|
||||
'label' => __('Comment'),
|
||||
'class' => 'span6',
|
||||
'rows' => 4,
|
||||
],
|
||||
[
|
||||
'field' => 'allowed_ips',
|
||||
'label' => __('Allowed IPs'),
|
||||
'class' => 'span6',
|
||||
'rows' => 4,
|
||||
],
|
||||
[
|
||||
'field' => 'expiration',
|
||||
'label' => __('Expiration (%s)', $validity ? __('keep empty for maximal validity of %s days', $validity) : __('keep empty for indefinite')),
|
||||
'class' => 'datepicker span6',
|
||||
'placeholder' => "YYYY-MM-DD",
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'field' => 'read_only',
|
||||
'label' => __('Read only (it will be not possible to do any change operation with this token)'),
|
||||
'type' => 'checkbox',
|
||||
]
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action'),
|
||||
'ajaxSubmit' => 'submitGenericFormInPlace();'
|
||||
]
|
||||
]
|
||||
]);
|
||||
// TODO: [3.x-MIGRATION]
|
||||
// if (!$ajax) {
|
||||
// echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
|
||||
// }
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
if ($ajax) {
|
||||
?>
|
||||
<div id="genericModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="genericModalLabel" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="genericModalLabel"><?= __('Auth key created'); ?></h3>
|
||||
</div>
|
||||
<div class="modal-body modal-body-long">
|
||||
<p><?= __('Please make sure that you note down the auth key below, this is the only time the auth key is shown in plain text, so make sure you save it. If you lose the key, simply remove the entry and generate a new one.'); ?></p>
|
||||
<p><?=__('MISP will use the first and the last 4 characters for identification purposes.')?></p>
|
||||
<pre class="quickSelect"><?= h($entity['AuthKey']['authkey_raw']) ?></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="<?= h($referer) ?>" class="btn btn-primary"><?= __('I have noted down my key, take me back now') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<h4><?= __('Auth key created'); ?></h4>
|
||||
<p><?= __('Please make sure that you note down the auth key below, this is the only time the auth key is shown in plain text, so make sure you save it. If you lose the key, simply remove the entry and generate a new one.'); ?></p>
|
||||
<p><?=__('MISP will use the first and the last 4 characters for identification purposes.')?></p>
|
||||
<pre class="quickSelect"><?= h($entity['AuthKey']['authkey_raw']) ?></pre>
|
||||
<a href="<?= h($referer) ?>" class="btn btn-primary"><?= __('I have noted down my key, take me back now') ?></a>
|
||||
<?php
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
echo sprintf('<div%s>', empty($ajax) ? ' class="index"' : '');
|
||||
if (!$advancedEnabled) {
|
||||
echo '<div class="alert">' . __('Advanced auth keys are not enabled.') . '</div>';
|
||||
}
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'pull' => 'right',
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'simple',
|
||||
'children' => [
|
||||
'data' => [
|
||||
'type' => 'simple',
|
||||
'fa-icon' => 'plus',
|
||||
'text' => __('Add authentication key'),
|
||||
'class' => 'btn-primary modal-open',
|
||||
'url' => '/auth-keys/add' . (empty($user_id) ? '' : ('/' . $user_id)),
|
||||
'requirement' => $canCreateAuthkey
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'searchKey' => 'quickFilter',
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'name' => '#',
|
||||
'sort' => 'id',
|
||||
'data_path' => 'id',
|
||||
],
|
||||
[
|
||||
'name' => __('User'),
|
||||
'sort' => 'User.email',
|
||||
'data_path' => 'User.email',
|
||||
'url' => '/users/view',
|
||||
'url_params_data_paths' => ['User.id'],
|
||||
'requirement' => $loggedUser['Role']['perm_admin'] || $loggedUser['Role']['perm_site_admin'],
|
||||
],
|
||||
[
|
||||
'name' => __('Auth Key'),
|
||||
'sort' => 'authkey_start',
|
||||
'element' => 'authkey',
|
||||
'data_path' => 'AuthKey',
|
||||
],
|
||||
[
|
||||
'name' => __('Expiration'),
|
||||
'sort' => 'expiration',
|
||||
'data_path' => 'expiration',
|
||||
'element' => 'expiration'
|
||||
],
|
||||
[
|
||||
'name' => ('Last used'),
|
||||
'data_path' => 'last_used',
|
||||
'element' => 'datetime',
|
||||
'requirements' => $keyUsageEnabled,
|
||||
'empty' => __('Never'),
|
||||
],
|
||||
[
|
||||
'name' => __('Comment'),
|
||||
'sort' => 'comment',
|
||||
'data_path' => 'comment',
|
||||
],
|
||||
[
|
||||
'name' => __('Allowed IPs'),
|
||||
'data_path' => 'allowed_ips',
|
||||
],
|
||||
[
|
||||
'name' => __('Seen IPs'),
|
||||
'data_path' => '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,
|
||||
'pull' => 'right',
|
||||
'actions' => [
|
||||
[
|
||||
'url' => '/auth-keys/view',
|
||||
'url_params_data_paths' => array(
|
||||
'id'
|
||||
),
|
||||
'icon' => 'eye',
|
||||
'title' => 'View auth key',
|
||||
],
|
||||
[
|
||||
'url' => '/auth-keys/edit',
|
||||
'url_params_data_paths' => array(
|
||||
'id'
|
||||
),
|
||||
'icon' => 'edit',
|
||||
'title' => 'Edit auth key',
|
||||
'requirement' => $canCreateAuthkey
|
||||
],
|
||||
[
|
||||
'class' => 'modal-open',
|
||||
'url' => '/authKeys/delete',
|
||||
'url_params_data_paths' => ['id'],
|
||||
'icon' => 'trash',
|
||||
'title' => __('Delete auth key'),
|
||||
'requirement' => $canCreateAuthkey
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
echo '</div>';
|
||||
// TODO: [3.x-MIGRATION]
|
||||
// if (empty($ajax)) {
|
||||
// echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
|
||||
// }
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
var passedArgsArray = <?php echo $passedArgs; ?>;
|
||||
$(function() {
|
||||
$('#quickFilterButton').click(function() {
|
||||
runIndexQuickFilter();
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,91 @@
|
|||
<?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';
|
||||
}
|
||||
} else {
|
||||
$lastUsed = null;
|
||||
$uniqueIps = null;
|
||||
}
|
||||
|
||||
echo $this->element('genericElements/SingleViews/single_view', [
|
||||
'title' => 'Auth key view',
|
||||
'data' => $entity,
|
||||
'fields' => [
|
||||
[
|
||||
'key' => __('ID'),
|
||||
'path' => 'id'
|
||||
],
|
||||
[
|
||||
'key' => __('UUID'),
|
||||
'path' => 'uuid',
|
||||
],
|
||||
[
|
||||
'key' => __('Auth Key'),
|
||||
'path' => 'AuthKey',
|
||||
'type' => 'authkey'
|
||||
],
|
||||
[
|
||||
'key' => __('User'),
|
||||
'path' => 'User.id',
|
||||
'pathName' => 'User.email',
|
||||
'model' => 'users',
|
||||
'type' => 'model'
|
||||
],
|
||||
[
|
||||
'key' => __('Comment'),
|
||||
'path' => 'comment'
|
||||
],
|
||||
[
|
||||
'key' => __('Allowed IPs'),
|
||||
'type' => 'custom',
|
||||
'function' => function (array $data) {
|
||||
if (is_array($data['allowed_ips'])) {
|
||||
return implode("<br />", array_map('h', $data['allowed_ips']));
|
||||
}
|
||||
return __('All');
|
||||
}
|
||||
],
|
||||
[
|
||||
'key' => __('Created'),
|
||||
'path' => 'created',
|
||||
'type' => 'datetime'
|
||||
],
|
||||
[
|
||||
'key' => __('Expiration'),
|
||||
'path' => 'expiration',
|
||||
'type' => 'expiration'
|
||||
],
|
||||
[
|
||||
'key' => __('Read only'),
|
||||
'path' => 'read_only',
|
||||
'type' => 'boolean'
|
||||
],
|
||||
[
|
||||
'key' => __('Key usage'),
|
||||
'type' => 'sparkline',
|
||||
'path' => 'id',
|
||||
'csv' => [
|
||||
'data' => $keyUsageCsv,
|
||||
],
|
||||
'requirement' => isset($keyUsage),
|
||||
],
|
||||
[
|
||||
'key' => __('Last used'),
|
||||
'raw' => $lastUsed ? $this->Time->time($lastUsed) : __('Not used yet'),
|
||||
'requirement' => isset($keyUsage),
|
||||
],
|
||||
[
|
||||
'key' => __('Seen IPs'),
|
||||
'path' => 'unique_ips',
|
||||
'type' => 'authkey_pin'
|
||||
]
|
||||
],
|
||||
]);
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
$data_path = $this->Hash->extract($row, $field['data_path']);
|
||||
$result = [];
|
||||
foreach ($data_path as $key => $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('<br />', $result);
|
||||
echo $result;
|
||||
?>
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
$authKey = $this->Hash->extract($data, $field['path']);
|
||||
echo sprintf(
|
||||
'<span class="authkey">%s</span>%s<span class="authkey">%s</span>',
|
||||
h($authKey['authkey_start']),
|
||||
str_repeat('•', 32),
|
||||
h($authKey['authkey_end'])
|
||||
);
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
$data_path = $this->Hash->extract($data, $field['path']);
|
||||
$result = [];
|
||||
foreach ($data_path as $key => $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('<br />', $result);
|
||||
echo $result;
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
$value = $this->Hash->extract($data, $field['path'])[0];
|
||||
echo $this->Time->time($value);
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
$data = $this->Hash->extract($data, $field['path']);
|
||||
if (is_array($data)) {
|
||||
if (count($data) > 1) {
|
||||
$data = implode(', ', $data);
|
||||
} else {
|
||||
if (count($data) > 0) {
|
||||
$data = $data[0];
|
||||
} else {
|
||||
$data = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
$data = h($data);
|
||||
if (is_numeric($data)) {
|
||||
if ($data == 0) {
|
||||
$data = '<span class="text-success">' . __('Indefinite') . '</span>';
|
||||
} else {
|
||||
if ($data <= time()) {
|
||||
$title = __('Expired at %s', date('Y-m-d H:i:s', $data));
|
||||
$data = '<span class="red bold" title="' . $title . '">' . __('Expired') . '</span>';
|
||||
} else {
|
||||
$diffInDays = floor(($data - time()) / (3600 * 24));
|
||||
$class = $diffInDays <= 14 ? 'text-warning bold' : 'text-success';
|
||||
$title = __n('Will expire in %s day', 'Will expire in %s days', $diffInDays, $diffInDays);
|
||||
$data = '<span class="' . $class . '" title="' . $title . '">' . date('Y-m-d H:i:s', $data) . '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($field['onClick'])) {
|
||||
$data = sprintf(
|
||||
'<span onclick="%s">%s</span>',
|
||||
$field['onClick'],
|
||||
$data
|
||||
);
|
||||
}
|
||||
echo $data;
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
$path = $this->Hash->extract($data, $field['path']);
|
||||
$pathName = $this->Hash->extract($data, $field['pathName']);
|
||||
if (!empty($path) && !empty($pathName)) {
|
||||
$id = $this->Hash->extract($data, $field['path'])[0];
|
||||
$pathName = $this->Hash->extract($data, $field['pathName'])[0];
|
||||
echo sprintf(
|
||||
'<a href="%s/%s/view/%s">%s</a>',
|
||||
$baseurl,
|
||||
$field['model'],
|
||||
h($id),
|
||||
h($pathName)
|
||||
);
|
||||
} else {
|
||||
echo empty($field['error']) ? ' ' : h($field['error']);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
$elementId = $this->Hash->extract($data, $field['path'])[0];
|
||||
if (!empty($field['csv_data_path'])) {
|
||||
$csv = $this->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));
|
||||
}
|
Loading…
Reference in New Issue