diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index b02594f068..32fb052b4b 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -1,363 +1,439 @@ providerConfig = $config; return $this; } public function hasProviderConfig() { return (bool)$this->providerConfig; } public function getProviderConfig() { if ($this->providerConfig === null) { throw new Exception( "Call attachProviderConfig() before getProviderConfig()!"); } return $this->providerConfig; } public function getConfigurationHelp() { return null; } public function getDefaultProviderConfig() { return id(new PhabricatorAuthProviderConfig()) ->setProviderClass(get_class($this)) ->setIsEnabled(1) ->setShouldAllowLogin(1) ->setShouldAllowRegistration(1) ->setShouldAllowLink(1) ->setShouldAllowUnlink(1); } public function getNameForCreate() { return $this->getProviderName(); } public function getDescriptionForCreate() { return null; } public function getProviderKey() { return $this->getAdapter()->getAdapterKey(); } public function getProviderType() { return $this->getAdapter()->getAdapterType(); } public function getProviderDomain() { return $this->getAdapter()->getAdapterDomain(); } public static function getAllBaseProviders() { static $providers; if ($providers === null) { $objects = id(new PhutilSymbolLoader()) ->setAncestorClass(__CLASS__) ->loadObjects(); $providers = $objects; } return $providers; } public static function getAllProviders() { static $providers; if ($providers === null) { $objects = self::getAllBaseProviders(); $configs = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->execute(); $providers = array(); foreach ($configs as $config) { if (!isset($objects[$config->getProviderClass()])) { // This configuration is for a provider which is not installed. continue; } $object = clone $objects[$config->getProviderClass()]; $object->attachProviderConfig($config); $key = $object->getProviderKey(); if (isset($providers[$key])) { throw new Exception( pht( "Two authentication providers use the same provider key ". "('%s'). Each provider must be identified by a unique ". "key.", $key)); } $providers[$key] = $object; } } return $providers; } public static function getAllEnabledProviders() { $providers = self::getAllProviders(); foreach ($providers as $key => $provider) { if (!$provider->isEnabled()) { unset($providers[$key]); } } return $providers; } public static function getEnabledProviderByKey($provider_key) { return idx(self::getAllEnabledProviders(), $provider_key); } abstract public function getProviderName(); abstract public function getAdapter(); public function isEnabled() { return $this->getProviderConfig()->getIsEnabled(); } public function shouldAllowLogin() { return $this->getProviderConfig()->getShouldAllowLogin(); } public function shouldAllowRegistration() { return $this->getProviderConfig()->getShouldAllowRegistration(); } public function shouldAllowAccountLink() { return $this->getProviderConfig()->getShouldAllowLink(); } public function shouldAllowAccountUnlink() { return $this->getProviderConfig()->getShouldAllowUnlink(); } public function buildLoginForm( PhabricatorAuthStartController $controller) { return $this->renderLoginForm($controller->getRequest(), $mode = 'start'); } abstract public function processLoginRequest( PhabricatorAuthLoginController $controller); public function buildLinkForm( PhabricatorAuthLinkController $controller) { return $this->renderLoginForm($controller->getRequest(), $mode = 'link'); } public function shouldAllowAccountRefresh() { return true; } public function buildRefreshForm( PhabricatorAuthLinkController $controller) { return $this->renderLoginForm($controller->getRequest(), $mode = 'refresh'); } protected function renderLoginForm( AphrontRequest $request, $mode) { throw new Exception("Not implemented!"); } public function createProviders() { return array($this); } protected function willSaveAccount(PhabricatorExternalAccount $account) { return; } public function willRegisterAccount(PhabricatorExternalAccount $account) { return; } protected function loadOrCreateAccount($account_id) { if (!strlen($account_id)) { throw new Exception( "loadOrCreateAccount(...): empty account ID!"); } $adapter = $this->getAdapter(); $adapter_class = get_class($adapter); if (!strlen($adapter->getAdapterType())) { throw new Exception( "AuthAdapter (of class '{$adapter_class}') has an invalid ". "implementation: no adapter type."); } if (!strlen($adapter->getAdapterDomain())) { throw new Exception( "AuthAdapter (of class '{$adapter_class}') has an invalid ". "implementation: no adapter domain."); } $account = id(new PhabricatorExternalAccount())->loadOneWhere( 'accountType = %s AND accountDomain = %s AND accountID = %s', $adapter->getAdapterType(), $adapter->getAdapterDomain(), $account_id); if (!$account) { $account = id(new PhabricatorExternalAccount()) ->setAccountType($adapter->getAdapterType()) ->setAccountDomain($adapter->getAdapterDomain()) ->setAccountID($account_id); } $account->setUsername($adapter->getAccountName()); $account->setRealName($adapter->getAccountRealName()); $account->setEmail($adapter->getAccountEmail()); $account->setAccountURI($adapter->getAccountURI()); $account->setProfileImagePHID(null); $image_uri = $adapter->getAccountImageURI(); if ($image_uri) { try { $name = PhabricatorSlug::normalize($this->getProviderName()); $name = $name.'-profile.jpg'; // TODO: If the image has not changed, we do not need to make a new // file entry for it, but there's no convenient way to do this with // PhabricatorFile right now. The storage will get shared, so the impact // here is negligible. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $image_file = PhabricatorFile::newFromFileDownload( $image_uri, array( 'name' => $name, )); unset($unguarded); if ($image_file) { $account->setProfileImagePHID($image_file->getPHID()); } } catch (Exception $ex) { // Log this but proceed, it's not especially important that we // be able to pull profile images. phlog($ex); } } $this->willSaveAccount($account); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $account->save(); unset($unguarded); return $account; } public function getLoginURI() { $app = PhabricatorApplication::getByClass('PhabricatorApplicationAuth'); $uri = $app->getApplicationURI('/login/'.$this->getProviderKey().'/'); return PhabricatorEnv::getURI($uri); } public function getSettingsURI() { return '/settings/panel/external/'; } public function getStartURI() { $app = PhabricatorApplication::getByClass('PhabricatorApplicationAuth'); $uri = $app->getApplicationURI('/start/'); return $uri; } public function isDefaultRegistrationProvider() { return false; } public function shouldRequireRegistrationPassword() { return false; } public function getDefaultExternalAccount() { throw new Exception("Not implemented!"); } public function getLoginOrder() { return '500-'.$this->getProviderName(); } protected function getLoginIcon() { return 'Generic'; } public function isLoginFormAButton() { return false; } public function renderConfigPropertyTransactionTitle( PhabricatorAuthProviderConfigTransaction $xaction) { return null; } public function readFormValuesFromProvider() { return array(); } public function readFormValuesFromRequest(AphrontRequest $request) { return array(); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { return; } public function willRenderLinkedAccount( PhabricatorUser $viewer, PhabricatorObjectItemView $item, PhabricatorExternalAccount $account) { $account_view = id(new PhabricatorAuthAccountView()) ->setExternalAccount($account) ->setAuthProvider($this); $item->appendChild( phutil_tag( 'div', array( 'class' => 'mmr mml mst mmb', ), $account_view)); } /** * Return true to use a two-step configuration (setup, configure) instead of * the default single-step configuration. In practice, this means that * creating a new provider instance will redirect back to the edit page * instead of the provider list. * * @return bool True if this provider uses two-step configuration. */ public function hasSetupStep() { return false; } + + /** + * Render a standard login/register button element. + * + * The `$attributes` parameter takes these keys: + * + * - `uri`: URI the button should take the user to when clicked. + * - `method`: Optional HTTP method the button should use, defaults to GET. + * + * @param AphrontRequest HTTP request. + * @param string Request mode string. + * @param map Additional parameters, see above. + * @return wild Login button. + */ + protected function renderStandardLoginButton( + AphrontRequest $request, + $mode, + array $attributes = array()) { + + PhutilTypeSpec::checkMap( + $attributes, + array( + 'method' => 'optional string', + 'uri' => 'string', + )); + + $viewer = $request->getUser(); + $adapter = $this->getAdapter(); + + if ($mode == 'link') { + $button_text = pht('Link External Account'); + } else if ($mode == 'refresh') { + $button_text = pht('Refresh Account Link'); + } else if ($this->shouldAllowRegistration()) { + $button_text = pht('Login or Register'); + } else { + $button_text = pht('Login'); + } + + $icon = id(new PHUIIconView()) + ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) + ->setSpriteIcon($this->getLoginIcon()); + + $button = id(new PHUIButtonView()) + ->setSize(PHUIButtonView::BIG) + ->setColor(PHUIButtonView::GREY) + ->setIcon($icon) + ->setText($button_text) + ->setSubtext($this->getProviderName()); + + $uri = $attributes['uri']; + $uri = new PhutilURI($uri); + $params = $uri->getQueryParams(); + $uri->setQueryParams(array()); + + $content = array($button); + + foreach ($params as $key => $value) { + $content[] = phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $key, + 'value' => $value, + )); + } + + return phabricator_form( + $viewer, + array( + 'method' => idx($attributes, 'method', 'GET'), + 'action' => (string)$uri, + ), + $content); + } + } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php index 88d1b60369..9578e3e2f4 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php @@ -1,380 +1,341 @@ getProviderName()); } public function getAdapter() { if (!$this->adapter) { $adapter = $this->newOAuthAdapter(); $this->adapter = $adapter; $this->configureAdapter($adapter); } return $this->adapter; } protected function configureAdapter(PhutilAuthAdapterOAuth $adapter) { $config = $this->getProviderConfig(); $adapter->setClientID($config->getProperty(self::PROPERTY_APP_ID)); $adapter->setClientSecret( new PhutilOpaqueEnvelope( $config->getProperty(self::PROPERTY_APP_SECRET))); $adapter->setRedirectURI($this->getLoginURI()); return $adapter; } public function isLoginFormAButton() { return true; } protected function renderLoginForm(AphrontRequest $request, $mode) { - $viewer = $request->getUser(); - - if ($mode == 'link') { - $button_text = pht('Link External Account'); - } else if ($mode == 'refresh') { - $button_text = pht('Refresh Account Link'); - } else if ($this->shouldAllowRegistration()) { - $button_text = pht('Login or Register'); - } else { - $button_text = pht('Login'); - } - - $icon = id(new PHUIIconView()) - ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) - ->setSpriteIcon($this->getLoginIcon()); - - $button = id(new PHUIButtonView()) - ->setSize(PHUIButtonView::BIG) - ->setColor(PHUIButtonView::GREY) - ->setIcon($icon) - ->setText($button_text) - ->setSubtext($this->getProviderName()); - $adapter = $this->getAdapter(); $adapter->setState(PhabricatorHash::digest($request->getCookie('phcid'))); - $uri = new PhutilURI($adapter->getAuthenticateURI()); - $params = $uri->getQueryParams(); - $uri->setQueryParams(array()); - - $content = array($button); - - foreach ($params as $key => $value) { - $content[] = phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => $key, - 'value' => $value, - )); - } + $attributes = array( + 'method' => 'GET', + 'uri' => $adapter->getAuthenticateURI(), + ); - return phabricator_form( - $viewer, - array( - 'method' => 'GET', - 'action' => (string)$uri, - ), - $content); + return $this->renderStandardLoginButton($request, $mode, $attributes); } + public function processLoginRequest( PhabricatorAuthLoginController $controller) { $request = $controller->getRequest(); $adapter = $this->getAdapter(); $account = null; $response = null; $error = $request->getStr('error'); if ($error) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider returned an error: %s', $error)); return array($account, $response); } $code = $request->getStr('code'); if (!strlen($code)) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider did not return a "code" parameter in its '. 'response.')); return array($account, $response); } if ($adapter->supportsStateParameter()) { $phcid = $request->getCookie('phcid'); if (!strlen($phcid)) { $response = $controller->buildProviderErrorResponse( $this, pht( 'Your browser did not submit a "phcid" cookie with OAuth state '. 'information in the request. Check that cookies are enabled. '. 'If this problem persists, you may need to clear your cookies.')); } $state = $request->getStr('state'); $expect = PhabricatorHash::digest($phcid); if ($state !== $expect) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider did not return the correct "state" parameter '. 'in its response. If this problem persists, you may need to clear '. 'your cookies.')); } } $adapter->setCode($code); // NOTE: As a side effect, this will cause the OAuth adapter to request // an access token. try { $account_id = $adapter->getAccountID(); } catch (Exception $ex) { // TODO: Handle this in a more user-friendly way. throw $ex; } if (!strlen($account_id)) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider failed to retrieve an account ID.')); return array($account, $response); } return array($this->loadOrCreateAccount($account_id), $response); } const PROPERTY_APP_ID = 'oauth:app:id'; const PROPERTY_APP_SECRET = 'oauth:app:secret'; public function readFormValuesFromProvider() { $config = $this->getProviderConfig(); $id = $config->getProperty(self::PROPERTY_APP_ID); $secret = $config->getProperty(self::PROPERTY_APP_SECRET); return array( self::PROPERTY_APP_ID => $id, self::PROPERTY_APP_SECRET => $secret, ); } public function readFormValuesFromRequest(AphrontRequest $request) { return array( self::PROPERTY_APP_ID => $request->getStr(self::PROPERTY_APP_ID), self::PROPERTY_APP_SECRET => $request->getStr(self::PROPERTY_APP_SECRET), ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); $key_id = self::PROPERTY_APP_ID; $key_secret = self::PROPERTY_APP_SECRET; if (!strlen($values[$key_id])) { $errors[] = pht('Application ID is required.'); $issues[$key_id] = pht('Required'); } if (!strlen($values[$key_secret])) { $errors[] = pht('Application secret is required.'); $issues[$key_secret] = pht('Required'); } // If the user has not changed the secret, don't update it (that is, // don't cause a bunch of "****" to be written to the database). if (preg_match('/^[*]+$/', $values[$key_secret])) { unset($values[$key_secret]); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { $key_id = self::PROPERTY_APP_ID; $key_secret = self::PROPERTY_APP_SECRET; $v_id = $values[$key_id]; $v_secret = $values[$key_secret]; if ($v_secret) { $v_secret = str_repeat('*', strlen($v_secret)); } $e_id = idx($issues, $key_id, $request->isFormPost() ? null : true); $e_secret = idx($issues, $key_secret, $request->isFormPost() ? null : true); $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('OAuth App ID')) ->setName($key_id) ->setValue($v_id) ->setError($e_id)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel(pht('OAuth App Secret')) ->setName($key_secret) ->setValue($v_secret) ->setError($e_secret)); } public function renderConfigPropertyTransactionTitle( PhabricatorAuthProviderConfigTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $key = $xaction->getMetadataValue( PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); switch ($key) { case self::PROPERTY_APP_ID: if (strlen($old)) { return pht( '%s updated the OAuth application ID for this provider from '. '"%s" to "%s".', $xaction->renderHandleLink($author_phid), $old, $new); } else { return pht( '%s set the OAuth application ID for this provider to '. '"%s".', $xaction->renderHandleLink($author_phid), $new); } case self::PROPERTY_APP_SECRET: if (strlen($old)) { return pht( '%s updated the OAuth application secret for this provider.', $xaction->renderHandleLink($author_phid)); } else { return pht( '%s set the OAuth application seceret for this provider.', $xaction->renderHandleLink($author_phid)); } } return parent::renderConfigPropertyTransactionTitle($xaction); } protected function willSaveAccount(PhabricatorExternalAccount $account) { parent::willSaveAccount($account); $this->synchronizeOAuthAccount($account); } protected function synchronizeOAuthAccount( PhabricatorExternalAccount $account) { $adapter = $this->getAdapter(); $oauth_token = $adapter->getAccessToken(); $account->setProperty('oauth.token.access', $oauth_token); if ($adapter->supportsTokenRefresh()) { $refresh_token = $adapter->getRefreshToken(); $account->setProperty('oauth.token.refresh', $refresh_token); } else { $account->setProperty('oauth.token.refresh', null); } $expires = $adapter->getAccessTokenExpires(); $account->setProperty('oauth.token.access.expires', $expires); } public function getOAuthAccessToken( PhabricatorExternalAccount $account, $force_refresh = false) { if ($account->getProviderKey() !== $this->getProviderKey()) { throw new Exception("Account does not match provider!"); } if (!$force_refresh) { $access_expires = $account->getProperty('oauth.token.access.expires'); $access_token = $account->getProperty('oauth.token.access'); // Don't return a token with fewer than this many seconds remaining until // it expires. $shortest_token = 60; if ($access_token) { if ($access_expires > (time() + $shortest_token)) { return $access_token; } } } $refresh_token = $account->getProperty('oauth.token.refresh'); if ($refresh_token) { $adapter = $this->getAdapter(); if ($adapter->supportsTokenRefresh()) { $adapter->refreshAccessToken($refresh_token); $this->synchronizeOAuthAccount($account); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $account->save(); unset($unguarded); return $account->getProperty('oauth.token.access'); } } return null; } public function willRenderLinkedAccount( PhabricatorUser $viewer, PhabricatorObjectItemView $item, PhabricatorExternalAccount $account) { // Get a valid token, possibly refreshing it. $oauth_token = $this->getOAuthAccessToken($account); $item->addAttribute(pht('OAuth2 Account')); if ($oauth_token) { $oauth_expires = $account->getProperty('oauth.token.access.expires'); if ($oauth_expires) { $item->addAttribute( pht( 'Active OAuth Token (Expires: %s)', phabricator_datetime($oauth_expires, $viewer))); } else { $item->addAttribute( pht( 'Active OAuth Token')); } } else { $item->addAttribute(pht('No OAuth Access Token')); } parent::willRenderLinkedAccount($viewer, $item, $account); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php index c5931cacd0..96cc5d7c1e 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php @@ -1,300 +1,257 @@ getProviderName()); } public function getAdapter() { if (!$this->adapter) { $adapter = $this->newOAuthAdapter(); $this->adapter = $adapter; $this->configureAdapter($adapter); } return $this->adapter; } protected function configureAdapter(PhutilAuthAdapterOAuth1 $adapter) { $config = $this->getProviderConfig(); $adapter->setConsumerKey($config->getProperty(self::PROPERTY_CONSUMER_KEY)); $secret = $config->getProperty(self::PROPERTY_CONSUMER_SECRET); if (strlen($secret)) { $adapter->setConsumerSecret(new PhutilOpaqueEnvelope($secret)); } $adapter->setCallbackURI($this->getLoginURI()); return $adapter; } public function isLoginFormAButton() { return true; } protected function renderLoginForm(AphrontRequest $request, $mode) { - $viewer = $request->getUser(); - - if ($mode == 'link') { - $button_text = pht('Link External Account'); - } else if ($mode == 'refresh') { - $button_text = pht('Refresh Account Link'); - } else if ($this->shouldAllowRegistration()) { - $button_text = pht('Login or Register'); - } else { - $button_text = pht('Login'); - } - - $icon = id(new PHUIIconView()) - ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) - ->setSpriteIcon($this->getLoginIcon()); - - $button = id(new PHUIButtonView()) - ->setSize(PHUIButtonView::BIG) - ->setColor(PHUIButtonView::GREY) - ->setIcon($icon) - ->setText($button_text) - ->setSubtext($this->getProviderName()); - - $adapter = $this->getAdapter(); - - $uri = new PhutilURI($this->getLoginURI()); - $params = $uri->getQueryParams(); - $uri->setQueryParams(array()); - - $content = array($button); - - foreach ($params as $key => $value) { - $content[] = phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => $key, - 'value' => $value, - )); - } - - return phabricator_form( - $viewer, - array( - 'method' => 'POST', - 'action' => (string)$uri, - ), - $content); + $attributes = array( + 'method' => 'POST', + 'uri' => $this->getLoginURI(), + ); + return $this->renderStandardLoginButton($request, $mode, $attributes); } public function processLoginRequest( PhabricatorAuthLoginController $controller) { $request = $controller->getRequest(); $adapter = $this->getAdapter(); $account = null; $response = null; if ($request->isHTTPPost()) { $uri = $adapter->getClientRedirectURI(); $response = id(new AphrontRedirectResponse())->setURI($uri); return array($account, $response); } $denied = $request->getStr('denied'); if (strlen($denied)) { // Twitter indicates that the user cancelled the login attempt by // returning "denied" as a parameter. throw new PhutilAuthUserAbortedException(); } // NOTE: You can get here via GET, this should probably be a bit more // user friendly. $token = $request->getStr('oauth_token'); $verifier = $request->getStr('oauth_verifier'); if (!$token) { throw new Exception("Expected 'oauth_token' in request!"); } if (!$verifier) { throw new Exception("Expected 'oauth_verifier' in request!"); } $adapter->setToken($token); $adapter->setVerifier($verifier); // NOTE: As a side effect, this will cause the OAuth adapter to request // an access token. try { $account_id = $adapter->getAccountID(); } catch (Exception $ex) { // TODO: Handle this in a more user-friendly way. throw $ex; } if (!strlen($account_id)) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider failed to retrieve an account ID.')); return array($account, $response); } return array($this->loadOrCreateAccount($account_id), $response); } public function readFormValuesFromProvider() { $config = $this->getProviderConfig(); $id = $config->getProperty(self::PROPERTY_CONSUMER_KEY); $secret = $config->getProperty(self::PROPERTY_CONSUMER_SECRET); return array( self::PROPERTY_CONSUMER_KEY => $id, self::PROPERTY_CONSUMER_SECRET => $secret, ); } public function readFormValuesFromRequest(AphrontRequest $request) { return array( self::PROPERTY_CONSUMER_KEY => $request->getStr(self::PROPERTY_CONSUMER_KEY), self::PROPERTY_CONSUMER_SECRET => $request->getStr(self::PROPERTY_CONSUMER_SECRET), ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); $key_ckey = self::PROPERTY_CONSUMER_KEY; $key_csecret = self::PROPERTY_CONSUMER_SECRET; if (!strlen($values[$key_ckey])) { $errors[] = pht('Consumer key is required.'); $issues[$key_ckey] = pht('Required'); } if (!strlen($values[$key_csecret])) { $errors[] = pht('Consumer secret is required.'); $issues[$key_csecret] = pht('Required'); } // If the user has not changed the secret, don't update it (that is, // don't cause a bunch of "****" to be written to the database). if (preg_match('/^[*]+$/', $values[$key_csecret])) { unset($values[$key_csecret]); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { $key_id = self::PROPERTY_CONSUMER_KEY; $key_secret = self::PROPERTY_CONSUMER_SECRET; $v_id = $values[$key_id]; $v_secret = $values[$key_secret]; if ($v_secret) { $v_secret = str_repeat('*', strlen($v_secret)); } $e_id = idx($issues, $key_id, $request->isFormPost() ? null : true); $e_secret = idx($issues, $key_secret, $request->isFormPost() ? null : true); $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('OAuth Consumer Key')) ->setName($key_id) ->setValue($v_id) ->setError($e_id)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel(pht('OAuth Consumer Secret')) ->setName($key_secret) ->setValue($v_secret) ->setError($e_secret)); } public function renderConfigPropertyTransactionTitle( PhabricatorAuthProviderConfigTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $key = $xaction->getMetadataValue( PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); switch ($key) { case self::PROPERTY_CONSUMER_KEY: if (strlen($old)) { return pht( '%s updated the OAuth consumer key for this provider from '. '"%s" to "%s".', $xaction->renderHandleLink($author_phid), $old, $new); } else { return pht( '%s set the OAuth consumer key for this provider to '. '"%s".', $xaction->renderHandleLink($author_phid), $new); } case self::PROPERTY_CONSUMER_SECRET: if (strlen($old)) { return pht( '%s updated the OAuth consumer secret for this provider.', $xaction->renderHandleLink($author_phid)); } else { return pht( '%s set the OAuth consumer secret for this provider.', $xaction->renderHandleLink($author_phid)); } } return parent::renderConfigPropertyTransactionTitle($xaction); } protected function willSaveAccount(PhabricatorExternalAccount $account) { parent::willSaveAccount($account); $this->synchronizeOAuthAccount($account); } protected function synchronizeOAuthAccount( PhabricatorExternalAccount $account) { $adapter = $this->getAdapter(); $oauth_token = $adapter->getToken(); $oauth_token_secret = $adapter->getTokenSecret(); $account->setProperty('oauth1.token', $oauth_token); $account->setProperty('oauth1.token.secret', $oauth_token_secret); } public function willRenderLinkedAccount( PhabricatorUser $viewer, PhabricatorObjectItemView $item, PhabricatorExternalAccount $account) { $item->addAttribute(pht('OAuth1 Account')); parent::willRenderLinkedAccount($viewer, $item, $account); } }