mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-28 23:16:03 +00:00
oidc userinfo support
This commit is contained in:
@@ -104,6 +104,11 @@ class ConfigDataProvider
|
||||
return $this->object->get('oidcTokenEndpoint');
|
||||
}
|
||||
|
||||
public function getUserInfoEndpoint(): ?string
|
||||
{
|
||||
return $this->object->get('oidcUserInfoEndpoint');
|
||||
}
|
||||
|
||||
public function getJwksEndpoint(): ?string
|
||||
{
|
||||
return $this->object->get('oidcJwksEndpoint');
|
||||
|
||||
@@ -38,6 +38,7 @@ use Espo\Core\Authentication\Logins\Espo;
|
||||
use Espo\Core\Authentication\Jwt\Exceptions\Invalid;
|
||||
use Espo\Core\Authentication\Jwt\Exceptions\SignatureNotVerified;
|
||||
use Espo\Core\Authentication\Jwt\Validator;
|
||||
use Espo\Core\Authentication\Oidc\UserProvider\UserInfo;
|
||||
use Espo\Core\Authentication\Result;
|
||||
use Espo\Core\Authentication\Result\FailReason;
|
||||
use Espo\Core\Utils\Json;
|
||||
@@ -45,6 +46,7 @@ use Espo\Core\Utils\Log;
|
||||
use JsonException;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use SensitiveParameter;
|
||||
use stdClass;
|
||||
|
||||
class Login implements LoginInterface
|
||||
@@ -62,7 +64,8 @@ class Login implements LoginInterface
|
||||
private Validator $validator,
|
||||
private TokenValidator $tokenValidator,
|
||||
private UserProvider $userProvider,
|
||||
private ApplicationState $applicationState
|
||||
private ApplicationState $applicationState,
|
||||
private UserInfoDataProvider $userInfoDataProvider,
|
||||
) {}
|
||||
|
||||
public function login(Data $data, Request $request): Result
|
||||
@@ -99,7 +102,8 @@ class Login implements LoginInterface
|
||||
throw new RuntimeException("No client secret.");
|
||||
}
|
||||
|
||||
[$rawToken, $failResult] = $this->requestToken($endpoint, $clientId, $code, $redirectUri, $clientSecret);
|
||||
[$rawToken, $failResult, $accessToken] =
|
||||
$this->requestToken($endpoint, $clientId, $code, $redirectUri, $clientSecret);
|
||||
|
||||
if ($failResult) {
|
||||
return $failResult;
|
||||
@@ -144,7 +148,9 @@ class Login implements LoginInterface
|
||||
return Result::fail(FailReason::DENIED);
|
||||
}
|
||||
|
||||
$user = $this->userProvider->get($tokenPayload);
|
||||
$userInfo = $this->getUserInfo($tokenPayload, $accessToken);
|
||||
|
||||
$user = $this->userProvider->get($userInfo);
|
||||
|
||||
if (!$user) {
|
||||
return Result::fail(FailReason::USER_NOT_FOUND);
|
||||
@@ -198,7 +204,7 @@ class Login implements LoginInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{?string, ?Result}
|
||||
* @return array{?string, ?Result, ?string}
|
||||
*/
|
||||
private function requestToken(
|
||||
string $endpoint,
|
||||
@@ -250,7 +256,7 @@ class Login implements LoginInterface
|
||||
|
||||
$this->log->warning(self::composeLogMessage('Token request error.', $status, $response));
|
||||
|
||||
return [null, Result::fail(FailReason::DENIED)];
|
||||
return [null, Result::fail(FailReason::DENIED), null];
|
||||
}
|
||||
|
||||
$parsedResponse = null;
|
||||
@@ -266,6 +272,7 @@ class Login implements LoginInterface
|
||||
}
|
||||
|
||||
$token = $parsedResponse->id_token ?? null;
|
||||
$accessToken = $parsedResponse->access_token ?? null;
|
||||
|
||||
if (!$token || !is_string($token)) {
|
||||
$this->log->error(self::composeLogMessage('Bad token response.', $status, $response));
|
||||
@@ -273,7 +280,7 @@ class Login implements LoginInterface
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return [$token, null];
|
||||
return [$token, null, $accessToken];
|
||||
}
|
||||
|
||||
private static function composeLogMessage(string $text, ?int $status = null, ?string $response = null): string
|
||||
@@ -295,4 +302,21 @@ class Login implements LoginInterface
|
||||
$this->tokenValidator->validateFields($token);
|
||||
$this->tokenValidator->validateSignature($token);
|
||||
}
|
||||
|
||||
private function getUserInfo(Token\Payload $payload, #[SensitiveParameter] ?string $accessToken): UserInfo
|
||||
{
|
||||
$endpoint = $this->configDataProvider->getUserInfoEndpoint();
|
||||
|
||||
if (!$endpoint) {
|
||||
return new UserInfo($payload, []);
|
||||
}
|
||||
|
||||
if (!$accessToken) {
|
||||
throw new RuntimeException("OIDC: No access token received.");
|
||||
}
|
||||
|
||||
$data = $this->userInfoDataProvider->get($accessToken);
|
||||
|
||||
return new UserInfo($payload, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Authentication\Oidc;
|
||||
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Utils\Log;
|
||||
use JsonException;
|
||||
use RuntimeException;
|
||||
use SensitiveParameter;
|
||||
|
||||
class UserInfoDataProvider
|
||||
{
|
||||
private const REQUEST_TIMEOUT = 10;
|
||||
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function get(#[SensitiveParameter] string $accessToken): array
|
||||
{
|
||||
return $this->load($accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function load(#[SensitiveParameter] string $accessToken): array
|
||||
{
|
||||
$endpoint = $this->configDataProvider->getUserInfoEndpoint();
|
||||
|
||||
if (!$endpoint) {
|
||||
throw new RuntimeException("No userinfo endpoint.");
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $endpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => self::REQUEST_TIMEOUT,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Accept: application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
/** @var string|false $response */
|
||||
$response = curl_exec($curl);
|
||||
$error = curl_error($curl);
|
||||
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($response === false) {
|
||||
$response = '';
|
||||
}
|
||||
|
||||
if ($error || is_int($status) && ($status >= 400 && $status < 500)) {
|
||||
$this->log->error(self::composeLogMessage('UserInfo response error.', $status, $response));
|
||||
|
||||
throw new RuntimeException("OIDC: Userinfo request error.");
|
||||
}
|
||||
|
||||
$parsedResponse = null;
|
||||
|
||||
try {
|
||||
$parsedResponse = Json::decode($response, true);
|
||||
} catch (JsonException) {}
|
||||
|
||||
if (!is_array($parsedResponse)) {
|
||||
throw new RuntimeException("OIDC: Bad userinfo response.");
|
||||
}
|
||||
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
private static function composeLogMessage(string $text, ?int $status = null, ?string $response = null): string
|
||||
{
|
||||
if ($status === null) {
|
||||
return "OIDC: $text";
|
||||
}
|
||||
|
||||
return "OIDC: $text; Status: $status; Response: $response";
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
namespace Espo\Core\Authentication\Oidc;
|
||||
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
use Espo\Core\Authentication\Oidc\UserProvider\UserInfo;
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface UserProvider
|
||||
{
|
||||
public function get(Payload $payload): ?User;
|
||||
public function get(UserInfo $userInfo): ?User;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
namespace Espo\Core\Authentication\Oidc\UserProvider;
|
||||
|
||||
use Espo\Core\ApplicationState;
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
use Espo\Core\Authentication\Oidc\ConfigDataProvider;
|
||||
use Espo\Core\Authentication\Oidc\UserProvider;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DefaultUserProvider implements UserProvider
|
||||
@@ -44,30 +44,30 @@ class DefaultUserProvider implements UserProvider
|
||||
private Sync $sync,
|
||||
private UserRepository $userRepository,
|
||||
private ApplicationState $applicationState,
|
||||
private Log $log
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
public function get(Payload $payload): ?User
|
||||
public function get(UserInfo $userInfo): ?User
|
||||
{
|
||||
$user = $this->findUser($payload);
|
||||
$user = $this->findUser($userInfo);
|
||||
|
||||
if ($user === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$this->syncUser($user, $payload);
|
||||
$this->syncUser($user, $userInfo);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
return $this->tryToCreateUser($payload);
|
||||
return $this->tryToCreateUser($userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User|false|null
|
||||
*/
|
||||
private function findUser(Payload $payload): User|bool|null
|
||||
private function findUser(UserInfo $userInfo): User|bool|null
|
||||
{
|
||||
$usernameClaim = $this->configDataProvider->getUsernameClaim();
|
||||
|
||||
@@ -75,10 +75,10 @@ class DefaultUserProvider implements UserProvider
|
||||
throw new RuntimeException("No username claim in config.");
|
||||
}
|
||||
|
||||
$username = $payload->get($usernameClaim);
|
||||
$username = $userInfo->get($usernameClaim);
|
||||
|
||||
if (!$username) {
|
||||
throw new RuntimeException("No username claim `$usernameClaim` in token.");
|
||||
throw new RuntimeException("No username claim `$usernameClaim` in token and userinfo.");
|
||||
}
|
||||
|
||||
$username = $this->sync->normalizeUsername($username);
|
||||
@@ -136,7 +136,7 @@ class DefaultUserProvider implements UserProvider
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function tryToCreateUser(Payload $payload): ?User
|
||||
private function tryToCreateUser(UserInfo $userInfo): ?User
|
||||
{
|
||||
if (!$this->configDataProvider->createUser()) {
|
||||
return null;
|
||||
@@ -148,16 +148,16 @@ class DefaultUserProvider implements UserProvider
|
||||
throw new RuntimeException("Could not create a user. No OIDC username claim in config.");
|
||||
}
|
||||
|
||||
$username = $payload->get($usernameClaim);
|
||||
$username = $userInfo->get($usernameClaim);
|
||||
|
||||
if (!$username) {
|
||||
throw new RuntimeException("Could not create a user. No username claim returned in token.");
|
||||
throw new RuntimeException("Could not create a user. No username claim in token and userinfo.");
|
||||
}
|
||||
|
||||
return $this->sync->createUser($payload);
|
||||
return $this->sync->createUser($userInfo);
|
||||
}
|
||||
|
||||
private function syncUser(User $user, Payload $payload): void
|
||||
private function syncUser(User $user, UserInfo $userInfo): void
|
||||
{
|
||||
if (
|
||||
!$this->configDataProvider->sync() &&
|
||||
@@ -166,6 +166,6 @@ class DefaultUserProvider implements UserProvider
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sync->syncUser($user, $payload);
|
||||
$this->sync->syncUser($user, $userInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Espo\Core\Authentication\Oidc\UserProvider;
|
||||
|
||||
use Espo\Core\Acl\Cache\Clearer as AclCacheClearer;
|
||||
use Espo\Core\ApplicationState;
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
use Espo\Core\Authentication\Oidc\ConfigDataProvider;
|
||||
use Espo\Core\Field\LinkMultiple;
|
||||
use Espo\Core\Name\Field;
|
||||
@@ -50,30 +49,31 @@ class Sync
|
||||
private UserRepository $userRepository,
|
||||
private PasswordHash $passwordHash,
|
||||
private AclCacheClearer $aclCacheClearer,
|
||||
private ApplicationState $applicationState
|
||||
private ApplicationState $applicationState,
|
||||
) {}
|
||||
|
||||
public function createUser(Payload $payload): User
|
||||
public function createUser(UserInfo $userInfo): User
|
||||
{
|
||||
$username = $this->getUsernameFromToken($payload);
|
||||
$username = $this->getUsernameFromToken($userInfo);
|
||||
|
||||
$this->usernameValidator->validate($username);
|
||||
|
||||
$user = $this->userRepository->getNew();
|
||||
|
||||
$user->set([
|
||||
'type' => User::TYPE_REGULAR,
|
||||
'userName' => $username,
|
||||
$user->setType(User::TYPE_REGULAR);
|
||||
$user->setUserName($username);
|
||||
|
||||
$user->setMultiple([
|
||||
'password' => $this->passwordHash->hash(Util::generatePassword(10, 4, 2, true)),
|
||||
]);
|
||||
|
||||
$user->set($this->getUserDataFromToken($payload));
|
||||
$user->set($this->getUserTeamsDataFromToken($payload));
|
||||
$user->set($this->getUserDataFromToken($userInfo));
|
||||
$user->set($this->getUserTeamsDataFromToken($userInfo));
|
||||
|
||||
if ($this->applicationState->isPortal()) {
|
||||
$portalId = $this->applicationState->getPortalId();
|
||||
|
||||
$user->set('type', User::TYPE_PORTAL);
|
||||
$user->setType(User::TYPE_PORTAL);
|
||||
$user->setPortals(LinkMultiple::create()->withAddedId($portalId));
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class Sync
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function syncUser(User $user, Payload $payload): void
|
||||
public function syncUser(User $user, UserInfo $payload): void
|
||||
{
|
||||
$username = $this->getUsernameFromToken($payload);
|
||||
|
||||
@@ -116,19 +116,19 @@ class Sync
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getUserDataFromToken(Payload $payload): array
|
||||
private function getUserDataFromToken(UserInfo $userInfo): array
|
||||
{
|
||||
return [
|
||||
'emailAddress' => $payload->get('email'),
|
||||
'phoneNumber' => $payload->get('phone_number'),
|
||||
'emailAddress' => $userInfo->get('email'),
|
||||
'phoneNumber' => $userInfo->get('phone_number'),
|
||||
'emailAddressData' => null,
|
||||
'phoneNumberData' => null,
|
||||
'firstName' => $payload->get('given_name'),
|
||||
'lastName' => $payload->get('family_name'),
|
||||
'middle_name' => $payload->get('middle_name'),
|
||||
'firstName' => $userInfo->get('given_name'),
|
||||
'lastName' => $userInfo->get('family_name'),
|
||||
'middle_name' => $userInfo->get('middle_name'),
|
||||
'gender' =>
|
||||
in_array($payload->get('gender'), ['male', 'female']) ?
|
||||
ucfirst($payload->get('gender') ?? '') :
|
||||
in_array($userInfo->get('gender'), ['male', 'female']) ?
|
||||
ucfirst($userInfo->get('gender') ?? '') :
|
||||
null,
|
||||
];
|
||||
}
|
||||
@@ -136,14 +136,14 @@ class Sync
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getUserTeamsDataFromToken(Payload $payload): array
|
||||
private function getUserTeamsDataFromToken(UserInfo $userInfo): array
|
||||
{
|
||||
return [
|
||||
'teamsIds' => $this->getTeamIdList($payload),
|
||||
'teamsIds' => $this->getTeamIdList($userInfo),
|
||||
];
|
||||
}
|
||||
|
||||
private function getUsernameFromToken(Payload $payload): string
|
||||
private function getUsernameFromToken(UserInfo $userInfo): string
|
||||
{
|
||||
$usernameClaim = $this->configDataProvider->getUsernameClaim();
|
||||
|
||||
@@ -151,7 +151,7 @@ class Sync
|
||||
throw new RuntimeException("No OIDC username claim in config.");
|
||||
}
|
||||
|
||||
$username = $payload->get($usernameClaim);
|
||||
$username = $userInfo->get($usernameClaim);
|
||||
|
||||
if (!$username) {
|
||||
throw new RuntimeException("No username claim returned in token.");
|
||||
@@ -167,7 +167,7 @@ class Sync
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getTeamIdList(Payload $payload): array
|
||||
private function getTeamIdList(UserInfo $userInfo): array
|
||||
{
|
||||
$idList = $this->configDataProvider->getTeamIds() ?? [];
|
||||
$columns = $this->configDataProvider->getTeamColumns() ?? (object) [];
|
||||
@@ -176,7 +176,7 @@ class Sync
|
||||
return [];
|
||||
}
|
||||
|
||||
$groupList = $this->getGroups($payload);
|
||||
$groupList = $this->getGroups($userInfo);
|
||||
|
||||
$resultIdList = [];
|
||||
|
||||
@@ -194,7 +194,7 @@ class Sync
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getGroups(Payload $payload): array
|
||||
private function getGroups(UserInfo $userInfo): array
|
||||
{
|
||||
$groupClaim = $this->configDataProvider->getGroupClaim();
|
||||
|
||||
@@ -202,7 +202,7 @@ class Sync
|
||||
return [];
|
||||
}
|
||||
|
||||
$value = $payload->get($groupClaim);
|
||||
$value = $userInfo->get($groupClaim);
|
||||
|
||||
if (!$value) {
|
||||
return [];
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Authentication\Oidc\UserProvider;
|
||||
|
||||
use Espo\Core\Authentication\Jwt\Token\Payload;
|
||||
|
||||
class UserInfo
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function __construct(
|
||||
private Payload $payload,
|
||||
private array $data,
|
||||
) {}
|
||||
|
||||
public function get(string $name): mixed
|
||||
{
|
||||
return $this->payload->get($name) ?? $this->data[$name] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,7 @@
|
||||
"oidcAuthorizationRedirectUri": "OIDC Authorization Redirect URI",
|
||||
"oidcAuthorizationEndpoint": "OIDC Authorization Endpoint",
|
||||
"oidcTokenEndpoint": "OIDC Token Endpoint",
|
||||
"oidcUserInfoEndpoint": "OIDC UserInfo Endpoint",
|
||||
"oidcJwksEndpoint": "OIDC JSON Web Key Set Endpoint",
|
||||
"oidcJwtSignatureAlgorithmList": "OIDC JWT Allowed Signature Algorithms",
|
||||
"oidcScopes": "OIDC Scopes",
|
||||
|
||||
@@ -70,6 +70,9 @@
|
||||
"oidcAuthorizationEndpoint": {
|
||||
"level": "admin"
|
||||
},
|
||||
"oidcUserInfoEndpoint": {
|
||||
"level": "admin"
|
||||
},
|
||||
"oidcTokenEndpoint": {
|
||||
"level": "admin"
|
||||
},
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
"name": "oidcJwtSignatureAlgorithmList"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "oidcUserInfoEndpoint"
|
||||
},
|
||||
false
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "oidcScopes"
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
"type": "url",
|
||||
"strip": false
|
||||
},
|
||||
"oidcUserInfoEndpoint": {
|
||||
"type": "url",
|
||||
"strip": false
|
||||
},
|
||||
"oidcTokenEndpoint": {
|
||||
"type": "url",
|
||||
"strip": false
|
||||
|
||||
@@ -824,6 +824,10 @@
|
||||
"type": "url",
|
||||
"strip": false
|
||||
},
|
||||
"oidcUserInfoEndpoint": {
|
||||
"type": "url",
|
||||
"strip": false
|
||||
},
|
||||
"oidcTokenEndpoint": {
|
||||
"type": "url",
|
||||
"strip": false
|
||||
|
||||
Reference in New Issue
Block a user