Completed
Pull Request — master (#1074)
by Luca
01:57 queued 28s
created

DeviceCodeGrant::setVerificationUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * OAuth 2.0 Device Code grant.
4
 *
5
 * @author      Alex Bilbie <[email protected]>
6
 * @copyright   Copyright (c) Alex Bilbie
7
 * @license     http://mit-license.org/
8
 *
9
 * @link        https://github.com/thephpleague/oauth2-server
10
 */
11
12
namespace League\OAuth2\Server\Grant;
13
14
use DateInterval;
15
use League\OAuth2\Server\Entities\ClientEntityInterface;
16
use League\OAuth2\Server\Entities\DeviceCodeEntityInterface;
17
use League\OAuth2\Server\Exception\OAuthServerException;
18
use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface;
19
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
20
use League\OAuth2\Server\RequestEvent;
21
use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest;
22
use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse;
23
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
24
use LogicException;
25
use Psr\Http\Message\ServerRequestInterface;
26
27
/**
28
 * Device Code grant class.
29
 */
30
class DeviceCodeGrant extends AbstractGrant
31
{
32
    /**
33
     * @var DateInterval
34
     */
35
    private $deviceCodeTTL;
36
37
    /**
38
     * @var int
39
     */
40
    private $retryInterval;
41
42
    /**
43
     * @var string
44
     */
45
    private $verificationUri;
46
47
    /**
48
     * @param DeviceCodeRepositoryInterface   $deviceCodeRepository
49
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
50
     * @param DateInterval                    $deviceCodeTTL
51
     * @param int                             $retryInterval
52
     */
53 9
    public function __construct(
54
        DeviceCodeRepositoryInterface $deviceCodeRepository,
55
        RefreshTokenRepositoryInterface $refreshTokenRepository,
56
        DateInterval $deviceCodeTTL,
57
        $retryInterval = 5
58
    ) {
59 9
        $this->setDeviceCodeRepository($deviceCodeRepository);
60 9
        $this->setRefreshTokenRepository($refreshTokenRepository);
61
62 9
        $this->refreshTokenTTL = new DateInterval('P1M');
63
64 9
        $this->deviceCodeTTL = $deviceCodeTTL;
65 9
        $this->retryInterval = $retryInterval;
66 9
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 1
    public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request)
72
    {
73 1
        return true;
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79 3
    public function validateDeviceAuthorizationRequest(ServerRequestInterface $request)
80
    {
81 3
        $clientId = $this->getRequestParameter(
82 3
            'client_id',
83 3
            $request,
84 3
            $this->getServerParameter('PHP_AUTH_USER', $request)
85
        );
86
87 3
        if ($clientId === null) {
88 1
            throw OAuthServerException::invalidRequest('client_id');
89
        }
90
91 2
        $client = $this->getClientEntityOrFail($clientId, $request);
92
93 1
        $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
94
95 1
        $deviceAuthorizationRequest = new DeviceAuthorizationRequest();
96 1
        $deviceAuthorizationRequest->setGrantTypeId($this->getIdentifier());
97 1
        $deviceAuthorizationRequest->setClient($client);
98 1
        $deviceAuthorizationRequest->setScopes($scopes);
99
100 1
        return $deviceAuthorizationRequest;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 1
    public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest)
107
    {
108 1
        $deviceCode = $this->issueDeviceCode(
109 1
            $this->deviceCodeTTL,
110 1
            $deviceRequest->getClient(),
111 1
            $this->verificationUri,
112 1
            $deviceRequest->getScopes()
113
        );
114
115
        $payload = [
116 1
            'client_id' => $deviceCode->getClient()->getIdentifier(),
117 1
            'device_code_id' => $deviceCode->getIdentifier(),
118 1
            'scopes' => $deviceCode->getScopes(),
119 1
            'user_code' => $deviceCode->getUserCode(),
120 1
            'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(),
121 1
            'verification_uri' => $deviceCode->getVerificationUri(),
122
        ];
123
124 1
        $jsonPayload = \json_encode($payload);
125
126 1
        if ($jsonPayload === false) {
127
            throw new LogicException('An error was encountered when JSON encoding the authorization request response');
128
        }
129
130 1
        $response = new DeviceCodeResponse();
131 1
        $response->setDeviceCode($deviceCode);
0 ignored issues
show
Bug introduced by
It seems like $deviceCode defined by $this->issueDeviceCode($...ceRequest->getScopes()) on line 108 can be null; however, League\OAuth2\Server\Res...sponse::setDeviceCode() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
132 1
        $response->setPayload($this->encrypt($jsonPayload));
133
134 1
        return $response;
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140 3
    public function respondToAccessTokenRequest(
141
        ServerRequestInterface $request,
142
        ResponseTypeInterface $responseType,
143
        DateInterval $accessTokenTTL
144
    ) {
145
        // Validate request
146 3
        $client = $this->validateClient($request);
147 2
        $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
148 2
        $deviceCode = $this->validateDeviceCode($request, $client);
149
150
        // TODO: if the request is too fast, respond with slow down
151
152
153
        // if device code has no user associated, respond with pending
154 1
        if (\is_null($deviceCode->getUserIdentifier())) {
155
            throw OAuthServerException::authorizationPending();
156
        }
157
158
        // Finalize the requested scopes
159 1
        $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, (string) $deviceCode->getUserIdentifier());
160
161
        // Issue and persist new access token
162 1
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, (string) $deviceCode->getUserIdentifier(), $finalizedScopes);
163 1
        $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
164 1
        $responseType->setAccessToken($accessToken);
0 ignored issues
show
Bug introduced by
It seems like $accessToken defined by $this->issueAccessToken(...er(), $finalizedScopes) on line 162 can be null; however, League\OAuth2\Server\Res...rface::setAccessToken() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
165
166
        // Issue and persist new refresh token if given
167 1
        $refreshToken = $this->issueRefreshToken($accessToken);
168
169 1
        if ($refreshToken !== null) {
170 1
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
171 1
            $responseType->setRefreshToken($refreshToken);
172
        }
173
174 1
        $this->deviceCodeRepository->revokeDeviceCode($deviceCode->getIdentifier());
175
176 1
        return $responseType;
177
    }
178
179
    /**
180
     * @param ServerRequestInterface $request
181
     * @param ClientEntityInterface  $client
182
     *
183
     * @throws OAuthServerException
184
     *
185
     * @return DeviceCodeEntityInterface
186
     */
187 2
    protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client)
188
    {
189 2
        $encryptedDeviceCode = $this->getRequestParameter('device_code', $request);
190
191 2
        if (\is_null($encryptedDeviceCode)) {
192 1
            throw OAuthServerException::invalidRequest('device_code');
193
        }
194
195 1
        $deviceCodePayload = $this->decodeDeviceCode($encryptedDeviceCode);
196
197 1
        if (!\property_exists($deviceCodePayload, 'device_code_id')) {
198
            throw OAuthServerException::invalidRequest('device_code', 'Device code malformed');
199
        }
200
201 1
        if (\time() > $deviceCodePayload->expire_time) {
202
            throw OAuthServerException::expiredToken('device_code');
203
        }
204
205 1
        if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCodePayload->device_code_id) === true) {
206
            throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked');
207
        }
208
209 1
        if ($deviceCodePayload->client_id !== $client->getIdentifier()) {
210
            throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client');
211
        }
212
213 1
        $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode(
214 1
            $deviceCodePayload->device_code_id,
215 1
            $this->getIdentifier(),
216 1
            $client
217
        );
218
219 1
        if ($deviceCode instanceof DeviceCodeEntityInterface === false) {
220
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
221
222
            throw OAuthServerException::invalidGrant();
223
        }
224
225 1
        return $deviceCode;
226
    }
227
228
    /**
229
     * @param string $encryptedDeviceCode
230
     *
231
     * @throws OAuthServerException
232
     *
233
     * @return \stdClass
234
     */
235 1
    protected function decodeDeviceCode($encryptedDeviceCode)
236
    {
237
        try {
238 1
            return \json_decode($this->decrypt($encryptedDeviceCode));
239
        } catch (LogicException $e) {
240
            throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e);
241
        }
242
    }
243
244
    /**
245
     * Set the verification uri
246
     *
247
     * @param string $verificationUri
248
     */
249
    public function setVerificationUri($verificationUri)
250
    {
251
        $this->verificationUri = $verificationUri;
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257 4
    public function getIdentifier()
258
    {
259 4
        return 'device_code';
260
    }
261
}
262