[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