diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php index d3cd2fef98..f602c4fb24 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php @@ -1,366 +1,398 @@ requireApplicationCapability( AuthManageProvidersCapability::CAPABILITY); $viewer = $this->getViewer(); $provider_class = $request->getStr('provider'); $config_id = $request->getURIData('id'); if ($config_id) { $config = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($config_id)) ->executeOne(); if (!$config) { return new Aphront404Response(); } $provider = $config->getProvider(); if (!$provider) { return new Aphront404Response(); } $is_new = false; } else { $provider = null; $providers = PhabricatorAuthProvider::getAllBaseProviders(); foreach ($providers as $candidate_provider) { if (get_class($candidate_provider) === $provider_class) { $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) { $id = head($configs)->getID(); $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setMethod('GET') ->setSubmitURI($this->getApplicationURI('config/edit/'.$id.'/')) ->setTitle(pht('Provider Already Configured')) ->appendChild( pht( 'This provider ("%s") already exists, and you can not add more '. 'than one instance of it. You can edit the existing provider, '. 'or you can choose a different provider.', $provider->getProviderName())) ->addCancelButton($this->getApplicationURI('config/new/')) ->addSubmitButton(pht('Edit Existing Provider')); return id(new AphrontDialogResponse())->setDialog($dialog); } $config = $provider->getDefaultProviderConfig(); $provider->attachProviderConfig($config); $is_new = true; } $errors = array(); + $validation_exception = null; $v_login = $config->getShouldAllowLogin(); $v_registration = $config->getShouldAllowRegistration(); $v_link = $config->getShouldAllowLink(); $v_unlink = $config->getShouldAllowUnlink(); $v_trust_email = $config->getShouldTrustEmails(); $v_auto_login = $config->getShouldAutoLogin(); if ($request->isFormPost()) { $properties = $provider->readFormValuesFromRequest($request); list($errors, $issues, $properties) = $provider->processEditForm( $request, $properties); $xactions = array(); if (!$errors) { if ($is_new) { if (!strlen($config->getProviderType())) { $config->setProviderType($provider->getProviderType()); } if (!strlen($config->getProviderDomain())) { $config->setProviderDomain($provider->getProviderDomain()); } } $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN) ->setNewValue($request->getInt('allowLogin', 0)); $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_UNLINK) ->setNewValue($request->getInt('allowUnlink', 0)); $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_TRUST_EMAILS) ->setNewValue($request->getInt('trustEmails', 0)); if ($provider->supportsAutoLogin()) { $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_AUTO_LOGIN) ->setNewValue($request->getInt('autoLogin', 0)); } foreach ($properties as $key => $value) { $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY) ->setMetadataValue('auth:property', $key) ->setNewValue($value); } if ($is_new) { $config->save(); } $editor = id(new PhabricatorAuthProviderConfigEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->applyTransactions($config, $xactions); + ->setContinueOnNoEffect(true); - $next_uri = $config->getURI(); + try { + $editor->applyTransactions($config, $xactions); + $next_uri = $config->getURI(); - return id(new AphrontRedirectResponse())->setURI($next_uri); + return id(new AphrontRedirectResponse())->setURI($next_uri); + } catch (Exception $ex) { + $validation_exception = $ex; + } } } else { $properties = $provider->readFormValuesFromProvider(); $issues = array(); } if ($is_new) { if ($provider->hasSetupStep()) { $button = pht('Next Step'); } else { $button = pht('Add Provider'); } $crumb = pht('Add Provider'); $title = pht('Add Auth Provider'); $header_icon = 'fa-plus-square'; $cancel_uri = $this->getApplicationURI('/config/new/'); } else { $button = pht('Save'); $crumb = pht('Edit Provider'); $title = pht('Edit Auth Provider'); $header_icon = 'fa-pencil'; $cancel_uri = $config->getURI(); } $header = id(new PHUIHeaderView()) ->setHeader(pht('%s: %s', $title, $provider->getProviderName())) ->setHeaderIcon($header_icon); if (!$is_new) { if ($config->getIsEnabled()) { $status_name = pht('Enabled'); $status_color = 'green'; $status_icon = 'fa-check'; $header->setStatus($status_icon, $status_color, $status_name); } else { $status_name = pht('Disabled'); $status_color = 'indigo'; $status_icon = 'fa-ban'; $header->setStatus($status_icon, $status_color, $status_name); } } $config_name = 'auth.email-domains'; $config_href = '/config/edit/'.$config_name.'/'; $email_domains = PhabricatorEnv::getEnvConfig($config_name); if ($email_domains) { $registration_warning = pht( 'Users will only be able to register with a verified email address '. 'at one of the configured [[ %s | %s ]] domains: **%s**', $config_href, $config_name, implode(', ', $email_domains)); } else { $registration_warning = pht( "NOTE: Any user who can browse to this install's login page will be ". "able to register a Phabricator account. To restrict who can register ". "an account, configure [[ %s | %s ]].", $config_href, $config_name); } $str_login = array( phutil_tag('strong', array(), pht('Allow Login:')), ' ', pht( 'Allow users to log in using this provider. If you disable login, '. 'users can still use account integrations for this provider.'), ); $str_registration = array( phutil_tag('strong', array(), 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.')); $str_trusted_email = hsprintf( '%s: %s', pht('Trust Email Addresses'), pht( 'Phabricator will skip email verification for accounts registered '. 'through this provider.')); $str_auto_login = hsprintf( '%s: %s', pht('Allow Auto Login'), pht( 'Phabricator will automatically login with this provider if it is '. 'the only available provider.')); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('provider', $provider_class) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel(pht('Allow')) ->addCheckbox( 'allowLogin', 1, $str_login, $v_login)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'allowRegistration', 1, $str_registration, $v_registration)) ->appendRemarkupInstructions($registration_warning) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'allowLink', 1, $str_link, $v_link)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'allowUnlink', 1, $str_unlink, $v_unlink)); if ($provider->shouldAllowEmailTrustConfiguration()) { $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'trustEmails', 1, $str_trusted_email, $v_trust_email)); } if ($provider->supportsAutoLogin()) { $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'autoLogin', 1, $str_auto_login, $v_auto_login)); } $provider->extendEditForm($request, $form, $properties, $issues); + $locked_config_key = 'auth.lock-config'; + $is_locked = PhabricatorEnv::getEnvConfig($locked_config_key); + + $locked_warning = null; + if ($is_locked && !$validation_exception) { + $message = pht( + 'Authentication provider configuration is locked, and can not be '. + 'changed without being unlocked. See the configuration setting %s '. + 'for details.', + phutil_tag( + 'a', + array( + 'href' => '/config/edit/'.$locked_config_key, + ), + $locked_config_key)); + $locked_warning = id(new PHUIInfoView()) + ->setViewer($viewer) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors(array($message)); + } + $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) + ->setDisabled($is_locked) ->setValue($button)); + $help = $provider->getConfigurationHelp(); if ($help) { $form->appendChild(id(new PHUIFormDividerControl())); $form->appendRemarkupInstructions($help); } $footer = $provider->renderConfigurationFooter(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($crumb); $crumbs->setBorder(true); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Provider')) ->setFormErrors($errors) + ->setValidationException($validation_exception) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( + $locked_warning, $form_box, $footer, )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } } diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php index b6ba91e7cd..5d1d85cca6 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -1,120 +1,122 @@ getViewer(); $configs = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) ->execute(); $list = new PHUIObjectItemListView(); $can_manage = $this->hasApplicationCapability( AuthManageProvidersCapability::CAPABILITY); $is_locked = PhabricatorEnv::getEnvConfig('auth.lock-config'); foreach ($configs as $config) { $item = new PHUIObjectItemView(); $id = $config->getID(); $view_uri = $config->getURI(); $provider = $config->getProvider(); $name = $provider->getProviderName(); $item ->setHeader($name) ->setHref($view_uri); $domain = $provider->getProviderDomain(); if ($domain !== 'self') { $item->addAttribute($domain); } if ($config->getShouldAllowRegistration()) { $item->addAttribute(pht('Allows Registration')); } else { $item->addAttribute(pht('Does Not Allow Registration')); } if ($config->getIsEnabled()) { $item->setStatusIcon('fa-check-circle green'); } else { $item->setStatusIcon('fa-ban red'); $item->addIcon('fa-ban grey', pht('Disabled')); } $list->addItem($item); } $list->setNoDataString( pht( '%s You have not added authentication providers yet. Use "%s" to add '. 'a provider, which will let users register new Phabricator accounts '. 'and log in.', phutil_tag( 'strong', array(), pht('No Providers Configured:')), phutil_tag( 'a', array( 'href' => $this->getApplicationURI('config/new/'), ), pht('Add Authentication Provider')))); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Login and Registration')); $crumbs->setBorder(true); $guidance_context = id(new PhabricatorAuthProvidersGuidanceContext()) ->setCanManage($can_manage); $guidance = id(new PhabricatorGuidanceEngine()) ->setViewer($viewer) ->setGuidanceContext($guidance_context) ->newInfoView(); + $is_disabled = (!$can_manage || $is_locked); $button = id(new PHUIButtonView()) ->setTag('a') ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) - ->setHref($this->getApplicationURI('config/new/')) ->setIcon('fa-plus') - ->setDisabled(!$can_manage || $is_locked) + ->setDisabled($is_disabled) + ->setWorkflow($is_disabled) + ->setHref($this->getApplicationURI('config/new/')) ->setText(pht('Add Provider')); $list->setFlush(true); $list = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Providers')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($list); $title = pht('Login and Registration Providers'); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setHeaderIcon('fa-key') ->addActionLink($button); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter( array( $guidance, $list, )); $nav = $this->newNavigation() ->setCrumbs($crumbs) ->appendChild($view); $nav->selectFilter('login'); return $this->newPage() ->setTitle($title) ->appendChild($nav); } } diff --git a/src/applications/auth/controller/config/PhabricatorAuthNewController.php b/src/applications/auth/controller/config/PhabricatorAuthNewController.php index 770c43208d..cb1c537ca8 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthNewController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthNewController.php @@ -1,74 +1,95 @@ requireApplicationCapability( AuthManageProvidersCapability::CAPABILITY); $viewer = $this->getViewer(); $cancel_uri = $this->getApplicationURI(); + $locked_config_key = 'auth.lock-config'; + $is_locked = PhabricatorEnv::getEnvConfig($locked_config_key); + + if ($is_locked) { + $message = pht( + 'Authentication provider configuration is locked, and can not be '. + 'changed without being unlocked. See the configuration setting %s '. + 'for details.', + phutil_tag( + 'a', + array( + 'href' => '/config/edit/'.$locked_config_key, + ), + $locked_config_key)); + + return $this->newDialog() + ->setUser($viewer) + ->setTitle(pht('Authentication Config Locked')) + ->appendChild($message) + ->addCancelButton($cancel_uri); + } $providers = PhabricatorAuthProvider::getAllBaseProviders(); $configured = PhabricatorAuthProvider::getAllProviders(); $configured_classes = array(); foreach ($configured as $configured_provider) { $configured_classes[get_class($configured_provider)] = true; } // Sort providers by login order, and move disabled providers to the // bottom. $providers = msort($providers, 'getLoginOrder'); $providers = array_diff_key($providers, $configured_classes) + $providers; $menu = id(new PHUIObjectItemListView()) ->setViewer($viewer) ->setBig(true) ->setFlush(true); foreach ($providers as $provider_key => $provider) { $provider_class = get_class($provider); $provider_uri = id(new PhutilURI('/config/edit/')) ->replaceQueryParam('provider', $provider_class); $provider_uri = $this->getApplicationURI($provider_uri); $already_exists = isset($configured_classes[get_class($provider)]); $item = id(new PHUIObjectItemView()) ->setHeader($provider->getNameForCreate()) ->setImageIcon($provider->newIconView()) ->addAttribute($provider->getDescriptionForCreate()); if (!$already_exists) { $item ->setHref($provider_uri) ->setClickable(true); } else { $item->setDisabled(true); } if ($already_exists) { $messages = array(); $messages[] = pht('You already have a provider of this type.'); $info = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors($messages); $item->appendChild($info); } $menu->addItem($item); } return $this->newDialog() ->setTitle(pht('Add Auth Provider')) ->setWidth(AphrontDialogView::WIDTH_FORM) ->appendChild($menu) ->addCancelButton($cancel_uri); } } diff --git a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php index 5599ff5364..1e75edfbf0 100644 --- a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php @@ -1,128 +1,149 @@ getTransactionType()) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: if ($object->getIsEnabled() === null) { return null; } else { return (int)$object->getIsEnabled(); } case PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN: return (int)$object->getShouldAllowLogin(); 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_TRUST_EMAILS: return (int)$object->getShouldTrustEmails(); case PhabricatorAuthProviderConfigTransaction::TYPE_AUTO_LOGIN: return (int)$object->getShouldAutoLogin(); case PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY: $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_LOGIN: case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: case PhabricatorAuthProviderConfigTransaction::TYPE_TRUST_EMAILS: case PhabricatorAuthProviderConfigTransaction::TYPE_AUTO_LOGIN: 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_LOGIN: return $object->setShouldAllowLogin($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_TRUST_EMAILS: return $object->setShouldTrustEmails($v); case PhabricatorAuthProviderConfigTransaction::TYPE_AUTO_LOGIN: return $object->setShouldAutoLogin($v); case PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY: $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_LOGIN: case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: case PhabricatorAuthProviderConfigTransaction::TYPE_TRUST_EMAILS: case PhabricatorAuthProviderConfigTransaction::TYPE_AUTO_LOGIN: // For these types, last transaction wins. return $v; } return parent::mergeTransactions($u, $v); } + protected function validateAllTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + $errors = parent::validateAllTransactions($object, $xactions); + + $locked_config_key = 'auth.lock-config'; + $is_locked = PhabricatorEnv::getEnvConfig($locked_config_key); + + if ($is_locked) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + null, + pht('Config Locked'), + pht('Authentication provider configuration is locked, and can not be '. + 'changed without being unlocked.'), + null); + } + + return $errors; + } + }