]> BookStack Code Mirror - bookstack/blob - app/Api/ApiTokenGuard.php
b347e536acb67e230e803065e271daaafb5a209a
[bookstack] / app / Api / ApiTokenGuard.php
1 <?php
2
3 namespace BookStack\Api;
4
5 use BookStack\Exceptions\ApiAuthException;
6 use Illuminate\Auth\GuardHelpers;
7 use Illuminate\Contracts\Auth\Authenticatable;
8 use Illuminate\Contracts\Auth\Guard;
9 use Illuminate\Support\Facades\Hash;
10 use Symfony\Component\HttpFoundation\Request;
11
12 class ApiTokenGuard implements Guard
13 {
14
15     use GuardHelpers;
16
17     /**
18      * The request instance.
19      */
20     protected $request;
21
22
23     /**
24      * The last auth exception thrown in this request.
25      * @var ApiAuthException
26      */
27     protected $lastAuthException;
28
29     /**
30      * ApiTokenGuard constructor.
31      */
32     public function __construct(Request $request)
33     {
34         $this->request = $request;
35     }
36
37
38     /**
39      * @inheritDoc
40      */
41     public function user()
42     {
43         // Return the user if we've already retrieved them.
44         // Effectively a request-instance cache for this method.
45         if (!is_null($this->user)) {
46             return $this->user;
47         }
48
49         $user = null;
50         try {
51             $user = $this->getAuthorisedUserFromRequest();
52         } catch (ApiAuthException $exception) {
53             $this->lastAuthException = $exception;
54         }
55
56         $this->user = $user;
57         return $user;
58     }
59
60     /**
61      * Determine if current user is authenticated. If not, throw an exception.
62      *
63      * @return \Illuminate\Contracts\Auth\Authenticatable
64      *
65      * @throws ApiAuthException
66      */
67     public function authenticate()
68     {
69         if (! is_null($user = $this->user())) {
70             return $user;
71         }
72
73         if ($this->lastAuthException) {
74             throw $this->lastAuthException;
75         }
76
77         throw new ApiAuthException('Unauthorized');
78     }
79
80     /**
81      * Check the API token in the request and fetch a valid authorised user.
82      * @throws ApiAuthException
83      */
84     protected function getAuthorisedUserFromRequest(): Authenticatable
85     {
86         $authToken = trim($this->request->headers->get('Authorization', ''));
87         if (empty($authToken)) {
88             throw new ApiAuthException(trans('errors.api_no_authorization_found'));
89         }
90
91         if (strpos($authToken, ':') === false || strpos($authToken, 'Token ') !== 0) {
92             throw new ApiAuthException(trans('errors.api_bad_authorization_format'));
93         }
94
95         [$id, $secret] = explode(':', str_replace('Token ', '', $authToken));
96         $token = ApiToken::query()
97             ->where('token_id', '=', $id)
98             ->with(['user'])->first();
99
100         if ($token === null) {
101             throw new ApiAuthException(trans('errors.api_user_token_not_found'));
102         }
103
104         if (!Hash::check($secret, $token->secret)) {
105             throw new ApiAuthException(trans('errors.api_incorrect_token_secret'));
106         }
107
108         if (!$token->user->can('access-api')) {
109             throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
110         }
111
112         return $token->user;
113     }
114
115     /**
116      * @inheritDoc
117      */
118     public function validate(array $credentials = [])
119     {
120         if (empty($credentials['id']) || empty($credentials['secret'])) {
121             return false;
122         }
123
124         $token = ApiToken::query()
125             ->where('token_id', '=', $credentials['id'])
126             ->with(['user'])->first();
127
128         if ($token === null) {
129             return false;
130         }
131
132         return Hash::check($credentials['secret'], $token->secret);
133     }
134
135 }