These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace AdvancedLearning\Oauth2Server\Tests; |
||
4 | |||
5 | use AdvancedLearning\Oauth2Server\AuthorizationServer\DefaultGenerator; |
||
6 | use AdvancedLearning\Oauth2Server\Controllers\AuthoriseController; |
||
7 | use AdvancedLearning\Oauth2Server\Entities\UserEntity; |
||
8 | use AdvancedLearning\Oauth2Server\Middleware\AuthenticationMiddleware; |
||
9 | use AdvancedLearning\Oauth2Server\Models\Client; |
||
10 | use AdvancedLearning\Oauth2Server\Repositories\AccessTokenRepository; |
||
11 | use AdvancedLearning\Oauth2Server\Repositories\ClientRepository; |
||
12 | use AdvancedLearning\Oauth2Server\Repositories\RefreshTokenRepository; |
||
13 | use AdvancedLearning\Oauth2Server\Repositories\ScopeRepository; |
||
14 | use AdvancedLearning\Oauth2Server\Repositories\UserRepository; |
||
15 | use AdvancedLearning\Oauth2Server\Services\Authenticator; |
||
16 | use GuzzleHttp\Psr7\Response; |
||
17 | use GuzzleHttp\Psr7\ServerRequest; |
||
18 | use League\OAuth2\Server\AuthorizationServer; |
||
19 | use League\OAuth2\Server\Grant\ClientCredentialsGrant; |
||
20 | use League\OAuth2\Server\Grant\PasswordGrant; |
||
21 | use Robbie\Psr7\HttpRequestAdapter; |
||
22 | use SilverStripe\Control\HTTPApplication; |
||
23 | use SilverStripe\Control\HTTPRequest; |
||
24 | use SilverStripe\Control\HTTPResponse; |
||
25 | use SilverStripe\Core\Environment; |
||
26 | use SilverStripe\Core\Injector\Injector; |
||
27 | use SilverStripe\Core\Kernel; |
||
28 | use SilverStripe\Core\Tests\Startup\ErrorControlChainMiddlewareTest\BlankKernel; |
||
29 | use SilverStripe\Dev\SapphireTest; |
||
30 | use SilverStripe\Security\Member; |
||
31 | use SilverStripe\Security\Security; |
||
32 | use function file_get_contents; |
||
33 | use function file_put_contents; |
||
34 | use function sys_get_temp_dir; |
||
35 | |||
36 | class OAuthServerTest extends SapphireTest |
||
37 | { |
||
38 | protected static $fixture_file = 'OAuthFixture.yml'; |
||
39 | |||
40 | protected static $privateKeyFile = 'private.key'; |
||
41 | |||
42 | protected static $publicKeyFile = 'public.key'; |
||
43 | |||
44 | /** |
||
45 | * Setup test environment. |
||
46 | */ |
||
47 | public function setUp() |
||
48 | { |
||
49 | parent::setUp(); |
||
50 | |||
51 | // copy private key so we can set correct permissions, file gets removed when tests finish |
||
52 | $path = $this->getPrivateKeyPath(); |
||
53 | file_put_contents($path, file_get_contents(__DIR__ . '/' . self::$privateKeyFile)); |
||
54 | chmod($path, 0660); |
||
55 | Environment::setEnv('OAUTH_PRIVATE_KEY_PATH', $path); |
||
56 | |||
57 | // copy public key |
||
58 | $path = $this->getPublicKeyPath(); |
||
59 | file_put_contents($path, file_get_contents(__DIR__ . '/' . self::$publicKeyFile)); |
||
60 | chmod($path, 0660); |
||
61 | Environment::setEnv('OAUTH_PUBLIC_KEY_PATH', $path); |
||
62 | |||
63 | Security::force_database_is_ready(true); |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * Test a client grant. |
||
68 | */ |
||
69 | public function testClientGrant() |
||
70 | { |
||
71 | $response = $this->generateClientAccessToken(); |
||
72 | $data = json_decode((string)$response->getBody(), true); |
||
73 | |||
74 | $this->assertArrayHasKey('token_type', $data, 'Response should have a token_type'); |
||
75 | $this->assertArrayHasKey('expires_in', $data, 'Response should have expire time for token'); |
||
76 | $this->assertArrayHasKey('access_token', $data, 'Response should have a token'); |
||
77 | $this->assertEquals('Bearer', $data['token_type'], 'Token type should be Bearer'); |
||
78 | } |
||
79 | |||
80 | public function testPasswordGrant() |
||
81 | { |
||
82 | $userRepository = new UserRepository(); |
||
83 | $refreshRepository = new RefreshTokenRepository(); |
||
84 | |||
85 | $server = $this->getAuthorisationServer(); |
||
86 | $server->enableGrantType( |
||
87 | new PasswordGrant($userRepository, $refreshRepository), |
||
88 | new \DateInterval('PT1H') |
||
89 | ); |
||
90 | |||
91 | $client = $this->objFromFixture(Client::class, 'webapp'); |
||
92 | $member = $this->objFromFixture(Member::class, 'member1'); |
||
93 | |||
94 | $request = (new ServerRequest( |
||
95 | 'POST', |
||
96 | '', |
||
97 | ['Content-Type' => 'application/json'] |
||
98 | ))->withParsedBody([ |
||
99 | 'grant_type' => 'password', |
||
100 | 'client_id' => $client->Identifier, |
||
101 | 'client_secret' => $client->Secret, |
||
102 | 'scope' => 'members', |
||
103 | 'username' => $member->Email, |
||
104 | 'password' => 'password1' |
||
105 | ]); |
||
106 | |||
107 | $response = new Response(); |
||
108 | $response = $server->respondToAccessTokenRequest($request, $response); |
||
109 | |||
110 | $data = json_decode((string)$response->getBody(), true); |
||
111 | |||
112 | $this->assertNotEmpty($data, 'Should have received response data'); |
||
113 | $this->assertArrayHasKey('token_type', $data, 'Response should have a token_type'); |
||
114 | $this->assertArrayHasKey('expires_in', $data, 'Response should have expire time for token'); |
||
115 | $this->assertArrayHasKey('access_token', $data, 'Response should have a token'); |
||
116 | $this->assertEquals('Bearer', $data['token_type'], 'Token type should be Bearer'); |
||
117 | } |
||
118 | |||
119 | public function testMiddleware() |
||
120 | { |
||
121 | $response = $this->generateClientAccessToken(); |
||
122 | $data = json_decode((string)$response->getBody(), true); |
||
123 | $token = $data['access_token']; |
||
124 | |||
125 | $server = $this->getResourceServer(); |
||
126 | |||
127 | // set the resource server on authenticator service |
||
128 | Injector::inst()->get(Authenticator::class)->setServer($server); |
||
129 | |||
130 | $request = new HTTPRequest('GET', '/'); |
||
131 | $request->addHeader('authorization', 'Bearer ' . $token); |
||
132 | // fake server port |
||
133 | $_SERVER['SERVER_PORT'] = 443; |
||
134 | |||
135 | // Mock app |
||
136 | $app = new HTTPApplication(new BlankKernel(BASE_PATH)); |
||
137 | $app->getKernel()->setEnvironment(Kernel::LIVE); |
||
138 | |||
139 | $result = (new AuthenticationMiddleware($app))->process($request, function () { |
||
0 ignored issues
–
show
|
|||
140 | return null; |
||
141 | }); |
||
142 | |||
143 | $this->assertNull($result, 'Resource Server shouldn\'t modify the response'); |
||
144 | } |
||
145 | |||
146 | public function testAuthoriseController() |
||
147 | { |
||
148 | $controller = new AuthoriseController(new DefaultGenerator()); |
||
149 | |||
150 | $client = $this->objFromFixture(Client::class, 'webapp'); |
||
151 | $request = $this->getClientRequest($client); |
||
152 | |||
153 | /** |
||
154 | * @var HTTPResponse $response |
||
155 | */ |
||
156 | $response = $controller->setRequest( |
||
157 | (new HttpRequestAdapter()) |
||
158 | ->fromPsr7($request) |
||
159 | // controller expects a string |
||
160 | ->setBody(json_encode($request->getParsedBody())) |
||
161 | ) |
||
162 | ->index(); |
||
163 | |||
164 | $this->assertInstanceOf(HTTPResponse::class, $response, 'Should receive a response object'); |
||
165 | $this->assertEquals(200, $response->getStatusCode(), 'Should receive a 200 response code'); |
||
166 | |||
167 | // check for access token |
||
168 | $data = json_decode($response->getBody(), true); |
||
169 | $this->assertArrayHasKey('token_type', $data, 'Response should have a token_type'); |
||
170 | $this->assertArrayHasKey('expires_in', $data, 'Response should have expire time for token'); |
||
171 | $this->assertArrayHasKey('access_token', $data, 'Response should have a token'); |
||
172 | $this->assertEquals('Bearer', $data['token_type'], 'Token type should be Bearer'); |
||
173 | } |
||
174 | |||
175 | public function testUserEntity() |
||
176 | { |
||
177 | $member = $this->objFromFixture(Member::class, 'member1'); |
||
178 | $entity = new UserEntity($member); |
||
179 | |||
180 | $this->assertEquals($member->ID, $entity->getMember()->ID, 'User entity member should have been set'); |
||
181 | } |
||
182 | |||
183 | public function testGraphQLClient() |
||
184 | { |
||
185 | // generate token |
||
186 | $response = $this->generateClientAccessToken(); |
||
187 | $data = json_decode((string)$response->getBody(), true); |
||
188 | $token = $data['access_token']; |
||
189 | |||
190 | // create request |
||
191 | $request = new HTTPRequest('GET', '/'); |
||
192 | $request->addHeader('authorization', 'Bearer ' . $token); |
||
193 | // fake server port |
||
194 | $_SERVER['SERVER_PORT'] = 443; |
||
195 | |||
196 | $member = (new \AdvancedLearning\Oauth2Server\GraphQL\Authenticator())->authenticate($request); |
||
197 | |||
198 | $this->assertEquals('My Web App', $member->FirstName, 'Member FirstName should be same as client name'); |
||
199 | $this->assertEquals(0, $member->ID, 'Member should not have and ID'); |
||
200 | } |
||
201 | |||
202 | public function testGraphQLMember() |
||
203 | { |
||
204 | $userRepository = new UserRepository(); |
||
205 | $refreshRepository = new RefreshTokenRepository(); |
||
206 | |||
207 | $server = $this->getAuthorisationServer(); |
||
208 | $server->enableGrantType( |
||
209 | new PasswordGrant($userRepository, $refreshRepository), |
||
210 | new \DateInterval('PT1H') |
||
211 | ); |
||
212 | |||
213 | $client = $this->objFromFixture(Client::class, 'webapp'); |
||
214 | $member = $this->objFromFixture(Member::class, 'member1'); |
||
215 | |||
216 | $request = (new ServerRequest( |
||
217 | 'POST', |
||
218 | '', |
||
219 | ['Content-Type' => 'application/json'] |
||
220 | ))->withParsedBody([ |
||
221 | 'grant_type' => 'password', |
||
222 | 'client_id' => $client->Identifier, |
||
223 | 'client_secret' => $client->Secret, |
||
224 | 'scope' => 'members', |
||
225 | 'username' => $member->Email, |
||
226 | 'password' => 'password1' |
||
227 | ]); |
||
228 | |||
229 | $response = new Response(); |
||
230 | $response = $server->respondToAccessTokenRequest($request, $response); |
||
231 | |||
232 | $data = json_decode((string)$response->getBody(), true); |
||
233 | $token = $data['access_token']; |
||
234 | |||
235 | // create request |
||
236 | $request = new HTTPRequest('GET', '/'); |
||
237 | $request->addHeader('authorization', 'Bearer ' . $token); |
||
238 | // fake server port |
||
239 | $_SERVER['SERVER_PORT'] = 443; |
||
240 | |||
241 | $authMember = (new \AdvancedLearning\Oauth2Server\GraphQL\Authenticator())->authenticate($request); |
||
242 | |||
243 | $this->assertEquals($member->ID, $authMember->ID, 'Member should exist in DB'); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Setup the Authorization Server. |
||
248 | * |
||
249 | * @return AuthorizationServer |
||
250 | */ |
||
251 | protected function getAuthorisationServer() |
||
252 | { |
||
253 | // Init our repositories |
||
254 | $clientRepository = new ClientRepository(); // instance of ClientRepositoryInterface |
||
255 | $scopeRepository = new ScopeRepository(); // instance of ScopeRepositoryInterface |
||
256 | $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface |
||
257 | |||
258 | // Path to public and private keys |
||
259 | $privateKey = $this->getPrivateKeyPath(); |
||
260 | $encryptionKey = 'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'; |
||
261 | |||
262 | // Setup the authorization server |
||
263 | $server = new AuthorizationServer( |
||
264 | $clientRepository, |
||
265 | $accessTokenRepository, |
||
266 | $scopeRepository, |
||
267 | $privateKey, |
||
268 | $encryptionKey |
||
269 | ); |
||
270 | |||
271 | return $server; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Get the resource server. |
||
276 | * |
||
277 | * @return \League\OAuth2\Server\ResourceServer |
||
278 | */ |
||
279 | protected function getResourceServer() |
||
280 | { |
||
281 | // Init our repositories |
||
282 | $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface |
||
283 | |||
284 | // Path to authorization server's public key |
||
285 | $publicKeyPath = $this->getPublicKeyPath(); |
||
286 | |||
287 | // Setup the authorization server |
||
288 | $server = new \League\OAuth2\Server\ResourceServer( |
||
289 | $accessTokenRepository, |
||
290 | $publicKeyPath |
||
291 | ); |
||
292 | |||
293 | return $server; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * Get the full path the private key. |
||
298 | * |
||
299 | * @return string |
||
300 | */ |
||
301 | protected function getPrivateKeyPath() |
||
302 | { |
||
303 | return sys_get_temp_dir() . '/' . self::$privateKeyFile; |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Get the full path the public key. |
||
308 | * |
||
309 | * @return string |
||
310 | */ |
||
311 | protected function getPublicKeyPath() |
||
312 | { |
||
313 | return sys_get_temp_dir() . '/' . self::$publicKeyFile; |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * Cleanup test environment. |
||
318 | */ |
||
319 | protected function tearDown() |
||
320 | { |
||
321 | parent::tearDown(); |
||
322 | // remove private key after tests have finished |
||
323 | unlink($this->getPrivateKeyPath()); |
||
324 | // remove public key after tests have finished |
||
325 | unlink($this->getPublicKeyPath()); |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Generates a response with an access token using the client grant. |
||
330 | * |
||
331 | * @return \Psr\Http\Message\ResponseInterface |
||
332 | */ |
||
333 | protected function generateClientAccessToken() |
||
334 | { |
||
335 | $server = $this->getAuthorisationServer(); |
||
336 | // Enable the client credentials grant on the server |
||
337 | $server->enableGrantType( |
||
338 | new ClientCredentialsGrant(), |
||
339 | new \DateInterval('PT1H') // access tokens will expire after 1 hour |
||
340 | ); |
||
341 | |||
342 | $client = $this->objFromFixture(Client::class, 'webapp'); |
||
343 | |||
344 | $request = $this->getClientRequest($client); |
||
345 | |||
346 | $response = new Response(); |
||
347 | return $server->respondToAccessTokenRequest($request, $response); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Get PSR7 request object to be used for a client grant. |
||
352 | * |
||
353 | * @param Client $client |
||
354 | * |
||
355 | * @return ServerRequest |
||
356 | */ |
||
357 | protected function getClientRequest(Client $client) |
||
358 | { |
||
359 | // setup server vars |
||
360 | $_SERVER['SERVER_PORT'] = 80; |
||
361 | $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; |
||
362 | |||
363 | return (new ServerRequest( |
||
364 | 'POST', |
||
365 | '', |
||
366 | ['Content-Type' => 'application/json'] |
||
367 | ))->withParsedBody([ |
||
368 | 'grant_type' => 'client_credentials', |
||
369 | 'client_id' => $client->Identifier, |
||
370 | 'client_secret' => $client->Secret, |
||
371 | 'scope' => 'members' |
||
372 | ]); |
||
373 | } |
||
374 | |||
375 | } |
||
376 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.