Skip to content

Commit

Permalink
Redirect to the last page visited if a back end session expires.
Browse files Browse the repository at this point in the history
  • Loading branch information
leofeyer committed Jan 5, 2018
1 parent e40e597 commit e4f0d29
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### DEV

* Redirect to the last page visited if a back end session expires.
* Replace the session data in the authentication success handler (see #1288).

### 4.5.1 (2018-01-04)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -122,6 +122,7 @@ security:
security: false

contao_backend:
entry_point: contao.security.entry_point
request_matcher: contao.routing.backend_matcher
provider: contao.security.backend_user_provider
user_checker: contao.security.user_checker
Expand All @@ -133,7 +134,6 @@ security:
login_path: contao_backend_login
check_path: contao_backend_login
default_target_path: contao_backend
always_use_default_target_path: true
success_handler: contao.security.authentication_success_handler
failure_handler: contao.security.authentication_failure_handler
remember_me: false
Expand Down
12 changes: 9 additions & 3 deletions src/Resources/config/services.yml
Expand Up @@ -374,6 +374,12 @@ services:
- "Contao\\BackendUser"
- "@logger"

contao.security.entry_point:
class: Contao\CoreBundle\Security\Authentication\AuthenticationEntryPoint
arguments:
- "@security.http_utils"
- "@router"

contao.security.frontend_preview_authenticator:
class: Contao\CoreBundle\Security\Authentication\FrontendPreviewAuthenticator
arguments:
Expand All @@ -395,15 +401,15 @@ services:
arguments:
- "@security.http_utils"

contao.security.sha1_password_encoder:
class: Contao\CoreBundle\Security\Encoder\Sha1PasswordEncoder

contao.security.logout_handler:
class: Contao\CoreBundle\Security\Logout\LogoutHandler
arguments:
- "@contao.framework"
- "@logger"

contao.security.sha1_password_encoder:
class: Contao\CoreBundle\Security\Encoder\Sha1PasswordEncoder

contao.security.token_checker:
class: Contao\CoreBundle\Security\Authentication\Token\TokenChecker
arguments:
Expand Down
9 changes: 9 additions & 0 deletions src/Resources/contao/controllers/BackendIndex.php
Expand Up @@ -60,6 +60,13 @@ public function run()
\Message::addError($GLOBALS['TL_LANG']['ERR']['invalidLogin']);
}

$targetPath = '/contao';

if ($referer = \Input::get('referer', true))
{
$targetPath = base64_decode($referer);
}

/** @var BackendTemplate|object $objTemplate */
$objTemplate = new \BackendTemplate('be_login');

Expand All @@ -81,6 +88,8 @@ public function run()
$objTemplate->feLink = $GLOBALS['TL_LANG']['MSC']['feLink'];
$objTemplate->default = $GLOBALS['TL_LANG']['MSC']['default'];
$objTemplate->jsDisabled = $GLOBALS['TL_LANG']['MSC']['jsDisabled'];
$objTemplate->targetPath = \Environment::get('base') . ltrim($targetPath, '/');
$objTemplate->failurePath = \Environment::get('base') . \Environment::get('request');

return $objTemplate->getResponse();
}
Expand Down
3 changes: 3 additions & 0 deletions src/Resources/contao/templates/backend/be_login.html5
Expand Up @@ -39,6 +39,9 @@
<div class="formbody">
<input type="hidden" name="FORM_SUBMIT" value="tl_login">
<input type="hidden" name="REQUEST_TOKEN" value="<?= REQUEST_TOKEN ?>">
<input type="hidden" name="_target_path" value="<?= StringUtil::specialchars($this->targetPath) ?>">
<input type="hidden" name="_failure_path" value="<?= StringUtil::specialchars($this->failurePath) ?>">
<input type="hidden" name="_always_use_target_path" value="1">
<h1><?= $this->headline ?></h1>
<div class="widget">
<label for="username"><?= $this->username ?></label>
Expand Down
2 changes: 1 addition & 1 deletion src/Resources/contao/templates/modules/mod_login.html5
Expand Up @@ -17,8 +17,8 @@
<?php if ($this->logout): ?>
<p class="login_info"><?= $this->loggedInAs ?><br><?= $this->lastLogin ?></p>
<?php else: ?>
<input type="hidden" name="_always_use_target_path" value="<?= $this->forceTargetPath ?>">
<input type="hidden" name="_failure_path" value="<?= $this->failurePath ?>">
<input type="hidden" name="_always_use_target_path" value="<?= $this->forceTargetPath ?>">
<div class="widget widget-text">
<label for="username"><?= $this->username ?></label>
<input type="text" name="username" id="username" class="text" value="<?= $this->value ?>" required><br>
Expand Down
61 changes: 61 additions & 0 deletions src/Security/Authentication/AuthenticationEntryPoint.php
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

/*
* This file is part of Contao.
*
* Copyright (c) 2005-2017 Leo Feyer
*
* @license LGPL-3.0+
*/

namespace Contao\CoreBundle\Security\Authentication;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\HttpUtils;

class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
{
/**
* @var HttpUtils
*/
private $httpUtils;

/**
* @var RouterInterface
*/
private $router;

/**
* @param HttpUtils $httpUtils
* @param RouterInterface $router
*/
public function __construct(HttpUtils $httpUtils, RouterInterface $router)
{
$this->httpUtils = $httpUtils;
$this->router = $router;
}

/**
* {@inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
if ($request->query->count() < 1) {
return $this->httpUtils->createRedirectResponse($request, 'contao_backend_login');
}

$url = $this->router->generate(
'contao_backend_login',
['referer' => base64_encode($request->getRequestUri())],
UrlGeneratorInterface::ABSOLUTE_URL
);

return $this->httpUtils->createRedirectResponse($request, $url);
}
}
49 changes: 30 additions & 19 deletions tests/DependencyInjection/ContaoCoreExtensionTest.php
Expand Up @@ -74,6 +74,7 @@
use Contao\CoreBundle\Routing\FrontendLoader;
use Contao\CoreBundle\Routing\ScopeMatcher;
use Contao\CoreBundle\Routing\UrlGenerator;
use Contao\CoreBundle\Security\Authentication\AuthenticationEntryPoint;
use Contao\CoreBundle\Security\Authentication\AuthenticationFailureHandler;
use Contao\CoreBundle\Security\Authentication\AuthenticationSuccessHandler;
use Contao\CoreBundle\Security\Authentication\FrontendPreviewAuthenticator;
Expand Down Expand Up @@ -490,10 +491,9 @@ public function testRegistersTheStoreRefererListener(): void

$this->assertSame(StoreRefererListener::class, $definition->getClass());
$this->assertTrue($definition->isPrivate());
$this->assertSame('session', (string) $definition->getArgument(0));
$this->assertSame('security.token_storage', (string) $definition->getArgument(1));
$this->assertSame('security.authentication.trust_resolver', (string) $definition->getArgument(2));
$this->assertSame('contao.routing.scope_matcher', (string) $definition->getArgument(3));
$this->assertSame('security.token_storage', (string) $definition->getArgument(0));
$this->assertSame('security.authentication.trust_resolver', (string) $definition->getArgument(1));
$this->assertSame('contao.routing.scope_matcher', (string) $definition->getArgument(2));

$tags = $definition->getTags();

Expand Down Expand Up @@ -566,11 +566,10 @@ public function testRegistersTheUserSessionListener(): void

$this->assertSame(EventUserSessionListener::class, $definition->getClass());
$this->assertTrue($definition->isPrivate());
$this->assertSame('session', (string) $definition->getArgument(0));
$this->assertSame('database_connection', (string) $definition->getArgument(1));
$this->assertSame('security.token_storage', (string) $definition->getArgument(2));
$this->assertSame('security.authentication.trust_resolver', (string) $definition->getArgument(3));
$this->assertSame('contao.routing.scope_matcher', (string) $definition->getArgument(4));
$this->assertSame('database_connection', (string) $definition->getArgument(0));
$this->assertSame('security.token_storage', (string) $definition->getArgument(1));
$this->assertSame('security.authentication.trust_resolver', (string) $definition->getArgument(2));
$this->assertSame('contao.routing.scope_matcher', (string) $definition->getArgument(3));

$tags = $definition->getTags();

Expand Down Expand Up @@ -1304,6 +1303,18 @@ public function testRegistersTheSecurityBackendUserProvider(): void
$this->assertSame('logger', (string) $definition->getArgument(3));
}

public function testRegistersTheSecurityEntryPoint(): void
{
$this->assertTrue($this->container->has('contao.security.entry_point'));

$definition = $this->container->getDefinition('contao.security.entry_point');

$this->assertSame(AuthenticationEntryPoint::class, $definition->getClass());
$this->assertTrue($definition->isPrivate());
$this->assertSame('security.http_utils', (string) $definition->getArgument(0));
$this->assertSame('router', (string) $definition->getArgument(1));
}

public function testRegistersTheSecurityFrontendPreviewAuthenticator(): void
{
$this->assertTrue($this->container->has('contao.security.frontend_preview_authenticator'));
Expand Down Expand Up @@ -1343,16 +1354,6 @@ public function testRegistersTheSecurityLogoutSuccessHandler(): void
$this->assertSame('security.http_utils', (string) $definition->getArgument(0));
}

public function testRegistersTheSecuritySha1PasswordEncoder(): void
{
$this->assertTrue($this->container->has('contao.security.sha1_password_encoder'));

$definition = $this->container->getDefinition('contao.security.sha1_password_encoder');

$this->assertSame(Sha1PasswordEncoder::class, $definition->getClass());
$this->assertTrue($definition->isPrivate());
}

public function testRegistersTheSecurityLogoutHandler(): void
{
$this->assertTrue($this->container->has('contao.security.logout_handler'));
Expand All @@ -1365,6 +1366,16 @@ public function testRegistersTheSecurityLogoutHandler(): void
$this->assertSame('logger', (string) $definition->getArgument(1));
}

public function testRegistersTheSecuritySha1PasswordEncoder(): void
{
$this->assertTrue($this->container->has('contao.security.sha1_password_encoder'));

$definition = $this->container->getDefinition('contao.security.sha1_password_encoder');

$this->assertSame(Sha1PasswordEncoder::class, $definition->getClass());
$this->assertTrue($definition->isPrivate());
}

public function testRegistersTheSecurityTokenChecker(): void
{
$this->assertTrue($this->container->has('contao.security.token_checker'));
Expand Down
95 changes: 95 additions & 0 deletions tests/Security/Authentication/AuthenticationEntryPointTest.php
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

/*
* This file is part of Contao.
*
* Copyright (c) 2005-2017 Leo Feyer
*
* @license LGPL-3.0+
*/

namespace Contao\CoreBundle\Tests\Security\Authentication;

use Contao\CoreBundle\Security\Authentication\AuthenticationEntryPoint;
use Contao\CoreBundle\Tests\TestCase;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Http\HttpUtils;

class AuthenticationEntryPointTest extends TestCase
{
public function testCanBeInstantiated(): void
{
$entryPoint = new AuthenticationEntryPoint(
$this->createMock(HttpUtils::class),
$this->createMock(RouterInterface::class)
);

$this->assertInstanceOf('Contao\CoreBundle\Security\Authentication\AuthenticationEntryPoint', $entryPoint);
}

public function testAddsTheRefererToTheRedirectUrl(): void
{
$request = new Request();
$request->server->set('REQUEST_URI', '/contao?do=page');
$request->query->add(['do' => 'page']);

$httpUtils = $this->createMock(HttpUtils::class);

$httpUtils
->expects($this->once())
->method('createRedirectResponse')
->willReturnCallback(
function (Request $request, string $url): RedirectResponse {
return new RedirectResponse($url);
}
)
;

$url = 'http://localhost/contao/login?referer='.base64_encode('/contao?do=page');

$router = $this->createMock(RouterInterface::class);

$router
->expects($this->once())
->method('generate')
->with('contao_backend_login', ['referer' => base64_encode('/contao?do=page')])
->willReturn($url)
;

$entryPoint = new AuthenticationEntryPoint($httpUtils, $router);
$response = $entryPoint->start($request);

$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
$this->assertSame($url, $response->getTargetUrl());
}

public function testDoesNotAddARefererToTheRedirectUrlIfTheQueryIsEmpty(): void
{
$request = new Request();
$httpUtils = $this->createMock(HttpUtils::class);

$httpUtils
->expects($this->once())
->method('createRedirectResponse')
->with($request, 'contao_backend_login')
->willReturn(new RedirectResponse('http://localhost/contao/login'))
;

$router = $this->createMock(RouterInterface::class);

$router
->expects($this->never())
->method('generate')
;

$entryPoint = new AuthenticationEntryPoint($httpUtils, $router);
$response = $entryPoint->start($request);

$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
$this->assertSame('http://localhost/contao/login', $response->getTargetUrl());
}
}

0 comments on commit e4f0d29

Please sign in to comment.