new: [permission limitations] subsystem added
- add limitations for users with given meta fields - x number / org and y number / globally - add comments to the limitations - enforced on user creation/modificationcli-modification-summary
parent
9d2c152a4e
commit
b1f09dc97e
|
@ -169,6 +169,13 @@ class ACLComponent extends Component
|
|||
'Pages' => [
|
||||
'display' => ['*']
|
||||
],
|
||||
'PermissionLimitations' => [
|
||||
"index" => ['*'],
|
||||
"add" => ['perm_admin'],
|
||||
"view" => ['*'],
|
||||
"edit" => ['perm_admin'],
|
||||
"delete" => ['perm_admin']
|
||||
],
|
||||
'Roles' => [
|
||||
'add' => ['perm_admin'],
|
||||
'delete' => ['perm_admin'],
|
||||
|
|
|
@ -123,6 +123,11 @@ class Sidemenu {
|
|||
'url' => '/auditLogs/index',
|
||||
'icon' => 'history',
|
||||
],
|
||||
'PermissionLimitations' => [
|
||||
'label' => __('Permission Limitations'),
|
||||
'url' => '/permissionLimitations/index',
|
||||
'icon' => 'jedi',
|
||||
],
|
||||
]
|
||||
],
|
||||
'API' => [
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\ForbiddenException;
|
||||
|
||||
class PermissionLimitationsController extends AppController
|
||||
{
|
||||
public $filterFields = ['scope', 'permission'];
|
||||
public $quickFilterFields = ['name'];
|
||||
public $containFields = [];
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->CRUD->index([
|
||||
'filters' => $this->filterFields,
|
||||
'quickFilters' => $this->quickFilterFields,
|
||||
'afterFind' => function($data) {
|
||||
$data['comment'] = is_resource($data['comment']) ? stream_get_contents($data['comment']) : $data['comment'];
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set('metaGroup', 'PermissionLimitations');
|
||||
}
|
||||
|
||||
public function add()
|
||||
{
|
||||
$this->CRUD->add([
|
||||
'afterFind' => function($data) {
|
||||
$data['comment'] = is_resource($data['comment']) ? stream_get_contents($data['comment']) : $data['comment'];
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set('metaGroup', 'PermissionLimitations');
|
||||
}
|
||||
|
||||
public function view($id)
|
||||
{
|
||||
$this->CRUD->view($id, [
|
||||
'afterFind' => function($data) {
|
||||
$data['comment'] = is_resource($data['comment']) ? stream_get_contents($data['comment']) : $data['comment'];
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set('metaGroup', 'PermissionLimitations');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$this->CRUD->edit($id, [
|
||||
'afterFind' => function($data) {
|
||||
$data['comment'] = is_resource($data['comment']) ? stream_get_contents($data['comment']) : $data['comment'];
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set('metaGroup', 'PermissionLimitations');
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$this->CRUD->delete($id);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set('metaGroup', 'PermissionLimitations');
|
||||
}
|
||||
}
|
|
@ -137,7 +137,11 @@ class UsersController extends AppController
|
|||
$id = $this->ACL->getUser()['id'];
|
||||
}
|
||||
$this->CRUD->view($id, [
|
||||
'contain' => ['Individuals' => ['Alignments' => 'Organisations'], 'Roles', 'Organisations']
|
||||
'contain' => ['Individuals' => ['Alignments' => 'Organisations'], 'Roles', 'Organisations'],
|
||||
'afterFind' => function($data) {
|
||||
$data = $this->fetchTable('PermissionLimitations')->attachLimitations($data);
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -414,9 +418,4 @@ class UsersController extends AppController
|
|||
}
|
||||
$this->viewBuilder()->setLayout('login');
|
||||
}
|
||||
|
||||
public function test()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Entity;
|
||||
|
||||
use App\Model\Entity\AppModel;
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
class PermissionLimitation extends AppModel
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
use Cake\Error\Debugger;
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
class PermissionLimitationsTable extends AppTable
|
||||
{
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->setDisplayField('permission');
|
||||
}
|
||||
|
||||
public function validationDefault(Validator $validator): Validator
|
||||
{
|
||||
$validator
|
||||
->notEmptyString('permission')
|
||||
->notEmptyString('scope')
|
||||
->requirePresence(['permission', 'scope', 'max_occurrence'], 'create');
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function getListOfLimitations(\App\Model\Entity\User $data)
|
||||
{
|
||||
$Users = TableRegistry::getTableLocator()->get('Users');
|
||||
$ownOrgUserIds = $Users->find('list', [
|
||||
'keyField' => 'id',
|
||||
'valueField' => 'id',
|
||||
'conditions' => [
|
||||
'organisation_id' => $data['organisation_id']
|
||||
]
|
||||
])->all()->toList();
|
||||
$MetaFields = TableRegistry::getTableLocator()->get('MetaFields');
|
||||
$raw = $this->find()->select(['scope', 'permission', 'max_occurrence'])->disableHydration()->toArray();
|
||||
$limitations = [];
|
||||
foreach ($raw as $entry) {
|
||||
$limitations[$entry['permission']][$entry['scope']] = [
|
||||
'limit' => $entry['max_occurrence']
|
||||
];
|
||||
}
|
||||
foreach ($limitations as $field => $data) {
|
||||
if (isset($data['global'])) {
|
||||
$limitations[$field]['global']['current'] = $MetaFields->find('all', [
|
||||
'conditions' => [
|
||||
'scope' => 'user',
|
||||
'field' => $field
|
||||
]
|
||||
])->count();
|
||||
}
|
||||
if (isset($data['global'])) {
|
||||
$limitations[$field]['organisation']['current'] = $MetaFields->find('all', [
|
||||
'conditions' => [
|
||||
'scope' => 'user',
|
||||
'field' => $field,
|
||||
'parent_id IN' => array_values($ownOrgUserIds)
|
||||
]
|
||||
])->count();
|
||||
}
|
||||
}
|
||||
return $limitations;
|
||||
}
|
||||
|
||||
public function attachLimitations(\App\Model\Entity\User $data)
|
||||
{
|
||||
$permissionLimitations = $this->getListOfLimitations($data);
|
||||
$icons = [
|
||||
'global' => 'globe',
|
||||
'organisation' => 'sitemap'
|
||||
|
||||
];
|
||||
if (!empty($data['MetaTemplates'])) {
|
||||
foreach ($data['MetaTemplates'] as &$metaTemplate) {
|
||||
foreach ($metaTemplate['meta_template_fields'] as &$meta_template_field) {
|
||||
$boolean = $meta_template_field['type'] === 'boolean';
|
||||
foreach ($meta_template_field['metaFields'] as &$metaField) {
|
||||
if ($boolean) {
|
||||
$metaField['value'] = '<i class="fas fa-' . ((bool)$metaField['value'] ? 'check' : 'times') . '"></i>';
|
||||
$metaField['no_escaping'] = true;
|
||||
}
|
||||
if (isset($permissionLimitations[$metaField['field']])) {
|
||||
foreach ($permissionLimitations[$metaField['field']] as $scope => $value) {
|
||||
$messageType = 'warning';
|
||||
if ($value['limit'] > $value['current']) {
|
||||
$messageType = 'info';
|
||||
}
|
||||
if ($value['limit'] < $value['current']) {
|
||||
$messageType = 'danger';
|
||||
}
|
||||
if (empty($metaField[$messageType])) {
|
||||
$metaField[$messageType] = '';
|
||||
}
|
||||
$altText = __(
|
||||
'There is a limitation enforced on the number of users with this permission {0}. Currently {1} slot(s) are used up of a maximum of {2} slot(s).',
|
||||
$scope === 'global' ? __('instance wide') : __('for your organisation'),
|
||||
$value['current'],
|
||||
$value['limit']
|
||||
);
|
||||
$metaField[$messageType] .= sprintf(
|
||||
' <span title="%s"><span class="text-dark"><i class="fas fa-%s"></i>: </span>%s/%s</span>',
|
||||
$altText,
|
||||
$icons[$scope],
|
||||
$value['current'],
|
||||
$value['limit']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -59,17 +59,61 @@ class UsersTable extends AppTable
|
|||
|
||||
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
||||
{
|
||||
if (isset($data['username'])) {
|
||||
$data['username'] = trim(mb_strtolower($data['username']));
|
||||
}
|
||||
}
|
||||
|
||||
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
|
||||
{
|
||||
if (!$entity->isNew()) {
|
||||
$success = $this->handleUserUpdateRouter($entity);
|
||||
}
|
||||
$permissionRestrictionCheck = $this->checkPermissionRestrictions($entity);
|
||||
if ($permissionRestrictionCheck !== true) {
|
||||
$entity->setErrors($permissionRestrictionCheck);
|
||||
$event->stopPropagation();
|
||||
$event->setResult(false);
|
||||
return false;
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
private function checkPermissionRestrictions(EntityInterface $entity)
|
||||
{
|
||||
if (!isset($this->PermissionLimitations)) {
|
||||
$this->PermissionLimitations = TableRegistry::get('PermissionLimitations');
|
||||
}
|
||||
$new = $entity->isNew();
|
||||
$permissions = $this->PermissionLimitations->getListOfLimitations($entity);
|
||||
foreach ($permissions as $permission_name => $permission) {
|
||||
foreach ($permission as $scope => $permission_data) {
|
||||
if (!empty($entity['meta_fields'])) {
|
||||
$enabled = false;
|
||||
foreach ($entity['meta_fields'] as $metaField) {
|
||||
if ($metaField['field'] === $permission_name) {
|
||||
$enabled = true;
|
||||
}
|
||||
}
|
||||
if (!$enabled) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$valueToCompareTo = $permission_data['current'] + ($new ? 1 : 0);
|
||||
if ($valueToCompareTo > $permission_data['limit']) {
|
||||
return [
|
||||
$permission_name =>
|
||||
__(
|
||||
'{0} limit exceeded.',
|
||||
$scope
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function initAuthBehaviors()
|
||||
{
|
||||
if (!empty(Configure::read('keycloak'))) {
|
||||
|
@ -80,6 +124,7 @@ class UsersTable extends AppTable
|
|||
public function validationDefault(Validator $validator): Validator
|
||||
{
|
||||
$validator
|
||||
->setStopOnFailure()
|
||||
->requirePresence(['password'], 'create')
|
||||
->add('password', [
|
||||
'password_complexity' => [
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'description' => __(
|
||||
'Add a limitation of how many users can have the given permission. The scope applies the limitation globally or for a given organisation.
|
||||
Permissions can be valid role permissions or any user meta field.
|
||||
An example: perm_misp global limit 500, organisation limit 10 would ensure that there are a maximum of 500 MISP admitted users on the instance, limiting the number of users to 10 / org.'
|
||||
),
|
||||
'model' => 'PermissionLimitation',
|
||||
'fields' => [
|
||||
[
|
||||
'field' => 'scope',
|
||||
'type' => 'dropdown',
|
||||
'label' => 'Scope',
|
||||
'options' => [
|
||||
'global' => 'global',
|
||||
'organisation' => 'organisation'
|
||||
]
|
||||
],
|
||||
[
|
||||
'field' => 'permission'
|
||||
],
|
||||
[
|
||||
'field' => 'max_occurrence',
|
||||
'label' => 'Limit'
|
||||
],
|
||||
[
|
||||
'field' => 'comment',
|
||||
'label' => 'Comment'
|
||||
]
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
]
|
||||
]
|
||||
]);
|
||||
?>
|
||||
</div>
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'simple',
|
||||
'children' => [
|
||||
'data' => [
|
||||
'type' => 'simple',
|
||||
'text' => __('Add permission limitation'),
|
||||
'class' => 'btn btn-primary',
|
||||
'popover_url' => '/PermissionLimitations/add'
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Search'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'value'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'name' => '#',
|
||||
'sort' => 'id',
|
||||
'data_path' => 'id',
|
||||
],
|
||||
[
|
||||
'name' => __('Scope'),
|
||||
'sort' => 'scope',
|
||||
'data_path' => 'scope',
|
||||
],
|
||||
[
|
||||
'name' => __('Permission'),
|
||||
'sort' => 'permission',
|
||||
'data_path' => 'permission'
|
||||
],
|
||||
[
|
||||
'name' => __('Limit'),
|
||||
'sort' => 'max_occurrence',
|
||||
'data_path' => 'max_occurrence'
|
||||
],
|
||||
[
|
||||
'name' => __('Comment'),
|
||||
'sort' => 'comment',
|
||||
'data_path' => 'comment'
|
||||
]
|
||||
],
|
||||
'title' => __('Permission Limitations Index'),
|
||||
'description' => __('A list of configurable user roles. Create or modify user access roles based on the settings below.'),
|
||||
'pull' => 'right',
|
||||
'actions' => [
|
||||
[
|
||||
'url' => '/permissionLimitations/view',
|
||||
'url_params_data_paths' => ['id'],
|
||||
'icon' => 'eye'
|
||||
],
|
||||
[
|
||||
'open_modal' => '/permissionLimitations/edit/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'edit',
|
||||
'requirement' => !empty($loggedUser['role']['perm_admin'])
|
||||
],
|
||||
[
|
||||
'open_modal' => '/permissionLimitations/delete/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'trash',
|
||||
'requirement' => !empty($loggedUser['role']['perm_admin'])
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
echo '</div>';
|
||||
?>
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
echo $this->element(
|
||||
'/genericElements/SingleViews/single_view',
|
||||
[
|
||||
'data' => $entity,
|
||||
'fields' => [
|
||||
[
|
||||
'key' => __('ID'),
|
||||
'path' => 'id'
|
||||
],
|
||||
[
|
||||
'key' => __('Scope'),
|
||||
'path' => 'scope'
|
||||
],
|
||||
[
|
||||
'key' => __('Permission'),
|
||||
'path' => 'permission'
|
||||
],
|
||||
[
|
||||
'key' => __('Limit'),
|
||||
'path' => 'limit'
|
||||
],
|
||||
[
|
||||
'key' => __('Comment'),
|
||||
'path' => 'comment'
|
||||
]
|
||||
],
|
||||
'children' => []
|
||||
]
|
||||
);
|
Loading…
Reference in New Issue