Enforcing MFA should force the user to set up MFA
authorMarcel Werk <burntime@woltlab.com>
Thu, 25 Jan 2024 17:24:09 +0000 (18:24 +0100)
committerMarcel Werk <burntime@woltlab.com>
Thu, 25 Jan 2024 17:24:09 +0000 (18:24 +0100)
Closes #5705

com.woltlab.wcf/templates/accountSecurity.tpl
wcfsetup/install/files/lib/http/middleware/CheckForMultifactorRequirement.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/http/middleware/EnforceAcpAuthentication.class.php
wcfsetup/install/files/lib/page/AccountSecurityPage.class.php
wcfsetup/install/files/lib/system/request/RequestHandler.class.php
wcfsetup/install/files/lib/system/user/multifactor/TMultifactorRequirementEnforcer.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index c91261b242b45bc094f1ce1b811ca7a48580296a..852e764dd6c73587f9d06738a88a1a131fe6d996 100644 (file)
@@ -2,6 +2,10 @@
 
 {include file='header' __disableAds=true __sidebarLeftHasMenu=true}
 
+{if $requiresMultifactor}
+       <woltlab-core-notice type="warning">{lang}wcf.user.security.requiresMultifactor{/lang}</woltlab-core-notice>
+{/if}
+
 <section class="section" id="section_multifactor">
        <header class="sectionHeader">
                <h2 class="sectionTitle">{lang}wcf.user.security.multifactor{/lang}</h2>
diff --git a/wcfsetup/install/files/lib/http/middleware/CheckForMultifactorRequirement.class.php b/wcfsetup/install/files/lib/http/middleware/CheckForMultifactorRequirement.class.php
new file mode 100644 (file)
index 0000000..6243dc2
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+namespace wcf\http\middleware;
+
+use Laminas\Diactoros\Response\RedirectResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\form\MultifactorManageForm;
+use wcf\http\Helper;
+use wcf\page\AccountSecurityPage;
+use wcf\system\request\LinkHandler;
+use wcf\system\request\RequestHandler;
+use wcf\system\WCF;
+
+/**
+ * Checks whether the user is required to set up the multi-factor authentication.
+ *
+ * @author      Marcel Werk
+ * @copyright   2001-2024 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since       6.1
+ */
+final class CheckForMultifactorRequirement implements MiddlewareInterface
+{
+    private const ALLOWED_CONTROLLERS = [
+        AccountSecurityPage::class,
+        MultifactorManageForm::class,
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        if (
+            $this->multifactorRequired()
+            && !$this->requestCanBypassMultifactor($request)
+        ) {
+            return new RedirectResponse(
+                LinkHandler::getInstance()->getControllerLink(AccountSecurityPage::class)
+            );
+        }
+
+        return $handler->handle($request);
+    }
+
+    private function multifactorRequired(): bool
+    {
+        return WCF::getUser()->userID
+            && WCF::getUser()->requiresMultifactor()
+            && !WCF::getUser()->multifactorActive;
+    }
+
+    private function requestCanBypassMultifactor(ServerRequestInterface $request): bool
+    {
+        $controller = RequestHandler::getInstance()->getActiveRequest()->getClassName();
+        if (\in_array($controller, self::ALLOWED_CONTROLLERS, true)) {
+            return true;
+        }
+
+        if (RequestHandler::getInstance()->getActiveRequest()->isAvailableDuringOfflineMode()) {
+            return true;
+        }
+
+        if (Helper::isAjaxRequest($request)) {
+            return true;
+        }
+
+        return false;
+    }
+}
index 5867f8d4cde6ac59e66679721ec4fea5796c56c5..36ac465b449ee2e39e2dd22dc3506671d7e2aa92 100644 (file)
@@ -21,7 +21,6 @@ use wcf\http\error\PermissionDeniedHandler;
 use wcf\http\Helper;
 use wcf\system\request\LinkHandler;
 use wcf\system\request\RequestHandler;
-use wcf\system\user\multifactor\TMultifactorRequirementEnforcer;
 use wcf\system\WCF;
 use wcf\system\WCFACP;
 use wcf\util\HeaderUtil;
@@ -37,8 +36,6 @@ use wcf\util\UserUtil;
  */
 final class EnforceAcpAuthentication implements MiddlewareInterface
 {
-    use TMultifactorRequirementEnforcer;
-
     private const ALLOWED_CONTROLLERS = [
         LoginForm::class,
         ReauthenticationForm::class,
@@ -76,8 +73,6 @@ final class EnforceAcpAuthentication implements MiddlewareInterface
             return $this->handleReauthentication($request);
         }
 
-        $this->enforceMultifactorAuthentication();
-
         // force debug mode if in ACP and authenticated
         WCFACP::overrideDebugMode();
 
index 5bbdd0b7bc559130eb64d2678f43e15103bb95b4..f1e1822783d6c97f507afd7966c456c25e67c09b 100644 (file)
@@ -79,6 +79,7 @@ class AccountSecurityPage extends AbstractPage
             'activeSessions' => $this->activeSessions,
             'multifactorMethods' => $this->multifactorMethods,
             'enabledMultifactorMethods' => $this->enabledMultifactorMethods,
+            'requiresMultifactor' => WCF::getUser()->requiresMultifactor() && !WCF::getUser()->multifactorActive,
         ]);
     }
 
index 400ae562e4157d0aa6f474d8d731e5b02a656049..6d222335070c87dd836164a257f57b4134bcf956 100644 (file)
@@ -16,6 +16,7 @@ use wcf\http\LegacyPlaceholderResponse;
 use wcf\http\middleware\AddAcpSecurityHeaders;
 use wcf\http\middleware\CheckForEnterpriseNonOwnerAccess;
 use wcf\http\middleware\CheckForExpiredAppEvaluation;
+use wcf\http\middleware\CheckForMultifactorRequirement;
 use wcf\http\middleware\CheckForOfflineMode;
 use wcf\http\middleware\CheckHttpMethod;
 use wcf\http\middleware\CheckSystemEnvironment;
@@ -143,6 +144,7 @@ final class RequestHandler extends SingletonFactory
                     new CheckForEnterpriseNonOwnerAccess(),
                     new CheckForExpiredAppEvaluation(),
                     new CheckForOfflineMode(),
+                    new CheckForMultifactorRequirement(),
                     new JsonBody(),
                     new TriggerBackgroundQueue(),
                     new HandleExceptions(),
index 66038655bb278612dfbe5bc9bd0ad6240449afc9..ba2fe960c80cf60a12fe5c6cb71c0f1ba203f516 100644 (file)
@@ -12,6 +12,7 @@ use wcf\system\WCF;
  * @copyright   2001-2020 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @since   5.4
+ * @deprecated 6.1
  */
 trait TMultifactorRequirementEnforcer
 {
index 1927e7730ea9274d0238d72b57b8b1c245de725c..fc98ef58420929f059ff9cc7b9ec09dd5f80cb73 100644 (file)
@@ -934,7 +934,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE
                <item name="wcf.acp.group.option.user.signature.attachment.allowedExtensions"><![CDATA[Erlaubte Dateiendungen]]></item>
                <item name="wcf.acp.group.option.user.signature.attachment.maxCount"><![CDATA[Maximale Dateianhänge]]></item>
                <item name="wcf.acp.group.requireMultifactor"><![CDATA[Mehrfaktor-Authentifizierung erzwingen]]></item>
-               <item name="wcf.acp.group.requireMultifactor.description"><![CDATA[Benutzer, die Mitglied dieser Benutzergruppe sind, können die Mehrfaktor-Authentifizierung nicht deaktivieren und können besonders geschützte Bereiche erst betreten, wenn sie die Mehrfaktor-Authentifizierung eingerichtet haben.]]></item>
+               <item name="wcf.acp.group.requireMultifactor.description"><![CDATA[Benutzer, die Mitglied dieser Benutzergruppe sind, müssen die Mehrfaktor-Authentifizierung einrichten.]]></item>
                <item name="wcf.acp.group.option.admin.user.canExportGdprData"><![CDATA[Kann persönliche Daten (DSGVO) exportieren]]></item>
                <item name="wcf.acp.group.option.admin.user.canExportGdprData.description"><![CDATA[Der Export gemäß der europäischen Datenschutz-Grundverordnung (DSGVO) enthält detaillierte persönliche Daten des jeweiligen Benutzers. Eine Filterung der im Export enthaltenen Information auf Basis der Zugriffsrechte des Administrators findet <strong>nicht</strong> statt und kann unter Umständen Informationen enthalten, die einem Administrator auf andere Weise nicht zugänglich wären.]]></item>
        </category>
@@ -5046,7 +5046,7 @@ Sobald {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Benutzerkonto freige
                <item name="wcf.user.security.multifactor.totp.success.add"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Smartphone <strong>{$deviceName}</strong> wurde erfolgreich hinterlegt.]]></item>
                <item name="wcf.user.security.multifactor.totp.success.delete"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Smartphone <strong>{$deviceName}</strong> wurde erfolgreich entfernt.]]></item>
                <item name="wcf.user.security.multifactor.totp.useTime"><![CDATA[Zuletzt Benutzt]]></item>
-               <item name="wcf.user.security.requiresMultifactor"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du bist{else}Sie sind{/if} Mitglied einer Benutzergruppe, die verpflichtet ist, die Mehrfaktor-Authentifizierung zu nutzen. Bevor {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} auf diese Seite zugreifen {if LANGUAGE_USE_INFORMAL_VARIANT}darfst, musst du{else}dürfen, müssen Sie{/if} <a href="{link controller='AccountSecurity' forceFrontend=true}#section_multifactor{/link}">die Mehrfaktor-Authentifizierung aktivieren</a>.]]></item>
+               <item name="wcf.user.security.requiresMultifactor"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du bist{else}Sie sind{/if} Mitglied einer Benutzergruppe, die verpflichtet ist, die Mehrfaktor-Authentifizierung zu nutzen.]]></item>
                <item name="wcf.user.security.sessionName"><![CDATA[{if $session->getUserAgent()->getBrowser()}{$session->getUserAgent()->getBrowser()}{if $session->getUserAgent()->getOS()} auf {$session->getUserAgent()->getOS()}{/if}{else}Unbekanntes Gerät{/if}]]></item>
                <item name="wcf.user.security.multifactor.otherOptions"><![CDATA[Andere Option wählen]]></item>
        </category>
index cc9445c8c1d293d7bf246d9e0d9e505baf51b46f..8f07fe3b13b54634ae7dd30ae7797aa27cc7b78f 100644 (file)
@@ -912,7 +912,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru
                <item name="wcf.acp.group.option.user.signature.attachment.allowedExtensions"><![CDATA[Allowed Attachment File Extensions]]></item>
                <item name="wcf.acp.group.option.user.signature.attachment.maxCount"><![CDATA[Maximum Attachments]]></item>
                <item name="wcf.acp.group.requireMultifactor"><![CDATA[Require Multi-factor Authentication]]></item>
-               <item name="wcf.acp.group.requireMultifactor.description"><![CDATA[Users that are members of this user group may not disable multi-factor authentication. They will also be required to set up multi-factor authentication before they may enter protected areas.]]></item>
+               <item name="wcf.acp.group.requireMultifactor.description"><![CDATA[Users that are members of this user group are required to set up multi-factor authentication.]]></item>
                <item name="wcf.acp.group.option.admin.user.canExportGdprData"><![CDATA[Can export personal data (GDPR)]]></item>
                <item name="wcf.acp.group.option.admin.user.canExportGdprData.description"><![CDATA[The export according to the European General Data Protection Regulation (GDPR) contains detailed personal data of the respective user. The export <strong>will not</strong> be filtered according to the administrator’s permissions and may contain information that would be inaccessible otherwise.]]></item>
        </category>
@@ -5045,7 +5045,7 @@ You also received a list of backup codes to use when your second factor becomes
                <item name="wcf.user.security.multifactor.totp.success.add"><![CDATA[Your smartphone <strong>{$deviceName}</strong> has successfully been added.]]></item>
                <item name="wcf.user.security.multifactor.totp.success.delete"><![CDATA[Your smartphone <strong>{$deviceName}</strong> has successfully been removed.]]></item>
                <item name="wcf.user.security.multifactor.totp.useTime"><![CDATA[Last Used]]></item>
-               <item name="wcf.user.security.requiresMultifactor"><![CDATA[You are a member of a user group that is required to use multi-factor authentication. Before you may access this page you must <a href="{link controller='AccountSecurity' forceFrontend=true}#section_multifactor{/link}">enable multi-factor authentication</a>.]]></item>
+               <item name="wcf.user.security.requiresMultifactor"><![CDATA[You are a member of a user group that is required to use multi-factor authentication.]]></item>
                <item name="wcf.user.security.sessionName"><![CDATA[{if $session->getUserAgent()->getBrowser()}{$session->getUserAgent()->getBrowser()}{if $session->getUserAgent()->getOS()} on {$session->getUserAgent()->getOS()}{/if}{else}Unknown Device{/if}]]></item>
                <item name="wcf.user.security.multifactor.otherOptions"><![CDATA[Select another option]]></item>
        </category>