]> BookStack Code Mirror - bookstack/blobdiff - app/Auth/Access/OpenIdService.php
Default OpenID display name set to standard value
[bookstack] / app / Auth / Access / OpenIdService.php
index 870299a57b0a5a0549fad973d182e552a2a08159..70df963f523eecbe22bf860e9c13637dce7eacd4 100644 (file)
@@ -5,8 +5,8 @@ use BookStack\Exceptions\JsonDebugException;
 use BookStack\Exceptions\OpenIdException;
 use BookStack\Exceptions\UserRegistrationException;
 use Exception;
-use Illuminate\Support\Str;
 use Lcobucci\JWT\Token;
+use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
 use OpenIDConnectClient\AccessToken;
 use OpenIDConnectClient\OpenIDConnectProvider;
 
@@ -17,17 +17,15 @@ use OpenIDConnectClient\OpenIDConnectProvider;
 class OpenIdService extends ExternalAuthService
 {
     protected $config;
-    protected $registrationService;
-    protected $user;
 
     /**
      * OpenIdService constructor.
      */
     public function __construct(RegistrationService $registrationService, User $user)
     {
+        parent::__construct($registrationService, $user);
+        
         $this->config = config('openid');
-        $this->registrationService = $registrationService;
-        $this->user = $user;
     }
 
     /**
@@ -56,6 +54,82 @@ class OpenIdService extends ExternalAuthService
         return ['url' => $url, 'id' => $id];
     }
 
+    /**
+     * Refresh the currently logged in user.
+     * @throws Error
+     */
+    public function refresh(): bool
+    {
+        // Retrieve access token for current session
+        $json = session()->get('openid_token');
+
+        // If no access token was found, reject the refresh
+        if (!$json) {
+            $this->actionLogout();
+            return false;
+        }
+
+        $accessToken = new AccessToken(json_decode($json, true) ?? []);
+
+        // If the token is not expired, refreshing isn't necessary
+        if ($this->isUnexpired($accessToken)) {
+            return true;
+        }
+
+        // Try to obtain refreshed access token
+        try {
+            $newAccessToken = $this->refreshAccessToken($accessToken);
+        } catch (\Exception $e) {
+            // Log out if an unknown problem arises
+            $this->actionLogout();
+            throw $e;
+        }
+
+        // If a token was obtained, update the access token, otherwise log out
+        if ($newAccessToken !== null) {
+            session()->put('openid_token', json_encode($newAccessToken));
+            return true;
+        } else {
+            $this->actionLogout();
+            return false;
+        }
+    }
+
+    /**
+     * Check whether an access token or OpenID token isn't expired.
+     */
+    protected function isUnexpired(AccessToken $accessToken): bool
+    {
+        $idToken = $accessToken->getIdToken();
+        
+        $accessTokenUnexpired = $accessToken->getExpires() && !$accessToken->hasExpired();
+        $idTokenUnexpired = !$idToken || !$idToken->isExpired(); 
+
+        return $accessTokenUnexpired && $idTokenUnexpired;
+    }
+
+    /**
+     * Generate an updated access token, through the associated refresh token.
+     * @throws Error
+     */
+    protected function refreshAccessToken(AccessToken $accessToken): ?AccessToken
+    {
+        // If no refresh token available, abort
+        if ($accessToken->getRefreshToken() === null) {
+            return null;
+        }
+
+        // ID token or access token is expired, we refresh it using the refresh token
+        try {
+            return $this->getProvider()->getAccessToken('refresh_token', [
+                'refresh_token' => $accessToken->getRefreshToken(),
+            ]);
+        } catch (IdentityProviderException $e) {
+            // Refreshing failed
+            return null;
+        }
+    }
+
     /**
      * Process the Authorization response from the authorization server and
      * return the matching, or new if registration active, user matched to
@@ -89,12 +163,13 @@ class OpenIdService extends ExternalAuthService
     }
 
     /**
-     * Load the underlying Onelogin SAML2 toolkit.
+     * Load the underlying OpenID Connect Provider.
      * @throws Error
      * @throws Exception
      */
     protected function getProvider(): OpenIDConnectProvider
     {
+        // Setup settings
         $settings = $this->config['openid'];
         $overrides = $this->config['openid_overrides'] ?? [];
 
@@ -105,12 +180,27 @@ class OpenIdService extends ExternalAuthService
         $openIdSettings = $this->loadOpenIdDetails();
         $settings = array_replace_recursive($settings, $openIdSettings, $overrides);
 
-        $signer = new \Lcobucci\JWT\Signer\Rsa\Sha256();
-        return new OpenIDConnectProvider($settings, ['signer' => $signer]);
+        // Setup services
+        $services = $this->loadOpenIdServices();
+        $overrides = $this->config['openid_services'] ?? [];
+
+        $services = array_replace_recursive($services, $overrides);
+
+        return new OpenIDConnectProvider($settings, $services);
     }
 
     /**
-     * Load dynamic service provider options required by the onelogin toolkit.
+     * 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
     {
@@ -158,7 +248,7 @@ class OpenIdService extends ExternalAuthService
     }
 
     /**
-     * Extract the details of a user from a SAML response.
+     * Extract the details of a user from an ID token.
      */
     protected function getUserDetails(Token $token): array
     {
@@ -175,31 +265,6 @@ class OpenIdService extends ExternalAuthService
         ];
     }
 
-    /**
-     * Get the user from the database for the specified details.
-     * @throws OpenIdException
-     * @throws UserRegistrationException
-     */
-    protected function getOrRegisterUser(array $userDetails): ?User
-    {
-        $user = $this->user->newQuery()
-          ->where('external_auth_id', '=', $userDetails['external_id'])
-          ->first();
-
-        if (is_null($user)) {
-            $userData = [
-                'name' => $userDetails['name'],
-                'email' => $userDetails['email'],
-                'password' => Str::random(32),
-                'external_auth_id' => $userDetails['external_id'],
-            ];
-
-            $user = $this->registrationService->registerUser($userData, null, false);
-        }
-
-        return $user;
-    }
-
     /**
      * Processes a received access token for a user. Login the user when
      * they exist, optionally registering them automatically.
@@ -230,6 +295,7 @@ class OpenIdService extends ExternalAuthService
         }
 
         auth()->login($user);
+        session()->put('openid_token', json_encode($accessToken));
         return $user;
     }
 }