Passed
Push — master ( 03ea2f...d61ccd )
by Alexandre
03:09
created

AuthorizationEndpoint::parseRequestData()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 1
dl 0
loc 10
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: Alexandre
5
 * Date: 18/02/2018
6
 * Time: 18:14
7
 */
8
9
namespace OAuth2\Endpoints;
10
11
12
use GuzzleHttp\Psr7\Response;
13
use OAuth2\Exceptions\InvalidRequestMethod;
14
use OAuth2\Exceptions\OAuthException;
15
use OAuth2\ResponseModes\ResponseModeInterface;
16
use OAuth2\Roles\ResourceOwnerInterface;
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use Psr\Http\Message\UriInterface;
20
21
/**
22
 * Class AuthorizationEndpoint
23
 * @package OAuth2\Endpoints
24
 *
25
 * @see https://tools.ietf.org/html/rfc6749#section-3.1
26
 * The authorization endpoint is used to interact with the resource
27
 * owner and obtain an authorization grant.  The authorization server
28
 * MUST first verify the identity of the resource owner.  The way in
29
 * which the authorization server authenticates the resource owner
30
 * (e.g., username and password login, session cookies) is beyond the
31
 * scope of this specification.
32
 *
33
 * The means through which the client obtains the location of the
34
 * authorization endpoint are beyond the scope of this specification,
35
 * but the location is typically provided in the service documentation.
36
 *
37
 * The endpoint URI MAY include an "application/x-www-form-urlencoded"
38
 * formatted (per Appendix B) query component ([RFC3986] Section 3.4),
39
 * which MUST be retained when adding additional query parameters.  The
40
 * endpoint URI MUST NOT include a fragment component.
41
 *
42
 * Since requests to the authorization endpoint result in user
43
 * authentication and the transmission of clear-text credentials (in the
44
 * HTTP response), the authorization server MUST require the use of TLS
45
 * as described in Section 1.6 when sending requests to the
46
 * authorization endpoint.
47
 *
48
 * The authorization server MUST support the use of the HTTP "GET"
49
 * method [RFC2616] for the authorization endpoint and MAY support the
50
 * use of the "POST" method as well.
51
 *
52
 * Parameters sent without a value MUST be treated as if they were
53
 * omitted from the request.  The authorization server MUST ignore
54
 * unrecognized request parameters.  Request and response parameters
55
 * MUST NOT be included more than once.
56
 */
57
class AuthorizationEndpoint implements EndpointInterface
58
{
59
    /**
60
     * @var ResourceOwnerInterface
61
     */
62
    private $resourceOwner;
63
    /**
64
     * @var AuthorizationRequestBuilder
65
     */
66
    private $authorizationRequestBuilder;
67
    /**
68
     * @var AuthorizationRequest|null
69
     */
70
    private $authorizationRequest = null;
71
72
    public function __construct(AuthorizationRequestBuilder $authorizationRequestBuilder,
73
                                ResourceOwnerInterface $resourceOwner)
74
    {
75
        $this->resourceOwner = $resourceOwner;
76
        $this->authorizationRequestBuilder = $authorizationRequestBuilder;
77
    }
78
79
    public function verifyRequest(ServerRequestInterface $request): ?ResponseInterface
80
    {
81
        try {
82
            $this->authorizationRequest = $this->authorizationRequestBuilder
83
                ->build($request, $this->resourceOwner, $redirectUri, $responseMode);
84
85
            if ($response = $this->verifyResourceOwner()) {
86
                return $response;
87
            }
88
        } catch (InvalidRequestMethod $e) {
89
            return new Response(404);
90
        } catch (OAuthException $e) {
91
            if (!$redirectUri instanceof UriInterface || !$responseMode instanceof ResponseModeInterface) {
92
                /**
93
                 * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1
94
                 * If the request fails due to a missing, invalid, or mismatching
95
                 * redirection URI, or if the client identifier is missing or invalid,
96
                 * the authorization server SHOULD inform the resource owner of the
97
                 * error and MUST NOT automatically redirect the user-agent to the
98
                 * invalid redirection URI.
99
                 */
100
                return new Response(400, ['content-type' => 'application/json'], $e->jsonSerialize());
101
            }
102
103
            /**
104
             * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1
105
             * If the resource owner denies the access request or if the request
106
             * fails for reasons other than a missing or invalid redirection URI,
107
             * the authorization server informs the client by adding the following
108
             * parameters to the query component of the redirection URI using the
109
             * "application/x-www-form-urlencoded" format, per Appendix B:
110
             *
111
             * error
112
             * REQUIRED.  A single ASCII [USASCII] error code
113
             *
114
             * error_description
115
             * OPTIONAL.  Human-readable ASCII [USASCII] text providing
116
             * additional information, used to assist the client developer in
117
             * understanding the error that occurred.
118
             * Values for the "error_description" parameter MUST NOT include
119
             * characters outside the set %x20-21 / %x23-5B / %x5D-7E.
120
             *
121
             * error_uri
122
             * OPTIONAL.  A URI identifying a human-readable web page with
123
             * information about the error, used to provide the client
124
             * developer with additional information about the error.
125
             * Values for the "error_uri" parameter MUST conform to the
126
             * URI-reference syntax and thus MUST NOT include characters
127
             * outside the set %x21 / %x23-5B / %x5D-7E.
128
             *
129
             * state
130
             * REQUIRED if a "state" parameter was present in the client
131
             * authorization request.  The exact value received from the
132
             * client.
133
             *
134
             */
135
            $responseData = [
136
                'error' => $e->getError()
137
            ];
138
            if ($e->getErrorDescription()) {
139
                $responseData['error_description'] = $e->getErrorDescription();
140
            }
141
            if ($e->getErrorUri()) {
142
                $responseData['error_uri'] = $e->getErrorUri();
143
            }
144
145
            if (!empty($this->state)) {
146
                $responseData['state'] = $this->state;
0 ignored issues
show
Bug Best Practice introduced by
The property state does not exist on OAuth2\Endpoints\AuthorizationEndpoint. Did you maybe forget to declare it?
Loading history...
147
            }
148
149
            return $responseMode->buildResponse($redirectUri, $responseData);
150
        }
151
152
        return null;
153
    }
154
155
    /**
156
     * @param ServerRequestInterface $request
157
     * @return ResponseInterface
158
     *
159
     * @see https://tools.ietf.org/html/rfc6749#section-4.1.1
160
     * The client constructs the request URI by adding the following
161
     * parameters to the query component of the authorization endpoint URI
162
     * using the "application/x-www-form-urlencoded" format, per Appendix B:
163
     *
164
     * response_type
165
     * REQUIRED.  Value MUST be set to [desired response type].
166
     *
167
     * client_id
168
     * REQUIRED.  The client identifier as described in Section 2.2.
169
     *
170
     * redirect_uri
171
     * OPTIONAL.  As described in Section 3.1.2.
172
     *
173
     * scope
174
     * OPTIONAL.  The scope of the access request as described by
175
     * Section 3.3.
176
     *
177
     * state
178
     * RECOMMENDED.  An opaque value used by the client to maintain
179
     * state between the request and callback.  The authorization
180
     * server includes this value when redirecting the user-agent back
181
     * to the client.  The parameter SHOULD be used for preventing
182
     * cross-site request forgery as described in Section 10.12.
183
     */
184
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
185
    {
186
        /**
187
         * @see https://tools.ietf.org/html/rfc6749#section-4.1.1
188
         * The authorization server validates the request to ensure that all
189
         * required parameters are present and valid.
190
         */
191
        if ($response = $this->verifyRequest($request)) {
192
            return $response;
193
        }
194
195
        if (is_null($this->authorizationRequest)) {
196
            throw new \LogicException();
197
        }
198
199
        try {
200
            /**
201
             * @see https://tools.ietf.org/html/rfc6749#section-4.1.1
202
             * If the request is valid,
203
             * the authorization server authenticates the resource owner and obtains
204
             * an authorization decision (by asking the resource owner or by
205
             * establishing approval via other means).
206
             */
207
            if ($response = $this->verifyConsent($this->authorizationRequest)) {
208
                return $response;
209
            }
210
211
            $responseData = $this->authorizationRequest->getResponseType()
212
                ->handleAuthorizationRequest($this->authorizationRequest);
213
        } catch (OAuthException $e) {
214
            /**
215
             * If the Authorization Server encounters any error, it MUST return an error response, per Section 3.1.2.6.
216
             */
217
            $responseData = [
218
                'error' => $e->getError()
219
            ];
220
            if ($e->getErrorDescription()) {
221
                $responseData['error_description'] = $e->getErrorDescription();
222
            }
223
            if ($e->getErrorUri()) {
224
                $responseData['error_uri'] = $e->getErrorUri();
225
            }
226
        }
227
228
        if (!empty($this->state)) {
229
            $responseData['state'] = $this->state;
0 ignored issues
show
Bug Best Practice introduced by
The property state does not exist on OAuth2\Endpoints\AuthorizationEndpoint. Did you maybe forget to declare it?
Loading history...
230
        }
231
232
        /**
233
         * @see https://tools.ietf.org/html/rfc6749#section-4.1.1
234
         * When a decision is established, the authorization server directs the
235
         * user-agent to the provided client redirection URI using an HTTP
236
         * redirection response, or by other means available to it via the
237
         * user-agent.
238
         */
239
        return $this->authorizationRequest->getResponseMode()
240
            ->buildResponse($this->authorizationRequest->getRedirectUri(), $responseData);
241
    }
242
243
244
    /**
245
     * @param AuthorizationRequest $authorizationRequest
246
     * @return null|ResponseInterface
247
     * @throws OAuthException
248
     */
249
    protected function verifyConsent(AuthorizationRequest $authorizationRequest): ?ResponseInterface
250
    {
251
        $consentGiven = $this->resourceOwner->hasGivenConsent($authorizationRequest->getClient(), $authorizationRequest->getScopes());
252
        if (is_null($consentGiven)) {
253
            return $this->resourceOwner->obtainConsent($authorizationRequest);
254
        }
255
256
        if (empty($consentGiven)) {
257
            throw new OAuthException('access_denied', 'The resource owner denied the request.',
258
                'https://tools.ietf.org/html/rfc6749#section-4.1');
259
        }
260
261
        return null;
262
    }
263
264
    protected function verifyResourceOwner(): ?ResponseInterface
265
    {
266
        if (!$this->resourceOwner->isAuthenticated()) {
267
            return $this->resourceOwner->authenticate();
268
        }
269
        return null;
270
    }
271
272
    /**
273
     * @return ResourceOwnerInterface
274
     */
275
    public function getResourceOwner(): ResourceOwnerInterface
276
    {
277
        return $this->resourceOwner;
278
    }
279
}