ApiMiddleware.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. <?php
  2. namespace Shaarli\Api;
  3. use Shaarli\Api\Exceptions\ApiException;
  4. use Shaarli\Api\Exceptions\ApiAuthorizationException;
  5. use Shaarli\Config\ConfigManager;
  6. use Slim\Container;
  7. use Slim\Http\Request;
  8. use Slim\Http\Response;
  9. /**
  10. * Class ApiMiddleware
  11. *
  12. * This will be called before accessing any API Controller.
  13. * Its role is to make sure that the API is enabled, configured, and to validate the JWT token.
  14. *
  15. * If the request is validated, the controller is called, otherwise a JSON error response is returned.
  16. *
  17. * @package Api
  18. */
  19. class ApiMiddleware
  20. {
  21. /**
  22. * @var int JWT token validity in seconds (9 min).
  23. */
  24. public static $TOKEN_DURATION = 540;
  25. /**
  26. * @var Container: contains conf, plugins, etc.
  27. */
  28. protected $container;
  29. /**
  30. * @var ConfigManager instance.
  31. */
  32. protected $conf;
  33. /**
  34. * ApiMiddleware constructor.
  35. *
  36. * @param Container $container instance.
  37. */
  38. public function __construct($container)
  39. {
  40. $this->container = $container;
  41. $this->conf = $this->container->get('conf');
  42. $this->setLinkDb($this->conf);
  43. }
  44. /**
  45. * Middleware execution:
  46. * - check the API request
  47. * - execute the controller
  48. * - return the response
  49. *
  50. * @param Request $request Slim request
  51. * @param Response $response Slim response
  52. * @param callable $next Next action
  53. *
  54. * @return Response response.
  55. */
  56. public function __invoke($request, $response, $next)
  57. {
  58. try {
  59. $this->checkRequest($request);
  60. $response = $next($request, $response);
  61. } catch (ApiException $e) {
  62. $e->setResponse($response);
  63. $e->setDebug($this->conf->get('dev.debug', false));
  64. $response = $e->getApiResponse();
  65. }
  66. return $response;
  67. }
  68. /**
  69. * Check the request validity (HTTP method, request value, etc.),
  70. * that the API is enabled, and the JWT token validity.
  71. *
  72. * @param Request $request Slim request
  73. *
  74. * @throws ApiAuthorizationException The API is disabled or the token is invalid.
  75. */
  76. protected function checkRequest($request)
  77. {
  78. if (! $this->conf->get('api.enabled', true)) {
  79. throw new ApiAuthorizationException('API is disabled');
  80. }
  81. $this->checkToken($request);
  82. }
  83. /**
  84. * Check that the JWT token is set and valid.
  85. * The API secret setting must be set.
  86. *
  87. * @param Request $request Slim request
  88. *
  89. * @throws ApiAuthorizationException The token couldn't be validated.
  90. */
  91. protected function checkToken($request)
  92. {
  93. if (! $request->hasHeader('Authorization')) {
  94. throw new ApiAuthorizationException('JWT token not provided');
  95. }
  96. if (empty($this->conf->get('api.secret'))) {
  97. throw new ApiAuthorizationException('Token secret must be set in Shaarli\'s administration');
  98. }
  99. $authorization = $request->getHeaderLine('Authorization');
  100. if (! preg_match('/^Bearer (.*)/i', $authorization, $matches)) {
  101. throw new ApiAuthorizationException('Invalid JWT header');
  102. }
  103. ApiUtils::validateJwtToken($matches[1], $this->conf->get('api.secret'));
  104. }
  105. /**
  106. * Instantiate a new LinkDB including private links,
  107. * and load in the Slim container.
  108. *
  109. * FIXME! LinkDB could use a refactoring to avoid this trick.
  110. *
  111. * @param ConfigManager $conf instance.
  112. */
  113. protected function setLinkDb($conf)
  114. {
  115. $linkDb = new \LinkDB(
  116. $conf->get('resource.datastore'),
  117. true,
  118. $conf->get('privacy.hide_public_links'),
  119. $conf->get('redirector.url'),
  120. $conf->get('redirector.encode_url')
  121. );
  122. $this->container['db'] = $linkDb;
  123. }
  124. }