2011-01-26 22:21:12 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
abstract class PhabricatorAuthController extends PhabricatorController {
|
|
|
|
|
|
|
|
public function buildStandardPageResponse($view, array $data) {
|
|
|
|
$page = $this->buildStandardPageView();
|
|
|
|
|
2013-01-27 01:17:44 +01:00
|
|
|
$page->setApplicationName(pht('Login'));
|
2011-01-26 22:21:12 +01:00
|
|
|
$page->setBaseURI('/login/');
|
|
|
|
$page->setTitle(idx($data, 'title'));
|
|
|
|
$page->appendChild($view);
|
|
|
|
|
|
|
|
$response = new AphrontWebpageResponse();
|
|
|
|
return $response->setContent($page->render());
|
|
|
|
}
|
|
|
|
|
New Registration Workflow
Summary:
Currently, registration and authentication are pretty messy. Two concrete problems:
- The `PhabricatorLDAPRegistrationController` and `PhabricatorOAuthDefaultRegistrationController` controllers are giant copy/pastes of one another. This is really bad.
- We can't practically implement OpenID because we can't reissue the authentication request.
Additionally, the OAuth registration controller can be replaced wholesale by config, which is a huge API surface area and a giant mess.
Broadly, the problem right now is that registration does too much: we hand it some set of indirect credentials (like OAuth tokens) and expect it to take those the entire way to a registered user. Instead, break registration into smaller steps:
- User authenticates with remote service.
- Phabricator pulls information (remote account ID, username, email, real name, profile picture, etc) from the remote service and saves it as `PhabricatorUserCredentials`.
- Phabricator hands the `PhabricatorUserCredentials` to the registration form, which is agnostic about where they originate from: it can process LDAP credentials, OAuth credentials, plain old email credentials, HTTP basic auth credentials, etc.
This doesn't do anything yet -- there is no way to create credentials objects (and no storage patch), but I wanted to get any initial feedback, especially about the event call for T2394. In particular, I think the implementation would look something like this:
$profile = $event->getValue('profile')
$username = $profile->getDefaultUsername();
$is_employee = is_this_a_facebook_employee($username);
if (!$is_employee) {
throw new Exception("You are not employed at Facebook.");
}
$fbid = get_fbid_for_facebook_username($username);
$profile->setDefaultEmail($fbid);
$profile->setCanEditUsername(false);
$profile->setCanEditEmail(false);
$profile->setCanEditRealName(false);
$profile->setShouldVerifyEmail(true);
Seem reasonable?
Test Plan: N/A yet, probably fatals.
Reviewers: vrana, btrahan, codeblock, chad
Reviewed By: btrahan
CC: aran, asherkin, nh, wez
Maniphest Tasks: T1536, T2394
Differential Revision: https://secure.phabricator.com/D4647
2013-06-16 19:13:49 +02:00
|
|
|
protected function renderErrorPage($title, array $messages) {
|
|
|
|
$view = new AphrontErrorView();
|
|
|
|
$view->setTitle($title);
|
|
|
|
$view->setErrors($messages);
|
|
|
|
|
|
|
|
return $this->buildApplicationPage(
|
|
|
|
$view,
|
|
|
|
array(
|
|
|
|
'title' => $title,
|
|
|
|
'device' => true,
|
|
|
|
));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-06-20 01:28:48 +02:00
|
|
|
/**
|
|
|
|
* Returns true if this install is newly setup (i.e., there are no user
|
|
|
|
* accounts yet). In this case, we enter a special mode to permit creation
|
|
|
|
* of the first account form the web UI.
|
|
|
|
*/
|
|
|
|
protected function isFirstTimeSetup() {
|
|
|
|
// If there are any auth providers, this isn't first time setup, even if
|
|
|
|
// we don't have accounts.
|
|
|
|
if (PhabricatorAuthProvider::getAllEnabledProviders()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, check if there are any user accounts. If not, we're in first
|
|
|
|
// time setup.
|
|
|
|
$any_users = id(new PhabricatorPeopleQuery())
|
|
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
|
|
->setLimit(1)
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
return !$any_users;
|
|
|
|
}
|
|
|
|
|
2013-06-17 15:12:45 +02:00
|
|
|
|
2013-06-17 01:35:36 +02:00
|
|
|
/**
|
|
|
|
* Log a user into a web session and return an @{class:AphrontResponse} which
|
|
|
|
* corresponds to continuing the login process.
|
|
|
|
*
|
|
|
|
* Normally, this is a redirect to the validation controller which makes sure
|
|
|
|
* the user's cookies are set. However, event listeners can intercept this
|
|
|
|
* event and do something else if they prefer.
|
|
|
|
*
|
|
|
|
* @param PhabricatorUser User to log the viewer in as.
|
|
|
|
* @return AphrontResponse Response which continues the login process.
|
|
|
|
*/
|
|
|
|
protected function loginUser(PhabricatorUser $user) {
|
|
|
|
|
|
|
|
$response = $this->buildLoginValidateResponse($user);
|
2014-01-14 22:22:34 +01:00
|
|
|
$session_type = PhabricatorAuthSession::TYPE_WEB;
|
2013-06-17 01:35:36 +02:00
|
|
|
|
|
|
|
$event_type = PhabricatorEventType::TYPE_AUTH_WILLLOGINUSER;
|
|
|
|
$event_data = array(
|
|
|
|
'user' => $user,
|
|
|
|
'type' => $session_type,
|
|
|
|
'response' => $response,
|
|
|
|
'shouldLogin' => true,
|
|
|
|
);
|
|
|
|
|
|
|
|
$event = id(new PhabricatorEvent($event_type, $event_data))
|
|
|
|
->setUser($user);
|
|
|
|
PhutilEventEngine::dispatchEvent($event);
|
|
|
|
|
|
|
|
$should_login = $event->getValue('shouldLogin');
|
|
|
|
if ($should_login) {
|
2014-01-14 22:22:27 +01:00
|
|
|
$session_key = id(new PhabricatorAuthSessionEngine())
|
|
|
|
->establishSession($session_type, $user->getPHID());
|
2013-06-17 01:35:36 +02:00
|
|
|
|
|
|
|
// NOTE: We allow disabled users to login and roadblock them later, so
|
|
|
|
// there's no check for users being disabled here.
|
|
|
|
|
|
|
|
$request = $this->getRequest();
|
2014-01-23 23:01:35 +01:00
|
|
|
$request->setCookie(
|
|
|
|
PhabricatorCookies::COOKIE_USERNAME,
|
|
|
|
$user->getUsername());
|
|
|
|
$request->setCookie(
|
|
|
|
PhabricatorCookies::COOKIE_SESSION,
|
|
|
|
$session_key);
|
2013-06-17 01:35:36 +02:00
|
|
|
|
|
|
|
$this->clearRegistrationCookies();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $event->getValue('response');
|
|
|
|
}
|
New Registration Workflow
Summary:
Currently, registration and authentication are pretty messy. Two concrete problems:
- The `PhabricatorLDAPRegistrationController` and `PhabricatorOAuthDefaultRegistrationController` controllers are giant copy/pastes of one another. This is really bad.
- We can't practically implement OpenID because we can't reissue the authentication request.
Additionally, the OAuth registration controller can be replaced wholesale by config, which is a huge API surface area and a giant mess.
Broadly, the problem right now is that registration does too much: we hand it some set of indirect credentials (like OAuth tokens) and expect it to take those the entire way to a registered user. Instead, break registration into smaller steps:
- User authenticates with remote service.
- Phabricator pulls information (remote account ID, username, email, real name, profile picture, etc) from the remote service and saves it as `PhabricatorUserCredentials`.
- Phabricator hands the `PhabricatorUserCredentials` to the registration form, which is agnostic about where they originate from: it can process LDAP credentials, OAuth credentials, plain old email credentials, HTTP basic auth credentials, etc.
This doesn't do anything yet -- there is no way to create credentials objects (and no storage patch), but I wanted to get any initial feedback, especially about the event call for T2394. In particular, I think the implementation would look something like this:
$profile = $event->getValue('profile')
$username = $profile->getDefaultUsername();
$is_employee = is_this_a_facebook_employee($username);
if (!$is_employee) {
throw new Exception("You are not employed at Facebook.");
}
$fbid = get_fbid_for_facebook_username($username);
$profile->setDefaultEmail($fbid);
$profile->setCanEditUsername(false);
$profile->setCanEditEmail(false);
$profile->setCanEditRealName(false);
$profile->setShouldVerifyEmail(true);
Seem reasonable?
Test Plan: N/A yet, probably fatals.
Reviewers: vrana, btrahan, codeblock, chad
Reviewed By: btrahan
CC: aran, asherkin, nh, wez
Maniphest Tasks: T1536, T2394
Differential Revision: https://secure.phabricator.com/D4647
2013-06-16 19:13:49 +02:00
|
|
|
|
2013-06-17 01:35:36 +02:00
|
|
|
protected function clearRegistrationCookies() {
|
New Registration Workflow
Summary:
Currently, registration and authentication are pretty messy. Two concrete problems:
- The `PhabricatorLDAPRegistrationController` and `PhabricatorOAuthDefaultRegistrationController` controllers are giant copy/pastes of one another. This is really bad.
- We can't practically implement OpenID because we can't reissue the authentication request.
Additionally, the OAuth registration controller can be replaced wholesale by config, which is a huge API surface area and a giant mess.
Broadly, the problem right now is that registration does too much: we hand it some set of indirect credentials (like OAuth tokens) and expect it to take those the entire way to a registered user. Instead, break registration into smaller steps:
- User authenticates with remote service.
- Phabricator pulls information (remote account ID, username, email, real name, profile picture, etc) from the remote service and saves it as `PhabricatorUserCredentials`.
- Phabricator hands the `PhabricatorUserCredentials` to the registration form, which is agnostic about where they originate from: it can process LDAP credentials, OAuth credentials, plain old email credentials, HTTP basic auth credentials, etc.
This doesn't do anything yet -- there is no way to create credentials objects (and no storage patch), but I wanted to get any initial feedback, especially about the event call for T2394. In particular, I think the implementation would look something like this:
$profile = $event->getValue('profile')
$username = $profile->getDefaultUsername();
$is_employee = is_this_a_facebook_employee($username);
if (!$is_employee) {
throw new Exception("You are not employed at Facebook.");
}
$fbid = get_fbid_for_facebook_username($username);
$profile->setDefaultEmail($fbid);
$profile->setCanEditUsername(false);
$profile->setCanEditEmail(false);
$profile->setCanEditRealName(false);
$profile->setShouldVerifyEmail(true);
Seem reasonable?
Test Plan: N/A yet, probably fatals.
Reviewers: vrana, btrahan, codeblock, chad
Reviewed By: btrahan
CC: aran, asherkin, nh, wez
Maniphest Tasks: T1536, T2394
Differential Revision: https://secure.phabricator.com/D4647
2013-06-16 19:13:49 +02:00
|
|
|
$request = $this->getRequest();
|
|
|
|
|
2013-06-16 19:18:56 +02:00
|
|
|
// Clear the registration key.
|
2014-01-23 23:01:35 +01:00
|
|
|
$request->clearCookie(PhabricatorCookies::COOKIE_REGISTRATION);
|
2013-06-16 19:18:56 +02:00
|
|
|
|
|
|
|
// Clear the client ID / OAuth state key.
|
2014-01-23 23:01:35 +01:00
|
|
|
$request->clearCookie(PhabricatorCookies::COOKIE_CLIENTID);
|
New Registration Workflow
Summary:
Currently, registration and authentication are pretty messy. Two concrete problems:
- The `PhabricatorLDAPRegistrationController` and `PhabricatorOAuthDefaultRegistrationController` controllers are giant copy/pastes of one another. This is really bad.
- We can't practically implement OpenID because we can't reissue the authentication request.
Additionally, the OAuth registration controller can be replaced wholesale by config, which is a huge API surface area and a giant mess.
Broadly, the problem right now is that registration does too much: we hand it some set of indirect credentials (like OAuth tokens) and expect it to take those the entire way to a registered user. Instead, break registration into smaller steps:
- User authenticates with remote service.
- Phabricator pulls information (remote account ID, username, email, real name, profile picture, etc) from the remote service and saves it as `PhabricatorUserCredentials`.
- Phabricator hands the `PhabricatorUserCredentials` to the registration form, which is agnostic about where they originate from: it can process LDAP credentials, OAuth credentials, plain old email credentials, HTTP basic auth credentials, etc.
This doesn't do anything yet -- there is no way to create credentials objects (and no storage patch), but I wanted to get any initial feedback, especially about the event call for T2394. In particular, I think the implementation would look something like this:
$profile = $event->getValue('profile')
$username = $profile->getDefaultUsername();
$is_employee = is_this_a_facebook_employee($username);
if (!$is_employee) {
throw new Exception("You are not employed at Facebook.");
}
$fbid = get_fbid_for_facebook_username($username);
$profile->setDefaultEmail($fbid);
$profile->setCanEditUsername(false);
$profile->setCanEditEmail(false);
$profile->setCanEditRealName(false);
$profile->setShouldVerifyEmail(true);
Seem reasonable?
Test Plan: N/A yet, probably fatals.
Reviewers: vrana, btrahan, codeblock, chad
Reviewed By: btrahan
CC: aran, asherkin, nh, wez
Maniphest Tasks: T1536, T2394
Differential Revision: https://secure.phabricator.com/D4647
2013-06-16 19:13:49 +02:00
|
|
|
}
|
|
|
|
|
2013-06-17 01:35:36 +02:00
|
|
|
private function buildLoginValidateResponse(PhabricatorUser $user) {
|
New Registration Workflow
Summary:
Currently, registration and authentication are pretty messy. Two concrete problems:
- The `PhabricatorLDAPRegistrationController` and `PhabricatorOAuthDefaultRegistrationController` controllers are giant copy/pastes of one another. This is really bad.
- We can't practically implement OpenID because we can't reissue the authentication request.
Additionally, the OAuth registration controller can be replaced wholesale by config, which is a huge API surface area and a giant mess.
Broadly, the problem right now is that registration does too much: we hand it some set of indirect credentials (like OAuth tokens) and expect it to take those the entire way to a registered user. Instead, break registration into smaller steps:
- User authenticates with remote service.
- Phabricator pulls information (remote account ID, username, email, real name, profile picture, etc) from the remote service and saves it as `PhabricatorUserCredentials`.
- Phabricator hands the `PhabricatorUserCredentials` to the registration form, which is agnostic about where they originate from: it can process LDAP credentials, OAuth credentials, plain old email credentials, HTTP basic auth credentials, etc.
This doesn't do anything yet -- there is no way to create credentials objects (and no storage patch), but I wanted to get any initial feedback, especially about the event call for T2394. In particular, I think the implementation would look something like this:
$profile = $event->getValue('profile')
$username = $profile->getDefaultUsername();
$is_employee = is_this_a_facebook_employee($username);
if (!$is_employee) {
throw new Exception("You are not employed at Facebook.");
}
$fbid = get_fbid_for_facebook_username($username);
$profile->setDefaultEmail($fbid);
$profile->setCanEditUsername(false);
$profile->setCanEditEmail(false);
$profile->setCanEditRealName(false);
$profile->setShouldVerifyEmail(true);
Seem reasonable?
Test Plan: N/A yet, probably fatals.
Reviewers: vrana, btrahan, codeblock, chad
Reviewed By: btrahan
CC: aran, asherkin, nh, wez
Maniphest Tasks: T1536, T2394
Differential Revision: https://secure.phabricator.com/D4647
2013-06-16 19:13:49 +02:00
|
|
|
$validate_uri = new PhutilURI($this->getApplicationURI('validate/'));
|
2014-01-23 23:01:35 +01:00
|
|
|
$validate_uri->setQueryParam('expect', $user->getUsername());
|
New Registration Workflow
Summary:
Currently, registration and authentication are pretty messy. Two concrete problems:
- The `PhabricatorLDAPRegistrationController` and `PhabricatorOAuthDefaultRegistrationController` controllers are giant copy/pastes of one another. This is really bad.
- We can't practically implement OpenID because we can't reissue the authentication request.
Additionally, the OAuth registration controller can be replaced wholesale by config, which is a huge API surface area and a giant mess.
Broadly, the problem right now is that registration does too much: we hand it some set of indirect credentials (like OAuth tokens) and expect it to take those the entire way to a registered user. Instead, break registration into smaller steps:
- User authenticates with remote service.
- Phabricator pulls information (remote account ID, username, email, real name, profile picture, etc) from the remote service and saves it as `PhabricatorUserCredentials`.
- Phabricator hands the `PhabricatorUserCredentials` to the registration form, which is agnostic about where they originate from: it can process LDAP credentials, OAuth credentials, plain old email credentials, HTTP basic auth credentials, etc.
This doesn't do anything yet -- there is no way to create credentials objects (and no storage patch), but I wanted to get any initial feedback, especially about the event call for T2394. In particular, I think the implementation would look something like this:
$profile = $event->getValue('profile')
$username = $profile->getDefaultUsername();
$is_employee = is_this_a_facebook_employee($username);
if (!$is_employee) {
throw new Exception("You are not employed at Facebook.");
}
$fbid = get_fbid_for_facebook_username($username);
$profile->setDefaultEmail($fbid);
$profile->setCanEditUsername(false);
$profile->setCanEditEmail(false);
$profile->setCanEditRealName(false);
$profile->setShouldVerifyEmail(true);
Seem reasonable?
Test Plan: N/A yet, probably fatals.
Reviewers: vrana, btrahan, codeblock, chad
Reviewed By: btrahan
CC: aran, asherkin, nh, wez
Maniphest Tasks: T1536, T2394
Differential Revision: https://secure.phabricator.com/D4647
2013-06-16 19:13:49 +02:00
|
|
|
|
|
|
|
return id(new AphrontRedirectResponse())->setURI((string)$validate_uri);
|
|
|
|
}
|
|
|
|
|
2013-06-17 01:35:36 +02:00
|
|
|
protected function renderError($message) {
|
|
|
|
return $this->renderErrorPage(
|
|
|
|
pht('Authentication Error'),
|
|
|
|
array(
|
|
|
|
$message,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2013-06-17 15:12:45 +02:00
|
|
|
protected function loadAccountForRegistrationOrLinking($account_key) {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
$viewer = $request->getUser();
|
|
|
|
|
|
|
|
$account = null;
|
|
|
|
$provider = null;
|
|
|
|
$response = null;
|
|
|
|
|
|
|
|
if (!$account_key) {
|
|
|
|
$response = $this->renderError(
|
|
|
|
pht('Request did not include account key.'));
|
|
|
|
return array($account, $provider, $response);
|
|
|
|
}
|
|
|
|
|
2013-06-17 21:14:00 +02:00
|
|
|
// NOTE: We're using the omnipotent user because the actual user may not
|
|
|
|
// be logged in yet, and because we want to tailor an error message to
|
|
|
|
// distinguish between "not usable" and "does not exist". We do explicit
|
|
|
|
// checks later on to make sure this account is valid for the intended
|
|
|
|
// operation.
|
|
|
|
|
|
|
|
$account = id(new PhabricatorExternalAccountQuery())
|
|
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
|
|
->withAccountSecrets(array($account_key))
|
|
|
|
->needImages(true)
|
|
|
|
->executeOne();
|
|
|
|
|
2013-06-17 15:12:45 +02:00
|
|
|
if (!$account) {
|
|
|
|
$response = $this->renderError(pht('No valid linkable account.'));
|
|
|
|
return array($account, $provider, $response);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($account->getUserPHID()) {
|
2014-02-24 20:45:28 +01:00
|
|
|
if ($account->getUserPHID() != $viewer->getPHID()) {
|
2013-06-17 15:12:45 +02:00
|
|
|
$response = $this->renderError(
|
|
|
|
pht(
|
|
|
|
'The account you are attempting to register or link is already '.
|
|
|
|
'linked to another user.'));
|
|
|
|
} else {
|
|
|
|
$response = $this->renderError(
|
|
|
|
pht(
|
|
|
|
'The account you are attempting to link is already linked '.
|
|
|
|
'to your account.'));
|
|
|
|
}
|
|
|
|
return array($account, $provider, $response);
|
|
|
|
}
|
|
|
|
|
2014-01-23 23:01:35 +01:00
|
|
|
$registration_key = $request->getCookie(
|
|
|
|
PhabricatorCookies::COOKIE_REGISTRATION);
|
2013-06-17 15:12:45 +02:00
|
|
|
|
|
|
|
// NOTE: This registration key check is not strictly necessary, because
|
|
|
|
// we're only creating new accounts, not linking existing accounts. It
|
|
|
|
// might be more hassle than it is worth, especially for email.
|
|
|
|
//
|
|
|
|
// The attack this prevents is getting to the registration screen, then
|
|
|
|
// copy/pasting the URL and getting someone else to click it and complete
|
|
|
|
// the process. They end up with an account bound to credentials you
|
|
|
|
// control. This doesn't really let you do anything meaningful, though,
|
|
|
|
// since you could have simply completed the process yourself.
|
|
|
|
|
|
|
|
if (!$registration_key) {
|
2014-01-23 23:01:35 +01:00
|
|
|
$response = $this->renderError(
|
2013-06-17 15:12:45 +02:00
|
|
|
pht(
|
|
|
|
'Your browser did not submit a registration key with the request. '.
|
|
|
|
'You must use the same browser to begin and complete registration. '.
|
|
|
|
'Check that cookies are enabled and try again.'));
|
|
|
|
return array($account, $provider, $response);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We store the digest of the key rather than the key itself to prevent a
|
|
|
|
// theoretical attacker with read-only access to the database from
|
|
|
|
// hijacking registration sessions.
|
|
|
|
|
|
|
|
$actual = $account->getProperty('registrationKey');
|
|
|
|
$expect = PhabricatorHash::digest($registration_key);
|
|
|
|
if ($actual !== $expect) {
|
|
|
|
$response = $this->renderError(
|
|
|
|
pht(
|
|
|
|
'Your browser submitted a different registration key than the one '.
|
|
|
|
'associated with this account. You may need to clear your cookies.'));
|
|
|
|
return array($account, $provider, $response);
|
|
|
|
}
|
|
|
|
|
|
|
|
$other_account = id(new PhabricatorExternalAccount())->loadAllWhere(
|
|
|
|
'accountType = %s AND accountDomain = %s AND accountID = %s
|
|
|
|
AND id != %d',
|
|
|
|
$account->getAccountType(),
|
|
|
|
$account->getAccountDomain(),
|
|
|
|
$account->getAccountID(),
|
|
|
|
$account->getID());
|
|
|
|
|
|
|
|
if ($other_account) {
|
|
|
|
$response = $this->renderError(
|
|
|
|
pht(
|
|
|
|
'The account you are attempting to register with already belongs '.
|
|
|
|
'to another user.'));
|
|
|
|
return array($account, $provider, $response);
|
|
|
|
}
|
|
|
|
|
|
|
|
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
|
|
|
|
$account->getProviderKey());
|
|
|
|
|
|
|
|
if (!$provider) {
|
|
|
|
$response = $this->renderError(
|
|
|
|
pht(
|
|
|
|
'The account you are attempting to register with uses a nonexistent '.
|
|
|
|
'or disabled authentication provider (with key "%s"). An '.
|
|
|
|
'administrator may have recently disabled this provider.',
|
|
|
|
$account->getProviderKey()));
|
|
|
|
return array($account, $provider, $response);
|
|
|
|
}
|
|
|
|
|
|
|
|
return array($account, $provider, null);
|
|
|
|
}
|
|
|
|
|
2011-01-26 22:21:12 +01:00
|
|
|
}
|