LoginControllerTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. <?php
  2. /**
  3. * @author Lukas Reschke <lukas@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 Tests\Core\Controller;
  22. use OC\Authentication\Login\Chain as LoginChain;
  23. use OC\Authentication\Login\LoginData;
  24. use OC\Authentication\Login\LoginResult;
  25. use OC\Authentication\TwoFactorAuth\Manager;
  26. use OC\Core\Controller\LoginController;
  27. use OC\Security\Bruteforce\Throttler;
  28. use OC\User\Session;
  29. use OCP\AppFramework\Http\RedirectResponse;
  30. use OCP\AppFramework\Http\TemplateResponse;
  31. use OCP\Defaults;
  32. use OCP\IConfig;
  33. use OCP\IInitialStateService;
  34. use OCP\ILogger;
  35. use OCP\IRequest;
  36. use OCP\ISession;
  37. use OCP\IURLGenerator;
  38. use OCP\IUser;
  39. use OCP\IUserManager;
  40. use PHPUnit\Framework\MockObject\MockObject;
  41. use Test\TestCase;
  42. class LoginControllerTest extends TestCase {
  43. /** @var LoginController */
  44. private $loginController;
  45. /** @var IRequest|MockObject */
  46. private $request;
  47. /** @var IUserManager|MockObject */
  48. private $userManager;
  49. /** @var IConfig|MockObject */
  50. private $config;
  51. /** @var ISession|MockObject */
  52. private $session;
  53. /** @var Session|MockObject */
  54. private $userSession;
  55. /** @var IURLGenerator|MockObject */
  56. private $urlGenerator;
  57. /** @var ILogger|MockObject */
  58. private $logger;
  59. /** @var Manager|MockObject */
  60. private $twoFactorManager;
  61. /** @var Defaults|MockObject */
  62. private $defaults;
  63. /** @var Throttler|MockObject */
  64. private $throttler;
  65. /** @var LoginChain|MockObject */
  66. private $chain;
  67. /** @var IInitialStateService|MockObject */
  68. private $initialStateService;
  69. public function setUp() {
  70. parent::setUp();
  71. $this->request = $this->createMock(IRequest::class);
  72. $this->userManager = $this->createMock(\OC\User\Manager::class);
  73. $this->config = $this->createMock(IConfig::class);
  74. $this->session = $this->createMock(ISession::class);
  75. $this->userSession = $this->createMock(Session::class);
  76. $this->urlGenerator = $this->createMock(IURLGenerator::class);
  77. $this->logger = $this->createMock(ILogger::class);
  78. $this->twoFactorManager = $this->createMock(Manager::class);
  79. $this->defaults = $this->createMock(Defaults::class);
  80. $this->throttler = $this->createMock(Throttler::class);
  81. $this->chain = $this->createMock(LoginChain::class);
  82. $this->initialStateService = $this->createMock(IInitialStateService::class);
  83. $this->request->method('getRemoteAddress')
  84. ->willReturn('1.2.3.4');
  85. $this->throttler->method('getDelay')
  86. ->with(
  87. $this->equalTo('1.2.3.4'),
  88. $this->equalTo('')
  89. )->willReturn(1000);
  90. $this->loginController = new LoginController(
  91. 'core',
  92. $this->request,
  93. $this->userManager,
  94. $this->config,
  95. $this->session,
  96. $this->userSession,
  97. $this->urlGenerator,
  98. $this->logger,
  99. $this->defaults,
  100. $this->throttler,
  101. $this->chain,
  102. $this->initialStateService
  103. );
  104. }
  105. public function testLogoutWithoutToken() {
  106. $this->request
  107. ->expects($this->once())
  108. ->method('getCookie')
  109. ->with('nc_token')
  110. ->willReturn(null);
  111. $this->config
  112. ->expects($this->never())
  113. ->method('deleteUserValue');
  114. $this->urlGenerator
  115. ->expects($this->once())
  116. ->method('linkToRouteAbsolute')
  117. ->with('core.login.showLoginForm')
  118. ->willReturn('/login');
  119. $expected = new RedirectResponse('/login');
  120. $expected->addHeader('Clear-Site-Data', '"cache", "storage"');
  121. $this->assertEquals($expected, $this->loginController->logout());
  122. }
  123. public function testLogoutWithToken() {
  124. $this->request
  125. ->expects($this->once())
  126. ->method('getCookie')
  127. ->with('nc_token')
  128. ->willReturn('MyLoginToken');
  129. $user = $this->createMock(IUser::class);
  130. $user
  131. ->expects($this->once())
  132. ->method('getUID')
  133. ->willReturn('JohnDoe');
  134. $this->userSession
  135. ->expects($this->once())
  136. ->method('getUser')
  137. ->willReturn($user);
  138. $this->config
  139. ->expects($this->once())
  140. ->method('deleteUserValue')
  141. ->with('JohnDoe', 'login_token', 'MyLoginToken');
  142. $this->urlGenerator
  143. ->expects($this->once())
  144. ->method('linkToRouteAbsolute')
  145. ->with('core.login.showLoginForm')
  146. ->willReturn('/login');
  147. $expected = new RedirectResponse('/login');
  148. $expected->addHeader('Clear-Site-Data', '"cache", "storage"');
  149. $this->assertEquals($expected, $this->loginController->logout());
  150. }
  151. public function testShowLoginFormForLoggedInUsers() {
  152. $this->userSession
  153. ->expects($this->once())
  154. ->method('isLoggedIn')
  155. ->willReturn(true);
  156. $expectedResponse = new RedirectResponse(\OC_Util::getDefaultPageUrl());
  157. $this->assertEquals($expectedResponse, $this->loginController->showLoginForm('', '', ''));
  158. }
  159. public function testShowLoginFormWithErrorsInSession() {
  160. $this->userSession
  161. ->expects($this->once())
  162. ->method('isLoggedIn')
  163. ->willReturn(false);
  164. $this->session
  165. ->expects($this->once())
  166. ->method('get')
  167. ->with('loginMessages')
  168. ->willReturn(
  169. [
  170. [
  171. 'ErrorArray1',
  172. 'ErrorArray2',
  173. ],
  174. [
  175. 'MessageArray1',
  176. 'MessageArray2',
  177. ],
  178. ]
  179. );
  180. $this->initialStateService->expects($this->at(0))
  181. ->method('provideInitialState')
  182. ->with(
  183. 'core',
  184. 'loginMessages',
  185. [
  186. 'MessageArray1',
  187. 'MessageArray2',
  188. ]
  189. );
  190. $this->initialStateService->expects($this->at(1))
  191. ->method('provideInitialState')
  192. ->with(
  193. 'core',
  194. 'loginErrors',
  195. [
  196. 'ErrorArray1',
  197. 'ErrorArray2',
  198. ]
  199. );
  200. $expectedResponse = new TemplateResponse(
  201. 'core',
  202. 'login',
  203. [
  204. 'alt_login' => [],
  205. ],
  206. 'guest'
  207. );
  208. $this->assertEquals($expectedResponse, $this->loginController->showLoginForm('', '', ''));
  209. }
  210. public function testShowLoginFormForFlowAuth() {
  211. $this->userSession
  212. ->expects($this->once())
  213. ->method('isLoggedIn')
  214. ->willReturn(false);
  215. $this->initialStateService->expects($this->at(2))
  216. ->method('provideInitialState')
  217. ->with(
  218. 'core',
  219. 'loginRedirectUrl',
  220. 'login/flow'
  221. );
  222. $expectedResponse = new TemplateResponse(
  223. 'core',
  224. 'login',
  225. [
  226. 'alt_login' => [],
  227. ],
  228. 'guest'
  229. );
  230. $this->assertEquals($expectedResponse, $this->loginController->showLoginForm('', 'login/flow', ''));
  231. }
  232. /**
  233. * @return array
  234. */
  235. public function passwordResetDataProvider() {
  236. return [
  237. [
  238. true,
  239. true,
  240. ],
  241. [
  242. false,
  243. false,
  244. ],
  245. ];
  246. }
  247. /**
  248. * @dataProvider passwordResetDataProvider
  249. */
  250. public function testShowLoginFormWithPasswordResetOption($canChangePassword,
  251. $expectedResult) {
  252. $this->userSession
  253. ->expects($this->once())
  254. ->method('isLoggedIn')
  255. ->willReturn(false);
  256. $this->config
  257. ->expects($this->exactly(2))
  258. ->method('getSystemValue')
  259. ->will($this->returnValueMap([
  260. ['login_form_autocomplete', true, true],
  261. ['lost_password_link', '', false],
  262. ]));
  263. $user = $this->createMock(IUser::class);
  264. $user
  265. ->expects($this->once())
  266. ->method('canChangePassword')
  267. ->willReturn($canChangePassword);
  268. $this->userManager
  269. ->expects($this->once())
  270. ->method('get')
  271. ->with('LdapUser')
  272. ->willReturn($user);
  273. $this->initialStateService->expects($this->at(0))
  274. ->method('provideInitialState')
  275. ->with(
  276. 'core',
  277. 'loginUsername',
  278. 'LdapUser'
  279. );
  280. $this->initialStateService->expects($this->at(4))
  281. ->method('provideInitialState')
  282. ->with(
  283. 'core',
  284. 'loginCanResetPassword',
  285. $expectedResult
  286. );
  287. $expectedResponse = new TemplateResponse(
  288. 'core',
  289. 'login',
  290. [
  291. 'alt_login' => [],
  292. ],
  293. 'guest'
  294. );
  295. $this->assertEquals($expectedResponse, $this->loginController->showLoginForm('LdapUser', '', ''));
  296. }
  297. public function testShowLoginFormForUserNamed0() {
  298. $this->userSession
  299. ->expects($this->once())
  300. ->method('isLoggedIn')
  301. ->willReturn(false);
  302. $this->config
  303. ->expects($this->exactly(2))
  304. ->method('getSystemValue')
  305. ->will($this->returnValueMap([
  306. ['login_form_autocomplete', true, true],
  307. ['lost_password_link', '', false],
  308. ]));
  309. $user = $this->createMock(IUser::class);
  310. $user->expects($this->once())
  311. ->method('canChangePassword')
  312. ->willReturn(false);
  313. $this->userManager
  314. ->expects($this->once())
  315. ->method('get')
  316. ->with('0')
  317. ->willReturn($user);
  318. $this->initialStateService->expects($this->at(1))
  319. ->method('provideInitialState')
  320. ->with(
  321. 'core',
  322. 'loginAutocomplete',
  323. true
  324. );
  325. $this->initialStateService->expects($this->at(3))
  326. ->method('provideInitialState')
  327. ->with(
  328. 'core',
  329. 'loginResetPasswordLink',
  330. false
  331. );
  332. $this->initialStateService->expects($this->at(4))
  333. ->method('provideInitialState')
  334. ->with(
  335. 'core',
  336. 'loginCanResetPassword',
  337. false
  338. );
  339. $expectedResponse = new TemplateResponse(
  340. 'core',
  341. 'login',
  342. [
  343. 'alt_login' => [],
  344. ],
  345. 'guest'
  346. );
  347. $this->assertEquals($expectedResponse, $this->loginController->showLoginForm('0', '', ''));
  348. }
  349. public function testLoginWithInvalidCredentials() {
  350. $user = 'MyUserName';
  351. $password = 'secret';
  352. $loginPageUrl = '/login?redirect_url=/apps/files';
  353. $this->request
  354. ->expects($this->once())
  355. ->method('passesCSRFCheck')
  356. ->willReturn(true);
  357. $loginData = new LoginData(
  358. $this->request,
  359. $user,
  360. $password,
  361. '/apps/files'
  362. );
  363. $loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
  364. $this->chain->expects($this->once())
  365. ->method('process')
  366. ->with($this->equalTo($loginData))
  367. ->willReturn($loginResult);
  368. $this->urlGenerator->expects($this->once())
  369. ->method('linkToRoute')
  370. ->with('core.login.showLoginForm', [
  371. 'user' => $user,
  372. 'redirect_url' => '/apps/files',
  373. ])
  374. ->will($this->returnValue($loginPageUrl));
  375. $expected = new \OCP\AppFramework\Http\RedirectResponse($loginPageUrl);
  376. $expected->throttle(['user' => 'MyUserName']);
  377. $response = $this->loginController->tryLogin($user, $password, '/apps/files');
  378. $this->assertEquals($expected, $response);
  379. }
  380. public function testLoginWithValidCredentials() {
  381. $user = 'MyUserName';
  382. $password = 'secret';
  383. $indexPageUrl = \OC_Util::getDefaultPageUrl();
  384. $this->request
  385. ->expects($this->once())
  386. ->method('passesCSRFCheck')
  387. ->willReturn(true);
  388. $loginData = new LoginData(
  389. $this->request,
  390. $user,
  391. $password
  392. );
  393. $loginResult = LoginResult::success($loginData);
  394. $this->chain->expects($this->once())
  395. ->method('process')
  396. ->with($this->equalTo($loginData))
  397. ->willReturn($loginResult);
  398. $expected = new \OCP\AppFramework\Http\RedirectResponse($indexPageUrl);
  399. $response = $this->loginController->tryLogin($user, $password);
  400. $this->assertEquals($expected, $response);
  401. }
  402. public function testLoginWithoutPassedCsrfCheckAndNotLoggedIn() {
  403. /** @var IUser|MockObject $user */
  404. $user = $this->createMock(IUser::class);
  405. $user->expects($this->any())
  406. ->method('getUID')
  407. ->will($this->returnValue('jane'));
  408. $password = 'secret';
  409. $originalUrl = 'another%20url';
  410. $this->request
  411. ->expects($this->once())
  412. ->method('passesCSRFCheck')
  413. ->willReturn(false);
  414. $this->userSession->expects($this->once())
  415. ->method('isLoggedIn')
  416. ->with()
  417. ->will($this->returnValue(false));
  418. $this->config->expects($this->never())
  419. ->method('deleteUserValue');
  420. $this->userSession->expects($this->never())
  421. ->method('createRememberMeToken');
  422. $expected = new \OCP\AppFramework\Http\RedirectResponse(\OC_Util::getDefaultPageUrl());
  423. $this->assertEquals($expected, $this->loginController->tryLogin('Jane', $password, $originalUrl));
  424. }
  425. public function testLoginWithoutPassedCsrfCheckAndLoggedIn() {
  426. /** @var IUser|MockObject $user */
  427. $user = $this->createMock(IUser::class);
  428. $user->expects($this->any())
  429. ->method('getUID')
  430. ->will($this->returnValue('jane'));
  431. $password = 'secret';
  432. $originalUrl = 'another%20url';
  433. $redirectUrl = 'http://localhost/another url';
  434. $this->request
  435. ->expects($this->once())
  436. ->method('passesCSRFCheck')
  437. ->willReturn(false);
  438. $this->userSession->expects($this->once())
  439. ->method('isLoggedIn')
  440. ->with()
  441. ->will($this->returnValue(true));
  442. $this->urlGenerator->expects($this->once())
  443. ->method('getAbsoluteURL')
  444. ->with(urldecode($originalUrl))
  445. ->will($this->returnValue($redirectUrl));
  446. $this->config->expects($this->never())
  447. ->method('deleteUserValue');
  448. $this->userSession->expects($this->never())
  449. ->method('createRememberMeToken');
  450. $this->config
  451. ->method('getSystemValue')
  452. ->with('remember_login_cookie_lifetime')
  453. ->willReturn(1234);
  454. $expected = new \OCP\AppFramework\Http\RedirectResponse($redirectUrl);
  455. $this->assertEquals($expected, $this->loginController->tryLogin('Jane', $password, $originalUrl));
  456. }
  457. public function testLoginWithValidCredentialsAndRedirectUrl() {
  458. $user = 'MyUserName';
  459. $password = 'secret';
  460. $indexPageUrl = \OC_Util::getDefaultPageUrl();
  461. $redirectUrl = 'https://next.cloud/apps/mail';
  462. $this->request
  463. ->expects($this->once())
  464. ->method('passesCSRFCheck')
  465. ->willReturn(true);
  466. $loginData = new LoginData(
  467. $this->request,
  468. $user,
  469. $password,
  470. '%2Fapps%2Fmail'
  471. );
  472. $loginResult = LoginResult::success($loginData);
  473. $this->chain->expects($this->once())
  474. ->method('process')
  475. ->with($this->equalTo($loginData))
  476. ->willReturn($loginResult);
  477. $this->userSession->expects($this->once())
  478. ->method('isLoggedIn')
  479. ->willReturn(true);
  480. $this->urlGenerator->expects($this->once())
  481. ->method('getAbsoluteURL')
  482. ->with(urldecode('/apps/mail'))
  483. ->will($this->returnValue($redirectUrl));
  484. $expected = new \OCP\AppFramework\Http\RedirectResponse($redirectUrl);
  485. $response = $this->loginController->tryLogin($user, $password, '%2Fapps%2Fmail');
  486. $this->assertEquals($expected, $response);
  487. }
  488. public function testToNotLeakLoginName() {
  489. $this->request
  490. ->expects($this->once())
  491. ->method('passesCSRFCheck')
  492. ->willReturn(true);
  493. $loginPageUrl = '/login?redirect_url=/apps/files';
  494. $loginData = new LoginData(
  495. $this->request,
  496. 'john@doe.com',
  497. 'just wrong',
  498. '/apps/files'
  499. );
  500. $loginResult = LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
  501. $this->chain->expects($this->once())
  502. ->method('process')
  503. ->with($this->equalTo($loginData))
  504. ->willReturnCallback(function(LoginData $data) use ($loginResult) {
  505. $data->setUsername('john');
  506. return $loginResult;
  507. });
  508. $this->urlGenerator->expects($this->once())
  509. ->method('linkToRoute')
  510. ->with('core.login.showLoginForm', [
  511. 'user' => 'john@doe.com',
  512. 'redirect_url' => '/apps/files',
  513. ])
  514. ->will($this->returnValue($loginPageUrl));
  515. $expected = new \OCP\AppFramework\Http\RedirectResponse($loginPageUrl);
  516. $expected->throttle(['user' => 'john']);
  517. $response = $this->loginController->tryLogin(
  518. 'john@doe.com',
  519. 'just wrong',
  520. '/apps/files'
  521. );
  522. $this->assertEquals($expected, $response);
  523. }
  524. }