Merge branch 'develop' into main

pull/92/head v1.1
iglocska 2021-11-24 01:53:10 +01:00
commit 8c8aba3fbc
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
23 changed files with 270 additions and 58 deletions

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
composer.lock
config/app_local.php
config/Migrations/schema-dump-default.lock
logs
tmp
vendor
webroot/theme/node_modules
.vscode
docker/run/

View File

@ -9,7 +9,7 @@
"admad/cakephp-social-auth": "^1.1",
"cakephp/authentication": "^2.0",
"cakephp/authorization": "^2.0",
"cakephp/cakephp": "^4.0",
"cakephp/cakephp": "^4.3",
"cakephp/migrations": "^3.0",
"cakephp/plugin-installer": "^1.2",
"erusev/parsedown": "^1.7",

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class UserOrg extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$exists = $this->hasTable('users');
if (!$exists) {
$alignments = $this->table('users')
->addColumn('organisation_id', 'integer', [
'default' => null,
'null' => true,
'signed' => false,
'length' => 10
])
->addIndex('org_id')
->update();
}
$q1 = $this->getQueryBuilder();
$org_id = $q1->select(['min(id)'])->from('organisations')->execute()->fetchAll()[0][0];
if (!empty($org_id)) {
$q2 = $this->getQueryBuilder();
$q2->update('users')
->set('organisation_id', $org_id)
->where(['organisation_id IS NULL'])
->execute();
}
}
}

View File

@ -102,7 +102,7 @@ class AppController extends Controller
$this->ACL->setPublicInterfaces();
if (!empty($this->request->getAttribute('identity'))) {
$user = $this->Users->get($this->request->getAttribute('identity')->getIdentifier(), [
'contain' => ['Roles', 'Individuals' => 'Organisations', 'UserSettings']
'contain' => ['Roles', 'Individuals' => 'Organisations', 'UserSettings', 'Organisations']
]);
if (!empty($user['disabled'])) {
$this->Authentication->logout();
@ -113,8 +113,10 @@ class AppController extends Controller
$this->ACL->setUser($user);
$this->request->getSession()->write('authUser', $user);
$this->isAdmin = $user['role']['perm_admin'];
$this->set('menu', $this->ACL->getMenu());
$this->set('loggedUser', $this->ACL->getUser());
if (!$this->ParamHandler->isRest()) {
$this->set('menu', $this->ACL->getMenu());
$this->set('loggedUser', $this->ACL->getUser());
}
} else if ($this->ParamHandler->isRest()) {
throw new MethodNotAllowedException(__('Invalid user credentials.'));
}
@ -153,12 +155,11 @@ class AppController extends Controller
if (!empty($authKey)) {
$this->loadModel('Users');
$user = $this->Users->get($authKey['user_id']);
$user = $logModel->userInfo();
$logModel->insert([
'action' => 'login',
'request_action' => 'login',
'model' => 'Users',
'model_id' => $user['id'],
'model_title' => $user['name'],
'model_title' => $user['username'],
'change' => []
]);
if (!empty($user)) {
@ -167,7 +168,7 @@ class AppController extends Controller
} else {
$user = $logModel->userInfo();
$logModel->insert([
'action' => 'login',
'request_action' => 'login',
'model' => 'Users',
'model_id' => $user['id'],
'model_title' => $user['name'],

View File

@ -11,8 +11,8 @@ use Cake\Core\Configure;
class AuditLogsController extends AppController
{
public $filterFields = ['model_id', 'model', 'action', 'user_id', 'title'];
public $quickFilterFields = ['model', 'action', 'title'];
public $filterFields = ['model_id', 'model', 'request_action', 'user_id', 'title'];
public $quickFilterFields = ['model', 'request_action', 'title'];
public $containFields = ['Users'];
public function index()

View File

@ -16,15 +16,25 @@ class AuthKeysController extends AppController
{
public $filterFields = ['Users.username', 'authkey', 'comment', 'Users.id'];
public $quickFilterFields = ['authkey', ['comment' => true]];
public $containFields = ['Users'];
public $containFields = ['Users' => ['fields' => ['id', 'username']]];
public function index()
{
$currentUser = $this->ACL->getUser();
$conditions = [];
if (empty($currentUser['role']['perm_admin'])) {
$conditions['Users.organisation_id'] = $currentUser['organisation_id'];
if (empty($currentUser['role']['perm_org_admin'])) {
$conditions['Users.id'] = $currentUser['id'];
}
}
$this->CRUD->index([
'filters' => $this->filterFields,
'quickFilters' => $this->quickFilterFields,
'contain' => $this->containFields,
'exclude_fields' => ['authkey']
'exclude_fields' => ['authkey'],
'conditions' => $conditions,
'hidden' => []
]);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
@ -35,7 +45,15 @@ class AuthKeysController extends AppController
public function delete($id)
{
$this->CRUD->delete($id);
$currentUser = $this->ACL->getUser();
$conditions = [];
if (empty($currentUser['role']['perm_admin'])) {
$conditions['Users.organisation_id'] = $currentUser['organisation_id'];
if (empty($currentUser['role']['perm_org_admin'])) {
$conditions['Users.id'] = $currentUser['id'];
}
}
$this->CRUD->delete($id, ['conditions' => $conditions, 'contain' => 'Users']);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;

View File

@ -145,24 +145,24 @@ class ACLComponent extends Component
'view' => ['*']
],
'SharingGroups' => [
'add' => ['perm_admin'],
'addOrg' => ['perm_admin'],
'delete' => ['perm_admin'],
'edit' => ['perm_admin'],
'add' => ['perm_org_admin'],
'addOrg' => ['perm_org_admin'],
'delete' => ['perm_org_admin'],
'edit' => ['perm_org_admin'],
'index' => ['*'],
'listOrgs' => ['*'],
'removeOrg' => ['perm_admin'],
'removeOrg' => ['perm_org_admin'],
'view' => ['*']
],
'Users' => [
'add' => ['perm_admin'],
'delete' => ['perm_admin'],
'add' => ['perm_org_admin'],
'delete' => ['perm_org_admin'],
'edit' => ['*'],
'index' => ['perm_admin'],
'index' => ['perm_org_admin'],
'login' => ['*'],
'logout' => ['*'],
'register' => ['*'],
'toggle' => ['perm_admin'],
'toggle' => ['perm_org_admin'],
'view' => ['*']
]
);
@ -290,6 +290,12 @@ class ACLComponent extends Component
if ($allConditionsMet) {
return true;
}
} else {
foreach ($this->aclList[$controller][$action] as $permission) {
if ($this->user['role'][$permission]) {
return true;
}
}
}
}
return false;

View File

@ -50,6 +50,9 @@ class CRUDComponent extends Component
}
$query = $this->setFilters($params, $query, $options);
$query = $this->setQuickFilters($params, $query, empty($options['quickFilters']) ? [] : $options['quickFilters']);
if (!empty($options['conditions'])) {
$query->where($options['conditions']);
}
if (!empty($options['contain'])) {
$query->contain($options['contain']);
}
@ -284,7 +287,14 @@ class CRUDComponent extends Component
$params['contain'][] = 'Tags';
$this->setAllTags();
}
$data = $this->Table->get($id, isset($params['get']) ? $params['get'] : $params);
$data = $this->Table->find()->where(['id' => $id]);
if (!empty($params['conditions'])) {
$data->where($params['conditions']);
}
$data = $data->first();
if (empty($data)) {
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
}
$data = $this->getMetaFields($id, $data);
if (!empty($params['fields'])) {
$this->Controller->set('fields', $params['fields']);
@ -414,11 +424,21 @@ class CRUDComponent extends Component
$this->Controller->set('entity', $data);
}
public function delete($id=false): void
public function delete($id=false, $params=[]): void
{
if ($this->request->is('get')) {
if(!empty($id)) {
$data = $this->Table->get($id);
$data = $this->Table->find()->where([$this->Table->getAlias() . '.id' => $id]);
if (!empty($params['conditions'])) {
$data->where($params['conditions']);
}
if (!empty($params['contain'])) {
$data->contain($params['contain']);
}
$data = $data->first();
if (empty($data)) {
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
}
$this->Controller->set('id', $data['id']);
$this->Controller->set('data', $data);
$this->Controller->set('bulkEnabled', false);
@ -430,9 +450,20 @@ class CRUDComponent extends Component
$isBulk = count($ids) > 1;
$bulkSuccesses = 0;
foreach ($ids as $id) {
$data = $this->Table->get($id);
$success = $this->Table->delete($data);
$success = true;
$data = $this->Table->find()->where([$this->Table->getAlias() . '.id' => $id]);
if (!empty($params['conditions'])) {
$data->where($params['conditions']);
}
if (!empty($params['contain'])) {
$data->contain($params['contain']);
}
$data = $data->first();
if (!empty($data)) {
$success = $this->Table->delete($data);
$success = true;
} else {
$success = false;
}
if ($success) {
$bulkSuccesses++;
}

View File

@ -49,7 +49,31 @@ class EncryptionKeysController extends AppController
public function add()
{
$this->CRUD->add(['redirect' => $this->referer()]);
$orgConditions = [];
$currentUser = $this->ACL->getUser();
$params = ['redirect' => $this->referer()];
if (empty($currentUser['role']['perm_admin'])) {
$params['beforeSave'] = function($entity) {
if ($entity['owner_model'] === 'organisation') {
$entity['owner_id'] = $currentUser['organisation_id'];
} else {
if ($currentUser['role']['perm_org_admin']) {
$validIndividuals = $this->Organisations->Alignments->find('list', [
'fields' => ['distinct(individual_id)'],
'conditions' => ['organisation_id' => $currentUser['organisation_id']]
]);
if (!in_array($entity['owner_id'], $validIndividuals)) {
throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.'));
}
} else {
if ($entity['owner_id'] !== $currentUser['id']) {
throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.'));
}
}
}
};
}
$this->CRUD->add($params);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
@ -58,7 +82,8 @@ class EncryptionKeysController extends AppController
$this->loadModel('Individuals');
$dropdownData = [
'organisation' => $this->Organisations->find('list', [
'sort' => ['name' => 'asc']
'sort' => ['name' => 'asc'],
'conditions' => $orgConditions
]),
'individual' => $this->Individuals->find('list', [
'sort' => ['email' => 'asc']
@ -70,12 +95,19 @@ class EncryptionKeysController extends AppController
public function edit($id = false)
{
$conditions = [];
$currentUser = $this->ACL->getUser();
$params = [
'fields' => [
'type', 'encryption_key', 'revoked'
],
'redirect' => $this->referer()
];
if (empty($currentUser['role']['perm_admin'])) {
if (empty($currentUser['role']['perm_org_admin'])) {
}
}
$this->CRUD->edit($id, $params);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {

View File

@ -16,10 +16,16 @@ class SharingGroupsController extends AppController
public function index()
{
$currentUser = $this->ACL->getUser();
$conditions = [];
if (empty($currentUser['role']['perm_admin'])) {
$conditions['SharingGroups.organisation_id'] = $currentUser['organisation_id'];
}
$this->CRUD->index([
'contain' => $this->containFields,
'filters' => $this->filterFields,
'quickFilters' => $this->quickFilterFields
'quickFilters' => $this->quickFilterFields,
'conditions' => $conditions
]);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
@ -60,7 +66,12 @@ class SharingGroupsController extends AppController
public function edit($id = false)
{
$this->CRUD->edit($id);
$params = [];
$currentUser = $this->ACL->getUser();
if (empty($currentUser['role']['perm_admin'])) {
$params['conditions'] = ['organisation_id' => $currentUser['organisation_id']];
}
$this->CRUD->edit($id, $params);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
@ -206,11 +217,11 @@ class SharingGroupsController extends AppController
$organisations = [];
if (!empty($user['role']['perm_admin'])) {
$organisations = $this->SharingGroups->Organisations->find('list')->order(['name' => 'ASC'])->toArray();
} else if (!empty($user['individual']['organisations'])) {
} else {
$organisations = $this->SharingGroups->Organisations->find('list', [
'sort' => ['name' => 'asc'],
'conditions' => [
'id IN' => array_values(\Cake\Utility\Hash::extract($user, 'individual.organisations.{n}.id'))
'id' => $user['organisation_id']
]
]);
}

View File

@ -11,16 +11,22 @@ use Cake\Core\Configure;
class UsersController extends AppController
{
public $filterFields = ['Individuals.uuid', 'username', 'Individuals.email', 'Individuals.first_name', 'Individuals.last_name'];
public $filterFields = ['Individuals.uuid', 'username', 'Individuals.email', 'Individuals.first_name', 'Individuals.last_name', 'Organisations.name'];
public $quickFilterFields = ['Individuals.uuid', ['username' => true], ['Individuals.first_name' => true], ['Individuals.last_name' => true], 'Individuals.email'];
public $containFields = ['Individuals', 'Roles', 'UserSettings'];
public $containFields = ['Individuals', 'Roles', 'UserSettings', 'Organisations'];
public function index()
{
$currentUser = $this->ACL->getUser();
$conditions = [];
if (empty($currentUser['role']['perm_admin'])) {
$conditions['organisation_id'] = $currentUser['organisation_id'];
}
$this->CRUD->index([
'contain' => $this->containFields,
'filters' => $this->filterFields,
'quickFilters' => $this->quickFilterFields,
'conditions' => $conditions
]);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
@ -31,8 +37,12 @@ class UsersController extends AppController
public function add()
{
$currentUser = $this->ACL->getUser();
$this->CRUD->add([
'beforeSave' => function($data) {
'beforeSave' => function($data) use ($currentUser) {
if (!$currentUser['role']['perm_admin']) {
$data['organisation_id'] = $currentUser['organisation_id'];
}
$this->Users->enrollUserRouter($data);
return $data;
}
@ -41,12 +51,28 @@ class UsersController extends AppController
if (!empty($responsePayload)) {
return $responsePayload;
}
/*
$alignments = $this->Users->Individuals->Alignments->find('list', [
//'keyField' => 'id',
'valueField' => 'organisation_id',
'groupField' => 'individual_id'
])->toArray();
$alignments = array_map(function($value) { return array_values($value); }, $alignments);
*/
$org_conditions = [];
if (empty($currentUser['role']['perm_admin'])) {
$org_conditions = ['id' => $currentUser['organisation_id']];
}
$dropdownData = [
'role' => $this->Users->Roles->find('list', [
'sort' => ['name' => 'asc']
]),
'individual' => $this->Users->Individuals->find('list', [
'sort' => ['email' => 'asc']
]),
'organisation' => $this->Users->Organisations->find('list', [
'sort' => ['name' => 'asc'],
'conditions' => $org_conditions
])
];
$this->set(compact('dropdownData'));
@ -59,7 +85,7 @@ class UsersController extends AppController
$id = $this->ACL->getUser()['id'];
}
$this->CRUD->view($id, [
'contain' => ['Individuals' => ['Alignments' => 'Organisations'], 'Roles']
'contain' => ['Individuals' => ['Alignments' => 'Organisations'], 'Roles', 'Organisations']
]);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
@ -70,9 +96,11 @@ class UsersController extends AppController
public function edit($id = false)
{
if (empty($id) || empty($this->ACL->getUser()['role']['perm_admin'])) {
$id = $this->ACL->getUser()['id'];
$currentUser = $this->ACL->getUser();
if (empty($id) || (empty($currentUser['role']['perm_org_admin']) && empty($currentUser['role']['perm_site_admin']))) {
$id = $currentUser['id'];
}
$params = [
'get' => [
'fields' => [
@ -88,6 +116,7 @@ class UsersController extends AppController
];
if (!empty($this->ACL->getUser()['role']['perm_admin'])) {
$params['fields'][] = 'role_id';
$params['fields'][] = 'organisation_id';
}
$this->CRUD->edit($id, $params);
$responsePayload = $this->CRUD->getResponsePayload();
@ -100,6 +129,9 @@ class UsersController extends AppController
]),
'individual' => $this->Users->Individuals->find('list', [
'sort' => ['email' => 'asc']
]),
'organisation' => $this->Users->Organisations->find('list', [
'sort' => ['name' => 'asc']
])
];
$this->set(compact('dropdownData'));

View File

@ -129,14 +129,13 @@ class AuditLogBehavior extends Behavior
$modelTitle = $entity[$modelTitleField];
}
$logEntity = $this->auditLogs()->newEntity([
$this->auditLogs()->insert([
'request_action' => $entity->getConstant('ACTION_DELETE'),
'model' => $entity->getSource(),
'model_id' => $this->old->id,
'model_title' => $modelTitle,
'change' => $this->changedFields($entity)
]);
$logEntity->save();
}
/**

View File

@ -98,7 +98,7 @@ class AuthKeycloakBehavior extends Behavior
{
$individual = $this->_table->Individuals->find()->where(
['id' => $data['individual_id']]
)->contain(['Organisations'])->first();
)->first();
$roleConditions = [
'id' => $data['role_id']
];
@ -106,10 +106,9 @@ class AuthKeycloakBehavior extends Behavior
$roleConditions['name'] = Configure::read('keycloak.default_role_name');
}
$role = $this->_table->Roles->find()->where($roleConditions)->first();
$orgs = [];
foreach ($individual['organisations'] as $org) {
$orgs[] = $org['uuid'];
}
$org = $this->_table->Organisations->find()->where([
['id' => $data['organisation_id']]
]);
$token = $this->getAdminAccessToken();
$keyCloakUser = [
'firstName' => $individual['first_name'],
@ -118,7 +117,7 @@ class AuthKeycloakBehavior extends Behavior
'email' => $individual['email'],
'attributes' => [
'role_name' => empty($role['name']) ? Configure::read('keycloak.default_role_name') : $role['name'],
'org_uuid' => empty($orgs[0]) ? '' : $orgs[0]
'org_uuid' => $orgs['uuid']
]
];
$keycloakConfig = Configure::read('keycloak');

View File

@ -36,6 +36,13 @@ class UsersTable extends AppTable
'cascadeCallbacks' => false
]
);
$this->belongsTo(
'Organisations',
[
'dependent' => false,
'cascadeCallbacks' => false
]
);
$this->hasMany(
'UserSettings',
[

View File

@ -45,8 +45,8 @@ echo $this->element('genericElements/IndexTable/index_table', [
],
[
'name' => __('Action'),
'sort' => 'action',
'data_path' => 'action',
'sort' => 'request_action',
'data_path' => 'request_action',
],
[
'name' => __('Change'),

View File

@ -94,12 +94,14 @@ echo $this->element('genericElements/IndexTable/index_table', [
[
'open_modal' => '/organisations/edit/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'edit'
'icon' => 'edit',
'requirement' => $loggedUser['role']['perm_admin']
],
[
'open_modal' => '/organisations/delete/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'trash'
'icon' => 'trash',
'requirement' => $loggedUser['role']['perm_admin']
],
]
]

View File

@ -78,12 +78,14 @@ echo $this->element('genericElements/IndexTable/index_table', [
[
'open_modal' => '/roles/edit/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'edit'
'icon' => 'edit',
'requirement' => !empty($loggedUser['role']['perm_site_admin'])
],
[
'open_modal' => '/roles/delete/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'trash'
'icon' => 'trash',
'requirement' => !empty($loggedUser['role']['perm_site_admin'])
],
]
]

View File

@ -35,6 +35,11 @@ echo $this->element('genericElements/IndexTable/index_table', [
'class' => 'short',
'data_path' => 'name',
],
[
'name' => __('Owner'),
'class' => 'short',
'data_path' => 'organisation.name'
],
[
'name' => __('UUID'),
'sort' => 'uuid',

View File

@ -14,6 +14,12 @@
'field' => 'username',
'autocomplete' => 'off'
],
[
'field' => 'organisation_id',
'type' => 'dropdown',
'label' => __('Associated organisation'),
'options' => $dropdownData['organisation']
],
[
'field' => 'password',
'label' => __('Password'),

View File

@ -51,6 +51,13 @@ echo $this->element('genericElements/IndexTable/index_table', [
'sort' => 'username',
'data_path' => 'username',
],
[
'name' => __('Organisation'),
'sort' => 'organisation.name',
'data_path' => 'organisation.name',
'url' => '/organisations/view/{{0}}',
'url_vars' => ['organisation.id']
],
[
'name' => __('Email'),
'sort' => 'individual.email',

View File

@ -21,6 +21,14 @@ echo $this->element(
'path' => 'individual.email'
],
[
'type' => 'generic',
'key' => __('Organisation'),
'path' => 'organisation.name',
'url' => '/organisations/view/{{0}}',
'url_vars' => 'organisation.id'
],
[
'type' => 'generic',
'key' => __('Role'),
'path' => 'role.name',
'url' => '/roles/view/{{0}}',

View File

@ -8,10 +8,10 @@ use Cake\Routing\Router;
</a>
<div class="dropdown-menu dropdown-menu-end">
<h6 class="dropdown-header">
<div class="fw-light"><?= __('Loggin in as') ?></div>
<div class="fw-light"><?= __('Logged in as') ?></div>
<div>
<?= $this->SocialProvider->getIcon($this->request->getAttribute('identity')) ?>
<strong><?= h($this->request->getAttribute('identity')['username']) ?></strong>
[<?= h($loggedUser['organisation']['name']) ?>] <strong><?= h($this->request->getAttribute('identity')['username']) ?></strong>
</div>
</h6>
<div class="dropdown-divider"></div>
@ -35,4 +35,4 @@ use Cake\Routing\Router;
</div>
<style>
</style>
</style>

View File

@ -30,7 +30,7 @@ body {
transform: rotate(100deg);
-webkit-transform-origin: center;
transform-origin: center;
left: 42%;
left: calc(50% - 120px);
top: 30%;
}