| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace BookStack\Api; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-06 05:07:08 +08:00
										 |  |  | use BookStack\Auth\Access\LoginService; | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  | use BookStack\Exceptions\ApiAuthException; | 
					
						
							|  |  |  | use Illuminate\Auth\GuardHelpers; | 
					
						
							|  |  |  | use Illuminate\Contracts\Auth\Authenticatable; | 
					
						
							|  |  |  | use Illuminate\Contracts\Auth\Guard; | 
					
						
							| 
									
										
										
										
											2019-12-31 03:51:41 +08:00
										 |  |  | use Illuminate\Support\Carbon; | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  | use Illuminate\Support\Facades\Hash; | 
					
						
							|  |  |  | use Symfony\Component\HttpFoundation\Request; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ApiTokenGuard implements Guard | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     use GuardHelpers; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The request instance. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected $request; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-06 05:07:08 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @var LoginService | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected $loginService; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * The last auth exception thrown in this request. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |      * @var ApiAuthException | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected $lastAuthException; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * ApiTokenGuard constructor. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-08-06 05:07:08 +08:00
										 |  |  |     public function __construct(Request $request, LoginService $loginService) | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $this->request = $request; | 
					
						
							| 
									
										
										
										
											2021-08-06 05:07:08 +08:00
										 |  |  |         $this->loginService = $loginService; | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-10-27 05:04:18 +08:00
										 |  |  |      * {@inheritdoc} | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public function user() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Return the user if we've already retrieved them.
 | 
					
						
							|  |  |  |         // Effectively a request-instance cache for this method.
 | 
					
						
							|  |  |  |         if (!is_null($this->user)) { | 
					
						
							|  |  |  |             return $this->user; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $user = null; | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |         try { | 
					
						
							|  |  |  |             $user = $this->getAuthorisedUserFromRequest(); | 
					
						
							|  |  |  |         } catch (ApiAuthException $exception) { | 
					
						
							|  |  |  |             $this->lastAuthException = $exception; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->user = $user; | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |         return $user; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Determine if current user is authenticated. If not, throw an exception. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @throws ApiAuthException | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							|  |  |  |      * @return \Illuminate\Contracts\Auth\Authenticatable | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public function authenticate() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |         if (!is_null($user = $this->user())) { | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |             return $user; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($this->lastAuthException) { | 
					
						
							|  |  |  |             throw $this->lastAuthException; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         throw new ApiAuthException('Unauthorized'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Check the API token in the request and fetch a valid authorised user. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |      * @throws ApiAuthException | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getAuthorisedUserFromRequest(): Authenticatable | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $authToken = trim($this->request->headers->get('Authorization', '')); | 
					
						
							| 
									
										
										
										
											2019-12-30 23:46:12 +08:00
										 |  |  |         $this->validateTokenHeaderValue($authToken); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         [$id, $secret] = explode(':', str_replace('Token ', '', $authToken)); | 
					
						
							|  |  |  |         $token = ApiToken::query() | 
					
						
							|  |  |  |             ->where('token_id', '=', $id) | 
					
						
							|  |  |  |             ->with(['user'])->first(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->validateToken($token, $secret); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-06 05:07:08 +08:00
										 |  |  |         if ($this->loginService->awaitingEmailConfirmation($token->user)) { | 
					
						
							|  |  |  |             throw new ApiAuthException(trans('errors.email_confirmation_awaiting')); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-30 23:46:12 +08:00
										 |  |  |         return $token->user; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Validate the format of the token header value string. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2019-12-30 23:46:12 +08:00
										 |  |  |      * @throws ApiAuthException | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function validateTokenHeaderValue(string $authToken): void | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |         if (empty($authToken)) { | 
					
						
							|  |  |  |             throw new ApiAuthException(trans('errors.api_no_authorization_found')); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (strpos($authToken, ':') === false || strpos($authToken, 'Token ') !== 0) { | 
					
						
							|  |  |  |             throw new ApiAuthException(trans('errors.api_bad_authorization_format')); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-12-30 23:46:12 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-30 23:46:12 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Validate the given secret against the given token and ensure the token | 
					
						
							|  |  |  |      * currently has access to the instance API. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2019-12-30 23:46:12 +08:00
										 |  |  |      * @throws ApiAuthException | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function validateToken(?ApiToken $token, string $secret): void | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |         if ($token === null) { | 
					
						
							|  |  |  |             throw new ApiAuthException(trans('errors.api_user_token_not_found')); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!Hash::check($secret, $token->secret)) { | 
					
						
							|  |  |  |             throw new ApiAuthException(trans('errors.api_incorrect_token_secret')); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-31 03:51:41 +08:00
										 |  |  |         $now = Carbon::now(); | 
					
						
							|  |  |  |         if ($token->expires_at <= $now) { | 
					
						
							|  |  |  |             throw new ApiAuthException(trans('errors.api_user_token_expired'), 403); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |         if (!$token->user->can('access-api')) { | 
					
						
							|  |  |  |             throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-10-27 05:04:18 +08:00
										 |  |  |      * {@inheritdoc} | 
					
						
							| 
									
										
										
										
											2019-12-30 22:51:28 +08:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public function validate(array $credentials = []) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (empty($credentials['id']) || empty($credentials['secret'])) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $token = ApiToken::query() | 
					
						
							|  |  |  |             ->where('token_id', '=', $credentials['id']) | 
					
						
							|  |  |  |             ->with(['user'])->first(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($token === null) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Hash::check($credentials['secret'], $token->secret); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-31 03:42:46 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * "Log out" the currently authenticated user. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function logout() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->user = null; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-03-08 06:24:05 +08:00
										 |  |  | } |