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 ( 5d6c38...ea6ba9 )
by François
03:55
created

OAuthModule::setExpiresIn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 *  Copyright (C) 2016 SURFnet.
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as
7
 *  published by the Free Software Foundation, either version 3 of the
8
 *  License, or (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace fkooman\RemoteStorage\OAuth;
20
21
use DateInterval;
22
use DateTime;
23
use fkooman\RemoteStorage\Http\Exception\HttpException;
24
use fkooman\RemoteStorage\Http\HtmlResponse;
25
use fkooman\RemoteStorage\Http\RedirectResponse;
26
use fkooman\RemoteStorage\Http\Request;
27
use fkooman\RemoteStorage\RandomInterface;
28
use fkooman\RemoteStorage\TplInterface;
29
30
class OAuthModule
31
{
32
    /** @var \fkooman\RemoteStorage\TplInterface */
33
    private $tpl;
34
35
    /** @var \fkooman\RemoteStorage\RandomInterface */
36
    private $random;
37
38
    /** @var TokenStorage */
39
    private $tokenStorage;
40
41
    /** @var \DateTime */
42
    private $dateTime;
43
44
    /** @var int */
45
    private $expiresIn = 7776000;   /* 90 days */
46
47
    public function __construct(TplInterface $tpl, RandomInterface $random, TokenStorage $tokenStorage, DateTime $dateTime = null)
48
    {
49
        $this->tpl = $tpl;
50
        $this->random = $random;
51
        $this->tokenStorage = $tokenStorage;
52
        if (is_null($dateTime)) {
53
            $dateTime = new DateTime();
54
        }
55
        $this->dateTime = $dateTime;
56
    }
57
58
    /**
59
     * @param int $expiresIn
60
     */
61
    public function setExpiresIn($expiresIn)
62
    {
63
        $this->expiresIn = (int) $expiresIn;
64
    }
65
66
    public function getAuthorize(Request $request, $userId)
0 ignored issues
show
Unused Code introduced by
The parameter $userId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
67
    {
68
        $this->validateRequest($request);
69
        $this->validateClient($request);
70
71
        // ask for approving this client/scope
72
        return new HtmlResponse(
73
            $this->tpl->render(
74
                'authorizeOAuthClient',
75
                [
76
                    'client_id' => $request->getQueryParameter('client_id'),
77
                    'scope' => $request->getQueryParameter('scope'),
78
                    'redirect_uri' => $request->getQueryParameter('redirect_uri'),
79
                ]
80
            )
81
        );
82
    }
83
84
    public function postAuthorize(Request $request, $userId)
85
    {
86
        $this->validateRequest($request);
87
        $this->validateClient($request);
88
89
        // state is OPTIONAL in remoteStorage specification
90
        $state = $request->getQueryParameter('state', false, null);
91
        $returnUriPattern = '%s#%s';
92
93
        if ('no' === $request->getPostParameter('approve')) {
94
            $redirectParameters = [
95
                'error' => 'access_denied',
96
                'error_description' => 'user refused authorization',
97
            ];
98
            if (!is_null($state)) {
99
                $redirectParameters['state'] = $state;
100
            }
101
            $redirectQuery = http_build_query($redirectParameters);
102
103
            $redirectUri = sprintf($returnUriPattern, $request->getQueryParameter('redirect_uri'), $redirectQuery);
104
105
            return new RedirectResponse($redirectUri, 302);
106
        }
107
108
        $accessToken = $this->getAccessToken(
109
            $userId,
110
            $request->getQueryParameter('client_id'),
111
            $request->getQueryParameter('scope')
112
        );
113
114
        // add access_token, expires_in (and optionally state) to redirect_uri
115
        $redirectParameters = [
116
            'access_token' => $accessToken,
117
            'expires_in' => $this->expiresIn,
118
        ];
119
        if (!is_null($state)) {
120
            $redirectParameters['state'] = $state;
121
        }
122
        $redirectQuery = http_build_query($redirectParameters);
123
        $redirectUri = sprintf($returnUriPattern, $request->getQueryParameter('redirect_uri'), $redirectQuery);
124
125
        return new RedirectResponse($redirectUri, 302);
126
    }
127
128
    private function getAccessToken($userId, $clientId, $scope)
129
    {
130
        $existingToken = $this->tokenStorage->getExistingToken(
131
            $userId,
132
            $clientId,
133
            $scope
134
        );
135
136
        if (false !== $existingToken && $this->dateTime < new DateTime($existingToken['expires_at'])) {
137
            // if the user already has an access_token for this client and
138
            // scope, reuse it
139
            $accessTokenKey = $existingToken['access_token_key'];
140
            $accessToken = $existingToken['access_token'];
141
        } else {
142
            // generate a new one
143
            $accessTokenKey = $this->random->get(16);
144
            $accessToken = $this->random->get(16);
145
            $expiresAt = date_add(clone $this->dateTime, new DateInterval(sprintf('PT%dS', $this->expiresIn)));
146
147
            // store it
148
            $this->tokenStorage->store(
149
                $userId,
150
                $accessTokenKey,
151
                $accessToken,
152
                $clientId,
153
                $scope,
154
                $expiresAt
155
            );
156
        }
157
158
        return sprintf('%s.%s', $accessTokenKey, $accessToken);
159
    }
160
161
    private function validateRequest(Request $request)
162
    {
163
        // we enforce that all parameter are set, nothing is "OPTIONAL"
164
        $clientId = $request->getQueryParameter('client_id');
165
        if (1 !== preg_match('/^[\x20-\x7E]+$/', $clientId)) {
166
            throw new HttpException('invalid client_id', 400);
167
        }
168
169
        // XXX we also should enforce HTTPS
170
        $redirectUri = $request->getQueryParameter('redirect_uri');
171 View Code Duplication
        if (false === filter_var($redirectUri, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED)) {
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...
172
            throw new HttpException('invalid redirect_uri', 400);
173
        }
174
        if (false !== strpos($redirectUri, '?')) {
175
            throw new HttpException('invalid redirect_uri', 400);
176
        }
177
        $responseType = $request->getQueryParameter('response_type');
178
        if ('token' !== $responseType) {
179
            throw new HttpException('invalid response_type', 400);
180
        }
181
        // XXX make sure this regexp/code is actually correct!
182
        $scope = $request->getQueryParameter('scope');
183
        $scopeTokens = explode(' ', $scope);
184
        foreach ($scopeTokens as $scopeToken) {
185
            if (1 !== preg_match('/^[\x21\x23-\x5B\x5D-\x7E]+$/', $scopeToken)) {
186
                throw new HttpException('invalid scope', 400);
187
            }
188
        }
189
190
        // state is OPTIONAL in remoteStorage
191
        $state = $request->getQueryParameter('state', false, null);
192
        if (!is_null($state)) {
193
            if (1 !== preg_match('/^[\x20-\x7E]+$/', $state)) {
194
                throw new HttpException('invalid state', 400);
195
            }
196
        }
197
    }
198
199
    private function validateClient(Request $request)
200
    {
201
        $clientId = $request->getQueryParameter('client_id');
202
        $redirectUri = $request->getQueryParameter('redirect_uri');
203
204
        // redirectUri has to start with clientId (or be equal)
205
        if (0 !== strpos($redirectUri, $clientId)) {
206
            throw new HttpException('"redirect_uri" does not match "client_id"', 400);
207
        }
208
    }
209
}
210