AuthorizationGrant::setAuthorizationServer()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
/**
4
 * Platine OAuth2
5
 *
6
 * Platine OAuth2 is a library that implements the OAuth2 specification
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine OAuth2
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
declare(strict_types=1);
32
33
namespace Platine\OAuth2\Grant;
34
35
use Platine\Http\ResponseInterface;
36
use Platine\Http\ServerRequestInterface;
37
use Platine\OAuth2\AuthorizationServerInterface;
38
use Platine\OAuth2\Entity\Client;
39
use Platine\OAuth2\Entity\TokenOwnerInterface;
40
use Platine\OAuth2\Exception\OAuth2Exception;
41
use Platine\OAuth2\Response\OAuthRedirectResponse;
42
use Platine\OAuth2\Service\AccessTokenService;
43
use Platine\OAuth2\Service\AuthorizationCodeService;
44
use Platine\OAuth2\Service\RefreshTokenService;
45
46
/**
47
 * @class AuthorizationGrant
48
 * @package Platine\OAuth2\Grant
49
 */
50
class AuthorizationGrant extends BaseGrant implements AuthorizationServerAwareInterface
51
{
52
    public const GRANT_TYPE = 'authorization_code';
53
    public const GRANT_RESPONSE_TYPE = 'code';
54
55
    /**
56
     * The authorization server instance
57
     * @var AuthorizationServerInterface|null
58
     */
59
    protected ?AuthorizationServerInterface $authorizationServer = null;
60
61
    /**
62
     * Create new instance
63
     * @param AuthorizationCodeService $authorizationCodeService
64
     * @param AccessTokenService $accessTokenService
65
     * @param RefreshTokenService $refreshTokenService
66
     */
67
    public function __construct(
68
        protected AuthorizationCodeService $authorizationCodeService,
69
        protected AccessTokenService $accessTokenService,
70
        protected RefreshTokenService $refreshTokenService
71
    ) {
72
    }
73
74
     /**
75
     * {@inheritdoc}
76
     */
77
    public function createAuthorizationResponse(
78
        ServerRequestInterface $request,
79
        Client $client,
80
        ?TokenOwnerInterface $owner = null
81
    ): ResponseInterface {
82
        $queryParams = $request->getQueryParams();
83
84
        // We must validate some parameters first
85
        $responseType = $queryParams['response_type'] ?? null;
86
        if ($responseType !== self::GRANT_RESPONSE_TYPE) {
87
            throw OAuth2Exception::invalidRequest(sprintf(
88
                'The desired grant type must be "code", but "%s" was given',
89
                $responseType
90
            ));
91
        }
92
93
        // We try to fetch the redirect URI from query param as per spec,
94
        // and if none found, we just use the first redirect URI defined in the client
95
        $clientRedirectUris = $client->getRedirectUris();
96
        $redirectUri = $queryParams['redirect_uri'] ?? $clientRedirectUris[0];
97
98
        // If the redirect URI cannot be found in the list, we throw an error
99
        // as we don't want the user to be redirected to an unauthorized URL
100
        if ($client->hasRedirectUri($redirectUri) === false) {
101
            throw OAuth2Exception::invalidRequest('Redirect URI does not match the client registered one');
102
        }
103
104
        // Scope and state allow to perform additional validation
105
        $scope = $queryParams['scope'] ?? null;
106
        $state = $queryParams['state'] ?? null;
107
        $scopes = is_string($scope) ? explode(' ', $scope) : [];
108
        $authorizationCode = $this->authorizationCodeService->createToken(
109
            $redirectUri,
110
            $owner,
111
            $client,
112
            $scopes
113
        );
114
115
        $uri = http_build_query(array_filter([
116
            'code' => $authorizationCode->getToken(),
117
            'state' => $state,
118
        ]));
119
120
        return new OAuthRedirectResponse($redirectUri . '?' . $uri);
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function createTokenResponse(
127
        ServerRequestInterface $request,
128
        ?Client $client = null,
129
        ?TokenOwnerInterface $owner = null
130
    ): ResponseInterface {
131
        $postParams = (array) $request->getParsedBody();
132
        $code = $postParams['code'] ?? null;
133
134
        if ($code === null) {
135
            throw OAuth2Exception::invalidRequest('Could not find the authorization code in the request');
136
        }
137
138
        $authorizationCode = $this->authorizationCodeService->getToken($code);
139
        if ($authorizationCode === null || $authorizationCode->isExpired()) {
140
            throw OAuth2Exception::invalidGrant('Authorization code cannot be found or is expired');
141
        }
142
143
        $clientId = $postParams['client_id'] ?? null;
144
        if ($authorizationCode->getClient() !== null && $authorizationCode->getClient()->getId() !== $clientId) {
145
            throw OAuth2Exception::invalidRequest(
146
                'Authorization code\'s client does not match with the one that created the authorization code'
147
            );
148
        }
149
150
        // If owner is null, we reuse the same as the authorization code
151
        if ($owner === null) {
152
            $owner = $authorizationCode->getOwner();
153
        }
154
155
        $scopes = $authorizationCode->getScopes();
156
        $accessToken = $this->accessTokenService->createToken($owner, $client, $scopes);
157
158
        // Before generating a refresh token, we must make sure the
159
        //  authorization server supports this grant
160
        $refreshToken = null;
161
        if (
162
            $this->authorizationServer !== null &&
163
            $this->authorizationServer->hasGrant(RefreshTokenGrant::GRANT_TYPE)
164
        ) {
165
            $refreshToken = $this->refreshTokenService->createToken($owner, $client, $scopes);
166
        }
167
168
        return $this->generateTokenResponse($accessToken, $refreshToken);
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function setAuthorizationServer(
175
        AuthorizationServerInterface $authorizationServer
176
    ): void {
177
        $this->authorizationServer = $authorizationServer;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function allowPublicClients(): bool
184
    {
185
        return true;
186
    }
187
}
188