Skip to content

Commit c9ff6ce

Browse files
author
epriestley
committedJan 24, 2019
Add CSRF to SMS challenges, and pave the way for more MFA types (including Duo)
Summary: Depends on D20026. Ref T13222. Ref T13231. The primary change here is that we'll no longer send you an SMS if you hit an MFA gate without CSRF tokens. Then there's a lot of support for genralizing into Duo (and other push factors, potentially), I'll annotate things inline. Test Plan: Implemented Duo, elsewhere. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13231, T13222 Differential Revision: https://secure.phabricator.com/D20028
1 parent 0691604 commit c9ff6ce

15 files changed

+279
-113
lines changed
 

‎resources/celerity/map.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
'names' => array(
1010
'conpherence.pkg.css' => '3c8a0668',
1111
'conpherence.pkg.js' => '020aebcf',
12-
'core.pkg.css' => 'a66ea2e7',
12+
'core.pkg.css' => 'e0cb8094',
1313
'core.pkg.js' => '5c737607',
1414
'differential.pkg.css' => 'b8df73d4',
1515
'differential.pkg.js' => '67c9ea4c',
@@ -151,15 +151,15 @@
151151
'rsrc/css/phui/phui-document.css' => '52b748a5',
152152
'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
153153
'rsrc/css/phui/phui-fontkit.css' => '9b714a5e',
154-
'rsrc/css/phui/phui-form-view.css' => '9508671e',
154+
'rsrc/css/phui/phui-form-view.css' => '0807e7ac',
155155
'rsrc/css/phui/phui-form.css' => '159e2d9c',
156156
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
157157
'rsrc/css/phui/phui-header-view.css' => '93cea4ec',
158158
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
159159
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
160160
'rsrc/css/phui/phui-icon.css' => '281f964d',
161161
'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2',
162-
'rsrc/css/phui/phui-info-view.css' => 'f9464caf',
162+
'rsrc/css/phui/phui-info-view.css' => '37b8d9ce',
163163
'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
164164
'rsrc/css/phui/phui-left-right.css' => '68513c34',
165165
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
@@ -817,15 +817,15 @@
817817
'phui-font-icon-base-css' => 'd7994e06',
818818
'phui-fontkit-css' => '9b714a5e',
819819
'phui-form-css' => '159e2d9c',
820-
'phui-form-view-css' => '9508671e',
820+
'phui-form-view-css' => '0807e7ac',
821821
'phui-head-thing-view-css' => 'd7f293df',
822822
'phui-header-view-css' => '93cea4ec',
823823
'phui-hovercard' => '074f0783',
824824
'phui-hovercard-view-css' => '6ca90fa0',
825825
'phui-icon-set-selector-css' => '7aa5f3ec',
826826
'phui-icon-view-css' => '281f964d',
827827
'phui-image-mask-css' => '62c7f4d2',
828-
'phui-info-view-css' => 'f9464caf',
828+
'phui-info-view-css' => '37b8d9ce',
829829
'phui-inline-comment-view-css' => '48acce5b',
830830
'phui-invisible-character-view-css' => 'c694c4a4',
831831
'phui-left-right-css' => '68513c34',

‎src/__phutil_library_map__.php

+2
Original file line numberDiff line numberDiff line change
@@ -2239,6 +2239,7 @@
22392239
'PhabricatorAuthFactorProviderTransactionType' => 'applications/auth/xaction/PhabricatorAuthFactorProviderTransactionType.php',
22402240
'PhabricatorAuthFactorProviderViewController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderViewController.php',
22412241
'PhabricatorAuthFactorResult' => 'applications/auth/factor/PhabricatorAuthFactorResult.php',
2242+
'PhabricatorAuthFactorResultException' => 'applications/auth/exception/PhabricatorAuthFactorResultException.php',
22422243
'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php',
22432244
'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php',
22442245
'PhabricatorAuthHMACKey' => 'applications/auth/storage/PhabricatorAuthHMACKey.php',
@@ -7965,6 +7966,7 @@
79657966
'PhabricatorAuthFactorProviderTransactionType' => 'PhabricatorModularTransactionType',
79667967
'PhabricatorAuthFactorProviderViewController' => 'PhabricatorAuthFactorProviderController',
79677968
'PhabricatorAuthFactorResult' => 'Phobject',
7969+
'PhabricatorAuthFactorResultException' => 'Exception',
79687970
'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase',
79697971
'PhabricatorAuthFinishController' => 'PhabricatorAuthController',
79707972
'PhabricatorAuthHMACKey' => 'PhabricatorAuthDAO',

‎src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php

+18-12
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,14 @@ public function handleRequestThrowable(
3838
$request);
3939

4040
$is_wait = false;
41+
$is_continue = false;
4142
foreach ($results as $result) {
4243
if ($result->getIsWait()) {
4344
$is_wait = true;
44-
break;
45+
}
46+
47+
if ($result->getIsContinue()) {
48+
$is_continue = true;
4549
}
4650
}
4751

@@ -55,7 +59,7 @@ public function handleRequestThrowable(
5559

5660
if ($is_wait) {
5761
$submit = pht('Wait Patiently');
58-
} else if ($is_upgrade) {
62+
} else if ($is_upgrade && !$is_continue) {
5963
$submit = pht('Enter High Security');
6064
} else {
6165
$submit = pht('Continue');
@@ -74,19 +78,21 @@ public function handleRequestThrowable(
7478
$form_layout = $form->buildLayoutView();
7579

7680
if ($is_upgrade) {
81+
$messages = array(
82+
pht(
83+
'You are taking an action which requires you to enter '.
84+
'high security.'),
85+
);
86+
87+
$info_view = id(new PHUIInfoView())
88+
->setSeverity(PHUIInfoView::SEVERITY_MFA)
89+
->setErrors($messages);
90+
7791
$dialog
78-
->setErrors(
79-
array(
80-
pht(
81-
'You are taking an action which requires you to enter '.
82-
'high security.'),
83-
))
92+
->appendChild($info_view)
8493
->appendParagraph(
8594
pht(
86-
'High security mode helps protect your account from security '.
87-
'threats, like session theft or someone messing with your stuff '.
88-
'while you\'re grabbing a coffee. To enter high security mode, '.
89-
'confirm your credentials.'))
95+
'To enter high security mode, confirm your credentials:'))
9096
->appendChild($form_layout)
9197
->appendParagraph(
9298
pht(

‎src/applications/auth/engine/PhabricatorAuthSessionEngine.php

+21-5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
4747

4848

4949
private $workflowKey;
50+
private $request;
5051

5152
public function setWorkflowKey($workflow_key) {
5253
$this->workflowKey = $workflow_key;
@@ -65,6 +66,10 @@ public function getWorkflowKey() {
6566
return $this->workflowKey;
6667
}
6768

69+
public function getRequest() {
70+
return $this->request;
71+
}
72+
6873

6974
/**
7075
* Get the session kind (e.g., anonymous, user, external account) from a
@@ -480,6 +485,7 @@ private function newHighSecurityToken(
480485
return $this->issueHighSecurityToken($session, true);
481486
}
482487

488+
$this->request = $request;
483489
foreach ($factors as $factor) {
484490
$factor->setSessionEngine($this);
485491
}
@@ -523,10 +529,17 @@ private function newHighSecurityToken(
523529
$provider = $factor->getFactorProvider();
524530
$impl = $provider->getFactor();
525531

526-
$new_challenges = $impl->getNewIssuedChallenges(
527-
$factor,
528-
$viewer,
529-
$issued_challenges);
532+
try {
533+
$new_challenges = $impl->getNewIssuedChallenges(
534+
$factor,
535+
$viewer,
536+
$issued_challenges);
537+
} catch (PhabricatorAuthFactorResultException $ex) {
538+
$ok = false;
539+
$validation_results[$factor_phid] = $ex->getResult();
540+
$challenge_map[$factor_phid] = $issued_challenges;
541+
continue;
542+
}
530543

531544
foreach ($new_challenges as $new_challenge) {
532545
$issued_challenges[] = $new_challenge;
@@ -546,7 +559,10 @@ private function newHighSecurityToken(
546559
continue;
547560
}
548561

549-
$ok = false;
562+
if (!$result->getIsValid()) {
563+
$ok = false;
564+
}
565+
550566
$validation_results[$factor_phid] = $result;
551567
}
552568

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
final class PhabricatorAuthFactorResultException
4+
extends Exception {
5+
6+
private $result;
7+
8+
public function __construct(PhabricatorAuthFactorResult $result) {
9+
$this->result = $result;
10+
parent::__construct();
11+
}
12+
13+
public function getResult() {
14+
return $this->result;
15+
}
16+
17+
}

‎src/applications/auth/factor/PhabricatorAuthFactor.php

+120
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ abstract protected function newResultFromChallengeResponse(
232232
final protected function newAutomaticControl(
233233
PhabricatorAuthFactorResult $result) {
234234

235+
$is_error = $result->getIsError();
236+
if ($is_error) {
237+
return $this->newErrorControl($result);
238+
}
239+
240+
$is_continue = $result->getIsContinue();
241+
if ($is_continue) {
242+
return $this->newContinueControl($result);
243+
}
244+
235245
$is_answered = (bool)$result->getAnsweredChallenge();
236246
if ($is_answered) {
237247
return $this->newAnsweredControl($result);
@@ -271,6 +281,34 @@ private function newAnsweredControl(
271281
pht('You responded to this challenge correctly.'));
272282
}
273283

284+
private function newErrorControl(
285+
PhabricatorAuthFactorResult $result) {
286+
287+
$error = $result->getErrorMessage();
288+
289+
$icon = id(new PHUIIconView())
290+
->setIcon('fa-times', 'red');
291+
292+
return id(new PHUIFormTimerControl())
293+
->setIcon($icon)
294+
->appendChild($error)
295+
->setError(pht('Error'));
296+
}
297+
298+
private function newContinueControl(
299+
PhabricatorAuthFactorResult $result) {
300+
301+
$error = $result->getErrorMessage();
302+
303+
$icon = id(new PHUIIconView())
304+
->setIcon('fa-commenting', 'green');
305+
306+
return id(new PHUIFormTimerControl())
307+
->setIcon($icon)
308+
->appendChild($error);
309+
}
310+
311+
274312

275313
/* -( Synchronizing New Factors )------------------------------------------ */
276314

@@ -400,4 +438,86 @@ final protected function getChallengeForCurrentContext(
400438
return null;
401439
}
402440

441+
442+
/**
443+
* @phutil-external-symbol class QRcode
444+
*/
445+
final protected function newQRCode($uri) {
446+
$root = dirname(phutil_get_library_root('phabricator'));
447+
require_once $root.'/externals/phpqrcode/phpqrcode.php';
448+
449+
$lines = QRcode::text($uri);
450+
451+
$total_width = 240;
452+
$cell_size = floor($total_width / count($lines));
453+
454+
$rows = array();
455+
foreach ($lines as $line) {
456+
$cells = array();
457+
for ($ii = 0; $ii < strlen($line); $ii++) {
458+
if ($line[$ii] == '1') {
459+
$color = '#000';
460+
} else {
461+
$color = '#fff';
462+
}
463+
464+
$cells[] = phutil_tag(
465+
'td',
466+
array(
467+
'width' => $cell_size,
468+
'height' => $cell_size,
469+
'style' => 'background: '.$color,
470+
),
471+
'');
472+
}
473+
$rows[] = phutil_tag('tr', array(), $cells);
474+
}
475+
476+
return phutil_tag(
477+
'table',
478+
array(
479+
'style' => 'margin: 24px auto;',
480+
),
481+
$rows);
482+
}
483+
484+
final protected function throwResult(PhabricatorAuthFactorResult $result) {
485+
throw new PhabricatorAuthFactorResultException($result);
486+
}
487+
488+
final protected function getInstallDisplayName() {
489+
$uri = PhabricatorEnv::getURI('/');
490+
$uri = new PhutilURI($uri);
491+
return $uri->getDomain();
492+
}
493+
494+
final protected function getChallengeResponseParameterName(
495+
PhabricatorAuthFactorConfig $config) {
496+
return $this->getParameterName($config, 'mfa.response');
497+
}
498+
499+
final protected function getChallengeResponseFromRequest(
500+
PhabricatorAuthFactorConfig $config,
501+
AphrontRequest $request) {
502+
503+
$name = $this->getChallengeResponseParameterName($config);
504+
505+
$value = $request->getStr($name);
506+
$value = (string)$value;
507+
$value = trim($value);
508+
509+
return $value;
510+
}
511+
512+
final protected function hasCSRF(PhabricatorAuthFactorConfig $config) {
513+
$engine = $config->getSessionEngine();
514+
$request = $engine->getRequest();
515+
516+
if (!$request->isHTTPPost()) {
517+
return false;
518+
}
519+
520+
return $request->validateCSRF();
521+
}
522+
403523
}

‎src/applications/auth/factor/PhabricatorAuthFactorResult.php

+20
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ final class PhabricatorAuthFactorResult
55

66
private $answeredChallenge;
77
private $isWait = false;
8+
private $isError = false;
9+
private $isContinue = false;
810
private $errorMessage;
911
private $value;
1012
private $issuedChallenges = array();
@@ -44,6 +46,24 @@ public function getIsWait() {
4446
return $this->isWait;
4547
}
4648

49+
public function setIsError($is_error) {
50+
$this->isError = $is_error;
51+
return $this;
52+
}
53+
54+
public function getIsError() {
55+
return $this->isError;
56+
}
57+
58+
public function setIsContinue($is_continue) {
59+
$this->isContinue = $is_continue;
60+
return $this;
61+
}
62+
63+
public function getIsContinue() {
64+
return $this->isContinue;
65+
}
66+
4767
public function setErrorMessage($error_message) {
4868
$this->errorMessage = $error_message;
4969
return $this;

0 commit comments

Comments
 (0)
Failed to load comments.