diff --git a/src/applications/auth/application/PhabricatorApplicationAuth.php b/src/applications/auth/application/PhabricatorApplicationAuth.php index 9607700b3a..8df53a63f1 100644 --- a/src/applications/auth/application/PhabricatorApplicationAuth.php +++ b/src/applications/auth/application/PhabricatorApplicationAuth.php @@ -1,84 +1,88 @@ isLoggedIn()) { $item = new PHUIListItemView(); $item->setName(pht('Log Out')); $item->setIcon('power'); $item->setWorkflow(true); $item->setHref('/logout/'); $item->setSelected(($controller instanceof PhabricatorLogoutController)); $items[] = $item; } return $items; } public function shouldAppearInLaunchView() { return false; } public function getRoutes() { return array( '/auth/' => array( /* '(query/(?P[^/]+)/)?' => 'PhabricatorAuthListController', 'config/' => array( 'new/' => 'PhabricatorAuthNewController', 'new/(?P[^/]+)/' => 'PhabricatorAuthEditController', 'edit/(?P\d+)/' => 'PhabricatorAuthEditController', '(?Penable|disable)/(?P\d+)/' => 'PhabricatorAuthDisableController', ), */ 'login/(?P[^/]+)/' => 'PhabricatorAuthLoginController', 'register/(?:(?P[^/]+)/)?' => 'PhabricatorAuthRegisterController', 'start/' => 'PhabricatorAuthStartController', 'validate/' => 'PhabricatorAuthValidateController', 'unlink/(?P[^/]+)/' => 'PhabricatorAuthUnlinkController', 'link/(?P[^/]+)/' => 'PhabricatorAuthLinkController', 'confirmlink/(?P[^/]+)/' => 'PhabricatorAuthConfirmLinkController', ), '/login/' => array( '' => 'PhabricatorLoginController', 'email/' => 'PhabricatorEmailLoginController', 'etoken/(?P\w+)/' => 'PhabricatorEmailTokenController', 'refresh/' => 'PhabricatorRefreshCSRFController', 'mustverify/' => 'PhabricatorMustVerifyEmailController', ), '/logout/' => 'PhabricatorLogoutController', '/oauth/' => array( '(?P\w+)/' => array( 'login/' => 'PhabricatorOAuthLoginController', 'diagnose/' => 'PhabricatorOAuthDiagnosticsController', ), ), '/ldap/' => array( 'login/' => 'PhabricatorLDAPLoginController', ), ); } } diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php index a7f4998657..c06beaa4e9 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php @@ -1,246 +1,267 @@ providerClass = idx($data, 'className'); $this->configID = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); if ($this->configID) { $config = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($this->configID)) ->executeOne(); if (!$config) { return new Aphront404Response(); } $provider = $config->getProvider(); if (!$provider) { return new Aphront404Response(); } $is_new = false; } else { $providers = PhabricatorAuthProvider::getAllBaseProviders(); foreach ($providers as $candidate_provider) { if (get_class($candidate_provider) === $this->providerClass) { $provider = $candidate_provider; break; } } if (!$provider) { return new Aphront404Response(); } // TODO: When we have multi-auth providers, support them here. $configs = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) ->withProviderClasses(array(get_class($provider))) ->execute(); if ($configs) { // TODO: We could link to the other config's edit interface here. throw new Exception("This provider is already configured!"); } $config = id(new PhabricatorAuthProviderConfig()) ->setProviderClass(get_class($provider)) ->setShouldAllowLogin(1) ->setShouldAllowRegistration(1) ->setShouldAllowLink(1) ->setShouldAllowUnlink(1); $is_new = true; } $errors = array(); $v_registration = $config->getShouldAllowRegistration(); $v_link = $config->getShouldAllowLink(); $v_unlink = $config->getShouldAllowUnlink(); if ($request->isFormPost()) { + + $properties = $provider->readFormValuesFromRequest($request); + list($errors, $issues, $properties) = $provider->processEditForm( + $request, + $properties); + $xactions = array(); - if ($is_new) { + if (!$errors) { + if ($is_new) { + $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) + ->setTransactionType( + PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE) + ->setNewValue(1); + + $config->setProviderType($provider->getProviderType()); + $config->setProviderDomain($provider->getProviderDomain()); + } + $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( - PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE) - ->setNewValue(1); + PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION) + ->setNewValue($request->getInt('allowRegistration', 0)); - $config->setProviderType($provider->getProviderType()); - $config->setProviderDomain($provider->getProviderDomain()); - } - - $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) - ->setTransactionType( - PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION) - ->setNewValue($request->getInt('allowRegistration', 0)); - - $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) - ->setTransactionType( - PhabricatorAuthProviderConfigTransaction::TYPE_LINK) - ->setNewValue($request->getInt('allowLink', 0)); + $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) + ->setTransactionType( + PhabricatorAuthProviderConfigTransaction::TYPE_LINK) + ->setNewValue($request->getInt('allowLink', 0)); - $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) - ->setTransactionType( - PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK) - ->setNewValue($request->getInt('allowUnlink', 0)); + $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) + ->setTransactionType( + PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK) + ->setNewValue($request->getInt('allowUnlink', 0)); + + foreach ($properties as $key => $value) { + $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) + ->setTransactionType( + PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY) + ->setMetadataValue('auth:property', $key) + ->setNewValue($value); + } - if (!$errors) { $editor = id(new PhabricatorAuthProviderConfigEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->applyTransactions($config, $xactions); return id(new AphrontRedirectResponse())->setURI( $this->getApplicationURI()); } + } else { + $properties = $provider->readFormValuesFromProvider(); + $issues = array(); } if ($errors) { $errors = id(new AphrontErrorView())->setErrors($errors); } if ($is_new) { $button = pht('Add Provider'); $crumb = pht('Add Provider'); $title = pht('Add Authentication Provider'); $cancel_uri = $this->getApplicationURI('/config/new/'); } else { $button = pht('Save'); $crumb = pht('Edit Provider'); $title = pht('Edit Authentication Provider'); $cancel_uri = $this->getApplicationURI(); } $str_registration = hsprintf( '%s: %s', pht('Allow Registration'), pht( 'Allow users to register new Phabricator accounts using this '. 'provider. If you disable registration, users can still use this '. 'provider to log in to existing accounts, but will not be able to '. 'create new accounts.')); $str_link = hsprintf( '%s: %s', pht('Allow Linking Accounts'), pht( 'Allow users to link account credentials for this provider to '. 'existing Phabricator accounts. There is normally no reason to '. 'disable this unless you are trying to move away from a provider '. 'and want to stop users from creating new account links.')); $str_unlink = hsprintf( '%s: %s', pht('Allow Unlinking Accounts'), pht( 'Allow users to unlink account credentials for this provider from '. 'existing Phabricator accounts. If you disable this, Phabricator '. 'accounts will be permanently bound to provider accounts.')); $status_tag = id(new PhabricatorTagView()) ->setType(PhabricatorTagView::TYPE_STATE); if ($config->getIsEnabled()) { $status_tag ->setName(pht('Enabled')) ->setBackgroundColor('green'); } else { $status_tag ->setName(pht('Disabled')) ->setBackgroundColor('red'); } $form = id(new AphrontFormView()) ->setUser($viewer) ->setFlexible(true) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Provider')) ->setValue($provider->getProviderName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Status')) ->setValue($status_tag)) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel(pht('Allow')) ->addCheckbox( 'allowRegistration', 1, $str_registration, $v_registration)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'allowLink', 1, $str_link, $v_link)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'allowUnlink', 1, $str_unlink, $v_unlink)); - $provider->extendEditForm($form); + $provider->extendEditForm($request, $form, $properties, $issues); $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($button)); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($crumb)); $xaction_view = null; if (!$is_new) { $xactions = id(new PhabricatorAuthProviderConfigTransactionQuery()) ->withObjectPHIDs(array($config->getPHID())) ->setViewer($viewer) ->execute(); + foreach ($xactions as $xaction) { + $xaction->setProvider($provider); + } + $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($viewer) ->setTransactions($xactions); } return $this->buildApplicationPage( array( $crumbs, $errors, $form, $xaction_view, ), array( 'title' => $title, 'dust' => true, 'device' => true, )); } } diff --git a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php index d6694609a3..5088519ce5 100644 --- a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php @@ -1,97 +1,99 @@ getTransactionType()) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: if ($object->getIsEnabled() === null) { return null; } else { return (int)$object->getIsEnabled(); } case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: return (int)$object->getShouldAllowRegistration(); case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: return (int)$object->getShouldAllowLink(); case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: return (int)$object->getShouldAllowUnlink(); case PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY: - // TODO - throw new Exception("TODO"); + $key = $xaction->getMetadataValue( + PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); + return $object->getProperty($key); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: case PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY: return $xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $v = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: return $object->setIsEnabled($v); case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: return $object->setShouldAllowRegistration($v); case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: return $object->setShouldAllowLink($v); case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: return $object->setShouldAllowUnlink($v); case PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY: - // TODO - throw new Exception("TODO"); + $key = $xaction->getMetadataValue( + PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); + return $object->setProperty($key, $v); } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return; } protected function mergeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $type = $u->getTransactionType(); switch ($type) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: // For these types, last transaction wins. return $v; } return parent::mergeTransactions($u, $v); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index aeb3a6dfdf..8c347b5099 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -1,261 +1,288 @@ providerConfig = $config; return $this; } public function getProviderConfig() { if ($this->config === null) { throw new Exception( "Call attachProviderConfig() before getProviderConfig()!"); } return $this->config; } 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(); $providers = array(); $from_class_map = array(); foreach ($objects as $object) { $from_class = get_class($object); $object_providers = $object->createProviders(); assert_instances_of($object_providers, 'PhabricatorAuthProvider'); foreach ($object_providers as $provider) { $key = $provider->getProviderKey(); if (isset($providers[$key])) { $first_class = $from_class_map[$key]; throw new Exception( "PhabricatorAuthProviders '{$first_class}' and '{$from_class}' ". "both created authentication providers identified by key ". "'{$key}'. Provider keys must be unique."); } $providers[$key] = $provider; $from_class_map[$key] = $from_class; } } } 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 true; } abstract public function shouldAllowLogin(); abstract public function shouldAllowRegistration(); abstract public function shouldAllowAccountLink(); abstract public function shouldAllowAccountUnlink(); 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'); } protected function renderLoginForm( AphrontRequest $request, $mode) { throw new Exception("Not implemented!"); } - public function extendEditForm(AphrontFormView $form) { - - } - - 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; + } + } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php index f0c12c1f82..6e76ee1ebb 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php @@ -1,204 +1,296 @@ getProviderName()); } public function getAdapter() { if (!$this->adapter) { $adapter = $this->newOAuthAdapter(); $this->adapter = $adapter; $this->configureAdapter($adapter); } return $this->adapter; } public function isEnabled() { return parent::isEnabled() && $this->getOAuthClientID() && $this->getOAuthClientSecret(); } protected function configureAdapter(PhutilAuthAdapterOAuth $adapter) { if ($this->getOAuthClientID()) { $adapter->setClientID($this->getOAuthClientID()); } if ($this->getOAuthClientSecret()) { $adapter->setClientSecret($this->getOAuthClientSecret()); } $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 ($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, )); } return phabricator_form( $viewer, array( 'method' => 'GET', 'action' => (string)$uri, ), $content); } 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); } - public function extendEditForm( - AphrontFormView $form) { - - $v_id = $this->getOAuthClientID(); + const PROPERTY_APP_ID = 'oauth:app:id'; + const PROPERTY_APP_SECRET = 'oauth:app:secret'; + public function readFormValuesFromProvider() { $secret = $this->getOAuthClientSecret(); if ($secret) { - $v_secret = str_repeat('*', strlen($secret->openEnvelope())); - } else { - $v_secret = ''; + $secret = $secret->openEnvelope(); + } + + return array( + self::PROPERTY_APP_ID => $this->getOAuthClientID(), + 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'); } - $e_id = strlen($v_id) ? null : true; - $e_secret = strlen($v_secret) ? null : true; + if (!strlen($values[$key_secret])) { + $errors[] = pht('Application secret is required.'); + $issues[$key_id] = 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('oauth:app:id') + ->setName($key_id) ->setValue($v_id) ->setError($e_id)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel(pht('OAuth App Secret')) - ->setName('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); } } diff --git a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php index def9791a60..23c825caf1 100644 --- a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php +++ b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php @@ -1,127 +1,147 @@ provider = $provider; + return $this; + } + + public function getProvider() { + return $this->provider; + } + public function getApplicationName() { return 'auth'; } public function getApplicationTransactionType() { return PhabricatorPHIDConstants::PHID_TYPE_AUTH; } public function getApplicationTransactionCommentObject() { return null; } public function getApplicationObjectTypeName() { return pht('authentication provider'); } public function getIcon() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_ENABLE: if ($new) { return 'new'; } else { return 'delete'; } } return parent::getIcon(); } public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_ENABLE: if ($new) { return 'green'; } else { return 'red'; } } return parent::getColor(); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_ENABLE: if ($old === null) { return pht( '%s created this provider.', $this->renderHandleLink($author_phid)); } else if ($new) { return pht( '%s enabled this provider.', $this->renderHandleLink($author_phid)); } else { return pht( '%s disabled this provider.', $this->renderHandleLink($author_phid)); } break; case self::TYPE_REGISTRATION: if ($new) { return pht( '%s enabled registration.', $this->renderHandleLink($author_phid)); } else { return pht( '%s disabled registration.', $this->renderHandleLink($author_phid)); } break; case self::TYPE_LINK: if ($new) { return pht( '%s enabled accont linking.', $this->renderHandleLink($author_phid)); } else { return pht( '%s disabled account linking.', $this->renderHandleLink($author_phid)); } break; case self::TYPE_UNLINK: if ($new) { return pht( '%s enabled account unlinking.', $this->renderHandleLink($author_phid)); } else { return pht( '%s disabled account unlinking.', $this->renderHandleLink($author_phid)); } break; case self::TYPE_PROPERTY: - // TODO + $provider = $this->getProvider(); + if ($provider) { + $title = $provider->renderConfigPropertyTransactionTitle($this); + if (strlen($title)) { + return $title; + } + } + return pht( '%s edited a property of this provider.', $this->renderHandleLink($author_phid)); break; } return parent::getTitle(); } } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 64fd98d675..0874b357d0 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1,420 +1,420 @@ metadata, $key, $default); } public function setMetadataValue($key, $value) { $this->metadata[$key] = $value; return $this; } public function generatePHID() { $type = PhabricatorPHIDConstants::PHID_TYPE_XACT; $subtype = $this->getApplicationTransactionType(); return PhabricatorPHID::generateNewPHID($type, $subtype); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'oldValue' => self::SERIALIZATION_JSON, 'newValue' => self::SERIALIZATION_JSON, 'metadata' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source->serialize(); return $this; } public function getContentSource() { return PhabricatorContentSource::newFromSerialized($this->contentSource); } public function hasComment() { return $this->getComment() && strlen($this->getComment()->getContent()); } public function getComment() { if ($this->commentNotLoaded) { throw new Exception("Comment for this transaction was not loaded."); } return $this->comment; } public function attachComment( PhabricatorApplicationTransactionComment $comment) { $this->comment = $comment; $this->commentNotLoaded = false; return $this; } public function setCommentNotLoaded($not_loaded) { $this->commentNotLoaded = $not_loaded; return $this; } /* -( Rendering )---------------------------------------------------------- */ public function setRenderingTarget($rendering_target) { $this->renderingTarget = $rendering_target; return $this; } public function getRenderingTarget() { return $this->renderingTarget; } public function getRequiredHandlePHIDs() { $phids = array(); $old = $this->getOldValue(); $new = $this->getNewValue(); $phids[] = array($this->getAuthorPHID()); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: $phids[] = $old; $phids[] = $new; break; case PhabricatorTransactions::TYPE_EDGE: $phids[] = ipull($old, 'dst'); $phids[] = ipull($new, 'dst'); break; case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_VIEW_POLICY: if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) { $phids[] = array($old); } if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) { $phids[] = array($new); } break; } return array_mergev($phids); } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function getHandle($phid) { if (empty($this->handles[$phid])) { throw new Exception( "Transaction requires a handle ('{$phid}') it did not load."); } return $this->handles[$phid]; } public function getHandleIfExists($phid) { return idx($this->handles, $phid); } public function getHandles() { if ($this->handles === null) { throw new Exception( 'Transaction requires handles and it did not load them.' ); } return $this->handles; } - protected function renderHandleLink($phid) { + public function renderHandleLink($phid) { if ($this->renderingTarget == self::TARGET_HTML) { return $this->getHandle($phid)->renderLink(); } else { return hsprintf('%s', $this->getHandle($phid)->getName()); } } - protected function renderHandleList(array $phids) { + public function renderHandleList(array $phids) { $links = array(); foreach ($phids as $phid) { $links[] = $this->renderHandleLink($phid); } return phutil_implode_html(', ', $links); } public function getIcon() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return 'comment'; case PhabricatorTransactions::TYPE_SUBSCRIBERS: return 'message'; case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return 'lock'; case PhabricatorTransactions::TYPE_EDGE: return 'link'; } return null; } public function getColor() { return null; } public function shouldHide() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: if ($this->getOldValue() === null) { return true; } else { return false; } break; } return false; } public function getNoEffectDescription() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('You can not post an empty comment.'); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( 'This %s already has that view policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( 'This %s already has that edit policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( 'All users are already subscribed to this %s.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_EDGE: return pht('Edges already exist; transaction has no effect.'); } return pht('Transaction has no effect.'); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment.', $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility of this %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName(), PhabricatorPolicy::newFromPolicyAndHandle( $old, $this->getHandleIfExists($old))->renderDescription(), PhabricatorPolicy::newFromPolicyAndHandle( $new, $this->getHandleIfExists($new))->renderDescription()); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( '%s changed the edit policy of this %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName(), PhabricatorPolicy::newFromPolicyAndHandle( $old, $this->getHandleIfExists($old))->renderDescription(), PhabricatorPolicy::newFromPolicyAndHandle( $new, $this->getHandleIfExists($new))->renderDescription()); case PhabricatorTransactions::TYPE_SUBSCRIBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return pht( '%s edited subscriber(s), added %d: %s; removed %d: %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add), count($rem), $this->renderHandleList($rem)); } else if ($add) { return pht( '%s added %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add)); } else { return pht( '%s removed %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($rem), $this->renderHandleList($rem)); } break; case PhabricatorTransactions::TYPE_EDGE: $type = $this->getMetadata('edge:type'); return pht( '%s edited edges of type %s.', $this->renderHandleLink($author_phid), $type); default: return pht( '%s edited this %s.', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName()); } } public function getTitleForFeed() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( '%s changed the edit policy for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( '%s updated subscribers of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_EDGE: return pht( '%s updated edges of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } return $this->getTitle(); } public function getActionStrength() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return 0.5; } return 1.0; } public function getActionName() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('Commented On'); case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht('Changed Policy'); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht('Changed Subscribers'); default: return pht('Updated'); } } public function getMailTags() { return array(); } public function hasChangeDetails() { return false; } public function renderChangeDetails(PhabricatorUser $viewer) { return null; } public function attachTransactionGroup(array $group) { assert_instances_of($group, 'PhabricatorApplicationTransaction'); $this->transactionGroup = $group; return $this; } public function getTransactionGroup() { return $this->transactionGroup; } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return ($viewer->getPHID() == $this->getAuthorPHID()); } }