166 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
<?php
 | 
						|
 | 
						|
namespace BookStack\Api;
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The request instance.
 | 
						|
     */
 | 
						|
    protected $request;
 | 
						|
 | 
						|
 | 
						|
    /**
 | 
						|
     * The last auth exception thrown in this request.
 | 
						|
     * @var ApiAuthException
 | 
						|
     */
 | 
						|
    protected $lastAuthException;
 | 
						|
 | 
						|
    /**
 | 
						|
     * ApiTokenGuard constructor.
 | 
						|
     */
 | 
						|
    public function __construct(Request $request)
 | 
						|
    {
 | 
						|
        $this->request = $request;
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * @inheritDoc
 | 
						|
     */
 | 
						|
    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;
 | 
						|
        try {
 | 
						|
            $user = $this->getAuthorisedUserFromRequest();
 | 
						|
        } catch (ApiAuthException $exception) {
 | 
						|
            $this->lastAuthException = $exception;
 | 
						|
        }
 | 
						|
 | 
						|
        $this->user = $user;
 | 
						|
        return $user;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Determine if current user is authenticated. If not, throw an exception.
 | 
						|
     *
 | 
						|
     * @return \Illuminate\Contracts\Auth\Authenticatable
 | 
						|
     *
 | 
						|
     * @throws ApiAuthException
 | 
						|
     */
 | 
						|
    public function authenticate()
 | 
						|
    {
 | 
						|
        if (! is_null($user = $this->user())) {
 | 
						|
            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.
 | 
						|
     * @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);
 | 
						|
 | 
						|
        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'));
 | 
						|
        }
 | 
						|
 | 
						|
        if (strpos($authToken, ':') === false || strpos($authToken, 'Token ') !== 0) {
 | 
						|
            throw new ApiAuthException(trans('errors.api_bad_authorization_format'));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 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'));
 | 
						|
        }
 | 
						|
 | 
						|
        if (!Hash::check($secret, $token->secret)) {
 | 
						|
            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);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @inheritDoc
 | 
						|
     */
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * "Log out" the currently authenticated user.
 | 
						|
     */
 | 
						|
    public function logout()
 | 
						|
    {
 | 
						|
        $this->user = null;
 | 
						|
    }
 | 
						|
} |