Completed
Pull Request — master (#1074)
by Luca
02:03
created

completeDeviceAuthorizationRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 2.0005

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 18
cts 19
cp 0.9474
rs 9.44
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2.0005
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\RefreshTokenRepositoryInterface;
19
use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface;
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 Psr\Http\Message\ServerRequestInterface;
25
26
/**
27
 * Device Code grant class.
28
 */
29
class DeviceCodeGrant extends AbstractGrant
30
{
31
    /**
32
     * @var DateInterval
33
     */
34
    private $deviceCodeTTL;
35
36
    /**
37
     * @var int
38
     */
39
    private $retryInterval;
40
41
    /**
42
     * @var string
43
     */
44
    private $verificationUri;
45
46
    /**
47
     * @param DeviceCodeRepositoryInterface $deviceCodeRepository
48
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
49
     * @param DateInterval $deviceCodeTTL
50
     * @param int $retryInterval
51
     */
52 9
    public function __construct(
53
        DeviceCodeRepositoryInterface $deviceCodeRepository,
54
        RefreshTokenRepositoryInterface $refreshTokenRepository,
55
        DateInterval $deviceCodeTTL,
56
        $retryInterval = 5
57
    ) {
58 9
        $this->setDeviceCodeRepository($deviceCodeRepository);
59 9
        $this->setRefreshTokenRepository($refreshTokenRepository);
60
61 9
        $this->refreshTokenTTL = new DateInterval('P1M');
62
63 9
        $this->deviceCodeTTL = $deviceCodeTTL;
64 9
        $this->retryInterval = $retryInterval;
65 9
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 1
    public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request)
71
    {
72 1
        return true;
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 3
    public function validateDeviceAuthorizationRequest(ServerRequestInterface $request)
79
    {
80 3
        $clientId = $this->getRequestParameter(
81 3
            'client_id',
82 3
            $request,
83 3
            $this->getServerParameter('PHP_AUTH_USER', $request)
84
        );
85
86 3
        if ($clientId === null) {
87 1
            throw OAuthServerException::invalidRequest('client_id');
88
        }
89
90 2
        $client = $this->getClientEntityOrFail($clientId, $request);
91
92 1
        $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
93
94 1
        $deviceAuthorizationRequest = new DeviceAuthorizationRequest();
95 1
        $deviceAuthorizationRequest->setGrantTypeId($this->getIdentifier());
96 1
        $deviceAuthorizationRequest->setClient($client);
97 1
        $deviceAuthorizationRequest->setScopes($scopes);
98
99 1
        return $deviceAuthorizationRequest;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 1
    public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest)
106
    {
107 1
        $deviceCode = $this->issueDeviceCode(
108 1
            $this->deviceCodeTTL,
109 1
            $deviceRequest->getClient(),
110 1
            $this->verificationUri,
111 1
            $deviceRequest->getScopes()
112
        );
113
114
        $payload = [
115 1
            'client_id' => $deviceCode->getClient()->getIdentifier(),
116 1
            'device_code_id' => $deviceCode->getIdentifier(),
117 1
            'scopes' => $deviceCode->getScopes(),
118 1
            'user_code' => $deviceCode->getUserCode(),
119 1
            'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(),
120 1
            'verification_uri' => $deviceCode->getVerificationUri()
121
        ];
122
123 1
        $jsonPayload = \json_encode($payload);
124
125 1
        if ($jsonPayload === false) {
126
            throw new LogicException('An error was encountered when JSON encoding the authorization request response');
127
        }
128
129 1
        $response = new DeviceCodeResponse();
130 1
        $response->setDeviceCode($deviceCode);
0 ignored issues
show
Bug introduced by
It seems like $deviceCode defined by $this->issueDeviceCode($...ceRequest->getScopes()) on line 107 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...
131 1
        $response->setPayload($this->encrypt($jsonPayload));
132
133 1
        return $response;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139 3
    public function respondToAccessTokenRequest(
140
        ServerRequestInterface $request,
141
        ResponseTypeInterface $responseType,
142
        DateInterval $accessTokenTTL
143
    ) {
144
        // Validate request
145 3
        $client = $this->validateClient($request);
146 2
        $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
147 2
        $deviceCode = $this->validateDeviceCode($request, $client);
148
149
        // TODO: if the request is too fast, respond with slow down
150
151
152
        // if device code has no user associated, respond with pending
153 1
        if (\is_null($deviceCode->getUserIdentifier())) {
154
            throw OAuthServerException::authorizationPending();
155
        }
156
157
        // Finalize the requested scopes
158 1
        $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $deviceCode->getUserIdentifier());
159
160
        // Issue and persist new access token
161 1
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $deviceCode->getUserIdentifier(), $finalizedScopes);
162 1
        $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
163 1
        $responseType->setAccessToken($accessToken);
0 ignored issues
show
Bug introduced by
It seems like $accessToken defined by $this->issueAccessToken(...er(), $finalizedScopes) on line 161 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...
164
165
        // Issue and persist new refresh token if given
166 1
        $refreshToken = $this->issueRefreshToken($accessToken);
167
168 1
        if ($refreshToken !== null) {
169 1
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
170 1
            $responseType->setRefreshToken($refreshToken);
171
        }
172
173 1
        $this->deviceCodeRepository->revokeDeviceCode($deviceCode->getIdentifier());
174
175 1
        return $responseType;
176
    }
177
178
    /**
179
     * @param ServerRequestInterface $request
180
     * @param ClientEntityInterface  $client
181
     *
182
     * @throws OAuthServerException
183
     *
184
     * @return DeviceCodeEntityInterface
185
     */
186 2
    protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client)
187
    {
188 2
        $encryptedDeviceCode = $this->getRequestParameter('device_code', $request);
189
190 2
        if (\is_null($encryptedDeviceCode)) {
191 1
            throw OAuthServerException::invalidRequest('device_code');
192
        }
193
194
        try {
195 1
            $deviceCodePayload = \json_decode($this->decrypt($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 1
                throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client');
211
            }
212
213
        } catch (\LogicException $e) {
214
            throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e);
215
        }
216
217 1
        $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode(
218 1
            $deviceCodePayload->device_code_id,
219 1
            $this->getIdentifier(),
220 1
            $client
221
        );
222
223 1
        if ($deviceCode instanceof DeviceCodeEntityInterface === false) {
224
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
225
226
            throw OAuthServerException::invalidGrant();
227
        }
228
229 1
        return $deviceCode;
230
    }
231
232
    /**
233
     * Set the verification uri
234
     *
235
     * @param $verificationUri
236
     */
237
    public function setVerificationUri($verificationUri)
238
    {
239
        $this->verificationUri = $verificationUri;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245 4
    public function getIdentifier()
246
    {
247 4
        return 'device_code';
248
    }
249
}
250