From b2ed0fa37c5f46710216a4fe8342b14e5be8f8e8 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 17 Sep 2025 15:32:11 +0200 Subject: [PATCH] feat(AppFramework): Add missing NoTwoFactorRequired attribute It's in our documentation but was never implemented. Signed-off-by: Carl Schwan --- .../lib/Controller/ThemingController.php | 14 +-- core/Controller/CSRFTokenController.php | 4 +- core/Controller/JsController.php | 12 +- core/Controller/OCJSController.php | 5 +- .../TwoFactorChallengeController.php | 32 ++--- core/Middleware/TwoFactorMiddleware.php | 37 +++++- lib/composer/composer/autoload_classmap.php | 2 + lib/composer/composer/autoload_static.php | 2 + .../Attributes/TwoFactorSetUpDoneRequired.php | 18 +++ .../Http/Attribute/NoTwoFactorRequired.php | 25 ++++ .../Middleware/TwoFactorMiddlewareTest.php | 111 +++++++++++------- 11 files changed, 165 insertions(+), 97 deletions(-) create mode 100644 lib/private/AppFramework/Http/Attributes/TwoFactorSetUpDoneRequired.php create mode 100644 lib/public/AppFramework/Http/Attribute/NoTwoFactorRequired.php diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 8bb9841ae55..455d6fc3b15 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -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(); diff --git a/core/Controller/CSRFTokenController.php b/core/Controller/CSRFTokenController.php index edf7c26e94c..85b187f42e6 100644 --- a/core/Controller/CSRFTokenController.php +++ b/core/Controller/CSRFTokenController.php @@ -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); diff --git a/core/Controller/JsController.php b/core/Controller/JsController.php index bb40a4f2117..b74e0583683 100644 --- a/core/Controller/JsController.php +++ b/core/Controller/JsController.php @@ -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'); diff --git a/core/Controller/OCJSController.php b/core/Controller/OCJSController.php index 083ad4b209f..db42a3608b5 100644 --- a/core/Controller/OCJSController.php +++ b/core/Controller/OCJSController.php @@ -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(); diff --git a/core/Controller/TwoFactorChallengeController.php b/core/Controller/TwoFactorChallengeController.php index 8072bb8e92a..a10729e456d 100644 --- a/core/Controller/TwoFactorChallengeController.php +++ b/core/Controller/TwoFactorChallengeController.php @@ -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)) { diff --git a/core/Middleware/TwoFactorMiddleware.php b/core/Middleware/TwoFactorMiddleware.php index 0dea402f127..afc693c15bb 100644 --- a/core/Middleware/TwoFactorMiddleware.php +++ b/core/Middleware/TwoFactorMiddleware.php @@ -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 $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; + } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 98f4d5a9dcf..612d0a75cc0 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index c463a93e9a3..c727a780de7 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.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', diff --git a/lib/private/AppFramework/Http/Attributes/TwoFactorSetUpDoneRequired.php b/lib/private/AppFramework/Http/Attributes/TwoFactorSetUpDoneRequired.php new file mode 100644 index 00000000000..f298fff77eb --- /dev/null +++ b/lib/private/AppFramework/Http/Attributes/TwoFactorSetUpDoneRequired.php @@ -0,0 +1,18 @@ +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'); } }