| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  | <?php | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  | namespace BookStack\Auth\Access\Oidc; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 23:04:09 +08:00
										 |  |  | use function auth; | 
					
						
							| 
									
										
										
										
											2021-10-12 02:05:16 +08:00
										 |  |  | use BookStack\Auth\Access\LoginService; | 
					
						
							|  |  |  | use BookStack\Auth\Access\RegistrationService; | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | use BookStack\Auth\User; | 
					
						
							|  |  |  | use BookStack\Exceptions\JsonDebugException; | 
					
						
							|  |  |  | use BookStack\Exceptions\StoppedAuthenticationException; | 
					
						
							|  |  |  | use BookStack\Exceptions\UserRegistrationException; | 
					
						
							| 
									
										
										
										
											2022-02-24 23:04:09 +08:00
										 |  |  | use function config; | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  | use Illuminate\Support\Facades\Cache; | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  | use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider; | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  | use League\OAuth2\Client\Provider\Exception\IdentityProviderException; | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  | use Psr\Http\Client\ClientInterface as HttpClient; | 
					
						
							| 
									
										
										
										
											2021-10-12 02:05:16 +08:00
										 |  |  | use function trans; | 
					
						
							|  |  |  | use function url; | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Class OpenIdConnectService | 
					
						
							|  |  |  |  * Handles any app-specific OIDC tasks. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  | class OidcService | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |     protected RegistrationService $registrationService; | 
					
						
							|  |  |  |     protected LoginService $loginService; | 
					
						
							|  |  |  |     protected HttpClient $httpClient; | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * OpenIdService constructor. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |     public function __construct(RegistrationService $registrationService, LoginService $loginService, HttpClient $httpClient) | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $this->registrationService = $registrationService; | 
					
						
							|  |  |  |         $this->loginService = $loginService; | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         $this->httpClient = $httpClient; | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Initiate an authorization flow. | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |      * @throws OidcException | 
					
						
							| 
									
										
										
										
											2022-02-24 23:04:09 +08:00
										 |  |  |      * | 
					
						
							|  |  |  |      * @return array{url: string, state: string} | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public function login(): array | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |         $settings = $this->getProviderSettings(); | 
					
						
							|  |  |  |         $provider = $this->getProvider($settings); | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         return [ | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |             'url'   => $provider->getAuthorizationUrl(), | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |             'state' => $provider->getState(), | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Process the Authorization response from the authorization server and | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |      * return the matching, or new if registration active, user matched to the | 
					
						
							|  |  |  |      * authorization server. Throws if the user cannot be auth if not authenticated. | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |      * @throws JsonDebugException | 
					
						
							|  |  |  |      * @throws OidcException | 
					
						
							|  |  |  |      * @throws StoppedAuthenticationException | 
					
						
							|  |  |  |      * @throws IdentityProviderException | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |     public function processAuthorizeResponse(?string $authorizationCode): User | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |         $settings = $this->getProviderSettings(); | 
					
						
							|  |  |  |         $provider = $this->getProvider($settings); | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Try to exchange authorization code for access token
 | 
					
						
							|  |  |  |         $accessToken = $provider->getAccessToken('authorization_code', [ | 
					
						
							|  |  |  |             'code' => $authorizationCode, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |         return $this->processAccessTokenCallback($accessToken, $settings); | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |      * @throws OidcException | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |     protected function getProviderSettings(): OidcProviderSettings | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         $config = $this->config(); | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |         $settings = new OidcProviderSettings([ | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |             'issuer'                => $config['issuer'], | 
					
						
							|  |  |  |             'clientId'              => $config['client_id'], | 
					
						
							|  |  |  |             'clientSecret'          => $config['client_secret'], | 
					
						
							|  |  |  |             'redirectUri'           => url('/oidc/callback'), | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |             'authorizationEndpoint' => $config['authorization_endpoint'], | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |             'tokenEndpoint'         => $config['token_endpoint'], | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Use keys if configured
 | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         if (!empty($config['jwt_public_key'])) { | 
					
						
							|  |  |  |             $settings->keys = [$config['jwt_public_key']]; | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Run discovery
 | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         if ($config['discover'] ?? false) { | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |             try { | 
					
						
							|  |  |  |                 $settings->discoverFromIssuer($this->httpClient, Cache::store(null), 15); | 
					
						
							|  |  |  |             } catch (OidcIssuerDiscoveryException $exception) { | 
					
						
							|  |  |  |                 throw new OidcException('OIDC Discovery Error: ' . $exception->getMessage()); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |         $settings->validate(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $settings; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Load the underlying OpenID Connect Provider. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |     protected function getProvider(OidcProviderSettings $settings): OidcOAuthProvider | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         return new OidcOAuthProvider($settings->arrayForProvider(), [ | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |             'httpClient'     => $this->httpClient, | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |             'optionProvider' => new HttpBasicAuthOptionProvider(), | 
					
						
							|  |  |  |         ]); | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |      * Calculate the display name. | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |     protected function getUserDisplayName(OidcIdToken $token, string $defaultValue): string | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         $displayNameAttr = $this->config()['display_name_claims']; | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $displayName = []; | 
					
						
							|  |  |  |         foreach ($displayNameAttr as $dnAttr) { | 
					
						
							| 
									
										
										
										
											2021-10-12 06:00:45 +08:00
										 |  |  |             $dnComponent = $token->getClaim($dnAttr) ?? ''; | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |             if ($dnComponent !== '') { | 
					
						
							|  |  |  |                 $displayName[] = $dnComponent; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (count($displayName) == 0) { | 
					
						
							|  |  |  |             $displayName[] = $defaultValue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return implode(' ', $displayName); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Extract the details of a user from an ID token. | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |      * @return array{name: string, email: string, external_id: string} | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |     protected function getUserDetails(OidcIdToken $token): array | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-12 06:00:45 +08:00
										 |  |  |         $id = $token->getClaim('sub'); | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         return [ | 
					
						
							|  |  |  |             'external_id' => $id, | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |             'email'       => $token->getClaim('email'), | 
					
						
							|  |  |  |             'name'        => $this->getUserDisplayName($token, $id), | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         ]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Processes a received access token for a user. Login the user when | 
					
						
							|  |  |  |      * they exist, optionally registering them automatically. | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |      * @throws OidcException | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |      * @throws JsonDebugException | 
					
						
							|  |  |  |      * @throws StoppedAuthenticationException | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |     protected function processAccessTokenCallback(OidcAccessToken $accessToken, OidcProviderSettings $settings): User | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-12 02:05:16 +08:00
										 |  |  |         $idTokenText = $accessToken->getIdToken(); | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |         $idToken = new OidcIdToken( | 
					
						
							| 
									
										
										
										
											2021-10-12 02:05:16 +08:00
										 |  |  |             $idTokenText, | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |             $settings->issuer, | 
					
						
							|  |  |  |             $settings->keys, | 
					
						
							| 
									
										
										
										
											2021-10-12 02:05:16 +08:00
										 |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         if ($this->config()['dump_user_details']) { | 
					
						
							| 
									
										
										
										
											2021-10-12 23:48:54 +08:00
										 |  |  |             throw new JsonDebugException($idToken->getAllClaims()); | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-12 06:00:45 +08:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2021-10-13 06:00:52 +08:00
										 |  |  |             $idToken->validate($settings->clientId); | 
					
						
							| 
									
										
										
										
											2021-10-13 06:04:28 +08:00
										 |  |  |         } catch (OidcInvalidTokenException $exception) { | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |             throw new OidcException("ID token validate failed with error: {$exception->getMessage()}"); | 
					
						
							| 
									
										
										
										
											2021-10-12 06:00:45 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $userDetails = $this->getUserDetails($idToken); | 
					
						
							|  |  |  |         $isLoggedIn = auth()->check(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  |         if (empty($userDetails['email'])) { | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |             throw new OidcException(trans('errors.oidc_no_email_address')); | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($isLoggedIn) { | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |             throw new OidcException(trans('errors.oidc_already_logged_in')); | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 22:16:09 +08:00
										 |  |  |         try { | 
					
						
							|  |  |  |             $user = $this->registrationService->findOrRegister( | 
					
						
							|  |  |  |                 $userDetails['name'], | 
					
						
							|  |  |  |                 $userDetails['email'], | 
					
						
							|  |  |  |                 $userDetails['external_id'] | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |         } catch (UserRegistrationException $exception) { | 
					
						
							|  |  |  |             throw new OidcException($exception->getMessage()); | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->loginService->login($user, 'oidc'); | 
					
						
							| 
									
										
										
										
											2021-10-16 23:01:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  |         return $user; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-13 23:51:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the OIDC config from the application. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function config(): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return config('oidc'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-07 06:05:26 +08:00
										 |  |  | } |