TwoFactorChallengeControllerTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace Test\Core\Controller;
  8. use OC\Authentication\TwoFactorAuth\Manager;
  9. use OC\Authentication\TwoFactorAuth\ProviderSet;
  10. use OC\Core\Controller\TwoFactorChallengeController;
  11. use OCP\AppFramework\Http\RedirectResponse;
  12. use OCP\AppFramework\Http\StandaloneTemplateResponse;
  13. use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
  14. use OCP\Authentication\TwoFactorAuth\ILoginSetupProvider;
  15. use OCP\Authentication\TwoFactorAuth\IProvider;
  16. use OCP\Authentication\TwoFactorAuth\TwoFactorException;
  17. use OCP\IRequest;
  18. use OCP\ISession;
  19. use OCP\IURLGenerator;
  20. use OCP\IUser;
  21. use OCP\IUserSession;
  22. use OCP\Template;
  23. use Psr\Log\LoggerInterface;
  24. use Test\TestCase;
  25. class TwoFactorChallengeControllerTest extends TestCase {
  26. /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
  27. private $request;
  28. /** @var Manager|\PHPUnit\Framework\MockObject\MockObject */
  29. private $twoFactorManager;
  30. /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
  31. private $userSession;
  32. /** @var ISession|\PHPUnit\Framework\MockObject\MockObject */
  33. private $session;
  34. /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
  35. private $urlGenerator;
  36. /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
  37. private $logger;
  38. /** @var TwoFactorChallengeController|\PHPUnit\Framework\MockObject\MockObject */
  39. private $controller;
  40. protected function setUp(): void {
  41. parent::setUp();
  42. $this->request = $this->createMock(IRequest::class);
  43. $this->twoFactorManager = $this->createMock(Manager::class);
  44. $this->userSession = $this->createMock(IUserSession::class);
  45. $this->session = $this->createMock(ISession::class);
  46. $this->urlGenerator = $this->createMock(IURLGenerator::class);
  47. $this->logger = $this->createMock(LoggerInterface::class);
  48. $this->controller = $this->getMockBuilder(TwoFactorChallengeController::class)
  49. ->setConstructorArgs([
  50. 'core',
  51. $this->request,
  52. $this->twoFactorManager,
  53. $this->userSession,
  54. $this->session,
  55. $this->urlGenerator,
  56. $this->logger,
  57. ])
  58. ->setMethods(['getLogoutUrl'])
  59. ->getMock();
  60. $this->controller->expects($this->any())
  61. ->method('getLogoutUrl')
  62. ->willReturn('logoutAttribute');
  63. }
  64. public function testSelectChallenge(): void {
  65. $user = $this->getMockBuilder(IUser::class)->getMock();
  66. $p1 = $this->createMock(IActivatableAtLogin::class);
  67. $p1->method('getId')->willReturn('p1');
  68. $backupProvider = $this->createMock(IProvider::class);
  69. $backupProvider->method('getId')->willReturn('backup_codes');
  70. $providerSet = new ProviderSet([$p1, $backupProvider], true);
  71. $this->twoFactorManager->expects($this->once())
  72. ->method('getLoginSetupProviders')
  73. ->with($user)
  74. ->willReturn([$p1]);
  75. $this->userSession->expects($this->once())
  76. ->method('getUser')
  77. ->willReturn($user);
  78. $this->twoFactorManager->expects($this->once())
  79. ->method('getProviderSet')
  80. ->with($user)
  81. ->willReturn($providerSet);
  82. $expected = new StandaloneTemplateResponse('core', 'twofactorselectchallenge', [
  83. 'providers' => [
  84. $p1,
  85. ],
  86. 'providerMissing' => true,
  87. 'backupProvider' => $backupProvider,
  88. 'redirect_url' => '/some/url',
  89. 'logout_url' => 'logoutAttribute',
  90. 'hasSetupProviders' => true,
  91. ], 'guest');
  92. $this->assertEquals($expected, $this->controller->selectChallenge('/some/url'));
  93. }
  94. public function testShowChallenge(): void {
  95. $user = $this->createMock(IUser::class);
  96. $provider = $this->createMock(IProvider::class);
  97. $provider->method('getId')->willReturn('myprovider');
  98. $backupProvider = $this->createMock(IProvider::class);
  99. $backupProvider->method('getId')->willReturn('backup_codes');
  100. $tmpl = $this->createMock(Template::class);
  101. $providerSet = new ProviderSet([$provider, $backupProvider], true);
  102. $this->userSession->expects($this->once())
  103. ->method('getUser')
  104. ->willReturn($user);
  105. $this->twoFactorManager->expects($this->once())
  106. ->method('getProviderSet')
  107. ->with($user)
  108. ->willReturn($providerSet);
  109. $provider->expects($this->once())
  110. ->method('getId')
  111. ->willReturn('u2f');
  112. $backupProvider->expects($this->once())
  113. ->method('getId')
  114. ->willReturn('backup_codes');
  115. $this->session->expects($this->once())
  116. ->method('exists')
  117. ->with('two_factor_auth_error')
  118. ->willReturn(true);
  119. $this->session->expects($this->exactly(2))
  120. ->method('remove')
  121. ->with($this->logicalOr($this->equalTo('two_factor_auth_error'), $this->equalTo('two_factor_auth_error_message')));
  122. $provider->expects($this->once())
  123. ->method('getTemplate')
  124. ->with($user)
  125. ->willReturn($tmpl);
  126. $tmpl->expects($this->once())
  127. ->method('fetchPage')
  128. ->willReturn('<html/>');
  129. $expected = new StandaloneTemplateResponse('core', 'twofactorshowchallenge', [
  130. 'error' => true,
  131. 'provider' => $provider,
  132. 'backupProvider' => $backupProvider,
  133. 'logout_url' => 'logoutAttribute',
  134. 'template' => '<html/>',
  135. 'redirect_url' => '/re/dir/ect/url',
  136. 'error_message' => null,
  137. ], 'guest');
  138. $this->assertEquals($expected, $this->controller->showChallenge('myprovider', '/re/dir/ect/url'));
  139. }
  140. public function testShowInvalidChallenge(): void {
  141. $user = $this->createMock(IUser::class);
  142. $providerSet = new ProviderSet([], false);
  143. $this->userSession->expects($this->once())
  144. ->method('getUser')
  145. ->willReturn($user);
  146. $this->twoFactorManager->expects($this->once())
  147. ->method('getProviderSet')
  148. ->with($user)
  149. ->willReturn($providerSet);
  150. $this->urlGenerator->expects($this->once())
  151. ->method('linkToRoute')
  152. ->with('core.TwoFactorChallenge.selectChallenge')
  153. ->willReturn('select/challenge/url');
  154. $expected = new RedirectResponse('select/challenge/url');
  155. $this->assertEquals($expected, $this->controller->showChallenge('myprovider', 'redirect/url'));
  156. }
  157. public function testSolveChallenge(): void {
  158. $user = $this->createMock(IUser::class);
  159. $provider = $this->createMock(IProvider::class);
  160. $this->userSession->expects($this->once())
  161. ->method('getUser')
  162. ->willReturn($user);
  163. $this->twoFactorManager->expects($this->once())
  164. ->method('getProvider')
  165. ->with($user, 'myprovider')
  166. ->willReturn($provider);
  167. $this->twoFactorManager->expects($this->once())
  168. ->method('verifyChallenge')
  169. ->with('myprovider', $user, 'token')
  170. ->willReturn(true);
  171. $this->urlGenerator
  172. ->expects($this->once())
  173. ->method('linkToDefaultPageUrl')
  174. ->willReturn('/default/foo');
  175. $expected = new RedirectResponse('/default/foo');
  176. $this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token'));
  177. }
  178. public function testSolveValidChallengeAndRedirect(): void {
  179. $user = $this->createMock(IUser::class);
  180. $provider = $this->createMock(IProvider::class);
  181. $this->userSession->expects($this->once())
  182. ->method('getUser')
  183. ->willReturn($user);
  184. $this->twoFactorManager->expects($this->once())
  185. ->method('getProvider')
  186. ->with($user, 'myprovider')
  187. ->willReturn($provider);
  188. $this->twoFactorManager->expects($this->once())
  189. ->method('verifyChallenge')
  190. ->with('myprovider', $user, 'token')
  191. ->willReturn(true);
  192. $this->urlGenerator->expects($this->once())
  193. ->method('getAbsoluteURL')
  194. ->with('redirect url')
  195. ->willReturn('redirect/url');
  196. $expected = new RedirectResponse('redirect/url');
  197. $this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token', 'redirect%20url'));
  198. }
  199. public function testSolveChallengeInvalidProvider(): void {
  200. $user = $this->getMockBuilder(IUser::class)->getMock();
  201. $this->userSession->expects($this->once())
  202. ->method('getUser')
  203. ->willReturn($user);
  204. $this->twoFactorManager->expects($this->once())
  205. ->method('getProvider')
  206. ->with($user, 'myprovider')
  207. ->willReturn(null);
  208. $this->urlGenerator->expects($this->once())
  209. ->method('linkToRoute')
  210. ->with('core.TwoFactorChallenge.selectChallenge')
  211. ->willReturn('select/challenge/url');
  212. $expected = new RedirectResponse('select/challenge/url');
  213. $this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token'));
  214. }
  215. public function testSolveInvalidChallenge(): void {
  216. $user = $this->createMock(IUser::class);
  217. $provider = $this->createMock(IProvider::class);
  218. $this->userSession->expects($this->once())
  219. ->method('getUser')
  220. ->willReturn($user);
  221. $this->twoFactorManager->expects($this->once())
  222. ->method('getProvider')
  223. ->with($user, 'myprovider')
  224. ->willReturn($provider);
  225. $this->twoFactorManager->expects($this->once())
  226. ->method('verifyChallenge')
  227. ->with('myprovider', $user, 'token')
  228. ->willReturn(false);
  229. $this->session->expects($this->once())
  230. ->method('set')
  231. ->with('two_factor_auth_error', true);
  232. $this->urlGenerator->expects($this->once())
  233. ->method('linkToRoute')
  234. ->with('core.TwoFactorChallenge.showChallenge', [
  235. 'challengeProviderId' => 'myprovider',
  236. 'redirect_url' => '/url',
  237. ])
  238. ->willReturn('files/index/url');
  239. $provider->expects($this->once())
  240. ->method('getId')
  241. ->willReturn('myprovider');
  242. $expected = new RedirectResponse('files/index/url');
  243. $this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token', '/url'));
  244. }
  245. public function testSolveChallengeTwoFactorException(): void {
  246. $user = $this->createMock(IUser::class);
  247. $provider = $this->createMock(IProvider::class);
  248. $exception = new TwoFactorException('2FA failed');
  249. $this->userSession->expects($this->once())
  250. ->method('getUser')
  251. ->willReturn($user);
  252. $this->twoFactorManager->expects($this->once())
  253. ->method('getProvider')
  254. ->with($user, 'myprovider')
  255. ->willReturn($provider);
  256. $this->twoFactorManager->expects($this->once())
  257. ->method('verifyChallenge')
  258. ->with('myprovider', $user, 'token')
  259. ->will($this->throwException($exception));
  260. $this->session->expects($this->exactly(2))
  261. ->method('set')
  262. ->withConsecutive(
  263. ['two_factor_auth_error_message', '2FA failed'],
  264. ['two_factor_auth_error', true]
  265. );
  266. $this->urlGenerator->expects($this->once())
  267. ->method('linkToRoute')
  268. ->with('core.TwoFactorChallenge.showChallenge', [
  269. 'challengeProviderId' => 'myprovider',
  270. 'redirect_url' => '/url',
  271. ])
  272. ->willReturn('files/index/url');
  273. $provider->expects($this->once())
  274. ->method('getId')
  275. ->willReturn('myprovider');
  276. $expected = new RedirectResponse('files/index/url');
  277. $this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token', '/url'));
  278. }
  279. public function testSetUpProviders(): void {
  280. $user = $this->createMock(IUser::class);
  281. $this->userSession->expects($this->once())
  282. ->method('getUser')
  283. ->willReturn($user);
  284. $provider = $this->createMock(IActivatableAtLogin::class);
  285. $this->twoFactorManager->expects($this->once())
  286. ->method('getLoginSetupProviders')
  287. ->with($user)
  288. ->willReturn([
  289. $provider,
  290. ]);
  291. $expected = new StandaloneTemplateResponse(
  292. 'core',
  293. 'twofactorsetupselection',
  294. [
  295. 'providers' => [
  296. $provider,
  297. ],
  298. 'logout_url' => 'logoutAttribute',
  299. 'redirect_url' => null,
  300. ],
  301. 'guest'
  302. );
  303. $response = $this->controller->setupProviders();
  304. $this->assertEquals($expected, $response);
  305. }
  306. public function testSetUpInvalidProvider(): void {
  307. $user = $this->createMock(IUser::class);
  308. $this->userSession->expects($this->once())
  309. ->method('getUser')
  310. ->willReturn($user);
  311. $provider = $this->createMock(IActivatableAtLogin::class);
  312. $provider->expects($this->any())
  313. ->method('getId')
  314. ->willReturn('prov1');
  315. $this->twoFactorManager->expects($this->once())
  316. ->method('getLoginSetupProviders')
  317. ->with($user)
  318. ->willReturn([
  319. $provider,
  320. ]);
  321. $this->urlGenerator->expects($this->once())
  322. ->method('linkToRoute')
  323. ->with('core.TwoFactorChallenge.selectChallenge')
  324. ->willReturn('2fa/select/page');
  325. $expected = new RedirectResponse('2fa/select/page');
  326. $response = $this->controller->setupProvider('prov2');
  327. $this->assertEquals($expected, $response);
  328. }
  329. public function testSetUpProvider(): void {
  330. $user = $this->createMock(IUser::class);
  331. $this->userSession->expects($this->once())
  332. ->method('getUser')
  333. ->willReturn($user);
  334. $provider = $this->createMock(IActivatableAtLogin::class);
  335. $provider->expects($this->any())
  336. ->method('getId')
  337. ->willReturn('prov1');
  338. $this->twoFactorManager->expects($this->once())
  339. ->method('getLoginSetupProviders')
  340. ->with($user)
  341. ->willReturn([
  342. $provider,
  343. ]);
  344. $loginSetup = $this->createMock(ILoginSetupProvider::class);
  345. $provider->expects($this->any())
  346. ->method('getLoginSetup')
  347. ->with($user)
  348. ->willReturn($loginSetup);
  349. $tmpl = $this->createMock(Template::class);
  350. $loginSetup->expects($this->once())
  351. ->method('getBody')
  352. ->willReturn($tmpl);
  353. $tmpl->expects($this->once())
  354. ->method('fetchPage')
  355. ->willReturn('tmpl');
  356. $expected = new StandaloneTemplateResponse(
  357. 'core',
  358. 'twofactorsetupchallenge',
  359. [
  360. 'provider' => $provider,
  361. 'logout_url' => 'logoutAttribute',
  362. 'template' => 'tmpl',
  363. 'redirect_url' => null,
  364. ],
  365. 'guest'
  366. );
  367. $response = $this->controller->setupProvider('prov1');
  368. $this->assertEquals($expected, $response);
  369. }
  370. public function testConfirmProviderSetup(): void {
  371. $this->urlGenerator->expects($this->once())
  372. ->method('linkToRoute')
  373. ->with(
  374. 'core.TwoFactorChallenge.showChallenge',
  375. [
  376. 'challengeProviderId' => 'totp',
  377. 'redirect_url' => null,
  378. ])
  379. ->willReturn('2fa/select/page');
  380. $expected = new RedirectResponse('2fa/select/page');
  381. $response = $this->controller->confirmProviderSetup('totp');
  382. $this->assertEquals($expected, $response);
  383. }
  384. }