3 abstract class PhabricatorOAuth1AuthProvider
4 extends PhabricatorOAuthAuthProvider
{
8 const PROPERTY_CONSUMER_KEY
= 'oauth1:consumer:key';
9 const PROPERTY_CONSUMER_SECRET
= 'oauth1:consumer:secret';
10 const PROPERTY_PRIVATE_KEY
= 'oauth1:private:key';
12 protected function getIDKey() {
13 return self
::PROPERTY_CONSUMER_KEY
;
16 protected function getSecretKey() {
17 return self
::PROPERTY_CONSUMER_SECRET
;
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 (strlen($secret)) {
25 $adapter->setConsumerSecret(new PhutilOpaqueEnvelope($secret));
27 $adapter->setCallbackURI(PhabricatorEnv
::getURI($this->getLoginURI()));
31 protected function renderLoginForm(AphrontRequest
$request, $mode) {
34 'uri' => $this->getLoginURI(),
36 return $this->renderStandardLoginButton($request, $mode, $attributes);
39 public function processLoginRequest(
40 PhabricatorAuthLoginController
$controller) {
42 $request = $controller->getRequest();
43 $adapter = $this->getAdapter();
47 if ($request->isHTTPPost()) {
48 // Add a CSRF code to the callback URI, which we'll verify when
49 // performing the login.
51 $client_code = $this->getAuthCSRFCode($request);
53 $callback_uri = $adapter->getCallbackURI();
54 $callback_uri = $callback_uri.$client_code.'/';
55 $adapter->setCallbackURI($callback_uri);
57 $uri = $adapter->getClientRedirectURI();
59 $this->saveHandshakeTokenSecret(
61 $adapter->getTokenSecret());
63 $response = id(new AphrontRedirectResponse())
66 return array($account, $response);
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();
76 // NOTE: You can get here via GET, this should probably be a bit more
79 $this->verifyAuthCSRFCode($request, $controller->getExtraURIData());
81 $token = $request->getStr('oauth_token');
82 $verifier = $request->getStr('oauth_verifier');
85 throw new Exception(pht("Expected '%s' in request!", 'oauth_token'));
89 throw new Exception(pht("Expected '%s' in request!", 'oauth_verifier'));
92 $adapter->setToken($token);
93 $adapter->setVerifier($verifier);
95 $client_code = $this->getAuthCSRFCode($request);
96 $token_secret = $this->loadHandshakeTokenSecret($client_code);
97 $adapter->setTokenSecret($token_secret);
99 // NOTE: As a side effect, this will cause the OAuth adapter to request
103 $identifiers = $adapter->getAccountIdentifiers();
104 } catch (Exception
$ex) {
105 // TODO: Handle this in a more user-friendly way.
110 $response = $controller->buildProviderErrorResponse(
113 'The OAuth provider failed to retrieve an account ID.'));
115 return array($account, $response);
118 $account = $this->newExternalAccountForIdentifiers($identifiers);
120 return array($account, $response);
123 public function processEditForm(
124 AphrontRequest
$request,
127 $key_ckey = self
::PROPERTY_CONSUMER_KEY
;
128 $key_csecret = self
::PROPERTY_CONSUMER_SECRET
;
130 return $this->processOAuthEditForm(
133 pht('Consumer key is required.'),
134 pht('Consumer secret is required.'));
137 public function extendEditForm(
138 AphrontRequest
$request,
139 AphrontFormView
$form,
143 return $this->extendOAuthEditForm(
148 pht('OAuth Consumer Key'),
149 pht('OAuth Consumer Secret'));
152 public function renderConfigPropertyTransactionTitle(
153 PhabricatorAuthProviderConfigTransaction
$xaction) {
155 $author_phid = $xaction->getAuthorPHID();
156 $old = $xaction->getOldValue();
157 $new = $xaction->getNewValue();
158 $key = $xaction->getMetadataValue(
159 PhabricatorAuthProviderConfigTransaction
::PROPERTY_KEY
);
162 case self
::PROPERTY_CONSUMER_KEY
:
165 '%s updated the OAuth consumer key for this provider from '.
167 $xaction->renderHandleLink($author_phid),
172 '%s set the OAuth consumer key for this provider to '.
174 $xaction->renderHandleLink($author_phid),
177 case self
::PROPERTY_CONSUMER_SECRET
:
180 '%s updated the OAuth consumer secret for this provider.',
181 $xaction->renderHandleLink($author_phid));
184 '%s set the OAuth consumer secret for this provider.',
185 $xaction->renderHandleLink($author_phid));
189 return parent
::renderConfigPropertyTransactionTitle($xaction);
192 protected function synchronizeOAuthAccount(
193 PhabricatorExternalAccount
$account) {
194 $adapter = $this->getAdapter();
196 $oauth_token = $adapter->getToken();
197 $oauth_token_secret = $adapter->getTokenSecret();
199 $account->setProperty('oauth1.token', $oauth_token);
200 $account->setProperty('oauth1.token.secret', $oauth_token_secret);
203 public function willRenderLinkedAccount(
204 PhabricatorUser
$viewer,
205 PHUIObjectItemView
$item,
206 PhabricatorExternalAccount
$account) {
208 $item->addAttribute(pht('OAuth1 Account'));
210 parent
::willRenderLinkedAccount($viewer, $item, $account);
213 protected function getContentSecurityPolicyFormActions() {
214 return $this->getAdapter()->getContentSecurityPolicyFormActions();
217 /* -( Temporary Secrets )-------------------------------------------------- */
220 private function saveHandshakeTokenSecret($client_code, $secret) {
221 $secret_type = PhabricatorOAuth1SecretTemporaryTokenType
::TOKENTYPE
;
222 $key = $this->getHandshakeTokenKeyFromClientCode($client_code);
223 $type = $this->getTemporaryTokenType($secret_type);
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))
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)
244 private function loadHandshakeTokenSecret($client_code) {
245 $secret_type = PhabricatorOAuth1SecretTemporaryTokenType
::TOKENTYPE
;
246 $key = $this->getHandshakeTokenKeyFromClientCode($client_code);
247 $type = $this->getTemporaryTokenType($secret_type);
249 $token = id(new PhabricatorAuthTemporaryTokenQuery())
250 ->setViewer(PhabricatorUser
::getOmnipotentUser())
251 ->withTokenResources(array($key))
252 ->withTokenTypes(array($type))
259 'Unable to load your OAuth1 token secret from storage. It may '.
260 'have expired. Try authenticating again.'));
263 return $token->getTokenCode();
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
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.
274 return $core_type.':'.$this->getProviderConfig()->getID();
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.
282 return 'oauth1:secret/'.$client_code;