commit
76bb82045f
|
@ -0,0 +1,231 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Cake\Console\Command;
|
||||||
|
use Cake\Console\Arguments;
|
||||||
|
use Cake\Console\ConsoleIo;
|
||||||
|
use Cake\Console\ConsoleOptionParser;
|
||||||
|
use Cake\Filesystem\File;
|
||||||
|
use Cake\Utility\Hash;
|
||||||
|
use Cake\Utility\Text;
|
||||||
|
use Cake\Validation\Validator;
|
||||||
|
use Cake\Http\Client;
|
||||||
|
use Cake\I18n\FrozenTime;
|
||||||
|
|
||||||
|
class SummaryCommand extends Command
|
||||||
|
{
|
||||||
|
protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
|
||||||
|
{
|
||||||
|
$parser->setDescription('Create a summary for data associated to the passed nationality that has been modified.');
|
||||||
|
$parser->addArgument('nationality', [
|
||||||
|
'short' => 'n',
|
||||||
|
'help' => 'The organisation nationality.',
|
||||||
|
'required' => false
|
||||||
|
]);
|
||||||
|
$parser->addOption('days', [
|
||||||
|
'short' => 'd',
|
||||||
|
'help' => 'The amount of days to look back in the logs',
|
||||||
|
'default' => 7
|
||||||
|
]);
|
||||||
|
return $parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(Arguments $args, ConsoleIo $io)
|
||||||
|
{
|
||||||
|
$this->__loadTables();
|
||||||
|
$this->io = $io;
|
||||||
|
$nationality = $args->getArgument('nationality');
|
||||||
|
$days = $args->getOption('days');
|
||||||
|
if (!is_null($nationality)) {
|
||||||
|
$nationalities = [$nationality];
|
||||||
|
} else {
|
||||||
|
$nationalities = $this->_fetchOrgNationalities();
|
||||||
|
}
|
||||||
|
foreach ($nationalities as $nationality) {
|
||||||
|
$this->io->out(sprintf('Nationality: %s', $nationality));
|
||||||
|
$this->_collectChangedForNationality($nationality, $days);
|
||||||
|
$this->io->out($io->nl(2));
|
||||||
|
$this->io->hr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _collectChangedForNationality($nationality, $days)
|
||||||
|
{
|
||||||
|
$filename = sprintf('/tmp/%s.txt', $nationality);
|
||||||
|
$file_input = fopen($filename, 'w');
|
||||||
|
$organisationIDsForNationality = $this->_fetchOrganisationsForNationality($nationality);
|
||||||
|
if (empty($organisationIDsForNationality)) {
|
||||||
|
$message = sprintf('No changes for organisations with nationality `%s`', $nationality);
|
||||||
|
fwrite($file_input, $message);
|
||||||
|
$this->io->warning($message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$userForOrg = $this->_fetchUserForOrg($organisationIDsForNationality);
|
||||||
|
$userID = Hash::extract($userForOrg, '{n}.id');
|
||||||
|
$individualID = Hash::extract($userForOrg, '{n}.individual_id');
|
||||||
|
|
||||||
|
$message = 'Modified users:' . PHP_EOL;
|
||||||
|
fwrite($file_input, $message);
|
||||||
|
$this->io->out($message);
|
||||||
|
$logsUsers = $this->_fetchLogsForUsers($userID, $days);
|
||||||
|
$modifiedUsers = $this->_formatLogsForTable($logsUsers);
|
||||||
|
foreach ($modifiedUsers as $row) {
|
||||||
|
fputcsv($file_input, $row);
|
||||||
|
}
|
||||||
|
$this->io->helper('Table')->output($modifiedUsers);
|
||||||
|
|
||||||
|
$message = PHP_EOL . 'Modified organisations:' . PHP_EOL;
|
||||||
|
fwrite($file_input, $message);
|
||||||
|
$this->io->out($message);
|
||||||
|
$logsOrgs = $this->_fetchLogsForOrgs($organisationIDsForNationality, $days);
|
||||||
|
$modifiedOrgs = $this->_formatLogsForTable($logsOrgs);
|
||||||
|
foreach ($modifiedOrgs as $row) {
|
||||||
|
fputcsv($file_input, $row);
|
||||||
|
}
|
||||||
|
$this->io->helper('Table')->output($modifiedOrgs);
|
||||||
|
|
||||||
|
$message = PHP_EOL . 'Modified individuals:' . PHP_EOL;
|
||||||
|
fwrite($file_input, $message);
|
||||||
|
$this->io->out($message);
|
||||||
|
$logsIndividuals = $this->_fetchLogsForIndividuals($individualID, $days);
|
||||||
|
$modifiedIndividuals = $this->_formatLogsForTable($logsIndividuals);
|
||||||
|
foreach ($modifiedIndividuals as $row) {
|
||||||
|
fputcsv($file_input, $row);
|
||||||
|
}
|
||||||
|
$this->io->helper('Table')->output($modifiedIndividuals);
|
||||||
|
fclose($file_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __loadTables()
|
||||||
|
{
|
||||||
|
$tables = ['Users', 'Organisations', 'Individuals', 'AuditLogs'];
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
$this->loadModel($table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _fetchOrganisationsForNationality(string $nationality): array
|
||||||
|
{
|
||||||
|
return array_keys($this->Organisations->find('list')
|
||||||
|
->where([
|
||||||
|
'nationality' => $nationality,
|
||||||
|
])
|
||||||
|
->all()
|
||||||
|
->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _fetchOrgNationalities(): array
|
||||||
|
{
|
||||||
|
return $this->Organisations->find()
|
||||||
|
->where([
|
||||||
|
'nationality !=' => '',
|
||||||
|
])
|
||||||
|
->all()
|
||||||
|
->extract('nationality')
|
||||||
|
->toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _fetchUserForOrg(array $orgIDs = []): array
|
||||||
|
{
|
||||||
|
if (empty($orgIDs)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $this->Users->find()
|
||||||
|
->contain(['Individuals', 'Roles', 'UserSettings', 'Organisations'])
|
||||||
|
->where([
|
||||||
|
'Organisations.id IN' => $orgIDs,
|
||||||
|
])
|
||||||
|
->enableHydration(false)
|
||||||
|
->all()->toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _fetchLogsForUsers(array $userIDs = [], int $days=7): array
|
||||||
|
{
|
||||||
|
if (empty($userIDs)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $this->_fetchLogs([
|
||||||
|
'contain' => ['Users'],
|
||||||
|
'conditions' => [
|
||||||
|
'model' => 'Users',
|
||||||
|
'request_action IN' => ['add', 'edit', 'delete'],
|
||||||
|
'model_id IN' => $userIDs,
|
||||||
|
'AuditLogs.created >=' => FrozenTime::now()->subDays($days),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _fetchLogsForOrgs(array $orgIDs = [], int $days = 7): array
|
||||||
|
{
|
||||||
|
if (empty($orgIDs)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $this->_fetchLogs([
|
||||||
|
'contain' => ['Users'],
|
||||||
|
'conditions' => [
|
||||||
|
'model' => 'Organisations',
|
||||||
|
'request_action IN' => ['add', 'edit', 'delete'],
|
||||||
|
'model_id IN' => $orgIDs,
|
||||||
|
'AuditLogs.created >=' => FrozenTime::now()->subDays($days),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _fetchLogsForIndividuals(array $individualID = [], int $days = 7): array
|
||||||
|
{
|
||||||
|
if (empty($individualID)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $this->_fetchLogs([
|
||||||
|
'contain' => ['Users'],
|
||||||
|
'conditions' => [
|
||||||
|
'model' => 'Individuals',
|
||||||
|
'request_action IN' => ['add', 'edit', 'delete'],
|
||||||
|
'model_id IN' => $individualID,
|
||||||
|
'AuditLogs.created >=' => FrozenTime::now()->subDays($days),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _fetchLogs(array $options=[]): array
|
||||||
|
{
|
||||||
|
$logs = $this->AuditLogs->find()
|
||||||
|
->contain($options['contain'])
|
||||||
|
->where($options['conditions'])
|
||||||
|
->enableHydration(false)
|
||||||
|
->all()->toList();
|
||||||
|
return array_map(function ($log) {
|
||||||
|
$log['changed'] = is_resource($log['changed']) ? stream_get_contents($log['changed']) : $log['changed'];
|
||||||
|
$log['changed'] = json_decode($log['changed']);
|
||||||
|
return $log;
|
||||||
|
}, $logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _formatLogsForTable($logEntries): array
|
||||||
|
{
|
||||||
|
$header = ['Model', 'Action', 'Editor user', 'Log ID', 'Datetime', 'Change'];
|
||||||
|
$data = [$header];
|
||||||
|
foreach ($logEntries as $logEntry) {
|
||||||
|
$formatted = [
|
||||||
|
$logEntry['model'],
|
||||||
|
$logEntry['request_action'],
|
||||||
|
sprintf('%s (%s)', $logEntry['user']['username'], $logEntry['user_id']),
|
||||||
|
$logEntry['id'],
|
||||||
|
$logEntry['created']->i18nFormat('yyyy-MM-dd HH:mm:ss'),
|
||||||
|
];
|
||||||
|
if ($logEntry['request_action'] == 'edit') {
|
||||||
|
$formatted[] = json_encode($logEntry['changed'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
} else {
|
||||||
|
$formatted[] = json_encode($logEntry['changed'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
$data[] = $formatted;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,12 @@ class IndividualsController extends AppController
|
||||||
'quickFilterForMetaField' => ['enabled' => true, 'wildcard_search' => true],
|
'quickFilterForMetaField' => ['enabled' => true, 'wildcard_search' => true],
|
||||||
'contain' => $this->containFields,
|
'contain' => $this->containFields,
|
||||||
'statisticsFields' => $this->statisticsFields,
|
'statisticsFields' => $this->statisticsFields,
|
||||||
|
'afterFind' => function($data) use ($currentUser) {
|
||||||
|
if ($currentUser['role']['perm_admin']) {
|
||||||
|
$data['user'] = $this->Individuals->Users->find()->select(['id', 'username', 'Organisations.id', 'Organisations.name'])->contain('Organisations')->where(['individual_id' => $data['id']])->all()->toArray();
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
$responsePayload = $this->CRUD->getResponsePayload();
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
if (!empty($responsePayload)) {
|
if (!empty($responsePayload)) {
|
||||||
|
@ -66,15 +72,29 @@ class IndividualsController extends AppController
|
||||||
|
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
|
$currentUser = $this->ACL->getUser();
|
||||||
|
if (!$currentUser['role']['perm_admin']) {
|
||||||
|
$validIndividuals = $this->Individuals->getValidIndividualsToEdit($currentUser);
|
||||||
|
if (!in_array($id, $validIndividuals)) {
|
||||||
|
throw new MethodNotAllowedException(__('You cannot modify that individual.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
$currentUser = $this->ACL->getUser();
|
$currentUser = $this->ACL->getUser();
|
||||||
$validIndividualIds = [];
|
$validIndividualIds = [];
|
||||||
if ($currentUser['role']['perm_admin']) {
|
if (!$currentUser['role']['perm_admin']) {
|
||||||
$validIndividualIds = $this->Individuals->getValidIndividualsToEdit($currentUser);
|
$validIndividualIds = $this->Individuals->getValidIndividualsToEdit($currentUser);
|
||||||
if (!isset($validIndividualIds[$id])) {
|
if (!in_array($id, $validIndividualIds)) {
|
||||||
throw new NotFoundException(__('Invalid individual.'));
|
throw new NotFoundException(__('Invalid individual.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->CRUD->edit($id);
|
$this->CRUD->edit($id, [
|
||||||
|
'beforeSave' => function($data) use ($currentUser) {
|
||||||
|
if ($currentUser['role']['perm_admin'] && isset($data['uuid'])) {
|
||||||
|
unset($data['uuid']);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
]);
|
||||||
$responsePayload = $this->CRUD->getResponsePayload();
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
if (!empty($responsePayload)) {
|
if (!empty($responsePayload)) {
|
||||||
return $responsePayload;
|
return $responsePayload;
|
||||||
|
|
|
@ -145,9 +145,6 @@ class AuthKeycloakBehavior extends Behavior
|
||||||
$roleConditions = [
|
$roleConditions = [
|
||||||
'id' => $data['role_id']
|
'id' => $data['role_id']
|
||||||
];
|
];
|
||||||
if (!empty(Configure::read('keycloak.user_management.actions'))) {
|
|
||||||
$roleConditions['name'] = Configure::read('keycloak.default_role_name');
|
|
||||||
}
|
|
||||||
$user = [
|
$user = [
|
||||||
'username' => $data['username'],
|
'username' => $data['username'],
|
||||||
'disabled' => false,
|
'disabled' => false,
|
||||||
|
|
|
@ -113,10 +113,12 @@ class IndividualsTable extends AppTable
|
||||||
|
|
||||||
public function getValidIndividualsToEdit(object $currentUser): array
|
public function getValidIndividualsToEdit(object $currentUser): array
|
||||||
{
|
{
|
||||||
|
$adminRoles = $this->Users->Roles->find('list')->select(['id'])->where(['perm_admin' => 1])->all()->toArray();
|
||||||
$validIndividualIds = $this->Users->find('list')->select(['individual_id'])->where(
|
$validIndividualIds = $this->Users->find('list')->select(['individual_id'])->where(
|
||||||
[
|
[
|
||||||
'organisation_id' => $currentUser['organisation_id'],
|
'organisation_id' => $currentUser['organisation_id'],
|
||||||
'disabled' => 0
|
'disabled' => 0,
|
||||||
|
'role_id NOT IN' => array_keys($adminRoles)
|
||||||
]
|
]
|
||||||
)->all()->toArray();
|
)->all()->toArray();
|
||||||
return array_keys($validIndividualIds);
|
return array_keys($validIndividualIds);
|
||||||
|
|
|
@ -208,27 +208,6 @@ class CerebrateSettingsProvider extends BaseSettingsProvider
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'keycloak.authoritative' => [
|
|
||||||
'name' => 'Authoritative',
|
|
||||||
'type' => 'boolean',
|
|
||||||
'severity' => 'info',
|
|
||||||
'description' => __('Override local role and organisation settings based on the settings in KeyCloak'),
|
|
||||||
'default' => false,
|
|
||||||
'dependsOn' => 'keycloak.enabled'
|
|
||||||
],
|
|
||||||
'keycloak.default_role_name' => [
|
|
||||||
'name' => 'Default role',
|
|
||||||
'type' => 'select',
|
|
||||||
'severity' => 'info',
|
|
||||||
'description' => __('Select the default role name to be used when creating users'),
|
|
||||||
'options' => function ($settingsProviders) {
|
|
||||||
$roleTable = TableRegistry::getTableLocator()->get('Roles');
|
|
||||||
$allRoleNames = $roleTable->find()->toArray();
|
|
||||||
$allRoleNames = array_column($allRoleNames, 'name');
|
|
||||||
return array_combine($allRoleNames, $allRoleNames);
|
|
||||||
},
|
|
||||||
'dependsOn' => 'keycloak.enabled'
|
|
||||||
],
|
|
||||||
'keycloak.screw' => [
|
'keycloak.screw' => [
|
||||||
'name' => 'Screw',
|
'name' => 'Screw',
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
|
|
|
@ -245,10 +245,6 @@ class UsersTable extends AppTable
|
||||||
{
|
{
|
||||||
$role = $this->Roles->find()->where(['name' => $user['role']['name']])->first();
|
$role = $this->Roles->find()->where(['name' => $user['role']['name']])->first();
|
||||||
if (empty($role)) {
|
if (empty($role)) {
|
||||||
if (!empty(Configure::read('keycloak.default_role_name'))) {
|
|
||||||
$default_role_name = Configure::read('keycloak.default_role_name');
|
|
||||||
$role = $this->Roles->find()->where(['name' => $default_role_name])->first();
|
|
||||||
}
|
|
||||||
if (empty($role)) {
|
if (empty($role)) {
|
||||||
throw new NotFoundException(__('Invalid role'));
|
throw new NotFoundException(__('Invalid role'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"version": "1.7",
|
"version": "1.8",
|
||||||
"application": "Cerebrate"
|
"application": "Cerebrate"
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,12 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'sort' => 'last_name',
|
'sort' => 'last_name',
|
||||||
'data_path' => 'last_name',
|
'data_path' => 'last_name',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Associated User(s)'),
|
||||||
|
'sort' => 'user',
|
||||||
|
'data_path' => 'user',
|
||||||
|
'element' => 'user'
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => __('Alignments'),
|
'name' => __('Alignments'),
|
||||||
'data_path' => 'alignments',
|
'data_path' => 'alignments',
|
||||||
|
|
|
@ -21,6 +21,9 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'placeholder' => __('Enter value to search'),
|
'placeholder' => __('Enter value to search'),
|
||||||
'data' => '',
|
'data' => '',
|
||||||
'searchKey' => 'value'
|
'searchKey' => 'value'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'table_action',
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
$kcurl = $keycloakConfig['provider']['baseUrl'] . '/realms/' . $keycloakConfig['provider']['realm'] . '/account/#/security/signingin';
|
if (!empty($keycloakConfig['enabled'])) {
|
||||||
|
$kcurl = $keycloakConfig['provider']['baseUrl'] . '/realms/' . $keycloakConfig['provider']['realm'] . '/account/#/security/signingin';
|
||||||
|
}
|
||||||
$fields = [
|
$fields = [
|
||||||
[
|
[
|
||||||
'key' => __('ID'),
|
'key' => __('ID'),
|
||||||
|
|
|
@ -1,11 +1,25 @@
|
||||||
<?php
|
<?php
|
||||||
if (!empty($row['user'])) {
|
if (!empty($row['user'])) {
|
||||||
$userId = $this->Hash->extract($row, 'user.id')[0];
|
if (isset($row['user']['id'])) {
|
||||||
$userName = $this->Hash->extract($row, 'user.username')[0];
|
$users = [$row['user']];
|
||||||
echo $this->Html->link(
|
} else {
|
||||||
h($userName),
|
$users = $row['user'];
|
||||||
['controller' => 'users', 'action' => 'view', $userId]
|
}
|
||||||
);
|
$links = [];
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$orgPrepend = '';
|
||||||
|
if (!empty($user['organisation']['name']) && !empty($user['organisation']['id'])) {
|
||||||
|
$orgPrepend = '[' . $this->Html->link(
|
||||||
|
h($user['organisation']['name']),
|
||||||
|
['controller' => 'organisations', 'action' => 'view', $user['organisation']['id']]
|
||||||
|
) . '] ';
|
||||||
|
}
|
||||||
|
$links[] = $orgPrepend . $this->Html->link(
|
||||||
|
h($user['username']),
|
||||||
|
['controller' => 'users', 'action' => 'view', $user['id']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo implode('<br />', $links);
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
Loading…
Reference in New Issue