2013-06-16 19:15:16 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorAuthStartController
|
|
|
|
extends PhabricatorAuthController {
|
|
|
|
|
|
|
|
public function shouldRequireLogin() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function processRequest() {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
$viewer = $request->getUser();
|
|
|
|
|
|
|
|
if ($viewer->isLoggedIn()) {
|
|
|
|
// Kick the user home if they are already logged in.
|
|
|
|
return id(new AphrontRedirectResponse())->setURI('/');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($request->isAjax()) {
|
|
|
|
return $this->processAjaxRequest();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($request->isConduit()) {
|
|
|
|
return $this->processConduitRequest();
|
|
|
|
}
|
|
|
|
|
Issue "anonymous" sessions for logged-out users
Summary:
Ref T4339. Ref T4310. Currently, sessions look like `"afad85d675fda87a4fadd54"`, and are only issued for logged-in users. To support logged-out CSRF and (eventually) external user sessions, I made two small changes:
- First, sessions now have a "kind", which is indicated by a prefix, like `"A/ab987asdcas7dca"`. This mostly allows us to issue session queries more efficiently: we don't have to issue a query at all for anonymous sessions, and can join the correct table for user and external sessions and save a query. Generally, this gives us more debugging information and more opportunity to recover from issues in a user-friendly way, as with the "invalid session" error in this diff.
- Secondly, if you load a page and don't have a session, we give you an anonymous session. This is just a secret with no special significance.
This does not implement CSRF yet, but gives us a client secret we can use to implement it.
Test Plan:
- Logged in.
- Logged out.
- Browsed around.
- Logged in again.
- Went through link/register.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4310, T4339
Differential Revision: https://secure.phabricator.com/D8043
2014-01-23 23:03:22 +01:00
|
|
|
// If the user gets this far, they aren't logged in, so if they have a
|
|
|
|
// user session token we can conclude that it's invalid: if it was valid,
|
|
|
|
// they'd have been logged in above and never made it here. Try to clear
|
|
|
|
// it and warn the user they may need to nuke their cookies.
|
2013-06-16 19:15:16 +02:00
|
|
|
|
Issue "anonymous" sessions for logged-out users
Summary:
Ref T4339. Ref T4310. Currently, sessions look like `"afad85d675fda87a4fadd54"`, and are only issued for logged-in users. To support logged-out CSRF and (eventually) external user sessions, I made two small changes:
- First, sessions now have a "kind", which is indicated by a prefix, like `"A/ab987asdcas7dca"`. This mostly allows us to issue session queries more efficiently: we don't have to issue a query at all for anonymous sessions, and can join the correct table for user and external sessions and save a query. Generally, this gives us more debugging information and more opportunity to recover from issues in a user-friendly way, as with the "invalid session" error in this diff.
- Secondly, if you load a page and don't have a session, we give you an anonymous session. This is just a secret with no special significance.
This does not implement CSRF yet, but gives us a client secret we can use to implement it.
Test Plan:
- Logged in.
- Logged out.
- Browsed around.
- Logged in again.
- Went through link/register.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4310, T4339
Differential Revision: https://secure.phabricator.com/D8043
2014-01-23 23:03:22 +01:00
|
|
|
$session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
|
|
|
|
if (strlen($session_token)) {
|
|
|
|
$kind = PhabricatorAuthSessionEngine::getSessionKindFromToken(
|
|
|
|
$session_token);
|
|
|
|
switch ($kind) {
|
|
|
|
case PhabricatorAuthSessionEngine::KIND_ANONYMOUS:
|
|
|
|
// If this is an anonymous session. It's expected that they won't
|
|
|
|
// be logged in, so we can just continue.
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// The session cookie is invalid, so clear it.
|
|
|
|
$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
|
|
|
|
$request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
|
2013-06-16 19:15:16 +02:00
|
|
|
|
Issue "anonymous" sessions for logged-out users
Summary:
Ref T4339. Ref T4310. Currently, sessions look like `"afad85d675fda87a4fadd54"`, and are only issued for logged-in users. To support logged-out CSRF and (eventually) external user sessions, I made two small changes:
- First, sessions now have a "kind", which is indicated by a prefix, like `"A/ab987asdcas7dca"`. This mostly allows us to issue session queries more efficiently: we don't have to issue a query at all for anonymous sessions, and can join the correct table for user and external sessions and save a query. Generally, this gives us more debugging information and more opportunity to recover from issues in a user-friendly way, as with the "invalid session" error in this diff.
- Secondly, if you load a page and don't have a session, we give you an anonymous session. This is just a secret with no special significance.
This does not implement CSRF yet, but gives us a client secret we can use to implement it.
Test Plan:
- Logged in.
- Logged out.
- Browsed around.
- Logged in again.
- Went through link/register.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4310, T4339
Differential Revision: https://secure.phabricator.com/D8043
2014-01-23 23:03:22 +01:00
|
|
|
return $this->renderError(
|
|
|
|
pht(
|
|
|
|
"Your login session is invalid. Try reloading the page and ".
|
|
|
|
"logging in again. If that does not work, clear your browser ".
|
|
|
|
"cookies."));
|
|
|
|
}
|
|
|
|
}
|
2013-06-16 19:15:16 +02:00
|
|
|
|
|
|
|
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
|
|
|
|
foreach ($providers as $key => $provider) {
|
|
|
|
if (!$provider->shouldAllowLogin()) {
|
|
|
|
unset($providers[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$providers) {
|
2013-06-20 01:28:48 +02:00
|
|
|
if ($this->isFirstTimeSetup()) {
|
|
|
|
// If this is a fresh install, let the user register their admin
|
|
|
|
// account.
|
|
|
|
return id(new AphrontRedirectResponse())
|
|
|
|
->setURI($this->getApplicationURI('/register/'));
|
|
|
|
}
|
|
|
|
|
2013-06-16 19:15:16 +02:00
|
|
|
return $this->renderError(
|
|
|
|
pht(
|
|
|
|
"This Phabricator install is not configured with any enabled ".
|
2013-06-17 19:55:05 +02:00
|
|
|
"authentication providers which can be used to log in. If you ".
|
|
|
|
"have accidentally locked yourself out by disabling all providers, ".
|
|
|
|
"you can use `phabricator/bin/auth recover <username>` to ".
|
|
|
|
"recover access to an administrative account."));
|
2013-06-16 19:15:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$next_uri = $request->getStr('next');
|
|
|
|
if (!$next_uri) {
|
|
|
|
$next_uri_path = $this->getRequest()->getPath();
|
|
|
|
if ($next_uri_path == '/auth/start/') {
|
|
|
|
$next_uri = '/';
|
|
|
|
} else {
|
|
|
|
$next_uri = $this->getRequest()->getRequestURI();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$request->isFormPost()) {
|
2014-01-23 23:01:35 +01:00
|
|
|
$request->setCookie(
|
|
|
|
PhabricatorCookies::COOKIE_NEXTURI,
|
|
|
|
$next_uri);
|
|
|
|
$request->setCookie(
|
|
|
|
PhabricatorCookies::COOKIE_CLIENTID,
|
|
|
|
Filesystem::readRandomCharacters(16));
|
2013-06-16 19:15:16 +02:00
|
|
|
}
|
|
|
|
|
2013-06-17 01:31:57 +02:00
|
|
|
$not_buttons = array();
|
|
|
|
$are_buttons = array();
|
|
|
|
$providers = msort($providers, 'getLoginOrder');
|
2013-06-16 19:15:16 +02:00
|
|
|
foreach ($providers as $provider) {
|
2013-06-17 01:31:57 +02:00
|
|
|
if ($provider->isLoginFormAButton()) {
|
|
|
|
$are_buttons[] = $provider->buildLoginForm($this);
|
|
|
|
} else {
|
|
|
|
$not_buttons[] = $provider->buildLoginForm($this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$out = array();
|
|
|
|
$out[] = $not_buttons;
|
|
|
|
if ($are_buttons) {
|
|
|
|
require_celerity_resource('auth-css');
|
|
|
|
|
|
|
|
foreach ($are_buttons as $key => $button) {
|
|
|
|
$are_buttons[$key] = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-login-button mmb',
|
|
|
|
),
|
|
|
|
$button);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we only have one button, add a second pretend button so that we
|
|
|
|
// always have two columns. This makes it easier to get the alignments
|
|
|
|
// looking reasonable.
|
|
|
|
if (count($are_buttons) == 1) {
|
|
|
|
$are_buttons[] = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$button_columns = id(new AphrontMultiColumnView())
|
|
|
|
->setFluidLayout(true);
|
|
|
|
$are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));
|
|
|
|
foreach ($are_buttons as $column) {
|
|
|
|
$button_columns->addColumn($column);
|
|
|
|
}
|
|
|
|
|
|
|
|
$out[] = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-login-buttons',
|
|
|
|
),
|
|
|
|
$button_columns);
|
2013-06-16 19:15:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$login_message = PhabricatorEnv::getEnvConfig('auth.login-message');
|
|
|
|
$login_message = phutil_safe_html($login_message);
|
|
|
|
|
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
2013-12-19 02:47:34 +01:00
|
|
|
$crumbs->addTextCrumb(pht('Login'));
|
2013-06-16 19:15:16 +02:00
|
|
|
|
|
|
|
return $this->buildApplicationPage(
|
|
|
|
array(
|
|
|
|
$crumbs,
|
|
|
|
$login_message,
|
|
|
|
$out,
|
|
|
|
),
|
|
|
|
array(
|
|
|
|
'title' => pht('Login to Phabricator'),
|
|
|
|
'device' => true,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function processAjaxRequest() {
|
|
|
|
$request = $this->getRequest();
|
2013-06-19 10:33:27 +02:00
|
|
|
$viewer = $request->getUser();
|
2013-06-16 19:15:16 +02:00
|
|
|
|
|
|
|
// We end up here if the user clicks a workflow link that they need to
|
|
|
|
// login to use. We give them a dialog saying "You need to login...".
|
|
|
|
|
|
|
|
if ($request->isDialogFormPost()) {
|
|
|
|
return id(new AphrontRedirectResponse())->setURI(
|
|
|
|
$request->getRequestURI());
|
|
|
|
}
|
|
|
|
|
|
|
|
$dialog = new AphrontDialogView();
|
|
|
|
$dialog->setUser($viewer);
|
|
|
|
$dialog->setTitle(pht('Login Required'));
|
|
|
|
$dialog->appendChild(pht('You must login to continue.'));
|
|
|
|
$dialog->addSubmitButton(pht('Login'));
|
|
|
|
$dialog->addCancelButton('/');
|
|
|
|
|
|
|
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function processConduitRequest() {
|
|
|
|
$request = $this->getRequest();
|
2013-06-19 10:33:27 +02:00
|
|
|
$viewer = $request->getUser();
|
2013-06-16 19:15:16 +02:00
|
|
|
|
|
|
|
// A common source of errors in Conduit client configuration is getting
|
|
|
|
// the request path wrong. The client will end up here, so make some
|
|
|
|
// effort to give them a comprehensible error message.
|
|
|
|
|
|
|
|
$request_path = $this->getRequest()->getPath();
|
|
|
|
$conduit_path = '/api/<method>';
|
|
|
|
$example_path = '/api/conduit.ping';
|
|
|
|
|
|
|
|
$message = pht(
|
|
|
|
'ERROR: You are making a Conduit API request to "%s", but the correct '.
|
|
|
|
'HTTP request path to use in order to access a COnduit method is "%s" '.
|
|
|
|
'(for example, "%s"). Check your configuration.',
|
|
|
|
$request_path,
|
|
|
|
$conduit_path,
|
|
|
|
$example_path);
|
|
|
|
|
|
|
|
return id(new AphrontPlainTextResponse())->setContent($message);
|
|
|
|
}
|
|
|
|
|
2013-06-17 01:35:36 +02:00
|
|
|
protected function renderError($message) {
|
2013-06-16 19:15:33 +02:00
|
|
|
return $this->renderErrorPage(
|
|
|
|
pht('Authentication Failure'),
|
|
|
|
array($message));
|
2013-06-16 19:15:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|