Skip to content

Commit f64ce71

Browse files
committed
Added oidc_id_token_pre_validate logical theme event
For #4200
1 parent 277d539 commit f64ce71

File tree

5 files changed

+101
-44
lines changed

5 files changed

+101
-44
lines changed

app/Auth/Access/Oidc/OidcIdToken.php

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,16 @@
44

55
class OidcIdToken
66
{
7-
/**
8-
* @var array
9-
*/
10-
protected $header;
11-
12-
/**
13-
* @var array
14-
*/
15-
protected $payload;
16-
17-
/**
18-
* @var string
19-
*/
20-
protected $signature;
7+
protected array $header;
8+
protected array $payload;
9+
protected string $signature;
10+
protected string $issuer;
11+
protected array $tokenParts = [];
2112

2213
/**
2314
* @var array[]|string[]
2415
*/
25-
protected $keys;
26-
27-
/**
28-
* @var string
29-
*/
30-
protected $issuer;
31-
32-
/**
33-
* @var array
34-
*/
35-
protected $tokenParts = [];
16+
protected array $keys;
3617

3718
public function __construct(string $token, string $issuer, array $keys)
3819
{
@@ -106,6 +87,14 @@ public function getAllClaims(): array
10687
return $this->payload;
10788
}
10889

90+
/**
91+
* Replace the existing claim data of this token with that provided.
92+
*/
93+
public function replaceClaims(array $claims): void
94+
{
95+
$this->payload = $claims;
96+
}
97+
10998
/**
11099
* Validate the structure of the given token and ensure we have the required pieces.
111100
* As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2.

app/Auth/Access/Oidc/OidcService.php

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use BookStack\Exceptions\JsonDebugException;
1010
use BookStack\Exceptions\StoppedAuthenticationException;
1111
use BookStack\Exceptions\UserRegistrationException;
12+
use BookStack\Facades\Theme;
13+
use BookStack\Theming\ThemeEvents;
1214
use Illuminate\Support\Arr;
1315
use Illuminate\Support\Facades\Cache;
1416
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
@@ -21,24 +23,12 @@
2123
*/
2224
class OidcService
2325
{
24-
protected RegistrationService $registrationService;
25-
protected LoginService $loginService;
26-
protected HttpClient $httpClient;
27-
protected GroupSyncService $groupService;
28-
29-
/**
30-
* OpenIdService constructor.
31-
*/
3226
public function __construct(
33-
RegistrationService $registrationService,
34-
LoginService $loginService,
35-
HttpClient $httpClient,
36-
GroupSyncService $groupService
27+
protected RegistrationService $registrationService,
28+
protected LoginService $loginService,
29+
protected HttpClient $httpClient,
30+
protected GroupSyncService $groupService
3731
) {
38-
$this->registrationService = $registrationService;
39-
$this->loginService = $loginService;
40-
$this->httpClient = $httpClient;
41-
$this->groupService = $groupService;
4232
}
4333

4434
/**
@@ -226,6 +216,16 @@ protected function processAccessTokenCallback(OidcAccessToken $accessToken, Oidc
226216
$settings->keys,
227217
);
228218

219+
$returnClaims = Theme::dispatch(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $idToken->getAllClaims(), [
220+
'access_token' => $accessToken->getToken(),
221+
'expires_in' => $accessToken->getExpires(),
222+
'refresh_token' => $accessToken->getRefreshToken(),
223+
]);
224+
225+
if (!is_null($returnClaims)) {
226+
$idToken->replaceClaims($returnClaims);
227+
}
228+
229229
if ($this->config()['dump_user_details']) {
230230
throw new JsonDebugException($idToken->getAllClaims());
231231
}

app/Theming/ThemeEvents.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ class ThemeEvents
7070
*/
7171
const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
7272

73+
/**
74+
* OIDC ID token pre-validate event.
75+
* Runs just before BookStack validates the user ID token data upon login.
76+
* Provides the existing found set of claims for the user as a key-value array,
77+
* along with an array of the proceeding access token data provided by the identity platform.
78+
* If the listener returns a non-null value, that will replace the existing ID token claim data.
79+
*
80+
* @param array $idTokenData
81+
* @param array $accessTokenData
82+
* @returns array|null
83+
*/
84+
const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate';
85+
7386
/**
7487
* Page include parse event.
7588
* Runs when a page include tag is being parsed, typically when page content is being processed for viewing.

tests/Auth/OidcTest.php

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use BookStack\Actions\ActivityType;
66
use BookStack\Auth\Role;
77
use BookStack\Auth\User;
8+
use BookStack\Facades\Theme;
9+
use BookStack\Theming\ThemeEvents;
810
use GuzzleHttp\Psr7\Request;
911
use GuzzleHttp\Psr7\Response;
1012
use Illuminate\Testing\TestResponse;
@@ -397,7 +399,6 @@ public function test_auth_uses_configured_external_id_claim_option()
397399
config()->set([
398400
'oidc.external_id_claim' => 'super_awesome_id',
399401
]);
400-
$roleA = Role::factory()->create(['display_name' => 'Wizards']);
401402

402403
$resp = $this->runLogin([
403404
'email' => 'benny@example.com',
@@ -464,6 +465,60 @@ public function test_login_group_sync_with_nested_groups_in_token()
464465
$this->assertTrue($user->hasRole($roleA->id));
465466
}
466467

468+
public function test_oidc_id_token_pre_validate_theme_event_without_return()
469+
{
470+
$args = [];
471+
$callback = function (...$eventArgs) use (&$args) {
472+
$args = $eventArgs;
473+
};
474+
Theme::listen(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $callback);
475+
476+
$resp = $this->runLogin([
477+
'email' => 'benny@example.com',
478+
'sub' => 'benny1010101',
479+
'name' => 'Benny',
480+
]);
481+
$resp->assertRedirect('/');
482+
483+
$this->assertDatabaseHas('users', [
484+
'external_auth_id' => 'benny1010101',
485+
]);
486+
487+
$this->assertArrayHasKey('iss', $args[0]);
488+
$this->assertArrayHasKey('sub', $args[0]);
489+
$this->assertEquals('Benny', $args[0]['name']);
490+
$this->assertEquals('benny1010101', $args[0]['sub']);
491+
492+
$this->assertArrayHasKey('access_token', $args[1]);
493+
$this->assertArrayHasKey('expires_in', $args[1]);
494+
$this->assertArrayHasKey('refresh_token', $args[1]);
495+
}
496+
497+
public function test_oidc_id_token_pre_validate_theme_event_with_return()
498+
{
499+
$callback = function (...$eventArgs) {
500+
return array_merge($eventArgs[0], [
501+
'email' => 'lenny@example.com',
502+
'sub' => 'lenny1010101',
503+
'name' => 'Lenny',
504+
]);
505+
};
506+
Theme::listen(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $callback);
507+
508+
$resp = $this->runLogin([
509+
'email' => 'benny@example.com',
510+
'sub' => 'benny1010101',
511+
'name' => 'Benny',
512+
]);
513+
$resp->assertRedirect('/');
514+
515+
$this->assertDatabaseHas('users', [
516+
'email' => 'lenny@example.com',
517+
'external_auth_id' => 'lenny1010101',
518+
'name' => 'Lenny',
519+
]);
520+
}
521+
467522
protected function withAutodiscovery()
468523
{
469524
config()->set([

tests/ThemeTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323

2424
class ThemeTest extends TestCase
2525
{
26-
protected $themeFolderName;
27-
protected $themeFolderPath;
26+
protected string $themeFolderName;
27+
protected string $themeFolderPath;
2828

2929
public function test_translation_text_can_be_overridden_via_theme()
3030
{

0 commit comments

Comments
 (0)