[SecurityBundle] Add functional test for form login with CSRF token
This commit is contained in:
parent
4a0057fd56
commit
c48c775018
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Controller;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\SecurityContextInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
class LoginController extends ContainerAware
|
||||
{
|
||||
public function loginAction()
|
||||
{
|
||||
$form = $this->container->get('form.factory')->create('user_login');
|
||||
|
||||
return $this->container->get('templating')->renderResponse('CsrfFormLoginBundle:Login:login.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
));
|
||||
}
|
||||
|
||||
public function afterLoginAction()
|
||||
{
|
||||
return $this->container->get('templating')->renderResponse('CsrfFormLoginBundle:Login:after_login.html.twig');
|
||||
}
|
||||
|
||||
public function loginCheckAction()
|
||||
{
|
||||
return new Response('', 400);
|
||||
}
|
||||
|
||||
public function secureAction()
|
||||
{
|
||||
throw new \Exception('Wrapper', 0, new \Exception('Another Wrapper', 0, new AccessDeniedException()));
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class CsrfFormLoginBundle extends Bundle
|
||||
{
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\Event\FilterDataEvent;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\SecurityContextInterface;
|
||||
|
||||
/**
|
||||
* Form type for use with the Security component's form-based authentication
|
||||
* listener.
|
||||
*
|
||||
* @author Henrik Bjornskov <henrik@bjrnskov.dk>
|
||||
* @author Jeremy Mikola <jmikola@gmail.com>
|
||||
*/
|
||||
class UserLoginFormType extends AbstractType
|
||||
{
|
||||
private $reqeust;
|
||||
|
||||
/**
|
||||
* @param Request $request A request instance
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Symfony\Component\Form\AbstractType::buildForm()
|
||||
*/
|
||||
public function buildForm(FormBuilder $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('username', 'text')
|
||||
->add('password', 'password')
|
||||
->add('_target_path', 'hidden')
|
||||
;
|
||||
|
||||
$request = $this->request;
|
||||
|
||||
/* Note: since the Security component's form login listener intercepts
|
||||
* the POST request, this form will never really be bound to the
|
||||
* request; however, we can match the expected behavior by checking the
|
||||
* session for an authentication error and last username.
|
||||
*/
|
||||
$builder->addEventListener(FormEvents::SET_DATA, function (FilterDataEvent $event) use ($request) {
|
||||
if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
|
||||
$error = $request->attributes->get(SecurityContextInterface::AUTHENTICATION_ERROR);
|
||||
} else {
|
||||
$error = $request->getSession()->get(SecurityContextInterface::AUTHENTICATION_ERROR);
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$event->getForm()->addError(new FormError($error->getMessage()));
|
||||
}
|
||||
|
||||
$event->setData(array_replace((array) $event->getData(), array(
|
||||
'username' => $request->getSession()->get(SecurityContextInterface::LAST_USERNAME),
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Symfony\Component\Form\AbstractType::getDefaultOptions()
|
||||
*/
|
||||
public function getDefaultOptions(array $options)
|
||||
{
|
||||
/* Note: the form's intention must correspond to that for the form login
|
||||
* listener in order for the CSRF token to validate successfully.
|
||||
*/
|
||||
return array(
|
||||
'intention' => 'authenticate',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Symfony\Component\Form\FormTypeInterface::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'user_login';
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
form_login:
|
||||
pattern: /login
|
||||
defaults: { _controller: CsrfFormLoginBundle:Login:login }
|
||||
|
||||
form_login_check:
|
||||
pattern: /login_check
|
||||
defaults: { _controller: CsrfFormLoginBundle:Login:loginCheck }
|
||||
|
||||
form_login_homepage:
|
||||
pattern: /
|
||||
defaults: { _controller: CsrfFormLoginBundle:Login:afterLogin }
|
||||
|
||||
form_login_custom_target_path:
|
||||
pattern: /foo
|
||||
defaults: { _controller: CsrfFormLoginBundle:Login:afterLogin }
|
||||
|
||||
form_login_default_target_path:
|
||||
pattern: /profile
|
||||
defaults: { _controller: CsrfFormLoginBundle:Login:afterLogin }
|
||||
|
||||
form_login_redirect_to_protected_resource_after_login:
|
||||
pattern: /protected-resource
|
||||
defaults: { _controller: CsrfFormLoginBundle:Login:afterLogin }
|
||||
|
||||
form_logout:
|
||||
pattern: /logout_path
|
||||
|
||||
form_secure_action:
|
||||
pattern: /secure-but-not-covered-by-access-control
|
||||
defaults: { _controller: CsrfFormLoginBundle:Login:secure }
|
@ -0,0 +1,6 @@
|
||||
{% extends "::base.html.twig" %}
|
||||
|
||||
{% block body %}
|
||||
Hello {{ app.user.username }}!<br /><br />
|
||||
You're browsing to path "{{ app.request.pathInfo }}".
|
||||
{% endblock %}
|
@ -0,0 +1,12 @@
|
||||
{% extends "::base.html.twig" %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<form action="{{ path('form_login_check') }}" method="post">
|
||||
{{ form_widget(form) }}
|
||||
|
||||
{# Note: ensure the submit name does not conflict with the form's name or it may clobber field data #}
|
||||
<input type="submit" name="login" />
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
|
||||
|
||||
/**
|
||||
* @group functional
|
||||
*/
|
||||
class CsrfFormLoginTest extends WebTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getConfigs
|
||||
*/
|
||||
public function testFormLogin($config)
|
||||
{
|
||||
$client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config));
|
||||
$client->insulate();
|
||||
|
||||
$form = $client->request('GET', '/login')->selectButton('login')->form();
|
||||
$form['user_login[username]'] = 'johannes';
|
||||
$form['user_login[password]'] = 'test';
|
||||
$client->submit($form);
|
||||
|
||||
$this->assertRedirect($client->getResponse(), '/profile');
|
||||
|
||||
$text = $client->followRedirect()->text();
|
||||
$this->assertContains('Hello johannes!', $text);
|
||||
$this->assertContains('You\'re browsing to path "/profile".', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getConfigs
|
||||
*/
|
||||
public function testFormLoginWithInvalidCsrfToken($config)
|
||||
{
|
||||
$client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config));
|
||||
$client->insulate();
|
||||
|
||||
$form = $client->request('GET', '/login')->selectButton('login')->form();
|
||||
$form['user_login[_token]'] = '';
|
||||
$client->submit($form);
|
||||
|
||||
$this->assertRedirect($client->getResponse(), '/login');
|
||||
|
||||
$text = $client->followRedirect()->text();
|
||||
$this->assertContains('Invalid CSRF token.', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getConfigs
|
||||
*/
|
||||
public function testFormLoginWithCustomTargetPath($config)
|
||||
{
|
||||
$client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config));
|
||||
$client->insulate();
|
||||
|
||||
$form = $client->request('GET', '/login')->selectButton('login')->form();
|
||||
$form['user_login[username]'] = 'johannes';
|
||||
$form['user_login[password]'] = 'test';
|
||||
$form['user_login[_target_path]'] = '/foo';
|
||||
$client->submit($form);
|
||||
|
||||
$this->assertRedirect($client->getResponse(), '/foo');
|
||||
|
||||
$text = $client->followRedirect()->text();
|
||||
$this->assertContains('Hello johannes!', $text);
|
||||
$this->assertContains('You\'re browsing to path "/foo".', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getConfigs
|
||||
*/
|
||||
public function testFormLoginRedirectsToProtectedResourceAfterLogin($config)
|
||||
{
|
||||
$client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config));
|
||||
$client->insulate();
|
||||
|
||||
$client->request('GET', '/protected-resource');
|
||||
$this->assertRedirect($client->getResponse(), '/login');
|
||||
|
||||
$form = $client->followRedirect()->selectButton('login')->form();
|
||||
$form['user_login[username]'] = 'johannes';
|
||||
$form['user_login[password]'] = 'test';
|
||||
$client->submit($form);
|
||||
$this->assertRedirect($client->getResponse(), '/protected-resource');
|
||||
|
||||
$text = $client->followRedirect()->text();
|
||||
$this->assertContains('Hello johannes!', $text);
|
||||
$this->assertContains('You\'re browsing to path "/protected-resource".', $text);
|
||||
}
|
||||
|
||||
public function getConfigs()
|
||||
{
|
||||
return array(
|
||||
array('config.yml'),
|
||||
array('routes_as_path.yml'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->deleteTmpDir('CsrfFormLogin');
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
$this->deleteTmpDir('CsrfFormLogin');
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
||||
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
||||
new Symfony\Bundle\TwigBundle\TwigBundle(),
|
||||
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\CsrfFormLoginBundle(),
|
||||
);
|
@ -0,0 +1,43 @@
|
||||
imports:
|
||||
- { resource: ./../config/default.yml }
|
||||
|
||||
services:
|
||||
csrf_form_login.form.type:
|
||||
class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginFormType
|
||||
scope: request
|
||||
arguments:
|
||||
- @request
|
||||
tags:
|
||||
- { name: form.type, alias: user_login }
|
||||
|
||||
security:
|
||||
encoders:
|
||||
Symfony\Component\Security\Core\User\User: plaintext
|
||||
|
||||
providers:
|
||||
in_memory:
|
||||
memory:
|
||||
users:
|
||||
johannes: { password: test, roles: [ROLE_USER] }
|
||||
|
||||
firewalls:
|
||||
# This firewall doesn't make sense in combination with the rest of the
|
||||
# configuration file, but it's here for testing purposes (do not use
|
||||
# this file in a real world scenario though)
|
||||
login_form:
|
||||
pattern: ^/login$
|
||||
security: false
|
||||
|
||||
default:
|
||||
form_login:
|
||||
check_path: /login_check
|
||||
default_target_path: /profile
|
||||
target_path_parameter: "user_login[_target_path]"
|
||||
username_parameter: "user_login[username]"
|
||||
password_parameter: "user_login[password]"
|
||||
csrf_parameter: "user_login[_token]"
|
||||
csrf_provider: form.csrf_provider
|
||||
anonymous: ~
|
||||
|
||||
access_control:
|
||||
- { path: .*, roles: IS_AUTHENTICATED_FULLY }
|
@ -0,0 +1,13 @@
|
||||
imports:
|
||||
- { resource: ./config.yml }
|
||||
|
||||
security:
|
||||
firewalls:
|
||||
default:
|
||||
form_login:
|
||||
login_path: form_login
|
||||
check_path: form_login_check
|
||||
default_target_path: form_login_default_target_path
|
||||
logout:
|
||||
path: form_logout
|
||||
target: form_login_homepage
|
@ -0,0 +1,2 @@
|
||||
_csrf_form_login_bundle:
|
||||
resource: @CsrfFormLoginBundle/Resources/config/routing.yml
|
Reference in New Issue
Block a user