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

tests/OAuthServerTest.php (2 issues)

Checks multi line function declaration space after function

Coding Style Informational

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\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
Expected 1 space after FUNCTION keyword; 0 found
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
Expected 1 space after FUNCTION keyword; 0 found
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);
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);
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);
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