1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-24 22:40:55 +01:00

Allow applications to require a High Security token without doing a session upgrade

Summary:
Ref T13222. See PHI873. Currently, when applications prompt users to enter MFA, their session upgrades as a side effect.

In some cases (like managing your email addresses) it makes sense to upgrade your session for a little while since it's common to make multiple edits in sequence (add a new address, make it primary, remove an old address). We generally want MFA to stay out of the way and not feel annoying.

In other cases, we don't expect multiple high-security actions in a row. Notably, PHI873 looks at more "one-shot" use cases where a prompt is answering a specific workflow. We already have at least one of these in the upstream: answering an MFA prompt when signing a Legalpad document.

Introduce a "token" workflow (in contrast to the existing "session") workflow that just does a one-shot prompt without upgrading your session statefully. Then, make Legalpad use this new workflow.

Note that this workflow has a significant problem: if the form submission is invalid for some other reason, we re-prompt you on resubmit. In Legalpad, this workflow looks like:

  - Forget to check the "I agree" checkbox.
  - Submit the form.
  - Get prompted for MFA.
  - Answer MFA prompt.
  - Get dumped back to the form with an error.
  - When you fix the error and submit again, you have to do another MFA check.

This isn't a fatal flaw in Legalpad, but would become a problem with wider adoption. I'll work on fixing this (so the MFA token sticks to the form) in the next set of changes.

Roughly, this is headed toward "MFA sticks to the form/workflow" instead of "MFA sticks to the user/session".

Test Plan:
  - Signed a legalpad document with MFA enabled.
  - Was prompted for MFA.
  - Session no longer upgraded (no purple "session in high security" badge).
  - Submitted form with error, answered MFA, fixed error, submitted form again.
    - Bad behavior: got re-prompted for MFA. In the future, MFA should stick to the form.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

Differential Revision: https://secure.phabricator.com/D19843
This commit is contained in:
epriestley 2018-11-27 05:14:45 -08:00
parent bb369c7b71
commit 1d0b99e1f8
3 changed files with 63 additions and 6 deletions

View file

@ -345,6 +345,33 @@ final class PhabricatorAuthSessionEngine extends Phobject {
/* -( High Security )------------------------------------------------------ */ /* -( High Security )------------------------------------------------------ */
/**
* Require the user respond to a high security (MFA) check.
*
* This method differs from @{method:requireHighSecuritySession} in that it
* does not upgrade the user's session as a side effect. This method is
* appropriate for one-time checks.
*
* @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.
* @task hisec
*/
public function requireHighSecurityToken(
PhabricatorUser $viewer,
AphrontRequest $request,
$cancel_uri) {
return $this->newHighSecurityToken(
$viewer,
$request,
$cancel_uri,
false,
false);
}
/** /**
* Require high security, or prompt the user to enter high security. * Require high security, or prompt the user to enter high security.
* *
@ -352,6 +379,11 @@ final class PhabricatorAuthSessionEngine extends Phobject {
* token. Otherwise, it will throw an exception which will eventually * token. Otherwise, it will throw an exception which will eventually
* be converted into a multi-factor authentication workflow. * be converted into a multi-factor authentication workflow.
* *
* This method upgrades the user's session to high security for a short
* period of time, and is appropriate if you anticipate they may need to
* take multiple high security actions. To perform a one-time check instead,
* use @{method:requireHighSecurityToken}.
*
* @param PhabricatorUser User whose session needs to be in high security. * @param PhabricatorUser User whose session needs to be in high security.
* @param AphrontReqeust Current request. * @param AphrontReqeust Current request.
* @param string URI to return the user to if they cancel. * @param string URI to return the user to if they cancel.
@ -367,11 +399,30 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$cancel_uri, $cancel_uri,
$jump_into_hisec = false) { $jump_into_hisec = false) {
return $this->newHighSecurityToken(
$viewer,
$request,
$cancel_uri,
false,
true);
}
private function newHighSecurityToken(
PhabricatorUser $viewer,
AphrontRequest $request,
$cancel_uri,
$jump_into_hisec,
$upgrade_session) {
if (!$viewer->hasSession()) { if (!$viewer->hasSession()) {
throw new Exception( throw new Exception(
pht('Requiring a high-security session from a user with no session!')); pht('Requiring a high-security session from a user with no session!'));
} }
// TODO: If a user answers a "requireHighSecurityToken()" prompt and hits
// a "requireHighSecuritySession()" prompt a short time later, the one-shot
// token should be good enough to upgrade the session.
$session = $viewer->getSession(); $session = $viewer->getSession();
// Check if the session is already in high security mode. // Check if the session is already in high security mode.
@ -441,6 +492,11 @@ final class PhabricatorAuthSessionEngine extends Phobject {
return $this->issueHighSecurityToken($session, true); return $this->issueHighSecurityToken($session, true);
} }
// If we aren't upgrading the session itself, just issue a token.
if (!$upgrade_session) {
return $this->issueHighSecurityToken($session, true);
}
$until = time() + phutil_units('15 minutes in seconds'); $until = time() + phutil_units('15 minutes in seconds');
$session->setHighSecurityUntil($until); $session->setHighSecurityUntil($until);

View file

@ -149,15 +149,16 @@ final class LegalpadDocumentSignController extends LegalpadController {
} }
$errors = array(); $errors = array();
$hisec_token = null;
if ($request->isFormOrHisecPost() && !$has_signed) { if ($request->isFormOrHisecPost() && !$has_signed) {
// Require two-factor auth to sign legal documents. // Require two-factor auth to sign legal documents.
if ($viewer->isLoggedIn()) { if ($viewer->isLoggedIn()) {
$engine = new PhabricatorAuthSessionEngine(); $hisec_token = id(new PhabricatorAuthSessionEngine())
$engine->requireHighSecuritySession( ->requireHighSecurityToken(
$viewer, $viewer,
$request, $request,
'/'.$document->getMonogram()); $document->getURI());
} }
list($form_data, $errors, $field_errors) = $this->readSignatureForm( list($form_data, $errors, $field_errors) = $this->readSignatureForm(

View file

@ -120,7 +120,7 @@ final class LegalpadDocument extends LegalpadDAO
return 'L'.$this->getID(); return 'L'.$this->getID();
} }
public function getViewURI() { public function getURI() {
return '/'.$this->getMonogram(); return '/'.$this->getMonogram();
} }