Completed
Push — master ( 8763c1...14778d )
by Conrad
03:00
created

OAuthServerTest::testGraphQLClient()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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