ResourceServer::verifyRequest()   F
last analyzed

Complexity

Conditions 20
Paths 966

Size

Total Lines 156

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
nc 966
nop 3
dl 0
loc 156
rs 0.0377
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\ResourceServer\BearerAuthenticationMethods\FormEncodedBodyParameter;
17
use OAuth2\Roles\ResourceServer\BearerAuthenticationMethods\URIQueryParameter;
18
use OAuth2\Roles\ResourceServerInterface;
19
use OAuth2\ScopePolicy\ScopePolicyManager;
20
use OAuth2\Storages\StorageManager;
21
use Psr\Http\Message\ResponseInterface;
22
use Psr\Http\Message\ServerRequestInterface;
23
24
class ResourceServer implements ResourceServerInterface
25
{
26
    /**
27
     * @var StorageManager
28
     */
29
    private $storageManager;
30
31
    /**
32
     * @var ScopePolicyManager
33
     */
34
    private $scopePolicyManager;
35
36
    /**
37
     * @var BearerAuthenticationMethodInterface[]
38
     */
39
    private $bearerAuthenticationMethods = [];
40
41
    /**
42
     * @var AuthenticatedRequest|null
43
     */
44
    private $authenticatedRequest;
45
46
    public function __construct(StorageManager $storageManager,
47
                                ScopePolicyManager $scopePolicyManager)
48
    {
49
        $this->storageManager = $storageManager;
50
        $this->scopePolicyManager = $scopePolicyManager;
51
52
        $this->bearerAuthenticationMethods = [
53
            /**
54
             * @see https://tools.ietf.org/html/rfc6750#section-2
55
             * Clients MUST NOT use more than one method to transmit the token in each request.
56
             */
57
            new AuthorizationRequestHeaderField()
58
        ];
59
    }
60
61
    /**
62
     * @param ServerRequestInterface $request
63
     * @param null|string $realm
64
     * @param null|string $scope
65
     * @return null|ResponseInterface
66
     */
67
    public function verifyRequest(ServerRequestInterface $request, ?string $realm = null, ?string $scope = null): ?ResponseInterface
68
    {
69
        try {
70
            $bearerAuthenticationMethodUsed = null;
71
            foreach ($this->bearerAuthenticationMethods as $bearerAuthenticationMethod) {
72
                if ($bearerAuthenticationMethod->support($request)) {
73
                    /**
74
                     * @see https://tools.ietf.org/html/rfc6750#section-2
75
                     * Clients MUST NOT use more than one method to transmit the token in each request.
76
                     */
77
                    if ($bearerAuthenticationMethodUsed) {
78
                        throw new OAuthException('invalid_request',
79
                            'The request utilizes more than one mechanism for authenticating the client.',
80
                            'https://tools.ietf.org/html/rfc6749#section-3.2.1');
81
                    }
82
83
                    $bearerAuthenticationMethodUsed = $bearerAuthenticationMethod;
84
                }
85
            }
86
87
            /**
88
             * https://tools.ietf.org/html/rfc6750#section-3
89
             * If the protected resource request does not include authentication
90
             * credentials or does not contain an access token that enables access
91
             * to the protected resource, the resource server MUST include the HTTP
92
             * "WWW-Authenticate" response header field; it MAY include it in
93
             * response to other conditions as well.  The "WWW-Authenticate" header
94
             * field uses the framework defined by HTTP/1.1 [RFC2617].
95
             *
96
             * @see https://tools.ietf.org/html/rfc6750#section-3.1
97
             * If the request lacks any authentication information (e.g., the client
98
             * was unaware that authentication is necessary or attempted using an
99
             * unsupported authentication method), the resource server SHOULD NOT
100
             * include an error code or other error information.
101
             *
102
             * For example:
103
             *
104
             * HTTP/1.1 401 Unauthorized
105
             * WWW-Authenticate: Bearer realm="example"
106
             */
107
            if (!$bearerAuthenticationMethodUsed) {
108
                return new Response(401, ['WWW-Authenticate' => 'Bearer' . ($realm ? ' realm="example"' : '')]);
109
            }
110
111
            $token = $bearerAuthenticationMethodUsed->authenticate($request);
112
113
            if (!$token) {
114
                throw new OAuthException('invalid_request',
115
                    'The request is missing a required parameter, includes an unsupported parameter or parameter value',
116
                    'https://tools.ietf.org/html/rfc6750#section-3.1');
117
            }
118
119
            $accessTokenStorage = $this->storageManager->getAccessTokenStorage();
120
121
            if (!$accessToken = $accessTokenStorage->get($token)) {
122
                throw new OAuthException('invalid_token',
123
                    'The access token provided is invalid.',
124
                    'https://tools.ietf.org/html/rfc6750#section-3.1');
125
            }
126
127
            if ($accessTokenStorage->hasExpired($accessToken)) {
128
                throw new OAuthException('invalid_token',
129
                    'The access token provided is expired.',
130
                    'https://tools.ietf.org/html/rfc6750#section-3.1');
131
            }
132
133
            if (!$client = $this->storageManager->getClientStorage()->get($accessToken->getClientIdentifier())) {
134
                throw new OAuthException('invalid_token',
135
                    'The access token provided is invalid. Client not found.',
136
                    'https://tools.ietf.org/html/rfc6750#section-3.1');
137
            }
138
139
            $resourceOwner = null;
140
            if ($accessToken->getResourceOwnerIdentifier()) {
141
                if (!$resourceOwner = $this->storageManager->getResourceOwnerStorage()->get($accessToken->getResourceOwnerIdentifier())) {
142
                    throw new OAuthException('invalid_token',
143
                        'The access token provided is invalid. Resource owner not found.',
144
                        'https://tools.ietf.org/html/rfc6750#section-3.1');
145
                }
146
            }
147
148
            $requiredScopes = $this->scopePolicyManager->scopeStringToArray($scope);
149
            if (!empty($requiredScopes) && !empty(array_diff($requiredScopes, $accessToken->getScopes()))) {
150
                throw new OAuthException('insufficient_scope',
151
                    'The request requires higher privileges than provided by the access token.',
152
                    'https://tools.ietf.org/html/rfc6750#section-3.1');
153
            }
154
        } catch (OAuthException $e) {
155
            /**
156
             * @see https://tools.ietf.org/html/rfc6750#section-3
157
             * If the protected resource request included an access token and failed
158
             * authentication, the resource server SHOULD include the "error"
159
             * attribute to provide the client with the reason why the access
160
             * request was declined.  The parameter value is described in
161
             * Section 3.1.  In addition, the resource server MAY include the
162
             * "error_description" attribute to provide developers a human-readable
163
             * explanation that is not meant to be displayed to end-users.  It also
164
             * MAY include the "error_uri" attribute with an absolute URI
165
             * identifying a human-readable web page explaining the error.  The
166
             * "error", "error_description", and "error_uri" attributes MUST NOT
167
             * appear more than once.
168
             *
169
             * @see https://tools.ietf.org/html/rfc6750#section-3.1
170
             * When a request fails, the resource server responds using the
171
             * appropriate HTTP status code (typically, 400, 401, 403, or 405) and
172
             * includes one of the following error codes in the response:
173
             *
174
             * invalid_request
175
             * The request is missing a required parameter, includes an
176
             * unsupported parameter or parameter value, repeats the same
177
             * parameter, uses more than one method for including an access
178
             * token, or is otherwise malformed.  The resource server SHOULD
179
             * respond with the HTTP 400 (Bad Request) status code.
180
             *
181
             * invalid_token
182
             * The access token provided is expired, revoked, malformed, or
183
             * invalid for other reasons.  The resource SHOULD respond with
184
             * the HTTP 401 (Unauthorized) status code.  The client MAY
185
             * request a new access token and retry the protected resource
186
             * request.
187
             *
188
             * insufficient_scope
189
             * The request requires higher privileges than provided by the
190
             * access token.  The resource server SHOULD respond with the HTTP
191
             * 403 (Forbidden) status code and MAY include the "scope"
192
             * attribute with the scope necessary to access the protected
193
             * resource.
194
             */
195
            switch ($e->getError()) {
196
                case 'invalid_token':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
197
                    $statusCode = 401;
198
                    break;
199
                case 'insufficient_scope':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
200
                    $statusCode = 403;
201
                    break;
202
                default:
203
                    $statusCode = 400;
204
            }
205
206
            $header = 'Bearer';
207
            if ($realm) {
208
                $header .= ' realm="' . $realm . '"';
209
            }
210
            $header .= ' error="' . $e->getError() . '"';
211
            if ($e->getErrorDescription()) {
212
                $header .= ' error_description="' . $e->getErrorDescription() . '"';
213
            }
214
            if ($e->getErrorUri()) {
215
                $header .= ' error_uri="' . $e->getErrorUri() . '"';
216
            }
217
218
            return new Response($statusCode, ['WWW-Authenticate' => $header]);
219
        }
220
221
        $this->authenticatedRequest = new AuthenticatedRequest($request, $client, $resourceOwner, $accessToken);
222
        return null;
223
    }
224
225
    public function addBearerAuthenticationMethod(BearerAuthenticationMethodInterface $method): self
226
    {
227
        $this->bearerAuthenticationMethods[] = $method;
228
229
        return $this;
230
    }
231
232
    /**
233
     * @return AuthenticatedRequest|null
234
     */
235
    public function getAuthenticatedRequest(): ?AuthenticatedRequest
236
    {
237
        return $this->authenticatedRequest;
238
    }
239
}