From eea5e1cca2d1552c35d1e3a1d07ac16351fbefd8 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 1 Mar 2024 18:37:47 +0100 Subject: [PATCH 1/4] fix(Session): avoid password confirmation on SSO SSO backends like SAML and OIDC tried a trick to suppress password confirmations as they are not possible by design. At least for SAML it was not reliable when existing user backends where used as user repositories. Now we are setting a special scope with the token, and also make sure that the scope is taken over when tokens are regenerated. Signed-off-by: Arthur Schiwon --- core/Controller/OCJSController.php | 9 ++- .../DependencyInjection/DIContainer.php | 3 +- .../PasswordConfirmationMiddleware.php | 30 +++++++- .../Token/PublicKeyTokenProvider.php | 1 + lib/private/Template/JSConfigHelper.php | 73 ++++++++++--------- lib/private/TemplateLayout.php | 4 +- lib/private/legacy/OC_User.php | 10 ++- .../PasswordConfirmationMiddlewareTest.php | 60 ++++++++++++++- 8 files changed, 144 insertions(+), 46 deletions(-) diff --git a/core/Controller/OCJSController.php b/core/Controller/OCJSController.php index fa13f21607c46..4f84e551af761 100644 --- a/core/Controller/OCJSController.php +++ b/core/Controller/OCJSController.php @@ -28,6 +28,7 @@ namespace OC\Core\Controller; use bantu\IniGetWrapper\IniGetWrapper; +use OC\Authentication\Token\IProvider; use OC\CapabilitiesManager; use OC\Template\JSConfigHelper; use OCP\App\IAppManager; @@ -59,7 +60,10 @@ public function __construct(string $appName, IniGetWrapper $iniWrapper, IURLGenerator $urlGenerator, CapabilitiesManager $capabilitiesManager, - IInitialStateService $initialStateService) { + IInitialStateService $initialStateService, + IProvider $tokenProvider, + ) { + parent::__construct($appName, $request); $this->helper = new JSConfigHelper( @@ -73,7 +77,8 @@ public function __construct(string $appName, $iniWrapper, $urlGenerator, $capabilitiesManager, - $initialStateService + $initialStateService, + $tokenProvider ); } diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 9b202f07fbf29..25e5aac453b6d 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -275,7 +275,8 @@ public function __construct(string $appName, array $urlParams = [], ServerContai $c->get(IControllerMethodReflector::class), $c->get(ISession::class), $c->get(IUserSession::class), - $c->get(ITimeFactory::class) + $c->get(ITimeFactory::class), + $c->get(\OC\Authentication\Token\IProvider::class), ) ); $dispatcher->registerMiddleware( diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php index 0ee9fdff881bb..beb64d42191ed 100644 --- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -25,11 +25,16 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\WipeTokenException; +use OC\Authentication\Token\IProvider; use OCP\AppFramework\Controller; use OCP\AppFramework\Middleware; use OCP\AppFramework\Utility\ITimeFactory; use OCP\ISession; use OCP\IUserSession; +use OCP\Session\Exceptions\SessionNotAvailableException; use OCP\User\Backend\IPasswordConfirmationBackend; class PasswordConfirmationMiddleware extends Middleware { @@ -43,6 +48,7 @@ class PasswordConfirmationMiddleware extends Middleware { private $timeFactory; /** @var array */ private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; + private IProvider $tokenProvider; /** * PasswordConfirmationMiddleware constructor. @@ -53,13 +59,16 @@ class PasswordConfirmationMiddleware extends Middleware { * @param ITimeFactory $timeFactory */ public function __construct(ControllerMethodReflector $reflector, - ISession $session, - IUserSession $userSession, - ITimeFactory $timeFactory) { + ISession $session, + IUserSession $userSession, + ITimeFactory $timeFactory, + IProvider $tokenProvider, + ) { $this->reflector = $reflector; $this->session = $session; $this->userSession = $userSession; $this->timeFactory = $timeFactory; + $this->tokenProvider = $tokenProvider; } /** @@ -82,8 +91,21 @@ public function beforeController($controller, $methodName) { $backendClassName = $user->getBackendClassName(); } + try { + $sessionId = $this->session->getId(); + $token = $this->tokenProvider->getToken($sessionId); + } catch (SessionNotAvailableException|InvalidTokenException|WipeTokenException|ExpiredTokenException) { + // States we do not deal with here. + return; + } + $scope = $token->getScopeAsArray(); + if (isset($scope['sso-based-login']) && $scope['sso-based-login'] === true) { + // Users logging in from SSO backends cannot confirm their password by design + return; + } + $lastConfirm = (int) $this->session->get('last-password-confirm'); - // we can't check the password against a SAML backend, so skip password confirmation in this case + // TODO: confirm excludedUserBackEnds can go away and remove it if (!isset($this->excludedUserBackEnds[$backendClassName]) && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay throw new NotConfirmedException(); } diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 824e2e056c813..726892bc4c593 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -242,6 +242,7 @@ public function renewSessionToken(string $oldSessionId, string $sessionId): ITok IToken::TEMPORARY_TOKEN, $token->getRemember() ); + $newToken->setScope($token->getScopeAsArray()); $this->mapper->delete($token); diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index 7b6d0a6a34648..d890ac785af61 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -34,6 +34,10 @@ namespace OC\Template; use bantu\IniGetWrapper\IniGetWrapper; +use OC\Authentication\Exceptions\ExpiredTokenException; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\WipeTokenException; +use OC\Authentication\Token\IProvider; use OC\CapabilitiesManager; use OC\Share\Share; use OCP\App\AppPathNotFoundException; @@ -49,47 +53,29 @@ use OCP\IURLGenerator; use OCP\ILogger; use OCP\IUser; +use OCP\Session\Exceptions\SessionNotAvailableException; use OCP\User\Backend\IPasswordConfirmationBackend; use OCP\Util; class JSConfigHelper { - protected IL10N $l; - protected Defaults $defaults; - protected IAppManager $appManager; - protected ISession $session; - protected ?IUser $currentUser; - protected IConfig $config; - protected IGroupManager $groupManager; - protected IniGetWrapper $iniWrapper; - protected IURLGenerator $urlGenerator; - protected CapabilitiesManager $capabilitiesManager; - protected IInitialStateService $initialStateService; /** @var array user back-ends excluded from password verification */ private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; - public function __construct(IL10N $l, - Defaults $defaults, - IAppManager $appManager, - ISession $session, - ?IUser $currentUser, - IConfig $config, - IGroupManager $groupManager, - IniGetWrapper $iniWrapper, - IURLGenerator $urlGenerator, - CapabilitiesManager $capabilitiesManager, - IInitialStateService $initialStateService) { - $this->l = $l; - $this->defaults = $defaults; - $this->appManager = $appManager; - $this->session = $session; - $this->currentUser = $currentUser; - $this->config = $config; - $this->groupManager = $groupManager; - $this->iniWrapper = $iniWrapper; - $this->urlGenerator = $urlGenerator; - $this->capabilitiesManager = $capabilitiesManager; - $this->initialStateService = $initialStateService; + public function __construct( + protected IL10N $l, + protected Defaults $defaults, + protected IAppManager $appManager, + protected ISession $session, + protected ?IUser $currentUser, + protected IConfig $config, + protected IGroupManager $groupManager, + protected IniGetWrapper $iniWrapper, + protected IURLGenerator $urlGenerator, + protected CapabilitiesManager $capabilitiesManager, + protected IInitialStateService $initialStateService, + protected IProvider $tokenProvider, + ) { } public function getConfig(): string { @@ -155,9 +141,13 @@ public function getConfig(): string { } if ($this->currentUser instanceof IUser) { - $lastConfirmTimestamp = $this->session->get('last-password-confirm'); - if (!is_int($lastConfirmTimestamp)) { - $lastConfirmTimestamp = 0; + if ($this->canUserValidatePassword()) { + $lastConfirmTimestamp = $this->session->get('last-password-confirm'); + if (!is_int($lastConfirmTimestamp)) { + $lastConfirmTimestamp = 0; + } + } else { + $lastConfirmTimestamp = PHP_INT_MAX; } } else { $lastConfirmTimestamp = 0; @@ -310,4 +300,15 @@ public function getConfig(): string { return $result; } + + protected function canUserValidatePassword(): bool { + try { + $token = $this->tokenProvider->getToken($this->session->getId()); + } catch (ExpiredTokenException|WipeTokenException|InvalidTokenException|SessionNotAvailableException) { + // actually we do not know, so we fall back to this statement + return true; + } + $scope = $token->getScopeAsArray(); + return !isset($scope['sso-based-login']) || $scope['sso-based-login'] === false; + } } diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 123fd6debb56d..fe2595c72a088 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -43,6 +43,7 @@ namespace OC; use bantu\IniGetWrapper\IniGetWrapper; +use OC\Authentication\Token\IProvider; use OC\Search\SearchQuery; use OC\Template\CSSResourceLocator; use OC\Template\JSConfigHelper; @@ -235,7 +236,8 @@ public function __construct($renderAs, $appId = '') { \OC::$server->get(IniGetWrapper::class), \OC::$server->getURLGenerator(), \OC::$server->getCapabilitiesManager(), - \OC::$server->query(IInitialStateService::class) + \OCP\Server::get(IInitialStateService::class), + \OCP\Server::get(IProvider::class), ); $config = $jsConfigHelper->getConfig(); if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) { diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php index caa4f5dca6512..24ffaa3b3aaf5 100644 --- a/lib/private/legacy/OC_User.php +++ b/lib/private/legacy/OC_User.php @@ -35,7 +35,7 @@ * along with this program. If not, see * */ - +use OC\Authentication\Token\IProvider; use OC\User\LoginException; use OCP\EventDispatcher\IEventDispatcher; use OCP\ILogger; @@ -193,6 +193,14 @@ public static function loginWithApache(\OCP\Authentication\IApacheBackend $backe $userSession->createSessionToken($request, $uid, $uid, $password); $userSession->createRememberMeToken($userSession->getUser()); + + if (empty($password)) { + $tokenProvider = \OC::$server->get(IProvider::class); + $token = $tokenProvider->getToken($userSession->getSession()->getId()); + $token->setScope(['sso-based-login' => true]); + $tokenProvider->updateToken($token); + } + // setup the filesystem OC_Util::setupFS($uid); // first call the post_login hooks, the login-process needs to be diff --git a/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php index 3153d7f0b0845..98847a69d2f67 100644 --- a/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php @@ -27,7 +27,9 @@ use OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware; use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\Controller; +use OC\Authentication\Token\IProvider; use OCP\AppFramework\Utility\ITimeFactory; +use OC\Authentication\Token\IToken; use OCP\ISession; use OCP\IUser; use OCP\IUserSession; @@ -48,6 +50,7 @@ class PasswordConfirmationMiddlewareTest extends TestCase { private $contoller; /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ private $timeFactory; + private IProvider|\PHPUnit\Framework\MockObject\MockObject $tokenProvider; protected function setUp(): void { $this->reflector = new ControllerMethodReflector(); @@ -56,12 +59,14 @@ protected function setUp(): void { $this->user = $this->createMock(IUser::class); $this->contoller = $this->createMock(Controller::class); $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->tokenProvider = $this->createMock(IProvider::class); $this->middleware = new PasswordConfirmationMiddleware( $this->reflector, $this->session, $this->userSession, - $this->timeFactory + $this->timeFactory, + $this->tokenProvider, ); } @@ -94,6 +99,13 @@ public function testDifferentAnnotation() { */ public function testAnnotation($backend, $lastConfirm, $currentTime, $exception) { $this->reflector->reflect(__CLASS__, __FUNCTION__); + $token = $this->createMock(IToken::class); + $token->method('getScopeAsArray') + ->willReturn([]); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->willReturn($token); + $this->user->method('getBackendClassName') ->willReturn($backend); @@ -107,6 +119,13 @@ public function testAnnotation($backend, $lastConfirm, $currentTime, $exception) $this->timeFactory->method('getTime') ->willReturn($currentTime); + $token = $this->createMock(IToken::class); + $token->method('getScopeAsArray') + ->willReturn([]); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->willReturn($token); + $thrown = false; try { $this->middleware->beforeController($this->contoller, __FUNCTION__); @@ -117,6 +136,8 @@ public function testAnnotation($backend, $lastConfirm, $currentTime, $exception) $this->assertSame($exception, $thrown); } + + public function dataProvider() { return [ ['foo', 2000, 4000, true], @@ -127,4 +148,41 @@ public function dataProvider() { ['foo', 2000, 3816, true], ]; } + + public function testSSO() { + static $sessionId = 'mySession1d'; + + $this->reflector->reflect($this->controller, __FUNCTION__); + + $this->user->method('getBackendClassName') + ->willReturn('fictional_backend'); + $this->userSession->method('getUser') + ->willReturn($this->user); + + $this->session->method('get') + ->with('last-password-confirm') + ->willReturn(0); + $this->session->method('getId') + ->willReturn($sessionId); + + $this->timeFactory->method('getTime') + ->willReturn(9876); + + $token = $this->createMock(IToken::class); + $token->method('getScopeAsArray') + ->willReturn(['sso-based-login' => true]); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with($sessionId) + ->willReturn($token); + + $thrown = false; + try { + $this->middleware->beforeController($this->controller, __FUNCTION__); + } catch (NotConfirmedException) { + $thrown = true; + } + + $this->assertSame(false, $thrown); + } } From 0f5c8f9111c327d13f4ef3637ac11fbcc6e64ffd Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 12 Jun 2024 11:05:43 +0200 Subject: [PATCH 2/4] fix(Token): make new scope future compatible - "password-unconfirmable" is the effective name for 30, but a draft name was backported. Signed-off-by: Arthur Schiwon --- .../Middleware/Security/PasswordConfirmationMiddleware.php | 2 +- lib/private/Template/JSConfigHelper.php | 2 +- lib/private/legacy/OC_User.php | 2 +- .../Middleware/Security/PasswordConfirmationMiddlewareTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php index beb64d42191ed..4425931b8bb6a 100644 --- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -99,7 +99,7 @@ public function beforeController($controller, $methodName) { return; } $scope = $token->getScopeAsArray(); - if (isset($scope['sso-based-login']) && $scope['sso-based-login'] === true) { + if (isset($scope['password-unconfirmable']) && $scope['password-unconfirmable'] === true) { // Users logging in from SSO backends cannot confirm their password by design return; } diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index d890ac785af61..6942fdeebcbc0 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -309,6 +309,6 @@ protected function canUserValidatePassword(): bool { return true; } $scope = $token->getScopeAsArray(); - return !isset($scope['sso-based-login']) || $scope['sso-based-login'] === false; + return !isset($scope['password-unconfirmable']) || $scope['password-unconfirmable'] === false; } } diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php index 24ffaa3b3aaf5..ec0b7e69c8ae1 100644 --- a/lib/private/legacy/OC_User.php +++ b/lib/private/legacy/OC_User.php @@ -197,7 +197,7 @@ public static function loginWithApache(\OCP\Authentication\IApacheBackend $backe if (empty($password)) { $tokenProvider = \OC::$server->get(IProvider::class); $token = $tokenProvider->getToken($userSession->getSession()->getId()); - $token->setScope(['sso-based-login' => true]); + $token->setScope(['password-unconfirmable' => true]); $tokenProvider->updateToken($token); } diff --git a/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php index 98847a69d2f67..102c7631003e8 100644 --- a/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php @@ -170,7 +170,7 @@ public function testSSO() { $token = $this->createMock(IToken::class); $token->method('getScopeAsArray') - ->willReturn(['sso-based-login' => true]); + ->willReturn(['password-unconfirmable' => true]); $this->tokenProvider->expects($this->once()) ->method('getToken') ->with($sessionId) From 06c64fdbc6a0e5eed5b716d09638bd2603943ba5 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 12 Jun 2024 14:58:27 +0200 Subject: [PATCH 3/4] style(PHP): remove unacceptable empty lines Signed-off-by: Arthur Schiwon --- core/Controller/OCJSController.php | 1 - lib/private/Template/JSConfigHelper.php | 1 - 2 files changed, 2 deletions(-) diff --git a/core/Controller/OCJSController.php b/core/Controller/OCJSController.php index 4f84e551af761..af0d4689efbe2 100644 --- a/core/Controller/OCJSController.php +++ b/core/Controller/OCJSController.php @@ -63,7 +63,6 @@ public function __construct(string $appName, IInitialStateService $initialStateService, IProvider $tokenProvider, ) { - parent::__construct($appName, $request); $this->helper = new JSConfigHelper( diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index 6942fdeebcbc0..e744b5362ce77 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -58,7 +58,6 @@ use OCP\Util; class JSConfigHelper { - /** @var array user back-ends excluded from password verification */ private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; From 527bc5d984f2fbad6d1f5ee95085bfdf7c30634f Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 13 Jun 2024 20:05:34 +0200 Subject: [PATCH 4/4] test(unit): adjust testSSO scenario and test class Signed-off-by: Arthur Schiwon --- ...sswordConfirmationMiddlewareController.php | 57 +++++++++++++++++++ .../PasswordConfirmationMiddlewareTest.php | 15 +++-- 2 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 tests/lib/AppFramework/Middleware/Security/Mock/PasswordConfirmationMiddlewareController.php diff --git a/tests/lib/AppFramework/Middleware/Security/Mock/PasswordConfirmationMiddlewareController.php b/tests/lib/AppFramework/Middleware/Security/Mock/PasswordConfirmationMiddlewareController.php new file mode 100644 index 0000000000000..e06b4f2a76a6e --- /dev/null +++ b/tests/lib/AppFramework/Middleware/Security/Mock/PasswordConfirmationMiddlewareController.php @@ -0,0 +1,57 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Test\AppFramework\Middleware\Security\Mock; + +use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; + +class PasswordConfirmationMiddlewareController extends \OCP\AppFramework\Controller { + public function testNoAnnotationNorAttribute() { + } + + /** + * @TestAnnotation + */ + public function testDifferentAnnotation() { + } + + /** + * @PasswordConfirmationRequired + */ + public function testAnnotation() { + } + + /** + * @PasswordConfirmationRequired + */ + public function testAttribute() { + } + + /** + * @PasswordConfirmationRequired + */ + public function testSSO() { + } +} diff --git a/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php index 102c7631003e8..b61732fbd4c6a 100644 --- a/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php @@ -30,9 +30,11 @@ use OC\Authentication\Token\IProvider; use OCP\AppFramework\Utility\ITimeFactory; use OC\Authentication\Token\IToken; +use OCP\IRequest; use OCP\ISession; use OCP\IUser; use OCP\IUserSession; +use Test\AppFramework\Middleware\Security\Mock\PasswordConfirmationMiddlewareController; use Test\TestCase; class PasswordConfirmationMiddlewareTest extends TestCase { @@ -47,7 +49,7 @@ class PasswordConfirmationMiddlewareTest extends TestCase { /** @var PasswordConfirmationMiddleware */ private $middleware; /** @var Controller */ - private $contoller; + private $controller; /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ private $timeFactory; private IProvider|\PHPUnit\Framework\MockObject\MockObject $tokenProvider; @@ -57,7 +59,10 @@ protected function setUp(): void { $this->session = $this->createMock(ISession::class); $this->userSession = $this->createMock(IUserSession::class); $this->user = $this->createMock(IUser::class); - $this->contoller = $this->createMock(Controller::class); + $this->controller = new PasswordConfirmationMiddlewareController( + 'test', + $this->createMock(IRequest::class) + ); $this->timeFactory = $this->createMock(ITimeFactory::class); $this->tokenProvider = $this->createMock(IProvider::class); @@ -77,7 +82,7 @@ public function testNoAnnotation() { $this->userSession->expects($this->never()) ->method($this->anything()); - $this->middleware->beforeController($this->contoller, __FUNCTION__); + $this->middleware->beforeController($this->controller, __FUNCTION__); } /** @@ -90,7 +95,7 @@ public function testDifferentAnnotation() { $this->userSession->expects($this->never()) ->method($this->anything()); - $this->middleware->beforeController($this->contoller, __FUNCTION__); + $this->middleware->beforeController($this->controller, __FUNCTION__); } /** @@ -128,7 +133,7 @@ public function testAnnotation($backend, $lastConfirm, $currentTime, $exception) $thrown = false; try { - $this->middleware->beforeController($this->contoller, __FUNCTION__); + $this->middleware->beforeController($this->controller, __FUNCTION__); } catch (NotConfirmedException $e) { $thrown = true; }