1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-04 12:42:43 +01:00
phorge-phorge/src/applications/auth/controller/PhabricatorAuthStartController.php

341 lines
10 KiB
PHP
Raw Normal View History

<?php
final class PhabricatorAuthStartController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$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();
}
// 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.
$session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
Implement "auth.logout" Conduit API method Summary: Ref T7303. Ref T7673. This implements an "auth.logout" which: - terminates all web sessions; - terminates the current OAuth token if called via OAuth; and - may always be called via OAuth. (Since it consumes an OAuth token, even a "malicious" OAuth application can't really be that much of a jerk with this: it can't continuously log you out, since calling the method once kills the token. The application would need to ask your permission again to get a fresh token.) The primary goal here is to let Phacility instances call this against the Phacility upstream, so that when you log out of an instance it also logs you out of your Phacility account (possibly with a checkbox or something). This also smooths over the session token code. Before this change, your sessions would get logged out but when you reloaded we'd tell you your session was invalid. Instead, try to clear the invalid session before telling the user there's an issue. I think that ssentially 100% of invalid sessions are a result of something in this vein (e.g., forced logout via Settings) nowadays, since the session code is generally stable and sane and has been for a long time. Test Plan: - Called `auth.logout` via console, got a reasonable logout experience. - Called `auth.logout` via OAuth. - Tried to make another call, verified OAuth token had been invalidated. - Verified web session had been invalidated. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7303, T7673 Differential Revision: https://secure.phabricator.com/D15594
2016-04-03 18:06:26 +02:00
$did_clear = $request->getStr('cleared');
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:
Implement "auth.logout" Conduit API method Summary: Ref T7303. Ref T7673. This implements an "auth.logout" which: - terminates all web sessions; - terminates the current OAuth token if called via OAuth; and - may always be called via OAuth. (Since it consumes an OAuth token, even a "malicious" OAuth application can't really be that much of a jerk with this: it can't continuously log you out, since calling the method once kills the token. The application would need to ask your permission again to get a fresh token.) The primary goal here is to let Phacility instances call this against the Phacility upstream, so that when you log out of an instance it also logs you out of your Phacility account (possibly with a checkbox or something). This also smooths over the session token code. Before this change, your sessions would get logged out but when you reloaded we'd tell you your session was invalid. Instead, try to clear the invalid session before telling the user there's an issue. I think that ssentially 100% of invalid sessions are a result of something in this vein (e.g., forced logout via Settings) nowadays, since the session code is generally stable and sane and has been for a long time. Test Plan: - Called `auth.logout` via console, got a reasonable logout experience. - Called `auth.logout` via OAuth. - Tried to make another call, verified OAuth token had been invalidated. - Verified web session had been invalidated. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7303, T7673 Differential Revision: https://secure.phabricator.com/D15594
2016-04-03 18:06:26 +02:00
// The session cookie is invalid, so try to clear it.
$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
$request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
Implement "auth.logout" Conduit API method Summary: Ref T7303. Ref T7673. This implements an "auth.logout" which: - terminates all web sessions; - terminates the current OAuth token if called via OAuth; and - may always be called via OAuth. (Since it consumes an OAuth token, even a "malicious" OAuth application can't really be that much of a jerk with this: it can't continuously log you out, since calling the method once kills the token. The application would need to ask your permission again to get a fresh token.) The primary goal here is to let Phacility instances call this against the Phacility upstream, so that when you log out of an instance it also logs you out of your Phacility account (possibly with a checkbox or something). This also smooths over the session token code. Before this change, your sessions would get logged out but when you reloaded we'd tell you your session was invalid. Instead, try to clear the invalid session before telling the user there's an issue. I think that ssentially 100% of invalid sessions are a result of something in this vein (e.g., forced logout via Settings) nowadays, since the session code is generally stable and sane and has been for a long time. Test Plan: - Called `auth.logout` via console, got a reasonable logout experience. - Called `auth.logout` via OAuth. - Tried to make another call, verified OAuth token had been invalidated. - Verified web session had been invalidated. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7303, T7673 Differential Revision: https://secure.phabricator.com/D15594
2016-04-03 18:06:26 +02:00
// We've previously tried to clear the cookie but we ended up back
// here, so it didn't work. Hard fatal instead of trying again.
if ($did_clear) {
return $this->renderError(
pht(
'Your login session is invalid, and clearing the session '.
'cookie was unsuccessful. Try clearing your browser cookies.'));
}
$redirect_uri = $request->getRequestURI();
$redirect_uri->replaceQueryParam('cleared', 1);
Implement "auth.logout" Conduit API method Summary: Ref T7303. Ref T7673. This implements an "auth.logout" which: - terminates all web sessions; - terminates the current OAuth token if called via OAuth; and - may always be called via OAuth. (Since it consumes an OAuth token, even a "malicious" OAuth application can't really be that much of a jerk with this: it can't continuously log you out, since calling the method once kills the token. The application would need to ask your permission again to get a fresh token.) The primary goal here is to let Phacility instances call this against the Phacility upstream, so that when you log out of an instance it also logs you out of your Phacility account (possibly with a checkbox or something). This also smooths over the session token code. Before this change, your sessions would get logged out but when you reloaded we'd tell you your session was invalid. Instead, try to clear the invalid session before telling the user there's an issue. I think that ssentially 100% of invalid sessions are a result of something in this vein (e.g., forced logout via Settings) nowadays, since the session code is generally stable and sane and has been for a long time. Test Plan: - Called `auth.logout` via console, got a reasonable logout experience. - Called `auth.logout` via OAuth. - Tried to make another call, verified OAuth token had been invalidated. - Verified web session had been invalidated. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7303, T7673 Differential Revision: https://secure.phabricator.com/D15594
2016-04-03 18:06:26 +02:00
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}
}
Implement "auth.logout" Conduit API method Summary: Ref T7303. Ref T7673. This implements an "auth.logout" which: - terminates all web sessions; - terminates the current OAuth token if called via OAuth; and - may always be called via OAuth. (Since it consumes an OAuth token, even a "malicious" OAuth application can't really be that much of a jerk with this: it can't continuously log you out, since calling the method once kills the token. The application would need to ask your permission again to get a fresh token.) The primary goal here is to let Phacility instances call this against the Phacility upstream, so that when you log out of an instance it also logs you out of your Phacility account (possibly with a checkbox or something). This also smooths over the session token code. Before this change, your sessions would get logged out but when you reloaded we'd tell you your session was invalid. Instead, try to clear the invalid session before telling the user there's an issue. I think that ssentially 100% of invalid sessions are a result of something in this vein (e.g., forced logout via Settings) nowadays, since the session code is generally stable and sane and has been for a long time. Test Plan: - Called `auth.logout` via console, got a reasonable logout experience. - Called `auth.logout` via OAuth. - Tried to make another call, verified OAuth token had been invalidated. - Verified web session had been invalidated. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7303, T7673 Differential Revision: https://secure.phabricator.com/D15594
2016-04-03 18:06:26 +02:00
// If we just cleared the session cookie and it worked, clean up after
// ourselves by redirecting to get rid of the "cleared" parameter. The
// the workflow will continue normally.
if ($did_clear) {
$redirect_uri = $request->getRequestURI();
$redirect_uri->removeQueryParam('cleared');
Implement "auth.logout" Conduit API method Summary: Ref T7303. Ref T7673. This implements an "auth.logout" which: - terminates all web sessions; - terminates the current OAuth token if called via OAuth; and - may always be called via OAuth. (Since it consumes an OAuth token, even a "malicious" OAuth application can't really be that much of a jerk with this: it can't continuously log you out, since calling the method once kills the token. The application would need to ask your permission again to get a fresh token.) The primary goal here is to let Phacility instances call this against the Phacility upstream, so that when you log out of an instance it also logs you out of your Phacility account (possibly with a checkbox or something). This also smooths over the session token code. Before this change, your sessions would get logged out but when you reloaded we'd tell you your session was invalid. Instead, try to clear the invalid session before telling the user there's an issue. I think that ssentially 100% of invalid sessions are a result of something in this vein (e.g., forced logout via Settings) nowadays, since the session code is generally stable and sane and has been for a long time. Test Plan: - Called `auth.logout` via console, got a reasonable logout experience. - Called `auth.logout` via OAuth. - Tried to make another call, verified OAuth token had been invalidated. - Verified web session had been invalidated. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7303, T7673 Differential Revision: https://secure.phabricator.com/D15594
2016-04-03 18:06:26 +02:00
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
foreach ($providers as $key => $provider) {
if (!$provider->shouldAllowLogin()) {
unset($providers[$key]);
}
}
2019-02-05 18:10:26 +01:00
$configs = array();
foreach ($providers as $provider) {
$configs[] = $provider->getProviderConfig();
}
if (!$providers) {
if ($this->isFirstTimeSetup()) {
// If this is a fresh install, let the user register their admin
// account.
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI('/register/'));
}
return $this->renderError(
pht(
'This server is not configured with any enabled authentication '.
'providers which can be used to log in. If you have accidentally '.
'locked yourself out by disabling all providers, you can use `%s` '.
'to recover access to an account.',
'./bin/auth recover <username>'));
}
$next_uri = $request->getStr('next');
if (!strlen($next_uri)) {
if ($this->getDelegatingController()) {
// Only set a next URI from the request path if this controller was
// delegated to, which happens when a user tries to view a page which
// requires them to login.
// If this controller handled the request directly, we're on the main
// login page, and never want to redirect the user back here after they
// login.
$next_uri = (string)$this->getRequest()->getRequestURI();
}
}
if (!$request->isFormPost()) {
if (strlen($next_uri)) {
PhabricatorCookies::setNextURICookie($request, $next_uri);
}
PhabricatorCookies::setClientIDCookie($request);
}
$auto_response = $this->tryAutoLogin($providers);
if ($auto_response) {
return $auto_response;
}
$invite = $this->loadInvite();
$not_buttons = array();
$are_buttons = array();
$providers = msort($providers, 'getLoginOrder');
foreach ($providers as $provider) {
if ($invite) {
$form = $provider->buildInviteForm($this);
} else {
$form = $provider->buildLoginForm($this);
}
if ($provider->isLoginFormAButton()) {
$are_buttons[] = $form;
} else {
$not_buttons[] = $form;
}
}
$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);
}
$invite_message = null;
if ($invite) {
$invite_message = $this->renderInviteHeader($invite);
}
$custom_message = $this->newCustomStartMessage();
2019-02-05 18:10:26 +01:00
$email_login = $this->newEmailLoginView($configs);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Login'));
$crumbs->setBorder(true);
$title = pht('Login');
$view = array(
$invite_message,
$custom_message,
$out,
2019-02-05 18:10:26 +01:00
$email_login,
);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function processAjaxRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
// 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());
}
When logged-out users hit a "Login Required" dialog, try to choose a better "next" URI Summary: Ref T10004. After a user logs in, we send them to the "next" URI cookie if there is one, but currently don't always do a very good job of selecting a "next" URI, especially if they tried to do something with a dialog before being asked to log in. In particular, if a logged-out user clicks an action like "Edit Blocking Tasks" on a Maniphest task, the default behavior is to send them to the standalone page for that dialog after they log in. This can be pretty confusing. See T2691 and D6416 for earlier efforts here. At that time, we added a mechanism to //manually// override the default behavior, and fixed the most common links. This worked, but I'd like to fix the //default// beahvior so we don't need to remember to `setObjectURI()` correctly all over the place. ApplicationEditor has also introduced new cases which are more difficult to get right. While we could get them right by using the override and being careful about things, this also motivates fixing the default behavior. Finally, we have better tools for fixing the default behavior now than we did in 2013. Instead of using manual overrides, have JS include an "X-Phabricator-Via" header in Ajax requests. This is basically like a referrer header, and will contain the page the user's browser is on. In essentially every case, this should be a very good place (and often the best place) to send them after login. For all pages currently using `setObjectURI()`, it should produce the same behavior by default. I'll remove the `setObjectURI()` mechanism in the next diff. Test Plan: Clicked various workflow actions while logged out, saw "next" get set to a reasonable value, was redirected to a sensible, non-confusing page after login (the page with whatever button I clicked on it). Reviewers: chad Reviewed By: chad Maniphest Tasks: T10004 Differential Revision: https://secure.phabricator.com/D14804
2015-12-17 15:10:04 +01:00
// Often, users end up here by clicking a disabled action link in the UI
// (for example, they might click "Edit Subtasks" on a Maniphest task
// page). After they log in we want to send them back to that main object
// page if we can, since it's confusing to end up on a standalone page with
// only a dialog (particularly if that dialog is another error,
When logged-out users hit a "Login Required" dialog, try to choose a better "next" URI Summary: Ref T10004. After a user logs in, we send them to the "next" URI cookie if there is one, but currently don't always do a very good job of selecting a "next" URI, especially if they tried to do something with a dialog before being asked to log in. In particular, if a logged-out user clicks an action like "Edit Blocking Tasks" on a Maniphest task, the default behavior is to send them to the standalone page for that dialog after they log in. This can be pretty confusing. See T2691 and D6416 for earlier efforts here. At that time, we added a mechanism to //manually// override the default behavior, and fixed the most common links. This worked, but I'd like to fix the //default// beahvior so we don't need to remember to `setObjectURI()` correctly all over the place. ApplicationEditor has also introduced new cases which are more difficult to get right. While we could get them right by using the override and being careful about things, this also motivates fixing the default behavior. Finally, we have better tools for fixing the default behavior now than we did in 2013. Instead of using manual overrides, have JS include an "X-Phabricator-Via" header in Ajax requests. This is basically like a referrer header, and will contain the page the user's browser is on. In essentially every case, this should be a very good place (and often the best place) to send them after login. For all pages currently using `setObjectURI()`, it should produce the same behavior by default. I'll remove the `setObjectURI()` mechanism in the next diff. Test Plan: Clicked various workflow actions while logged out, saw "next" get set to a reasonable value, was redirected to a sensible, non-confusing page after login (the page with whatever button I clicked on it). Reviewers: chad Reviewed By: chad Maniphest Tasks: T10004 Differential Revision: https://secure.phabricator.com/D14804
2015-12-17 15:10:04 +01:00
// like a policy exception).
$via_header = AphrontRequest::getViaHeaderName();
$via_uri = AphrontRequest::getHTTPHeader($via_header);
if (strlen($via_uri)) {
PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true);
}
return $this->newDialog()
->setTitle(pht('Login Required'))
->appendParagraph(pht('You must log in to take this action.'))
->addSubmitButton(pht('Log In'))
When logged-out users hit a "Login Required" dialog, try to choose a better "next" URI Summary: Ref T10004. After a user logs in, we send them to the "next" URI cookie if there is one, but currently don't always do a very good job of selecting a "next" URI, especially if they tried to do something with a dialog before being asked to log in. In particular, if a logged-out user clicks an action like "Edit Blocking Tasks" on a Maniphest task, the default behavior is to send them to the standalone page for that dialog after they log in. This can be pretty confusing. See T2691 and D6416 for earlier efforts here. At that time, we added a mechanism to //manually// override the default behavior, and fixed the most common links. This worked, but I'd like to fix the //default// beahvior so we don't need to remember to `setObjectURI()` correctly all over the place. ApplicationEditor has also introduced new cases which are more difficult to get right. While we could get them right by using the override and being careful about things, this also motivates fixing the default behavior. Finally, we have better tools for fixing the default behavior now than we did in 2013. Instead of using manual overrides, have JS include an "X-Phabricator-Via" header in Ajax requests. This is basically like a referrer header, and will contain the page the user's browser is on. In essentially every case, this should be a very good place (and often the best place) to send them after login. For all pages currently using `setObjectURI()`, it should produce the same behavior by default. I'll remove the `setObjectURI()` mechanism in the next diff. Test Plan: Clicked various workflow actions while logged out, saw "next" get set to a reasonable value, was redirected to a sensible, non-confusing page after login (the page with whatever button I clicked on it). Reviewers: chad Reviewed By: chad Maniphest Tasks: T10004 Differential Revision: https://secure.phabricator.com/D14804
2015-12-17 15:10:04 +01:00
->addCancelButton('/');
}
private function processConduitRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
// 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);
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Authentication Failure'),
array($message));
}
private function tryAutoLogin(array $providers) {
$request = $this->getRequest();
// If the user just logged out, don't immediately log them in again.
if ($request->getURIData('loggedout')) {
return null;
}
// If we have more than one provider, we can't autologin because we
// don't know which one the user wants.
if (count($providers) != 1) {
return null;
}
$provider = head($providers);
if (!$provider->supportsAutoLogin()) {
return null;
}
$config = $provider->getProviderConfig();
if (!$config->getShouldAutoLogin()) {
return null;
}
$auto_uri = $provider->getAutoLoginURI($request);
return id(new AphrontRedirectResponse())
->setIsExternal(true)
->setURI($auto_uri);
}
2019-02-05 18:10:26 +01:00
private function newEmailLoginView(array $configs) {
assert_instances_of($configs, 'PhabricatorAuthProviderConfig');
// Check if password auth is enabled. If it is, the password login form
// renders a "Forgot password?" link, so we don't need to provide a
// supplemental link.
$has_password = false;
foreach ($configs as $config) {
$provider = $config->getProvider();
if ($provider instanceof PhabricatorPasswordAuthProvider) {
$has_password = true;
}
}
if ($has_password) {
return null;
}
$view = array(
pht('Trouble logging in?'),
' ',
phutil_tag(
'a',
array(
'href' => '/login/email/',
),
pht('Send a login link to your email address.')),
);
return phutil_tag(
'div',
array(
'class' => 'auth-custom-message',
),
$view);
}
}