]> BookStack Code Mirror - bookstack/blobdiff - app/Api/ApiTokenGuard.php
Added 'Sort Book' action to chapters
[bookstack] / app / Api / ApiTokenGuard.php
index b347e536acb67e230e803065e271daaafb5a209a..1bb672556dfa2dec6d008a2078247cd34a7a1d7e 100644 (file)
@@ -2,16 +2,17 @@
 
 namespace BookStack\Api;
 
+use BookStack\Auth\Access\LoginService;
 use BookStack\Exceptions\ApiAuthException;
 use Illuminate\Auth\GuardHelpers;
 use Illuminate\Contracts\Auth\Authenticatable;
 use Illuminate\Contracts\Auth\Guard;
+use Illuminate\Support\Carbon;
 use Illuminate\Support\Facades\Hash;
 use Symfony\Component\HttpFoundation\Request;
 
 class ApiTokenGuard implements Guard
 {
-
     use GuardHelpers;
 
     /**
@@ -19,9 +20,14 @@ class ApiTokenGuard implements Guard
      */
     protected $request;
 
+    /**
+     * @var LoginService
+     */
+    protected $loginService;
 
     /**
      * The last auth exception thrown in this request.
+     *
      * @var ApiAuthException
      */
     protected $lastAuthException;
@@ -29,14 +35,14 @@ class ApiTokenGuard implements Guard
     /**
      * ApiTokenGuard constructor.
      */
-    public function __construct(Request $request)
+    public function __construct(Request $request, LoginService $loginService)
     {
         $this->request = $request;
+        $this->loginService = $loginService;
     }
 
-
     /**
-     * @inheritDoc
+     * {@inheritdoc}
      */
     public function user()
     {
@@ -47,6 +53,7 @@ class ApiTokenGuard implements Guard
         }
 
         $user = null;
+
         try {
             $user = $this->getAuthorisedUserFromRequest();
         } catch (ApiAuthException $exception) {
@@ -54,19 +61,20 @@ class ApiTokenGuard implements Guard
         }
 
         $this->user = $user;
+
         return $user;
     }
 
     /**
      * Determine if current user is authenticated. If not, throw an exception.
      *
-     * @return \Illuminate\Contracts\Auth\Authenticatable
-     *
      * @throws ApiAuthException
+     *
+     * @return \Illuminate\Contracts\Auth\Authenticatable
      */
     public function authenticate()
     {
-        if (! is_null($user = $this->user())) {
+        if (!is_null($user = $this->user())) {
             return $user;
         }
 
@@ -79,11 +87,35 @@ class ApiTokenGuard implements Guard
 
     /**
      * Check the API token in the request and fetch a valid authorised user.
+     *
      * @throws ApiAuthException
      */
     protected function getAuthorisedUserFromRequest(): Authenticatable
     {
         $authToken = trim($this->request->headers->get('Authorization', ''));
+        $this->validateTokenHeaderValue($authToken);
+
+        [$id, $secret] = explode(':', str_replace('Token ', '', $authToken));
+        $token = ApiToken::query()
+            ->where('token_id', '=', $id)
+            ->with(['user'])->first();
+
+        $this->validateToken($token, $secret);
+
+        if ($this->loginService->awaitingEmailConfirmation($token->user)) {
+            throw new ApiAuthException(trans('errors.email_confirmation_awaiting'));
+        }
+
+        return $token->user;
+    }
+
+    /**
+     * Validate the format of the token header value string.
+     *
+     * @throws ApiAuthException
+     */
+    protected function validateTokenHeaderValue(string $authToken): void
+    {
         if (empty($authToken)) {
             throw new ApiAuthException(trans('errors.api_no_authorization_found'));
         }
@@ -91,12 +123,16 @@ class ApiTokenGuard implements Guard
         if (strpos($authToken, ':') === false || strpos($authToken, 'Token ') !== 0) {
             throw new ApiAuthException(trans('errors.api_bad_authorization_format'));
         }
+    }
 
-        [$id, $secret] = explode(':', str_replace('Token ', '', $authToken));
-        $token = ApiToken::query()
-            ->where('token_id', '=', $id)
-            ->with(['user'])->first();
-
+    /**
+     * Validate the given secret against the given token and ensure the token
+     * currently has access to the instance API.
+     *
+     * @throws ApiAuthException
+     */
+    protected function validateToken(?ApiToken $token, string $secret): void
+    {
         if ($token === null) {
             throw new ApiAuthException(trans('errors.api_user_token_not_found'));
         }
@@ -105,15 +141,18 @@ class ApiTokenGuard implements Guard
             throw new ApiAuthException(trans('errors.api_incorrect_token_secret'));
         }
 
+        $now = Carbon::now();
+        if ($token->expires_at <= $now) {
+            throw new ApiAuthException(trans('errors.api_user_token_expired'), 403);
+        }
+
         if (!$token->user->can('access-api')) {
             throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
         }
-
-        return $token->user;
     }
 
     /**
-     * @inheritDoc
+     * {@inheritdoc}
      */
     public function validate(array $credentials = [])
     {
@@ -132,4 +171,11 @@ class ApiTokenGuard implements Guard
         return Hash::check($credentials['secret'], $token->secret);
     }
 
-}
\ No newline at end of file
+    /**
+     * "Log out" the currently authenticated user.
+     */
+    public function logout()
+    {
+        $this->user = null;
+    }
+}