Added oidc_id_token_pre_validate logical theme event

For #4200
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
{
/**
* @var array
*/
protected $header;
/**
* @var array
*/
protected $payload;
/**
* @var string
*/
protected $signature;
protected array $header;
protected array $payload;
protected string $signature;
protected string $issuer;
protected array $tokenParts = [];
/**
* @var array[]|string[]
*/
protected $keys;
/**
* @var string
*/
protected $issuer;
/**
* @var array
*/
protected $tokenParts = [];
protected array $keys;
public function __construct(string $token, string $issuer, array $keys)
{
@ -106,6 +87,14 @@ class OidcIdToken
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.
* 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\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
@ -21,24 +23,12 @@ use Psr\Http\Client\ClientInterface as HttpClient;
*/
class OidcService
{
protected RegistrationService $registrationService;
protected LoginService $loginService;
protected HttpClient $httpClient;
protected GroupSyncService $groupService;
/**
* OpenIdService constructor.
*/
public function __construct(
RegistrationService $registrationService,
LoginService $loginService,
HttpClient $httpClient,
GroupSyncService $groupService
protected RegistrationService $registrationService,
protected LoginService $loginService,
protected HttpClient $httpClient,
protected GroupSyncService $groupService
) {
$this->registrationService = $registrationService;
$this->loginService = $loginService;
$this->httpClient = $httpClient;
$this->groupService = $groupService;
}
/**
@ -226,6 +216,16 @@ class OidcService
$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']) {
throw new JsonDebugException($idToken->getAllClaims());
}

View File

@ -70,6 +70,19 @@ class ThemeEvents
*/
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.
* 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\Auth\Role;
use BookStack\Auth\User;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Illuminate\Testing\TestResponse;
@ -397,7 +399,6 @@ class OidcTest extends TestCase
config()->set([
'oidc.external_id_claim' => 'super_awesome_id',
]);
$roleA = Role::factory()->create(['display_name' => 'Wizards']);
$resp = $this->runLogin([
'email' => 'benny@example.com',
@ -464,6 +465,60 @@ class OidcTest extends TestCase
$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()
{
config()->set([

View File

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