]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'development' into lukeshu/oidc-development
authorDan Brown <redacted>
Tue, 16 Apr 2024 13:57:36 +0000 (14:57 +0100)
committerDan Brown <redacted>
Tue, 16 Apr 2024 13:57:36 +0000 (14:57 +0100)
.env.example.complete
app/Access/Oidc/OidcProviderSettings.php
app/Access/Oidc/OidcService.php
app/Config/oidc.php
tests/Auth/OidcTest.php

index e8520a24caec0801b745537701bf4e66b5762924..1242968182a0e22b9a8d9b8fa20b60ddda9ec3df 100644 (file)
@@ -267,6 +267,7 @@ OIDC_ISSUER_DISCOVER=false
 OIDC_PUBLIC_KEY=null
 OIDC_AUTH_ENDPOINT=null
 OIDC_TOKEN_ENDPOINT=null
+OIDC_USERINFO_ENDPOINT=null
 OIDC_ADDITIONAL_SCOPES=null
 OIDC_DUMP_USER_DETAILS=false
 OIDC_USER_TO_GROUPS=false
index bea6a523e773987612aefae83aeb903ee0d1ae93..49ccab6f0deec10dd10239aafac32d83bb4496e6 100644 (file)
@@ -22,6 +22,7 @@ class OidcProviderSettings
     public ?string $authorizationEndpoint;
     public ?string $tokenEndpoint;
     public ?string $endSessionEndpoint;
+    public ?string $userinfoEndpoint;
 
     /**
      * @var string[]|array[]
@@ -128,6 +129,10 @@ class OidcProviderSettings
             $discoveredSettings['tokenEndpoint'] = $result['token_endpoint'];
         }
 
+        if (!empty($result['userinfo_endpoint'])) {
+            $discoveredSettings['userinfoEndpoint'] = $result['userinfo_endpoint'];
+        }
+
         if (!empty($result['jwks_uri'])) {
             $keys = $this->loadKeysFromUri($result['jwks_uri'], $httpClient);
             $discoveredSettings['keys'] = $this->filterKeys($keys);
@@ -177,7 +182,7 @@ class OidcProviderSettings
      */
     public function arrayForProvider(): array
     {
-        $settingKeys = ['clientId', 'clientSecret', 'redirectUri', 'authorizationEndpoint', 'tokenEndpoint'];
+        $settingKeys = ['clientId', 'clientSecret', 'redirectUri', 'authorizationEndpoint', 'tokenEndpoint', 'userinfoEndpoint'];
         $settings = [];
         foreach ($settingKeys as $setting) {
             $settings[$setting] = $this->$setting;
index 036c9fc47efcf7ac32f0908c57337743d0f005b5..467e31417704931412ef4100b11ed03154a5d566 100644 (file)
@@ -95,6 +95,7 @@ class OidcService
             'authorizationEndpoint' => $config['authorization_endpoint'],
             'tokenEndpoint'         => $config['token_endpoint'],
             'endSessionEndpoint'    => is_string($config['end_session_endpoint']) ? $config['end_session_endpoint'] : null,
+            'userinfoEndpoint'      => $config['userinfo_endpoint'],
         ]);
 
         // Use keys if configured
@@ -238,6 +239,17 @@ class OidcService
 
         session()->put("oidc_id_token", $idTokenText);
 
+        if (!empty($settings->userinfoEndpoint)) {
+            $provider = $this->getProvider($settings);
+            $request = $provider->getAuthenticatedRequest('GET', $settings->userinfoEndpoint, $accessToken->getToken());
+            $response = $provider->getParsedResponse($request);
+            $claims = $idToken->getAllClaims();
+            foreach ($response as $key => $value) {
+                $claims[$key] = $value;
+            }
+            $idToken->replaceClaims($claims);
+        }
+
         $returnClaims = Theme::dispatch(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $idToken->getAllClaims(), [
             'access_token' => $accessToken->getToken(),
             'expires_in' => $accessToken->getExpires(),
index 7f8f40d419090af498907ee4bb2b8c59dee6e926..8b5470931d08379431c8f3b420f02aa7356c87b1 100644 (file)
@@ -35,6 +35,7 @@ return [
     // OAuth2 endpoints.
     'authorization_endpoint' => env('OIDC_AUTH_ENDPOINT', null),
     'token_endpoint'         => env('OIDC_TOKEN_ENDPOINT', null),
+    'userinfo_endpoint'      => env('OIDC_USERINFO_ENDPOINT', null),
 
     // OIDC RP-Initiated Logout endpoint URL.
     // A false value force-disables RP-Initiated Logout.
index 228c75e9eade8f29e5daf3adfa08e1d725f23c78..6617229838fd0fedf81a02de1d0cd4d9ae7ce86c 100644 (file)
@@ -702,11 +702,44 @@ class OidcTest extends TestCase
 
     protected function runLogin($claimOverrides = []): TestResponse
     {
+        // These two variables should perhaps be arguments instead of
+        // assuming that they're tied to whether discovery is enabled,
+        // but that's how the tests are written for now.
+        $claimsInIdToken = !config('oidc.discover');
+        $tokenEndpoint = config('oidc.discover')
+                       ? OidcJwtHelper::defaultIssuer() . '/oidc/token'
+                       : 'https://oidc.local/token';
+
         $this->post('/oidc/login');
         $state = session()->get('oidc_state');
-        $this->mockHttpClient([$this->getMockAuthorizationResponse($claimOverrides)]);
 
-        return $this->get('/oidc/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=' . $state);
+        $providerResponses = [$this->getMockAuthorizationResponse($claimsInIdToken ? $claimOverrides : [])];
+        if (!$claimsInIdToken) {
+            $providerResponses[] = new Response(200, [
+                'Content-Type'  => 'application/json',
+                'Cache-Control' => 'no-cache, no-store',
+                'Pragma'        => 'no-cache',
+            ], json_encode($claimOverrides));
+        }
+
+        $transactions = $this->mockHttpClient($providerResponses);
+
+        $response = $this->get('/oidc/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=' . $state);
+
+        if (auth()->check()) {
+            $this->assertEquals($claimsInIdToken ? 1 : 2, $transactions->requestCount());
+            $tokenRequest = $transactions->requestAt(0);
+            $this->assertEquals($tokenEndpoint, (string) $tokenRequest->getUri());
+            $this->assertEquals('POST', $tokenRequest->getMethod());
+            if (!$claimsInIdToken) {
+                $userinfoRequest = $transactions->requestAt(1);
+                $this->assertEquals(OidcJwtHelper::defaultIssuer() . '/oidc/userinfo', (string) $userinfoRequest->getUri());
+                $this->assertEquals('GET', $userinfoRequest->getMethod());
+                $this->assertEquals('Bearer abc123', $userinfoRequest->getHeader('Authorization')[0]);
+            }
+        }
+
+        return $response;
     }
 
     protected function getAutoDiscoveryResponse($responseOverrides = []): Response
@@ -718,6 +751,7 @@ class OidcTest extends TestCase
         ], json_encode(array_merge([
             'token_endpoint'         => OidcJwtHelper::defaultIssuer() . '/oidc/token',
             'authorization_endpoint' => OidcJwtHelper::defaultIssuer() . '/oidc/authorize',
+            'userinfo_endpoint'      => OidcJwtHelper::defaultIssuer() . '/oidc/userinfo',
             'jwks_uri'               => OidcJwtHelper::defaultIssuer() . '/oidc/keys',
             'issuer'                 => OidcJwtHelper::defaultIssuer(),
             'end_session_endpoint'   => OidcJwtHelper::defaultIssuer() . '/oidc/logout',