mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-30 02:32:42 +01:00
Generalize login flows for new registration
Summary: Ref T1536. None of this code is reachable. `PhabricatorAuthLoginController` provides a completely generic login/link flow, similar to how D6155 provides a generic registration flow. `PhabricatorAuthProvider` wraps a `PhutilAuthAdapter` and glues the generic top-level flow to a concrete authentication provider. Test Plan: Static only, code isn't meaningfully reachable. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T1536 Differential Revision: https://secure.phabricator.com/D6159
This commit is contained in:
parent
db1cf41ec4
commit
c05ee9ed68
5 changed files with 258 additions and 0 deletions
|
@ -815,6 +815,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuditReplyHandler' => 'applications/audit/mail/PhabricatorAuditReplyHandler.php',
|
||||
'PhabricatorAuditStatusConstants' => 'applications/audit/constants/PhabricatorAuditStatusConstants.php',
|
||||
'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php',
|
||||
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
|
||||
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
|
||||
'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php',
|
||||
'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php',
|
||||
'PhabricatorBarePageExample' => 'applications/uiexample/examples/PhabricatorBarePageExample.php',
|
||||
|
@ -2669,6 +2671,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuditPreviewController' => 'PhabricatorAuditController',
|
||||
'PhabricatorAuditReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'PhabricatorAuthController' => 'PhabricatorController',
|
||||
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
|
||||
'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
|
||||
'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorBarePageExample' => 'PhabricatorUIExample',
|
||||
|
|
|
@ -32,6 +32,7 @@ final class PhabricatorApplicationAuth extends PhabricatorApplication {
|
|||
public function getRoutes() {
|
||||
return array(
|
||||
'/auth/' => array(
|
||||
'login/(?P<pkey>[^/]+)/' => 'PhabricatorAuthLoginController',
|
||||
'register/(?P<akey>[^/]+)/' => 'PhabricatorAuthRegisterController',
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthLoginController
|
||||
extends PhabricatorAuthController {
|
||||
|
||||
private $providerKey;
|
||||
private $provider;
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->providerKey = $data['pkey'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$response = $this->loadProvider();
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$provider = $this->provider;
|
||||
|
||||
list($account, $response) = $provider->processLoginRequest($this);
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($account->getUserPHID()) {
|
||||
// The account is already attached to a Phabricator user, so this is
|
||||
// either a login or a bad account link request.
|
||||
if (!$viewer->isLoggedIn()) {
|
||||
if ($provider->shouldAllowLogin()) {
|
||||
return $this->processLoginUser($account);
|
||||
} else {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'The external account ("%s") you just authenticated with is '.
|
||||
'not configured to allow logins on this Phabricator install. '.
|
||||
'An administrator may have recently disabled it.',
|
||||
$provider->getProviderName()));
|
||||
}
|
||||
} else if ($viewer->getPHID() == $account->getUserPHID()) {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'This external account ("%s") is already linked to your '.
|
||||
'Phabricator account.'));
|
||||
} else {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'The external account ("%s") you just used to login is alerady '.
|
||||
'associated with another Phabricator user account. Login to the '.
|
||||
'other Phabricator account and unlink the external account before '.
|
||||
'linking it to a new Phabricator account.',
|
||||
$provider->getProviderName()));
|
||||
}
|
||||
} else {
|
||||
// The account is not yet attached to a Phabricator user, so this is
|
||||
// either a registration or an account link request.
|
||||
if (!$viewer->isLoggedIn()) {
|
||||
if ($provider->shouldAllowRegistration()) {
|
||||
return $this->processRegisterUser($account);
|
||||
} else {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'The external account ("%s") you just authenticated with is '.
|
||||
'not configured to allow registration on this Phabricator '.
|
||||
'install. An administrator may have recently disabled it.',
|
||||
$provider->getProviderName()));
|
||||
}
|
||||
} else {
|
||||
if ($provider->shouldAllowAccountLink()) {
|
||||
return $this->processLinkUser($account);
|
||||
} else {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'The external account ("%s") you just authenticated with is '.
|
||||
'not configured to allow account linking on this Phabricator '.
|
||||
'install. An administrator may have recently disabled it.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should be unreachable, but fail explicitly if we get here somehow.
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
private function processLoginUser(PhabricatorExternalAccount $account) {
|
||||
// TODO: Implement.
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
private function processRegisterUser(PhabricatorExternalAccount $account) {
|
||||
if ($account->getUserPHID()) {
|
||||
throw new Exception("Account is already registered.");
|
||||
}
|
||||
|
||||
// Regenerate the registration secret key, set it on the external account,
|
||||
// set a cookie on the user's machine, and redirect them to registration.
|
||||
// See PhabricatorAuthRegisterController for discussion of the registration
|
||||
// key.
|
||||
|
||||
$registration_key = Filesystem::readRandomCharacters(32);
|
||||
$account->setProperty('registrationKey', $registration_key);
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$account->save();
|
||||
unset($unguarded);
|
||||
|
||||
$this->getRequest()->setCookie('phreg', $registration_key);
|
||||
|
||||
$account_secret = $account->getAccountSecret();
|
||||
$register_uri = $this->getApplicationURI('register/'.$account_secret.'/');
|
||||
return id(new AphrontRedirectResponse())->setURI($register_uri);
|
||||
}
|
||||
|
||||
private function processLinkUser(PhabricatorExternalAccount $account) {
|
||||
// TODO: Implement.
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
private function loadProvider() {
|
||||
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
|
||||
$this->providerKey);
|
||||
|
||||
if (!$provider) {
|
||||
return $this->renderError(
|
||||
pht(
|
||||
'The account you are attempting to login with uses a nonexistent '.
|
||||
'or disabled authentication provider (with key "%s"). An '.
|
||||
'administrator may have recently disabled this provider.',
|
||||
$this->providerKey));
|
||||
}
|
||||
|
||||
$this->provider = $provider;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function renderError($message) {
|
||||
$title = pht('Login Failed');
|
||||
|
||||
$view = new AphrontErrorView();
|
||||
$view->setTitle($title);
|
||||
$view->appendChild($message);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$view,
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
'dust' => true,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
81
src/applications/auth/provider/PhabricatorAuthProvider.php
Normal file
81
src/applications/auth/provider/PhabricatorAuthProvider.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorAuthProvider {
|
||||
|
||||
private $adapter;
|
||||
|
||||
public function setAdapater(PhutilAuthAdapter $adapter) {
|
||||
$this->adapter = $adapter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAdapater() {
|
||||
if ($this->adapter === null) {
|
||||
throw new Exception("Call setAdapter() before getAdapter()!");
|
||||
}
|
||||
return $this->adapter;
|
||||
}
|
||||
|
||||
public function getProviderKey() {
|
||||
return $this->getAdapter()->getAdapterKey();
|
||||
}
|
||||
|
||||
public static function getAllProviders() {
|
||||
static $providers;
|
||||
|
||||
if ($providers === null) {
|
||||
$objects = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->loadObjects();
|
||||
|
||||
$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 getEnabledProviders() {
|
||||
$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::getEnabledProviders(), $provider_key);
|
||||
}
|
||||
|
||||
abstract public function getProviderName();
|
||||
abstract public function isEnabled();
|
||||
abstract public function shouldAllowLogin();
|
||||
abstract public function shouldAllowRegistration();
|
||||
abstract public function shouldAllowAccountLink();
|
||||
abstract public function processLoginRequest(
|
||||
PhabricatorAuthLoginController $controller);
|
||||
|
||||
public function createProviders() {
|
||||
return array($this);
|
||||
}
|
||||
|
||||
}
|
|
@ -37,6 +37,10 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO {
|
|||
return $tmp_usr;
|
||||
}
|
||||
|
||||
public function getProviderKey() {
|
||||
return $this->getAccountType().':'.$this->accountDomain();
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if (!$this->getAccountSecret()) {
|
||||
$this->setAccountSecret(Filesystem::readRandomCharacters(32));
|
||||
|
@ -44,4 +48,13 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO {
|
|||
return parent::save();
|
||||
}
|
||||
|
||||
public function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue