[SecurityBundle] Add functional test for form login with CSRF token

This commit is contained in:
Jeremy Mikola 2011-12-30 20:55:45 -05:00
parent 4a0057fd56
commit c48c775018
11 changed files with 393 additions and 0 deletions

View File

@ -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()));
}
}

View File

@ -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
{
}

View File

@ -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';
}
}

View File

@ -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 }

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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');
}
}

View File

@ -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(),
);

View File

@ -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 }

View File

@ -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

View File

@ -0,0 +1,2 @@
_csrf_form_login_bundle:
resource: @CsrfFormLoginBundle/Resources/config/routing.yml