resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
prefix: /api/doc
+login:
+ pattern: /login
+ defaults: { _controller: WallabagCoreBundle:Security:login }
+login_check:
+ pattern: /login_check
+logout:
+ path: /logout
+
#wallabag_api:
# resource: "@WallabagApiBundle/Controller/"
# type: annotation
-# you can read more about security in the related section of the documentation
-# http://symfony.com/doc/current/book/security.html
security:
- # http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
encoders:
- Symfony\Component\Security\Core\User\User: plaintext
+ Wallabag\CoreBundle\Entity\Users:
+ algorithm: sha1
+ encode_as_base64: false
+ iterations: 1
- # http://symfony.com/doc/current/book/security.html#hierarchical-roles
role_hierarchy:
ROLE_ADMIN: ROLE_USER
- ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
+ ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
- # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
- in_memory:
- memory:
- users:
- user: { password: userpass, roles: [ 'ROLE_USER' ] }
- admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
+ administrators:
+ entity: { class: WallabagCoreBundle:Users, property: username }
# the main part of the security, where you can set up firewalls
# for specific sections of your app
firewalls:
- # disables authentication for assets and the profiler, adapt it according to your needs
- dev:
- pattern: ^/(_(profiler|wdt)|css|images|js)/
- security: false
- # the login page has to be accessible for everybody
- demo_login:
- pattern: ^/demo/secured/login$
- security: false
-
- # secures part of the application
- demo_secured_area:
- pattern: ^/demo/secured/
- # it's important to notice that in this case _demo_security_check and _demo_login
- # are route names and that they are specified in the AcmeDemoBundle
+ #wsse_secured:
+ # pattern: /api/.*
+ # wsse: true
+ login_firewall:
+ pattern: ^/login$
+ anonymous: ~
+
+ secured_area:
+ pattern: ^/
+ anonymous: ~
form_login:
- check_path: _demo_security_check
- login_path: _demo_login
+ login_path: /login
+
+ use_forward: false
+
+ check_path: /login_check
+
+ post_only: true
+
+ always_use_default_target_path: true
+ default_target_path: /
+ target_path_parameter: redirect_url
+ use_referer: true
+
+ failure_path: null
+ failure_forward: false
+
+ username_parameter: _username
+ password_parameter: _password
+
+ csrf_parameter: _csrf_token
+ intention: authenticate
+
logout:
- path: _demo_logout
- target: _demo
- #anonymous: ~
- #http_basic:
- # realm: "Secured Demo Area"
-
- # with these settings you can restrict or allow access for different parts
- # of your application based on roles, ip, host or methods
- # http://symfony.com/doc/current/cookbook/security/access_control.html
+ path: /logout
+ target: /
+
access_control:
- #- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
\ No newline at end of file
+ - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
+ - { path: ^/, roles: ROLE_USER }
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\SecurityContext;
+
+class SecurityController extends Controller
+{
+ public function loginAction(Request $request)
+ {
+ $session = $request->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
*/
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();
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\DependencyInjection\Security\Factory;
+
+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;
+
+class WsseFactory implements SecurityFactoryInterface
+{
+ public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
+ {
+ $providerId = 'security.authentication.provider.wsse.'.$id;
+ $container
+ ->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
*
* @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntriesRepository")
* @ORM\Table(name="entries")
+ *
*/
class Entries
{
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
* @ORM\Table(name="users")
* @ORM\Entity
*/
-class Users
+class Users implements AdvancedUserInterface, \Serializable
{
/**
* @var integer
*/
private $username;
+ /**
+ * @ORM\Column(type="string", length=32)
+ */
+ private $salt;
+
/**
* @var string
*
*/
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
return $this->username;
}
+ /**
+ * @inheritDoc
+ */
+ public function getSalt()
+ {
+ return $this->salt;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getRoles()
+ {
+ return array('ROLE_USER');
+ }
+
/**
* Set password
*
{
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;
+ }
}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Helper;
+
+
+class Entries {
+
+
+
+}
\ No newline at end of file
use Doctrine\ORM\Query;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\Pagination\Paginator;
+use Wallabag\CoreBundle\Entity\Entries;
+use Wallabag\CoreBundle\Service\Extractor;
class EntriesRepository extends EntityRepository
{
public function findEntries($userId, $isArchived, $isStarred, $isDeleted, $sort, $order)
{
+ //TODO tous les paramètres ne sont pas utilisés, à corriger
$qb = $this->createQueryBuilder('e')
->select('e')
->where('e.isFav =:isStarred')->setParameter('isStarred', $isStarred)
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
+ <!-- Twig -->
<service id="wallabag_core.twig.wallabag" class="Wallabag\CoreBundle\Twig\Extension\WallabagExtension">
<tag name="twig.extension" />
</service>
- </services>
+ <!-- Security -->
+ <service id="wsse.security.authentication.provider"
+ class="Wallabag\CoreBundle\Security\Authentication\Provider\WsseProvider" public="false">
+ <argument /> <!-- User Provider -->
+ <argument>%kernel.cache_dir%/security/nonces</argument>
+ </service>
+ <service id="wsse.security.authentication.listener"
+ class="Wallabag\CoreBundle\Security\Firewall\WsseListener" public="false">
+ <argument type="service" id="security.context"/>
+ <argument type="service" id="security.authentication.manager" />
+ </service>
+ </services>
</container>
--- /dev/null
+{% extends "WallabagCoreBundle::layout-login.html.twig" %}
+
+{% block title %}{% trans %}login to your wallabag{% endtrans %}{% endblock %}
+{% block content %}
+ {% if error %}
+ <div>{{ error.message }}</div>
+ {% endif %}
+
+ <form action="{{ path('login_check') }}" method="post" name="loginform">
+ <fieldset class="w500p center">
+ <h2 class="mbs txtcenter">{% trans %}Login to wallabag{% endtrans %}</h2>
+
+ <div class="row">
+ <label class="col w150p" for="username">{% trans %}Username{% endtrans %}</label>
+ <input type="text" id="username" name="_username" value="{{ last_username }}" />
+ </div>
+
+ <div class="row">
+ <label class="col w150p" for="password">{% trans %}Password{% endtrans %}</label>
+ <input type="password" id="password" name="_password" />
+ </div>
+ {#
+ Si vous voulez contrôler l'URL vers laquelle l'utilisateur est redirigé en cas de succès
+ (plus de détails ci-dessous)
+ <input type="hidden" name="_target_path" value="/account" />
+ #}
+ <div class="row mts txtcenter">
+ <button type="submit">login</button>
+ </div>
+ </fieldset>
+ </form>
+{% endblock %}
</li>
<li><a href="?view=config">{% trans %}config{% endtrans %}</a></li>
<li><a href={{ path('about') }}>{% trans %}about{% endtrans %}</a></li>
- <li><a class="icon icon-power" href="?logout" title="{% trans %}logout{% endtrans %}">{% trans %}logout{% endtrans %}</a></li>
+ <li><a class="icon icon-power" href="{{ path('logout') }}" title="{% trans %}logout{% endtrans %}">{% trans %}logout{% endtrans %}</a></li>
</ul>
--- /dev/null
+<!DOCTYPE html>
+<!--[if lte IE 6]><html class="no-js ie6 ie67 ie678" lang="en"><![endif]-->
+<!--[if lte IE 7]><html class="no-js ie7 ie67 ie678" lang="en"><![endif]-->
+<!--[if IE 8]><html class="no-js ie8 ie678" lang="en"><![endif]-->
+<!--[if gt IE 8]><html class="no-js" lang="en"><![endif]-->
+<html lang="en">
+ <head>
+ <meta name="viewport" content="initial-scale=1.0">
+ <meta charset="utf-8">
+ <!--[if IE]>
+ <meta http-equiv="X-UA-Compatible" content="IE=10">
+ <![endif]-->
+ <title>{% block title %}{% endblock %} - wallabag</title>
+ {% include "WallabagCoreBundle::_head.html.twig" %}
+ </head>
+ <body class="login">
+ {% include "WallabagCoreBundle::_top.html.twig" %}
+ <div id="main">
+ {% block menu %}{% endblock %}
+ <div id="content" class="w600p center">
+ {% block content %}{% endblock %}
+ </div>
+ </div>
+ {% include "WallabagCoreBundle::_footer.html.twig" %}
+ </body>
+</html>
\ No newline at end of file
<!--[if IE 8]><html class="no-js ie8 ie678" lang="en"><![endif]-->
<!--[if gt IE 8]><html class="no-js" lang="en"><![endif]-->
<html lang="en">
-<head>
- <meta name="viewport" content="initial-scale=1.0">
- <meta charset="utf-8">
- <!--[if IE]>
- <meta http-equiv="X-UA-Compatible" content="IE=10">
- <![endif]-->
- <title>{% block title %}{% endblock %} - wallabag</title>
- {% include "WallabagCoreBundle::_head.html.twig" %}
- {% include "WallabagCoreBundle::_bookmarklet.html.twig" %}
-</head>
-<body>
-{% include "WallabagCoreBundle::_top.html.twig" %}
-<div id="main">
- {% block menu %}{% endblock %}
- {% block precontent %}{% endblock %}
- {% for flashMessage in app.session.flashbag.get('notice') %}
- <div class="flash-notice">
- {{ flashMessage }}
+ <head>
+ <meta name="viewport" content="initial-scale=1.0">
+ <meta charset="utf-8">
+ <!--[if IE]>
+ <meta http-equiv="X-UA-Compatible" content="IE=10">
+ <![endif]-->
+ <title>{% block title %}{% endblock %} - wallabag</title>
+ {% include "WallabagCoreBundle::_head.html.twig" %}
+ {% include "WallabagCoreBundle::_bookmarklet.html.twig" %}
+ </head>
+ <body>
+ {% include "WallabagCoreBundle::_top.html.twig" %}
+ <div id="main">
+ {% block menu %}{% endblock %}
+ {% block precontent %}{% endblock %}
+ {% for flashMessage in app.session.flashbag.get('notice') %}
+ <div class="flash-notice">
+ {{ flashMessage }}
+ </div>
+ {% endfor %}
+ <div id="content" class="w600p center">
+ {% block content %}{% endblock %}
</div>
- {% endfor %}
- <div id="content" class="w600p center">
- {% block content %}{% endblock %}
</div>
-</div>
-{% include "WallabagCoreBundle::_footer.html.twig" %}
-</body>
+ {% include "WallabagCoreBundle::_footer.html.twig" %}
+ </body>
</html>
\ No newline at end of file
--- /dev/null
+<?php
+namespace Wallabag\CoreBundle\Security\Authentication\Provider;
+
+use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
+use Symfony\Component\Security\Core\User\UserProviderInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\Exception\NonceExpiredException;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Wallabag\CoreBundle\Security\Authentication\Token\WsseUserToken;
+
+class WsseProvider implements AuthenticationProviderInterface
+{
+ private $userProvider;
+ private $cacheDir;
+
+ public function __construct(UserProviderInterface $userProvider, $cacheDir)
+ {
+ $this->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
--- /dev/null
+<?php
+namespace Wallabag\CoreBundle\Security\Authentication\Token;
+
+use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
+
+class WsseUserToken extends AbstractToken
+{
+ public $created;
+ public $digest;
+ public $nonce;
+
+ public function __construct(array $roles = array())
+ {
+ parent::__construct($roles);
+
+ $this->setAuthenticated(count($roles) > 0);
+ }
+
+ public function getCredentials()
+ {
+ return '';
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Security\Firewall;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+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 Wallabag\CoreBundle\Security\Authentication\Token\WsseUserToken;
+
+class WsseListener implements ListenerInterface
+{
+ protected $securityContext;
+ protected $authenticationManager;
+
+ public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager)
+ {
+ $this->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
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());
+ }
}