KeycloakTest   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 307
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 182
c 1
b 0
f 0
dl 0
loc 307
rs 10
wmc 16

16 Methods

Rating   Name   Duplication   Size   Complexity  
A testGetAuthorizationUrl() 0 6 1
A testUserDataFailsWhenEncryptionEncounteredAndNotConfigured() 0 25 1
A testAuthorizationUrl() 0 13 1
A testScopes() 0 8 1
A testGetAccessToken() 0 23 1
A testUserData() 0 38 1
A tearDown() 0 4 1
A testGetLogoutUrl() 0 6 1
A testUserDataWithEncryption() 0 48 1
A testEncryptionKeyPath() 0 20 1
A testErrorResponse() 0 14 1
A testGetBaseAccessTokenUrl() 0 8 1
A testEncryptionKey() 0 13 1
A buildQueryString() 0 3 1
A testEncryptionAlgorithm() 0 13 1
A setUp() 0 8 1
1
<?php
2
/**
3
 * Application: directus_keycloak_client
4
 * Author: Eric Delaporte <[email protected]>
5
 * Date: 19.11.19
6
 * Time: 23:59
7
 */
8
declare(strict_types=1);
9
10
11
12
namespace {
13
    $mockFileGetContents = null;
14
}
15
16
namespace Makuro\Directus\KeycloakClient\Provider {
17
18
    use Exception;
19
20
    /**
21
     * @return mixed|null
22
     *
23
     * @throws Exception
24
     */
25
    function file_get_contents()
26
    {
27
        global $mockFileGetContents;
28
        if (isset($mockFileGetContents) && !is_null($mockFileGetContents)) {
29
            if (is_a($mockFileGetContents, 'Exception') || $mockFileGetContents instanceof Exception) {
30
                throw $mockFileGetContents;
31
            }
32
            return $mockFileGetContents;
33
        } else {
34
            return call_user_func_array('\file_get_contents', func_get_args());
35
        }
36
    }
37
}
38
39
namespace Makuro\Directus\KeycloakClient\Test\Provider {
40
41
    use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
42
    use League\OAuth2\Client\Tool\QueryBuilderTrait;
43
    use Makuro\Directus\KeycloakClient\Provider\Exception\EncryptionConfigurationException;
44
    use Makuro\Directus\KeycloakClient\Provider\Keycloak;
45
    use Mockery as m;
46
    use PHPUnit\Framework\TestCase;
47
48
    final class KeycloakTest extends TestCase
49
    {
50
        use QueryBuilderTrait;
51
52
        protected $provider;
53
54
        protected function setUp(): void
55
        {
56
            $this->provider = new Keycloak([
57
                'authServerUrl' => 'http://mock.url/auth',
58
                'realm' => 'mock_realm',
59
                'clientId' => 'mock_client_id',
60
                'clientSecret' => 'mock_secret',
61
                'redirectUri' => 'none',
62
            ]);
63
        }
64
65
        public function tearDown(): void
66
        {
67
            m::close();
68
            parent::tearDown();
69
        }
70
71
        public function testAuthorizationUrl()
72
        {
73
            $url = $this->provider->getAuthorizationUrl();
74
            $uri = parse_url($url);
75
            parse_str($uri['query'], $query);
76
77
            $this->assertArrayHasKey('client_id', $query);
78
            $this->assertArrayHasKey('redirect_uri', $query);
79
            $this->assertArrayHasKey('state', $query);
80
            $this->assertArrayHasKey('scope', $query);
81
            $this->assertArrayHasKey('response_type', $query);
82
            $this->assertArrayHasKey('approval_prompt', $query);
83
            $this->assertNotNull($this->provider->getState());
84
        }
85
86
        public function testScopes()
87
        {
88
            $scopeSeparator = ',';
89
            $options = ['scope' => [uniqid(), uniqid()]];
90
            $query = ['scope' => implode($scopeSeparator, $options['scope'])];
91
            $url = $this->provider->getAuthorizationUrl($options);
92
            $encodedScope = $this->buildQueryString($query);
93
            $this->assertStringContainsString($encodedScope, $url);
94
        }
95
96
        public function testGetAuthorizationUrl()
97
        {
98
            $url = $this->provider->getAuthorizationUrl();
99
            $uri = parse_url($url);
100
101
            $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/auth', $uri['path']);
102
        }
103
104
        public function testGetLogoutUrl()
105
        {
106
            $url = $this->provider->getLogoutUrl();
107
            $uri = parse_url($url);
108
109
            $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/logout', $uri['path']);
110
        }
111
112
        public function testGetBaseAccessTokenUrl()
113
        {
114
            $params = [];
115
116
            $url = $this->provider->getBaseAccessTokenUrl($params);
117
            $uri = parse_url($url);
118
119
            $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/token', $uri['path']);
120
        }
121
122
        public function testGetAccessToken()
123
        {
124
            $response = m::mock('Psr\Http\Message\ResponseInterface');
125
            $response
126
                ->shouldReceive('getBody')
127
                ->andReturn('{"access_token":"mock_access_token", "scope":"email", "token_type":"bearer"}');
128
            $response
129
                ->shouldReceive('getHeader')
130
                ->andReturn(['content-type' => 'json']);
131
132
            $client = m::mock('GuzzleHttp\ClientInterface');
133
            $client
134
                ->shouldReceive('send')
135
                ->times(1)
136
                ->andReturn($response);
137
            $this->provider->setHttpClient($client);
138
139
            $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
140
141
            $this->assertEquals('mock_access_token', $token->getToken());
142
            $this->assertNull($token->getExpires());
143
            $this->assertNull($token->getRefreshToken());
144
            $this->assertNull($token->getResourceOwnerId());
145
        }
146
147
        public function testUserData()
148
        {
149
            $userId = rand(1000, 9999);
150
            $name = uniqid();
151
            $email = uniqid();
152
153
            $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
154
            $postResponse
155
                ->shouldReceive('getBody')
156
                ->andReturn('access_token=mock_access_token&expires=3600' .
157
                    '&refresh_token=mock_refresh_token&otherKey={1234}');
158
            $postResponse
159
                ->shouldReceive('getHeader')
160
                ->andReturn(['content-type' => 'application/x-www-form-urlencoded']);
161
162
            $userResponse = m::mock('Psr\Http\Message\ResponseInterface');
163
            $userResponse
164
                ->shouldReceive('getBody')
165
                ->andReturn('{"sub": ' . $userId . ', "name": "' . $name . '", "email": "' . $email . '"}');
166
            $userResponse
167
                ->shouldReceive('getHeader')
168
                ->andReturn(['content-type' => 'json']);
169
170
            $client = m::mock('GuzzleHttp\ClientInterface');
171
            $client->shouldReceive('send')
172
                ->times(2)
173
                ->andReturn($postResponse, $userResponse);
174
            $this->provider->setHttpClient($client);
175
176
            $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
177
            $user = $this->provider->getResourceOwner($token);
178
179
            $this->assertEquals($userId, $user->getId());
180
            $this->assertEquals($userId, $user->toArray()['sub']);
181
            $this->assertEquals($name, $user->getName());
182
            $this->assertEquals($name, $user->toArray()['name']);
183
            $this->assertEquals($email, $user->getEmail());
184
            $this->assertEquals($email, $user->toArray()['email']);
185
        }
186
187
        public function testErrorResponse()
188
        {
189
            $response = m::mock('Psr\Http\Message\ResponseInterface');
190
            $response
191
                ->shouldReceive('getBody')
192
                ->andReturn('{"error": "invalid_grant", "error_description": "Code not found"}');
193
            $response->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
194
195
            $client = m::mock('GuzzleHttp\ClientInterface');
196
            $client->shouldReceive('send')->times(1)->andReturn($response);
197
            $this->provider->setHttpClient($client);
198
199
            $this->expectException(IdentityProviderException::class);
200
            $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
201
        }
202
        /**
203
         * Build a query string from an array.
204
         *
205
         * @param array $params
206
         *
207
         * @return string
208
         */
209
        protected function buildQueryString(array $params)
210
        {
211
            return http_build_query($params, '', '&', \PHP_QUERY_RFC3986);
212
        }
213
214
        public function testEncryptionAlgorithm()
215
        {
216
            $algorithm = uniqid();
217
            $provider = new Keycloak([
218
                'encryptionAlgorithm' => $algorithm,
219
            ]);
220
221
            $this->assertEquals($algorithm, $provider->encryptionAlgorithm);
222
223
            $algorithm = uniqid();
224
            $provider->setEncryptionAlgorithm($algorithm);
225
226
            $this->assertEquals($algorithm, $provider->encryptionAlgorithm);
227
        }
228
229
        public function testEncryptionKey()
230
        {
231
            $key = uniqid();
232
            $provider = new Keycloak([
233
                'encryptionKey' => $key,
234
            ]);
235
236
            $this->assertEquals($key, $provider->encryptionKey);
237
238
            $key = uniqid();
239
            $provider->setEncryptionKey($key);
240
241
            $this->assertEquals($key, $provider->encryptionKey);
242
        }
243
244
        public function testEncryptionKeyPath()
245
        {
246
            global $mockFileGetContents;
247
            $path = uniqid();
248
            $key = uniqid();
249
            $mockFileGetContents = $key;
250
251
            $provider = new Keycloak([
252
                'encryptionKeyPath' => $path,
253
            ]);
254
255
            $this->assertEquals($key, $provider->encryptionKey);
256
257
            $path = uniqid();
258
            $key = uniqid();
259
            $mockFileGetContents = $key;
260
261
            $provider->setEncryptionKeyPath($path);
262
263
            $this->assertEquals($key, $provider->encryptionKey);
264
        }
265
/*
266
        public function testEncryptionKeyPathFails()
267
        {
268
            global $mockFileGetContents;
269
            $path = uniqid();
270
            $mockFileGetContents = new Exception();
271
272
            $provider = new Keycloak([
273
                'encryptionKeyPath' => $path,
274
            ]);
275
276
            $provider->setEncryptionKeyPath($path);
277
        }
278
*/
279
280
        public function testUserDataWithEncryption()
281
        {
282
            $userId = rand(1000, 9999);
283
            $name = uniqid();
284
            $email = uniqid();
285
            $jwt = uniqid();
286
            $algorithm = uniqid();
287
            $key = uniqid();
288
289
            $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
290
            $postResponse
291
                ->shouldReceive('getBody')
292
                ->andReturn('access_token=mock_access_token&expires=3600' .
293
                    '&refresh_token=mock_refresh_token&otherKey={1234}');
294
            $postResponse
295
                ->shouldReceive('getHeader')
296
                ->andReturn(['content-type' => 'application/x-www-form-urlencoded']);
297
            $postResponse->shouldReceive('getStatusCode')->andReturn(200);
298
299
            $userResponse = m::mock('Psr\Http\Message\ResponseInterface');
300
            $userResponse->shouldReceive('getBody')->andReturn($jwt);
301
            $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/jwt']);
302
            $userResponse->shouldReceive('getStatusCode')->andReturn(200);
303
304
            $decoder = m::mock('overload:Firebase\JWT\JWT');
305
            $decoder->shouldReceive('decode')->with($jwt, $key, [$algorithm])->andReturn([
306
                'sub' => $userId,
307
                'email' => $email,
308
                'name' => $name,
309
            ]);
310
311
            $client = m::mock('GuzzleHttp\ClientInterface');
312
            $client->shouldReceive('send')
313
                ->times(2)
314
                ->andReturn($postResponse, $userResponse);
315
            $this->provider->setHttpClient($client);
316
317
            $token = $this->provider->setEncryptionAlgorithm($algorithm)
318
                ->setEncryptionKey($key)
319
                ->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
320
            $user = $this->provider->getResourceOwner($token);
321
322
            $this->assertEquals($userId, $user->getId());
323
            $this->assertEquals($userId, $user->toArray()['sub']);
324
            $this->assertEquals($name, $user->getName());
325
            $this->assertEquals($name, $user->toArray()['name']);
326
            $this->assertEquals($email, $user->getEmail());
327
            $this->assertEquals($email, $user->toArray()['email']);
328
        }
329
330
        public function testUserDataFailsWhenEncryptionEncounteredAndNotConfigured()
331
        {
332
            $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
333
            $postResponse->shouldReceive('getBody')
334
                ->andReturn('access_token=mock_access_token&expires=3600' .
335
                    '&refresh_token=mock_refresh_token&otherKey={1234}');
336
            $postResponse
337
                ->shouldReceive('getHeader')
338
                ->andReturn(['content-type' => 'application/x-www-form-urlencoded']);
339
            $postResponse->shouldReceive('getStatusCode')->andReturn(200);
340
341
            $userResponse = m::mock('Psr\Http\Message\ResponseInterface');
342
            $userResponse->shouldReceive('getBody')->andReturn(uniqid());
343
            $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/jwt']);
344
            $userResponse->shouldReceive('getStatusCode')->andReturn(200);
345
346
            $client = m::mock('GuzzleHttp\ClientInterface');
347
            $client->shouldReceive('send')
348
                ->times(2)
349
                ->andReturn($postResponse, $userResponse);
350
            $this->provider->setHttpClient($client);
351
352
            $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
353
            $this->expectException(EncryptionConfigurationException::class);
354
            $this->provider->getResourceOwner($token);
355
        }
356
    }
357
}