]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - tests/api/ApiUtilsTest.php
lint: apply phpcbf to tests/
[github/shaarli/Shaarli.git] / tests / api / ApiUtilsTest.php
1 <?php
2
3 namespace Shaarli\Api;
4
5 use Shaarli\Base64Url;
6
7 /**
8 * Class ApiUtilsTest
9 */
10 class ApiUtilsTest extends \PHPUnit_Framework_TestCase
11 {
12 /**
13 * Force the timezone for ISO datetimes.
14 */
15 public static function setUpBeforeClass()
16 {
17 date_default_timezone_set('UTC');
18 }
19
20 /**
21 * Generate a valid JWT token.
22 *
23 * @param string $secret API secret used to generate the signature.
24 *
25 * @return string Generated token.
26 */
27 public static function generateValidJwtToken($secret)
28 {
29 $header = Base64Url::encode('{
30 "typ": "JWT",
31 "alg": "HS512"
32 }');
33 $payload = Base64Url::encode('{
34 "iat": '. time() .'
35 }');
36 $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload, $secret, true));
37 return $header .'.'. $payload .'.'. $signature;
38 }
39
40 /**
41 * Generate a JWT token from given header and payload.
42 *
43 * @param string $header Header in JSON format.
44 * @param string $payload Payload in JSON format.
45 * @param string $secret API secret used to hash the signature.
46 *
47 * @return string JWT token.
48 */
49 public static function generateCustomJwtToken($header, $payload, $secret)
50 {
51 $header = Base64Url::encode($header);
52 $payload = Base64Url::encode($payload);
53 $signature = Base64Url::encode(hash_hmac('sha512', $header . '.' . $payload, $secret, true));
54 return $header . '.' . $payload . '.' . $signature;
55 }
56
57 /**
58 * Test validateJwtToken() with a valid JWT token.
59 */
60 public function testValidateJwtTokenValid()
61 {
62 $secret = 'WarIsPeace';
63 ApiUtils::validateJwtToken(self::generateValidJwtToken($secret), $secret);
64 }
65
66 /**
67 * Test validateJwtToken() with a malformed JWT token.
68 *
69 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
70 * @expectedExceptionMessage Malformed JWT token
71 */
72 public function testValidateJwtTokenMalformed()
73 {
74 $token = 'ABC.DEF';
75 ApiUtils::validateJwtToken($token, 'foo');
76 }
77
78 /**
79 * Test validateJwtToken() with an empty JWT token.
80 *
81 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
82 * @expectedExceptionMessage Malformed JWT token
83 */
84 public function testValidateJwtTokenMalformedEmpty()
85 {
86 $token = false;
87 ApiUtils::validateJwtToken($token, 'foo');
88 }
89
90 /**
91 * Test validateJwtToken() with a JWT token without header.
92 *
93 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
94 * @expectedExceptionMessage Malformed JWT token
95 */
96 public function testValidateJwtTokenMalformedEmptyHeader()
97 {
98 $token = '.payload.signature';
99 ApiUtils::validateJwtToken($token, 'foo');
100 }
101
102 /**
103 * Test validateJwtToken() with a JWT token without payload
104 *
105 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
106 * @expectedExceptionMessage Malformed JWT token
107 */
108 public function testValidateJwtTokenMalformedEmptyPayload()
109 {
110 $token = 'header..signature';
111 ApiUtils::validateJwtToken($token, 'foo');
112 }
113
114 /**
115 * Test validateJwtToken() with a JWT token with an empty signature.
116 *
117 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
118 * @expectedExceptionMessage Invalid JWT signature
119 */
120 public function testValidateJwtTokenInvalidSignatureEmpty()
121 {
122 $token = 'header.payload.';
123 ApiUtils::validateJwtToken($token, 'foo');
124 }
125
126 /**
127 * Test validateJwtToken() with a JWT token with an invalid signature.
128 *
129 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
130 * @expectedExceptionMessage Invalid JWT signature
131 */
132 public function testValidateJwtTokenInvalidSignature()
133 {
134 $token = 'header.payload.nope';
135 ApiUtils::validateJwtToken($token, 'foo');
136 }
137
138 /**
139 * Test validateJwtToken() with a JWT token with a signature generated with the wrong API secret.
140 *
141 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
142 * @expectedExceptionMessage Invalid JWT signature
143 */
144 public function testValidateJwtTokenInvalidSignatureSecret()
145 {
146 ApiUtils::validateJwtToken(self::generateValidJwtToken('foo'), 'bar');
147 }
148
149 /**
150 * Test validateJwtToken() with a JWT token with a an invalid header (not JSON).
151 *
152 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
153 * @expectedExceptionMessage Invalid JWT header
154 */
155 public function testValidateJwtTokenInvalidHeader()
156 {
157 $token = $this->generateCustomJwtToken('notJSON', '{"JSON":1}', 'secret');
158 ApiUtils::validateJwtToken($token, 'secret');
159 }
160
161 /**
162 * Test validateJwtToken() with a JWT token with a an invalid payload (not JSON).
163 *
164 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
165 * @expectedExceptionMessage Invalid JWT payload
166 */
167 public function testValidateJwtTokenInvalidPayload()
168 {
169 $token = $this->generateCustomJwtToken('{"JSON":1}', 'notJSON', 'secret');
170 ApiUtils::validateJwtToken($token, 'secret');
171 }
172
173 /**
174 * Test validateJwtToken() with a JWT token without issued time.
175 *
176 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
177 * @expectedExceptionMessage Invalid JWT issued time
178 */
179 public function testValidateJwtTokenInvalidTimeEmpty()
180 {
181 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"JSON":1}', 'secret');
182 ApiUtils::validateJwtToken($token, 'secret');
183 }
184
185 /**
186 * Test validateJwtToken() with an expired JWT token.
187 *
188 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
189 * @expectedExceptionMessage Invalid JWT issued time
190 */
191 public function testValidateJwtTokenInvalidTimeExpired()
192 {
193 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() - 600) . '}', 'secret');
194 ApiUtils::validateJwtToken($token, 'secret');
195 }
196
197 /**
198 * Test validateJwtToken() with a JWT token issued in the future.
199 *
200 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
201 * @expectedExceptionMessage Invalid JWT issued time
202 */
203 public function testValidateJwtTokenInvalidTimeFuture()
204 {
205 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() + 60) . '}', 'secret');
206 ApiUtils::validateJwtToken($token, 'secret');
207 }
208
209 /**
210 * Test formatLink() with a link using all useful fields.
211 */
212 public function testFormatLinkComplete()
213 {
214 $indexUrl = 'https://domain.tld/sub/';
215 $link = [
216 'id' => 12,
217 'url' => 'http://lol.lol',
218 'shorturl' => 'abc',
219 'title' => 'Important Title',
220 'description' => 'It is very lol<tag>' . PHP_EOL . 'new line',
221 'tags' => 'blip .blop ',
222 'private' => '1',
223 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'),
224 'updated' => \DateTime::createFromFormat('Ymd_His', '20170107_160612'),
225 ];
226
227 $expected = [
228 'id' => 12,
229 'url' => 'http://lol.lol',
230 'shorturl' => 'abc',
231 'title' => 'Important Title',
232 'description' => 'It is very lol<tag>' . PHP_EOL . 'new line',
233 'tags' => ['blip', '.blop'],
234 'private' => true,
235 'created' => '2017-01-07T16:01:02+00:00',
236 'updated' => '2017-01-07T16:06:12+00:00',
237 ];
238
239 $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl));
240 }
241
242 /**
243 * Test formatLink() with only minimal fields filled, and internal link.
244 */
245 public function testFormatLinkMinimalNote()
246 {
247 $indexUrl = 'https://domain.tld/sub/';
248 $link = [
249 'id' => 12,
250 'url' => '?abc',
251 'shorturl' => 'abc',
252 'title' => 'Note',
253 'description' => '',
254 'tags' => '',
255 'private' => '',
256 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'),
257 ];
258
259 $expected = [
260 'id' => 12,
261 'url' => 'https://domain.tld/sub/?abc',
262 'shorturl' => 'abc',
263 'title' => 'Note',
264 'description' => '',
265 'tags' => [],
266 'private' => false,
267 'created' => '2017-01-07T16:01:02+00:00',
268 'updated' => '',
269 ];
270
271 $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl));
272 }
273
274 /**
275 * Test updateLink with valid data, and also unnecessary fields.
276 */
277 public function testUpdateLink()
278 {
279 $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102');
280 $old = [
281 'id' => 12,
282 'url' => '?abc',
283 'shorturl' => 'abc',
284 'title' => 'Note',
285 'description' => '',
286 'tags' => '',
287 'private' => '',
288 'created' => $created,
289 ];
290
291 $new = [
292 'id' => 13,
293 'shorturl' => 'nope',
294 'url' => 'http://somewhere.else',
295 'title' => 'Le Cid',
296 'description' => 'Percé jusques au fond du cœur [...]',
297 'tags' => 'corneille rodrigue',
298 'private' => true,
299 'created' => 'creation',
300 'updated' => 'updation',
301 ];
302
303 $result = ApiUtils::updateLink($old, $new);
304 $this->assertEquals(12, $result['id']);
305 $this->assertEquals('http://somewhere.else', $result['url']);
306 $this->assertEquals('abc', $result['shorturl']);
307 $this->assertEquals('Le Cid', $result['title']);
308 $this->assertEquals('Percé jusques au fond du cœur [...]', $result['description']);
309 $this->assertEquals('corneille rodrigue', $result['tags']);
310 $this->assertEquals(true, $result['private']);
311 $this->assertEquals($created, $result['created']);
312 $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']);
313 }
314
315 /**
316 * Test updateLink with minimal data.
317 */
318 public function testUpdateLinkMinimal()
319 {
320 $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102');
321 $old = [
322 'id' => 12,
323 'url' => '?abc',
324 'shorturl' => 'abc',
325 'title' => 'Note',
326 'description' => 'Interesting description!',
327 'tags' => 'doggo',
328 'private' => true,
329 'created' => $created,
330 ];
331
332 $new = [
333 'url' => '',
334 'title' => '',
335 'description' => '',
336 'tags' => '',
337 'private' => false,
338 ];
339
340 $result = ApiUtils::updateLink($old, $new);
341 $this->assertEquals(12, $result['id']);
342 $this->assertEquals('?abc', $result['url']);
343 $this->assertEquals('abc', $result['shorturl']);
344 $this->assertEquals('?abc', $result['title']);
345 $this->assertEquals('', $result['description']);
346 $this->assertEquals('', $result['tags']);
347 $this->assertEquals(false, $result['private']);
348 $this->assertEquals($created, $result['created']);
349 $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']);
350 }
351 }