@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 285 lines 8.7 kB view raw
1<?php 2 3abstract 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 ($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 (phutil_nonempty_string($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 (phutil_nonempty_string($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}