TwoFactorChallengeControllerTest.php 14 KB

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