From c3235553ddc2bb5965f6fe00e750cfe4aac9ccdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Sat, 31 Jan 2015 15:14:10 +0100 Subject: first implementation of security --- .../CoreBundle/Controller/SecurityController.php | 27 +++++++ .../Controller/WallabagRestController.php | 9 ++- .../Security/Factory/WsseFactory.php | 40 ++++++++++ src/Wallabag/CoreBundle/Entity/Entries.php | 1 + src/Wallabag/CoreBundle/Entity/Users.php | 87 +++++++++++++++++++++- src/Wallabag/CoreBundle/Helper/Entries.php | 10 +++ .../CoreBundle/Repository/EntriesRepository.php | 3 + .../CoreBundle/Resources/config/services.xml | 14 +++- .../Resources/views/Security/login.html.twig | 32 ++++++++ .../CoreBundle/Resources/views/_menu.html.twig | 2 +- .../Resources/views/layout-login.html.twig | 26 +++++++ .../CoreBundle/Resources/views/layout.html.twig | 48 ++++++------ .../Authentication/Provider/WsseProvider.php | 59 +++++++++++++++ .../Authentication/Token/WsseUserToken.php | 23 ++++++ .../CoreBundle/Security/Firewall/WsseListener.php | 58 +++++++++++++++ src/Wallabag/CoreBundle/WallabagCoreBundle.php | 9 +++ 16 files changed, 417 insertions(+), 31 deletions(-) create mode 100644 src/Wallabag/CoreBundle/Controller/SecurityController.php create mode 100644 src/Wallabag/CoreBundle/DependencyInjection/Security/Factory/WsseFactory.php create mode 100644 src/Wallabag/CoreBundle/Helper/Entries.php create mode 100644 src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig create mode 100644 src/Wallabag/CoreBundle/Resources/views/layout-login.html.twig create mode 100644 src/Wallabag/CoreBundle/Security/Authentication/Provider/WsseProvider.php create mode 100644 src/Wallabag/CoreBundle/Security/Authentication/Token/WsseUserToken.php create mode 100644 src/Wallabag/CoreBundle/Security/Firewall/WsseListener.php (limited to 'src') diff --git a/src/Wallabag/CoreBundle/Controller/SecurityController.php b/src/Wallabag/CoreBundle/Controller/SecurityController.php new file mode 100644 index 00000000..51f9cc26 --- /dev/null +++ b/src/Wallabag/CoreBundle/Controller/SecurityController.php @@ -0,0 +1,27 @@ +getSession(); + // get the login error if there is one + if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR); + } else { + $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); + $session->remove(SecurityContext::AUTHENTICATION_ERROR); + } + return $this->render('WallabagCoreBundle:Security:login.html.twig', array( + // last username entered by the user + 'last_username' => $session->get(SecurityContext::LAST_USERNAME), + 'error' => $error, + )); + } +} \ No newline at end of file diff --git a/src/Wallabag/CoreBundle/Controller/WallabagRestController.php b/src/Wallabag/CoreBundle/Controller/WallabagRestController.php index a6c0db37..8e018e88 100644 --- a/src/Wallabag/CoreBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/CoreBundle/Controller/WallabagRestController.php @@ -82,17 +82,18 @@ class WallabagRestController extends Controller */ public function postEntriesAction(Request $request) { - //TODO la récup ne marche + //TODO la récup ne marche pas //TODO gérer si on passe le titre //TODO gérer si on passe les tags //TODO ne pas avoir du code comme ça qui doit se trouver dans le Repository + $url = $request->request->get('url'); + + $content = Extractor::extract($url); $entry = new Entries(); $entry->setUserId(1); - $content = Extractor::extract($request->request->get('url')); - + $entry->setUrl($url); $entry->setTitle($content->getTitle()); $entry->setContent($content->getBody()); - $em = $this->getDoctrine()->getManager(); $em->persist($entry); $em->flush(); diff --git a/src/Wallabag/CoreBundle/DependencyInjection/Security/Factory/WsseFactory.php b/src/Wallabag/CoreBundle/DependencyInjection/Security/Factory/WsseFactory.php new file mode 100644 index 00000000..9807fe9a --- /dev/null +++ b/src/Wallabag/CoreBundle/DependencyInjection/Security/Factory/WsseFactory.php @@ -0,0 +1,40 @@ +setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider')) + ->replaceArgument(0, new Reference($userProvider)) + ; + + $listenerId = 'security.authentication.listener.wsse.'.$id; + $listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener')); + + return array($providerId, $listenerId, $defaultEntryPoint); + } + + public function getPosition() + { + return 'pre_auth'; + } + + public function getKey() + { + return 'wsse'; + } + + public function addConfiguration(NodeDefinition $node) + { + } +} \ No newline at end of file diff --git a/src/Wallabag/CoreBundle/Entity/Entries.php b/src/Wallabag/CoreBundle/Entity/Entries.php index 712ff126..3c061a37 100644 --- a/src/Wallabag/CoreBundle/Entity/Entries.php +++ b/src/Wallabag/CoreBundle/Entity/Entries.php @@ -10,6 +10,7 @@ use Symfony\Component\Validator\Constraints as Assert; * * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntriesRepository") * @ORM\Table(name="entries") + * */ class Entries { diff --git a/src/Wallabag/CoreBundle/Entity/Users.php b/src/Wallabag/CoreBundle/Entity/Users.php index 3db4a3fd..96867bd6 100644 --- a/src/Wallabag/CoreBundle/Entity/Users.php +++ b/src/Wallabag/CoreBundle/Entity/Users.php @@ -3,6 +3,9 @@ namespace Wallabag\CoreBundle\Entity; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\EquatableInterface; +use Symfony\Component\Security\Core\User\AdvancedUserInterface; /** * Users @@ -10,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM; * @ORM\Table(name="users") * @ORM\Entity */ -class Users +class Users implements AdvancedUserInterface, \Serializable { /** * @var integer @@ -28,6 +31,11 @@ class Users */ private $username; + /** + * @ORM\Column(type="string", length=32) + */ + private $salt; + /** * @var string * @@ -49,7 +57,16 @@ class Users */ private $email; + /** + * @ORM\Column(name="is_active", type="boolean") + */ + private $isActive; + public function __construct() + { + $this->isActive = true; + $this->salt = md5(uniqid(null, true)); + } /** * Get id @@ -84,6 +101,22 @@ class Users return $this->username; } + /** + * @inheritDoc + */ + public function getSalt() + { + return $this->salt; + } + + /** + * @inheritDoc + */ + public function getRoles() + { + return array('ROLE_USER'); + } + /** * Set password * @@ -152,4 +185,56 @@ class Users { return $this->email; } + + /** + * @inheritDoc + */ + public function eraseCredentials() + { + } + + /** + * @see \Serializable::serialize() + */ + public function serialize() + { + return serialize(array( + $this->id, + )); + } + + /** + * @see \Serializable::unserialize() + */ + public function unserialize($serialized) + { + list ( + $this->id, + ) = unserialize($serialized); + } + + public function isEqualTo(UserInterface $user) + { + return $this->username === $user->getUsername(); + } + + public function isAccountNonExpired() + { + return true; + } + + public function isAccountNonLocked() + { + return true; + } + + public function isCredentialsNonExpired() + { + return true; + } + + public function isEnabled() + { + return $this->isActive; + } } diff --git a/src/Wallabag/CoreBundle/Helper/Entries.php b/src/Wallabag/CoreBundle/Helper/Entries.php new file mode 100644 index 00000000..a54c3a74 --- /dev/null +++ b/src/Wallabag/CoreBundle/Helper/Entries.php @@ -0,0 +1,10 @@ +createQueryBuilder('e') ->select('e') ->where('e.isFav =:isStarred')->setParameter('isStarred', $isStarred) diff --git a/src/Wallabag/CoreBundle/Resources/config/services.xml b/src/Wallabag/CoreBundle/Resources/config/services.xml index 02308e6a..d5bc5cca 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.xml +++ b/src/Wallabag/CoreBundle/Resources/config/services.xml @@ -5,12 +5,24 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + - + + + + %kernel.cache_dir%/security/nonces + + + + + + diff --git a/src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig b/src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig new file mode 100644 index 00000000..2437e3b0 --- /dev/null +++ b/src/Wallabag/CoreBundle/Resources/views/Security/login.html.twig @@ -0,0 +1,32 @@ +{% extends "WallabagCoreBundle::layout-login.html.twig" %} + +{% block title %}{% trans %}login to your wallabag{% endtrans %}{% endblock %} +{% block content %} + {% if error %} +
{{ error.message }}
+ {% endif %} + +
+
+

{% trans %}Login to wallabag{% endtrans %}

+ +
+ + +
+ +
+ + +
+ {# + Si vous voulez contrôler l'URL vers laquelle l'utilisateur est redirigé en cas de succès + (plus de détails ci-dessous) + + #} +
+ +
+
+
+{% endblock %} diff --git a/src/Wallabag/CoreBundle/Resources/views/_menu.html.twig b/src/Wallabag/CoreBundle/Resources/views/_menu.html.twig index d4560e84..3065ce44 100644 --- a/src/Wallabag/CoreBundle/Resources/views/_menu.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/_menu.html.twig @@ -10,6 +10,6 @@
  • {% trans %}config{% endtrans %}
  • {% trans %}about{% endtrans %}
  • -
  • {% trans %}logout{% endtrans %}
  • +
  • {% trans %}logout{% endtrans %}
  • diff --git a/src/Wallabag/CoreBundle/Resources/views/layout-login.html.twig b/src/Wallabag/CoreBundle/Resources/views/layout-login.html.twig new file mode 100644 index 00000000..9c39ea9f --- /dev/null +++ b/src/Wallabag/CoreBundle/Resources/views/layout-login.html.twig @@ -0,0 +1,26 @@ + + + + + + + + + + + {% block title %}{% endblock %} - wallabag + {% include "WallabagCoreBundle::_head.html.twig" %} + + + {% include "WallabagCoreBundle::_top.html.twig" %} +
    + {% block menu %}{% endblock %} +
    + {% block content %}{% endblock %} +
    +
    + {% include "WallabagCoreBundle::_footer.html.twig" %} + + \ No newline at end of file diff --git a/src/Wallabag/CoreBundle/Resources/views/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/layout.html.twig index 83830a4a..1f1753a4 100644 --- a/src/Wallabag/CoreBundle/Resources/views/layout.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/layout.html.twig @@ -4,30 +4,30 @@ - - - - - {% block title %}{% endblock %} - wallabag - {% include "WallabagCoreBundle::_head.html.twig" %} - {% include "WallabagCoreBundle::_bookmarklet.html.twig" %} - - -{% include "WallabagCoreBundle::_top.html.twig" %} -
    - {% block menu %}{% endblock %} - {% block precontent %}{% endblock %} - {% for flashMessage in app.session.flashbag.get('notice') %} -
    - {{ flashMessage }} + + + + + {% block title %}{% endblock %} - wallabag + {% include "WallabagCoreBundle::_head.html.twig" %} + {% include "WallabagCoreBundle::_bookmarklet.html.twig" %} + + + {% include "WallabagCoreBundle::_top.html.twig" %} +
    + {% block menu %}{% endblock %} + {% block precontent %}{% endblock %} + {% for flashMessage in app.session.flashbag.get('notice') %} +
    + {{ flashMessage }} +
    + {% endfor %} +
    + {% block content %}{% endblock %}
    - {% endfor %} -
    - {% block content %}{% endblock %}
    -
    -{% include "WallabagCoreBundle::_footer.html.twig" %} - + {% include "WallabagCoreBundle::_footer.html.twig" %} + \ No newline at end of file diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Provider/WsseProvider.php b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WsseProvider.php new file mode 100644 index 00000000..ac57e27d --- /dev/null +++ b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WsseProvider.php @@ -0,0 +1,59 @@ +userProvider = $userProvider; + $this->cacheDir = $cacheDir; + } + + public function authenticate(TokenInterface $token) + { + $user = $this->userProvider->loadUserByUsername($token->getUsername()); + + if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) { + $authenticatedToken = new WsseUserToken($user->getRoles()); + $authenticatedToken->setUser($user); + + return $authenticatedToken; + } + + throw new AuthenticationException('The WSSE authentication failed.'); + } + + protected function validateDigest($digest, $nonce, $created, $secret) + { + // Expire le timestamp après 5 minutes + if (time() - strtotime($created) > 300) { + return false; + } + + // Valide que le nonce est unique dans les 5 minutes + if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) { + throw new NonceExpiredException('Previously used nonce detected'); + } + file_put_contents($this->cacheDir.'/'.$nonce, time()); + + // Valide le Secret + $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true)); + + return $digest === $expected; + } + + public function supports(TokenInterface $token) + { + return $token instanceof WsseUserToken; + } +} \ No newline at end of file diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Token/WsseUserToken.php b/src/Wallabag/CoreBundle/Security/Authentication/Token/WsseUserToken.php new file mode 100644 index 00000000..b189e22a --- /dev/null +++ b/src/Wallabag/CoreBundle/Security/Authentication/Token/WsseUserToken.php @@ -0,0 +1,23 @@ +setAuthenticated(count($roles) > 0); + } + + public function getCredentials() + { + return ''; + } +} \ No newline at end of file diff --git a/src/Wallabag/CoreBundle/Security/Firewall/WsseListener.php b/src/Wallabag/CoreBundle/Security/Firewall/WsseListener.php new file mode 100644 index 00000000..b2474785 --- /dev/null +++ b/src/Wallabag/CoreBundle/Security/Firewall/WsseListener.php @@ -0,0 +1,58 @@ +securityContext = $securityContext; + $this->authenticationManager = $authenticationManager; + } + + public function handle(GetResponseEvent $event) + { + $request = $event->getRequest(); + + $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/'; + if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) { + return; + } + + $token = new WsseUserToken(); + $token->setUser($matches[1]); + + $token->digest = $matches[2]; + $token->nonce = $matches[3]; + $token->created = $matches[4]; + + try { + $authToken = $this->authenticationManager->authenticate($token); + + $this->securityContext->setToken($authToken); + } catch (AuthenticationException $failed) { + // ... you might log something here + + // To deny the authentication clear the token. This will redirect to the login page. + // $this->securityContext->setToken(null); + // return; + + // Deny authentication with a '403 Forbidden' HTTP response + $response = new Response(); + $response->setStatusCode(403); + $event->setResponse($response); + + } + } +} \ No newline at end of file diff --git a/src/Wallabag/CoreBundle/WallabagCoreBundle.php b/src/Wallabag/CoreBundle/WallabagCoreBundle.php index f5899e39..1deab03a 100644 --- a/src/Wallabag/CoreBundle/WallabagCoreBundle.php +++ b/src/Wallabag/CoreBundle/WallabagCoreBundle.php @@ -3,7 +3,16 @@ namespace Wallabag\CoreBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Wallabag\CoreBundle\DependencyInjection\Security\Factory\WsseFactory; +use Symfony\Component\DependencyInjection\ContainerBuilder; class WallabagCoreBundle extends Bundle { + public function build(ContainerBuilder $container) + { + parent::build($container); + + $extension = $container->getExtension('security'); + $extension->addSecurityListenerFactory(new WsseFactory()); + } } -- cgit v1.2.3