diff --git a/src/Model/Behavior/AuthKeycloakBehavior.php b/src/Model/Behavior/AuthKeycloakBehavior.php index 38bd14f..eda0c51 100644 --- a/src/Model/Behavior/AuthKeycloakBehavior.php +++ b/src/Model/Behavior/AuthKeycloakBehavior.php @@ -15,9 +15,20 @@ use Cake\Core\Configure; use Cake\Http\Client; use Cake\Http\Client\FormData; use Cake\Http\Exception\NotFoundException; +use Cake\Utility\Inflector; class AuthKeycloakBehavior extends Behavior { + + public function getMappedFieldList(): array + { + $mappers = Configure::read('keycloak.user_meta_mapping'); + if (empty($mappers)) { + return []; + } + return explode(',', $mappers); + } + public function getUser(EntityInterface $profile, Session $session) { $userId = $session->read('Auth.User.id'); @@ -224,6 +235,10 @@ class AuthKeycloakBehavior extends Behavior $user['organisation'] = $this->_table->Organisations->find()->where([ 'id' => $user['organisation_id'] ])->first(); + $user['meta_fields'] = $this->_table->MetaFields->find()->where([ + 'scope' => 'user', + 'parent_id' => $user['id'] + ])->all()->toArray(); $users = [$user->toArray()]; $clientId = $this->getClientId(); @@ -327,7 +342,9 @@ class AuthKeycloakBehavior extends Behavior $response = $this->restApiRequest('%s/admin/realms/%s/users', [], 'get'); $keycloakUsers = json_decode($response->getStringBody(), true); $keycloakUsersParsed = []; + $mappers = array_merge(['role_name', 'role_uuid', 'org_uuid', 'org_name'], $this->getMappedFieldList()); foreach ($keycloakUsers as $u) { + $attributes = []; $keycloakUsersParsed[$u['username']] = [ 'id' => $u['id'], 'username' => $u['username'], @@ -335,20 +352,19 @@ class AuthKeycloakBehavior extends Behavior 'firstName' => $u['firstName'], 'lastName' => $u['lastName'], 'email' => $u['email'], - 'attributes' => [ - 'role_name' => $u['attributes']['role_name'][0] ?? '', - 'role_uuid' => $u['attributes']['role_uuid'][0] ?? '', - 'org_uuid' => $u['attributes']['org_uuid'][0] ?? '', - 'org_name' => $u['attributes']['org_name'][0] ?? '' - ] + 'attributes' => [] ]; + foreach ($mappers as $mapper) { + $keycloakUsersParsed[$u['username']]['attributes'][$mapper] = $u['attributes'][$mapper][0] ?? ''; + } } return $keycloakUsersParsed; } private function getCerebrateUsers(): array { - return $this->_table->find()->contain(['Individuals', 'Organisations', 'Roles'])->select([ + $metaFieldsSelector = ['fields' => ['MetaFields.field', 'MetaFields.parent_id', 'MetaFields.value']]; + $results = $this->_table->find()->contain(['Individuals', 'Organisations', 'Roles', 'MetaFields' => $metaFieldsSelector])->select([ 'id', 'uuid', 'username', @@ -362,23 +378,55 @@ class AuthKeycloakBehavior extends Behavior 'Organisations.name', 'Organisations.uuid' ])->disableHydration()->toArray(); + foreach ($results as &$result) { + if (!empty($result['meta_fields'])) { + $temp = []; + foreach ($result['meta_fields'] as $meta_field) { + $temp[$meta_field['field']] = $meta_field['value']; + } + $result['meta_fields'] = $temp; + } + } + return $results; } private function checkAndUpdateUser(array $keycloakUser, array $user): bool { + if (!empty($user['meta_fields'][0])) { + $temp = []; + foreach ($user['meta_fields'] as $meta_field) { + $temp[$meta_field['field']] = $meta_field['value']; + } + $user['meta_fields'] = $temp; + } if ($this->checkKeycloakUserRequiresUpdate($keycloakUser, $user)) { + $default_mappers = [ + 'role' => [ + 'uuid', + 'name' + ], + 'organisation' => [ + 'uuid', + 'name' + ] + ]; + $custom_mappers = $this->getMappedFieldList(); $change = [ 'enabled' => !$user['disabled'], 'firstName' => $user['individual']['first_name'], 'lastName' => $user['individual']['last_name'], 'email' => $user['individual']['email'], - 'attributes' => [ - 'role_name' => $user['role']['name'], - 'role_uuid' => $user['role']['uuid'], - 'org_name' => $user['organisation']['name'], - 'org_uuid' => $user['organisation']['uuid'] - ] + 'attributes' => [] ]; + foreach ($default_mappers as $object_type => $object_data) { + foreach ($object_data as $mapper) { + $object_type_kc = $object_type === 'organisation' ? 'org' : $object_type; + $change['attributes'][$object_type_kc . '_' . $mapper] = $user[$object_type][$mapper]; + } + } + foreach ($custom_mappers as $mapper) { + $change['attributes'][$mapper] = $user['meta_fields'][$mapper] ?? ''; + } $response = $this->restApiRequest('%s/admin/realms/%s/users/' . $keycloakUser['id'], $change, 'put'); if (!$response->isOk()) { $this->_table->auditLogs()->insert([ @@ -404,6 +452,11 @@ class AuthKeycloakBehavior extends Behavior $keycloakUsersParsed = Hash::combine($keycloakUsers, '{n}.username', '{n}'); $status = []; foreach ($users as $username => $user) { + $temp = []; + foreach ($user['meta_fields'] as $meta_field) { + $temp[$meta_field['field']] = $meta_field['value']; + } + $user['meta_fields'] = $temp; $differences = []; $keycloakUser = $keycloakUsersParsed[$username] ?? []; $requireUpdate = $this->checkKeycloakUserRequiresUpdate($keycloakUser, $user, $differences); @@ -416,7 +469,8 @@ class AuthKeycloakBehavior extends Behavior } private function checkKeycloakUserRequiresUpdate(array $keycloakUser, array $user, array &$differences = []): bool - { + { + $mappedFields = $this->getMappedFieldList(); $condEnabled = $keycloakUser['enabled'] == $user['disabled']; $condFirstname = mb_strtolower($keycloakUser['firstName']) !== mb_strtolower($user['individual']['first_name']); $condLastname = mb_strtolower($keycloakUser['lastName']) !== mb_strtolower($user['individual']['last_name']); @@ -425,7 +479,24 @@ class AuthKeycloakBehavior extends Behavior $condRoleuuid = (empty($keycloakUser['attributes']['role_uuid']) || mb_strtolower($keycloakUser['attributes']['role_uuid']) !== mb_strtolower($user['role']['uuid'])); $condOrgname = (empty($keycloakUser['attributes']['org_name']) || mb_strtolower($keycloakUser['attributes']['org_name']) !== mb_strtolower($user['organisation']['name'])); $condOrguuid = (empty($keycloakUser['attributes']['org_uuid']) || mb_strtolower($keycloakUser['attributes']['org_uuid']) !== mb_strtolower($user['organisation']['uuid'])); - if ($condEnabled || $condFirstname || $condLastname || $condEmail || $condRolename || $condRoleuuid || $condOrgname || $condOrguuid) { + $condMapped = false; + if (!empty($user['meta_fields']) && isset($user['meta_fields'][0])) { + $temp = []; + foreach ($user['meta_fields'] as $meta_field) { + $temp[$meta_field['field']] = $meta_field['value']; + } + $user['meta_fields'] = $temp; + } + foreach ($mappedFields as $mappedField) { + if (($keycloakUser['attributes'][$mappedField] ?? '') != ($user['meta_fields'][$mappedField] ?? '')) { + $condMapped = true; + $differences[$mappedField] = [ + 'keycloak' => $keycloakUser['attributes'][$mappedField] ?? '', + 'cerebrate' => $user['meta_fields'][$mappedField] ?? '' + ]; + } + } + if ($condEnabled || $condFirstname || $condLastname || $condEmail || $condRolename || $condRoleuuid || $condOrgname || $condOrguuid || $condMapped) { if ($condEnabled) { $differences['enabled'] = ['keycloak' => $keycloakUser['enabled'], 'cerebrate' => $user['disabled']]; }