]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/api/ApiMiddleware.php
adc8b2666306d185f70fb0668fcefdf2b40b7d13
[github/shaarli/Shaarli.git] / application / api / ApiMiddleware.php
1 <?php
2 namespace Shaarli\Api;
3
4 use malkusch\lock\mutex\FlockMutex;
5 use Shaarli\Api\Exceptions\ApiAuthorizationException;
6 use Shaarli\Api\Exceptions\ApiException;
7 use Shaarli\Bookmark\BookmarkFileService;
8 use Shaarli\Config\ConfigManager;
9 use Slim\Container;
10 use Slim\Http\Request;
11 use Slim\Http\Response;
12
13 /**
14 * Class ApiMiddleware
15 *
16 * This will be called before accessing any API Controller.
17 * Its role is to make sure that the API is enabled, configured, and to validate the JWT token.
18 *
19 * If the request is validated, the controller is called, otherwise a JSON error response is returned.
20 *
21 * @package Api
22 */
23 class ApiMiddleware
24 {
25 /**
26 * @var int JWT token validity in seconds (9 min).
27 */
28 public static $TOKEN_DURATION = 540;
29
30 /**
31 * @var Container: contains conf, plugins, etc.
32 */
33 protected $container;
34
35 /**
36 * @var ConfigManager instance.
37 */
38 protected $conf;
39
40 /**
41 * ApiMiddleware constructor.
42 *
43 * @param Container $container instance.
44 */
45 public function __construct($container)
46 {
47 $this->container = $container;
48 $this->conf = $this->container->get('conf');
49 $this->setLinkDb($this->conf);
50 }
51
52 /**
53 * Middleware execution:
54 * - check the API request
55 * - execute the controller
56 * - return the response
57 *
58 * @param Request $request Slim request
59 * @param Response $response Slim response
60 * @param callable $next Next action
61 *
62 * @return Response response.
63 */
64 public function __invoke($request, $response, $next)
65 {
66 try {
67 $this->checkRequest($request);
68 $response = $next($request, $response);
69 } catch (ApiException $e) {
70 $e->setResponse($response);
71 $e->setDebug($this->conf->get('dev.debug', false));
72 $response = $e->getApiResponse();
73 }
74
75 return $response
76 ->withHeader('Access-Control-Allow-Origin', '*')
77 ->withHeader(
78 'Access-Control-Allow-Headers',
79 'X-Requested-With, Content-Type, Accept, Origin, Authorization'
80 )
81 ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
82 ;
83 }
84
85 /**
86 * Check the request validity (HTTP method, request value, etc.),
87 * that the API is enabled, and the JWT token validity.
88 *
89 * @param Request $request Slim request
90 *
91 * @throws ApiAuthorizationException The API is disabled or the token is invalid.
92 */
93 protected function checkRequest($request)
94 {
95 if (! $this->conf->get('api.enabled', true)) {
96 throw new ApiAuthorizationException('API is disabled');
97 }
98 $this->checkToken($request);
99 }
100
101 /**
102 * Check that the JWT token is set and valid.
103 * The API secret setting must be set.
104 *
105 * @param Request $request Slim request
106 *
107 * @throws ApiAuthorizationException The token couldn't be validated.
108 */
109 protected function checkToken($request)
110 {
111 if (!$request->hasHeader('Authorization')
112 && !isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION'])
113 ) {
114 throw new ApiAuthorizationException('JWT token not provided');
115 }
116
117 if (empty($this->conf->get('api.secret'))) {
118 throw new ApiAuthorizationException('Token secret must be set in Shaarli\'s administration');
119 }
120
121 if (isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION'])) {
122 $authorization = $this->container->environment['REDIRECT_HTTP_AUTHORIZATION'];
123 } else {
124 $authorization = $request->getHeaderLine('Authorization');
125 }
126
127 if (! preg_match('/^Bearer (.*)/i', $authorization, $matches)) {
128 throw new ApiAuthorizationException('Invalid JWT header');
129 }
130
131 ApiUtils::validateJwtToken($matches[1], $this->conf->get('api.secret'));
132 }
133
134 /**
135 * Instantiate a new LinkDB including private bookmarks,
136 * and load in the Slim container.
137 *
138 * FIXME! LinkDB could use a refactoring to avoid this trick.
139 *
140 * @param ConfigManager $conf instance.
141 */
142 protected function setLinkDb($conf)
143 {
144 $linkDb = new BookmarkFileService(
145 $conf,
146 $this->container->get('history'),
147 new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2),
148 true
149 );
150 $this->container['db'] = $linkDb;
151 }
152 }