diff --git a/app/views/tfa/setup.php b/app/views/tfa/setup.php index 6522bd099a2a59cd079a800c8b1f1216bc082ba7..5c605597ec2eb6351bf2687f2c9aab8834a8b6c4 100644 --- a/app/views/tfa/setup.php +++ b/app/views/tfa/setup.php @@ -4,12 +4,7 @@ <fieldset> <legend><?= _('Zwei-Faktor-Authentifizierung einrichten') ?></legend> - <p> - <?= _('Mittels Zwei-Faktor-Authentifizierung können Sie Ihr Konto schützen, ' - . 'indem bei jedem Login ein Token von Ihnen eingegeben werden muss.') ?> - <?= _('Dieses Token erhalten Sie entweder per E-Mail oder können es über ' - . 'eine geeignete Authenticator-App erzeugen lassen.') ?> - </p> + <?= formatReady(Config::get()->TFA_TEXT_INTRODUCTION) ?> <label> <input required type="radio" name="type" value="email"> diff --git a/db/migrations/5.1.5_tfa_improved_texts.php b/db/migrations/5.1.5_tfa_improved_texts.php new file mode 100644 index 0000000000000000000000000000000000000000..9ddaf8fd2bf7977542cb1d9c31962a3e9de7e732 --- /dev/null +++ b/db/migrations/5.1.5_tfa_improved_texts.php @@ -0,0 +1,93 @@ +<?php +final class TfaImprovedTexts extends Migration +{ + public function description() + { + return 'TIC #11510: Improve texts for two factor authentication'; + } + + protected function up() + { + $query = "INSERT IGNORE INTO `config` ( + `field`, `value`, `type`, `range`, `section`, + `mkdate`, `chdate`, `description` + ) VALUES( + :id, :text, 'i18n', 'global', 'Zwei-Faktor-Authentifizierung', + UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :description + )"; + DBManager::get()->execute($query, [ + ':id' => 'TFA_TEXT_INTRODUCTION', + ':text' => $this->getIntroductionText('de'), + ':description' => 'Text, der als Einleitung beim Einrichten der Zwei-Faktor-Authentisierung angezeigt wird', + ]); + DBManager::get()->execute($query, [ + ':id' => 'TFA_TEXT_APP', + ':text' => $this->getAppText('de'), + ':description' => 'Text, der als Einleitung beim Einrichten der Zwei-Faktor-Authentisierung via App angezeigt wird', + ]); + + $query = "INSERT IGNORE INTO `i18n` (`object_id`, `table`, `field`, `lang`, `value`) + VALUES (MD5(:id), 'config', 'value', 'en_GB', :text)"; + DBManager::get()->execute($query, [ + ':id' => 'TFA_TEXT_INTRODUCTION', + ':text' => $this->getIntroductionText('en'), + ]); + DBManager::get()->execute($query, [ + ':id' => 'TFA_TEXT_APP', + ':text' => $this->getAppText('en'), + ]); + } + + protected function down() + { + $query = "DELETE `config`, `config_values` + FROM `config` + LEFT JOIN `config_values` USING (`field`) + WHERE `field` IN ('TFA_TEXT_INTRODUCTION', 'TFA_TEXT_APP')"; + DBManager::get()->exec($query); + + $query = "DELETE FROM `i18n` + WHERE `object_id` IN (MD5('TFA_TEXT_INTRODUCTION'), MD5('TFA_TEXT_APP')) + AND `table` = 'config' + AND `field` = 'value'"; + DBManager::get()->exec($query); + } + + private function getIntroductionText($language) + { + if ($language === 'en') { + return 'Using two-factor authentication you can protect your account by ' + . 'entering a token on each login. ' + . 'You get that token either via E-Mail or by using an appropriate ' + . 'authenticator app.'; + } else { + return 'Mittels Zwei-Faktor-Authentifizierung können Sie Ihr Konto schützen, ' + . 'indem bei jedem Login ein Token von Ihnen eingegeben werden muss. ' + . 'Dieses Token erhalten Sie entweder per E-Mail oder können es über ' + . 'eine geeignete Authenticator-App erzeugen lassen.'; + } + } + + private function getAppText($language) + { + if ($language === 'en') { + $result = "Set up a suitable OTP authenticator app for this purpose. " + . "Here you will find a list of known and compatible apps:\n"; + } else { + $result = "Richten Sie dafür eine geeignete OTP-Authenticator-App ein. Hier " + . "finden Sie eine Liste bekannter und kompatibler Apps:\n"; + } + return $result . $this->getAuthenticatorList(); + } + + private function getAuthenticatorList() + { + return implode("\n", [ + '- [Authy]https://authy.com/', + '- [FreeOTP]https://freeotp.github.io/', + '- Google Authenticator: [Android]https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2 oder [iOS]https://apps.apple.com/app/google-authenticator/id388497605', + '- [LastPass Authenticator]https://lastpass.com/auth/', + '- [Microsoft Authenticator]https://www.microsoft.com/authenticator', + ]); + } +} diff --git a/db/migrations/5.1.6_tfa_trust_duration.php b/db/migrations/5.1.6_tfa_trust_duration.php new file mode 100644 index 0000000000000000000000000000000000000000..55d7d75916387d94e04b9d2d64d4ea9e343b6a97 --- /dev/null +++ b/db/migrations/5.1.6_tfa_trust_duration.php @@ -0,0 +1,32 @@ +<?php +final class TfaTrustDuration extends Migration +{ + public function description() + { + return 'TIC #11508: Configurable trust duration for devices'; + } + + protected function up() + { + // Create course config + $query = "INSERT IGNORE INTO `config` ( + `field`, `value`, `type`, `range`, `section`, + `mkdate`, `chdate`, + `description` + ) VALUES( + 'TFA_TRUST_DURATION', '30', 'integer', 'global', 'Zwei-Faktor-Authentifizierung', + UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), + 'Dauer, denen Geräte vertraut werden soll in Tagen (0 für dauerhaftes Vertrauen)' + )"; + DBManager::get()->exec($query); + } + + protected function down() + { + $query = "DELETE `config`, `config_values` + FROM `config` + LEFT JOIN `config_values` USING (`field`) + WHERE `field` = 'TFA_TRUST_DURATION'"; + DBManager::get()->exec($query); + } +} diff --git a/lib/classes/TwoFactorAuth.php b/lib/classes/TwoFactorAuth.php index f66b66784da4f9d2ac2befc0f27a71a4c464ada5..ef7e6685aa8705d154420df73934e02e8e989ea4 100644 --- a/lib/classes/TwoFactorAuth.php +++ b/lib/classes/TwoFactorAuth.php @@ -221,9 +221,10 @@ final class TwoFactorAuth echo $GLOBALS['template_factory']->render( 'tfa-validate.php', $_SESSION[self::SESSION_DATA] + [ - 'secret' => $this->secret, - 'text' => $text, - 'blocked' => $this->isBlocked(), + 'secret' => $this->secret, + 'text' => $text, + 'blocked' => $this->isBlocked(), + 'duration' => Config::get()->TFA_TRUST_DURATION, ], 'layouts/base.php' ); @@ -250,11 +251,14 @@ final class TwoFactorAuth */ private function registerSecretInCookie() { + $lifetime_in_days = Config::get()->TFA_TRUST_DURATION; + $lifetime = $lifetime_in_days > 0 ? strtotime("+{$lifetime_in_days} days") : 2147483647; + $timeslice = mt_rand(0, PHP_INT_MAX); setcookie( self::COOKIE_KEY, implode(':', [$this->secret->getToken($timeslice), $timeslice]), - strtotime('+30 days'), + $lifetime, $GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP'] ); } diff --git a/templates/tfa-validate.php b/templates/tfa-validate.php index 4770a67c31c054b74399f7653c2217df3765e5d1..62a5eb3f25e90738f7c8af12ded3c014a36b7ab1 100644 --- a/templates/tfa-validate.php +++ b/templates/tfa-validate.php @@ -11,6 +11,7 @@ <? else: ?> <p><?= htmlReady($text ?: _('Bitte geben Sie ein gültiges Token ein')) ?></p> <? if ($secret->type === 'app' && !$secret->confirmed): ?> + <?= formatReady(Config::get()->TFA_TEXT_APP) ?> <p> <?= _('Scannen Sie diesen Code mit Ihrer App ein und geben Sie ' . 'anschliessend ein gültiges Token ein.') ?> @@ -53,7 +54,18 @@ <? if ($global): ?> <label> <input type="checkbox" name="tfa-trusted" value="1"> - <?= _('Diesem Gerät für 30 Tage vertrauen.') ?> + <? if ($duration > 0): ?> + <?= sprintf( + ngettext( + 'Diesem Gerät für %u Tag vertrauen.', + 'Diesem Gerät für %u Tage vertrauen.', + $duration + ), + $duration + ) ?> + <? else: ?> + <?= _('Diesem Gerät dauerhaft vertrauen') ?> + <? endif; ?> </label> <? endif; ?> </fieldset>