GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( c1b37b...10ae8a )
by François
27:12 queued 13:50
created

AccessToken::fromRefreshResponse()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 3
eloc 7
nc 4
nop 3
1
<?php
2
3
/**
4
 * Copyright (c) 2016, 2017 François Kooman <[email protected]>.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
namespace fkooman\OAuth\Client;
26
27
use DateInterval;
28
use DateTime;
29
use fkooman\OAuth\Client\Exception\AccessTokenException;
30
31
class AccessToken
32
{
33
    /** @var \DateTime */
34
    private $issuedAt;
35
36
    /** @var string */
37
    private $accessToken;
38
39
    /** @var string */
40
    private $tokenType;
41
42
    /** @var int|null */
43
    private $expiresIn = null;
44
45
    /** @var string|null */
46
    private $refreshToken = null;
47
48
    /** @var string|null */
49
    private $scope = null;
50
51
    /**
52
     * @param array $tokenData
53
     */
54
    private function __construct(array $tokenData)
55
    {
56
        $requiredKeys = ['issued_at', 'access_token', 'token_type'];
57
        foreach ($requiredKeys as $requiredKey) {
58
            if (!array_key_exists($requiredKey, $tokenData)) {
59
                throw new AccessTokenException(sprintf('missing key "%s"', $requiredKey));
60
            }
61
        }
62
63
        // set required keys
64
        $this->setIssuedAt($tokenData['issued_at']);
65
        $this->setAccessToken($tokenData['access_token']);
66
        $this->setTokenType($tokenData['token_type']);
67
68
        // set optional keys
69
        if (array_key_exists('expires_in', $tokenData)) {
70
            $this->setExpiresIn($tokenData['expires_in']);
71
        }
72
        if (array_key_exists('refresh_token', $tokenData)) {
73
            $this->setRefreshToken($tokenData['refresh_token']);
74
        }
75
        if (array_key_exists('scope', $tokenData)) {
76
            $this->setScope($tokenData['scope']);
77
        }
78
    }
79
80
    /**
81
     * @param \DateTime $dateTime
82
     * @param array     $tokenData
83
     * @param string    $scope
84
     */
85
    public static function fromCodeResponse(DateTime $dateTime, array $tokenData, $scope)
86
    {
87
        // if the scope was not part of the response, add the request scope,
88
        // because according to the RFC, if the scope is ommitted the requested
89
        // scope was granted!
90
        if (!array_key_exists('scope', $tokenData)) {
91
            $tokenData['scope'] = $scope;
92
        }
93
        // add the current DateTime as well to be able to figure out if the
94
        // token expired
95
        $tokenData['issued_at'] = $dateTime->format('Y-m-d H:i:s');
96
97
        return new self($tokenData);
98
    }
99
100
    /**
101
     * @param \DateTime   $dateTime
102
     * @param array       $tokenData
103
     * @param AccessToken $accessToken to steal the old scope and refresh_token from!
104
     */
105
    public static function fromRefreshResponse(DateTime $dateTime, array $tokenData, AccessToken $accessToken)
106
    {
107
        // if the scope is not part of the response, add the request scope,
108
        // because according to the RFC, if the scope is ommitted the requested
109
        // scope was granted!
110
        if (!array_key_exists('scope', $tokenData)) {
111
            $tokenData['scope'] = $accessToken->getScope();
112
        }
113
        // if the refresh_token is not part of the response, we wil reuse the
114
        // existing refresh_token for future refresh_token requests
115
        if (!array_key_exists('refresh_token', $tokenData)) {
116
            $tokenData['refresh_token'] = $accessToken->getRefreshToken();
117
        }
118
        // add the current DateTime as well to be able to figure out if the
119
        // token expired
120
        $tokenData['issued_at'] = $dateTime->format('Y-m-d H:i:s');
121
122
        return new self($tokenData);
123
    }
124
125
    /**
126
     * @param string $tokenString a JSON encoded AccessToken
127
     *
128
     * @return AccessToken
129
     */
130
    public static function fromStorage($tokenString)
131
    {
132
        $tokenData = json_decode($tokenString, true);
133 View Code Duplication
        if (is_null($tokenData) && JSON_ERROR_NONE !== json_last_error()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134
            $errorMsg = function_exists('json_last_error_msg') ? json_last_error_msg() : json_last_error();
135
            throw new AccessTokenException(sprintf('unable to decode JSON from storage: %s', $errorMsg));
136
        }
137
138
        return new self($tokenData);
139
    }
140
141
    /**
142
     * @return string a JSON encoded AccessToken
143
     */
144
    public function toStorage()
145
    {
146
        return json_encode(
147
            [
148
                'issued_at' => $this->issuedAt->format('Y-m-d H:i:s'),
149
                'access_token' => $this->getToken(),
150
                'token_type' => $this->getTokenType(),
151
                'expires_in' => $this->getExpiresIn(),
152
                'refresh_token' => $this->getRefreshToken(),
153
                'scope' => $this->getScope(),
154
            ]
155
        );
156
    }
157
158
    /**
159
     * @return string
160
     *
161
     * @see https://tools.ietf.org/html/rfc6749#section-5.1
162
     */
163
    public function getToken()
164
    {
165
        return $this->accessToken;
166
    }
167
168
    /**
169
     * @return string
170
     *
171
     * @see https://tools.ietf.org/html/rfc6749#section-7.1
172
     */
173
    public function getTokenType()
174
    {
175
        return $this->tokenType;
176
    }
177
178
    /**
179
     * @return int|null
180
     *
181
     * @see https://tools.ietf.org/html/rfc6749#section-5.1
182
     */
183
    public function getExpiresIn()
184
    {
185
        return $this->expiresIn;
186
    }
187
188
    /**
189
     * @return string|null the refresh token
190
     *
191
     * @see https://tools.ietf.org/html/rfc6749#section-1.5
192
     */
193
    public function getRefreshToken()
194
    {
195
        return $this->refreshToken;
196
    }
197
198
    /**
199
     * @return string|null
200
     *
201
     * @see https://tools.ietf.org/html/rfc6749#section-3.3
202
     */
203
    public function getScope()
204
    {
205
        return $this->scope;
206
    }
207
208
    /**
209
     * @param \DateTime $dateTime
210
     *
211
     * @return bool
212
     */
213
    public function isExpired(DateTime $dateTime)
214
    {
215
        if (is_null($this->getExpiresIn())) {
216
            // if no expiry was indicated, assume it is valid
217
            return false;
218
        }
219
220
        // check to see if issuedAt + expiresIn > provided DateTime
221
        $issuedAt = clone $this->issuedAt; // XXX do we need to clone here?
222
        $expiresAt = date_add($issuedAt, new DateInterval(sprintf('PT%dS', $this->getExpiresIn())));
223
224
        return $dateTime >= $expiresAt;
225
    }
226
227
    /**
228
     * @param string $issuedAt
229
     */
230
    private function setIssuedAt($issuedAt)
231
    {
232
        self::requireString('expires_at', $issuedAt);
233
        if (1 !== preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $issuedAt)) {
234
            throw new AccessTokenException('invalid "expires_at"');
235
        }
236
        $this->issuedAt = new DateTime($issuedAt);
237
    }
238
239
    /**
240
     * @param string $accessToken
241
     */
242
    private function setAccessToken($accessToken)
243
    {
244
        self::requireString('access_token', $accessToken);
245
        // access-token = 1*VSCHAR
246
        // VSCHAR       = %x20-7E
247
        if (1 !== preg_match('/^[\x20-\x7E]+$/', $accessToken)) {
248
            throw new AccessTokenException('invalid "access_token"');
249
        }
250
        $this->accessToken = $accessToken;
251
    }
252
253
    /**
254
     * @param string $tokenType
255
     */
256
    private function setTokenType($tokenType)
257
    {
258
        self::requireString('token_type', $tokenType);
259
        if ('bearer' !== $tokenType) {
260
            throw new AccessTokenException('unsupported "token_type"');
261
        }
262
        $this->tokenType = $tokenType;
263
    }
264
265
    /**
266
     * @param int|null $expiresIn
267
     */
268
    private function setExpiresIn($expiresIn)
269
    {
270
        if (!is_null($expiresIn)) {
271
            self::requireInt('expires_in', $expiresIn);
272
            if (0 >= $expiresIn) {
273
                throw new AccessTokenException('invalid "expires_in"');
274
            }
275
        }
276
        $this->expiresIn = $expiresIn;
277
    }
278
279
    /**
280
     * @param string|null $refreshToken
281
     */
282 View Code Duplication
    private function setRefreshToken($refreshToken)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
283
    {
284
        if (!is_null($refreshToken)) {
285
            self::requireString('refresh_token', $refreshToken);
286
            // refresh-token = 1*VSCHAR
287
            // VSCHAR        = %x20-7E
288
            if (1 !== preg_match('/^[\x20-\x7E]+$/', $refreshToken)) {
289
                throw new AccessTokenException('invalid "refresh_token"');
290
            }
291
        }
292
        $this->refreshToken = $refreshToken;
293
    }
294
295
    /**
296
     * @param string|null $scope
297
     */
298 View Code Duplication
    private function setScope($scope)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
299
    {
300
        if (!is_null($scope)) {
301
            self::requireString('scope', $scope);
302
            // scope       = scope-token *( SP scope-token )
303
            // scope-token = 1*NQCHAR
304
            // NQCHAR      = %x21 / %x23-5B / %x5D-7E
305
            foreach (explode(' ', $scope) as $scopeToken) {
306
                if (1 !== preg_match('/^[\x21\x23-\x5B\x5D-\x7E]+$/', $scopeToken)) {
307
                    throw new AccessTokenException('invalid "scope"');
308
                }
309
            }
310
        }
311
        $this->scope = $scope;
312
    }
313
314
    /**
315
     * @param string $k
316
     * @param string $v
317
     */
318
    private static function requireString($k, $v)
319
    {
320
        if (!is_string($v)) {
321
            throw new AccessTokenException(sprintf('"%s" must be string', $k));
322
        }
323
    }
324
325
    /**
326
     * @param string $k
327
     * @param int    $v
328
     */
329
    private static function requireInt($k, $v)
330
    {
331
        if (!is_int($v)) {
332
            throw new AccessTokenException(sprintf('"%s" must be int', $k));
333
        }
334
    }
335
}
336