Completed
Push — master ( 364709...8763c1 )
by Conrad
01:41
created

OAuthServerTest::getAuthorisationServer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
c 0
b 0
f 0
rs 9.2
cc 1
eloc 13
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
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
Coding Style introduced by
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
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
Coding Style introduced by
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
$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(
169
            (new HttpRequestAdapter())
170
                ->fromPsr7($request)
171
                // controller expects a string
172
                ->setBody(json_encode($request->getParsedBody()))
173
        )
174
            ->index();
175
176
        $this->assertInstanceOf(HTTPResponse::class, $response, 'Should receive a response object');
177
        $this->assertEquals(200, $response->getStatusCode(), 'Should receive a 200 response code');
178
179
        // check for access token
180
        $data = json_decode($response->getBody(), true);
181
        $this->assertArrayHasKey('token_type', $data, 'Response should have a token_type');
182
        $this->assertArrayHasKey('expires_in', $data, 'Response should have expire time for token');
183
        $this->assertArrayHasKey('access_token', $data, 'Response should have a token');
184
        $this->assertEquals('Bearer', $data['token_type'], 'Token type should be Bearer');
185
    }
186
187
    public function testUserEntity()
188
    {
189
        $member = $this->objFromFixture(Member::class, 'member1');
190
        $entity = new UserEntity($member);
0 ignored issues
show
Documentation introduced by
$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...
191
192
        $this->assertEquals($member->ID, $entity->getMember()->ID, 'User entity member should have been set');
193
    }
194
195
    /**
196
     * Setup the Authorization Server.
197
     *
198
     * @return AuthorizationServer
199
     */
200
    protected function getAuthorisationServer()
201
    {
202
        // Init our repositories
203
        $clientRepository = new ClientRepository(); // instance of ClientRepositoryInterface
204
        $scopeRepository = new ScopeRepository(); // instance of ScopeRepositoryInterface
205
        $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface
206
207
        // Path to public and private keys
208
        $privateKey = $this->getPrivateKeyPath();
209
        $encryptionKey = 'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen';
210
211
        // Setup the authorization server
212
        $server = new AuthorizationServer(
213
            $clientRepository,
214
            $accessTokenRepository,
215
            $scopeRepository,
216
            $privateKey,
217
            $encryptionKey
218
        );
219
220
        return $server;
221
    }
222
223
    /**
224
     * Get the resource server.
225
     *
226
     * @return \League\OAuth2\Server\ResourceServer
227
     */
228
    protected function getResourceServer()
229
    {
230
        // Init our repositories
231
        $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface
232
233
        // Path to authorization server's public key
234
        $publicKeyPath = $this->getPublicKeyPath();
235
236
        // Setup the authorization server
237
        $server = new \League\OAuth2\Server\ResourceServer(
238
            $accessTokenRepository,
239
            $publicKeyPath
240
        );
241
242
        return $server;
243
    }
244
245
    /**
246
     * Get the full path the private key.
247
     *
248
     * @return string
249
     */
250
    protected function getPrivateKeyPath()
251
    {
252
        return sys_get_temp_dir() . '/' . self::$privateKeyFile;
253
    }
254
255
    /**
256
     * Get the full path the public key.
257
     *
258
     * @return string
259
     */
260
    protected function getPublicKeyPath()
261
    {
262
        return sys_get_temp_dir() . '/' . self::$publicKeyFile;
263
    }
264
265
    /**
266
     * Cleanup test environment.
267
     */
268
    protected function tearDown()
269
    {
270
        parent::tearDown();
271
        // remove private key after tests have finished
272
        unlink($this->getPrivateKeyPath());
273
        // remove public key after tests have finished
274
        unlink($this->getPublicKeyPath());
275
    }
276
277
    /**
278
     * Generates a response with an access token using the client grant.
279
     *
280
     * @return \Psr\Http\Message\ResponseInterface
281
     */
282
    protected function generateClientAccessToken()
283
    {
284
        $server = $this->getAuthorisationServer();
285
        // Enable the client credentials grant on the server
286
        $server->enableGrantType(
287
            new ClientCredentialsGrant(),
288
            new \DateInterval('PT1H') // access tokens will expire after 1 hour
289
        );
290
291
        $client = $this->objFromFixture(Client::class, 'webapp');
292
293
        $request = $this->getClientRequest($client);
0 ignored issues
show
Documentation introduced by
$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...
294
295
        $response = new Response();
296
        return $server->respondToAccessTokenRequest($request, $response);
297
    }
298
299
    /**
300
     * Get PSR7 request object to be used for a client grant.
301
     *
302
     * @param Client $client
303
     *
304
     * @return ServerRequest
305
     */
306
    protected function getClientRequest(Client $client)
307
    {
308
        // setup server vars
309
        $_SERVER['SERVER_PORT'] = 80;
310
        $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
311
312
        return (new ServerRequest(
313
            'POST',
314
            '',
315
            ['Content-Type' => 'application/json']
316
        ))->withParsedBody([
317
            'grant_type' => 'client_credentials',
318
            'client_id' => $client->ID,
319
            'client_secret' => $client->Secret,
320
            'scope' => 'members'
321
        ]);
322
    }
323
324
}
325