Started refactor for merge of OIDC
- Made oidc config more generic to not be overly reliant on the library based upon learnings from saml2 auth. - Removed any settings that are redundant or not deemed required for initial implementation. - Reduced some methods down where not needed. - Renamed OpenID to OIDC - Updated .env.example.complete to align with all options and their defaults Related to #2169
This commit is contained in:
parent
193d7fb3fe
commit
2ec0aa85ca
7 changed files with 283 additions and 338 deletions
|
@ -240,12 +240,15 @@ SAML2_GROUP_ATTRIBUTE=group
|
|||
SAML2_REMOVE_FROM_GROUPS=false
|
||||
|
||||
# OpenID Connect authentication configuration
|
||||
OPENID_CLIENT_ID=null
|
||||
OPENID_CLIENT_SECRET=null
|
||||
OPENID_ISSUER=https://example.com
|
||||
OPENID_PUBLIC_KEY=file:///my/public.key
|
||||
OPENID_URL_AUTHORIZE=https://example.com/authorize
|
||||
OPENID_URL_TOKEN=https://example.com/token
|
||||
OIDC_NAME=SSO
|
||||
OIDC_DISPLAY_NAME_CLAIMS=name
|
||||
OIDC_CLIENT_ID=null
|
||||
OIDC_CLIENT_SECRET=null
|
||||
OIDC_ISSUER=null
|
||||
OIDC_PUBLIC_KEY=null
|
||||
OIDC_AUTH_ENDPOINT=null
|
||||
OIDC_TOKEN_ENDPOINT=null
|
||||
OIDC_DUMP_USER_DETAILS=false
|
||||
|
||||
# Disable default third-party services such as Gravatar and Draw.IO
|
||||
# Service-specific options will override this option
|
||||
|
|
|
@ -4,8 +4,8 @@ namespace BookStack\Auth\Access;
|
|||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ExternalAuthService
|
||||
|
|
|
@ -5,9 +5,11 @@ use BookStack\Exceptions\JsonDebugException;
|
|||
use BookStack\Exceptions\OpenIdException;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use Exception;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Lcobucci\JWT\Token;
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||
use OpenIDConnectClient\AccessToken;
|
||||
use OpenIDConnectClient\Exception\InvalidTokenException;
|
||||
use OpenIDConnectClient\OpenIDConnectProvider;
|
||||
|
||||
/**
|
||||
|
@ -25,12 +27,12 @@ class OpenIdService extends ExternalAuthService
|
|||
{
|
||||
parent::__construct($registrationService, $user);
|
||||
|
||||
$this->config = config('openid');
|
||||
$this->config = config('oidc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a authorization flow.
|
||||
* @throws Error
|
||||
* Initiate an authorization flow.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function login(): array
|
||||
{
|
||||
|
@ -43,7 +45,6 @@ class OpenIdService extends ExternalAuthService
|
|||
|
||||
/**
|
||||
* Initiate a logout flow.
|
||||
* @throws Error
|
||||
*/
|
||||
public function logout(): array
|
||||
{
|
||||
|
@ -56,7 +57,7 @@ class OpenIdService extends ExternalAuthService
|
|||
|
||||
/**
|
||||
* Refresh the currently logged in user.
|
||||
* @throws Error
|
||||
* @throws Exception
|
||||
*/
|
||||
public function refresh(): bool
|
||||
{
|
||||
|
@ -79,7 +80,7 @@ class OpenIdService extends ExternalAuthService
|
|||
// Try to obtain refreshed access token
|
||||
try {
|
||||
$newAccessToken = $this->refreshAccessToken($accessToken);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
// Log out if an unknown problem arises
|
||||
$this->actionLogout();
|
||||
throw $e;
|
||||
|
@ -110,7 +111,7 @@ class OpenIdService extends ExternalAuthService
|
|||
|
||||
/**
|
||||
* Generate an updated access token, through the associated refresh token.
|
||||
* @throws Error
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function refreshAccessToken(AccessToken $accessToken): ?AccessToken
|
||||
{
|
||||
|
@ -135,11 +136,8 @@ class OpenIdService extends ExternalAuthService
|
|||
* return the matching, or new if registration active, user matched to
|
||||
* the authorization server.
|
||||
* Returns null if not authenticated.
|
||||
* @throws Error
|
||||
* @throws OpenIdException
|
||||
* @throws ValidationError
|
||||
* @throws JsonDebugException
|
||||
* @throws UserRegistrationException
|
||||
* @throws Exception
|
||||
* @throws InvalidTokenException
|
||||
*/
|
||||
public function processAuthorizeResponse(?string $authorizationCode): ?User
|
||||
{
|
||||
|
@ -164,87 +162,50 @@ class OpenIdService extends ExternalAuthService
|
|||
|
||||
/**
|
||||
* Load the underlying OpenID Connect Provider.
|
||||
* @throws Error
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getProvider(): OpenIDConnectProvider
|
||||
{
|
||||
// Setup settings
|
||||
$settings = $this->config['openid'];
|
||||
$overrides = $this->config['openid_overrides'] ?? [];
|
||||
|
||||
if ($overrides && is_string($overrides)) {
|
||||
$overrides = json_decode($overrides, true);
|
||||
}
|
||||
|
||||
$openIdSettings = $this->loadOpenIdDetails();
|
||||
$settings = array_replace_recursive($settings, $openIdSettings, $overrides);
|
||||
$settings = [
|
||||
'clientId' => $this->config['client_id'],
|
||||
'clientSecret' => $this->config['client_secret'],
|
||||
'idTokenIssuer' => $this->config['issuer'],
|
||||
'redirectUri' => url('/openid/redirect'),
|
||||
'urlAuthorize' => $this->config['authorization_endpoint'],
|
||||
'urlAccessToken' => $this->config['token_endpoint'],
|
||||
'urlResourceOwnerDetails' => null,
|
||||
'publicKey' => $this->config['jwt_public_key'],
|
||||
'scopes' => 'profile email',
|
||||
];
|
||||
|
||||
// Setup services
|
||||
$services = $this->loadOpenIdServices();
|
||||
$overrides = $this->config['openid_services'] ?? [];
|
||||
|
||||
$services = array_replace_recursive($services, $overrides);
|
||||
$services = [
|
||||
'signer' => new Sha256(),
|
||||
];
|
||||
|
||||
return new OpenIDConnectProvider($settings, $services);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load services utilized by the OpenID Connect provider.
|
||||
*/
|
||||
protected function loadOpenIdServices(): array
|
||||
{
|
||||
return [
|
||||
'signer' => new \Lcobucci\JWT\Signer\Rsa\Sha256(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load dynamic service provider options required by the OpenID Connect provider.
|
||||
*/
|
||||
protected function loadOpenIdDetails(): array
|
||||
{
|
||||
return [
|
||||
'redirectUri' => url('/openid/redirect'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the display name
|
||||
*/
|
||||
protected function getUserDisplayName(Token $token, string $defaultValue): string
|
||||
{
|
||||
$displayNameAttr = $this->config['display_name_attributes'];
|
||||
$displayNameAttr = $this->config['display_name_claims'];
|
||||
|
||||
$displayName = [];
|
||||
foreach ($displayNameAttr as $dnAttr) {
|
||||
$dnComponent = $token->getClaim($dnAttr, '');
|
||||
$dnComponent = $token->claims()->get($dnAttr, '');
|
||||
if ($dnComponent !== '') {
|
||||
$displayName[] = $dnComponent;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($displayName) == 0) {
|
||||
$displayName = $defaultValue;
|
||||
} else {
|
||||
$displayName = implode(' ', $displayName);
|
||||
$displayName[] = $defaultValue;
|
||||
}
|
||||
|
||||
return $displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value to use as the external id saved in BookStack
|
||||
* used to link the user to an existing BookStack DB user.
|
||||
*/
|
||||
protected function getExternalId(Token $token, string $defaultValue)
|
||||
{
|
||||
$userNameAttr = $this->config['external_id_attribute'];
|
||||
if ($userNameAttr === null) {
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
return $token->getClaim($userNameAttr, $defaultValue);
|
||||
return implode(' ', $displayName);;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -252,16 +213,11 @@ class OpenIdService extends ExternalAuthService
|
|||
*/
|
||||
protected function getUserDetails(Token $token): array
|
||||
{
|
||||
$email = null;
|
||||
$emailAttr = $this->config['email_attribute'];
|
||||
if ($token->hasClaim($emailAttr)) {
|
||||
$email = $token->getClaim($emailAttr);
|
||||
}
|
||||
|
||||
$id = $token->claims()->get('sub');
|
||||
return [
|
||||
'external_id' => $token->getClaim('sub'),
|
||||
'email' => $email,
|
||||
'name' => $this->getUserDisplayName($token, $email),
|
||||
'external_id' => $id,
|
||||
'email' => $token->claims()->get('email'),
|
||||
'name' => $this->getUserDisplayName($token, $id),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class Saml2Service extends ExternalAuthService
|
|||
/**
|
||||
* Saml2Service constructor.
|
||||
*/
|
||||
public function __construct(RegistrationService $registrationService, LoginService $loginService, User $user),
|
||||
public function __construct(RegistrationService $registrationService, LoginService $loginService, User $user)
|
||||
{
|
||||
parent::__construct($registrationService, $user);
|
||||
|
||||
|
|
30
app/Config/oidc.php
Normal file
30
app/Config/oidc.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
// Display name, shown to users, for OpenId option
|
||||
'name' => env('OIDC_NAME', 'SSO'),
|
||||
|
||||
// Dump user details after a login request for debugging purposes
|
||||
'dump_user_details' => env('OIDC_DUMP_USER_DETAILS', false),
|
||||
|
||||
// Attribute, within a OpenId token, to find the user's display name
|
||||
'display_name_claims' => explode('|', env('OIDC_DISPLAY_NAME_CLAIMS', 'name')),
|
||||
|
||||
// OAuth2/OpenId client id, as configured in your Authorization server.
|
||||
'client_id' => env('OIDC_CLIENT_ID', null),
|
||||
|
||||
// OAuth2/OpenId client secret, as configured in your Authorization server.
|
||||
'client_secret' => env('OIDC_CLIENT_SECRET', null),
|
||||
|
||||
// The issuer of the identity token (id_token) this will be compared with what is returned in the token.
|
||||
'issuer' => env('OIDC_ISSUER', null),
|
||||
|
||||
// Public key that's used to verify the JWT token with.
|
||||
// Can be the key value itself or a local 'file://public.key' reference.
|
||||
'jwt_public_key' => env('OIDC_PUBLIC_KEY', null),
|
||||
|
||||
// OAuth2 endpoints.
|
||||
'authorization_endpoint' => env('OIDC_AUTH_ENDPOINT', null),
|
||||
'token_endpoint' => env('OIDC_TOKEN_ENDPOINT', null),
|
||||
];
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
// Display name, shown to users, for OpenId option
|
||||
'name' => env('OPENID_NAME', 'SSO'),
|
||||
|
||||
// Dump user details after a login request for debugging purposes
|
||||
'dump_user_details' => env('OPENID_DUMP_USER_DETAILS', false),
|
||||
|
||||
// Attribute, within a OpenId token, to find the user's email address
|
||||
'email_attribute' => env('OPENID_EMAIL_ATTRIBUTE', 'email'),
|
||||
// Attribute, within a OpenId token, to find the user's display name
|
||||
'display_name_attributes' => explode('|', env('OPENID_DISPLAY_NAME_ATTRIBUTES', 'name')),
|
||||
// Attribute, within a OpenId token, to use to connect a BookStack user to the OpenId user.
|
||||
'external_id_attribute' => env('OPENID_EXTERNAL_ID_ATTRIBUTE', null),
|
||||
|
||||
// Overrides, in JSON format, to the configuration passed to underlying OpenIDConnectProvider library.
|
||||
'openid_overrides' => env('OPENID_OVERRIDES', null),
|
||||
|
||||
// Custom service instances, used by the underlying OpenIDConnectProvider library
|
||||
'openid_services' => [],
|
||||
|
||||
'openid' => [
|
||||
// OAuth2/OpenId client id, as configured in your Authorization server.
|
||||
'clientId' => env('OPENID_CLIENT_ID', ''),
|
||||
|
||||
// OAuth2/OpenId client secret, as configured in your Authorization server.
|
||||
'clientSecret' => env('OPENID_CLIENT_SECRET', ''),
|
||||
|
||||
// OAuth2 scopes that are request, by default the OpenId-native profile and email scopes.
|
||||
'scopes' => 'profile email',
|
||||
|
||||
// The issuer of the identity token (id_token) this will be compared with what is returned in the token.
|
||||
'idTokenIssuer' => env('OPENID_ISSUER', ''),
|
||||
|
||||
// Public key that's used to verify the JWT token with.
|
||||
'publicKey' => env('OPENID_PUBLIC_KEY', ''),
|
||||
|
||||
// OAuth2 endpoints.
|
||||
'urlAuthorize' => env('OPENID_URL_AUTHORIZE', ''),
|
||||
'urlAccessToken' => env('OPENID_URL_TOKEN', ''),
|
||||
'urlResourceOwnerDetails' => env('OPENID_URL_RESOURCE', ''),
|
||||
],
|
||||
|
||||
];
|
418
composer.lock
generated
418
composer.lock
generated
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue