@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.

Add Twitter as an authentication provider

Summary: Ref T3687. Depends on D6864. Implements the `OAuth1` provider in Phabricator (which is mostly similar to the OAuth2 provider, but doesn't share quite enough code to actually extend a common base class, I think) and Twitter as a concrete subclass.

Test Plan:
Created a Twitter provider. Registered, logged in, linked, refreshed account link.

{F57054}

{F57056}

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T3687

Differential Revision: https://secure.phabricator.com/D6865

+331
+4
src/__phutil_library_map__.php
··· 902 902 'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php', 903 903 'PhabricatorAuthProviderLDAP' => 'applications/auth/provider/PhabricatorAuthProviderLDAP.php', 904 904 'PhabricatorAuthProviderOAuth' => 'applications/auth/provider/PhabricatorAuthProviderOAuth.php', 905 + 'PhabricatorAuthProviderOAuth1' => 'applications/auth/provider/PhabricatorAuthProviderOAuth1.php', 906 + 'PhabricatorAuthProviderOAuth1Twitter' => 'applications/auth/provider/PhabricatorAuthProviderOAuth1Twitter.php', 905 907 'PhabricatorAuthProviderOAuthAmazon' => 'applications/auth/provider/PhabricatorAuthProviderOAuthAmazon.php', 906 908 'PhabricatorAuthProviderOAuthAsana' => 'applications/auth/provider/PhabricatorAuthProviderOAuthAsana.php', 907 909 'PhabricatorAuthProviderOAuthDisqus' => 'applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php', ··· 2954 2956 'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 2955 2957 'PhabricatorAuthProviderLDAP' => 'PhabricatorAuthProvider', 2956 2958 'PhabricatorAuthProviderOAuth' => 'PhabricatorAuthProvider', 2959 + 'PhabricatorAuthProviderOAuth1' => 'PhabricatorAuthProvider', 2960 + 'PhabricatorAuthProviderOAuth1Twitter' => 'PhabricatorAuthProviderOAuth1', 2957 2961 'PhabricatorAuthProviderOAuthAmazon' => 'PhabricatorAuthProviderOAuth', 2958 2962 'PhabricatorAuthProviderOAuthAsana' => 'PhabricatorAuthProviderOAuth', 2959 2963 'PhabricatorAuthProviderOAuthDisqus' => 'PhabricatorAuthProviderOAuth',
+292
src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorAuthProviderOAuth1 extends PhabricatorAuthProvider { 4 + 5 + protected $adapter; 6 + 7 + const PROPERTY_CONSUMER_KEY = 'oauth1:consumer:key'; 8 + const PROPERTY_CONSUMER_SECRET = 'oauth1:consumer:secret'; 9 + const PROPERTY_PRIVATE_KEY = 'oauth1:private:key'; 10 + 11 + abstract protected function newOAuthAdapter(); 12 + 13 + public function getDescriptionForCreate() { 14 + return pht('Configure %s OAuth.', $this->getProviderName()); 15 + } 16 + 17 + public function getAdapter() { 18 + if (!$this->adapter) { 19 + $adapter = $this->newOAuthAdapter(); 20 + $this->adapter = $adapter; 21 + $this->configureAdapter($adapter); 22 + } 23 + return $this->adapter; 24 + } 25 + 26 + protected function configureAdapter(PhutilAuthAdapterOAuth1 $adapter) { 27 + $config = $this->getProviderConfig(); 28 + $adapter->setConsumerKey($config->getProperty(self::PROPERTY_CONSUMER_KEY)); 29 + $adapter->setConsumerSecret( 30 + new PhutilOpaqueEnvelope( 31 + $config->getProperty(self::PROPERTY_CONSUMER_SECRET))); 32 + $adapter->setCallbackURI($this->getLoginURI()); 33 + return $adapter; 34 + } 35 + 36 + public function isLoginFormAButton() { 37 + return true; 38 + } 39 + 40 + protected function renderLoginForm(AphrontRequest $request, $mode) { 41 + $viewer = $request->getUser(); 42 + 43 + if ($mode == 'link') { 44 + $button_text = pht('Link External Account'); 45 + } else if ($mode == 'refresh') { 46 + $button_text = pht('Refresh Account Link'); 47 + } else if ($this->shouldAllowRegistration()) { 48 + $button_text = pht('Login or Register'); 49 + } else { 50 + $button_text = pht('Login'); 51 + } 52 + 53 + $icon = id(new PHUIIconView()) 54 + ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) 55 + ->setSpriteIcon($this->getLoginIcon()); 56 + 57 + $button = id(new PHUIButtonView()) 58 + ->setSize(PHUIButtonView::BIG) 59 + ->setColor(PHUIButtonView::GREY) 60 + ->setIcon($icon) 61 + ->setText($button_text) 62 + ->setSubtext($this->getProviderName()); 63 + 64 + $adapter = $this->getAdapter(); 65 + 66 + $uri = new PhutilURI($this->getLoginURI()); 67 + $params = $uri->getQueryParams(); 68 + $uri->setQueryParams(array()); 69 + 70 + $content = array($button); 71 + 72 + foreach ($params as $key => $value) { 73 + $content[] = phutil_tag( 74 + 'input', 75 + array( 76 + 'type' => 'hidden', 77 + 'name' => $key, 78 + 'value' => $value, 79 + )); 80 + } 81 + 82 + return phabricator_form( 83 + $viewer, 84 + array( 85 + 'method' => 'POST', 86 + 'action' => (string)$uri, 87 + ), 88 + $content); 89 + } 90 + 91 + public function processLoginRequest( 92 + PhabricatorAuthLoginController $controller) { 93 + 94 + $request = $controller->getRequest(); 95 + $adapter = $this->getAdapter(); 96 + $account = null; 97 + $response = null; 98 + 99 + if ($request->isHTTPPost()) { 100 + $uri = $adapter->getClientRedirectURI(); 101 + $response = id(new AphrontRedirectResponse())->setURI($uri); 102 + return array($account, $response); 103 + } 104 + 105 + // NOTE: You can get here via GET, this should probably be a bit more 106 + // user friendly. 107 + 108 + $token = $request->getStr('oauth_token'); 109 + $verifier = $request->getStr('oauth_verifier'); 110 + 111 + if (!$token) { 112 + throw new Exception("Expected 'oauth_token' in request!"); 113 + } 114 + 115 + if (!$verifier) { 116 + throw new Exception("Expected 'oauth_verifier' in request!"); 117 + } 118 + 119 + $adapter->setToken($token); 120 + $adapter->setVerifier($verifier); 121 + 122 + // NOTE: As a side effect, this will cause the OAuth adapter to request 123 + // an access token. 124 + 125 + try { 126 + $account_id = $adapter->getAccountID(); 127 + } catch (Exception $ex) { 128 + // TODO: Handle this in a more user-friendly way. 129 + throw $ex; 130 + } 131 + 132 + if (!strlen($account_id)) { 133 + $response = $controller->buildProviderErrorResponse( 134 + $this, 135 + pht( 136 + 'The OAuth provider failed to retrieve an account ID.')); 137 + 138 + return array($account, $response); 139 + } 140 + 141 + return array($this->loadOrCreateAccount($account_id), $response); 142 + } 143 + 144 + public function readFormValuesFromProvider() { 145 + $config = $this->getProviderConfig(); 146 + $id = $config->getProperty(self::PROPERTY_CONSUMER_KEY); 147 + $secret = $config->getProperty(self::PROPERTY_CONSUMER_SECRET); 148 + 149 + return array( 150 + self::PROPERTY_CONSUMER_KEY => $id, 151 + self::PROPERTY_CONSUMER_SECRET => $secret, 152 + ); 153 + } 154 + 155 + public function readFormValuesFromRequest(AphrontRequest $request) { 156 + return array( 157 + self::PROPERTY_CONSUMER_KEY 158 + => $request->getStr(self::PROPERTY_CONSUMER_KEY), 159 + self::PROPERTY_CONSUMER_SECRET 160 + => $request->getStr(self::PROPERTY_CONSUMER_SECRET), 161 + ); 162 + } 163 + 164 + public function processEditForm( 165 + AphrontRequest $request, 166 + array $values) { 167 + $errors = array(); 168 + $issues = array(); 169 + 170 + $key_ckey = self::PROPERTY_CONSUMER_KEY; 171 + $key_csecret = self::PROPERTY_CONSUMER_SECRET; 172 + 173 + if (!strlen($values[$key_ckey])) { 174 + $errors[] = pht('Consumer key is required.'); 175 + $issues[$key_ckey] = pht('Required'); 176 + } 177 + 178 + if (!strlen($values[$key_csecret])) { 179 + $errors[] = pht('Consumer secret is required.'); 180 + $issues[$key_csecret] = pht('Required'); 181 + } 182 + 183 + // If the user has not changed the secret, don't update it (that is, 184 + // don't cause a bunch of "****" to be written to the database). 185 + if (preg_match('/^[*]+$/', $values[$key_csecret])) { 186 + unset($values[$key_csecret]); 187 + } 188 + 189 + return array($errors, $issues, $values); 190 + } 191 + 192 + public function extendEditForm( 193 + AphrontRequest $request, 194 + AphrontFormView $form, 195 + array $values, 196 + array $issues) { 197 + 198 + $key_id = self::PROPERTY_CONSUMER_KEY; 199 + $key_secret = self::PROPERTY_CONSUMER_SECRET; 200 + 201 + $v_id = $values[$key_id]; 202 + $v_secret = $values[$key_secret]; 203 + if ($v_secret) { 204 + $v_secret = str_repeat('*', strlen($v_secret)); 205 + } 206 + 207 + $e_id = idx($issues, $key_id, $request->isFormPost() ? null : true); 208 + $e_secret = idx($issues, $key_secret, $request->isFormPost() ? null : true); 209 + 210 + $form 211 + ->appendChild( 212 + id(new AphrontFormTextControl()) 213 + ->setLabel(pht('OAuth Consumer Key')) 214 + ->setName($key_id) 215 + ->setValue($v_id) 216 + ->setError($e_id)) 217 + ->appendChild( 218 + id(new AphrontFormPasswordControl()) 219 + ->setLabel(pht('OAuth Consumer Secret')) 220 + ->setName($key_secret) 221 + ->setValue($v_secret) 222 + ->setError($e_secret)); 223 + } 224 + 225 + public function renderConfigPropertyTransactionTitle( 226 + PhabricatorAuthProviderConfigTransaction $xaction) { 227 + 228 + $author_phid = $xaction->getAuthorPHID(); 229 + $old = $xaction->getOldValue(); 230 + $new = $xaction->getNewValue(); 231 + $key = $xaction->getMetadataValue( 232 + PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); 233 + 234 + switch ($key) { 235 + case self::PROPERTY_CONSUMER_KEY: 236 + if (strlen($old)) { 237 + return pht( 238 + '%s updated the OAuth consumer key for this provider from '. 239 + '"%s" to "%s".', 240 + $xaction->renderHandleLink($author_phid), 241 + $old, 242 + $new); 243 + } else { 244 + return pht( 245 + '%s set the OAuth consumer key for this provider to '. 246 + '"%s".', 247 + $xaction->renderHandleLink($author_phid), 248 + $new); 249 + } 250 + case self::PROPERTY_CONSUMER_SECRET: 251 + if (strlen($old)) { 252 + return pht( 253 + '%s updated the OAuth consumer secret for this provider.', 254 + $xaction->renderHandleLink($author_phid)); 255 + } else { 256 + return pht( 257 + '%s set the OAuth consumer secret for this provider.', 258 + $xaction->renderHandleLink($author_phid)); 259 + } 260 + } 261 + 262 + return parent::renderConfigPropertyTransactionTitle($xaction); 263 + } 264 + 265 + protected function willSaveAccount(PhabricatorExternalAccount $account) { 266 + parent::willSaveAccount($account); 267 + $this->synchronizeOAuthAccount($account); 268 + } 269 + 270 + protected function synchronizeOAuthAccount( 271 + PhabricatorExternalAccount $account) { 272 + $adapter = $this->getAdapter(); 273 + 274 + $oauth_token = $adapter->getToken(); 275 + $oauth_token_secret = $adapter->getTokenSecret(); 276 + 277 + $account->setProperty('oauth1.token', $oauth_token); 278 + $account->setProperty('oauth1.token.secret', $oauth_token_secret); 279 + } 280 + 281 + public function willRenderLinkedAccount( 282 + PhabricatorUser $viewer, 283 + PhabricatorObjectItemView $item, 284 + PhabricatorExternalAccount $account) { 285 + 286 + $item->addAttribute(pht('OAuth1 Account')); 287 + 288 + parent::willRenderLinkedAccount($viewer, $item, $account); 289 + } 290 + 291 + 292 + }
+35
src/applications/auth/provider/PhabricatorAuthProviderOAuth1Twitter.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthProviderOAuth1Twitter 4 + extends PhabricatorAuthProviderOAuth1 { 5 + 6 + public function getProviderName() { 7 + return pht('Twitter'); 8 + } 9 + 10 + public function getConfigurationHelp() { 11 + $login_uri = $this->getLoginURI(); 12 + 13 + return pht( 14 + "To configure Twitter OAuth, create a new application here:". 15 + "\n\n". 16 + "https://dev.twitter.com/apps". 17 + "\n\n". 18 + "When creating your application, use these settings:". 19 + "\n\n". 20 + " - **Callback URL:** Set this to: `%s`". 21 + "\n\n". 22 + "After completing configuration, copy the **Consumer Key** and ". 23 + "**Consumer Secret** to the fields above.", 24 + $login_uri); 25 + } 26 + 27 + protected function newOAuthAdapter() { 28 + return new PhutilAuthAdapterOAuthTwitter(); 29 + } 30 + 31 + protected function getLoginIcon() { 32 + return 'Twitter'; 33 + } 34 + 35 + }