aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/TwoFactorAuth.php
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2024-11-13 14:56:58 +0000
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2024-11-13 14:56:58 +0000
commit51a0e793f0ca08a6e0ea0ad6b7181e662946eeef (patch)
treeab2905eb7b852387f557027dd546ccb083d54e57 /lib/classes/TwoFactorAuth.php
parent60f7675f3d58fe1fe2cf79da4314a3407cea6055 (diff)
fix 2fa token waiting (and more cleaning up than should be, sorry), fixes #4855
Closes #4855 Merge request studip/studip!3640
Diffstat (limited to 'lib/classes/TwoFactorAuth.php')
-rw-r--r--lib/classes/TwoFactorAuth.php52
1 files changed, 28 insertions, 24 deletions
diff --git a/lib/classes/TwoFactorAuth.php b/lib/classes/TwoFactorAuth.php
index 6c04b8e..b67f4be 100644
--- a/lib/classes/TwoFactorAuth.php
+++ b/lib/classes/TwoFactorAuth.php
@@ -11,8 +11,6 @@
final class TwoFactorAuth
{
const SESSION_KEY = 'tfa/confirmed';
- const SESSION_REDIRECT = 'tfa/redirect';
- const SESSION_ENFORCE = 'tfa/enforce';
const SESSION_DATA = 'tfa/data';
const SESSION_CONFIRMATIONS = 'tfa/confirmations';
const SESSION_FAILED = 'tfa/failed';
@@ -26,7 +24,7 @@ final class TwoFactorAuth
* Returns an instance of the authentication
* @return TwoFactorAuth object
*/
- public static function get()
+ public static function get(): TwoFactorAuth
{
if (self::$instance === null) {
self::$instance = new self();
@@ -39,16 +37,16 @@ final class TwoFactorAuth
* user (defaults to current user). The user's permissions decide whether
* the two factor authentication is enabled or not.
*
- * @param User $user User to check (optional, defaults to current user)
+ * @param User|null $user User to check (optional, defaults to current user)
* @return boolean
*/
- public static function isEnabledForUser(User $user = null)
+ public static function isEnabledForUser(User $user = null): bool
{
if ($user === null) {
$user = User::findCurrent();
}
- $valid_perms = array_filter(array_map('trim', explode(',', Config::get()->TFA_PERMS)));
+ $valid_perms = array_filter(array_map('trim', explode(',', Config::get()->getValue('TFA_PERMS'))));
return in_array($user->perms, $valid_perms);
}
@@ -67,11 +65,13 @@ final class TwoFactorAuth
/**
* Private constructor to enforce singleton
+ *
+ * @throws SessionRequiredException
*/
private function __construct()
{
if (session_status() !== PHP_SESSION_ACTIVE) {
- throw new Exception('2FA requires a valid session');
+ throw new SessionRequiredException('2FA requires a valid session');
}
if (!isset($_SESSION[self::SESSION_FAILED])) {
@@ -81,7 +81,7 @@ final class TwoFactorAuth
$_SESSION[self::SESSION_FAILED] = array_filter(
$_SESSION[self::SESSION_FAILED],
function ($timestamp) {
- return $timestamp > time() - Config::get()->TFA_MAX_TRIES_TIMESPAN;
+ return $timestamp > time() - Config::get()->getValue('TFA_MAX_TRIES_TIMESPAN');
}
);
}
@@ -145,7 +145,7 @@ final class TwoFactorAuth
// User has already confirmed this session?
if (isset($_SESSION[self::SESSION_KEY])) {
- list($code, $timeslice) = array_values($_SESSION[self::SESSION_KEY]);
+ [$code, $timeslice] = array_values($_SESSION[self::SESSION_KEY]);
if ($this->secret->validateToken($code, (int) $timeslice, true)) {
return;
}
@@ -155,7 +155,7 @@ final class TwoFactorAuth
// Trusted computer?
$user_cookie_key = self::COOKIE_KEY . '/' . $GLOBALS['user']->id;
if (isset($_COOKIE[$user_cookie_key])) {
- list($code, $timeslice) = explode(':', $_COOKIE[$user_cookie_key]);
+ [$code, $timeslice] = explode(':', $_COOKIE[$user_cookie_key]);
if ($this->secret->validateToken($code, (int) $timeslice, true)) {
$this->registerSecretInSession();
return;
@@ -177,7 +177,7 @@ final class TwoFactorAuth
* @param array $data Optional additional data to pass to the
* confirmation screen (for internal use)
*/
- public function confirm($action, $text, array $data = []): void
+ public function confirm(string $action, string $text, array $data = []): void
{
if (
isset($_SESSION[self::SESSION_CONFIRMATIONS])
@@ -202,7 +202,7 @@ final class TwoFactorAuth
* @param string $text Text to display to the user
* @param array $data Optional additional data (for internal use)
*/
- private function showConfirmationScreen($text = '', array $data = [])
+ private function showConfirmationScreen(string $text = '', array $data = [])
{
$data = array_merge(['global' => false], $data);
@@ -235,8 +235,8 @@ final class TwoFactorAuth
$_SESSION[self::SESSION_DATA] + [
'secret' => $this->secret,
'text' => $text,
- 'blocked' => $this->isBlocked(),
- 'duration' => Config::get()->TFA_TRUST_DURATION,
+ 'waittime' => $this->getWaitingTime(),
+ 'duration' => Config::get()->getValue('TFA_TRUST_DURATION'),
],
'layouts/base.php'
);
@@ -263,7 +263,7 @@ final class TwoFactorAuth
*/
private function registerSecretInCookie()
{
- $lifetime_in_days = Config::get()->TFA_TRUST_DURATION;
+ $lifetime_in_days = Config::get()->getValue('TFA_TRUST_DURATION');
$lifetime = $lifetime_in_days > 0 ? strtotime("+{$lifetime_in_days} days") : 2147483647;
$timeslice = mt_rand(0, PHP_INT_MAX);
@@ -288,7 +288,7 @@ final class TwoFactorAuth
private function validateFromRequest()
{
if (
- $this->isBlocked()
+ $this->getWaitingTime()
|| !Request::isPost()
|| !Request::submitted('tfacode-input')
|| !Request::submitted('tfa-nonce')
@@ -341,16 +341,20 @@ final class TwoFactorAuth
}
/**
- * Returns whether the current session is blocked from any more token
- * inputs. This happens if too many false inputs happen in a short amount
- * of time and should prevent brute force attacks.
+ * Returns the time the user has to wait before entering a token.
*
- * @return boolean
+ * This happens if too many invalid tokens have been input.
*/
- private function isBlocked()
+ private function getWaitingTime(): int
{
- return count($_SESSION[self::SESSION_FAILED]) >= Config::get()->TFA_MAX_TRIES
- ? min($_SESSION[self::SESSION_FAILED])
- : false;
+ if (count($_SESSION[self::SESSION_FAILED]) < Config::get()->getValue('TFA_MAX_TRIES')) {
+ return 0;
+ }
+
+ $min_timestamp = min(array_slice($_SESSION[self::SESSION_FAILED], -Config::get()->getValue('TFA_MAX_TRIES')));
+
+ $wait_time = $min_timestamp + Config::get()->getValue('TFA_MAX_TRIES_TIMESPAN') - time();
+
+ return ceil($wait_time / 60);
}
}