From 22e84f016692cfe269336c05a3e7ea8c563874e3 Mon Sep 17 00:00:00 2001 From: Shane Mc Cormack Date: Mon, 11 Mar 2019 00:29:03 +0000 Subject: [PATCH] Add ability to restrict certain key types to certain permissions. #35 --- classes/twofactorkey.php | 24 +++++++++++++++++++++++- classes/user.php | 1 + web/1.0/index.php | 15 +++++++++------ web/1.0/methods/useradmin.php | 16 ++++++++++++++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/classes/twofactorkey.php b/classes/twofactorkey.php index 72cdbf0..534ee66 100644 --- a/classes/twofactorkey.php +++ b/classes/twofactorkey.php @@ -142,6 +142,16 @@ public function setActive($value) { return $this->setData('active', parseBool($value) ? 'true' : 'false'); } + public function getRequiredPermissionsForType() { + switch ($this->getType()) { + case "authy": + return ['2fa_push']; + + default: + return []; + } + } + public function setType($value) { // Clear key when changing type. $this->setData('key', NULL); @@ -255,10 +265,11 @@ public static function getKeyTypes() { * - They are not one time, or they have not been used * - expiry date is "0" or in the future * - We have the required ability to validate + * - If a user is passed, also check that they have the appropriate permissions. * * @return True if key is usable. */ - public function isUsableKey() { + public function isUsableKey($user = FALSE) { // Key is usable. $usable = $this->isActive(); @@ -277,6 +288,17 @@ public function isUsableKey() { $usable = false; } + if ($usable && $user !== FALSE) { + // Can we use this type of key. + $usable &= (($this->isPush() && $user->getPermission('2fa_push')) || $this->isCode()); + + // Check that the user still has the right permissions for this + // key. + foreach ($this->getRequiredPermissionsForType() as $perm) { + $usable &= $user->getPermission($perm); + } + } + return $usable; } diff --git a/classes/user.php b/classes/user.php index 402241e..d986f3c 100644 --- a/classes/user.php +++ b/classes/user.php @@ -27,6 +27,7 @@ class User extends DBObject { 'impersonate_users', 'system_service_mgmt', 'manage_articles', + '2fa_push', ]; // Permissions levels for unknown objects. diff --git a/web/1.0/index.php b/web/1.0/index.php index 3868d6e..ea04f68 100644 --- a/web/1.0/index.php +++ b/web/1.0/index.php @@ -176,13 +176,13 @@ } $keys = []; - foreach ($possibleKeys as $key) { if ($key->isUsableKey()) { $keys[] = $key; } } + foreach ($possibleKeys as $key) { if ($key->isUsableKey($user)) { $keys[] = $key; } } $valid = true; if (count($keys) > 0) { $valid = false; $testCode = isset($_SERVER['HTTP_X_2FA_KEY']) ? $_SERVER['HTTP_X_2FA_KEY'] : NULL; - $tryPush = isset($_SERVER['HTTP_X_2FA_PUSH']) && parseBool($_SERVER['HTTP_X_2FA_PUSH']); + $tryPush = isset($_SERVER['HTTP_X_2FA_PUSH']) && parseBool($_SERVER['HTTP_X_2FA_PUSH']) && $user->getPermission('2fa_push'); if ($testCode !== NULL) { foreach ($keys as $key) { @@ -242,10 +242,13 @@ } else { $errorExtraData = '2FA key required.'; $resp->setHeader('login_error', '2fa_required'); - foreach ($keys as $key) { - if ($key->isPush()) { - $resp->setHeader('2fa_push', '2fa push supported'); - break; + + if ($user->getPermission('2fa_push')) { + foreach ($keys as $key) { + if ($key->isPush()) { + $resp->setHeader('2fa_push', '2fa push supported'); + break; + } } } } diff --git a/web/1.0/methods/useradmin.php b/web/1.0/methods/useradmin.php index 01ee4c2..9fddd05 100644 --- a/web/1.0/methods/useradmin.php +++ b/web/1.0/methods/useradmin.php @@ -453,7 +453,7 @@ protected function get2FAKeys($user) { if ($v->isActive()) { unset($result[$k]['key']); } - $result[$k]['usable'] = parseBool($v->isUsableKey()); + $result[$k]['usable'] = parseBool($v->isUsableKey($user)); $result[$k]['active'] = parseBool($result[$k]['active']); $result[$k]['onetime'] = parseBool($result[$k]['onetime']); } @@ -492,7 +492,7 @@ protected function get2FAKey($user, $key) { if ($key->isActive()) { unset($k['key']); } - $k['usable'] = $key->isUsableKey(); + $k['usable'] = $key->isUsableKey($user); $k['active'] = parseBool($k['active']); $k['onetime'] = parseBool($k['onetime']); @@ -513,6 +513,12 @@ protected function create2FAKey($user) { $key->setOneTime($data['data']['onetime']); } + foreach ($key->getRequiredPermissionsForType() as $perm) { + if (!$user->getPermission($perm)) { + $this->getContextKey('response')->sendError('Error creating key: Missing permission "' . $perm . '"'); + } + } + try { $key->setKey(TRUE); } catch (TwoFactorKeyAutoValueException $e) { @@ -613,6 +619,12 @@ protected function verify2FAKey($user, $key) { $this->getContextKey('response')->sendError('No code provided for verification.'); } + foreach ($key->getRequiredPermissionsForType() as $perm) { + if (!$user->getPermission($perm)) { + $this->getContextKey('response')->sendError('Error verifying key: Missing permission "' . $perm . '"'); + } + } + if ($key->isPush()) { $this->getContextKey('response')->setHeader('info', 'Key will be verified in the background.');