1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* The MIT License (MIT) |
7
|
|
|
* |
8
|
|
|
* Copyright (c) 2014-2019 Spomky-Labs |
9
|
|
|
* |
10
|
|
|
* This software may be modified and distributed under the terms |
11
|
|
|
* of the MIT license. See the LICENSE file for details. |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace OAuth2Framework\Component\AuthorizationCodeGrant\Tests; |
15
|
|
|
|
16
|
|
|
use OAuth2Framework\Component\AuthorizationCodeGrant\AuthorizationCodeGrantType; |
17
|
|
|
use OAuth2Framework\Component\AuthorizationCodeGrant\AuthorizationCodeId; |
18
|
|
|
use OAuth2Framework\Component\AuthorizationCodeGrant\AuthorizationCodeRepository; |
19
|
|
|
use OAuth2Framework\Component\AuthorizationCodeGrant\PKCEMethod\PKCEMethodManager; |
20
|
|
|
use OAuth2Framework\Component\AuthorizationCodeGrant\PKCEMethod\Plain; |
21
|
|
|
use OAuth2Framework\Component\AuthorizationCodeGrant\PKCEMethod\S256; |
22
|
|
|
use OAuth2Framework\Component\Core\Client\Client; |
23
|
|
|
use OAuth2Framework\Component\Core\Client\ClientId; |
24
|
|
|
use OAuth2Framework\Component\Core\DataBag\DataBag; |
25
|
|
|
use OAuth2Framework\Component\Core\Message\OAuth2Error; |
26
|
|
|
use OAuth2Framework\Component\Core\ResourceServer\ResourceServerId; |
27
|
|
|
use OAuth2Framework\Component\Core\UserAccount\UserAccountId; |
28
|
|
|
use OAuth2Framework\Component\TokenEndpoint\GrantTypeData; |
29
|
|
|
use PHPUnit\Framework\TestCase; |
30
|
|
|
use Prophecy\Argument; |
31
|
|
|
use Prophecy\PhpUnit\ProphecyTrait; |
32
|
|
|
use Prophecy\Prophecy\ObjectProphecy; |
33
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
34
|
|
|
use Psr\Http\Message\StreamInterface; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @group GrantType |
38
|
|
|
* @group AuthorizationCodeGrantType |
39
|
|
|
* |
40
|
|
|
* @internal |
41
|
|
|
*/ |
42
|
|
|
final class AuthorizationCodeGrantTypeTest extends TestCase |
43
|
|
|
{ |
44
|
|
|
use ProphecyTrait; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var null|AuthorizationCodeGrantType |
48
|
|
|
*/ |
49
|
|
|
private $grantType; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var null|PKCEMethodManager |
53
|
|
|
*/ |
54
|
|
|
private $pkceMethodManager; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @test |
58
|
|
|
*/ |
59
|
|
|
public function genericInformation() |
60
|
|
|
{ |
61
|
|
|
static::assertEquals(['code'], $this->getGrantType()->associatedResponseTypes()); |
62
|
|
|
static::assertEquals('authorization_code', $this->getGrantType()->name()); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @test |
67
|
|
|
*/ |
68
|
|
|
public function theRequestHaveMissingParameters() |
69
|
|
|
{ |
70
|
|
|
$request = $this->buildRequest([]); |
71
|
|
|
|
72
|
|
|
try { |
73
|
|
|
$this->getGrantType()->checkRequest($request->reveal()); |
74
|
|
|
static::fail('An OAuth2 exception should be thrown.'); |
75
|
|
|
} catch (OAuth2Error $e) { |
76
|
|
|
static::assertEquals(400, $e->getCode()); |
77
|
|
|
static::assertEquals([ |
78
|
|
|
'error' => 'invalid_request', |
79
|
|
|
'error_description' => 'Missing grant type parameter(s): code, redirect_uri.', |
80
|
|
|
], $e->getData()); |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @test |
86
|
|
|
*/ |
87
|
|
|
public function theRequestHaveAllRequiredParameters() |
88
|
|
|
{ |
89
|
|
|
$request = $this->buildRequest(['code' => 'AUTHORIZATION_CODE_ID', 'redirect_uri' => 'http://localhost:8000/']); |
90
|
|
|
|
91
|
|
|
$this->getGrantType()->checkRequest($request->reveal()); |
92
|
|
|
static::assertTrue(true); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @test |
97
|
|
|
*/ |
98
|
|
|
public function theTokenResponseIsCorrectlyPrepared() |
99
|
|
|
{ |
100
|
|
|
$client = $this->prophesize(Client::class); |
101
|
|
|
$request = $this->buildRequest(['code' => 'AUTHORIZATION_CODE_ID', 'redirect_uri' => 'http://localhost:8000/']); |
102
|
|
|
$grantTypeData = new GrantTypeData($client->reveal()); |
103
|
|
|
|
104
|
|
|
$this->getGrantType()->prepareResponse($request->reveal(), $grantTypeData); |
105
|
|
|
static::assertSame($grantTypeData, $grantTypeData); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @test |
110
|
|
|
*/ |
111
|
|
|
public function theGrantTypeCannotGrantTheClientAsTheCodeVerifierIsMissing() |
112
|
|
|
{ |
113
|
|
|
$client = $this->prophesize(Client::class); |
114
|
|
|
$client->isPublic()->willReturn(false); |
115
|
|
|
$client->getPublicId()->willReturn(new ClientId('CLIENT_ID')); |
116
|
|
|
|
117
|
|
|
$request = $this->buildRequest(['code' => 'AUTHORIZATION_CODE_ID', 'redirect_uri' => 'http://localhost:8000/']); |
118
|
|
|
$request->getAttribute('client')->willReturn($client); |
119
|
|
|
$grantTypeData = new GrantTypeData($client->reveal()); |
120
|
|
|
|
121
|
|
|
try { |
122
|
|
|
$this->getGrantType()->grant($request->reveal(), $grantTypeData); |
123
|
|
|
} catch (OAuth2Error $e) { |
124
|
|
|
static::assertEquals(400, $e->getCode()); |
125
|
|
|
static::assertEquals([ |
126
|
|
|
'error' => 'invalid_grant', |
127
|
|
|
'error_description' => 'The parameter "code_verifier" is missing or invalid.', |
128
|
|
|
], $e->getData()); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @test |
134
|
|
|
*/ |
135
|
|
|
public function theGrantTypeCanGrantTheClient() |
136
|
|
|
{ |
137
|
|
|
$client = $this->prophesize(Client::class); |
138
|
|
|
$client->isPublic()->willReturn(false); |
139
|
|
|
$client->getPublicId()->willReturn(new ClientId('CLIENT_ID')); |
140
|
|
|
|
141
|
|
|
$request = $this->buildRequest(['code' => 'AUTHORIZATION_CODE_ID', 'redirect_uri' => 'http://localhost:8000/', 'code_verifier' => 'ABCDEFGH']); |
142
|
|
|
$request->getAttribute('client')->willReturn($client); |
143
|
|
|
$grantTypeData = new GrantTypeData($client->reveal()); |
144
|
|
|
|
145
|
|
|
$this->getGrantType()->grant($request->reveal(), $grantTypeData); |
146
|
|
|
static::assertEquals('USER_ACCOUNT_ID', $grantTypeData->getResourceOwnerId()->getValue()); |
147
|
|
|
static::assertEquals('CLIENT_ID', $grantTypeData->getClient()->getPublicId()->getValue()); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
private function getGrantType(): AuthorizationCodeGrantType |
151
|
|
|
{ |
152
|
|
|
if (null === $this->grantType) { |
153
|
|
|
$authorizationCode = new AuthorizationCode( |
154
|
|
|
new AuthorizationCodeId('AUTHORIZATION_CODE_ID'), |
155
|
|
|
new ClientId('CLIENT_ID'), |
156
|
|
|
new UserAccountId('USER_ACCOUNT_ID'), |
157
|
|
|
[ |
158
|
|
|
'code_challenge' => 'ABCDEFGH', |
159
|
|
|
'code_challenge_method' => 'plain', |
160
|
|
|
], |
161
|
|
|
'http://localhost:8000/', |
162
|
|
|
new \DateTimeImmutable('now +1 day'), |
163
|
|
|
new DataBag([ |
164
|
|
|
'scope' => 'scope1 scope2', |
165
|
|
|
]), |
166
|
|
|
new DataBag([]), |
167
|
|
|
new ResourceServerId('RESOURCE_SERVER_ID') |
168
|
|
|
); |
169
|
|
|
$authorizationCodeRepository = $this->prophesize(AuthorizationCodeRepository::class); |
170
|
|
|
$authorizationCodeRepository->find(new AuthorizationCodeId('AUTHORIZATION_CODE_ID'))->willReturn($authorizationCode); |
171
|
|
|
$authorizationCodeRepository->save(Argument::type(AuthorizationCode::class))->will(function (array $args) { |
|
|
|
|
172
|
|
|
}); |
173
|
|
|
|
174
|
|
|
$this->grantType = new AuthorizationCodeGrantType( |
175
|
|
|
$authorizationCodeRepository->reveal(), |
176
|
|
|
$this->getPkceMethodManager() |
177
|
|
|
); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
return $this->grantType; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
private function getPkceMethodManager(): PKCEMethodManager |
184
|
|
|
{ |
185
|
|
|
if (null === $this->pkceMethodManager) { |
186
|
|
|
$this->pkceMethodManager = new PKCEMethodManager(); |
187
|
|
|
$this->pkceMethodManager->add(new Plain()); |
188
|
|
|
$this->pkceMethodManager->add(new S256()); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
return $this->pkceMethodManager; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
private function buildRequest(array $data): ObjectProphecy |
195
|
|
|
{ |
196
|
|
|
$body = $this->prophesize(StreamInterface::class); |
197
|
|
|
$body->getContents()->willReturn(http_build_query($data)); |
198
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
199
|
|
|
$request->hasHeader('Content-Type')->willReturn(true); |
200
|
|
|
$request->getHeader('Content-Type')->willReturn(['application/x-www-form-urlencoded']); |
201
|
|
|
$request->getBody()->willReturn($body->reveal()); |
202
|
|
|
$request->getParsedBody()->willReturn([]); |
203
|
|
|
|
204
|
|
|
return $request; |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.