diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php index 0adfcc8bdc..f1c84c6e91 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php @@ -1,220 +1,230 @@ providerClass = idx($data, 'className'); $this->configID = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); - $provider = null; 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()) { $xactions = array(); 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_REGISTRATION) - ->setNewValue($request->getInt('allowRegistration')); + ->setNewValue($request->getInt('allowRegistration', 0)); $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_LINK) - ->setNewValue($request->getInt('allowLink')); + ->setNewValue($request->getInt('allowLink', 0)); $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK) - ->setNewValue($request->getInt('allowUnlink')); + ->setNewValue($request->getInt('allowUnlink', 0)); if (!$errors) { $editor = id(new PhabricatorAuthProviderConfigEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->applyTransactions($config, $xactions); return id(new AphrontRedirectResponse())->setURI( $this->getApplicationURI()); } } 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.')); $form = id(new AphrontFormView()) ->setUser($viewer) ->setFlexible(true) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Provider')) ->setValue($provider->getProviderName())) ->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); $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()); - // TOOD: ... + $xactions = id(new PhabricatorAuthProviderConfigTransactionQuery()) + ->withObjectPHIDs(array($config->getPHID())) + ->setViewer($viewer) + ->execute(); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setTransactions($xactions); } return $this->buildApplicationPage( array( $crumbs, $errors, $form, - $xaction_view + $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 a32d23f04d..d6694609a3 100644 --- a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php @@ -1,94 +1,97 @@ getTransactionType()) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: - return $object->getIsEnabled(); + if ($object->getIsEnabled() === null) { + return null; + } else { + return (int)$object->getIsEnabled(); + } case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: - return $object->getShouldAllowRegistration(); + return (int)$object->getShouldAllowRegistration(); case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: - return $object->getShouldAllowLink(); + return (int)$object->getShouldAllowLink(); case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: - return $object->getShouldAllowUnlink(); + return (int)$object->getShouldAllowUnlink(); case PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY: // TODO throw new Exception("TODO"); } } 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"); } } 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 6db66a08a6..3d05da8a76 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -1,235 +1,250 @@ 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(), $is_link = false); } abstract public function processLoginRequest( PhabricatorAuthLoginController $controller); public function buildLinkForm( PhabricatorAuthLinkController $controller) { return $this->renderLoginForm($controller->getRequest(), $is_link = true); } protected function renderLoginForm( AphrontRequest $request, $is_link) { 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()); 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. $image_uri = $adapter->getAccountImageURI(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $image_file = PhabricatorFile::newFromFileDownload( $image_uri, array( 'name' => $name, )); unset($unguarded); $account->setProfileImagePHID($image_file->getPHID()); } catch (Exception $ex) { $account->setProfileImagePHID(null); } $this->willSaveAccount($account); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $account->save(); unset($unguarded); return $account; } protected function getLoginURI() { $app = PhabricatorApplication::getByClass('PhabricatorApplicationAuth'); $uri = $app->getApplicationURI('/login/'.$this->getProviderKey().'/'); return PhabricatorEnv::getURI($uri); } protected function getCancelLinkURI() { return '/settings/panel/external/'; } 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; } } diff --git a/src/applications/auth/storage/PhabricatorAuthProviderConfig.php b/src/applications/auth/storage/PhabricatorAuthProviderConfig.php index 75c72c89c0..b4595d96d0 100644 --- a/src/applications/auth/storage/PhabricatorAuthProviderConfig.php +++ b/src/applications/auth/storage/PhabricatorAuthProviderConfig.php @@ -1,65 +1,84 @@ true, self::CONFIG_SERIALIZATION => array( 'properties' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function getProperty($key, $default = null) { return idx($this->properties, $key, $default); } public function setProperty($key, $value) { $this->properties[$key] = $value; return $this; } + public function getProvider() { + if (!$this->provider) { + $base = PhabricatorAuthProvider::getAllBaseProviders(); + $found = null; + foreach ($base as $provider) { + if (get_class($provider) == $this->providerClass) { + $found = $provider; + break; + } + } + if ($found) { + $this->provider = id(clone $found)->attachProviderConfig($this); + } + } + return $this->provider; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::POLICY_USER; case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_ADMIN; } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } } diff --git a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php index ed36b8394a..def9791a60 100644 --- a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php +++ b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php @@ -1,29 +1,127 @@ 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 + return pht( + '%s edited a property of this provider.', + $this->renderHandleLink($author_phid)); + break; + } + + return parent::getTitle(); + } + }