8

I have read many posts on stackoverflow about this. But most of the methods not useful in Symfony 2.3. So I have try to log in user manually in test to make some actions in back-end. Here is my security.yml

security:
...
  role_hierarchy:
        ROLE_SILVER: [ROLE_BRONZE]
        ROLE_GOLD: [ROLE_BRONZE, ROLE_SILVER]
        ROLE_PLATINUM: [ROLE_BRONZE, ROLE_SILVER, ROLE_GOLD]
        ROLE_ADMIN: [ROLE_BRONZE, ROLE_SILVER, ROLE_GOLD, ROLE_PLATINUM, ROLE_ALLOWED_TO_SWITCH]

    providers:
        database:
            entity: { class: Fox\PersonBundle\Entity\Person, property: username }

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        login:
            pattern:  ^/person/login$
            security: false

        main:
            pattern:    ^/
            provider:   database
            form_login:
                check_path: /person/login-check
                login_path: /person/login
                default_target_path: /person/view
                always_use_default_target_path: true
            logout:
                path:   /person/logout
                target: /
            anonymous: true

    access_control:
        - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/person/registration, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/person, roles: ROLE_BRONZE }

Here is my test:

class ProfileControllerTest extends WebTestCase
{
    public function setUp()
    {
        $kernel = self::getKernelClass();

        self::$kernel = new $kernel('dev', true);
        self::$kernel->boot();
    }

    public function testView()
    {
        $client = static::createClient();

        $person = self::$kernel->getContainer()->get('doctrine')->getRepository('FoxPersonBundle:Person')->findOneByUsername('master');

        $token = new UsernamePasswordToken($person, $person->getPassword(), 'main', $person->getRoles());

        self::$kernel->getContainer()->get('security.context')->setToken($token);

        self::$kernel->getContainer()->get('event_dispatcher')->dispatch(
        AuthenticationEvents::AUTHENTICATION_SUCCESS,
        new AuthenticationEvent($token));

        $crawler = $client->request('GET', '/person/view');
    }

And when I run this test, $person = $this->get(security.context)->getToken()->getUser(); method is not working in testing Controller. Say if in controller call $person->getId(); I will have an error Call to a member function getId() on a non-object in... .

So can you tell the properly way to log in user in functional test in Symfony 2.3?

Thanks!

EDIT_1: If I change Symfony/Component/Security/Http/Firewall/ContextListener.php and comment one string:

if (null === $session || null === $token = $session->get('_security_'.$this->contextKey)) {
            // $this->context->setToken(null);

            return;
        }

all tests going on without errors.

EDIT_2: This is reference that i have trying to use: first second third fourth fifth sixth seventh eighth nineth

Community
  • 1
  • 1
Serge Kvashnin
  • 4,332
  • 4
  • 23
  • 37
  • In your edit_1, what is `$session`? I would debug from that line as it seems all you need to do is to inject the token there. Also your question is pretty much backwards. What you actually want to know is how you can inject a user manually for testing purposes. And so far you haven't made clear and provided reference why your code *should* work at all. I think you should provide reference first, that will make your question more clear and more easy to answer. – hakre Sep 26 '13 at 09:47
  • @hakre I have edit the post (EDIT_2). If not enough give me to know. And thanks for advance. – Serge Kvashnin Sep 26 '13 at 19:55
  • There is no reference at all for the case you have here and the symfony version you're using. So far all that is visible by that is that just applying older symfony versions' configuration does not apply which somehow does not wonder a lot as it's a different version. – hakre Sep 26 '13 at 23:29
  • @hakre So maybe you have a reference for current 2.3? – Serge Kvashnin Sep 27 '13 at 06:32
  • No, sorry, I don't have. However you can install an older version in parallel, check the given suggestions and then see which components are involved and then check the changelog of 2.3 if you see something in that area. – hakre Sep 27 '13 at 07:08

2 Answers2

18

Finaly i solve it! This is example of working code:

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\BrowserKit\Cookie;

class ProfileControllerTest extends WebTestCase
{
    protected function createAuthorizedClient()
    {
        $client = static::createClient();
        $container = static::$kernel->getContainer();
        $session = $container->get('session');
        $person = self::$kernel->getContainer()->get('doctrine')->getRepository('FoxPersonBundle:Person')->findOneByUsername('master');

        $token = new UsernamePasswordToken($person, null, 'main', $person->getRoles());
        $session->set('_security_main', serialize($token));
        $session->save();

        $client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));

        return $client;
    }

    public function testView()
    {
        $client = $this->createAuthorizedClient();
        $crawler = $client->request('GET', '/person/view');
        $this->assertEquals(
            200,
            $client->getResponse()->getStatusCode()
        );
    }   

Hope it helps to save your time and nerves ;)

Serge Kvashnin
  • 4,332
  • 4
  • 23
  • 37
  • 1
    You could also do `$client->getContainer()` instead of `static::$kernel->getContainer` and use `$contaienr->get('doctrine')` instead of `self::$kernel->getContainer()->get('doctrine')`. Wouldn't it be shorter and simpler? Or am I maybe wrong? – cezar Feb 18 '19 at 14:51
  • 1
    pay attention that the session variable name must match your firewall name after the prefix `_security_`, for example, if your firewall is `admin_area`, you must set the value to `security_admin_area` – Paulo Lima Mar 06 '20 at 20:50
3

As an addition to the accepted solution I will show my function to login user in controller.

// <!-- Symfony 2.4 --> //

use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

private function loginUser(UsernamePasswordToken $token, Request $request)     {
    $this->get('security.context')->setToken($token);

    $s = $this->get('session');
    $s->set('_security_main', serialize($token)); // `main` is firewall name
    $s->save();

    $ed = $this->get('event_dispatcher');

    $ed->dispatch(
        AuthenticationEvents::AUTHENTICATION_SUCCESS,
        new AuthenticationEvent($token)
    );

    $ed->dispatch(
        "security.interactive_login",
        new InteractiveLoginEvent($request, $token)
    );
}
Paul T. Rawkeen
  • 3,994
  • 3
  • 35
  • 51