Permalink
Browse files

Add support for 2 step validation (with Google Authenticator)

  • Loading branch information...
rande committed Mar 18, 2012
1 parent eca94ef commit 1af77093364a21c03350a59c10015576516b271d
@@ -39,6 +39,13 @@ public function getConfigTreeBuilder()
->scalarNode('user_group')->defaultValue('fos_user_user_group')->end()
->end()
->end()
+ ->arrayNode('google_authenticator')
+ ->addDefaultsIfNotSet()
+ ->children()
+ ->scalarNode('server')->cannotBeEmpty()->end()
+ ->scalarNode('enabled')->defaultValue(false)->end()
+ ->end()
+ ->end()
->scalarNode('manager_type')
->defaultValue('orm')
->validate()
@@ -41,6 +41,7 @@ public function load(array $configs, ContainerBuilder $container)
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load(sprintf('admin_%s.xml', $config['manager_type']));
$loader->load('form.xml');
+ $loader->load('google_authenticator.xml');
if ($config['security_acl']) {
$loader->load('security_acl.xml');
@@ -56,6 +57,36 @@ public function load(array $configs, ContainerBuilder $container)
$container->getParameter('twig.form.resources'),
array('SonataUserBundle:Form:form_admin_fields.html.twig')
));
+
+ $this->configureGoogleAuthenticator($config, $container);
+ }
+
+ /**
+ * @throws \RuntimeException
+ * @param $config
+ * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+ * @return
+ */
+ public function configureGoogleAuthenticator($config, ContainerBuilder $container)
+ {
+ $container->setParameter('sonata.user.google.authenticator.enabled', $config['google_authenticator']['enabled']);
+
+ if (!$config['google_authenticator']['enabled']) {
+ $container->removeDefinition('sonata.user.google.authenticator');
+ $container->removeDefinition('sonata.user.google.authenticator.provider');
+ $container->removeDefinition('sonata.user.google.authenticator.interactive_login_listener');
+ $container->removeDefinition('sonata.user.google.authenticator.request_listener');
+
+ return;
+ }
+
+ if (!class_exists('Google\Authenticator\GoogleAuthenticator')) {
+ throw new \RuntimeException('Please install GoogleAuthenticator.php available on github.com');
+ }
+
+ $container->getDefinition('sonata.user.google.authenticator.provider')
+ ->replaceArgument(0, $config['google_authenticator']['server']);
+
}
/**
View
@@ -1,4 +1,5 @@
<?php
+
/*
* This file is part of the Sonata project.
*
@@ -8,17 +9,19 @@
* file that was distributed with this source code.
*/
-
namespace Sonata\UserBundle\Document;
use FOS\UserBundle\Document\User as AbstractedUser;
+use Sonata\UserBundle\Model\UserInterface;
-class BaseUser extends AbstractedUser
+class BaseUser extends AbstractedUser implements UserInterface
{
protected $createdAt;
protected $updatedAt;
+ protected $twoStepVerificationCode;
+
/**
* Set createdAt
*
@@ -97,4 +100,21 @@ public function setGroups($groups)
$this->addGroup($group);
}
}
+
+ /**
+ * @param string $twoStepVerificationCode
+ * @return void
+ */
+ public function setTwoStepVerificationCode($twoStepVerificationCode)
+ {
+ $this->twoStepVerificationCode = $twoStepVerificationCode;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTwoStepVerificationCode()
+ {
+ return $this->twoStepVerificationCode;
+ }
}
View
@@ -1,4 +1,5 @@
<?php
+
/*
* This file is part of the Sonata project.
*
@@ -8,17 +9,19 @@
* file that was distributed with this source code.
*/
-
namespace Sonata\UserBundle\Entity;
use FOS\UserBundle\Entity\User as AbstractedUser;
+use Sonata\UserBundle\Model\UserInterface;
-class BaseUser extends AbstractedUser
+class BaseUser extends AbstractedUser implements UserInterface
{
protected $createdAt;
protected $updatedAt;
+ protected $twoStepVerificationCode;
+
/**
* Set createdAt
*
@@ -114,4 +117,21 @@ public function setGroups($groups)
$this->addGroup($group);
}
}
+
+ /**
+ * @param string $twoStepVerificationCode
+ * @return void
+ */
+ public function setTwoStepVerificationCode($twoStepVerificationCode)
+ {
+ $this->twoStepVerificationCode = $twoStepVerificationCode;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTwoStepVerificationCode()
+ {
+ return $this->twoStepVerificationCode;
+ }
}
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Sonata project.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\UserBundle\GoogleAuthenticator;
+
+use Google\Authenticator\GoogleAuthenticator as BaseGoogleAuthenticator;
+use Sonata\UserBundle\Model\UserInterface;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+
+class Helper
+{
+ protected $server;
+
+ protected $authenticator;
+
+ /**
+ * @param $server
+ * @param \Google\Authenticator\GoogleAuthenticator $authenticator
+ */
+ public function __construct($server, BaseGoogleAuthenticator $authenticator)
+ {
+ $this->server = $server;
+ $this->authenticator = $authenticator;
+ }
+
+ /**
+ * @param \Sonata\UserBundle\Model\UserInterface $user
+ * @param $code
+ * @return bool
+ */
+ public function checkCode(UserInterface $user, $code)
+ {
+ return $this->authenticator->checkCode($user->getTwoStepVerificationCode(), $code);
+ }
+
+ /**
+ * @param \Sonata\UserBundle\Model\UserInterface $user
+ * @return string
+ */
+ public function getUrl(UserInterface $user)
+ {
+ return $this->authenticator->getUrl($user->getUsername(), $this->server, $user->getTwoStepVerificationCode());
+ }
+
+ /**
+ * @return string
+ */
+ public function generateSecret()
+ {
+ return $this->authenticator->generateSecret();
+ }
+
+ /**
+ * @param \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken $token
+ * @return string
+ */
+ public function getSessionKey(UsernamePasswordToken $token)
+ {
+ return sprintf('sonata_user_google_authenticator_%s_%s', $token->getProviderKey(), $token->getUsername());
+ }
+}
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the Sonata project.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\UserBundle\GoogleAuthenticator;
+
+use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Sonata\UserBundle\Model\UserInterface;
+
+class InteractiveLoginListener
+{
+ protected $helper;
+
+ /**
+ * @param Helper $helper
+ */
+ public function __construct(Helper $helper)
+ {
+ $this->helper = $helper;
+ }
+
+ /**
+ * @param \Symfony\Component\Security\Http\Event\InteractiveLoginEvent $event
+ * @return
+ */
+ public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
+ {
+ if (!$event->getAuthenticationToken() instanceof UsernamePasswordToken) {
+ return;
+ }
+
+ $token = $event->getAuthenticationToken();
+
+ if (!$token->getUser() instanceof UserInterface) {
+ return;
+ }
+
+ if (!$token->getUser()->getTwoStepVerificationCode()) {
+ return;
+ }
+
+ $event->getRequest()->getSession()->set($this->helper->getSessionKey($token), null);
+ }
+}
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the Sonata project.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\UserBundle\GoogleAuthenticator;
+
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Sonata\UserBundle\Model\UserInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\Security\Core\SecurityContextInterface;
+use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
+
+
+class RequestListener
+{
+ protected $helper;
+
+ protected $securityContext;
+
+ protected $templating;
+
+ /**
+ * @param Helper $helper
+ * @param \Symfony\Component\Security\Core\SecurityContextInterface $securityContext
+ * @param \Symfony\Bundle\FrameworkBundle\Templating\EngineInterface $templating
+ */
+ public function __construct(Helper $helper, SecurityContextInterface $securityContext, EngineInterface $templating)
+ {
+ $this->helper = $helper;
+ $this->securityContext = $securityContext;
+ $this->templating = $templating;
+ }
+
+ /**
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * @return
+ */
+ public function onCoreRequest(GetResponseEvent $event)
+ {
+ $token = $this->securityContext->getToken();
+
+ if (!$token) {
+ return;
+ }
+
+ if (!$token instanceof UsernamePasswordToken) {
+ return;
+ }
+
+ $key = $this->helper->getSessionKey($this->securityContext->getToken());
+ $request = $event->getRequest();
+ $session = $event->getRequest()->getSession();
+ $user = $this->securityContext->getToken()->getUser();
+
+ if (!$session->has($key)) {
+ return;
+ }
+
+ if ($session->get($key) === true) {
+ return;
+ }
+
+ $state = 'init';
+ if ($request->getMethod() == 'POST') {
+ if ($this->helper->checkCode($user, $request->get('_code')) == true) {
+ $session->set($key, true);
+
+ return;
+ }
+
+ $state = 'error';
+ }
+
+ $event->setResponse($this->templating->renderResponse('SonataUserBundle:Admin:Security/two_step_form.html.twig', array(
+ 'state' => $state
+ )));
+ }
+}
Oops, something went wrong.

0 comments on commit 1af7709

Please sign in to comment.