Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php
12256 views
1
<?php
2
3
abstract class PhabricatorOAuth1AuthProvider
4
extends PhabricatorOAuthAuthProvider {
5
6
protected $adapter;
7
8
const PROPERTY_CONSUMER_KEY = 'oauth1:consumer:key';
9
const PROPERTY_CONSUMER_SECRET = 'oauth1:consumer:secret';
10
const PROPERTY_PRIVATE_KEY = 'oauth1:private:key';
11
12
protected function getIDKey() {
13
return self::PROPERTY_CONSUMER_KEY;
14
}
15
16
protected function getSecretKey() {
17
return self::PROPERTY_CONSUMER_SECRET;
18
}
19
20
protected function configureAdapter(PhutilOAuth1AuthAdapter $adapter) {
21
$config = $this->getProviderConfig();
22
$adapter->setConsumerKey($config->getProperty(self::PROPERTY_CONSUMER_KEY));
23
$secret = $config->getProperty(self::PROPERTY_CONSUMER_SECRET);
24
if (phutil_nonempty_string($secret)) {
25
$adapter->setConsumerSecret(new PhutilOpaqueEnvelope($secret));
26
}
27
$adapter->setCallbackURI(PhabricatorEnv::getURI($this->getLoginURI()));
28
return $adapter;
29
}
30
31
protected function renderLoginForm(AphrontRequest $request, $mode) {
32
$attributes = array(
33
'method' => 'POST',
34
'uri' => $this->getLoginURI(),
35
);
36
return $this->renderStandardLoginButton($request, $mode, $attributes);
37
}
38
39
public function processLoginRequest(
40
PhabricatorAuthLoginController $controller) {
41
42
$request = $controller->getRequest();
43
$adapter = $this->getAdapter();
44
$account = null;
45
$response = null;
46
47
if ($request->isHTTPPost()) {
48
// Add a CSRF code to the callback URI, which we'll verify when
49
// performing the login.
50
51
$client_code = $this->getAuthCSRFCode($request);
52
53
$callback_uri = $adapter->getCallbackURI();
54
$callback_uri = $callback_uri.$client_code.'/';
55
$adapter->setCallbackURI($callback_uri);
56
57
$uri = $adapter->getClientRedirectURI();
58
59
$this->saveHandshakeTokenSecret(
60
$client_code,
61
$adapter->getTokenSecret());
62
63
$response = id(new AphrontRedirectResponse())
64
->setIsExternal(true)
65
->setURI($uri);
66
return array($account, $response);
67
}
68
69
$denied = $request->getStr('denied');
70
if (strlen($denied)) {
71
// Twitter indicates that the user cancelled the login attempt by
72
// returning "denied" as a parameter.
73
throw new PhutilAuthUserAbortedException();
74
}
75
76
// NOTE: You can get here via GET, this should probably be a bit more
77
// user friendly.
78
79
$this->verifyAuthCSRFCode($request, $controller->getExtraURIData());
80
81
$token = $request->getStr('oauth_token');
82
$verifier = $request->getStr('oauth_verifier');
83
84
if (!$token) {
85
throw new Exception(pht("Expected '%s' in request!", 'oauth_token'));
86
}
87
88
if (!$verifier) {
89
throw new Exception(pht("Expected '%s' in request!", 'oauth_verifier'));
90
}
91
92
$adapter->setToken($token);
93
$adapter->setVerifier($verifier);
94
95
$client_code = $this->getAuthCSRFCode($request);
96
$token_secret = $this->loadHandshakeTokenSecret($client_code);
97
$adapter->setTokenSecret($token_secret);
98
99
// NOTE: As a side effect, this will cause the OAuth adapter to request
100
// an access token.
101
102
try {
103
$identifiers = $adapter->getAccountIdentifiers();
104
} catch (Exception $ex) {
105
// TODO: Handle this in a more user-friendly way.
106
throw $ex;
107
}
108
109
if (!$identifiers) {
110
$response = $controller->buildProviderErrorResponse(
111
$this,
112
pht(
113
'The OAuth provider failed to retrieve an account ID.'));
114
115
return array($account, $response);
116
}
117
118
$account = $this->newExternalAccountForIdentifiers($identifiers);
119
120
return array($account, $response);
121
}
122
123
public function processEditForm(
124
AphrontRequest $request,
125
array $values) {
126
127
$key_ckey = self::PROPERTY_CONSUMER_KEY;
128
$key_csecret = self::PROPERTY_CONSUMER_SECRET;
129
130
return $this->processOAuthEditForm(
131
$request,
132
$values,
133
pht('Consumer key is required.'),
134
pht('Consumer secret is required.'));
135
}
136
137
public function extendEditForm(
138
AphrontRequest $request,
139
AphrontFormView $form,
140
array $values,
141
array $issues) {
142
143
return $this->extendOAuthEditForm(
144
$request,
145
$form,
146
$values,
147
$issues,
148
pht('OAuth Consumer Key'),
149
pht('OAuth Consumer Secret'));
150
}
151
152
public function renderConfigPropertyTransactionTitle(
153
PhabricatorAuthProviderConfigTransaction $xaction) {
154
155
$author_phid = $xaction->getAuthorPHID();
156
$old = $xaction->getOldValue();
157
$new = $xaction->getNewValue();
158
$key = $xaction->getMetadataValue(
159
PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY);
160
161
switch ($key) {
162
case self::PROPERTY_CONSUMER_KEY:
163
if (strlen($old)) {
164
return pht(
165
'%s updated the OAuth consumer key for this provider from '.
166
'"%s" to "%s".',
167
$xaction->renderHandleLink($author_phid),
168
$old,
169
$new);
170
} else {
171
return pht(
172
'%s set the OAuth consumer key for this provider to '.
173
'"%s".',
174
$xaction->renderHandleLink($author_phid),
175
$new);
176
}
177
case self::PROPERTY_CONSUMER_SECRET:
178
if (strlen($old)) {
179
return pht(
180
'%s updated the OAuth consumer secret for this provider.',
181
$xaction->renderHandleLink($author_phid));
182
} else {
183
return pht(
184
'%s set the OAuth consumer secret for this provider.',
185
$xaction->renderHandleLink($author_phid));
186
}
187
}
188
189
return parent::renderConfigPropertyTransactionTitle($xaction);
190
}
191
192
protected function synchronizeOAuthAccount(
193
PhabricatorExternalAccount $account) {
194
$adapter = $this->getAdapter();
195
196
$oauth_token = $adapter->getToken();
197
$oauth_token_secret = $adapter->getTokenSecret();
198
199
$account->setProperty('oauth1.token', $oauth_token);
200
$account->setProperty('oauth1.token.secret', $oauth_token_secret);
201
}
202
203
public function willRenderLinkedAccount(
204
PhabricatorUser $viewer,
205
PHUIObjectItemView $item,
206
PhabricatorExternalAccount $account) {
207
208
$item->addAttribute(pht('OAuth1 Account'));
209
210
parent::willRenderLinkedAccount($viewer, $item, $account);
211
}
212
213
protected function getContentSecurityPolicyFormActions() {
214
return $this->getAdapter()->getContentSecurityPolicyFormActions();
215
}
216
217
/* -( Temporary Secrets )-------------------------------------------------- */
218
219
220
private function saveHandshakeTokenSecret($client_code, $secret) {
221
$secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE;
222
$key = $this->getHandshakeTokenKeyFromClientCode($client_code);
223
$type = $this->getTemporaryTokenType($secret_type);
224
225
// Wipe out an existing token, if one exists.
226
$token = id(new PhabricatorAuthTemporaryTokenQuery())
227
->setViewer(PhabricatorUser::getOmnipotentUser())
228
->withTokenResources(array($key))
229
->withTokenTypes(array($type))
230
->executeOne();
231
if ($token) {
232
$token->delete();
233
}
234
235
// Save the new secret.
236
id(new PhabricatorAuthTemporaryToken())
237
->setTokenResource($key)
238
->setTokenType($type)
239
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
240
->setTokenCode($secret)
241
->save();
242
}
243
244
private function loadHandshakeTokenSecret($client_code) {
245
$secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE;
246
$key = $this->getHandshakeTokenKeyFromClientCode($client_code);
247
$type = $this->getTemporaryTokenType($secret_type);
248
249
$token = id(new PhabricatorAuthTemporaryTokenQuery())
250
->setViewer(PhabricatorUser::getOmnipotentUser())
251
->withTokenResources(array($key))
252
->withTokenTypes(array($type))
253
->withExpired(false)
254
->executeOne();
255
256
if (!$token) {
257
throw new Exception(
258
pht(
259
'Unable to load your OAuth1 token secret from storage. It may '.
260
'have expired. Try authenticating again.'));
261
}
262
263
return $token->getTokenCode();
264
}
265
266
private function getTemporaryTokenType($core_type) {
267
// Namespace the type so that multiple providers don't step on each
268
// others' toes if a user starts Mediawiki and Bitbucket auth at the
269
// same time.
270
271
// TODO: This isn't really a proper use of the table and should get
272
// cleaned up some day: the type should be constant.
273
274
return $core_type.':'.$this->getProviderConfig()->getID();
275
}
276
277
private function getHandshakeTokenKeyFromClientCode($client_code) {
278
// NOTE: This is very slightly coercive since the TemporaryToken table
279
// expects an "objectPHID" as an identifier, but nothing about the storage
280
// is bound to PHIDs.
281
282
return 'oauth1:secret/'.$client_code;
283
}
284
285
}
286
287