Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15433895
D8851.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
D8851.diff
View Options
diff --git a/resources/sql/autopatches/20140423.session.1.hisec.sql b/resources/sql/autopatches/20140423.session.1.hisec.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140423.session.1.hisec.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_user.phabricator_session
+ ADD highSecurityUntil INT UNSIGNED;
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1206,7 +1206,10 @@
'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php',
'PhabricatorAuthDAO' => 'applications/auth/storage/PhabricatorAuthDAO.php',
'PhabricatorAuthDisableController' => 'applications/auth/controller/config/PhabricatorAuthDisableController.php',
+ 'PhabricatorAuthDowngradeSessionController' => 'applications/auth/controller/PhabricatorAuthDowngradeSessionController.php',
'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php',
+ 'PhabricatorAuthHighSecurityRequiredException' => 'applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php',
+ 'PhabricatorAuthHighSecurityToken' => 'applications/auth/data/PhabricatorAuthHighSecurityToken.php',
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
@@ -3947,7 +3950,9 @@
'PhabricatorAuthController' => 'PhabricatorController',
'PhabricatorAuthDAO' => 'PhabricatorLiskDAO',
'PhabricatorAuthDisableController' => 'PhabricatorAuthProviderConfigController',
+ 'PhabricatorAuthDowngradeSessionController' => 'PhabricatorAuthController',
'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController',
+ 'PhabricatorAuthHighSecurityRequiredException' => 'Exception',
'PhabricatorAuthLinkController' => 'PhabricatorAuthController',
'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php
--- a/src/aphront/AphrontRequest.php
+++ b/src/aphront/AphrontRequest.php
@@ -18,6 +18,7 @@
const TYPE_WORKFLOW = '__wflow__';
const TYPE_CONTINUE = '__continue__';
const TYPE_PREVIEW = '__preview__';
+ const TYPE_HISEC = '__hisec__';
private $host;
private $path;
@@ -263,6 +264,7 @@
final public function isFormPost() {
$post = $this->getExists(self::TYPE_FORM) &&
+ !$this->getExists(self::TYPE_HISEC) &&
$this->isHTTPPost();
if (!$post) {
diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
--- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
+++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
@@ -123,6 +123,49 @@
return $response;
}
+ if ($ex instanceof PhabricatorAuthHighSecurityRequiredException) {
+
+ $form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm(
+ $user,
+ $request);
+
+ $dialog = id(new AphrontDialogView())
+ ->setUser($user)
+ ->setTitle(pht('Entering High Security'))
+ ->setShortTitle(pht('Security Checkpoint'))
+ ->setWidth(AphrontDialogView::WIDTH_FORM)
+ ->addHiddenInput(AphrontRequest::TYPE_HISEC, true)
+ ->setErrors(
+ array(
+ pht(
+ 'You are taking an action which requires you to enter '.
+ 'high security.'),
+ ))
+ ->appendParagraph(
+ pht(
+ 'High security mode helps protect your account from security '.
+ 'threats, like session theft or someone messing with your stuff '.
+ 'while you\'re grabbing a coffee. To enter high security mode, '.
+ 'confirm your credentials.'))
+ ->appendChild($form->buildLayoutView())
+ ->appendParagraph(
+ pht(
+ 'Your account will remain in high security mode for a short '.
+ 'period of time. When you are finished taking sensitive '.
+ 'actions, you should leave high security.'))
+ ->setSubmitURI($request->getPath())
+ ->addCancelButton($ex->getCancelURI())
+ ->addSubmitButton(pht('Enter High Security'));
+
+ foreach ($request->getPassthroughRequestParameters() as $key => $value) {
+ $dialog->addHiddenInput($key, $value);
+ }
+
+ $response = new AphrontDialogResponse();
+ $response->setDialog($dialog);
+ return $response;
+ }
+
if ($ex instanceof PhabricatorPolicyException) {
if (!$user->isLoggedIn()) {
diff --git a/src/applications/auth/application/PhabricatorApplicationAuth.php b/src/applications/auth/application/PhabricatorApplicationAuth.php
--- a/src/applications/auth/application/PhabricatorApplicationAuth.php
+++ b/src/applications/auth/application/PhabricatorApplicationAuth.php
@@ -88,6 +88,8 @@
=> 'PhabricatorAuthConfirmLinkController',
'session/terminate/(?P<id>[^/]+)/'
=> 'PhabricatorAuthTerminateSessionController',
+ 'session/downgrade/'
+ => 'PhabricatorAuthDowngradeSessionController',
),
'/oauth/(?P<provider>\w+)/login/'
diff --git a/src/applications/auth/controller/PhabricatorAuthDowngradeSessionController.php b/src/applications/auth/controller/PhabricatorAuthDowngradeSessionController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/controller/PhabricatorAuthDowngradeSessionController.php
@@ -0,0 +1,52 @@
+<?php
+
+final class PhabricatorAuthDowngradeSessionController
+ extends PhabricatorAuthController {
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $panel_uri = '/settings/panel/sessions/';
+
+ $session = $viewer->getSession();
+ if ($session->getHighSecurityUntil() < time()) {
+ return $this->newDialog()
+ ->setTitle(pht('Normal Security Restored'))
+ ->appendParagraph(
+ pht('Your session is no longer in high security.'))
+ ->addCancelButton($panel_uri, pht('Continue'));
+ }
+
+ if ($request->isFormPost()) {
+
+ queryfx(
+ $session->establishConnection('w'),
+ 'UPDATE %T SET highSecurityUntil = NULL WHERE id = %d',
+ $session->getTableName(),
+ $session->getID());
+
+ return id(new AphrontRedirectResponse())
+ ->setURI($this->getApplicationURI('session/downgrade/'));
+ }
+
+ return $this->newDialog()
+ ->setTitle(pht('Leaving High Security'))
+ ->appendParagraph(
+ pht(
+ 'Leave high security and return your session to normal '.
+ 'security levels?'))
+ ->appendParagraph(
+ pht(
+ 'If you leave high security, you will need to authenticate '.
+ 'again the next time you try to take a high security action.'))
+ ->appendParagraph(
+ pht(
+ 'On the plus side, that purple notification bubble will '.
+ 'disappear.'))
+ ->addSubmitButton(pht('Leave High Security'))
+ ->addCancelButton($panel_uri, pht('Stay in High Security'));
+ }
+
+
+}
diff --git a/src/applications/auth/data/PhabricatorAuthHighSecurityToken.php b/src/applications/auth/data/PhabricatorAuthHighSecurityToken.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/data/PhabricatorAuthHighSecurityToken.php
@@ -0,0 +1,5 @@
+<?php
+
+final class PhabricatorAuthHighSecurityToken {
+
+}
diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php
--- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php
+++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @task hisec High Security Mode
+ */
final class PhabricatorAuthSessionEngine extends Phobject {
/**
@@ -78,28 +81,43 @@
$session_table = new PhabricatorAuthSession();
$user_table = new PhabricatorUser();
$conn_r = $session_table->establishConnection('r');
+ $session_key = PhabricatorHash::digest($session_token);
// NOTE: We're being clever here because this happens on every page load,
- // and by joining we can save a query.
+ // and by joining we can save a query. This might be getting too clever
+ // for its own good, though...
$info = queryfx_one(
$conn_r,
- 'SELECT s.sessionExpires AS _sessionExpires, s.id AS _sessionID, u.*
+ 'SELECT
+ s.id AS s_id,
+ s.sessionExpires AS s_sessionExpires,
+ s.sessionStart AS s_sessionStart,
+ s.highSecurityUntil AS s_highSecurityUntil,
+ u.*
FROM %T u JOIN %T s ON u.phid = s.userPHID
AND s.type = %s AND s.sessionKey = %s',
$user_table->getTableName(),
$session_table->getTableName(),
$session_type,
- PhabricatorHash::digest($session_token));
+ $session_key);
if (!$info) {
return null;
}
- $expires = $info['_sessionExpires'];
- $id = $info['_sessionID'];
- unset($info['_sessionExpires']);
- unset($info['_sessionID']);
+ $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);
$ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
@@ -107,19 +125,21 @@
// 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.
- if (time() + (0.80 * $ttl) > $expires) {
+ if (time() + (0.80 * $ttl) > $session->getSessionExpires()) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$conn_w = $session_table->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET sessionExpires = UNIX_TIMESTAMP() + %d WHERE id = %d',
- $session_table->getTableName(),
+ $session->getTableName(),
$ttl,
- $id);
+ $session->getID());
unset($unguarded);
}
- return $user_table->loadFromArray($info);
+ $user = $user_table->loadFromArray($info);
+ $user->attachSession($session);
+ return $user;
}
@@ -182,4 +202,104 @@
return $session_key;
}
+
+ /**
+ * 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.
+ */
+ 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();
+
+ $token = $this->issueHighSecurityToken($session);
+ if ($token) {
+ return $token;
+ }
+
+ if ($request->isHTTPPost()) {
+ $request->validateCSRF();
+ if ($request->getExists(AphrontRequest::TYPE_HISEC)) {
+
+ // TODO: Actually verify that the user provided some multi-factor
+ // auth credentials here. For now, we just let you enter high
+ // security.
+
+ $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());
+ }
+ }
+
+ $token = $this->issueHighSecurityToken($session);
+ if ($token) {
+ return $token;
+ }
+
+ throw id(new PhabricatorAuthHighSecurityRequiredException())
+ ->setCancelURI($cancel_uri);
+ }
+
+
+ /**
+ * Issue a high security token for a session, if authorized.
+ *
+ * @param PhabricatorAuthSession Session to issue a token for.
+ * @return PhabricatorAuthHighSecurityToken|null Token, if authorized.
+ */
+ private function issueHighSecurityToken(PhabricatorAuthSession $session) {
+ $until = $session->getHighSecurityUntil();
+ if ($until > time()) {
+ return new PhabricatorAuthHighSecurityToken();
+ }
+ return null;
+ }
+
+
+ /**
+ * Render a form for providing relevant multi-factor credentials.
+ *
+ * @param PhabricatorUser Viewing user.
+ * @param AphrontRequest Current request.
+ * @return AphrontFormView Renderable form.
+ */
+ public function renderHighSecurityForm(
+ PhabricatorUser $viewer,
+ AphrontRequest $request) {
+
+ // TODO: This is stubbed.
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->appendRemarkupInstructions('')
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('Secret Stuff')))
+ ->appendRemarkupInstructions('');
+
+ return $form;
+ }
+
+
}
diff --git a/src/applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php b/src/applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php
@@ -0,0 +1,16 @@
+<?php
+
+final class PhabricatorAuthHighSecurityRequiredException extends Exception {
+
+ private $cancelURI;
+
+ public function setCancelURI($cancel_uri) {
+ $this->cancelURI = $cancel_uri;
+ return $this;
+ }
+
+ public function getCancelURI() {
+ return $this->cancelURI;
+ }
+
+}
diff --git a/src/applications/auth/storage/PhabricatorAuthSession.php b/src/applications/auth/storage/PhabricatorAuthSession.php
--- a/src/applications/auth/storage/PhabricatorAuthSession.php
+++ b/src/applications/auth/storage/PhabricatorAuthSession.php
@@ -11,6 +11,7 @@
protected $sessionKey;
protected $sessionStart;
protected $sessionExpires;
+ protected $highSecurityUntil;
private $identityObject = self::ATTACHABLE;
diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php
--- a/src/applications/people/storage/PhabricatorUser.php
+++ b/src/applications/people/storage/PhabricatorUser.php
@@ -42,6 +42,7 @@
private $customFields = self::ATTACHABLE;
private $alternateCSRFString = self::ATTACHABLE;
+ private $session = self::ATTACHABLE;
protected function readField($field) {
switch ($field) {
@@ -178,6 +179,19 @@
return $result;
}
+ public function attachSession(PhabricatorAuthSession $session) {
+ $this->session = $session;
+ return $this;
+ }
+
+ public function getSession() {
+ return $this->assertAttached($this->session);
+ }
+
+ public function hasSession() {
+ return ($this->session !== self::ATTACHABLE);
+ }
+
private function generateConduitCertificate() {
return Filesystem::readRandomCharacters(255);
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php b/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
--- a/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
@@ -38,6 +38,18 @@
return $this->renderKeyListView($request);
}
+ /*
+
+ NOTE: Uncomment this to test hisec.
+ TOOD: Implement this fully once hisec does something useful.
+
+ $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
+ $viewer,
+ $request,
+ '/settings/panel/ssh/');
+
+ */
+
$id = nonempty($edit, $delete);
if ($id && is_numeric($id)) {
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php b/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php
--- a/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php
@@ -66,10 +66,15 @@
pht('Terminate'));
}
+ $hisec = ($session->getHighSecurityUntil() - time());
+
$rows[] = array(
$handles[$session->getUserPHID()]->renderLink(),
substr($session->getSessionKey(), 0, 6),
$session->getType(),
+ ($hisec > 0)
+ ? phabricator_format_relative_time($hisec)
+ : null,
phabricator_datetime($session->getSessionStart(), $viewer),
phabricator_date($session->getSessionExpires(), $viewer),
$button,
@@ -84,6 +89,7 @@
pht('Identity'),
pht('Session'),
pht('Type'),
+ pht('HiSec'),
pht('Created'),
pht('Expires'),
pht(''),
@@ -95,6 +101,7 @@
'',
'right',
'right',
+ 'right',
'action',
));
@@ -113,6 +120,20 @@
->setHeader(pht('Active Login Sessions'))
->addActionLink($terminate_button);
+ $hisec = ($viewer->getSession()->getHighSecurityUntil() - time());
+ if ($hisec > 0) {
+ $hisec_icon = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
+ ->setSpriteIcon('lock');
+ $hisec_button = id(new PHUIButtonView())
+ ->setText(pht('Leave High Security'))
+ ->setHref('/auth/session/downgrade/')
+ ->setTag('a')
+ ->setWorkflow(true)
+ ->setIcon($hisec_icon);
+ $header->addActionLink($hisec_button);
+ }
+
$panel = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($table);
diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php
--- a/src/view/page/PhabricatorStandardPageView.php
+++ b/src/view/page/PhabricatorStandardPageView.php
@@ -168,6 +168,22 @@
Javelin::initBehavior('device');
+ if ($user->hasSession()) {
+ $hisec = ($user->getSession()->getHighSecurityUntil() - time());
+ if ($hisec > 0) {
+ $remaining_time = phabricator_format_relative_time($hisec);
+ Javelin::initBehavior(
+ 'high-security-warning',
+ array(
+ 'uri' => '/auth/session/downgrade/',
+ 'message' => pht(
+ 'Your session is in high security mode. When you '.
+ 'finish using it, click here to leave.',
+ $remaining_time),
+ ));
+ }
+ }
+
if ($console) {
require_celerity_resource('aphront-dark-console-css');
diff --git a/webroot/rsrc/css/aphront/notification.css b/webroot/rsrc/css/aphront/notification.css
--- a/webroot/rsrc/css/aphront/notification.css
+++ b/webroot/rsrc/css/aphront/notification.css
@@ -47,6 +47,11 @@
border: 1px solid {$red};
}
+.jx-notification-security {
+ background: {$lightviolet};
+ border: 1px solid {$violet};
+}
+
.jx-notification-container .phabricator-notification {
padding: 0;
}
diff --git a/webroot/rsrc/js/core/behavior-high-security-warning.js b/webroot/rsrc/js/core/behavior-high-security-warning.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/core/behavior-high-security-warning.js
@@ -0,0 +1,19 @@
+/**
+ * @provides javelin-behavior-high-security-warning
+ * @requires javelin-behavior
+ * javelin-uri
+ * phabricator-notification
+ */
+
+JX.behavior('high-security-warning', function(config) {
+
+ var n = new JX.Notification()
+ .setContent(config.message)
+ .setDuration(0)
+ .alterClassName('jx-notification-security', true);
+
+ n.listen('activate', function() { JX.$U(config.uri).go(); });
+
+ n.show();
+
+});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 26, 1:05 AM (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7702399
Default Alt Text
D8851.diff (20 KB)
Attached To
Mode
D8851: Add "High Security" mode to support multi-factor auth
Attached
Detach File
Event Timeline
Log In to Comment