diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 5e228ebca8..ac56803e1b 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -1,441 +1,440 @@ 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); + return $app->getApplicationURI('/login/'.$this->getProviderKey().'/'); } 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, PHUIObjectItemView $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', 'sigil' => 'optional 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, 'sigil' => idx($attributes, 'sigil'), ), $content); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php index 548ff0bfe0..cf74ff8f46 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php @@ -1,349 +1,349 @@ 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()); + $adapter->setRedirectURI(PhabricatorEnv::getURI($this->getLoginURI())); return $adapter; } public function isLoginFormAButton() { return true; } protected function renderLoginForm(AphrontRequest $request, $mode) { $adapter = $this->getAdapter(); $adapter->setState( PhabricatorHash::digest( $request->getCookie(PhabricatorCookies::COOKIE_CLIENTID))); $scope = $request->getStr('scope'); if ($scope) { $adapter->setScope($scope); } $attributes = array( 'method' => 'GET', 'uri' => $adapter->getAuthenticateURI(), ); 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(PhabricatorCookies::COOKIE_CLIENTID); if (!strlen($phcid)) { $response = $controller->buildProviderErrorResponse( $this, pht( 'Your browser did not submit a "%s" cookie with OAuth state '. 'information in the request. Check that cookies are enabled. '. 'If this problem persists, you may need to clear your cookies.', PhabricatorCookies::COOKIE_CLIENTID)); } $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 === null || $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, PHUIObjectItemView $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 30c82cd65f..3173c13aeb 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php @@ -1,257 +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()); + $adapter->setCallbackURI(PhabricatorEnv::getURI($this->getLoginURI())); return $adapter; } public function isLoginFormAButton() { return true; } protected function renderLoginForm(AphrontRequest $request, $mode) { $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, PHUIObjectItemView $item, PhabricatorExternalAccount $account) { $item->addAttribute(pht('OAuth1 Account')); parent::willRenderLinkedAccount($viewer, $item, $account); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth1JIRA.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth1JIRA.php index faa21f6c3d..9c835790d9 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth1JIRA.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth1JIRA.php @@ -1,286 +1,286 @@ getProviderConfig()->getProperty(self::PROPERTY_JIRA_URI); } public function getProviderName() { return pht('JIRA'); } public function getDescriptionForCreate() { return pht('Configure JIRA OAuth. NOTE: Only supports JIRA 6.'); } public function getConfigurationHelp() { if ($this->isSetup()) { return pht( "**Step 1 of 2**: Provide the name and URI for your JIRA install.\n\n". "In the next step, you will configure JIRA."); } else { - $login_uri = $this->getLoginURI(); + $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "**Step 2 of 2**: In this step, you will configure JIRA.\n\n". "**Create a JIRA Application**: Log into JIRA and go to ". "**Administration**, then **Add-ons**, then **Application Links**. ". "Click the button labeled **Add Application Link**, and use these ". "settings to create an application:\n\n". " - **Server URL**: `%s`\n". " - Then, click **Next**. On the second page:\n". " - **Application Name**: `Phabricator`\n". " - **Application Type**: `Generic Application`\n". " - Then, click **Create**.\n\n". "**Configure Your Application**: Find the application you just ". "created in the table, and click the **Configure** link under ". "**Actions**. Select **Incoming Authentication** and click the ". "**OAuth** tab (it may be selected by default). Then, use these ". "settings:\n\n". " - **Consumer Key**: Set this to the \"Consumer Key\" value in the ". "form above.\n". " - **Consumer Name**: `Phabricator`\n". " - **Public Key**: Set this to the \"Public Key\" value in the ". "form above.\n". " - **Consumer Callback URL**: `%s`\n". "Click **Save** in JIRA. Authentication should now be configured, ". "and this provider should work correctly.", PhabricatorEnv::getProductionURI('/'), $login_uri); } } protected function newOAuthAdapter() { $config = $this->getProviderConfig(); return id(new PhutilAuthAdapterOAuthJIRA()) ->setAdapterDomain($config->getProviderDomain()) ->setJIRABaseURI($config->getProperty(self::PROPERTY_JIRA_URI)) ->setPrivateKey( new PhutilOpaqueEnvelope( $config->getProperty(self::PROPERTY_PRIVATE_KEY))); } protected function getLoginIcon() { return 'Jira'; } private function isSetup() { return !$this->getProviderConfig()->getID(); } const PROPERTY_JIRA_NAME = 'oauth1:jira:name'; const PROPERTY_JIRA_URI = 'oauth1:jira:uri'; const PROPERTY_PUBLIC_KEY = 'oauth1:jira:key:public'; const PROPERTY_PRIVATE_KEY = 'oauth1:jira:key:private'; public function readFormValuesFromProvider() { $config = $this->getProviderConfig(); $uri = $config->getProperty(self::PROPERTY_JIRA_URI); return array( self::PROPERTY_JIRA_NAME => $this->getProviderDomain(), self::PROPERTY_JIRA_URI => $uri, ); } public function readFormValuesFromRequest(AphrontRequest $request) { $is_setup = $this->isSetup(); if ($is_setup) { $name = $request->getStr(self::PROPERTY_JIRA_NAME); } else { $name = $this->getProviderDomain(); } return array( self::PROPERTY_JIRA_NAME => $name, self::PROPERTY_JIRA_URI => $request->getStr(self::PROPERTY_JIRA_URI), ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); $is_setup = $this->isSetup(); $key_name = self::PROPERTY_JIRA_NAME; $key_uri = self::PROPERTY_JIRA_URI; if (!strlen($values[$key_name])) { $errors[] = pht('JIRA instance name is required.'); $issues[$key_name] = pht('Required'); } else if (!preg_match('/^[a-z0-9.]+$/', $values[$key_name])) { $errors[] = pht( 'JIRA instance name must contain only lowercase letters, digits, and '. 'period.'); $issues[$key_name] = pht('Invalid'); } if (!strlen($values[$key_uri])) { $errors[] = pht('JIRA base URI is required.'); $issues[$key_uri] = pht('Required'); } else { $uri = new PhutilURI($values[$key_uri]); if (!$uri->getProtocol()) { $errors[] = pht( 'JIRA base URI should include protocol (like "https://").'); $issues[$key_uri] = pht('Invalid'); } } if (!$errors && $is_setup) { $config = $this->getProviderConfig(); $config->setProviderDomain($values[$key_name]); $consumer_key = 'phjira.'.Filesystem::readRandomCharacters(16); list($public, $private) = PhutilAuthAdapterOAuthJIRA::newJIRAKeypair(); $config->setProperty(self::PROPERTY_PUBLIC_KEY, $public); $config->setProperty(self::PROPERTY_PRIVATE_KEY, $private); $config->setProperty(self::PROPERTY_CONSUMER_KEY, $consumer_key); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { if (!function_exists('openssl_pkey_new')) { // TODO: This could be a bit prettier. throw new Exception( pht( "The PHP 'openssl' extension is not installed. You must install ". "this extension in order to add a JIRA authentication provider, ". "because JIRA OAuth requests use the RSA-SHA1 signing algorithm. ". "Install the 'openssl' extension, restart your webserver, and try ". "again.")); } $form->appendRemarkupInstructions( pht( 'NOTE: This provider **only supports JIRA 6**. It will not work with '. 'JIRA 5 or earlier.')); $is_setup = $this->isSetup(); $e_required = $request->isFormPost() ? null : true; $v_name = $values[self::PROPERTY_JIRA_NAME]; if ($is_setup) { $e_name = idx($issues, self::PROPERTY_JIRA_NAME, $e_required); } else { $e_name = null; } $v_uri = $values[self::PROPERTY_JIRA_URI]; $e_uri = idx($issues, self::PROPERTY_JIRA_URI, $e_required); if ($is_setup) { $form ->appendRemarkupInstructions( pht( "**JIRA Instance Name**\n\n". "Choose a permanent name for this instance of JIRA. Phabricator ". "uses this name internally to keep track of this instance of ". "JIRA, in case the URL changes later.\n\n". "Use lowercase letters, digits, and period. For example, ". "`jira`, `jira.mycompany` or `jira.engineering` are reasonable ". "names.")) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('JIRA Instance Name')) ->setValue($v_name) ->setName(self::PROPERTY_JIRA_NAME) ->setError($e_name)); } else { $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('JIRA Instance Name')) ->setValue($v_name)); } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('JIRA Base URI')) ->setValue($v_uri) ->setName(self::PROPERTY_JIRA_URI) ->setCaption( pht( 'The URI where JIRA is installed. For example: %s', phutil_tag('tt', array(), 'https://jira.mycompany.com/'))) ->setError($e_uri)); if (!$is_setup) { $config = $this->getProviderConfig(); $ckey = $config->getProperty(self::PROPERTY_CONSUMER_KEY); $ckey = phutil_tag('tt', array(), $ckey); $pkey = $config->getProperty(self::PROPERTY_PUBLIC_KEY); $pkey = phutil_escape_html_newlines($pkey); $pkey = phutil_tag('tt', array(), $pkey); $form ->appendRemarkupInstructions( pht( 'NOTE: **To complete setup**, copy and paste these keys into JIRA '. 'according to the instructions below.')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Consumer Key')) ->setValue($ckey)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Public Key')) ->setValue($pkey)); } } /** * JIRA uses a setup step to generate public/private keys. */ public function hasSetupStep() { return true; } public static function getJIRAProvider() { $providers = self::getAllEnabledProviders(); foreach ($providers as $provider) { if ($provider instanceof PhabricatorAuthProviderOAuth1JIRA) { return $provider; } } return null; } public function newJIRAFuture( PhabricatorExternalAccount $account, $path, $method, $params = array()) { $adapter = clone $this->getAdapter(); $adapter->setToken($account->getProperty('oauth1.token')); $adapter->setTokenSecret($account->getProperty('oauth1.token.secret')); return $adapter->newJIRAFuture($path, $method, $params); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth1Twitter.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth1Twitter.php index c1cdd0ded9..a6df0295a4 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth1Twitter.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth1Twitter.php @@ -1,35 +1,35 @@ getLoginURI(); + $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "To configure Twitter OAuth, create a new application here:". "\n\n". "https://dev.twitter.com/apps". "\n\n". "When creating your application, use these settings:". "\n\n". " - **Callback URL:** Set this to: `%s`". "\n\n". "After completing configuration, copy the **Consumer Key** and ". "**Consumer Secret** to the fields above.", $login_uri); } protected function newOAuthAdapter() { return new PhutilAuthAdapterOAuthTwitter(); } protected function getLoginIcon() { return 'Twitter'; } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthAmazon.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthAmazon.php index 99f7fccd29..d01f216eef 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthAmazon.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthAmazon.php @@ -1,46 +1,46 @@ getLoginURI(); + $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); $uri = new PhutilURI(PhabricatorEnv::getProductionURI('/')); $https_note = null; if ($uri->getProtocol() !== 'https') { $https_note = pht( 'NOTE: Amazon **requires** HTTPS, but your Phabricator install does '. 'not use HTTPS. **You will not be able to add Amazon as an '. 'authentication provider until you configure HTTPS on this install**.'); } return pht( "%s\n\n". "To configure Amazon OAuth, create a new 'API Project' here:". "\n\n". "http://login.amazon.com/manageApps". "\n\n". "Use these settings:". "\n\n". " - **Allowed Return URLs:** Add this: `%s`". "\n\n". "After completing configuration, copy the **Client ID** and ". "**Client Secret** to the fields above.", $https_note, $login_uri); } protected function newOAuthAdapter() { return new PhutilAuthAdapterOAuthAmazon(); } protected function getLoginIcon() { return 'Amazon'; } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthAsana.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthAsana.php index a929c8f26e..183bcc221a 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthAsana.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthAsana.php @@ -1,50 +1,50 @@ getLoginURI(); + $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "To configure Asana OAuth, create a new application here:". "\n\n". "https://app.asana.com/-/account_api". "\n\n". "When creating your application, use these settings:". "\n\n". " - **App URL:** Set this to: `%s`\n". " - **Redirect URL:** Set this to: `%s`". "\n\n". "After completing configuration, copy the **Client ID** and ". "**Client Secret** to the fields above.", $app_uri, $login_uri); } protected function newOAuthAdapter() { return new PhutilAuthAdapterOAuthAsana(); } protected function getLoginIcon() { return 'Asana'; } public static function getAsanaProvider() { $providers = self::getAllEnabledProviders(); foreach ($providers as $provider) { if ($provider instanceof PhabricatorAuthProviderOAuthAsana) { return $provider; } } return null; } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php index 4c374be64c..58c55ccaab 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php @@ -1,36 +1,36 @@ getLoginURI(); + $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "To configure Disqus OAuth, create a new application here:". "\n\n". "http://disqus.com/api/applications/". "\n\n". "Create an application, then adjust these settings:". "\n\n". " - **Callback URL:** Set this to `%s`". "\n\n". "After creating an application, copy the **Public Key** and ". "**Secret Key** to the fields above (the **Public Key** goes in ". "**OAuth App ID**).", $login_uri); } protected function newOAuthAdapter() { return new PhutilAuthAdapterOAuthDisqus(); } protected function getLoginIcon() { return 'Disqus'; } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php index fe57b7d96a..9c0eb7c907 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php @@ -1,44 +1,44 @@ getLoginURI(); + $callback_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "To configure GitHub OAuth, create a new GitHub Application here:". "\n\n". "https://github.com/settings/applications/new". "\n\n". "You should use these settings in your application:". "\n\n". " - **URL:** Set this to your full domain with protocol. For this ". " Phabricator install, the correct value is: `%s`\n". " - **Callback URL**: Set this to: `%s`\n". "\n\n". "Once you've created an application, copy the **Client ID** and ". "**Client Secret** into the fields above.", $uri, $callback_uri); } protected function newOAuthAdapter() { return new PhutilAuthAdapterOAuthGitHub(); } protected function getLoginIcon() { return 'Github'; } public function getLoginURI() { // TODO: Clean this up. See PhabricatorAuthOldOAuthRedirectController. - return PhabricatorEnv::getURI('/oauth/github/login/'); + return '/oauth/github/login/'; } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php index bd10f13afd..6815032e6f 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php @@ -1,44 +1,44 @@ getLoginURI(); + $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "To configure Google OAuth, create a new 'API Project' here:". "\n\n". "https://code.google.com/apis/console/". "\n\n". "You don't need to enable any Services, just go to **API Access**, ". "click **Create an OAuth 2.0 client ID...**, and configure these ". "settings:". "\n\n". " - During initial setup click **More Options** (or after creating ". " the client ID, click **Edit Settings...**), then add this to ". " **Authorized Redirect URIs**: `%s`\n". "\n\n". "After completing configuration, copy the **Client ID** and ". "**Client Secret** to the fields above.", $login_uri); } protected function newOAuthAdapter() { return new PhutilAuthAdapterOAuthGoogle(); } protected function getLoginIcon() { return 'Google'; } public function getLoginURI() { // TODO: Clean this up. See PhabricatorAuthOldOAuthRedirectController. - return PhabricatorEnv::getURI('/oauth/google/login/'); + return '/oauth/google/login/'; } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthTwitch.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthTwitch.php index db4b12730f..8ab03e06a4 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthTwitch.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthTwitch.php @@ -1,36 +1,36 @@ getLoginURI(); + $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "To configure Twitch.tv OAuth, create a new application here:". "\n\n". "http://www.twitch.tv/settings/applications". "\n\n". "When creating your application, use these settings:". "\n\n". " - **Redirect URI:** Set this to: `%s`". "\n\n". "After completing configuration, copy the **Client ID** and ". "**Client Secret** to the fields above. (You may need to generate the ". "client secret by clicking 'New Secret' first.)", $login_uri); } protected function newOAuthAdapter() { return new PhutilAuthAdapterOAuthTwitch(); } protected function getLoginIcon() { return 'TwitchTV'; } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderPersona.php b/src/applications/auth/provider/PhabricatorAuthProviderPersona.php index e96c8d96d1..dca32ed3dd 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderPersona.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderPersona.php @@ -1,83 +1,83 @@ adapter) { $adapter = new PhutilAuthAdapterPersona(); $this->adapter = $adapter; } return $this->adapter; } protected function renderLoginForm( AphrontRequest $request, $mode) { Javelin::initBehavior( 'persona-login', array( - 'loginURI' => $this->getLoginURI(), + 'loginURI' => PhabricatorEnv::getURI($this->getLoginURI()), )); return $this->renderStandardLoginButton( $request, $mode, array( 'uri' => $this->getLoginURI(), 'sigil' => 'persona-login-form', )); } public function isLoginFormAButton() { return true; } public function processLoginRequest( PhabricatorAuthLoginController $controller) { $request = $controller->getRequest(); $adapter = $this->getAdapter(); $account = null; $response = null; if (!$request->isAjax()) { throw new Exception("Expected this request to come via Ajax."); } $assertion = $request->getStr('assertion'); if (!$assertion) { throw new Exception("Expected identity assertion."); } $adapter->setAssertion($assertion); $adapter->setAudience(PhabricatorEnv::getURI('/')); try { $account_id = $adapter->getAccountID(); } catch (Exception $ex) { // TODO: Handle this in a more user-friendly way. throw $ex; } return array($this->loadOrCreateAccount($account_id), $response); } protected function getLoginIcon() { return 'Persona'; } }