1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-25 06:50:55 +01: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
This commit is contained in:
epriestley 2014-01-23 14:03:22 -08:00
parent 0727418023
commit 69ddb0ced6
6 changed files with 123 additions and 20 deletions

View file

@ -24,18 +24,33 @@ final class PhabricatorAuthStartController
return $this->processConduitRequest(); return $this->processConduitRequest();
} }
if (strlen($request->getCookie(PhabricatorCookies::COOKIE_SESSION))) { // If the user gets this far, they aren't logged in, so if they have a
// The session cookie is invalid, so clear it. // user session token we can conclude that it's invalid: if it was valid,
$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME); // they'd have been logged in above and never made it here. Try to clear
$request->clearCookie(PhabricatorCookies::COOKIE_SESSION); // it and warn the user they may need to nuke their cookies.
return $this->renderError( $session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
pht( if (strlen($session_token)) {
"Your login session is invalid. Try reloading the page and logging ". $kind = PhabricatorAuthSessionEngine::getSessionKindFromToken(
"in again. If that does not work, clear your browser cookies.")); $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);
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."));
}
} }
$providers = PhabricatorAuthProvider::getAllEnabledProviders(); $providers = PhabricatorAuthProvider::getAllEnabledProviders();
foreach ($providers as $key => $provider) { foreach ($providers as $key => $provider) {
if (!$provider->shouldAllowLogin()) { if (!$provider->shouldAllowLogin()) {

View file

@ -2,7 +2,79 @@
final class PhabricatorAuthSessionEngine extends Phobject { final class PhabricatorAuthSessionEngine extends Phobject {
public function loadUserForSession($session_type, $session_key) { /**
* Session issued to normal users after they login through a standard channel.
* Associates the client with a standard user identity.
*/
const KIND_USER = 'U';
/**
* Session issued to users who login with some sort of credentials but do not
* have full accounts. These are sometimes called "grey users".
*
* TODO: We do not currently issue these sessions, see T4310.
*/
const KIND_EXTERNAL = 'X';
/**
* Session issued to logged-out users which has no real identity information.
* Its purpose is to protect logged-out users from CSRF.
*/
const KIND_ANONYMOUS = 'A';
/**
* Session kind isn't known.
*/
const KIND_UNKNOWN = '?';
/**
* Get the session kind (e.g., anonymous, user, external account) from a
* session token. Returns a `KIND_` constant.
*
* @param string Session token.
* @return const Session kind constant.
*/
public static function getSessionKindFromToken($session_token) {
if (strpos($session_token, '/') === false) {
// Old-style session, these are all user sessions.
return self::KIND_USER;
}
list($kind, $key) = explode('/', $session_token, 2);
switch ($kind) {
case self::KIND_ANONYMOUS:
case self::KIND_USER:
case self::KIND_EXTERNAL:
return $kind;
default:
return self::KIND_UNKNOWN;
}
}
public function loadUserForSession($session_type, $session_token) {
$session_kind = self::getSessionKindFromToken($session_token);
switch ($session_kind) {
case self::KIND_ANONYMOUS:
// Don't bother trying to load a user for an anonymous session, since
// neither the session nor the user exist.
return null;
case self::KIND_UNKNOWN:
// If we don't know what kind of session this is, don't go looking for
// it.
return null;
case self::KIND_USER:
break;
case self::KIND_EXTERNAL:
// TODO: Implement these (T4310).
return null;
}
$session_table = new PhabricatorAuthSession(); $session_table = new PhabricatorAuthSession();
$user_table = new PhabricatorUser(); $user_table = new PhabricatorUser();
$conn_r = $session_table->establishConnection('r'); $conn_r = $session_table->establishConnection('r');
@ -18,7 +90,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$user_table->getTableName(), $user_table->getTableName(),
$session_table->getTableName(), $session_table->getTableName(),
$session_type, $session_type,
PhabricatorHash::digest($session_key)); PhabricatorHash::digest($session_token));
if (!$info) { if (!$info) {
return null; return null;
@ -63,19 +135,24 @@ final class PhabricatorAuthSessionEngine extends Phobject {
* You can configure the maximum number of concurrent sessions for various * You can configure the maximum number of concurrent sessions for various
* session types in the Phabricator configuration. * session types in the Phabricator configuration.
* *
* @param const Session type constant (see * @param const Session type constant (see
* @{class:PhabricatorAuthSession}). * @{class:PhabricatorAuthSession}).
* @param phid Identity to establish a session for, usually a user PHID. * @param phid|null Identity to establish a session for, usually a user
* @return string Newly generated session key. * PHID. With `null`, generates an anonymous session.
* @return string Newly generated session key.
*/ */
public function establishSession($session_type, $identity_phid) { public function establishSession($session_type, $identity_phid) {
$session_table = new PhabricatorAuthSession();
$conn_w = $session_table->establishConnection('w');
// Consume entropy to generate a new session key, forestalling the eventual // Consume entropy to generate a new session key, forestalling the eventual
// heat death of the universe. // heat death of the universe.
$session_key = Filesystem::readRandomCharacters(40); $session_key = Filesystem::readRandomCharacters(40);
if ($identity_phid === null) {
return self::KIND_ANONYMOUS.'/'.$session_key;
}
$session_table = new PhabricatorAuthSession();
$conn_w = $session_table->establishConnection('w');
// This has a side effect of validating the session type. // This has a side effect of validating the session type.
$session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); $session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);

View file

@ -50,7 +50,6 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO
} }
} }
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -36,7 +36,7 @@ abstract class PhabricatorController extends AphrontController {
$user = new PhabricatorUser(); $user = new PhabricatorUser();
$phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
if ($phsid) { if (strlen($phsid)) {
$session_user = id(new PhabricatorAuthSessionEngine()) $session_user = id(new PhabricatorAuthSessionEngine())
->loadUserForSession(PhabricatorAuthSession::TYPE_WEB, $phsid); ->loadUserForSession(PhabricatorAuthSession::TYPE_WEB, $phsid);
if ($session_user) { if ($session_user) {

View file

@ -68,6 +68,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
if (!$this->session) { if (!$this->session) {
// TODO: This is not correct if there's a cookie prefix. This object // TODO: This is not correct if there's a cookie prefix. This object
// should take an AphrontRequest. // should take an AphrontRequest.
// TODO: Maybe record session kind, or drop this for anonymous sessions?
$this->setSession(idx($_COOKIE, PhabricatorCookies::COOKIE_SESSION)); $this->setSession(idx($_COOKIE, PhabricatorCookies::COOKIE_SESSION));
} }
$this->details['host'] = php_uname('n'); $this->details['host'] = php_uname('n');

View file

@ -163,6 +163,17 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
Javelin::initBehavior('history-install'); Javelin::initBehavior('history-install');
Javelin::initBehavior('phabricator-gesture'); Javelin::initBehavior('phabricator-gesture');
// If the client doesn't have a session token, generate an anonymous
// session. This is used to provide CSRF protection to logged-out users.
$session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
if (!strlen($session_token)) {
$anonymous_session = id(new PhabricatorAuthSessionEngine())
->establishSession('web', null);
$request->setCookie(
PhabricatorCookies::COOKIE_SESSION,
$anonymous_session);
}
$current_token = null; $current_token = null;
if ($user) { if ($user) {
$current_token = $user->getCSRFToken(); $current_token = $user->getCSRFToken();