new: [users:view] Added keycloak status showing the potential differences between Cerebrate and Keycloak
parent
21c5601c29
commit
af622dd19b
|
@ -21,11 +21,22 @@ class UsersController extends AppController
|
|||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$conditions['organisation_id'] = $currentUser['organisation_id'];
|
||||
}
|
||||
$keycloakUsersParsed = null;
|
||||
if (!empty(Configure::read('keycloak.enabled'))) {
|
||||
$keycloakUsersParsed = $this->Users->getParsedKeycloakUser();
|
||||
}
|
||||
$this->CRUD->index([
|
||||
'contain' => $this->containFields,
|
||||
'filters' => $this->filterFields,
|
||||
'quickFilters' => $this->quickFilterFields,
|
||||
'conditions' => $conditions
|
||||
'conditions' => $conditions,
|
||||
'afterFind' => function($data) use ($keycloakUsersParsed) {
|
||||
// if (!empty(Configure::read('keycloak.enabled'))) {
|
||||
// $keycloakUser = $keycloakUsersParsed[$data->username];
|
||||
// $data['keycloak_status'] = array_values($this->Users->checkKeycloakStatus([$data->toArray()], [$keycloakUser]))[0];
|
||||
// }
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -139,10 +150,18 @@ class UsersController extends AppController
|
|||
if (empty($id) || (empty($currentUser['role']['perm_org_admin']) && empty($currentUser['role']['perm_admin']))) {
|
||||
$id = $this->ACL->getUser()['id'];
|
||||
}
|
||||
$keycloakUsersParsed = null;
|
||||
if (!empty(Configure::read('keycloak.enabled'))) {
|
||||
$keycloakUsersParsed = $this->Users->getParsedKeycloakUser();
|
||||
}
|
||||
$this->CRUD->view($id, [
|
||||
'contain' => ['Individuals' => ['Alignments' => 'Organisations'], 'Roles', 'Organisations'],
|
||||
'afterFind' => function($data) {
|
||||
'afterFind' => function($data) use ($keycloakUsersParsed) {
|
||||
$data = $this->fetchTable('PermissionLimitations')->attachLimitations($data);
|
||||
if (!empty(Configure::read('keycloak.enabled'))) {
|
||||
$keycloakUser = $keycloakUsersParsed[$data->username];
|
||||
$data['keycloak_status'] = array_values($this->Users->checkKeycloakStatus([$data->toArray()], [$keycloakUser]))[0];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -284,27 +284,34 @@ class AuthKeycloakBehavior extends Behavior
|
|||
{
|
||||
$this->updateMappers();
|
||||
$results = [];
|
||||
$data['Users'] = $this->_table->find()->contain(['Individuals', 'Organisations', 'Roles'])->select(
|
||||
[
|
||||
'id',
|
||||
'uuid',
|
||||
'username',
|
||||
'disabled',
|
||||
'Individuals.email',
|
||||
'Individuals.first_name',
|
||||
'Individuals.last_name',
|
||||
'Individuals.uuid',
|
||||
'Roles.name',
|
||||
'Roles.uuid',
|
||||
'Organisations.name',
|
||||
'Organisations.uuid'
|
||||
]
|
||||
)->disableHydration()->toArray();
|
||||
$data['Users'] = $this->getCerebrateUsers();
|
||||
$clientId = $this->getClientId();
|
||||
return $this->syncUsers($data['Users'], $clientId);
|
||||
}
|
||||
|
||||
private function syncUsers(array $users, $clientId): array
|
||||
{
|
||||
$keycloakUsersParsed = $this->getParsedKeycloakUser();
|
||||
$changes = [
|
||||
'created' => [],
|
||||
'modified' => [],
|
||||
];
|
||||
foreach ($users as &$user) {
|
||||
$changed = false;
|
||||
if (empty($keycloakUsersParsed[$user['username']])) {
|
||||
if ($this->createUser($user, $clientId)) {
|
||||
$changes['created'][] = $user['username'];
|
||||
}
|
||||
} else {
|
||||
if ($this->checkAndUpdateUser($keycloakUsersParsed[$user['username']], $user)) {
|
||||
$changes['modified'][] = $user['username'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
public function getParsedKeycloakUser(): array
|
||||
{
|
||||
$response = $this->restApiRequest('%s/admin/realms/%s/users', [], 'get');
|
||||
$keycloakUsers = json_decode($response->getStringBody(), true);
|
||||
|
@ -325,23 +332,25 @@ class AuthKeycloakBehavior extends Behavior
|
|||
]
|
||||
];
|
||||
}
|
||||
$changes = [
|
||||
'created' => [],
|
||||
'modified' => [],
|
||||
];
|
||||
foreach ($users as &$user) {
|
||||
$changed = false;
|
||||
if (empty($keycloakUsersParsed[$user['username']])) {
|
||||
if ($this->createUser($user, $clientId)) {
|
||||
$changes['created'][] = $user['username'];
|
||||
}
|
||||
} else {
|
||||
if ($this->checkAndUpdateUser($keycloakUsersParsed[$user['username']], $user)) {
|
||||
$changes['modified'][] = $user['username'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $changes;
|
||||
return $keycloakUsersParsed;
|
||||
}
|
||||
|
||||
private function getCerebrateUsers(): array
|
||||
{
|
||||
return $this->_table->find()->contain(['Individuals', 'Organisations', 'Roles'])->select([
|
||||
'id',
|
||||
'uuid',
|
||||
'username',
|
||||
'disabled',
|
||||
'Individuals.email',
|
||||
'Individuals.first_name',
|
||||
'Individuals.last_name',
|
||||
'Individuals.uuid',
|
||||
'Roles.name',
|
||||
'Roles.uuid',
|
||||
'Organisations.name',
|
||||
'Organisations.uuid'
|
||||
])->disableHydration()->toArray();
|
||||
}
|
||||
|
||||
private function checkAndUpdateUser(array $keycloakUser, array $user): bool
|
||||
|
@ -387,6 +396,63 @@ class AuthKeycloakBehavior extends Behavior
|
|||
return false;
|
||||
}
|
||||
|
||||
public function checkKeycloakStatus(array $users, array $keycloakUsers): array
|
||||
{
|
||||
$users = Hash::combine($users, '{n}.username', '{n}');
|
||||
$keycloakUsersParsed = Hash::combine($keycloakUsers, '{n}.username', '{n}');
|
||||
$status = [];
|
||||
foreach ($users as $username => $user) {
|
||||
$differences = [];
|
||||
$requireUpdate = $this->checkKeycloakUserRequiresUpdate($keycloakUsersParsed[$username], $user, $differences);
|
||||
$status[$user['id']] = [
|
||||
'require_update' => $requireUpdate,
|
||||
'differences' => $differences,
|
||||
];
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
private function checkKeycloakUserRequiresUpdate(array $keycloakUser, array $user, array &$differences = []): bool
|
||||
{
|
||||
|
||||
$cEnabled = $keycloakUser['enabled'] == $user['disabled'];
|
||||
$cFn = $keycloakUser['firstName'] !== $user['individual']['first_name'];
|
||||
$Ln = $keycloakUser['lastName'] !== $user['individual']['last_name'];
|
||||
$cEmail = $keycloakUser['email'] !== $user['individual']['email'];
|
||||
$cRolename = (empty($keycloakUser['attributes']['role_name']) || $keycloakUser['attributes']['role_name'] !== $user['role']['name']);
|
||||
$cRoleuuid = (empty($keycloakUser['attributes']['role_uuid']) || $keycloakUser['attributes']['role_uuid'] !== $user['role']['uuid']);
|
||||
$cOrgname = (empty($keycloakUser['attributes']['org_name']) || $keycloakUser['attributes']['org_name'] !== $user['organisation']['name']);
|
||||
$cOrguuid = (empty($keycloakUser['attributes']['org_uuid']) || $keycloakUser['attributes']['org_uuid'] !== $user['organisation']['uuid']);
|
||||
if ($cEnabled || $cFn || $Ln || $cEmail || $cRolename || $cRoleuuid || $cOrgname || $cOrguuid) {
|
||||
if ($cEnabled) {
|
||||
$differences['enabled'] = ['kc' => $keycloakUser['enabled'], 'cerebrate' => $user['disabled']];
|
||||
}
|
||||
if ($cFn) {
|
||||
$differences['first_name'] = ['kc' => $keycloakUser['firstName'], 'cerebrate' => $user['individual']['first_name']];
|
||||
}
|
||||
if ($Ln) {
|
||||
$differences['last_name'] = ['kc' => $keycloakUser['lastName'], 'cerebrate' => $user['individual']['last_name']];
|
||||
}
|
||||
if ($cEmail) {
|
||||
$differences['email'] = ['kc' => $keycloakUser['email'], 'cerebrate' => $user['individual']['email']];
|
||||
}
|
||||
if ($cRolename) {
|
||||
$differences['role_name'] = ['kc' => $keycloakUser['attributes']['role_name'], 'cerebrate' => $user['role']['name']];
|
||||
}
|
||||
if ($cRoleuuid) {
|
||||
$differences['role_uuid'] = ['kc' => $keycloakUser['attributes']['role_uuid'], 'cerebrate' => $user['role']['uuid']];
|
||||
}
|
||||
if ($cOrgname) {
|
||||
$differences['org_name'] = ['kc' => $keycloakUser['attributes']['org_name'], 'cerebrate' => $user['organisation']['name']];
|
||||
}
|
||||
if ($cOrguuid) {
|
||||
$differences['org_uuid'] = ['kc' => $keycloakUser['attributes']['org_uuid'], 'cerebrate' => $user['organisation']['uuid']];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function createUser(array $user, string $clientId)
|
||||
{
|
||||
$newUser = [
|
||||
|
|
|
@ -183,18 +183,6 @@ class UsersTable extends AppTable
|
|||
return $rules;
|
||||
}
|
||||
|
||||
public function test()
|
||||
{
|
||||
$this->Roles = TableRegistry::get('Roles');
|
||||
$role = $this->Roles->newEntity([
|
||||
'name' => 'admin',
|
||||
'perm_admin' => 1,
|
||||
'perm_org_admin' => 1,
|
||||
'perm_sync' => 1
|
||||
]);
|
||||
$this->Roles->save($role);
|
||||
}
|
||||
|
||||
public function checkForNewInstance(): bool
|
||||
{
|
||||
if (empty($this->find()->first())) {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Cake\Core\Configure;
|
||||
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
|
@ -92,6 +95,12 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'url' => '/user-settings/index?Users.id={{url_data}}',
|
||||
'url_data_path' => 'id'
|
||||
],
|
||||
// [
|
||||
// 'name' => __('Keycloak status'),
|
||||
// 'element' => 'keycloak_status',
|
||||
// 'data_path' => 'keycloak_status',
|
||||
// 'requirements' => Configure::read('keycloak.enabled', false),
|
||||
// ],
|
||||
],
|
||||
'title' => __('User index'),
|
||||
'description' => __('The list of enrolled users in this Cerebrate instance. All of the users have or at one point had access to the system.'),
|
||||
|
|
|
@ -51,14 +51,22 @@ $fields = [
|
|||
'scope' => 'individuals'
|
||||
]
|
||||
];
|
||||
if ($keycloakConfig['enabled'] && $loggedUser['id'] == $entity['id']) {
|
||||
if ($keycloakConfig['enabled']) {
|
||||
$fields[] = [
|
||||
'type' => 'generic',
|
||||
'key' => __('Modify keycloak profile'),
|
||||
'path' => 'username',
|
||||
'url' => $kcurl,
|
||||
'requirements' => false
|
||||
'key' => __('Keycloak status'),
|
||||
'type' => 'keycloakStatus',
|
||||
'path' => 'keycloak_status',
|
||||
'requirements' => !empty($keycloakConfig['enabled']),
|
||||
];
|
||||
if ($loggedUser['id'] == $entity['id']) {
|
||||
$fields[] = [
|
||||
'type' => 'generic',
|
||||
'key' => __('Modify keycloak profile'),
|
||||
'path' => 'username',
|
||||
'url' => $kcurl,
|
||||
'requirements' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
echo $this->element(
|
||||
'/genericElements/SingleViews/single_view',
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
$data = $this->Hash->get($row, $field['data_path']);
|
||||
if (is_null($data)) {
|
||||
echo '';
|
||||
} else if (!empty($data['require_update'])) {
|
||||
echo sprintf(
|
||||
'<span data-bs-toggle="tooltip" data-bs-title="%s">%s</span>',
|
||||
sprintf('Fields having differences: %s', (implode(', ', array_keys($data['differences'])))),
|
||||
$this->Bootstrap->icon('times', ['class' => 'text-danger', ])
|
||||
);
|
||||
} else {
|
||||
echo $this->Bootstrap->icon('check', ['class' => 'text-success',]);
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
use Cake\Utility\Hash;
|
||||
|
||||
$value = Hash::get($data, $field['path']);
|
||||
$differencesRearranged = array_map(function($difference) {
|
||||
return [
|
||||
__('Local: {0}', h($difference['cerebrate'])),
|
||||
__('Keycloak: {0}', h($difference['kc'])),
|
||||
];
|
||||
}, $value['differences']);
|
||||
if (!empty($value['require_update'])) {
|
||||
echo sprintf(
|
||||
'<div class="alert alert-warning"><div>%s</div>%s</div>',
|
||||
$this->Bootstrap->icon('exclamation-triangle') . __(' This user is not synchronise with Keycloak. Differences:'),
|
||||
$this->Html->nestedList($differencesRearranged, ['class' => ''])
|
||||
);
|
||||
} else {
|
||||
echo $this->Bootstrap->icon('check', ['class' => 'text-success',]);
|
||||
}
|
||||
?>
|
Loading…
Reference in New Issue