Search code examples
symfonyfunctional-testingfosfacebookbundle

Faking authentication on Symfony2 tests - get('security.context')->getToken() on Controller returns differnt token than the one set up in the TestCase


I'm writing symfony functional tests for a Symfony 2.0 application that uses the FOSFacebookBundle (v2.0). The controllers I'm testing check for authentication like so:

if($this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')){...}

So I need the test's $client (of type Symfony\Bundle\FrameworkBundle\Client) to be authenticated.

Supposedly I need to create a valid token and set it on the $client's security.context, session, and cookieJar for $client to be authenticated, right?

The issue is that the security.context in the controllers return a different token from the valid one I set up on the test's $client.

Does anyone know why the token gets reset after making the request?

Here's how I'm setting this up in the test case class:

use FOS\FacebookBundle\Security\Authentication\Provider\FacebookProvider;
use FOS\FacebookBundle\Security\Authentication\Token\FacebookUserToken;
use Symfony\Component\BrowserKit\Cookie;

$providerKey = 'public';

$facebookMock = $this->getMockBuilder('\Facebook')
    ->disableOriginalConstructor()
    ->getMock();
$facebookMock->expects($this->once())
    ->method('getUser')
    ->will($this->returnValue('DevTestUsername'));

$facebookProvider = new FacebookProvider($providerKey, $facebookMock);

$tokenMock = new FacebookUserToken(
    'public', 
    'DevTestUsername', 
    array('ROLE_MEMBER', 'ROLE_MANAGER', 'ROLE_USER')
);

$authenticatedToken = $facebookProvider->authenticate($tokenMock);

$client->getContainer()->get('security.context')->setToken($authenticatedToken);
$client->getContainer()->get('session')->set('_security_public', serialize($authenticatedToken));
$client->getContainer()->get('session')->save();

$client->getCookieJar()->set(new Cookie(session_name(), $client->getContainer()->get('session')->getId()));

After this setup, on the test case this returns true:

if($client->getContainer()->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')){...}

But on the controllers, it returns false

if($this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')){...}

Am I setting up things wrong in the security.context or in the session?

Here's the application's security config:

security:
    factories:
        - "%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"

    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

    role_hierarchy:
        ROLE_ADMIN:       [ROLE_USER, ROLE_ALLOWED_TO_SWITCH]
        ROLE_SUPER_ADMIN: [ROLE_ADMIN]

    providers:
        chain_provider:
            providers: [fos_userbundle, facebook_provider]
        fos_userbundle:
            id: fos_user.user_manager
        facebook_provider:
            id: facebook.user

    firewalls:
        public:
            pattern:   ^/.*
            fos_facebook:
                provider: facebook_provider
                app_url: "http://apps.facebook.com/myapp"
                server_url: "http://myapp.local/"
                login_path: /login
                check_path: /login_check
                default_target_path: /target
            form_login:
                provider: fos_userbundle
                csrf_provider: form.csrf_provider
                login_path: /login
                check_path: /login_check
            anonymous: true
            switch_user: { role: ROLE_ADMIN }
            logout:
                target: http://myapp.com
                handlers: ["fos_facebook.logout_handler"]
            context: primary_auth

The facebook.id service is a class that implements the Symfony\Component\Security\Core\User\UserProviderInterface

This class uses facebook's php api (BaseFacebook class) to make a call to the facebook graph api (BaseFacebook->api('/me')), but this never happens, as I don't get past Symfony's security.

Any comments, pointers or links to resources greatly appreciated. Thanks!


Solution

  • The issue is a bug in Symfony 2.0

    I noticed that $this->contextKey equals 'primary_auth' inside method: \Symfony\Component\Security\Http\Firewall\ContextListener::handle(GetResponseEvent $event)

    so in my test suite I do:

    $client->getContainer()->get('session')->set('_security_primary_auth', serialize($authenticatedToken));
    

    instead of

    $client->getContainer()->get('session')->set('_security_public', serialize($authenticatedToken));
    

    (this issue is documented here: https://github.com/symfony/symfony/issues/5228 (read the whole thing))