Skip to content

Instantly share code, notes, and snippets.

@guillaumepotier
Created February 4, 2013 09:07
Show Gist options
  • Save guillaumepotier/4705757 to your computer and use it in GitHub Desktop.
Save guillaumepotier/4705757 to your computer and use it in GitHub Desktop.
Custom FB Symfony2 User Provider. Functional on Sf2.1
<?php
namespace Proj\UserBundle\Facebook;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
/**
* The factory is where you hook into the security component,
* telling it the name of your provider and any configuration
* options available
* for it.
**/
class FacebookFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.facebook.' . $id;
$container->setDefinition($providerId, new DefinitionDecorator('facebook.security.authentication.provider'));
$listenerId = 'security.authentication.listener.facebook.' . $id;
$container->setDefinition($listenerId, new DefinitionDecorator('facebook.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'facebook';
}
public function addConfiguration(NodeDefinition $node)
{
}
}
<?php
namespace Proj\UserBundle\Facebook;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Facebook;
use Proj\UserBundle\Entity\User;
/**
* The listener is responsible for fielding requests to the firewall
* and calling the authentication provider.
**/
class FacebookListener implements ListenerInterface
{
protected $securityContext;
protected $authenticationManager;
protected $entityManager;
protected $facebookProvider;
private $appId;
private $appSecret;
private $facebook;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, EntityManager $entityManager, FacebookProvider $facebookProvider, $appId, $appSecret)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
$this->entityManager = $entityManager;
$this->facebookProvider = $facebookProvider;
$this->appId = $appId;
$this->appSecret = $appSecret;
}
public function handle(GetResponseEvent $event)
{
if (null !== $this->securityContext->getToken() || $event->getRequest()->attributes->get('_route') !== 'login') {
return;
}
$this->facebook = new Facebook(array(
'appId' => $this->appId,
'secret' => $this->appSecret,
));
if ($userId = $this->facebook->getUser()) {
$token = $this->facebookProvider->generateToken($userId);
if (false === $token = $this->facebookProvider->authenticate($token, false)) {
$userInfo = $this->facebook->api('/' + $userId);
$token = $this->facebookProvider->generateToken($userId);
/** create new user **/
$user = new User();
$user->setFacebookId($userId);
$user->setUsername($userInfo['name']);
$user->setLang($userInfo['locale']);
$user->setEmail($userInfo['email']);
$this->entityManager->persist($user);
$this->entityManager->flush();
$token->setUser($user);
}
$this->securityContext->setToken($token);
}
}
}
<?php
namespace Proj\UserBundle\Facebook;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Doctrine\ORM\EntityManager;
class FacebookProvider implements AuthenticationProviderInterface
{
private $entityManager;
private $rolesRoot;
public function __construct(EntityManager $entityManager, $rolesRoot)
{
$this->entityManager = $entityManager;
$this->rolesRoot = $rolesRoot;
}
public function authenticate(TokenInterface $token, $withHash = true)
{
$userId = $token->getKey();
$user = $this->entityManager->getRepository('UserBundle:User')->findOneBy(array('facebook_id' => $userId));
if ($user) {
if ($withHash && $user->getUserToken() !== $token->getHash()) {
return false;
}
$token = $this->generateToken($userId);
$token->setUser($user);
return $token;
}
return false;
}
public function generateToken($userId)
{
if (in_array($userId, $this->rolesRoot)) {
$token = new FacebookUserToken(array('ROLE_ROOT'));
} else {
$token = new FacebookUserToken(array('ROLE_USER'));
}
return $token->setKey($userId);
}
public function supports(TokenInterface $token)
{
return $token instanceof FacebookUserToken;
}
}
<?php
namespace Proj\UserBundle\Facebook;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
/**
* A token represents the user authentication data present in the request.
* Once a request is authenticated, the token retains the user`s data,
* and delivers this data across the security context.
**/
class FacebookUserToken extends AbstractToken
{
protected $key;
protected $hash;
public function __construct(array $roles = array())
{
parent::__construct($roles);
// If the user has roles, consider it authenticated
$this->setAuthenticated(count($roles) > 0);
}
public function setKey($key)
{
$this->key = $key;
return $this;
}
public function getKey()
{
return $this->key;
}
public function getAuthenticated()
{
return $this->authenticated;
}
public function getCredentials()
{
return '';
}
}
<?php
namespace Proj\UserBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class FrontendController extends Controller
{
/**
* @Route("/", name="index")
* @Template()
*/
public function indexAction()
{
return array(
'facebookAppId' => $this->container->getParameter('facebookAppId'),
'user' => null !== $this->get('security.context') ? $this->getUser() : $this->get('security.context')->getToken(),
);
}
/**
* @Route("/login", name="login")
*/
public function login(Request $request)
{
return $this->redirect($request->server->get('HTTP_REFERER', $this->generateUrl('index')));
}
}
<?php
namespace Proj\UserBundle\Security;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\SecurityContext;
class LogoutHandler implements LogoutSuccessHandlerInterface
{
private $router;
private $security;
public function __construct(Router $router, SecurityContext $security)
{
$this->router = $router;
$this->security = $security;
}
public function onLogoutSuccess(Request $request)
{
$this->security->setToken(null);
$request->getSession()->invalidate();
return new RedirectResponse($request->server->get('HTTP_REFERER', $this->router->generate('index')));
}
}
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="proj.logout.handler" class="Proj\UserBundle\Security\LogoutHandler">
<argument type="service" id="router" />
<argument type="service" id="security.context" />
</service>
</services>
</container>
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_ROOT: ROLE_ADMIN
providers:
main:
entity: { class: Proj\UserBundle\Entity\User, property: username }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
facebook: ~
logout:
path: /logout
target: /
success_handler: proj.logout.handler
jms_security_extra:
secure_all_services: false
expressions: true
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="facebook.security.authentication.provider" class="Proj\UserBundle\Facebook\FacebookProvider">
<argument type="service" id="doctrine.orm.entity_manager" />
<argument>%rolesRoot%</argument>
</service>
<service id="facebook.security.authentication.listener" class="Proj\UserBundle\Facebook\FacebookListener" public="false">
<argument type="service" id="security.context"/>
<argument type="service" id="security.authentication.manager" />
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="facebook.security.authentication.provider" />
<argument>%facebookAppId%</argument>
<argument>%facebookAppSecret%</argument>
</service>
</services>
</container>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment