Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/oauthserver/PhabricatorOAuthServer.php
12241 views
1
<?php
2
3
/**
4
* Implements core OAuth 2.0 Server logic.
5
*
6
* This class should be used behind business logic that parses input to
7
* determine pertinent @{class:PhabricatorUser} $user,
8
* @{class:PhabricatorOAuthServerClient} $client(s),
9
* @{class:PhabricatorOAuthServerAuthorizationCode} $code(s), and.
10
* @{class:PhabricatorOAuthServerAccessToken} $token(s).
11
*
12
* For an OAuth 2.0 server, there are two main steps:
13
*
14
* 1) Authorization - the user authorizes a given client to access the data
15
* the OAuth 2.0 server protects. Once this is achieved / if it has
16
* been achived already, the OAuth server sends the client an authorization
17
* code.
18
* 2) Access Token - the client should send the authorization code received in
19
* step 1 along with its id and secret to the OAuth server to receive an
20
* access token. This access token can later be used to access Phabricator
21
* data on behalf of the user.
22
*
23
* @task auth Authorizing @{class:PhabricatorOAuthServerClient}s and
24
* generating @{class:PhabricatorOAuthServerAuthorizationCode}s
25
* @task token Validating @{class:PhabricatorOAuthServerAuthorizationCode}s
26
* and generating @{class:PhabricatorOAuthServerAccessToken}s
27
* @task internal Internals
28
*/
29
final class PhabricatorOAuthServer extends Phobject {
30
31
const AUTHORIZATION_CODE_TIMEOUT = 300;
32
33
private $user;
34
private $client;
35
36
private function getUser() {
37
if (!$this->user) {
38
throw new PhutilInvalidStateException('setUser');
39
}
40
return $this->user;
41
}
42
43
public function setUser(PhabricatorUser $user) {
44
$this->user = $user;
45
return $this;
46
}
47
48
private function getClient() {
49
if (!$this->client) {
50
throw new PhutilInvalidStateException('setClient');
51
}
52
return $this->client;
53
}
54
55
public function setClient(PhabricatorOAuthServerClient $client) {
56
$this->client = $client;
57
return $this;
58
}
59
60
/**
61
* @task auth
62
* @return tuple <bool hasAuthorized, ClientAuthorization or null>
63
*/
64
public function userHasAuthorizedClient(array $scope) {
65
66
$authorization = id(new PhabricatorOAuthClientAuthorization())
67
->loadOneWhere(
68
'userPHID = %s AND clientPHID = %s',
69
$this->getUser()->getPHID(),
70
$this->getClient()->getPHID());
71
if (empty($authorization)) {
72
return array(false, null);
73
}
74
75
if ($scope) {
76
$missing_scope = array_diff_key($scope, $authorization->getScope());
77
} else {
78
$missing_scope = false;
79
}
80
81
if ($missing_scope) {
82
return array(false, $authorization);
83
}
84
85
return array(true, $authorization);
86
}
87
88
/**
89
* @task auth
90
*/
91
public function authorizeClient(array $scope) {
92
$authorization = new PhabricatorOAuthClientAuthorization();
93
$authorization->setUserPHID($this->getUser()->getPHID());
94
$authorization->setClientPHID($this->getClient()->getPHID());
95
$authorization->setScope($scope);
96
$authorization->save();
97
98
return $authorization;
99
}
100
101
/**
102
* @task auth
103
*/
104
public function generateAuthorizationCode(PhutilURI $redirect_uri) {
105
106
$code = Filesystem::readRandomCharacters(32);
107
$client = $this->getClient();
108
109
$authorization_code = new PhabricatorOAuthServerAuthorizationCode();
110
$authorization_code->setCode($code);
111
$authorization_code->setClientPHID($client->getPHID());
112
$authorization_code->setClientSecret($client->getSecret());
113
$authorization_code->setUserPHID($this->getUser()->getPHID());
114
$authorization_code->setRedirectURI((string)$redirect_uri);
115
$authorization_code->save();
116
117
return $authorization_code;
118
}
119
120
/**
121
* @task token
122
*/
123
public function generateAccessToken() {
124
125
$token = Filesystem::readRandomCharacters(32);
126
127
$access_token = new PhabricatorOAuthServerAccessToken();
128
$access_token->setToken($token);
129
$access_token->setUserPHID($this->getUser()->getPHID());
130
$access_token->setClientPHID($this->getClient()->getPHID());
131
$access_token->save();
132
133
return $access_token;
134
}
135
136
/**
137
* @task token
138
*/
139
public function validateAuthorizationCode(
140
PhabricatorOAuthServerAuthorizationCode $test_code,
141
PhabricatorOAuthServerAuthorizationCode $valid_code) {
142
143
// check that all the meta data matches
144
if ($test_code->getClientPHID() != $valid_code->getClientPHID()) {
145
return false;
146
}
147
if ($test_code->getClientSecret() != $valid_code->getClientSecret()) {
148
return false;
149
}
150
151
// check that the authorization code hasn't timed out
152
$created_time = $test_code->getDateCreated();
153
$must_be_used_by = $created_time + self::AUTHORIZATION_CODE_TIMEOUT;
154
return (time() < $must_be_used_by);
155
}
156
157
/**
158
* @task token
159
*/
160
public function authorizeToken(
161
PhabricatorOAuthServerAccessToken $token) {
162
163
$user_phid = $token->getUserPHID();
164
$client_phid = $token->getClientPHID();
165
166
$authorization = id(new PhabricatorOAuthClientAuthorizationQuery())
167
->setViewer(PhabricatorUser::getOmnipotentUser())
168
->withUserPHIDs(array($user_phid))
169
->withClientPHIDs(array($client_phid))
170
->executeOne();
171
if (!$authorization) {
172
return null;
173
}
174
175
$application = $authorization->getClient();
176
if ($application->getIsDisabled()) {
177
return null;
178
}
179
180
return $authorization;
181
}
182
183
public function validateRedirectURI($uri) {
184
try {
185
$this->assertValidRedirectURI($uri);
186
return true;
187
} catch (Exception $ex) {
188
return false;
189
}
190
}
191
192
/**
193
* See http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2
194
* for details on what makes a given redirect URI "valid".
195
*/
196
public function assertValidRedirectURI($raw_uri) {
197
// This covers basics like reasonable formatting and the existence of a
198
// protocol.
199
PhabricatorEnv::requireValidRemoteURIForLink($raw_uri);
200
201
$uri = new PhutilURI($raw_uri);
202
203
$fragment = $uri->getFragment();
204
if (strlen($fragment)) {
205
throw new Exception(
206
pht(
207
'OAuth application redirect URIs must not contain URI '.
208
'fragments, but the URI "%s" has a fragment ("%s").',
209
$raw_uri,
210
$fragment));
211
}
212
213
$protocol = $uri->getProtocol();
214
switch ($protocol) {
215
case 'http':
216
case 'https':
217
break;
218
default:
219
throw new Exception(
220
pht(
221
'OAuth application redirect URIs must only use the "http" or '.
222
'"https" protocols, but the URI "%s" uses the "%s" protocol.',
223
$raw_uri,
224
$protocol));
225
}
226
}
227
228
/**
229
* If there's a URI specified in an OAuth request, it must be validated in
230
* its own right. Further, it must have the same domain, the same path, the
231
* same port, and (at least) the same query parameters as the primary URI.
232
*/
233
public function validateSecondaryRedirectURI(
234
PhutilURI $secondary_uri,
235
PhutilURI $primary_uri) {
236
237
// The secondary URI must be valid.
238
if (!$this->validateRedirectURI($secondary_uri)) {
239
return false;
240
}
241
242
// Both URIs must point at the same domain.
243
if ($secondary_uri->getDomain() != $primary_uri->getDomain()) {
244
return false;
245
}
246
247
// Both URIs must have the same path
248
if ($secondary_uri->getPath() != $primary_uri->getPath()) {
249
return false;
250
}
251
252
// Both URIs must have the same port
253
if ($secondary_uri->getPort() != $primary_uri->getPort()) {
254
return false;
255
}
256
257
// Any query parameters present in the first URI must be exactly present
258
// in the second URI.
259
$need_params = $primary_uri->getQueryParamsAsMap();
260
$have_params = $secondary_uri->getQueryParamsAsMap();
261
262
foreach ($need_params as $key => $value) {
263
if (!array_key_exists($key, $have_params)) {
264
return false;
265
}
266
if ((string)$have_params[$key] != (string)$value) {
267
return false;
268
}
269
}
270
271
// If the first URI is HTTPS, the second URI must also be HTTPS. This
272
// defuses an attack where a third party with control over the network
273
// tricks you into using HTTP to authenticate over a link which is supposed
274
// to be HTTPS only and sniffs all your token cookies.
275
if (strtolower($primary_uri->getProtocol()) == 'https') {
276
if (strtolower($secondary_uri->getProtocol()) != 'https') {
277
return false;
278
}
279
}
280
281
return true;
282
}
283
284
}
285
286