0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-05-04 00:09:58 +00:00

Added oidc_id_token_pre_validate logical theme event

For 
This commit is contained in:
Dan Brown 2023-04-27 23:40:14 +01:00
parent 277d5392fb
commit f64ce71afc
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
5 changed files with 101 additions and 44 deletions

View file

@ -4,35 +4,16 @@ namespace BookStack\Auth\Access\Oidc;
class OidcIdToken class OidcIdToken
{ {
/** protected array $header;
* @var array protected array $payload;
*/ protected string $signature;
protected $header; protected string $issuer;
protected array $tokenParts = [];
/**
* @var array
*/
protected $payload;
/**
* @var string
*/
protected $signature;
/** /**
* @var array[]|string[] * @var array[]|string[]
*/ */
protected $keys; protected array $keys;
/**
* @var string
*/
protected $issuer;
/**
* @var array
*/
protected $tokenParts = [];
public function __construct(string $token, string $issuer, array $keys) public function __construct(string $token, string $issuer, array $keys)
{ {
@ -106,6 +87,14 @@ class OidcIdToken
return $this->payload; return $this->payload;
} }
/**
* Replace the existing claim data of this token with that provided.
*/
public function replaceClaims(array $claims): void
{
$this->payload = $claims;
}
/** /**
* Validate the structure of the given token and ensure we have the required pieces. * Validate the structure of the given token and ensure we have the required pieces.
* As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2. * As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2.

View file

@ -9,6 +9,8 @@ use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException; use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\StoppedAuthenticationException; use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException; use BookStack\Exceptions\UserRegistrationException;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider; use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
@ -21,24 +23,12 @@ use Psr\Http\Client\ClientInterface as HttpClient;
*/ */
class OidcService class OidcService
{ {
protected RegistrationService $registrationService;
protected LoginService $loginService;
protected HttpClient $httpClient;
protected GroupSyncService $groupService;
/**
* OpenIdService constructor.
*/
public function __construct( public function __construct(
RegistrationService $registrationService, protected RegistrationService $registrationService,
LoginService $loginService, protected LoginService $loginService,
HttpClient $httpClient, protected HttpClient $httpClient,
GroupSyncService $groupService protected GroupSyncService $groupService
) { ) {
$this->registrationService = $registrationService;
$this->loginService = $loginService;
$this->httpClient = $httpClient;
$this->groupService = $groupService;
} }
/** /**
@ -226,6 +216,16 @@ class OidcService
$settings->keys, $settings->keys,
); );
$returnClaims = Theme::dispatch(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $idToken->getAllClaims(), [
'access_token' => $accessToken->getToken(),
'expires_in' => $accessToken->getExpires(),
'refresh_token' => $accessToken->getRefreshToken(),
]);
if (!is_null($returnClaims)) {
$idToken->replaceClaims($returnClaims);
}
if ($this->config()['dump_user_details']) { if ($this->config()['dump_user_details']) {
throw new JsonDebugException($idToken->getAllClaims()); throw new JsonDebugException($idToken->getAllClaims());
} }

View file

@ -70,6 +70,19 @@ class ThemeEvents
*/ */
const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure'; const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
/**
* OIDC ID token pre-validate event.
* Runs just before BookStack validates the user ID token data upon login.
* Provides the existing found set of claims for the user as a key-value array,
* along with an array of the proceeding access token data provided by the identity platform.
* If the listener returns a non-null value, that will replace the existing ID token claim data.
*
* @param array $idTokenData
* @param array $accessTokenData
* @returns array|null
*/
const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate';
/** /**
* Page include parse event. * Page include parse event.
* Runs when a page include tag is being parsed, typically when page content is being processed for viewing. * Runs when a page include tag is being parsed, typically when page content is being processed for viewing.

View file

@ -5,6 +5,8 @@ namespace Tests\Auth;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Auth\Role; use BookStack\Auth\Role;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Response;
use Illuminate\Testing\TestResponse; use Illuminate\Testing\TestResponse;
@ -397,7 +399,6 @@ class OidcTest extends TestCase
config()->set([ config()->set([
'oidc.external_id_claim' => 'super_awesome_id', 'oidc.external_id_claim' => 'super_awesome_id',
]); ]);
$roleA = Role::factory()->create(['display_name' => 'Wizards']);
$resp = $this->runLogin([ $resp = $this->runLogin([
'email' => 'benny@example.com', 'email' => 'benny@example.com',
@ -464,6 +465,60 @@ class OidcTest extends TestCase
$this->assertTrue($user->hasRole($roleA->id)); $this->assertTrue($user->hasRole($roleA->id));
} }
public function test_oidc_id_token_pre_validate_theme_event_without_return()
{
$args = [];
$callback = function (...$eventArgs) use (&$args) {
$args = $eventArgs;
};
Theme::listen(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $callback);
$resp = $this->runLogin([
'email' => 'benny@example.com',
'sub' => 'benny1010101',
'name' => 'Benny',
]);
$resp->assertRedirect('/');
$this->assertDatabaseHas('users', [
'external_auth_id' => 'benny1010101',
]);
$this->assertArrayHasKey('iss', $args[0]);
$this->assertArrayHasKey('sub', $args[0]);
$this->assertEquals('Benny', $args[0]['name']);
$this->assertEquals('benny1010101', $args[0]['sub']);
$this->assertArrayHasKey('access_token', $args[1]);
$this->assertArrayHasKey('expires_in', $args[1]);
$this->assertArrayHasKey('refresh_token', $args[1]);
}
public function test_oidc_id_token_pre_validate_theme_event_with_return()
{
$callback = function (...$eventArgs) {
return array_merge($eventArgs[0], [
'email' => 'lenny@example.com',
'sub' => 'lenny1010101',
'name' => 'Lenny',
]);
};
Theme::listen(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $callback);
$resp = $this->runLogin([
'email' => 'benny@example.com',
'sub' => 'benny1010101',
'name' => 'Benny',
]);
$resp->assertRedirect('/');
$this->assertDatabaseHas('users', [
'email' => 'lenny@example.com',
'external_auth_id' => 'lenny1010101',
'name' => 'Lenny',
]);
}
protected function withAutodiscovery() protected function withAutodiscovery()
{ {
config()->set([ config()->set([

View file

@ -23,8 +23,8 @@ use League\CommonMark\Environment\Environment;
class ThemeTest extends TestCase class ThemeTest extends TestCase
{ {
protected $themeFolderName; protected string $themeFolderName;
protected $themeFolderPath; protected string $themeFolderPath;
public function test_translation_text_can_be_overridden_via_theme() public function test_translation_text_can_be_overridden_via_theme()
{ {