Passed
Push — master ( de5894...b396dd )
by Conrad
07:34
created

OAuthServerTest   C

Complexity

Total Complexity 10

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
wmc 10
lcom 1
cbo 20
dl 0
loc 228
c 0
b 0
f 0
rs 6.4705

10 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 16 1
A testClientGrant() 0 10 1
B testPasswordGrant() 0 37 1
B testMiddleware() 0 34 1
A getAuthorisationServer() 0 22 1
A getResourceServer() 0 16 1
A getPrivateKeyPath() 0 4 1
A getPublicKeyPath() 0 4 1
A tearDown() 0 8 1
B generateClientAccessToken() 0 25 1
1
<?php
2
3
namespace AdvancedLearning\Oauth2Server\Tests;
4
5
use AdvancedLearning\Oauth2Server\Middleware\ResourceServerMiddleware;
6
use AdvancedLearning\Oauth2Server\Models\AccessToken;
7
use AdvancedLearning\Oauth2Server\Models\Client;
8
use AdvancedLearning\Oauth2Server\Repositories\AccessTokenRepository;
9
use AdvancedLearning\Oauth2Server\Repositories\ClientRepository;
10
use AdvancedLearning\Oauth2Server\Repositories\RefreshTokenRepository;
11
use AdvancedLearning\Oauth2Server\Repositories\ScopeRepository;
12
use AdvancedLearning\Oauth2Server\Repositories\UserRepository;
13
use function base64_encode;
14
use function file_get_contents;
15
use function file_put_contents;
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 const PHP_EOL;
22
use SilverStripe\Control\Director;
23
use SilverStripe\Control\HTTPApplication;
24
use SilverStripe\Control\HTTPRequest;
25
use SilverStripe\Core\CoreKernel;
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 sys_get_temp_dir;
32
33
class OAuthServerTest extends SapphireTest
34
{
35
    protected static $fixture_file = 'OAuthFixture.yml';
36
37
    protected static $privateKeyFile = 'private.key';
38
39
    protected static $publicKeyFile = 'public.key';
40
41
    /**
42
     * Setup test environment.
43
     */
44
    public function setUp()
45
    {
46
        parent::setUp();
47
48
        // copy private key so we can set correct permissions, file gets removed when tests finish
49
        $path = $this->getPrivateKeyPath();
50
        file_put_contents($path, file_get_contents(__DIR__ . '/' . self::$privateKeyFile));
51
        chmod($path, 0660);
52
53
        // copy public key
54
        $path = $this->getPublicKeyPath();
55
        file_put_contents($path, file_get_contents(__DIR__ . '/' . self::$publicKeyFile));
56
        chmod($path, 0660);
57
58
        Security::force_database_is_ready(true);
59
    }
60
61
    /**
62
     * Test a client grant.
63
     */
64
    public function testClientGrant()
65
    {
66
        $response = $this->generateClientAccessToken();
67
        $data = json_decode((string)$response->getBody(), true);
68
69
        $this->assertArrayHasKey('token_type', $data, 'Response should have a token_type');
70
        $this->assertArrayHasKey('expires_in', $data, 'Response should have expire time for token');
71
        $this->assertArrayHasKey('access_token', $data, 'Response should have a token');
72
        $this->assertEquals('Bearer', $data['token_type'], 'Token type should be Bearer');
73
    }
74
75
    public function testPasswordGrant()
76
    {
77
        $userRepository = new UserRepository();
78
        $refreshRepository = new RefreshTokenRepository();
79
80
        $server = $this->getAuthorisationServer();
81
        $server->enableGrantType(
82
            new PasswordGrant($userRepository, $refreshRepository),
83
            new \DateInterval('PT1H')
84
        );
85
86
        $client = $this->objFromFixture(Client::class, 'webapp');
87
        $member = $this->objFromFixture(Member::class, 'member1');
88
89
        $request = (new ServerRequest(
90
            'POST',
91
            '',
92
            ['Content-Type' => 'application/json']
93
        ))->withParsedBody([
94
            'grant_type' => 'password',
95
            'client_id' => $client->ID,
96
            'client_secret' => $client->Secret,
97
            'scope' => 'members',
98
            'username' => $member->Email,
99
            'password' => 'password1'
100
        ]);
101
102
        $response = new Response();
103
        $response = $server->respondToAccessTokenRequest($request, $response);
104
105
        $data = json_decode((string)$response->getBody(), true);
106
107
        $this->assertArrayHasKey('token_type', $data, 'Response should have a token_type');
108
        $this->assertArrayHasKey('expires_in', $data, 'Response should have expire time for token');
109
        $this->assertArrayHasKey('access_token', $data, 'Response should have a token');
110
        $this->assertEquals('Bearer', $data['token_type'], 'Token type should be Bearer');
111
    }
112
113
    public function testMiddleware()
114
    {
115
        $response = $this->generateClientAccessToken();
116
        $data = json_decode((string)$response->getBody(), true);
117
        $token = $data['access_token'];
118
119
        $server = $this->getResourceServer();
120
121
        $request = new HTTPRequest('GET', '/');
122
        $request->addHeader('authorization', 'Bearer ' . $token);
123
        // fake server port
124
        $_SERVER['SERVER_PORT'] = 443;
125
126
        // Mock app
127
        $app = new HTTPApplication(new BlankKernel(BASE_PATH));
128
        $app->getKernel()->setEnvironment(Kernel::LIVE);
129
130
        $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...
131
            return null;
132
        });
133
134
        $this->assertNull($result, 'Resource Server shouldn\'t modify the response');
135
136
        // failed authentication
137
        $request->removeHeader('authorization');
138
139
        $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...
140
            return null;
141
        });
142
143
        // should have an error response
144
        $this->assertNotNull($result);
145
        $this->assertEquals(401, $result->getStatusCode());
146
    }
147
148
    /**
149
     * Setup the Authorization Server.
150
     *
151
     * @return AuthorizationServer
152
     */
153
    protected function getAuthorisationServer()
154
    {
155
        // Init our repositories
156
        $clientRepository = new ClientRepository(); // instance of ClientRepositoryInterface
157
        $scopeRepository = new ScopeRepository(); // instance of ScopeRepositoryInterface
158
        $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface
159
160
        // Path to public and private keys
161
        $privateKey = $this->getPrivateKeyPath();
162
        $encryptionKey = 'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen';
163
164
        // Setup the authorization server
165
        $server = new AuthorizationServer(
166
            $clientRepository,
167
            $accessTokenRepository,
168
            $scopeRepository,
169
            $privateKey,
170
            $encryptionKey
171
        );
172
173
        return $server;
174
    }
175
176
    /**
177
     * Get the resource server.
178
     *
179
     * @return \League\OAuth2\Server\ResourceServer
180
     */
181
    protected function getResourceServer()
182
    {
183
        // Init our repositories
184
        $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface
185
186
        // Path to authorization server's public key
187
        $publicKeyPath = $this->getPublicKeyPath();
188
189
        // Setup the authorization server
190
        $server = new \League\OAuth2\Server\ResourceServer(
191
            $accessTokenRepository,
192
            $publicKeyPath
193
        );
194
195
        return $server;
196
    }
197
198
    /**
199
     * Get the full path the private key.
200
     *
201
     * @return string
202
     */
203
    protected function getPrivateKeyPath()
204
    {
205
        return sys_get_temp_dir() . '/' . self::$privateKeyFile;
206
    }
207
208
    /**
209
     * Get the full path the public key.
210
     *
211
     * @return string
212
     */
213
    protected function getPublicKeyPath()
214
    {
215
        return sys_get_temp_dir() . '/' . self::$publicKeyFile;
216
    }
217
218
    /**
219
     * Cleanup test environment.
220
     */
221
    protected function tearDown()
222
    {
223
        parent::tearDown();
224
        // remove private key after tests have finished
225
        unlink($this->getPrivateKeyPath());
226
        // remove public key after tests have finished
227
        unlink($this->getPublicKeyPath());
228
    }
229
230
    /**
231
     * Generates a response with an access token using the client grant.
232
     *
233
     * @return \Psr\Http\Message\ResponseInterface
234
     */
235
    protected function generateClientAccessToken()
236
    {
237
        $server = $this->getAuthorisationServer();
238
        // Enable the client credentials grant on the server
239
        $server->enableGrantType(
240
            new ClientCredentialsGrant(),
241
            new \DateInterval('PT1H') // access tokens will expire after 1 hour
242
        );
243
244
        $client = $this->objFromFixture(Client::class, 'webapp');
245
246
        $request = (new ServerRequest(
247
            'POST',
248
            '',
249
            ['Content-Type' => 'application/json']
250
        ))->withParsedBody([
251
            'grant_type' => 'client_credentials',
252
            'client_id' => $client->ID,
253
            'client_secret' => $client->Secret,
254
            'scope' => 'members'
255
        ]);
256
257
        $response = new Response();
258
        return $server->respondToAccessTokenRequest($request, $response);
259
    }
260
}
261