2019-12-30 22:51:28 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace BookStack\Api;
|
|
|
|
|
2023-05-18 00:56:55 +08:00
|
|
|
use BookStack\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
|
|
|
}
|