1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by PhpStorm. |
4
|
|
|
* User: Alexandre |
5
|
|
|
* Date: 27/05/2018 |
6
|
|
|
* Time: 17:46 |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace OAuth2\Roles\ResourceServer; |
10
|
|
|
|
11
|
|
|
|
12
|
|
|
use GuzzleHttp\Psr7\Response; |
13
|
|
|
use OAuth2\Exceptions\OAuthException; |
14
|
|
|
use OAuth2\Roles\ResourceServer\BearerAuthenticationMethods\AuthorizationRequestHeaderField; |
15
|
|
|
use OAuth2\Roles\ResourceServer\BearerAuthenticationMethods\BearerAuthenticationMethodInterface; |
16
|
|
|
use OAuth2\Roles\ResourceServerInterface; |
17
|
|
|
use OAuth2\Storages\AccessTokenStorageInterface; |
18
|
|
|
use OAuth2\Storages\ClientStorageInterface; |
19
|
|
|
use OAuth2\Storages\ResourceOwnerStorageInterface; |
20
|
|
|
use Psr\Http\Message\ResponseInterface; |
21
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
22
|
|
|
|
23
|
|
|
class ResourceServer implements ResourceServerInterface |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var BearerAuthenticationMethodInterface[] |
27
|
|
|
*/ |
28
|
|
|
private $bearerAuthenticationMethods = []; |
29
|
|
|
/** |
30
|
|
|
* @var AccessTokenStorageInterface |
31
|
|
|
*/ |
32
|
|
|
private $accessTokenStorage; |
33
|
|
|
/** |
34
|
|
|
* @var ClientStorageInterface |
35
|
|
|
*/ |
36
|
|
|
private $clientStorage; |
37
|
|
|
/** |
38
|
|
|
* @var ResourceOwnerStorageInterface |
39
|
|
|
*/ |
40
|
|
|
private $resourceOwnerStorage; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var AuthenticatedRequest|null |
44
|
|
|
*/ |
45
|
|
|
private $authenticatedRequest; |
46
|
|
|
|
47
|
|
|
public function __construct(AccessTokenStorageInterface $accessTokenStorage, |
48
|
|
|
ClientStorageInterface $clientStorage, |
49
|
|
|
ResourceOwnerStorageInterface $resourceOwnerStorage) |
50
|
|
|
{ |
51
|
|
|
$this->bearerAuthenticationMethods = [ |
52
|
|
|
/** |
53
|
|
|
* Resource servers MUST support this method |
54
|
|
|
* @see https://tools.ietf.org/html/rfc6750#section-2.1 |
55
|
|
|
*/ |
56
|
|
|
new AuthorizationRequestHeaderField() |
57
|
|
|
]; |
58
|
|
|
$this->accessTokenStorage = $accessTokenStorage; |
59
|
|
|
$this->clientStorage = $clientStorage; |
60
|
|
|
$this->resourceOwnerStorage = $resourceOwnerStorage; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param ServerRequestInterface $request |
65
|
|
|
* @return null|ResponseInterface |
66
|
|
|
* @throws OAuthException |
67
|
|
|
*/ |
68
|
|
|
public function verifyRequest(ServerRequestInterface $request, array $requiredScopes, ?string $realm = null): ?ResponseInterface |
69
|
|
|
{ |
70
|
|
|
try { |
71
|
|
|
$bearerAuthenticationMethodUsed = null; |
72
|
|
|
foreach ($this->bearerAuthenticationMethods as $bearerAuthenticationMethod) { |
73
|
|
|
if ($bearerAuthenticationMethod->support($request)) { |
74
|
|
|
if ($bearerAuthenticationMethodUsed) { |
75
|
|
|
throw new OAuthException('invalid_request', |
76
|
|
|
'The request utilizes more than one mechanism for authenticating the client.', |
77
|
|
|
'https://tools.ietf.org/html/rfc6749#section-3.2.1'); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
$bearerAuthenticationMethodUsed = $bearerAuthenticationMethod; |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @see https://tools.ietf.org/html/rfc6750#section-3.1 |
86
|
|
|
* If the request lacks any authentication information (e.g., the client |
87
|
|
|
* was unaware that authentication is necessary or attempted using an |
88
|
|
|
* unsupported authentication method), the resource server SHOULD NOT |
89
|
|
|
* include an error code or other error information. |
90
|
|
|
* |
91
|
|
|
* For example: |
92
|
|
|
* |
93
|
|
|
* HTTP/1.1 401 Unauthorized |
94
|
|
|
* WWW-Authenticate: Bearer realm="example" |
95
|
|
|
*/ |
96
|
|
|
if (!$bearerAuthenticationMethodUsed) { |
97
|
|
|
return new Response(401, ['WWW-Authenticate' => 'Bearer' . ($realm ? ' realm="example"' : '')]); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
$token = $bearerAuthenticationMethodUsed->authenticate($request); |
101
|
|
|
|
102
|
|
|
if (!$token) { |
103
|
|
|
throw new OAuthException('invalid_request', |
104
|
|
|
'The request is missing a required parameter, includes an unsupported parameter or parameter value', |
105
|
|
|
'https://tools.ietf.org/html/rfc6750#section-3.1'); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
if (!$accessToken = $this->accessTokenStorage->get($token)) { |
109
|
|
|
throw new OAuthException('invalid_token', |
110
|
|
|
'The access token provided is invalid.', |
111
|
|
|
'https://tools.ietf.org/html/rfc6750#section-3.1'); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
if ($this->accessTokenStorage->hasExpired($accessToken)) { |
115
|
|
|
throw new OAuthException('invalid_token', |
116
|
|
|
'The access token provided is expired.', |
117
|
|
|
'https://tools.ietf.org/html/rfc6750#section-3.1'); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
if (!$client = $this->clientStorage->get($accessToken->getClientIdentifier())) { |
121
|
|
|
throw new OAuthException('invalid_token', |
122
|
|
|
'The access token provided is invalid. Client not found.', |
123
|
|
|
'https://tools.ietf.org/html/rfc6750#section-3.1'); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
$resourceOwner = null; |
127
|
|
|
if ($accessToken->getResourceOwnerIdentifier()) { |
128
|
|
|
if (!$resourceOwner = $this->resourceOwnerStorage->get($accessToken->getResourceOwnerIdentifier())) { |
129
|
|
|
throw new OAuthException('invalid_token', |
130
|
|
|
'The access token provided is invalid. Resource owner not found.', |
131
|
|
|
'https://tools.ietf.org/html/rfc6750#section-3.1'); |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
if (!empty(array_diff($requiredScopes, $accessToken->getScopes()))) { |
136
|
|
|
throw new OAuthException('insufficient_scope', |
137
|
|
|
'The request requires higher privileges than provided by the access token.', |
138
|
|
|
'https://tools.ietf.org/html/rfc6750#section-3.1'); |
139
|
|
|
} |
140
|
|
|
} catch (OAuthException $e) { |
141
|
|
|
switch ($e->getError()) { |
142
|
|
|
case 'invalid_token': |
|
|
|
|
143
|
|
|
$statusCode = 401; |
144
|
|
|
break; |
145
|
|
|
case 'insufficient_scope': |
|
|
|
|
146
|
|
|
$statusCode = 403; |
|
|
|
|
147
|
|
|
default: |
148
|
|
|
$statusCode = 400; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
$header = 'Bearer'; |
152
|
|
|
if ($realm) { |
153
|
|
|
$header .= ' realm="' . $realm . '"'; |
154
|
|
|
} |
155
|
|
|
$header .= ' error="'.$e->getError().'"'; |
156
|
|
|
if($e->getErrorDescription()) { |
157
|
|
|
$header .= ' error_description="'.$e->getErrorDescription().'"'; |
158
|
|
|
} |
159
|
|
|
if($e->getErrorUri()) { |
160
|
|
|
$header .= ' error_uri="'.$e->getErrorUri().'"'; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
return new Response($statusCode, ['WWW-Authenticate' => $header]); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
$this->authenticatedRequest = new AuthenticatedRequest($request, $client, $resourceOwner, $accessToken->getScopes()); |
167
|
|
|
return null; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
public function addBearerAuthenticationMethod(BearerAuthenticationMethodInterface $method): self |
171
|
|
|
{ |
172
|
|
|
$this->bearerAuthenticationMethods[] = $method; |
173
|
|
|
|
174
|
|
|
return $this; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* @return AuthenticatedRequest|null |
179
|
|
|
*/ |
180
|
|
|
public function getAuthenticatedRequest(): ?AuthenticatedRequest |
181
|
|
|
{ |
182
|
|
|
return $this->authenticatedRequest; |
183
|
|
|
} |
184
|
|
|
} |
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.