3 declare(strict_types
=1);
5 namespace Shaarli\Front\Controller\Visitor
;
7 use Shaarli\Config\ConfigManager
;
8 use Shaarli\Front\Exception\LoginBannedException
;
9 use Shaarli\Front\Exception\WrongTokenException
;
10 use Shaarli\Render\TemplatePage
;
11 use Shaarli\Security\CookieManager
;
12 use Shaarli\Security\SessionManager
;
14 use Slim\Http\Request
;
15 use Slim\Http\Response
;
17 class LoginControllerTest
extends TestCase
19 use FrontControllerMockHelper
;
21 /** @var LoginController */
22 protected $controller;
24 public function setUp(): void
26 $this->createContainer();
28 $this->container
->cookieManager
= $this->createMock(CookieManager
::class);
29 $this->container
->sessionManager
->method('checkToken')->willReturn(true);
31 $this->controller
= new LoginController($this->container
);
35 * Test displaying login form with valid parameters.
37 public function testValidControllerInvoke(): void
39 $request = $this->createMock(Request
::class);
41 ->expects(static::atLeastOnce())
43 ->willReturnCallback(function (string $key) {
44 return 'returnurl' === $key ? '> referer' : null;
47 $response = new Response();
49 $assignedVariables = [];
50 $this->container
->pageBuilder
52 ->willReturnCallback(function ($key, $value) use (&$assignedVariables) {
53 $assignedVariables[$key] = $value;
59 $this->container
->loginManager
->method('canLogin')->willReturn(true);
61 $result = $this->controller
->index($request, $response);
63 static::assertInstanceOf(Response
::class, $result);
64 static::assertSame(200, $result->getStatusCode());
65 static::assertSame(TemplatePage
::LOGIN
, (string) $result->getBody());
67 static::assertSame('> referer', $assignedVariables['returnurl']);
68 static::assertSame(true, $assignedVariables['remember_user_default']);
69 static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']);
73 * Test displaying login form with username defined in the request.
75 public function testValidControllerInvokeWithUserName(): void
77 $this->container
->environment
= ['HTTP_REFERER' => '> referer'];
79 $request = $this->createMock(Request
::class);
81 ->expects(static::atLeastOnce())
83 ->willReturnCallback(function (string $key, $default) {
84 if ('login' === $key) {
91 $response = new Response();
93 $assignedVariables = [];
94 $this->container
->pageBuilder
96 ->willReturnCallback(function ($key, $value) use (&$assignedVariables) {
97 $assignedVariables[$key] = $value;
103 $this->container
->loginManager
->expects(static::once())->method('canLogin')->willReturn(true);
105 $result = $this->controller
->index($request, $response);
107 static::assertInstanceOf(Response
::class, $result);
108 static::assertSame(200, $result->getStatusCode());
109 static::assertSame('loginform', (string) $result->getBody());
111 static::assertSame('myUser>', $assignedVariables['username']);
112 static::assertSame('> referer', $assignedVariables['returnurl']);
113 static::assertSame(true, $assignedVariables['remember_user_default']);
114 static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']);
118 * Test displaying login page while being logged in.
120 public function testLoginControllerWhileLoggedIn(): void
122 $request = $this->createMock(Request
::class);
123 $response = new Response();
125 $this->container
->loginManager
->expects(static::once())->method('isLoggedIn')->willReturn(true);
127 $result = $this->controller
->index($request, $response);
129 static::assertInstanceOf(Response
::class, $result);
130 static::assertSame(302, $result->getStatusCode());
131 static::assertSame(['/subfolder/'], $result->getHeader('Location'));
135 * Test displaying login page with open shaarli configured: redirect to homepage.
137 public function testLoginControllerOpenShaarli(): void
139 $request = $this->createMock(Request
::class);
140 $response = new Response();
142 $conf = $this->createMock(ConfigManager
::class);
143 $conf->method('get')->willReturnCallback(function (string $parameter, $default) {
144 if ($parameter === 'security.open_shaarli') {
149 $this->container
->conf
= $conf;
151 $result = $this->controller
->index($request, $response);
153 static::assertInstanceOf(Response
::class, $result);
154 static::assertSame(302, $result->getStatusCode());
155 static::assertSame(['/subfolder/'], $result->getHeader('Location'));
159 * Test displaying login page while being banned.
161 public function testLoginControllerWhileBanned(): void
163 $request = $this->createMock(Request
::class);
164 $response = new Response();
166 $this->container
->loginManager
->method('isLoggedIn')->willReturn(false);
167 $this->container
->loginManager
->method('canLogin')->willReturn(false);
169 $this->expectException(LoginBannedException
::class);
171 $this->controller
->index($request, $response);
175 * Test processing login with valid parameters.
177 public function testProcessLoginWithValidParameters(): void
181 'password' => 'pass',
183 $request = $this->createMock(Request
::class);
185 ->expects(static::atLeastOnce())
187 ->willReturnCallback(function (string $key) use ($parameters) {
188 return $parameters[$key] ?? null;
191 $response = new Response();
193 $this->container
->loginManager
->method('canLogin')->willReturn(true);
194 $this->container
->loginManager
->expects(static::once())->method('handleSuccessfulLogin');
195 $this->container
->loginManager
196 ->expects(static::once())
197 ->method('checkCredentials')
198 ->with('1.2.3.4', 'bob', 'pass')
201 $this->container
->loginManager
->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8)));
203 $this->container
->sessionManager
->expects(static::never())->method('extendSession');
204 $this->container
->sessionManager
->expects(static::once())->method('destroy');
205 $this->container
->sessionManager
206 ->expects(static::once())
207 ->method('cookieParameters')
208 ->with(0, '/subfolder/', 'shaarli')
210 $this->container
->sessionManager
->expects(static::once())->method('start');
211 $this->container
->sessionManager
->expects(static::once())->method('regenerateId')->with(true);
213 $result = $this->controller
->login($request, $response);
215 static::assertSame(302, $result->getStatusCode());
216 static::assertSame('/subfolder/', $result->getHeader('location')[0]);
220 * Test processing login with return URL.
222 public function testProcessLoginWithReturnUrl(): void
225 'returnurl' => 'http://shaarli/subfolder/admin/shaare',
227 $request = $this->createMock(Request
::class);
229 ->expects(static::atLeastOnce())
231 ->willReturnCallback(function (string $key) use ($parameters) {
232 return $parameters[$key] ?? null;
235 $response = new Response();
237 $this->container
->loginManager
->method('canLogin')->willReturn(true);
238 $this->container
->loginManager
->expects(static::once())->method('handleSuccessfulLogin');
239 $this->container
->loginManager
->expects(static::once())->method('checkCredentials')->willReturn(true);
240 $this->container
->loginManager
->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8)));
242 $result = $this->controller
->login($request, $response);
244 static::assertSame(302, $result->getStatusCode());
245 static::assertSame('/subfolder/admin/shaare', $result->getHeader('location')[0]);
249 * Test processing login with remember me session enabled.
251 public function testProcessLoginLongLastingSession(): void
254 'longlastingsession' => true,
256 $request = $this->createMock(Request
::class);
258 ->expects(static::atLeastOnce())
260 ->willReturnCallback(function (string $key) use ($parameters) {
261 return $parameters[$key] ?? null;
264 $response = new Response();
266 $this->container
->loginManager
->method('canLogin')->willReturn(true);
267 $this->container
->loginManager
->expects(static::once())->method('handleSuccessfulLogin');
268 $this->container
->loginManager
->expects(static::once())->method('checkCredentials')->willReturn(true);
269 $this->container
->loginManager
->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8)));
271 $this->container
->sessionManager
->expects(static::once())->method('destroy');
272 $this->container
->sessionManager
273 ->expects(static::once())
274 ->method('cookieParameters')
275 ->with(42, '/subfolder/', 'shaarli')
277 $this->container
->sessionManager
->expects(static::once())->method('start');
278 $this->container
->sessionManager
->expects(static::once())->method('regenerateId')->with(true);
279 $this->container
->sessionManager
->expects(static::once())->method('extendSession')->willReturn(42);
281 $this->container
->cookieManager
= $this->createMock(CookieManager
::class);
282 $this->container
->cookieManager
283 ->expects(static::once())
284 ->method('setCookieParameter')
285 ->willReturnCallback(function (string $name): CookieManager
{
286 static::assertSame(CookieManager
::STAY_SIGNED_IN
, $name);
288 return $this->container
->cookieManager
;
292 $result = $this->controller
->login($request, $response);
294 static::assertSame(302, $result->getStatusCode());
295 static::assertSame('/subfolder/', $result->getHeader('location')[0]);
299 * Test processing login with invalid credentials
301 public function testProcessLoginWrongCredentials(): void
304 'returnurl' => 'http://shaarli/subfolder/admin/shaare',
306 $request = $this->createMock(Request
::class);
308 ->expects(static::atLeastOnce())
310 ->willReturnCallback(function (string $key) use ($parameters) {
311 return $parameters[$key] ?? null;
314 $response = new Response();
316 $this->container
->loginManager
->method('canLogin')->willReturn(true);
317 $this->container
->loginManager
->expects(static::once())->method('handleFailedLogin');
318 $this->container
->loginManager
->expects(static::once())->method('checkCredentials')->willReturn(false);
320 $this->container
->sessionManager
321 ->expects(static::once())
322 ->method('setSessionParameter')
323 ->with(SessionManager
::KEY_ERROR_MESSAGES
, ['Wrong login/password.'])
326 $result = $this->controller
->login($request, $response);
328 static::assertSame(200, $result->getStatusCode());
329 static::assertSame(TemplatePage
::LOGIN
, (string) $result->getBody());
333 * Test processing login with wrong token
335 public function testProcessLoginWrongToken(): void
337 $request = $this->createMock(Request
::class);
338 $response = new Response();
340 $this->container
->sessionManager
= $this->createMock(SessionManager
::class);
341 $this->container
->sessionManager
->method('checkToken')->willReturn(false);
343 $this->expectException(WrongTokenException
::class);
345 $this->controller
->login($request, $response);
349 * Test processing login with wrong token
351 public function testProcessLoginAlreadyLoggedIn(): void
353 $request = $this->createMock(Request
::class);
354 $response = new Response();
356 $this->container
->loginManager
->method('isLoggedIn')->willReturn(true);
357 $this->container
->loginManager
->expects(static::never())->method('handleSuccessfulLogin');
358 $this->container
->loginManager
->expects(static::never())->method('handleFailedLogin');
360 $result = $this->controller
->login($request, $response);
362 static::assertSame(302, $result->getStatusCode());
363 static::assertSame('/subfolder/', $result->getHeader('location')[0]);
367 * Test processing login with wrong token
369 public function testProcessLoginInOpenShaarli(): void
371 $request = $this->createMock(Request
::class);
372 $response = new Response();
374 $this->container
->conf
= $this->createMock(ConfigManager
::class);
375 $this->container
->conf
->method('get')->willReturnCallback(function (string $key, $value) {
376 return 'security.open_shaarli' === $key ? true : $value;
379 $this->container
->loginManager
->expects(static::never())->method('handleSuccessfulLogin');
380 $this->container
->loginManager
->expects(static::never())->method('handleFailedLogin');
382 $result = $this->controller
->login($request, $response);
384 static::assertSame(302, $result->getStatusCode());
385 static::assertSame('/subfolder/', $result->getHeader('location')[0]);
389 * Test processing login while being banned
391 public function testProcessLoginWhileBanned(): void
393 $request = $this->createMock(Request
::class);
394 $response = new Response();
396 $this->container
->loginManager
->method('canLogin')->willReturn(false);
397 $this->container
->loginManager
->expects(static::never())->method('handleSuccessfulLogin');
398 $this->container
->loginManager
->expects(static::never())->method('handleFailedLogin');
400 $this->expectException(LoginBannedException
::class);
402 $this->controller
->login($request, $response);