Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add support for 2 step validation (with Google Authenticator)

  • Loading branch information...
commit 1af77093364a21c03350a59c10015576516b271d 1 parent eca94ef
Thomas rande authored
Showing with 564 additions and 6 deletions.
  1. +7 −0 DependencyInjection/Configuration.php
  2. +31 −0 DependencyInjection/SonataUserExtension.php
  3. +22 −2 Document/BaseUser.php
  4. +22 −2 Entity/BaseUser.php
  5. +69 −0 GoogleAuthenticator/Helper.php
  6. +52 −0 GoogleAuthenticator/InteractiveLoginListener.php
  7. +85 −0 GoogleAuthenticator/RequestListener.php
  8. +26 −0 Model/UserInterface.php
  9. +1 −0  Resources/config/doctrine/BaseUser.mongodb.xml
  10. +1 −0  Resources/config/doctrine/BaseUser.orm.xml
  11. +32 −0 Resources/config/google_authenticator.xml
  12. +3 −2 Resources/config/security_acl.xml
  13. +1 −0  Resources/doc/index.rst
  14. +46 −0 Resources/doc/reference/two_step_validation.rst
  15. +12 −0 Resources/translations/SonataUserBundle.ca.xliff
  16. +12 −0 Resources/translations/SonataUserBundle.cs.xliff
  17. +12 −0 Resources/translations/SonataUserBundle.de.xliff
  18. +12 −0 Resources/translations/SonataUserBundle.en.xliff
  19. +12 −0 Resources/translations/SonataUserBundle.es.xliff
  20. +12 −0 Resources/translations/SonataUserBundle.fr.xliff
  21. +12 −0 Resources/translations/SonataUserBundle.it.xliff
  22. +12 −0 Resources/translations/SonataUserBundle.pt.xliff
  23. +12 −0 Resources/translations/SonataUserBundle.ru.xliff
  24. +12 −0 Resources/translations/SonataUserBundle.sl.xliff
  25. +46 −0 Resources/views/Admin/Security/two_step_form.html.twig
7 DependencyInjection/Configuration.php
View
@@ -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()
31 DependencyInjection/SonataUserExtension.php
View
@@ -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']);
+
}
/**
24 Document/BaseUser.php
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;
+ }
}
24 Entity/BaseUser.php
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;
+ }
}
69 GoogleAuthenticator/Helper.php
View
@@ -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());
+ }
+}
52 GoogleAuthenticator/InteractiveLoginListener.php
View
@@ -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);
+ }
+}
85 GoogleAuthenticator/RequestListener.php
View
@@ -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
+ )));
+ }
+}
26 Model/UserInterface.php
View
@@ -0,0 +1,26 @@
+<?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\Model;
+
+interface UserInterface extends \FOS\UserBundle\Model\UserInterface
+{
+ /**
+ * @return string
+ */
+ public function getTwoStepVerificationCode();
+
+ /**
+ * @param $code
+ * @return string
+ */
+ public function setTwoStepVerificationCode($code);
+}
1  Resources/config/doctrine/BaseUser.mongodb.xml
View
@@ -7,6 +7,7 @@
<mapped-superclass name="Sonata\UserBundle\Document\BaseUser">
<field name="createdAt" type="date" />
<field name="updatedAt" type="date" />
+ <field name="twoStepVerificationCode" type="string" />
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="prePersist" />
1  Resources/config/doctrine/BaseUser.orm.xml
View
@@ -7,6 +7,7 @@
<mapped-superclass name="Sonata\UserBundle\Entity\BaseUser">
<field name="createdAt" type="datetime" column="created_at" />
<field name="updatedAt" type="datetime" column="updated_at" />
+ <field name="twoStepVerificationCode" type="string" length="255" column="two_step_code" />
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="prePersist" />
32 Resources/config/google_authenticator.xml
View
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<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="sonata.user.google.authenticator" class="Google\Authenticator\GoogleAuthenticator">
+
+ </service>
+
+ <service id="sonata.user.google.authenticator.provider" class="Sonata\UserBundle\GoogleAuthenticator\Helper">
+ <argument />
+ <argument type="service" id="sonata.user.google.authenticator" />
+ </service>
+
+ <service id="sonata.user.google.authenticator.interactive_login_listener" class="Sonata\UserBundle\GoogleAuthenticator\InteractiveLoginListener">
+ <tag name="kernel.event_listener" event="security.interactive_login" method="onSecurityInteractiveLogin" />
+
+ <argument type="service" id="sonata.user.google.authenticator.provider" />
+ </service>
+
+ <service id="sonata.user.google.authenticator.request_listener" class="Sonata\UserBundle\GoogleAuthenticator\RequestListener">
+ <tag name="kernel.event_listener" event="kernel.request" method="onCoreRequest" priority="-1"/>
+
+ <argument type="service" id="sonata.user.google.authenticator.provider" />
+ <argument type="service" id="security.context" />
+ <argument type="service" id="templating" />
+ </service>
+
+ </services>
+</container>
5 Resources/config/security_acl.xml
View
@@ -11,12 +11,13 @@
<services>
<service id="security.acl.voter.user_permissions" class="%security.acl.user_voter.class%" public="false">
<tag name="monolog.logger" channel="security" />
+ <tag name="security.voter" priority="255" />
+
<argument type="service" id="security.acl.provider" />
<argument type="service" id="security.acl.object_identity_retrieval_strategy" />
<argument type="service" id="security.acl.security_identity_retrieval_strategy" />
<argument type="service" id="security.acl.permission.map" />
<argument type="service" id="logger" on-invalid="null" />
- <tag name="security.voter" priority="255" />
</service>
- </services>
+ </services>
</container>
1  Resources/doc/index.rst
View
@@ -11,3 +11,4 @@ Reference Guide
reference/introduction
reference/installation
reference/advanced_configuration
+ reference/two_step_validation
46 Resources/doc/reference/two_step_validation.rst
View
@@ -0,0 +1,46 @@
+Two Step Validation (with Google Authenticator)
+===============================================
+
+The SonataUserBundle provides an optional layer of security by including a support for a Two Step Validation process.
+
+When the option is enabled, the login process is done with the following workflow :
+
+* the user enters the login and password
+* if the user get the correct credentials, then
+* a code validation form is diplayed
+* at this point the user must enter a time based code provided by the Google Authenticator application
+* the code is valid only once per minute
+
+So if your login and password are compromised then the hacker must also hold your phone!
+
+
+Installation
+------------
+
+Add the following lines to the file ``deps``::
+
+ [SonataUserBundle]
+ git=git://github.com/sonata-project/SonataUserBundle.git
+ target=/bundles/Sonata/UserBundle
+
+Update the autoload.php file::
+
+ // app/autoload.php
+ $loader->registerNamespaces(array(
+ // ...
+ 'Google' => __DIR__.'/../vendor/google-authenticator/lib'
+ // ...
+ ));
+
+Edit the configuration file
+
+.. code-block:: yaml
+
+ # app/config/config.yml
+
+ sonata_user:
+ google_authenticator:
+ enabled: true
+ server: yourserver.com
+
+Now if the ``User::twoStepVerificationCode`` property is not null, then a second form will be displayed.
12 Resources/translations/SonataUserBundle.ca.xliff
View
@@ -46,6 +46,18 @@
<source>user_block_logout</source>
<target>Sortir</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.cs.xliff
View
@@ -142,6 +142,18 @@
<source>list.label_roles</source>
<target>Role</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.de.xliff
View
@@ -138,6 +138,18 @@
<source>list.label_roles</source>
<target>Rollen</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.en.xliff
View
@@ -142,6 +142,18 @@
<source>list.label_roles</source>
<target>Roles</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>Two-step verification</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>Enter the verification code generated by your mobile application.</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>The verification code is not valid</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.es.xliff
View
@@ -142,6 +142,18 @@
<source>list.label_roles</source>
<target>Perfiles</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.fr.xliff
View
@@ -138,6 +138,18 @@
<source>list.label_roles</source>
<target>Rôles</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>Validation en deux étapes</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>Rentrer votre code de validation provenant de votre téléphone</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>Le code de validation est invalide</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.it.xliff
View
@@ -142,6 +142,18 @@
<source>list.label_roles</source>
<target>Ruoli</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.pt.xliff
View
@@ -142,6 +142,18 @@
<source>list.label_roles</source>
<target>Perfís</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.ru.xliff
View
@@ -138,6 +138,18 @@
<source>list.label_roles</source>
<target>Роли</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
12 Resources/translations/SonataUserBundle.sl.xliff
View
@@ -35,6 +35,18 @@
<source>groups</source>
<target>Skupine</target>
</trans-unit>
+ <trans-unit id="label_two_step_code">
+ <source>label_two_step_code</source>
+ <target>label_two_step_code</target>
+ </trans-unit>
+ <trans-unit id="message_two_step_code_help">
+ <source>message_two_step_code_help</source>
+ <target>message_two_step_code_help</target>
+ </trans-unit>
+ <trans-unit id="label_two_step_code_error">
+ <source>label_two_step_code_error</source>
+ <target>label_two_step_code_error</target>
+ </trans-unit>
</body>
</file>
</xliff>
46 Resources/views/Admin/Security/two_step_form.html.twig
View
@@ -0,0 +1,46 @@
+{#
+
+This file is part of the Sonata package.
+
+(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.
+
+#}
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" href="{{ asset('bundles/sonataadmin/bootstrap/bootstrap.css') }}" type="text/css" media="all" >
+ <link rel="stylesheet" href="/bundles/sonataadmin/css/layout.css" type="text/css" media="all">
+ <link rel="stylesheet" href="/bundles/sonataadmin/css/colors.css" type="text/css" media="all">
+ </head>
+
+ <body class="sonata-bc">
+ <div class="container-fluid">
+ <div class="row">
+ <div class="span8 offset5 border connection">
+
+ <form method="POST" class="form-stacked">
+ {% if state == 'error' %}
+ <div class="alert-message error">{{ 'label_two_step_code_error'|trans({}, 'SonataUserBundle') }}</div>
+ {% endif %}
+
+ <div class="clearfix">
+ <label for="code">{{ 'label_two_step_code'|trans({}, 'SonataUserBundle') }}</label>
+
+ <div class="input">
+ <input type="text" id="username" name="_code" class="big sonata-medium" autocomplete='off'/>
+ <span class="help-block sonata-ba-field-help">{{ 'message_two_step_code_help'|trans({}, 'SonataUserBundle') }}</span>
+ </div>
+ </div>
+
+ <div class="actions">
+ <input type="submit" class="btn primary" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}" />
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
Please sign in to comment.
Something went wrong with that request. Please try again.