Completed
Push — master ( 0f5e6b...d874fd )
by Conrad
01:54
created

tests/OAuthServerTest.php (3 issues)

Upgrade to new PHP Analysis Engine

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 () {
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);
0 ignored issues
show
$client is of type object<SilverStripe\ORM\DataObject>|null, but the function expects a object<AdvancedLearning\...h2Server\Models\Client>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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);
0 ignored issues
show
$member is of type object<SilverStripe\ORM\DataObject>|null, but the function expects a object<SilverStripe\Security\Member>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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);
0 ignored issues
show
$client is of type object<SilverStripe\ORM\DataObject>|null, but the function expects a object<AdvancedLearning\...h2Server\Models\Client>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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