diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php index c06beaa4e9..1f50db00aa 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php @@ -1,267 +1,262 @@ 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); + $config = $provider->getDefaultProviderConfig(); + $provider->attachProviderConfig($config); $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 (!$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_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)); foreach ($properties as $key => $value) { $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY) ->setMetadataValue('auth:property', $key) ->setNewValue($value); } $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()) { + if ($is_new) { + $status_tag + ->setName(pht('New Provider')) + ->setBackgroundColor('blue'); + } else 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($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/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 8c347b5099..3674c94c89 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -1,288 +1,357 @@ providerConfig = $config; return $this; } + public function hasProviderConfig() { + return (bool)$this->providerConfig; + } + public function getProviderConfig() { - if ($this->config === null) { + if ($this->providerConfig === null) { throw new Exception( "Call attachProviderConfig() before getProviderConfig()!"); } - return $this->config; + return $this->providerConfig; + } + + 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(); - $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 ($configs) { + 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])) { - $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."); + pht( + "Two authentication providers use the same provider key ". + "('%s'). Each provider must be identified by a unique ". + "key.", + $key)); + } + $providers[$key] = $object; + } + } else { + // TODO: Remove this once we transition to be completely database + // driven. + $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; } - $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() { + if ($this->hasProviderConfig()) { + return $this->getProviderConfig()->getIsEnabled(); + } + return true; + } + + public function shouldAllowLogin() { + if ($this->hasProviderConfig()) { + return $this->getProviderConfig()->getShouldAllowLogin(); + } return true; } - abstract public function shouldAllowLogin(); - abstract public function shouldAllowRegistration(); - abstract public function shouldAllowAccountLink(); - abstract public function shouldAllowAccountUnlink(); + public function shouldAllowRegistration() { + if ($this->hasProviderConfig()) { + return $this->getProviderConfig()->getShouldAllowRegistration(); + } + return true; + } + + public function shouldAllowAccountLink() { + if ($this->hasProviderConfig()) { + return $this->getProviderConfig()->getShouldAllowLink(); + } + return true; + } + + public function shouldAllowAccountUnlink() { + if ($this->hasProviderConfig()) { + return $this->getProviderConfig()->getShouldAllowUnlink(); + } + return true; + } 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 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/PhabricatorAuthProviderLDAP.php b/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php index 8f88990023..9f21b8c9af 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php @@ -1,415 +1,408 @@ setProperty(self::KEY_PORT, 389) + ->setProperty(self::KEY_VERSION, 3); + } public function isEnabled() { + if ($this->hasProviderConfig()) { + return parent::isEnabled(); + } + return parent::isEnabled() && PhabricatorEnv::getEnvConfig('ldap.auth-enabled'); } public function getAdapter() { if (!$this->adapter) { $adapter = id(new PhutilAuthAdapterLDAP()) ->setHostname(PhabricatorEnv::getEnvConfig('ldap.hostname')) ->setPort(PhabricatorEnv::getEnvConfig('ldap.port')) ->setBaseDistinguishedName(PhabricatorEnv::getEnvConfig('ldap.base_dn')) ->setSearchAttribute( PhabricatorEnv::getEnvConfig('ldap.search_attribute')) ->setUsernameAttribute( PhabricatorEnv::getEnvConfig('ldap.username-attribute')) ->setRealNameAttributes( PhabricatorEnv::getEnvConfig('ldap.real_name_attributes')) ->setLDAPVersion(PhabricatorEnv::getEnvConfig('ldap.version')) ->setLDAPReferrals(PhabricatorEnv::getEnvConfig('ldap.referrals')) ->setLDAPStartTLS(PhabricatorEnv::getEnvConfig('ldap.start-tls')) ->setAnonymousUsername( PhabricatorEnv::getEnvConfig('ldap.anonymous-user-name')) ->setAnonymousPassword( new PhutilOpaqueEnvelope( PhabricatorEnv::getEnvConfig('ldap.anonymous-user-password'))) ->setSearchFirst(PhabricatorEnv::getEnvConfig('ldap.search-first')) ->setActiveDirectoryDomain( PhabricatorEnv::getEnvConfig('ldap.activedirectory_domain')); $this->adapter = $adapter; } return $this->adapter; } - public function shouldAllowLogin() { - return true; - } - - public function shouldAllowRegistration() { - return true; - } - - public function shouldAllowAccountLink() { - return true; - } - - public function shouldAllowAccountUnlink() { - return true; - } - protected function renderLoginForm(AphrontRequest $request, $mode) { $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) ->setSubmitURI($this->getLoginURI()) ->setUser($viewer); if ($mode == 'link') { $dialog->setTitle(pht('Link LDAP Account')); $dialog->addSubmitButton(pht('Link Accounts')); $dialog->addCancelButton($this->getSettingsURI()); } else { if ($this->shouldAllowRegistration()) { $dialog->setTitle(pht('Login or Register with LDAP')); $dialog->addSubmitButton(pht('Login or Register')); } else { $dialog->setTitle(pht('Login with LDAP')); $dialog->addSubmitButton(pht('Login')); } if ($mode == 'login') { $dialog->addCancelButton($this->getStartURI()); } } $v_user = $request->getStr('ldap_username'); $e_user = null; $e_pass = null; $errors = array(); if ($request->isHTTPPost()) { // NOTE: This is intentionally vague so as not to disclose whether a // given username exists. $e_user = pht('Invalid'); $e_pass = pht('Invalid'); $errors[] = pht('Username or password are incorrect.'); } $form = id(new AphrontFormLayoutView()) ->setUser($viewer) ->setFullWidth(true) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('LDAP Username') ->setName('ldap_username') ->setValue($v_user) ->setError($e_user)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('LDAP Password') ->setName('ldap_password') ->setError($e_pass)); if ($errors) { $errors = id(new AphrontErrorView())->setErrors($errors); } $dialog->appendChild($errors); $dialog->appendChild($form); return $dialog; } public function processLoginRequest( PhabricatorAuthLoginController $controller) { $request = $controller->getRequest(); $viewer = $request->getUser(); $response = null; $account = null; $username = $request->getStr('ldap_username'); $password = $request->getStr('ldap_password'); $has_password = strlen($password); $password = new PhutilOpaqueEnvelope($password); if (!strlen($username) || !$has_password) { $response = $controller->buildProviderPageResponse( $this, $this->renderLoginForm($request, 'login')); return array($account, $response); } try { if (strlen($username) && $has_password) { $adapter = $this->getAdapter(); $adapter->setLoginUsername($username); $adapter->setLoginPassword($password); // TODO: This calls ldap_bind() eventually, which dumps cleartext // passwords to the error log. See note in PhutilAuthAdapterLDAP. // See T3351. DarkConsoleErrorLogPluginAPI::enableDiscardMode(); $account_id = $adapter->getAccountID(); DarkConsoleErrorLogPluginAPI::disableDiscardMode(); } else { throw new Exception("Username and password are required!"); } } catch (Exception $ex) { // TODO: Make this cleaner. throw $ex; } return array($this->loadOrCreateAccount($account_id), $response); } const KEY_HOSTNAME = 'ldap:host'; const KEY_PORT = 'ldap:port'; const KEY_DISTINGUISHED_NAME = 'ldap:dn'; const KEY_SEARCH_ATTRIBUTE = 'ldap:search-attribute'; const KEY_USERNAME_ATTRIBUTE = 'ldap:username-attribute'; const KEY_REALNAME_ATTRIBUTES = 'ldap:realname-attributes'; const KEY_VERSION = 'ldap:version'; const KEY_REFERRALS = 'ldap:referrals'; const KEY_START_TLS = 'ldap:start-tls'; const KEY_ANONYMOUS_USERNAME = 'ldap:anoynmous-username'; const KEY_ANONYMOUS_PASSWORD = 'ldap:anonymous-password'; const KEY_SEARCH_FIRST = 'ldap:search-first'; const KEY_ACTIVEDIRECTORY_DOMAIN = 'ldap:activedirectory-domain'; private function getPropertyKeys() { return array( self::KEY_HOSTNAME, self::KEY_PORT, self::KEY_DISTINGUISHED_NAME, self::KEY_SEARCH_ATTRIBUTE, self::KEY_USERNAME_ATTRIBUTE, self::KEY_VERSION, self::KEY_REFERRALS, self::KEY_START_TLS, self::KEY_ANONYMOUS_USERNAME, self::KEY_ANONYMOUS_PASSWORD, self::KEY_SEARCH_FIRST, self::KEY_ACTIVEDIRECTORY_DOMAIN, ); } private function getPropertyLabels() { return array( self::KEY_HOSTNAME => pht('LDAP Hostname'), self::KEY_PORT => pht('LDAP Port'), self::KEY_DISTINGUISHED_NAME => pht('Base Distinguished Name'), self::KEY_SEARCH_ATTRIBUTE => pht('Search Attribute'), self::KEY_USERNAME_ATTRIBUTE => pht('Username Attribute'), self::KEY_REALNAME_ATTRIBUTES => pht('Realname Attributes'), self::KEY_VERSION => pht('LDAP Version'), self::KEY_REFERRALS => pht('Enable Referrals'), self::KEY_START_TLS => pht('Use TLS'), self::KEY_SEARCH_FIRST => pht('Search First'), self::KEY_ANONYMOUS_USERNAME => pht('Anonymous Username'), self::KEY_ANONYMOUS_PASSWORD => pht('Anonymous Password'), self::KEY_ACTIVEDIRECTORY_DOMAIN => pht('ActiveDirectory Domain'), ); } public function readFormValuesFromProvider() { return array( self::KEY_HOSTNAME => PhabricatorEnv::getEnvConfig('ldap.hostname'), self::KEY_PORT => PhabricatorEnv::getEnvConfig('ldap.port'), self::KEY_DISTINGUISHED_NAME => PhabricatorEnv::getEnvConfig('ldap.base_dn'), self::KEY_SEARCH_ATTRIBUTE => PhabricatorEnv::getEnvConfig('ldap.search_attribute'), self::KEY_USERNAME_ATTRIBUTE => PhabricatorEnv::getEnvConfig('ldap.username-attribute'), self::KEY_REALNAME_ATTRIBUTES => PhabricatorEnv::getEnvConfig('ldap.real_name_attributes'), self::KEY_VERSION => PhabricatorEnv::getEnvConfig('ldap.version'), self::KEY_REFERRALS => PhabricatorEnv::getEnvConfig('ldap.referrals'), self::KEY_START_TLS => PhabricatorEnv::getEnvConfig('ldap.start-tls'), self::KEY_ANONYMOUS_USERNAME => PhabricatorEnv::getEnvConfig('ldap.anonymous-user-name'), self::KEY_ANONYMOUS_PASSWORD => PhabricatorEnv::getEnvConfig('ldap.anonymous-user-password'), self::KEY_SEARCH_FIRST => PhabricatorEnv::getEnvConfig('ldap.search-first'), self::KEY_ACTIVEDIRECTORY_DOMAIN => PhabricatorEnv::getEnvConfig('ldap.activedirectory_domain'), ); } public function readFormValuesFromRequest(AphrontRequest $request) { $values = array(); foreach ($this->getPropertyKeys() as $key) { switch ($key) { case self::KEY_REALNAME_ATTRIBUTES: $values[$key] = $request->getStrList($key); break; default: $values[$key] = $request->getStr($key); break; } } return $values; } 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) { $labels = $this->getPropertyLabels(); $captions = array( self::KEY_HOSTNAME => pht('Example: %s', hsprintf('%s', pht('ldap.example.com'))), self::KEY_DISTINGUISHED_NAME => pht('Example: %s', hsprintf('%s', pht('ou=People, dc=example, dc=com'))), self::KEY_SEARCH_ATTRIBUTE => pht('Example: %s', hsprintf('%s', pht('sn'))), self::KEY_USERNAME_ATTRIBUTE => pht('Optional, if different from search attribute.'), self::KEY_REALNAME_ATTRIBUTES => pht('Optional. Example: %s', hsprintf('%s', pht('firstname, lastname'))), self::KEY_REFERRALS => pht('Follow referrals. Disable this for Windows AD 2003.'), self::KEY_START_TLS => pht('Start TLS after binding to the LDAP server.'), self::KEY_SEARCH_FIRST => pht( 'When the user enters their username, search for a matching '. 'record using the "Search Attribute", then try to bind using '. 'the DN for the record. This is useful if usernames are not '. 'part of the record DN.'), self::KEY_ANONYMOUS_USERNAME => pht('Username to bind with before searching.'), self::KEY_ANONYMOUS_PASSWORD => pht('Password to bind with before searching.'), ); $types = array( self::KEY_REFERRALS => 'checkbox', self::KEY_START_TLS => 'checkbox', self::KEY_SEARCH_FIRST => 'checkbox', self::KEY_REALNAME_ATTRIBUTES => 'list', self::KEY_ANONYMOUS_PASSWORD => 'password', ); foreach ($labels as $key => $label) { $caption = idx($captions, $key); $type = idx($types, $key); $value = idx($values, $key); $control = null; switch ($type) { case 'checkbox': $control = id(new AphrontFormCheckboxControl()) ->addCheckbox( $key, 1, hsprintf('%s: %s', $label, $caption), $value); break; case 'list': $control = id(new AphrontFormTextControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setValue(implode(', ', $value)); break; case 'password': $control = id(new AphrontFormPasswordControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setValue($value); break; default: $control = id(new AphrontFormTextControl()) ->setName($key) ->setLabel($label) ->setCaption($caption) ->setValue($value); break; } $form->appendChild($control); } } public function renderConfigPropertyTransactionTitle( PhabricatorAuthProviderConfigTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $key = $xaction->getMetadataValue( PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); $labels = $this->getPropertyLabels(); if (isset($labels[$key])) { $label = $labels[$key]; $mask = false; switch ($key) { case self::KEY_ANONYMOUS_PASSWORD: $mask = true; break; } if ($mask) { return pht( '%s updated the "%s" value.', $xaction->renderHandleLink($author_phid), $label); } if (!strlen($old)) { return pht( '%s set the "%s" value to "%s".', $xaction->renderHandleLink($author_phid), $label, $new); } else { return pht( '%s changed the "%s" value from "%s" to "%s".', $xaction->renderHandleLink($author_phid), $label, $old, $new); } } return parent::renderConfigPropertyTransactionTitle($xaction); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php index 6e76ee1ebb..883cd9099e 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth.php @@ -1,296 +1,317 @@ getProviderName()); } public function getAdapter() { if (!$this->adapter) { $adapter = $this->newOAuthAdapter(); $this->adapter = $adapter; $this->configureAdapter($adapter); } return $this->adapter; } public function isEnabled() { + if ($this->hasProviderConfig()) { + return parent::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()); + if ($this->hasProviderConfig()) { + $config = $this->getProviderConfig(); + $adapter->setClientID($config->getProperty(self::PROPERTY_APP_ID)); + $adapter->setClientSecret( + new PhutilOpaqueEnvelope( + $config->getProperty(self::PROPERTY_APP_SECRET))); + } else { + 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); } const PROPERTY_APP_ID = 'oauth:app:id'; const PROPERTY_APP_SECRET = 'oauth:app:secret'; public function readFormValuesFromProvider() { - $secret = $this->getOAuthClientSecret(); - if ($secret) { - $secret = $secret->openEnvelope(); + + if ($this->hasProviderConfig()) { + $config = $this->getProviderConfig(); + $id = $config->getProperty(self::PROPERTY_APP_ID); + $secret = $config->getProperty(self::PROPERTY_APP_SECRET); + } else { + $id = $this->getOAuthClientID(); + $secret = $this->getOAuthClientSecret(); + if ($secret) { + $secret = $secret->openEnvelope(); + } } return array( - self::PROPERTY_APP_ID => $this->getOAuthClientID(), + 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_id] = pht('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); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php index eb866c6121..3f4a88ed09 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthDisqus.php @@ -1,51 +1,54 @@ hasProviderConfig()) { + return parent::isEnabled(); + } + return parent::isEnabled() && PhabricatorEnv::getEnvConfig('disqus.auth-enabled'); } protected function getOAuthClientID() { return PhabricatorEnv::getEnvConfig('disqus.application-id'); } protected function getOAuthClientSecret() { $secret = PhabricatorEnv::getEnvConfig('disqus.application-secret'); if ($secret) { return new PhutilOpaqueEnvelope($secret); } return null; } - public function shouldAllowLogin() { - return true; - } - public function shouldAllowRegistration() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowRegistration(); + } return PhabricatorEnv::getEnvConfig('disqus.registration-enabled'); } - public function shouldAllowAccountLink() { - return true; - } - public function shouldAllowAccountUnlink() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowAccountUnlink(); + } + return !PhabricatorEnv::getEnvConfig('disqus.auth-permanent'); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php index 607e5aff9d..cfb8d84a6b 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php @@ -1,127 +1,134 @@ setProperty(self::KEY_REQUIRE_SECURE, 1); + } + protected function newOAuthAdapter() { $secure_only = PhabricatorEnv::getEnvConfig('facebook.require-https-auth'); return id(new PhutilAuthAdapterOAuthFacebook()) ->setRequireSecureBrowsing($secure_only); } protected function getLoginIcon() { return 'Facebook'; } public function isEnabled() { + if ($this->hasProviderConfig()) { + return parent::isEnabled(); + } + return parent::isEnabled() && PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); } protected function getOAuthClientID() { return PhabricatorEnv::getEnvConfig('facebook.application-id'); } protected function getOAuthClientSecret() { $secret = PhabricatorEnv::getEnvConfig('facebook.application-secret'); if ($secret) { return new PhutilOpaqueEnvelope($secret); } return null; } - public function shouldAllowLogin() { - return true; - } - public function shouldAllowRegistration() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowRegistration(); + } return PhabricatorEnv::getEnvConfig('facebook.registration-enabled'); } - public function shouldAllowAccountLink() { - return true; - } - public function shouldAllowAccountUnlink() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowAccountUnlink(); + } return !PhabricatorEnv::getEnvConfig('facebook.auth-permanent'); } public function readFormValuesFromProvider() { $require_secure = PhabricatorEnv::getEnvConfig( 'facebook.require-https-auth'); // TODO: When we read from config, default this on for new providers. return parent::readFormValuesFromProvider() + array( self::KEY_REQUIRE_SECURE => $require_secure, ); } public function readFormValuesFromRequest(AphrontRequest $request) { return parent::readFormValuesFromRequest($request) + array( self::KEY_REQUIRE_SECURE => $request->getBool(self::KEY_REQUIRE_SECURE), ); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { parent::extendEditForm($request, $form, $values, $issues); $key_require = self::KEY_REQUIRE_SECURE; $v_require = idx($values, $key_require); $form ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( $key_require, $v_require, pht( "%s ". "Require users to enable 'secure browsing' on Facebook in order ". "to use Facebook to authenticate with Phabricator. This ". "improves security by preventing an attacker from capturing ". "an insecure Facebook session and escalating it into a ". "Phabricator session. Enabling it is recommended.", hsprintf( '%s', pht('Require Secure Browsing:'))))); } 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::KEY_REQUIRE_SECURE: if ($new) { return pht( '%s turned "Require Secure Browsing" on.', $xaction->renderHandleLink($author_phid)); } else { return pht( '%s turned "Require Secure Browsing" off.', $xaction->renderHandleLink($author_phid)); } } return parent::renderConfigPropertyTransactionTitle($xaction); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php index fdb6bdeccd..f0c09fb25a 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGitHub.php @@ -1,51 +1,53 @@ hasProviderConfig()) { + return parent::isEnabled(); + } + return parent::isEnabled() && PhabricatorEnv::getEnvConfig('github.auth-enabled'); } protected function getOAuthClientID() { return PhabricatorEnv::getEnvConfig('github.application-id'); } protected function getOAuthClientSecret() { $secret = PhabricatorEnv::getEnvConfig('github.application-secret'); if ($secret) { return new PhutilOpaqueEnvelope($secret); } return null; } - public function shouldAllowLogin() { - return true; - } - public function shouldAllowRegistration() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowRegistration(); + } return PhabricatorEnv::getEnvConfig('github.registration-enabled'); } - public function shouldAllowAccountLink() { - return true; - } - public function shouldAllowAccountUnlink() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowAccountUnlink(); + } return !PhabricatorEnv::getEnvConfig('github.auth-permanent'); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php index a9dfd98820..70ea28fa7e 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php @@ -1,56 +1,57 @@ hasProviderConfig()) { + return parent::isEnabled(); + } return parent::isEnabled() && PhabricatorEnv::getEnvConfig('google.auth-enabled'); } protected function getOAuthClientID() { return PhabricatorEnv::getEnvConfig('google.application-id'); } protected function getOAuthClientSecret() { $secret = PhabricatorEnv::getEnvConfig('google.application-secret'); if ($secret) { return new PhutilOpaqueEnvelope($secret); } return null; } - public function shouldAllowLogin() { - return true; - } - public function shouldAllowRegistration() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowRegistration(); + } return PhabricatorEnv::getEnvConfig('google.registration-enabled'); } - public function shouldAllowAccountLink() { - return true; - } - public function shouldAllowAccountUnlink() { + if ($this->hasProviderConfig()) { + return parent::shouldAllowAccountUnlink(); + } return !PhabricatorEnv::getEnvConfig('google.auth-permanent'); } public function getLoginURI() { // TODO: Clean this up. See PhabricatorAuthOldOAuthRedirectController. return PhabricatorEnv::getURI('/oauth/google/login/'); } } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderPassword.php b/src/applications/auth/provider/PhabricatorAuthProviderPassword.php index 42a4e813f6..6795ad64a0 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderPassword.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderPassword.php @@ -1,239 +1,239 @@ hasProviderConfig()) { + return parent::isEnabled(); + } + return parent::isEnabled() && PhabricatorEnv::getEnvConfig('auth.password-auth-enabled'); } public function getAdapter() { if (!$this->adapter) { $adapter = new PhutilAuthAdapterEmpty(); $adapter->setAdapterType('password'); $adapter->setAdapterDomain('self'); $this->adapter = $adapter; } return $this->adapter; } public function getLoginOrder() { // Make sure username/password appears first if it is enabled. return '100-'.$this->getProviderName(); } - public function shouldAllowLogin() { - return true; - } - public function shouldAllowRegistration() { // TODO: Hard code this as "false" for now so we don't inadvertantly open // up password registration where it did not previously exist. return false; } public function shouldAllowAccountLink() { return false; } public function shouldAllowAccountUnlink() { return false; } public function isDefaultRegistrationProvider() { return true; } public function buildLoginForm( PhabricatorAuthStartController $controller) { $request = $controller->getRequest(); return $this->renderPasswordLoginForm($request); } public function buildLinkForm( PhabricatorAuthLinkController $controller) { throw new Exception("Password providers can't be linked."); } private function renderPasswordLoginForm( AphrontRequest $request, $require_captcha = false, $captcha_valid = false) { $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) ->setSubmitURI($this->getLoginURI()) ->setUser($viewer) ->setTitle(pht('Login to Phabricator')) ->addSubmitButton(pht('Login')); if ($this->shouldAllowRegistration()) { $dialog->addCancelButton( '/auth/register/', pht('Register New Account')); } $dialog->addFooter( phutil_tag( 'a', array( 'href' => '/login/email/', ), pht('Forgot your password?'))); $v_user = nonempty( $request->getStr('username'), $request->getCookie('phusr')); $e_user = null; $e_pass = null; $e_captcha = null; $errors = array(); if ($require_captcha && !$captcha_valid) { if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) { $e_captcha = pht('Invalid'); $errors[] = pht('CAPTCHA was not entered correctly.'); } else { $e_captcha = pht('Required'); $errors[] = pht('Too many login failures recently. You must '. 'submit a CAPTCHA with your login request.'); } } else if ($request->isHTTPPost()) { // NOTE: This is intentionally vague so as not to disclose whether a // given username or email is registered. $e_user = pht('Invalid'); $e_pass = pht('Invalid'); $errors[] = pht('Username or password are incorrect.'); } if ($errors) { $errors = id(new AphrontErrorView())->setErrors($errors); } $form = id(new AphrontFormLayoutView()) ->setFullWidth(true) ->appendChild($errors) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username or Email') ->setName('username') ->setValue($v_user) ->setError($e_user)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('Password') ->setName('password') ->setError($e_pass)); if ($require_captcha) { $form->appendChild( id(new AphrontFormRecaptchaControl()) ->setError($e_captcha)); } $dialog->appendChild($form); return $dialog; } public function processLoginRequest( PhabricatorAuthLoginController $controller) { $request = $controller->getRequest(); $viewer = $request->getUser(); $require_captcha = false; $captcha_valid = false; if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) { $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( PhabricatorUserLog::ACTION_LOGIN_FAILURE, 60 * 15); if (count($failed_attempts) > 5) { $require_captcha = true; $captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request); } } $response = null; $account = null; $log_user = null; if (!$require_captcha || $captcha_valid) { $username_or_email = $request->getStr('username'); if (strlen($username_or_email)) { $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $username_or_email); if (!$user) { $user = PhabricatorUser::loadOneWithEmailAddress($username_or_email); } if ($user) { $envelope = new PhutilOpaqueEnvelope($request->getStr('password')); if ($user->comparePassword($envelope)) { $account = $this->loadOrCreateAccount($user->getPHID()); $log_user = $user; } } } } if (!$account) { $log = PhabricatorUserLog::newLog( null, $log_user, PhabricatorUserLog::ACTION_LOGIN_FAILURE); $log->save(); $request->clearCookie('phusr'); $request->clearCookie('phsid'); $response = $controller->buildProviderPageResponse( $this, $this->renderPasswordLoginForm( $request, $require_captcha, $captcha_valid)); } return array($account, $response); } public function shouldRequireRegistrationPassword() { return true; } public function getDefaultExternalAccount() { $adapter = $this->getAdapter(); return id(new PhabricatorExternalAccount()) ->setAccountType($adapter->getAdapterType()) ->setAccountDomain($adapter->getAdapterDomain()); } protected function willSaveAccount(PhabricatorExternalAccount $account) { parent::willSaveAccount($account); $account->setUserPHID($account->getAccountID()); } public function willRegisterAccount(PhabricatorExternalAccount $account) { parent::willRegisterAccount($account); $account->setAccountID($account->getUserPHID()); } }