mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
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
@ -240,12 +240,15 @@ SAML2_GROUP_ATTRIBUTE=group
|
|||||||
SAML2_REMOVE_FROM_GROUPS=false
|
SAML2_REMOVE_FROM_GROUPS=false
|
||||||
|
|
||||||
# OpenID Connect authentication configuration
|
# OpenID Connect authentication configuration
|
||||||
OPENID_CLIENT_ID=null
|
OIDC_NAME=SSO
|
||||||
OPENID_CLIENT_SECRET=null
|
OIDC_DISPLAY_NAME_CLAIMS=name
|
||||||
OPENID_ISSUER=https://example.com
|
OIDC_CLIENT_ID=null
|
||||||
OPENID_PUBLIC_KEY=file:///my/public.key
|
OIDC_CLIENT_SECRET=null
|
||||||
OPENID_URL_AUTHORIZE=https://example.com/authorize
|
OIDC_ISSUER=null
|
||||||
OPENID_URL_TOKEN=https://example.com/token
|
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
|
# Disable default third-party services such as Gravatar and Draw.IO
|
||||||
# Service-specific options will override this option
|
# Service-specific options will override this option
|
||||||
|
@ -4,8 +4,8 @@ namespace BookStack\Auth\Access;
|
|||||||
|
|
||||||
use BookStack\Auth\Role;
|
use BookStack\Auth\Role;
|
||||||
use BookStack\Auth\User;
|
use BookStack\Auth\User;
|
||||||
|
use BookStack\Exceptions\UserRegistrationException;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ExternalAuthService
|
class ExternalAuthService
|
||||||
|
@ -5,9 +5,11 @@ use BookStack\Exceptions\JsonDebugException;
|
|||||||
use BookStack\Exceptions\OpenIdException;
|
use BookStack\Exceptions\OpenIdException;
|
||||||
use BookStack\Exceptions\UserRegistrationException;
|
use BookStack\Exceptions\UserRegistrationException;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||||
use Lcobucci\JWT\Token;
|
use Lcobucci\JWT\Token;
|
||||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||||
use OpenIDConnectClient\AccessToken;
|
use OpenIDConnectClient\AccessToken;
|
||||||
|
use OpenIDConnectClient\Exception\InvalidTokenException;
|
||||||
use OpenIDConnectClient\OpenIDConnectProvider;
|
use OpenIDConnectClient\OpenIDConnectProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,12 +27,12 @@ class OpenIdService extends ExternalAuthService
|
|||||||
{
|
{
|
||||||
parent::__construct($registrationService, $user);
|
parent::__construct($registrationService, $user);
|
||||||
|
|
||||||
$this->config = config('openid');
|
$this->config = config('oidc');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate a authorization flow.
|
* Initiate an authorization flow.
|
||||||
* @throws Error
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function login(): array
|
public function login(): array
|
||||||
{
|
{
|
||||||
@ -43,7 +45,6 @@ class OpenIdService extends ExternalAuthService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate a logout flow.
|
* Initiate a logout flow.
|
||||||
* @throws Error
|
|
||||||
*/
|
*/
|
||||||
public function logout(): array
|
public function logout(): array
|
||||||
{
|
{
|
||||||
@ -56,7 +57,7 @@ class OpenIdService extends ExternalAuthService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the currently logged in user.
|
* Refresh the currently logged in user.
|
||||||
* @throws Error
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function refresh(): bool
|
public function refresh(): bool
|
||||||
{
|
{
|
||||||
@ -79,7 +80,7 @@ class OpenIdService extends ExternalAuthService
|
|||||||
// Try to obtain refreshed access token
|
// Try to obtain refreshed access token
|
||||||
try {
|
try {
|
||||||
$newAccessToken = $this->refreshAccessToken($accessToken);
|
$newAccessToken = $this->refreshAccessToken($accessToken);
|
||||||
} catch (\Exception $e) {
|
} catch (Exception $e) {
|
||||||
// Log out if an unknown problem arises
|
// Log out if an unknown problem arises
|
||||||
$this->actionLogout();
|
$this->actionLogout();
|
||||||
throw $e;
|
throw $e;
|
||||||
@ -110,7 +111,7 @@ class OpenIdService extends ExternalAuthService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate an updated access token, through the associated refresh token.
|
* Generate an updated access token, through the associated refresh token.
|
||||||
* @throws Error
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
protected function refreshAccessToken(AccessToken $accessToken): ?AccessToken
|
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
|
* return the matching, or new if registration active, user matched to
|
||||||
* the authorization server.
|
* the authorization server.
|
||||||
* Returns null if not authenticated.
|
* Returns null if not authenticated.
|
||||||
* @throws Error
|
* @throws Exception
|
||||||
* @throws OpenIdException
|
* @throws InvalidTokenException
|
||||||
* @throws ValidationError
|
|
||||||
* @throws JsonDebugException
|
|
||||||
* @throws UserRegistrationException
|
|
||||||
*/
|
*/
|
||||||
public function processAuthorizeResponse(?string $authorizationCode): ?User
|
public function processAuthorizeResponse(?string $authorizationCode): ?User
|
||||||
{
|
{
|
||||||
@ -164,87 +162,50 @@ class OpenIdService extends ExternalAuthService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the underlying OpenID Connect Provider.
|
* Load the underlying OpenID Connect Provider.
|
||||||
* @throws Error
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
*/
|
||||||
protected function getProvider(): OpenIDConnectProvider
|
protected function getProvider(): OpenIDConnectProvider
|
||||||
{
|
{
|
||||||
// Setup settings
|
// Setup settings
|
||||||
$settings = $this->config['openid'];
|
$settings = [
|
||||||
$overrides = $this->config['openid_overrides'] ?? [];
|
'clientId' => $this->config['client_id'],
|
||||||
|
'clientSecret' => $this->config['client_secret'],
|
||||||
if ($overrides && is_string($overrides)) {
|
'idTokenIssuer' => $this->config['issuer'],
|
||||||
$overrides = json_decode($overrides, true);
|
'redirectUri' => url('/openid/redirect'),
|
||||||
}
|
'urlAuthorize' => $this->config['authorization_endpoint'],
|
||||||
|
'urlAccessToken' => $this->config['token_endpoint'],
|
||||||
$openIdSettings = $this->loadOpenIdDetails();
|
'urlResourceOwnerDetails' => null,
|
||||||
$settings = array_replace_recursive($settings, $openIdSettings, $overrides);
|
'publicKey' => $this->config['jwt_public_key'],
|
||||||
|
'scopes' => 'profile email',
|
||||||
|
];
|
||||||
|
|
||||||
// Setup services
|
// Setup services
|
||||||
$services = $this->loadOpenIdServices();
|
$services = [
|
||||||
$overrides = $this->config['openid_services'] ?? [];
|
'signer' => new Sha256(),
|
||||||
|
];
|
||||||
$services = array_replace_recursive($services, $overrides);
|
|
||||||
|
|
||||||
return new OpenIDConnectProvider($settings, $services);
|
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
|
* Calculate the display name
|
||||||
*/
|
*/
|
||||||
protected function getUserDisplayName(Token $token, string $defaultValue): string
|
protected function getUserDisplayName(Token $token, string $defaultValue): string
|
||||||
{
|
{
|
||||||
$displayNameAttr = $this->config['display_name_attributes'];
|
$displayNameAttr = $this->config['display_name_claims'];
|
||||||
|
|
||||||
$displayName = [];
|
$displayName = [];
|
||||||
foreach ($displayNameAttr as $dnAttr) {
|
foreach ($displayNameAttr as $dnAttr) {
|
||||||
$dnComponent = $token->getClaim($dnAttr, '');
|
$dnComponent = $token->claims()->get($dnAttr, '');
|
||||||
if ($dnComponent !== '') {
|
if ($dnComponent !== '') {
|
||||||
$displayName[] = $dnComponent;
|
$displayName[] = $dnComponent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($displayName) == 0) {
|
if (count($displayName) == 0) {
|
||||||
$displayName = $defaultValue;
|
$displayName[] = $defaultValue;
|
||||||
} else {
|
|
||||||
$displayName = implode(' ', $displayName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $displayName;
|
return implode(' ', $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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -252,16 +213,11 @@ class OpenIdService extends ExternalAuthService
|
|||||||
*/
|
*/
|
||||||
protected function getUserDetails(Token $token): array
|
protected function getUserDetails(Token $token): array
|
||||||
{
|
{
|
||||||
$email = null;
|
$id = $token->claims()->get('sub');
|
||||||
$emailAttr = $this->config['email_attribute'];
|
|
||||||
if ($token->hasClaim($emailAttr)) {
|
|
||||||
$email = $token->getClaim($emailAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'external_id' => $token->getClaim('sub'),
|
'external_id' => $id,
|
||||||
'email' => $email,
|
'email' => $token->claims()->get('email'),
|
||||||
'name' => $this->getUserDisplayName($token, $email),
|
'name' => $this->getUserDisplayName($token, $id),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class Saml2Service extends ExternalAuthService
|
|||||||
/**
|
/**
|
||||||
* Saml2Service constructor.
|
* Saml2Service constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(RegistrationService $registrationService, LoginService $loginService, User $user),
|
public function __construct(RegistrationService $registrationService, LoginService $loginService, User $user)
|
||||||
{
|
{
|
||||||
parent::__construct($registrationService, $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 New Issue
Block a user