2014-01-14 22:22:27 +01:00
|
|
|
<?php
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
/**
|
2014-05-01 19:23:19 +02:00
|
|
|
*
|
|
|
|
* @task use Using Sessions
|
|
|
|
* @task new Creating Sessions
|
|
|
|
* @task hisec High Security
|
|
|
|
* @task partial Partial Sessions
|
2014-04-28 02:31:11 +02:00
|
|
|
*/
|
2014-01-14 22:22:27 +01:00
|
|
|
final class PhabricatorAuthSessionEngine extends Phobject {
|
|
|
|
|
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 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-01 19:23:19 +02:00
|
|
|
/**
|
|
|
|
* Load the user identity associated with a session of a given type,
|
|
|
|
* identified by token.
|
|
|
|
*
|
|
|
|
* When the user presents a session token to an API, this method verifies
|
|
|
|
* it is of the correct type and loads the corresponding identity if the
|
|
|
|
* session exists and is valid.
|
|
|
|
*
|
|
|
|
* NOTE: `$session_type` is the type of session that is required by the
|
|
|
|
* loading context. This prevents use of a Conduit sesssion as a Web
|
|
|
|
* session, for example.
|
|
|
|
*
|
|
|
|
* @param const The type of session to load.
|
|
|
|
* @param string The session token.
|
|
|
|
* @return PhabricatorUser|null
|
|
|
|
* @task use
|
|
|
|
*/
|
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
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-01-14 22:22:27 +01:00
|
|
|
$session_table = new PhabricatorAuthSession();
|
|
|
|
$user_table = new PhabricatorUser();
|
2014-01-15 22:56:16 +01:00
|
|
|
$conn_r = $session_table->establishConnection('r');
|
2014-04-28 02:31:11 +02:00
|
|
|
$session_key = PhabricatorHash::digest($session_token);
|
2014-01-15 22:56:16 +01:00
|
|
|
|
|
|
|
// NOTE: We're being clever here because this happens on every page load,
|
2014-04-28 02:31:11 +02:00
|
|
|
// and by joining we can save a query. This might be getting too clever
|
|
|
|
// for its own good, though...
|
2014-01-14 22:22:27 +01:00
|
|
|
|
|
|
|
$info = queryfx_one(
|
|
|
|
$conn_r,
|
2014-04-28 02:31:11 +02:00
|
|
|
'SELECT
|
|
|
|
s.id AS s_id,
|
|
|
|
s.sessionExpires AS s_sessionExpires,
|
|
|
|
s.sessionStart AS s_sessionStart,
|
|
|
|
s.highSecurityUntil AS s_highSecurityUntil,
|
2014-05-01 19:23:02 +02:00
|
|
|
s.isPartial AS s_isPartial,
|
2014-04-28 02:31:11 +02:00
|
|
|
u.*
|
2014-01-15 22:56:16 +01:00
|
|
|
FROM %T u JOIN %T s ON u.phid = s.userPHID
|
2014-01-16 02:27:59 +01:00
|
|
|
AND s.type = %s AND s.sessionKey = %s',
|
2014-01-14 22:22:27 +01:00
|
|
|
$user_table->getTableName(),
|
|
|
|
$session_table->getTableName(),
|
2014-01-16 02:27:59 +01:00
|
|
|
$session_type,
|
2014-04-28 02:31:11 +02:00
|
|
|
$session_key);
|
2014-01-14 22:22:27 +01:00
|
|
|
|
|
|
|
if (!$info) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
$session_dict = array(
|
|
|
|
'userPHID' => $info['phid'],
|
|
|
|
'sessionKey' => $session_key,
|
|
|
|
'type' => $session_type,
|
|
|
|
);
|
|
|
|
foreach ($info as $key => $value) {
|
|
|
|
if (strncmp($key, 's_', 2) === 0) {
|
|
|
|
unset($info[$key]);
|
|
|
|
$session_dict[substr($key, 2)] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$session = id(new PhabricatorAuthSession())->loadFromArray($session_dict);
|
2014-01-15 22:56:16 +01:00
|
|
|
|
|
|
|
$ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
|
|
|
|
|
|
|
|
// If more than 20% of the time on this session has been used, refresh the
|
|
|
|
// TTL back up to the full duration. The idea here is that sessions are
|
|
|
|
// good forever if used regularly, but get GC'd when they fall out of use.
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
if (time() + (0.80 * $ttl) > $session->getSessionExpires()) {
|
2014-01-15 22:56:16 +01:00
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
|
|
$conn_w = $session_table->establishConnection('w');
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'UPDATE %T SET sessionExpires = UNIX_TIMESTAMP() + %d WHERE id = %d',
|
2014-04-28 02:31:11 +02:00
|
|
|
$session->getTableName(),
|
2014-01-15 22:56:16 +01:00
|
|
|
$ttl,
|
2014-04-28 02:31:11 +02:00
|
|
|
$session->getID());
|
2014-01-15 22:56:16 +01:00
|
|
|
unset($unguarded);
|
|
|
|
}
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
$user = $user_table->loadFromArray($info);
|
|
|
|
$user->attachSession($session);
|
|
|
|
return $user;
|
2014-01-14 22:22:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Issue a new session key for a given identity. Phabricator supports
|
|
|
|
* different types of sessions (like "web" and "conduit") and each session
|
|
|
|
* type may have multiple concurrent sessions (this allows a user to be
|
|
|
|
* logged in on multiple browsers at the same time, for instance).
|
|
|
|
*
|
|
|
|
* Note that this method is transport-agnostic and does not set cookies or
|
|
|
|
* issue other types of tokens, it ONLY generates a new session key.
|
|
|
|
*
|
|
|
|
* You can configure the maximum number of concurrent sessions for various
|
|
|
|
* session types in the Phabricator configuration.
|
|
|
|
*
|
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
|
|
|
* @param const Session type constant (see
|
|
|
|
* @{class:PhabricatorAuthSession}).
|
|
|
|
* @param phid|null Identity to establish a session for, usually a user
|
|
|
|
* PHID. With `null`, generates an anonymous session.
|
2014-05-01 19:23:02 +02:00
|
|
|
* @param bool True to issue a partial session.
|
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 string Newly generated session key.
|
2014-01-14 22:22:27 +01:00
|
|
|
*/
|
2014-05-01 19:23:02 +02:00
|
|
|
public function establishSession($session_type, $identity_phid, $partial) {
|
2014-01-14 22:22:27 +01:00
|
|
|
// Consume entropy to generate a new session key, forestalling the eventual
|
|
|
|
// heat death of the universe.
|
|
|
|
$session_key = Filesystem::readRandomCharacters(40);
|
|
|
|
|
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 ($identity_phid === null) {
|
|
|
|
return self::KIND_ANONYMOUS.'/'.$session_key;
|
|
|
|
}
|
|
|
|
|
|
|
|
$session_table = new PhabricatorAuthSession();
|
|
|
|
$conn_w = $session_table->establishConnection('w');
|
|
|
|
|
2014-01-16 02:27:59 +01:00
|
|
|
// This has a side effect of validating the session type.
|
|
|
|
$session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
|
2014-01-14 22:22:27 +01:00
|
|
|
|
2014-05-01 19:23:02 +02:00
|
|
|
$digest_key = PhabricatorHash::digest($session_key);
|
|
|
|
|
2014-01-16 02:27:59 +01:00
|
|
|
// Logging-in users don't have CSRF stuff yet, so we have to unguard this
|
|
|
|
// write.
|
2014-01-14 22:22:27 +01:00
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
2014-01-16 02:27:59 +01:00
|
|
|
id(new PhabricatorAuthSession())
|
|
|
|
->setUserPHID($identity_phid)
|
|
|
|
->setType($session_type)
|
2014-05-01 19:23:02 +02:00
|
|
|
->setSessionKey($digest_key)
|
2014-01-16 02:27:59 +01:00
|
|
|
->setSessionStart(time())
|
|
|
|
->setSessionExpires(time() + $session_ttl)
|
2014-05-01 19:23:02 +02:00
|
|
|
->setIsPartial($partial ? 1 : 0)
|
2014-01-16 02:27:59 +01:00
|
|
|
->save();
|
|
|
|
|
|
|
|
$log = PhabricatorUserLog::initializeNewLog(
|
|
|
|
null,
|
2014-01-14 22:22:27 +01:00
|
|
|
$identity_phid,
|
2014-05-01 19:23:02 +02:00
|
|
|
($partial
|
|
|
|
? PhabricatorUserLog::ACTION_LOGIN_PARTIAL
|
|
|
|
: PhabricatorUserLog::ACTION_LOGIN));
|
|
|
|
|
2014-01-16 02:27:59 +01:00
|
|
|
$log->setDetails(
|
|
|
|
array(
|
|
|
|
'session_type' => $session_type,
|
|
|
|
));
|
2014-05-01 19:23:02 +02:00
|
|
|
$log->setSession($digest_key);
|
2014-01-16 02:27:59 +01:00
|
|
|
$log->save();
|
|
|
|
unset($unguarded);
|
2014-01-14 22:22:27 +01:00
|
|
|
|
|
|
|
return $session_key;
|
|
|
|
}
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
|
2014-05-01 19:23:19 +02:00
|
|
|
/* -( High Security )------------------------------------------------------ */
|
|
|
|
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
/**
|
|
|
|
* Require high security, or prompt the user to enter high security.
|
|
|
|
*
|
|
|
|
* If the user's session is in high security, this method will return a
|
|
|
|
* token. Otherwise, it will throw an exception which will eventually
|
|
|
|
* be converted into a multi-factor authentication workflow.
|
|
|
|
*
|
|
|
|
* @param PhabricatorUser User whose session needs to be in high security.
|
|
|
|
* @param AphrontReqeust Current request.
|
|
|
|
* @param string URI to return the user to if they cancel.
|
|
|
|
* @return PhabricatorAuthHighSecurityToken Security token.
|
2014-05-01 19:23:19 +02:00
|
|
|
* @task hisec
|
2014-04-28 02:31:11 +02:00
|
|
|
*/
|
|
|
|
public function requireHighSecuritySession(
|
|
|
|
PhabricatorUser $viewer,
|
|
|
|
AphrontRequest $request,
|
|
|
|
$cancel_uri) {
|
|
|
|
|
|
|
|
if (!$viewer->hasSession()) {
|
|
|
|
throw new Exception(
|
|
|
|
pht('Requiring a high-security session from a user with no session!'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$session = $viewer->getSession();
|
|
|
|
|
2014-04-28 19:20:54 +02:00
|
|
|
// Check if the session is already in high security mode.
|
2014-04-28 02:31:11 +02:00
|
|
|
$token = $this->issueHighSecurityToken($session);
|
|
|
|
if ($token) {
|
|
|
|
return $token;
|
|
|
|
}
|
|
|
|
|
2014-04-28 19:20:54 +02:00
|
|
|
// Load the multi-factor auth sources attached to this account.
|
|
|
|
$factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere(
|
|
|
|
'userPHID = %s',
|
|
|
|
$viewer->getPHID());
|
|
|
|
|
|
|
|
// If the account has no associated multi-factor auth, just issue a token
|
|
|
|
// without putting the session into high security mode. This is generally
|
|
|
|
// easier for users. A minor but desirable side effect is that when a user
|
|
|
|
// adds an auth factor, existing sessions won't get a free pass into hisec,
|
|
|
|
// since they never actually got marked as hisec.
|
|
|
|
if (!$factors) {
|
|
|
|
return $this->issueHighSecurityToken($session, true);
|
|
|
|
}
|
|
|
|
|
2014-04-30 23:30:31 +02:00
|
|
|
// Check for a rate limit without awarding points, so the user doesn't
|
|
|
|
// get partway through the workflow only to get blocked.
|
|
|
|
PhabricatorSystemActionEngine::willTakeAction(
|
|
|
|
array($viewer->getPHID()),
|
|
|
|
new PhabricatorAuthTryFactorAction(),
|
|
|
|
0);
|
|
|
|
|
2014-04-28 19:20:54 +02:00
|
|
|
$validation_results = array();
|
2014-04-28 02:31:11 +02:00
|
|
|
if ($request->isHTTPPost()) {
|
|
|
|
$request->validateCSRF();
|
|
|
|
if ($request->getExists(AphrontRequest::TYPE_HISEC)) {
|
|
|
|
|
2014-04-30 23:30:31 +02:00
|
|
|
// Limit factor verification rates to prevent brute force attacks.
|
|
|
|
PhabricatorSystemActionEngine::willTakeAction(
|
|
|
|
array($viewer->getPHID()),
|
|
|
|
new PhabricatorAuthTryFactorAction(),
|
|
|
|
1);
|
|
|
|
|
2014-04-28 19:20:54 +02:00
|
|
|
$ok = true;
|
|
|
|
foreach ($factors as $factor) {
|
|
|
|
$id = $factor->getID();
|
|
|
|
$impl = $factor->requireImplementation();
|
|
|
|
|
|
|
|
$validation_results[$id] = $impl->processValidateFactorForm(
|
|
|
|
$factor,
|
|
|
|
$viewer,
|
|
|
|
$request);
|
|
|
|
|
|
|
|
if (!$impl->isFactorValid($factor, $validation_results[$id])) {
|
|
|
|
$ok = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($ok) {
|
2014-04-30 23:30:31 +02:00
|
|
|
// Give the user a credit back for a successful factor verification.
|
|
|
|
PhabricatorSystemActionEngine::willTakeAction(
|
|
|
|
array($viewer->getPHID()),
|
|
|
|
new PhabricatorAuthTryFactorAction(),
|
|
|
|
-1);
|
|
|
|
|
2014-05-01 19:23:02 +02:00
|
|
|
if ($session->getIsPartial()) {
|
|
|
|
// If we have a partial session, just issue a token without
|
|
|
|
// putting it in high security mode.
|
|
|
|
return $this->issueHighSecurityToken($session, true);
|
|
|
|
}
|
|
|
|
|
2014-04-28 19:20:54 +02:00
|
|
|
$until = time() + phutil_units('15 minutes in seconds');
|
|
|
|
$session->setHighSecurityUntil($until);
|
|
|
|
|
|
|
|
queryfx(
|
|
|
|
$session->establishConnection('w'),
|
|
|
|
'UPDATE %T SET highSecurityUntil = %d WHERE id = %d',
|
|
|
|
$session->getTableName(),
|
|
|
|
$until,
|
|
|
|
$session->getID());
|
|
|
|
|
|
|
|
$log = PhabricatorUserLog::initializeNewLog(
|
|
|
|
$viewer,
|
|
|
|
$viewer->getPHID(),
|
|
|
|
PhabricatorUserLog::ACTION_ENTER_HISEC);
|
|
|
|
$log->save();
|
|
|
|
} else {
|
|
|
|
$log = PhabricatorUserLog::initializeNewLog(
|
|
|
|
$viewer,
|
|
|
|
$viewer->getPHID(),
|
|
|
|
PhabricatorUserLog::ACTION_FAIL_HISEC);
|
|
|
|
$log->save();
|
|
|
|
}
|
2014-04-28 02:31:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$token = $this->issueHighSecurityToken($session);
|
|
|
|
if ($token) {
|
|
|
|
return $token;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw id(new PhabricatorAuthHighSecurityRequiredException())
|
2014-04-28 19:20:54 +02:00
|
|
|
->setCancelURI($cancel_uri)
|
|
|
|
->setFactors($factors)
|
|
|
|
->setFactorValidationResults($validation_results);
|
2014-04-28 02:31:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Issue a high security token for a session, if authorized.
|
|
|
|
*
|
|
|
|
* @param PhabricatorAuthSession Session to issue a token for.
|
2014-05-01 19:23:02 +02:00
|
|
|
* @param bool Force token issue.
|
2014-04-28 02:31:11 +02:00
|
|
|
* @return PhabricatorAuthHighSecurityToken|null Token, if authorized.
|
2014-05-01 19:23:19 +02:00
|
|
|
* @task hisec
|
2014-04-28 02:31:11 +02:00
|
|
|
*/
|
2014-05-01 19:23:02 +02:00
|
|
|
private function issueHighSecurityToken(
|
|
|
|
PhabricatorAuthSession $session,
|
|
|
|
$force = false) {
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
$until = $session->getHighSecurityUntil();
|
2014-05-01 19:23:02 +02:00
|
|
|
if ($until > time() || $force) {
|
2014-04-28 02:31:11 +02:00
|
|
|
return new PhabricatorAuthHighSecurityToken();
|
|
|
|
}
|
2014-05-01 19:23:19 +02:00
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render a form for providing relevant multi-factor credentials.
|
|
|
|
*
|
2014-05-01 19:23:19 +02:00
|
|
|
* @param PhabricatorUser Viewing user.
|
|
|
|
* @param AphrontRequest Current request.
|
|
|
|
* @return AphrontFormView Renderable form.
|
|
|
|
* @task hisec
|
2014-04-28 02:31:11 +02:00
|
|
|
*/
|
|
|
|
public function renderHighSecurityForm(
|
2014-04-28 19:20:54 +02:00
|
|
|
array $factors,
|
|
|
|
array $validation_results,
|
2014-04-28 02:31:11 +02:00
|
|
|
PhabricatorUser $viewer,
|
|
|
|
AphrontRequest $request) {
|
|
|
|
|
|
|
|
$form = id(new AphrontFormView())
|
|
|
|
->setUser($viewer)
|
|
|
|
->appendRemarkupInstructions('');
|
|
|
|
|
2014-04-28 19:20:54 +02:00
|
|
|
foreach ($factors as $factor) {
|
|
|
|
$factor->requireImplementation()->renderValidateFactorForm(
|
|
|
|
$factor,
|
|
|
|
$form,
|
|
|
|
$viewer,
|
|
|
|
idx($validation_results, $factor->getID()));
|
|
|
|
}
|
|
|
|
|
|
|
|
$form->appendRemarkupInstructions('');
|
|
|
|
|
2014-04-28 02:31:11 +02:00
|
|
|
return $form;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-01 19:23:19 +02:00
|
|
|
/**
|
|
|
|
* Strip the high security flag from a session.
|
|
|
|
*
|
|
|
|
* Kicks a session out of high security and logs the exit.
|
|
|
|
*
|
|
|
|
* @param PhabricatorUser Acting user.
|
|
|
|
* @param PhabricatorAuthSession Session to return to normal security.
|
|
|
|
* @return void
|
|
|
|
* @task hisec
|
|
|
|
*/
|
2014-04-28 02:32:09 +02:00
|
|
|
public function exitHighSecurity(
|
|
|
|
PhabricatorUser $viewer,
|
|
|
|
PhabricatorAuthSession $session) {
|
|
|
|
|
2014-05-01 19:23:19 +02:00
|
|
|
if (!$session->getHighSecurityUntil()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-04-28 02:32:09 +02:00
|
|
|
queryfx(
|
|
|
|
$session->establishConnection('w'),
|
|
|
|
'UPDATE %T SET highSecurityUntil = NULL WHERE id = %d',
|
|
|
|
$session->getTableName(),
|
|
|
|
$session->getID());
|
|
|
|
|
|
|
|
$log = PhabricatorUserLog::initializeNewLog(
|
|
|
|
$viewer,
|
|
|
|
$viewer->getPHID(),
|
|
|
|
PhabricatorUserLog::ACTION_EXIT_HISEC);
|
|
|
|
$log->save();
|
|
|
|
}
|
|
|
|
|
2014-05-01 19:23:02 +02:00
|
|
|
|
2014-05-01 19:23:19 +02:00
|
|
|
/* -( Partial Sessions )--------------------------------------------------- */
|
|
|
|
|
|
|
|
|
2014-05-01 19:23:02 +02:00
|
|
|
/**
|
|
|
|
* Upgrade a partial session to a full session.
|
|
|
|
*
|
|
|
|
* @param PhabricatorAuthSession Session to upgrade.
|
|
|
|
* @return void
|
2014-05-01 19:23:19 +02:00
|
|
|
* @task partial
|
2014-05-01 19:23:02 +02:00
|
|
|
*/
|
|
|
|
public function upgradePartialSession(PhabricatorUser $viewer) {
|
|
|
|
if (!$viewer->hasSession()) {
|
|
|
|
throw new Exception(
|
|
|
|
pht('Upgrading partial session of user with no session!'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$session = $viewer->getSession();
|
|
|
|
|
|
|
|
if (!$session->getIsPartial()) {
|
|
|
|
throw new Exception(pht('Session is not partial!'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
|
|
$session->setIsPartial(0);
|
|
|
|
|
|
|
|
queryfx(
|
|
|
|
$session->establishConnection('w'),
|
|
|
|
'UPDATE %T SET isPartial = %d WHERE id = %d',
|
|
|
|
$session->getTableName(),
|
|
|
|
0,
|
|
|
|
$session->getID());
|
|
|
|
|
|
|
|
$log = PhabricatorUserLog::initializeNewLog(
|
|
|
|
$viewer,
|
|
|
|
$viewer->getPHID(),
|
|
|
|
PhabricatorUserLog::ACTION_LOGIN_FULL);
|
|
|
|
$log->save();
|
|
|
|
unset($unguarded);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-01-14 22:22:27 +01:00
|
|
|
}
|