19

I am currently in the process of migrating a 2.0.* project to the current 2.1 beta of Symfony.

In my functional tests i currently have this code to create a client with authentication:

$client = // create a normal test client
$role = 'ROLE_USER';
$firewallName = 'main';
$user = // pull a user from db

$client->getCookieJar()->set(new \Symfony\Component\BrowserKit\Cookie(session_name(), true));

$token = new UsernamePasswordToken($user, null, $firewallName, array($role));

self::$kernel->getContainer()->get('session')->set('_security_' . $firewallName, 
serialize($token));

this works as expected in 2.0.* but not in 2.1, the data does not get set in the session.

Any ideas?

Edit (adding more info):

it seems that the problem lies in the file "Symfony\Component\Security\Http\Firewall\ContextListener" in the method "onKernelResponse". There is this code:

if ((null === $token = $this->context->getToken()) || ($token instanceof AnonymousToken)) {
    $session->remove('_security_'.$this->contextKey);
} else {
    $session->set('_security_'.$this->contextKey, serialize($token));
}

in my case the if "$token instanceof AnonymousToken" is true, and because of that the session key gets removed. if i comment out that code everything works as expected.

So i guess my new question is: What can i do to make the token not anonymous?

j0k
  • 22,600
  • 28
  • 79
  • 90
smoove
  • 3,920
  • 3
  • 25
  • 31
  • I'm using a very similar snippet to yours to login a user for particular functional tests. I'm also upgrading to 2.1 and would be awesome to get this working, as a lot of my functional tests are now failing. – josef.van.niekerk Aug 07 '12 at 06:05
  • I see that in ContextListener.php, a call is made to $this->context->setToken(null), and that is getting called because $session === null? Mmm... – josef.van.niekerk Aug 07 '12 at 06:44
  • 1
    When I comment out ContextListener.php:line 77 - $this->context->setToken(null), my tests work perfectly! So maybe that's a clue! – josef.van.niekerk Aug 07 '12 at 06:49

5 Answers5

11

Proper way to authenticate user is:

$firewallName = 'your-firewall-name';
$container = self::$kernel->getContainer()
$token = new UsernamePasswordToken($user, null, $firewallName, $user->getRoles());
$container->get('security.context')->setToken($token);

and firewall authentication:

$session = $container->get('session');
$session->set('_security_'.$firewallName, serialize($token));
$session->save();
Maciej Pyszyński
  • 9,266
  • 3
  • 25
  • 28
  • After some digging around, i came to the same conclusion. But this does not work in my tests. Thanks for your answer anyway. – smoove Jul 19 '12 at 09:35
  • Did you also removed staring new session with this line: $client->getCookieJar()->set(new \Symfony\Component\BrowserKit\Cookie(session_name(), true)); – Maciej Pyszyński Jul 19 '12 at 09:59
  • I tried with and without that line and it does not change anything. – smoove Jul 19 '12 at 10:20
  • This worked for me (thank you so much this has been bugging me for ages). But I also had to disable the firewall in security.yml: firewalls: test: pattern: ^/.* security: false I guess technically it would be better to test without disabling the firewall, but just disabling the firewall didn't work either because I needed to have an authenticated user in the system (as your code does). Either way it is good enough for what I want; can finally write some functional tests for pages behind login! – timhc22 Sep 17 '14 at 16:55
1

I've logged a ticket on: https://github.com/symfony/symfony/issues/3287

There appears to be an existing issue with Symfony 2.1 RC.

josef.van.niekerk
  • 11,941
  • 20
  • 97
  • 157
1

In my testsuite (working on 2.0 and now 2.1) I use a Base class WebTestCase that extends the Symfony2 WebTestCase class.

I have these two functions that create an anonymous client or a logged one in my tests:

static protected function createClient(array $options = array(), array $server = array())
{
    $client = parent::createClient($options, $server);

    self::$container = self::$kernel->getContainer();

    return $client;
}

protected function createAuthClient($user, $pass)
{
    return self::createClient(array(), array(
        'PHP_AUTH_USER' => $user,
        'PHP_AUTH_PW'   => $pass,
    ));
}

Then, in my Test classes, I use:

    $client = static::createClient();

to create an anon client and

    $client = static::createAuthClient('user','pass');

to create an authenticated one.

This works like a charm on 2.1

guillaumepotier
  • 7,369
  • 8
  • 45
  • 72
1

I can suggest another solution (thus it working for me). I have an custom authentication provider, with complicated logic and I can't use http_basic mechanic at all.

You need to create special Action like this

 //TestAuthController.php
 public function authTestAction(Request $request)
 { 
   // you can add different security checks as you wish
   $user_id = $request->query->get("id");
   // find user  by $user_id using service or user provider service from firewall config
   $token = new UsernamePasswordToken($user, null, $firewallName, array($role));
    // or another authenticated token
   $this->container->get('security.context')->setToken($token);
 }

add routing_test.yml and plug it just as routing_dev.yml

Important thing that this authentication route must exists only in test enviroment, for security reasons.

 //app/config/routing_test.yml
 // ... the same routes as in routing_dev.yml
_testauth:
    pattern:  /__test
    defaults: { _controller: MyBundle:TestAuth:authTest }

Then in test just send client to this url

public function testSomething()
{
    $client = static::createClient();
    $client->request('GET','/__test',array('id'=>146));
    $this->assertTrue($client->getContainer()->get('security.context')->isGranted('ROLE_USER')) //it will pass;
}
j0k
  • 22,600
  • 28
  • 79
  • 90
Sawered
  • 152
  • 1
  • 2
  • 10
1

i had the same issue and found a solution while debugging. Depending on your Session Storage, you might have to change your cookie from session_name() to 'MOCKSESSID' (which is used if your using the mock session. I think if you have the following in your config_test.yml it is automatically changed to Mock Session Storage:

framework:
   test: ~

For completeness, here my full code (i'm using FOS/UserBundle)

    protected function createAuthorizedClient($username = 'user') {
      $client = $this->createClient(); //Normal WebTestCase client     
      $userProvider = $this->get('fos_user.user_manager');
      $user = $userProvider->loadUserByUsername($username);
      //$client->getCookieJar()->set(new Cookie(session_name(), true));
      $client->getCookieJar()->set(new Cookie('MOCKSESSID', true));
      $session = self::$kernel->getContainer()->get('session');
      $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
      $client->getContainer()->get('security.context')->setToken($token);
      $session->set('_security_main', serialize($token));

    return $client;
}
m0c
  • 2,180
  • 26
  • 45