feat(AppFramework): Add missing NoTwoFactorRequired attribute

It's in our documentation but was never implemented.

Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
pull/55474/head
Carl Schwan 2025-09-17 15:32:11 +07:00
parent 5f6e6b305f
commit b2ed0fa37c
No known key found for this signature in database
GPG Key ID: 02325448204E452A
11 changed files with 165 additions and 97 deletions

@ -17,6 +17,7 @@ use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\ContentSecurityPolicy;
@ -61,13 +62,10 @@ class ThemingController extends Controller {
}
/**
* @param string $setting
* @param string $value
* @return DataResponse
* @throws NotPermittedException
*/
#[AuthorizedAdminSetting(settings: Admin::class)]
public function updateStylesheet($setting, $value) {
public function updateStylesheet(string $setting, string $value): DataResponse {
$value = trim($value);
$error = null;
$saved = false;
@ -153,13 +151,10 @@ class ThemingController extends Controller {
}
/**
* @param string $setting
* @param mixed $value
* @return DataResponse
* @throws NotPermittedException
*/
#[AuthorizedAdminSetting(settings: Admin::class)]
public function updateAppMenu($setting, $value) {
public function updateAppMenu(string $setting, mixed $value): DataResponse {
$error = null;
switch ($setting) {
case 'defaultApps':
@ -204,7 +199,6 @@ class ThemingController extends Controller {
}
/**
* @return DataResponse
* @throws NotPermittedException
*/
#[AuthorizedAdminSetting(settings: Admin::class)]
@ -367,7 +361,6 @@ class ThemingController extends Controller {
/**
* @NoSameSiteCookieRequired
* @NoTwoFactorRequired
*
* Get the CSS stylesheet for a theme
*
@ -381,6 +374,7 @@ class ThemingController extends Controller {
*/
#[PublicPage]
#[NoCSRFRequired]
#[NoTwoFactorRequired]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getThemeStylesheet(string $themeId, bool $plain = false, bool $withCustomCss = false) {
$themes = $this->themesService->getThemes();

@ -13,6 +13,7 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
@ -34,13 +35,12 @@ class CSRFTokenController extends Controller {
*
* 200: CSRF token returned
* 403: Strict cookie check failed
*
* @NoTwoFactorRequired
*/
#[PublicPage]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/csrftoken')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
#[NoTwoFactorRequired]
public function index(): JSONResponse {
if (!$this->request->passesStrictCookieCheck()) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);

@ -13,6 +13,7 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\FileDisplayResponse;
@ -42,16 +43,15 @@ class JsController extends Controller {
/**
* @NoSameSiteCookieRequired
* @NoTwoFactorRequired
*
* @param string $fileName js filename with extension
* @param string $appName js folder name
* @return FileDisplayResponse|NotFoundResponse
*/
#[PublicPage]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/js/{appName}/{fileName}')]
public function getJs(string $fileName, string $appName): Response {
#[NoTwoFactorRequired]
public function getJs(string $fileName, string $appName): FileDisplayResponse|NotFoundResponse {
try {
$folder = $this->appData->getFolder($appName);
$gzip = false;
@ -76,15 +76,11 @@ class JsController extends Controller {
}
/**
* @NoTwoFactorRequired
*
* @param ISimpleFolder $folder
* @param string $fileName
* @param bool $gzip is set to true if we use the gzip file
* @return ISimpleFile
*
* @throws NotFoundException
*/
#[NoTwoFactorRequired]
private function getFile(ISimpleFolder $folder, string $fileName, bool &$gzip): ISimpleFile {
$encoding = $this->request->getHeader('Accept-Encoding');

@ -16,6 +16,7 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataDisplayResponse;
@ -75,12 +76,10 @@ class OCJSController extends Controller {
);
}
/**
* @NoTwoFactorRequired
*/
#[PublicPage]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/core/js/oc.js')]
#[NoTwoFactorRequired]
public function getConfig(): DataDisplayResponse {
$data = $this->helper->getConfig();

@ -7,6 +7,7 @@
*/
namespace OC\Core\Controller;
use OC\AppFramework\Http\Attributes\TwoFactorSetUpDoneRequired;
use OC\Authentication\TwoFactorAuth\Manager;
use OC_User;
use OCP\AppFramework\Controller;
@ -67,16 +68,11 @@ class TwoFactorChallengeController extends Controller {
return [$regular, $backup];
}
/**
* @TwoFactorSetUpDoneRequired
*
* @param string $redirect_url
* @return StandaloneTemplateResponse
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/login/selectchallenge')]
public function selectChallenge($redirect_url) {
#[TwoFactorSetUpDoneRequired]
public function selectChallenge(string $redirect_url): StandaloneTemplateResponse {
$user = $this->userSession->getUser();
$providerSet = $this->twoFactorManager->getProviderSet($user);
$allProviders = $providerSet->getProviders();
@ -95,18 +91,12 @@ class TwoFactorChallengeController extends Controller {
return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
}
/**
* @TwoFactorSetUpDoneRequired
*
* @param string $challengeProviderId
* @param string $redirect_url
* @return StandaloneTemplateResponse|RedirectResponse
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[UseSession]
#[TwoFactorSetUpDoneRequired]
#[FrontpageRoute(verb: 'GET', url: '/login/challenge/{challengeProviderId}')]
public function showChallenge($challengeProviderId, $redirect_url) {
public function showChallenge(string $challengeProviderId, string $redirect_url): StandaloneTemplateResponse|RedirectResponse {
$user = $this->userSession->getUser();
$providerSet = $this->twoFactorManager->getProviderSet($user);
$provider = $providerSet->getProvider($challengeProviderId);
@ -148,21 +138,13 @@ class TwoFactorChallengeController extends Controller {
return $response;
}
/**
* @TwoFactorSetUpDoneRequired
*
*
* @param string $challengeProviderId
* @param string $challenge
* @param string $redirect_url
* @return RedirectResponse
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[UseSession]
#[FrontpageRoute(verb: 'POST', url: '/login/challenge/{challengeProviderId}')]
#[TwoFactorSetUpDoneRequired]
#[UserRateLimit(limit: 5, period: 100)]
public function solveChallenge($challengeProviderId, $challenge, $redirect_url = null) {
public function solveChallenge(string $challengeProviderId, string $challenge, ?string $redirect_url = null): RedirectResponse {
$user = $this->userSession->getUser();
$provider = $this->twoFactorManager->getProvider($user, $challengeProviderId);
if (is_null($provider)) {

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OC\Core\Middleware;
use Exception;
use OC\AppFramework\Http\Attributes\TwoFactorSetUpDoneRequired;
use OC\Authentication\Exceptions\TwoFactorAuthRequiredException;
use OC\Authentication\Exceptions\UserAlreadyLoggedInException;
use OC\Authentication\TwoFactorAuth\Manager;
@ -18,6 +19,7 @@ use OC\Core\Controller\TwoFactorChallengeController;
use OC\User\Session;
use OCA\TwoFactorNextcloudNotification\Controller\APIController;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\IControllerMethodReflector;
@ -26,6 +28,8 @@ use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use Psr\Log\LoggerInterface;
use ReflectionMethod;
class TwoFactorMiddleware extends Middleware {
public function __construct(
@ -35,6 +39,7 @@ class TwoFactorMiddleware extends Middleware {
private IURLGenerator $urlGenerator,
private IControllerMethodReflector $reflector,
private IRequest $request,
private LoggerInterface $logger,
) {
}
@ -43,7 +48,9 @@ class TwoFactorMiddleware extends Middleware {
* @param string $methodName
*/
public function beforeController($controller, $methodName) {
if ($this->reflector->hasAnnotation('NoTwoFactorRequired')) {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'NoTwoFactorRequired', NoTwoFactorRequired::class)) {
// Route handler explicitly marked to work without finished 2FA are
// not blocked
return;
@ -56,7 +63,7 @@ class TwoFactorMiddleware extends Middleware {
if ($controller instanceof TwoFactorChallengeController
&& $this->userSession->getUser() !== null
&& !$this->reflector->hasAnnotation('TwoFactorSetUpDoneRequired')) {
&& !$reflectionMethod->getAttributes(TwoFactorSetUpDoneRequired::class)) {
$providers = $this->twoFactorManager->getProviderSet($this->userSession->getUser());
if (!($providers->getPrimaryProviders() === [] && !$providers->isProviderMissing())) {
@ -86,7 +93,7 @@ class TwoFactorMiddleware extends Middleware {
|| $this->session->exists('app_api') // authenticated using an AppAPI Auth
|| $this->twoFactorManager->isTwoFactorAuthenticated($user)) {
$this->checkTwoFactor($controller, $methodName, $user);
$this->checkTwoFactor($controller, $user);
} elseif ($controller instanceof TwoFactorChallengeController) {
// Allow access to the two-factor controllers only if two-factor authentication
// is in progress.
@ -96,7 +103,7 @@ class TwoFactorMiddleware extends Middleware {
// TODO: dont check/enforce 2FA if a auth token is used
}
private function checkTwoFactor(Controller $controller, $methodName, IUser $user) {
private function checkTwoFactor(Controller $controller, IUser $user) {
// If two-factor auth is in progress disallow access to any controllers
// defined within "LoginController".
$needsSecondFactor = $this->twoFactorManager->needsSecondFactor($user);
@ -130,4 +137,26 @@ class TwoFactorMiddleware extends Middleware {
throw $exception;
}
/**
* @template T
*
* @param ReflectionMethod $reflectionMethod
* @param ?string $annotationName
* @param class-string<T> $attributeClass
* @return boolean
*/
protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, ?string $annotationName, string $attributeClass): bool {
if (!empty($reflectionMethod->getAttributes($attributeClass))) {
return true;
}
if ($annotationName && $this->reflector->hasAnnotation($annotationName)) {
$this->logger->debug($reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName() . ' uses the @' . $annotationName . ' annotation and should use the #[' . $attributeClass . '] attribute instead');
return true;
}
return false;
}
}

@ -92,6 +92,7 @@ return array(
'OCP\\AppFramework\\Http\\Attribute\\IgnoreOpenAPI' => $baseDir . '/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\NoAdminRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\NoCSRFRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\NoTwoFactorRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoTwoFactorRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\OpenAPI' => $baseDir . '/lib/public/AppFramework/Http/Attribute/OpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\PasswordConfirmationRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\PublicPage' => $baseDir . '/lib/public/AppFramework/Http/Attribute/PublicPage.php',
@ -1053,6 +1054,7 @@ return array(
'OC\\AppFramework\\Bootstrap\\ServiceRegistration' => $baseDir . '/lib/private/AppFramework/Bootstrap/ServiceRegistration.php',
'OC\\AppFramework\\DependencyInjection\\DIContainer' => $baseDir . '/lib/private/AppFramework/DependencyInjection/DIContainer.php',
'OC\\AppFramework\\Http' => $baseDir . '/lib/private/AppFramework/Http.php',
'OC\\AppFramework\\Http\\Attributes\\TwoFactorSetUpDoneRequired' => $baseDir . '/lib/private/AppFramework/Http/Attributes/TwoFactorSetUpDoneRequired.php',
'OC\\AppFramework\\Http\\Dispatcher' => $baseDir . '/lib/private/AppFramework/Http/Dispatcher.php',
'OC\\AppFramework\\Http\\Output' => $baseDir . '/lib/private/AppFramework/Http/Output.php',
'OC\\AppFramework\\Http\\Request' => $baseDir . '/lib/private/AppFramework/Http/Request.php',

@ -133,6 +133,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\AppFramework\\Http\\Attribute\\IgnoreOpenAPI' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\NoAdminRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\NoCSRFRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\NoTwoFactorRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoTwoFactorRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\OpenAPI' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/OpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\PasswordConfirmationRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\PublicPage' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/PublicPage.php',
@ -1094,6 +1095,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\AppFramework\\Bootstrap\\ServiceRegistration' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Bootstrap/ServiceRegistration.php',
'OC\\AppFramework\\DependencyInjection\\DIContainer' => __DIR__ . '/../../..' . '/lib/private/AppFramework/DependencyInjection/DIContainer.php',
'OC\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http.php',
'OC\\AppFramework\\Http\\Attributes\\TwoFactorSetUpDoneRequired' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Attributes/TwoFactorSetUpDoneRequired.php',
'OC\\AppFramework\\Http\\Dispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Dispatcher.php',
'OC\\AppFramework\\Http\\Output' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Output.php',
'OC\\AppFramework\\Http\\Request' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Request.php',

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
* SPDX-FileContributor: Carl Schwan
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Http\Attributes;
use Attribute;
#[Attribute]
class TwoFactorSetUpDoneRequired {
}

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
* SPDX-FileContributor: Carl Schwan
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\AppFramework\Http\Attribute;
use Attribute;
/**
* A user can access the page before the two-factor challenge has been passed
* (use this wisely and only in two-factor auth apps, e.g. to allow setup during
* login).
*
* @since 33.0.0
*/
#[Attribute]
class NoTwoFactorRequired {
}

@ -8,6 +8,7 @@
namespace Test\Core\Middleware;
use OC\AppFramework\Http\Attributes\TwoFactorSetUpDoneRequired;
use OC\AppFramework\Http\Request;
use OC\Authentication\Exceptions\TwoFactorAuthRequiredException;
use OC\Authentication\Exceptions\UserAlreadyLoggedInException;
@ -17,7 +18,9 @@ use OC\Core\Controller\TwoFactorChallengeController;
use OC\Core\Middleware\TwoFactorMiddleware;
use OC\User\Session;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCP\Authentication\TwoFactorAuth\ALoginSetupController;
use OCP\Authentication\TwoFactorAuth\IProvider;
@ -28,33 +31,52 @@ use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserSession;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class TwoFactorMiddlewareTest extends TestCase {
/** @var Manager|MockObject */
private $twoFactorManager;
/** @var IUserSession|MockObject */
private $userSession;
/** @var ISession|MockObject */
private $session;
class HasTwoFactorAnnotationController extends Controller {
#[NoTwoFactorRequired]
public function index(): Response {
return new Response();
}
}
/** @var IURLGenerator|MockObject */
private $urlGenerator;
class LoginSetupController extends ALoginSetupController {
public function index(): Response {
return new Response();
}
}
/** @var IControllerMethodReflector|MockObject */
private $reflector;
class NoTwoFactorAnnotationController extends Controller {
public function index(): Response {
return new Response();
}
}
/** @var IRequest|MockObject */
private $request;
class NoTwoFactorChallengeAnnotationController extends TwoFactorChallengeController {
public function index(): Response {
return new Response();
}
}
/** @var TwoFactorMiddleware */
private $middleware;
class HasTwoFactorSetUpDoneAnnotationController extends TwoFactorChallengeController {
#[TwoFactorSetUpDoneRequired]
public function index(): Response {
return new Response();
}
}
/** @var Controller */
private $controller;
class TwoFactorMiddlewareTest extends TestCase {
private Manager&MockObject $twoFactorManager;
private IUserSession&MockObject $userSession;
private ISession&MockObject $session;
private IURLGenerator&MockObject $urlGenerator;
private IControllerMethodReflector&MockObject $reflector;
private IRequest $request;
private TwoFactorMiddleware $middleware;
private LoggerInterface&MockObject $logger;
protected function setUp(): void {
parent::setUp();
@ -68,6 +90,7 @@ class TwoFactorMiddlewareTest extends TestCase {
$this->session = $this->createMock(ISession::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->reflector = $this->createMock(IControllerMethodReflector::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->request = new Request(
[
'server' => [
@ -78,8 +101,7 @@ class TwoFactorMiddlewareTest extends TestCase {
$this->createMock(IConfig::class)
);
$this->middleware = new TwoFactorMiddleware($this->twoFactorManager, $this->userSession, $this->session, $this->urlGenerator, $this->reflector, $this->request);
$this->controller = $this->createMock(Controller::class);
$this->middleware = new TwoFactorMiddleware($this->twoFactorManager, $this->userSession, $this->session, $this->urlGenerator, $this->reflector, $this->request, $this->logger);
}
public function testBeforeControllerNotLoggedIn(): void {
@ -90,12 +112,14 @@ class TwoFactorMiddlewareTest extends TestCase {
$this->userSession->expects($this->never())
->method('getUser');
$this->middleware->beforeController($this->controller, 'index');
$controller = $this->getMockBuilder(NoTwoFactorAnnotationController::class)
->disableOriginalConstructor()
->getMock();
$this->middleware->beforeController($controller, 'index');
}
public function testBeforeSetupController(): void {
$user = $this->createMock(IUser::class);
$controller = $this->createMock(ALoginSetupController::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
@ -105,7 +129,7 @@ class TwoFactorMiddlewareTest extends TestCase {
$this->userSession->expects($this->never())
->method('isLoggedIn');
$this->middleware->beforeController($controller, 'create');
$this->middleware->beforeController(new LoginSetupController('foo', $this->request), 'index');
}
public function testBeforeControllerNoTwoFactorCheckNeeded(): void {
@ -122,7 +146,10 @@ class TwoFactorMiddlewareTest extends TestCase {
->with($user)
->willReturn(false);
$this->middleware->beforeController($this->controller, 'index');
$controller = $this->getMockBuilder(NoTwoFactorAnnotationController::class)
->disableOriginalConstructor()
->getMock();
$this->middleware->beforeController($controller, 'index');
}
@ -146,7 +173,10 @@ class TwoFactorMiddlewareTest extends TestCase {
->with($user)
->willReturn(true);
$this->middleware->beforeController($this->controller, 'index');
$controller = $this->getMockBuilder(NoTwoFactorAnnotationController::class)
->disableOriginalConstructor()
->getMock();
$this->middleware->beforeController($controller, 'index');
}
@ -155,9 +185,6 @@ class TwoFactorMiddlewareTest extends TestCase {
$user = $this->createMock(IUser::class);
$this->reflector
->method('hasAnnotation')
->willReturn(false);
$this->userSession->expects($this->once())
->method('isLoggedIn')
->willReturn(true);
@ -173,7 +200,7 @@ class TwoFactorMiddlewareTest extends TestCase {
->with($user)
->willReturn(false);
$twoFactorChallengeController = $this->getMockBuilder(TwoFactorChallengeController::class)
$twoFactorChallengeController = $this->getMockBuilder(NoTwoFactorChallengeAnnotationController::class)
->disableOriginalConstructor()
->getMock();
$this->middleware->beforeController($twoFactorChallengeController, 'index');
@ -188,7 +215,8 @@ class TwoFactorMiddlewareTest extends TestCase {
->willReturn('test/url');
$expected = new RedirectResponse('test/url');
$this->assertEquals($expected, $this->middleware->afterException($this->controller, 'index', $ex));
$controller = new HasTwoFactorAnnotationController('foo', $this->request);
$this->assertEquals($expected, $this->middleware->afterException($controller, 'index', $ex));
}
public function testAfterException(): void {
@ -200,17 +228,13 @@ class TwoFactorMiddlewareTest extends TestCase {
->willReturn('redirect/url');
$expected = new RedirectResponse('redirect/url');
$this->assertEquals($expected, $this->middleware->afterException($this->controller, 'index', $ex));
$controller = new HasTwoFactorAnnotationController('foo', $this->request);
$this->assertEquals($expected, $this->middleware->afterException($controller, 'index', $ex));
}
public function testRequires2FASetupDoneAnnotated(): void {
$user = $this->createMock(IUser::class);
$this->reflector
->method('hasAnnotation')
->willReturnCallback(function (string $annotation) {
return $annotation === 'TwoFactorSetUpDoneRequired';
});
$this->userSession->expects($this->once())
->method('isLoggedIn')
->willReturn(true);
@ -228,10 +252,10 @@ class TwoFactorMiddlewareTest extends TestCase {
$this->expectException(UserAlreadyLoggedInException::class);
$twoFactorChallengeController = $this->getMockBuilder(TwoFactorChallengeController::class)
$controller = $this->getMockBuilder(HasTwoFactorSetUpDoneAnnotationController::class)
->disableOriginalConstructor()
->getMock();
$this->middleware->beforeController($twoFactorChallengeController, 'index');
$this->middleware->beforeController($controller, 'index');
}
public static function dataRequires2FASetupDone(): array {
@ -243,7 +267,7 @@ class TwoFactorMiddlewareTest extends TestCase {
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataRequires2FASetupDone')]
#[DataProvider('dataRequires2FASetupDone')]
public function testRequires2FASetupDone(bool $hasProvider, bool $missingProviders, bool $expectEception): void {
if ($hasProvider) {
$provider = $this->createMock(IProvider::class);
@ -257,9 +281,6 @@ class TwoFactorMiddlewareTest extends TestCase {
$user = $this->createMock(IUser::class);
$this->reflector
->method('hasAnnotation')
->willReturn(false);
$this->userSession
->method('getUser')
->willReturn($user);
@ -278,9 +299,9 @@ class TwoFactorMiddlewareTest extends TestCase {
$this->assertTrue(true);
}
$twoFactorChallengeController = $this->getMockBuilder(TwoFactorChallengeController::class)
$controller = $this->getMockBuilder(NoTwoFactorChallengeAnnotationController::class)
->disableOriginalConstructor()
->getMock();
$this->middleware->beforeController($twoFactorChallengeController, 'index');
$this->middleware->beforeController($controller, 'index');
}
}