Passed
Push — master ( feb246...605e0e )
by Conrad
01:29
created

OAuthServerTest::generateClientAccessToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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