11 class ApiUtilsTest
extends \PHPUnit_Framework_TestCase
14 * Force the timezone for ISO datetimes.
16 public static function setUpBeforeClass()
18 date_default_timezone_set('UTC');
22 * Generate a valid JWT token.
24 * @param string $secret API secret used to generate the signature.
26 * @return string Generated token.
28 public static function generateValidJwtToken($secret)
30 $header = Base64Url
::encode('{
34 $payload = Base64Url
::encode('{
37 $signature = Base64Url
::encode(hash_hmac('sha512', $header .'.'. $payload , $secret, true));
38 return $header .'.'. $payload .'.'. $signature;
42 * Generate a JWT token from given header and payload.
44 * @param string $header Header in JSON format.
45 * @param string $payload Payload in JSON format.
46 * @param string $secret API secret used to hash the signature.
48 * @return string JWT token.
50 public static function generateCustomJwtToken($header, $payload, $secret)
52 $header = Base64Url
::encode($header);
53 $payload = Base64Url
::encode($payload);
54 $signature = Base64Url
::encode(hash_hmac('sha512', $header . '.' . $payload, $secret, true));
55 return $header . '.' . $payload . '.' . $signature;
59 * Test validateJwtToken() with a valid JWT token.
61 public function testValidateJwtTokenValid()
63 $secret = 'WarIsPeace';
64 ApiUtils
::validateJwtToken(self
::generateValidJwtToken($secret), $secret);
68 * Test validateJwtToken() with a malformed JWT token.
70 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
71 * @expectedExceptionMessage Malformed JWT token
73 public function testValidateJwtTokenMalformed()
76 ApiUtils
::validateJwtToken($token, 'foo');
80 * Test validateJwtToken() with an empty JWT token.
82 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
83 * @expectedExceptionMessage Malformed JWT token
85 public function testValidateJwtTokenMalformedEmpty()
88 ApiUtils
::validateJwtToken($token, 'foo');
92 * Test validateJwtToken() with a JWT token without header.
94 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
95 * @expectedExceptionMessage Malformed JWT token
97 public function testValidateJwtTokenMalformedEmptyHeader()
99 $token = '.payload.signature';
100 ApiUtils
::validateJwtToken($token, 'foo');
104 * Test validateJwtToken() with a JWT token without payload
106 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
107 * @expectedExceptionMessage Malformed JWT token
109 public function testValidateJwtTokenMalformedEmptyPayload()
111 $token = 'header..signature';
112 ApiUtils
::validateJwtToken($token, 'foo');
116 * Test validateJwtToken() with a JWT token with an empty signature.
118 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
119 * @expectedExceptionMessage Invalid JWT signature
121 public function testValidateJwtTokenInvalidSignatureEmpty()
123 $token = 'header.payload.';
124 ApiUtils
::validateJwtToken($token, 'foo');
128 * Test validateJwtToken() with a JWT token with an invalid signature.
130 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
131 * @expectedExceptionMessage Invalid JWT signature
133 public function testValidateJwtTokenInvalidSignature()
135 $token = 'header.payload.nope';
136 ApiUtils
::validateJwtToken($token, 'foo');
140 * Test validateJwtToken() with a JWT token with a signature generated with the wrong API secret.
142 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
143 * @expectedExceptionMessage Invalid JWT signature
145 public function testValidateJwtTokenInvalidSignatureSecret()
147 ApiUtils
::validateJwtToken(self
::generateValidJwtToken('foo'), 'bar');
151 * Test validateJwtToken() with a JWT token with a an invalid header (not JSON).
153 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
154 * @expectedExceptionMessage Invalid JWT header
156 public function testValidateJwtTokenInvalidHeader()
158 $token = $this->generateCustomJwtToken('notJSON', '{"JSON":1}', 'secret');
159 ApiUtils
::validateJwtToken($token, 'secret');
163 * Test validateJwtToken() with a JWT token with a an invalid payload (not JSON).
165 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
166 * @expectedExceptionMessage Invalid JWT payload
168 public function testValidateJwtTokenInvalidPayload()
170 $token = $this->generateCustomJwtToken('{"JSON":1}', 'notJSON', 'secret');
171 ApiUtils
::validateJwtToken($token, 'secret');
175 * Test validateJwtToken() with a JWT token without issued time.
177 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
178 * @expectedExceptionMessage Invalid JWT issued time
180 public function testValidateJwtTokenInvalidTimeEmpty()
182 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"JSON":1}', 'secret');
183 ApiUtils
::validateJwtToken($token, 'secret');
187 * Test validateJwtToken() with an expired JWT token.
189 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
190 * @expectedExceptionMessage Invalid JWT issued time
192 public function testValidateJwtTokenInvalidTimeExpired()
194 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() - 600) . '}', 'secret');
195 ApiUtils
::validateJwtToken($token, 'secret');
199 * Test validateJwtToken() with a JWT token issued in the future.
201 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
202 * @expectedExceptionMessage Invalid JWT issued time
204 public function testValidateJwtTokenInvalidTimeFuture()
206 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() + 60) . '}', 'secret');
207 ApiUtils
::validateJwtToken($token, 'secret');
211 * Test formatLink() with a link using all useful fields.
213 public function testFormatLinkComplete()
215 $indexUrl = 'https://domain.tld/sub/';
218 'url' => 'http://lol.lol',
220 'title' => 'Important Title',
221 'description' => 'It is very lol<tag>' . PHP_EOL
. 'new line',
222 'tags' => 'blip .blop ',
224 'created' => \DateTime
::createFromFormat('Ymd_His', '20170107_160102'),
225 'updated' => \DateTime
::createFromFormat('Ymd_His', '20170107_160612'),
230 'url' => 'http://lol.lol',
232 'title' => 'Important Title',
233 'description' => 'It is very lol<tag>' . PHP_EOL
. 'new line',
234 'tags' => ['blip', '.blop'],
236 'created' => '2017-01-07T16:01:02+00:00',
237 'updated' => '2017-01-07T16:06:12+00:00',
240 $this->assertEquals($expected, ApiUtils
::formatLink($link, $indexUrl));
244 * Test formatLink() with only minimal fields filled, and internal link.
246 public function testFormatLinkMinimalNote()
248 $indexUrl = 'https://domain.tld/sub/';
257 'created' => \DateTime
::createFromFormat('Ymd_His', '20170107_160102'),
262 'url' => 'https://domain.tld/sub/?abc',
268 'created' => '2017-01-07T16:01:02+00:00',
272 $this->assertEquals($expected, ApiUtils
::formatLink($link, $indexUrl));
276 * Test updateLink with valid data, and also unnecessary fields.
278 public function testUpdateLink()
280 $created = \DateTime
::createFromFormat('Ymd_His', '20170107_160102');
289 'created' => $created,
294 'shorturl' => 'nope',
295 'url' => 'http://somewhere.else',
297 'description' => 'Percé jusques au fond du cœur [...]',
298 'tags' => 'corneille rodrigue',
300 'created' => 'creation',
301 'updated' => 'updation',
304 $result = ApiUtils
::updateLink($old, $new);
305 $this->assertEquals(12, $result['id']);
306 $this->assertEquals('http://somewhere.else', $result['url']);
307 $this->assertEquals('abc', $result['shorturl']);
308 $this->assertEquals('Le Cid', $result['title']);
309 $this->assertEquals('Percé jusques au fond du cœur [...]', $result['description']);
310 $this->assertEquals('corneille rodrigue', $result['tags']);
311 $this->assertEquals(true, $result['private']);
312 $this->assertEquals($created, $result['created']);
313 $this->assertTrue(new \
DateTime('5 seconds ago') < $result['updated']);
317 * Test updateLink with minimal data.
319 public function testUpdateLinkMinimal()
321 $created = \DateTime
::createFromFormat('Ymd_His', '20170107_160102');
327 'description' => 'Interesting description!',
330 'created' => $created,
341 $result = ApiUtils
::updateLink($old, $new);
342 $this->assertEquals(12, $result['id']);
343 $this->assertEquals('?abc', $result['url']);
344 $this->assertEquals('abc', $result['shorturl']);
345 $this->assertEquals('?abc', $result['title']);
346 $this->assertEquals('', $result['description']);
347 $this->assertEquals('', $result['tags']);
348 $this->assertEquals(false, $result['private']);
349 $this->assertEquals($created, $result['created']);
350 $this->assertTrue(new \
DateTime('5 seconds ago') < $result['updated']);