2016-12-15 10:13:00 +01:00
|
|
|
<?php
|
|
|
|
namespace Shaarli\Api;
|
|
|
|
|
2020-09-26 14:18:01 +02:00
|
|
|
use malkusch\lock\mutex\FlockMutex;
|
2016-12-15 10:13:00 +01:00
|
|
|
use Shaarli\Api\Exceptions\ApiAuthorizationException;
|
2019-01-12 23:55:38 +01:00
|
|
|
use Shaarli\Api\Exceptions\ApiException;
|
2020-09-30 15:57:57 +02:00
|
|
|
use Shaarli\Bookmark\BookmarkFileService;
|
2017-05-07 16:50:20 +02:00
|
|
|
use Shaarli\Config\ConfigManager;
|
2016-12-15 10:13:00 +01:00
|
|
|
use Slim\Container;
|
|
|
|
use Slim\Http\Request;
|
|
|
|
use Slim\Http\Response;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class ApiMiddleware
|
|
|
|
*
|
|
|
|
* This will be called before accessing any API Controller.
|
|
|
|
* Its role is to make sure that the API is enabled, configured, and to validate the JWT token.
|
|
|
|
*
|
|
|
|
* If the request is validated, the controller is called, otherwise a JSON error response is returned.
|
|
|
|
*
|
|
|
|
* @package Api
|
|
|
|
*/
|
|
|
|
class ApiMiddleware
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var int JWT token validity in seconds (9 min).
|
|
|
|
*/
|
|
|
|
public static $TOKEN_DURATION = 540;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Container: contains conf, plugins, etc.
|
|
|
|
*/
|
|
|
|
protected $container;
|
|
|
|
|
|
|
|
/**
|
2017-05-07 16:50:20 +02:00
|
|
|
* @var ConfigManager instance.
|
2016-12-15 10:13:00 +01:00
|
|
|
*/
|
|
|
|
protected $conf;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ApiMiddleware constructor.
|
|
|
|
*
|
|
|
|
* @param Container $container instance.
|
|
|
|
*/
|
|
|
|
public function __construct($container)
|
|
|
|
{
|
|
|
|
$this->container = $container;
|
|
|
|
$this->conf = $this->container->get('conf');
|
|
|
|
$this->setLinkDb($this->conf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Middleware execution:
|
|
|
|
* - check the API request
|
|
|
|
* - execute the controller
|
|
|
|
* - return the response
|
|
|
|
*
|
|
|
|
* @param Request $request Slim request
|
|
|
|
* @param Response $response Slim response
|
|
|
|
* @param callable $next Next action
|
|
|
|
*
|
|
|
|
* @return Response response.
|
|
|
|
*/
|
|
|
|
public function __invoke($request, $response, $next)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$this->checkRequest($request);
|
|
|
|
$response = $next($request, $response);
|
2018-10-13 00:19:03 +02:00
|
|
|
} catch (ApiException $e) {
|
2016-12-15 10:13:00 +01:00
|
|
|
$e->setResponse($response);
|
|
|
|
$e->setDebug($this->conf->get('dev.debug', false));
|
|
|
|
$response = $e->getApiResponse();
|
|
|
|
}
|
|
|
|
|
2020-09-30 15:57:57 +02:00
|
|
|
return $response
|
|
|
|
->withHeader('Access-Control-Allow-Origin', '*')
|
|
|
|
->withHeader(
|
|
|
|
'Access-Control-Allow-Headers',
|
|
|
|
'X-Requested-With, Content-Type, Accept, Origin, Authorization'
|
|
|
|
)
|
|
|
|
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
|
|
|
;
|
2016-12-15 10:13:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check the request validity (HTTP method, request value, etc.),
|
|
|
|
* that the API is enabled, and the JWT token validity.
|
|
|
|
*
|
|
|
|
* @param Request $request Slim request
|
|
|
|
*
|
|
|
|
* @throws ApiAuthorizationException The API is disabled or the token is invalid.
|
|
|
|
*/
|
|
|
|
protected function checkRequest($request)
|
|
|
|
{
|
|
|
|
if (! $this->conf->get('api.enabled', true)) {
|
|
|
|
throw new ApiAuthorizationException('API is disabled');
|
|
|
|
}
|
|
|
|
$this->checkToken($request);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that the JWT token is set and valid.
|
|
|
|
* The API secret setting must be set.
|
|
|
|
*
|
|
|
|
* @param Request $request Slim request
|
|
|
|
*
|
|
|
|
* @throws ApiAuthorizationException The token couldn't be validated.
|
|
|
|
*/
|
2018-10-13 00:19:03 +02:00
|
|
|
protected function checkToken($request)
|
|
|
|
{
|
2020-09-30 15:57:57 +02:00
|
|
|
if (!$request->hasHeader('Authorization')
|
|
|
|
&& !isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION'])
|
|
|
|
) {
|
2016-12-15 10:13:00 +01:00
|
|
|
throw new ApiAuthorizationException('JWT token not provided');
|
|
|
|
}
|
2020-09-30 12:29:54 +02:00
|
|
|
|
2016-12-15 10:13:00 +01:00
|
|
|
if (empty($this->conf->get('api.secret'))) {
|
|
|
|
throw new ApiAuthorizationException('Token secret must be set in Shaarli\'s administration');
|
|
|
|
}
|
|
|
|
|
2020-09-30 12:29:54 +02:00
|
|
|
if (isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION'])) {
|
|
|
|
$authorization = $this->container->environment['REDIRECT_HTTP_AUTHORIZATION'];
|
|
|
|
} else {
|
2020-09-29 12:15:04 +02:00
|
|
|
$authorization = $request->getHeaderLine('Authorization');
|
2020-09-30 12:29:54 +02:00
|
|
|
}
|
2017-01-07 22:23:47 +01:00
|
|
|
|
|
|
|
if (! preg_match('/^Bearer (.*)/i', $authorization, $matches)) {
|
|
|
|
throw new ApiAuthorizationException('Invalid JWT header');
|
|
|
|
}
|
|
|
|
|
|
|
|
ApiUtils::validateJwtToken($matches[1], $this->conf->get('api.secret'));
|
2016-12-15 10:13:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-30 15:57:57 +02:00
|
|
|
* Instantiate a new LinkDB including private bookmarks,
|
2016-12-15 10:13:00 +01:00
|
|
|
* and load in the Slim container.
|
|
|
|
*
|
|
|
|
* FIXME! LinkDB could use a refactoring to avoid this trick.
|
|
|
|
*
|
2017-05-07 16:50:20 +02:00
|
|
|
* @param ConfigManager $conf instance.
|
2016-12-15 10:13:00 +01:00
|
|
|
*/
|
|
|
|
protected function setLinkDb($conf)
|
|
|
|
{
|
2020-09-30 15:57:57 +02:00
|
|
|
$linkDb = new BookmarkFileService(
|
|
|
|
$conf,
|
|
|
|
$this->container->get('history'),
|
2020-09-26 14:18:01 +02:00
|
|
|
new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2),
|
2020-09-30 15:57:57 +02:00
|
|
|
true
|
2016-12-15 10:13:00 +01:00
|
|
|
);
|
|
|
|
$this->container['db'] = $linkDb;
|
|
|
|
}
|
|
|
|
}
|