%PDF- %PDF-
Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/modules/auth-keycloak/components/ |
Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/modules/auth-keycloak/components/KeycloakApi.php |
<?php /** * Keycloak Sign-In * @link https://github.com/cuzy-app/humhub-modules-auth-keycloak * @license https://github.com/cuzy-app/humhub-modules-auth-keycloak/blob/master/docs/LICENCE.md * @author [Marc FARRE](https://marc.fun) for [CUZY.APP](https://www.cuzy.app) */ namespace humhub\modules\authKeycloak\components; use GuzzleHttp\Command\Exception\CommandClientException; use GuzzleHttp\Command\Exception\CommandException; use humhub\modules\authKeycloak\authclient\Keycloak; use humhub\modules\authKeycloak\models\ConfigureForm; use humhub\modules\authKeycloak\models\GroupKeycloak; use humhub\modules\authKeycloak\Module; use humhub\modules\user\models\Auth; use humhub\modules\user\models\User; use Keycloak\Admin\KeycloakClient; use Yii; use yii\base\Component; use yii\helpers\ArrayHelper; class KeycloakApi extends Component { /** * Maximum users in the results */ public const MAX_USERS_RESULT = 100; /** * KeycloakClient methods: https://github.com/MohammadWaleed/keycloak-admin-client * @var KeycloakClient|null */ public $api; /** * @var array */ public $realm; /** * @param int $groupKeycloakId * @return bool */ public function removeGroup($groupKeycloakId) { if ( !$this->isConnected() || !$groupKeycloakId ) { return false; } $result = $this->api->removeGroup(['id' => $groupKeycloakId]); return !$this->hasError($result, 'Keycloak groups to Humhub groups users synchronization with the API failed'); } /** * @return bool */ public function isConnected() { return $this->api !== null && $this->realm !== null; } /** * @param $response * @param string|null $message * @param bool $addErrorToLog * @param string|null $errorToIgnoreForLog by default, 'User not found' as we do not check if the user exists to avoid an extra API request * @return bool */ protected function hasError($response, ?string $message = null, bool $addErrorToLog = true, ?string $errorToIgnoreForLog = 'User not found') { $errors = []; if (!empty($response['error'])) { $errors[] = $response['error']; } if (!empty($response['errorMessage'])) { $errors[] = $response['errorMessage']; } if (!empty($response['error_description'])) { $errors[] = $response['error_description']; } if (count($errors) > 0) { $errorMessage = implode(' | ', $errors); if ($addErrorToLog && strpos($errorMessage, $errorToIgnoreForLog) === false) { Yii::error( 'Auth Keycloak module error' . ($message ? ': ' . $message : '') . '. Error message: ' . $errorMessage, 'auth-keycloak' ); } return true; } return false; } /** * @param int $groupId * @return bool */ public function renameGroup($groupId) { if ( !$this->isConnected() || ($groupKeycloak = GroupKeycloak::getKeycloakGroup($groupId)) === null ) { return false; } $result = $this->api->updateGroup([ 'id' => $groupKeycloak->keycloak_id, 'name' => $groupKeycloak->name, ]); return !$this->hasError($result, 'Error removing group ID ' . $groupKeycloak->id); } /** * @param int $userId * @return int[] */ public function getUserGroups($userId) { if ( !$this->isConnected() || ($userAuth = static::getUserAuth($userId)) === null ) { return []; } $result = $this->api->getUserGroups(['id' => $userAuth->source_id]); if ($this->hasError($result, 'Error retrieving user\'s groups from Keycloak (user ID: ' . $userId . ')')) { return []; } if (!is_array($result)) { Yii::error('Error retrieving user\'s groups from Keycloak for user ID: ' . $userId . ' (result is not an array)', 'auth-keycloak'); return []; } return array_map(static function ($group) { return (int)$group['id']; }, $result); } /** * User Keycloak authentification * @param $userId * @return Auth|null */ public static function getUserAuth($userId) { return Auth::find() ->where(['source' => Keycloak::DEFAULT_NAME, 'user_id' => $userId]) ->orderBy(['id' => SORT_DESC]) // get the latest if it has multiple ->one(); } /** * @param int $userId * @param int $groupId * @param bool $createGroupIfNotExists * @return bool */ public function addUserToGroup($userId, $groupId, bool $createGroupIfNotExists = true) { if ( !$this->isConnected() || ($userAuth = static::getUserAuth($userId)) === null ) { return false; } // Check if Keycloak group exists if (($groupKeycloak = GroupKeycloak::findOne($groupId)) === null) { // Try creating it if ($createGroupIfNotExists && $this->linkSameGroupNameOrCreateGroup($groupId)) { $groupKeycloak = GroupKeycloak::findOne($groupId); } else { return false; } } $result = $this->api->addUserToGroup([ 'id' => $userAuth->source_id, 'groupId' => $groupKeycloak->keycloak_id ]); return !$this->hasError($result, 'Error adding group ID ' . $groupId . ' to user ID ' . $userId); } /** * @param int $groupId * @return bool */ public function linkSameGroupNameOrCreateGroup($groupId) { if ( !$this->isConnected() || ($groupKeycloak = GroupKeycloak::findOne($groupId)) === null ) { return false; } // Check if group with same name already exists if (($keycloakGroupId = $this->getGroupIdFromName($groupKeycloak->name)) !== null) { $groupKeycloak->keycloak_id = $keycloakGroupId; return $groupKeycloak->save(); } // Try creating group $result = $this->api->createGroup(['name' => $groupKeycloak->name]); if ($this->hasError($result, 'Error creating group ID ' . $groupKeycloak->id)) { return false; } // Get group ID $keycloakGroupId = $this->getGroupIdFromName($groupKeycloak->name); $groupKeycloak->keycloak_id = $keycloakGroupId; return $groupKeycloak->save(); } /** * @param string $groupName * @return string|null */ public function getGroupIdFromName(string $groupName) { if (!$this->isConnected()) { return null; } return array_search($groupName, $this->getGroupsNamesById(), true) ?: null; } /** * @return array|null id => name */ public function getGroupsNamesById() { if (!$this->isConnected()) { return null; } $result = $this->api->getGroups(); if ($this->hasError($result, 'Error retrieving groups')) { return null; } if (!is_array($result)) { Yii::error('Error retrieving groups from Keycloak (result is not an array)', 'auth-keycloak'); return null; } return ArrayHelper::map($result, 'id', 'name'); } /** * @param int $userId * @param int $groupId * @return bool */ public function deleteUserFromGroup($userId, $groupId) { if ( !$this->isConnected() || ($userAuth = static::getUserAuth($userId)) === null || ($groupKeycloak = GroupKeycloak::getKeycloakGroup($groupId)) === null ) { return false; } $result = $this->api->deleteUserFromGroup([ 'id' => $userAuth->source_id, 'groupId' => $groupKeycloak->keycloak_id ]); return !$this->hasError($result, 'Error deleting group ID ' . $groupId . ' to user ID ' . $userId); } /** * @inerhitdoc */ public function init() { parent::init(); $config = new ConfigureForm(); if ( !$config->enabled || !$config->hasApiParams() || !Yii::$app->authClientCollection->hasClient(Keycloak::DEFAULT_NAME) ) { return; } if (!class_exists('Keycloak\Admin\KeycloakClient')) { require Yii::getAlias('@auth-keycloak/vendor/autoload.php'); } /** @var Module $module */ $module = Yii::$app->getModule('auth-keycloak'); $this->api = KeycloakClient::factory([ 'realm' => $config->realm, 'client_id' => $config->clientId, 'client_secret' => $config->clientSecret, 'username' => $config->apiUsername, 'password' => $config->apiPassword, 'baseUri' => $config->baseUrl . '/', 'scope' => 'openid', 'verify' => $module->apiVerifySsl, ]); if ($config->realm !== 'master') { $this->api->setRealmName($config->realm); } try { $this->realm = $this->api->getRealm(); } catch (CommandClientException|CommandException $e) { Yii::error('Error trying to connect to Keycloak API: ' . $e, 'auth-keycloak'); return; } } /** * @param User $user * @return bool|null */ public function updateUserUsername(User $user) { if ( !$this->isConnected() || ($userAuth = static::getUserAuth($user->id)) === null ) { return null; } // Do not update username if Keycloak username is the email if ( isset($this->realm['registrationEmailAsUsername']) && $this->realm['registrationEmailAsUsername'] ) { return null; } // Update username $result = $this->api->updateUser([ 'id' => $userAuth->source_id, 'username' => $user->username, ]); return !$this->hasError($result, 'Error saving user\'s new username on Keycloak for user ID: ' . $user->id); } /** * @param User $user * @return bool|null */ public function updateUserEmail(User $user) { if ( !$this->isConnected() || ($userAuth = static::getUserAuth($user->id)) === null ) { return null; } // Update email $result = $this->api->updateUser(array_merge( [ 'id' => $userAuth->source_id, 'email' => $user->email, ], ((isset($this->realm['registrationEmailAsUsername']) && $this->realm['registrationEmailAsUsername']) ? ['username' => $user->email] : []) )); return !$this->hasError($result, 'Error saving user\'s new email on Keycloak for user ID: ' . $user->id); } /** * @param User $user * @param string $newPassword * @return bool|string string is the error message if present */ public function resetUserPassword(User $user, string $newPassword) { if ( !$this->isConnected() || ($userAuth = static::getUserAuth($user->id)) === null ) { return null; } // Update password $result = $this->api->resetUserPassword([ 'id' => $userAuth->source_id, 'value' => $newPassword, ]); return $this->hasError($result, 'Error saving user\'s new password on Keycloak for user ID: ' . $user->id, true, 'Invalid password') ? $result['error_description'] ?? false : true; } /** * @param int $userId * @return bool|null */ public function revokeUserSession($userId) { if ( !$this->isConnected() || ($userAuth = static::getUserAuth($userId)) === null ) { return false; } $result = $this->api->getUserSessions(['id' => $userAuth->source_id]); if ($this->hasError($result, 'Error retrieving user\'s sessions from Keycloak (user ID: ' . $userId . ')')) { return false; } if (!is_array($result)) { Yii::error('Error retrieving user\'s sessions from Keycloak for user ID: ' . $userId . ' (result is not an array)', 'auth-keycloak'); return false; } foreach ($result as $session) { if (isset($session['id'])) { // revoke session $result = $this->api->revokeUserSession([ 'session' => $session['id'], ]); if ($this->hasError($result, 'Error revoking user\'s session on Keycloak for user ID: ' . $userId)) { return false; } } } return true; } /** * @param $groupId * @return array|null */ public function getGroupMemberIds($groupId) { if (!$this->isConnected()) { return null; } $first = 0; $memberIds = []; while (!isset($currentMembers) || count($currentMembers) === static::MAX_USERS_RESULT) { $currentMembers = $this->api->getGroupMembers([ 'id' => $groupId, 'first' => $first, 'max' => static::MAX_USERS_RESULT, ]); $currentMemberIds = array_map(static function ($member) { return $member['id'] ?? null; }, $currentMembers); $currentMemberIds = array_filter($currentMemberIds); $memberIds = array_merge($memberIds, $currentMemberIds); $first += static::MAX_USERS_RESULT; } return $memberIds; } }