TwoFactorChallengeControllerTest.php 14 KB

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