mirror of
https://github.com/wallabag/wallabag.git
synced 2024-11-26 19:11:07 +00:00
first implementation of security
This commit is contained in:
parent
71691fe44a
commit
c3235553dd
18 changed files with 469 additions and 69 deletions
|
@ -10,6 +10,14 @@ doc-api:
|
||||||
resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
|
resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
|
||||||
prefix: /api/doc
|
prefix: /api/doc
|
||||||
|
|
||||||
|
login:
|
||||||
|
pattern: /login
|
||||||
|
defaults: { _controller: WallabagCoreBundle:Security:login }
|
||||||
|
login_check:
|
||||||
|
pattern: /login_check
|
||||||
|
logout:
|
||||||
|
path: /logout
|
||||||
|
|
||||||
#wallabag_api:
|
#wallabag_api:
|
||||||
# resource: "@WallabagApiBundle/Controller/"
|
# resource: "@WallabagApiBundle/Controller/"
|
||||||
# type: annotation
|
# type: annotation
|
||||||
|
|
|
@ -1,52 +1,58 @@
|
||||||
# you can read more about security in the related section of the documentation
|
|
||||||
# http://symfony.com/doc/current/book/security.html
|
|
||||||
security:
|
security:
|
||||||
# http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
|
|
||||||
encoders:
|
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_hierarchy:
|
||||||
ROLE_ADMIN: ROLE_USER
|
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:
|
providers:
|
||||||
in_memory:
|
administrators:
|
||||||
memory:
|
entity: { class: WallabagCoreBundle:Users, property: username }
|
||||||
users:
|
|
||||||
user: { password: userpass, roles: [ 'ROLE_USER' ] }
|
|
||||||
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
|
|
||||||
|
|
||||||
# the main part of the security, where you can set up firewalls
|
# the main part of the security, where you can set up firewalls
|
||||||
# for specific sections of your app
|
# for specific sections of your app
|
||||||
firewalls:
|
firewalls:
|
||||||
# disables authentication for assets and the profiler, adapt it according to your needs
|
#wsse_secured:
|
||||||
dev:
|
# pattern: /api/.*
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
# wsse: true
|
||||||
security: false
|
login_firewall:
|
||||||
# the login page has to be accessible for everybody
|
pattern: ^/login$
|
||||||
demo_login:
|
anonymous: ~
|
||||||
pattern: ^/demo/secured/login$
|
|
||||||
security: false
|
|
||||||
|
|
||||||
# secures part of the application
|
secured_area:
|
||||||
demo_secured_area:
|
pattern: ^/
|
||||||
pattern: ^/demo/secured/
|
anonymous: ~
|
||||||
# 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
|
|
||||||
form_login:
|
form_login:
|
||||||
check_path: _demo_security_check
|
login_path: /login
|
||||||
login_path: _demo_login
|
|
||||||
logout:
|
use_forward: false
|
||||||
path: _demo_logout
|
|
||||||
target: _demo
|
check_path: /login_check
|
||||||
#anonymous: ~
|
|
||||||
#http_basic:
|
post_only: true
|
||||||
# realm: "Secured Demo Area"
|
|
||||||
|
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: /logout
|
||||||
|
target: /
|
||||||
|
|
||||||
# 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
|
|
||||||
access_control:
|
access_control:
|
||||||
#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
|
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
- { path: ^/, roles: ROLE_USER }
|
||||||
|
|
27
src/Wallabag/CoreBundle/Controller/SecurityController.php
Normal file
27
src/Wallabag/CoreBundle/Controller/SecurityController.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?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,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -82,17 +82,18 @@ class WallabagRestController extends Controller
|
||||||
*/
|
*/
|
||||||
public function postEntriesAction(Request $request)
|
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 le titre
|
||||||
//TODO gérer si on passe les tags
|
//TODO gérer si on passe les tags
|
||||||
//TODO ne pas avoir du code comme ça qui doit se trouver dans le Repository
|
//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 = new Entries();
|
||||||
$entry->setUserId(1);
|
$entry->setUserId(1);
|
||||||
$content = Extractor::extract($request->request->get('url'));
|
$entry->setUrl($url);
|
||||||
|
|
||||||
$entry->setTitle($content->getTitle());
|
$entry->setTitle($content->getTitle());
|
||||||
$entry->setContent($content->getBody());
|
$entry->setContent($content->getBody());
|
||||||
|
|
||||||
$em = $this->getDoctrine()->getManager();
|
$em = $this->getDoctrine()->getManager();
|
||||||
$em->persist($entry);
|
$em->persist($entry);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||||
*
|
*
|
||||||
* @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntriesRepository")
|
* @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntriesRepository")
|
||||||
* @ORM\Table(name="entries")
|
* @ORM\Table(name="entries")
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
class Entries
|
class Entries
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
namespace Wallabag\CoreBundle\Entity;
|
namespace Wallabag\CoreBundle\Entity;
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
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
|
* Users
|
||||||
|
@ -10,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
* @ORM\Table(name="users")
|
* @ORM\Table(name="users")
|
||||||
* @ORM\Entity
|
* @ORM\Entity
|
||||||
*/
|
*/
|
||||||
class Users
|
class Users implements AdvancedUserInterface, \Serializable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var integer
|
* @var integer
|
||||||
|
@ -28,6 +31,11 @@ class Users
|
||||||
*/
|
*/
|
||||||
private $username;
|
private $username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=32)
|
||||||
|
*/
|
||||||
|
private $salt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
|
@ -49,7 +57,16 @@ class Users
|
||||||
*/
|
*/
|
||||||
private $email;
|
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
|
* Get id
|
||||||
|
@ -84,6 +101,22 @@ class Users
|
||||||
return $this->username;
|
return $this->username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getSalt()
|
||||||
|
{
|
||||||
|
return $this->salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getRoles()
|
||||||
|
{
|
||||||
|
return array('ROLE_USER');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set password
|
* Set password
|
||||||
*
|
*
|
||||||
|
@ -152,4 +185,56 @@ class Users
|
||||||
{
|
{
|
||||||
return $this->email;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/Wallabag/CoreBundle/Helper/Entries.php
Normal file
10
src/Wallabag/CoreBundle/Helper/Entries.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Wallabag\CoreBundle\Helper;
|
||||||
|
|
||||||
|
|
||||||
|
class Entries {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ namespace Wallabag\CoreBundle\Repository;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||||
|
use Wallabag\CoreBundle\Entity\Entries;
|
||||||
|
use Wallabag\CoreBundle\Service\Extractor;
|
||||||
|
|
||||||
class EntriesRepository extends EntityRepository
|
class EntriesRepository extends EntityRepository
|
||||||
{
|
{
|
||||||
|
@ -79,6 +81,7 @@ class EntriesRepository extends EntityRepository
|
||||||
|
|
||||||
public function findEntries($userId, $isArchived, $isStarred, $isDeleted, $sort, $order)
|
public function findEntries($userId, $isArchived, $isStarred, $isDeleted, $sort, $order)
|
||||||
{
|
{
|
||||||
|
//TODO tous les paramètres ne sont pas utilisés, à corriger
|
||||||
$qb = $this->createQueryBuilder('e')
|
$qb = $this->createQueryBuilder('e')
|
||||||
->select('e')
|
->select('e')
|
||||||
->where('e.isFav =:isStarred')->setParameter('isStarred', $isStarred)
|
->where('e.isFav =:isStarred')->setParameter('isStarred', $isStarred)
|
||||||
|
|
|
@ -5,13 +5,25 @@
|
||||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
|
||||||
<services>
|
<services>
|
||||||
|
<!-- Twig -->
|
||||||
<service id="wallabag_core.twig.wallabag" class="Wallabag\CoreBundle\Twig\Extension\WallabagExtension">
|
<service id="wallabag_core.twig.wallabag" class="Wallabag\CoreBundle\Twig\Extension\WallabagExtension">
|
||||||
<tag name="twig.extension" />
|
<tag name="twig.extension" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<!-- 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>
|
</services>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</container>
|
</container>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% 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 %}
|
|
@ -10,6 +10,6 @@
|
||||||
</li>
|
</li>
|
||||||
<li><a href="?view=config">{% trans %}config{% endtrans %}</a></li>
|
<li><a href="?view=config">{% trans %}config{% endtrans %}</a></li>
|
||||||
<li><a href={{ path('about') }}>{% trans %}about{% 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>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?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 '';
|
||||||
|
}
|
||||||
|
}
|
58
src/Wallabag/CoreBundle/Security/Firewall/WsseListener.php
Normal file
58
src/Wallabag/CoreBundle/Security/Firewall/WsseListener.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,16 @@
|
||||||
namespace Wallabag\CoreBundle;
|
namespace Wallabag\CoreBundle;
|
||||||
|
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
use Wallabag\CoreBundle\DependencyInjection\Security\Factory\WsseFactory;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
|
||||||
class WallabagCoreBundle extends Bundle
|
class WallabagCoreBundle extends Bundle
|
||||||
{
|
{
|
||||||
|
public function build(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
parent::build($container);
|
||||||
|
|
||||||
|
$extension = $container->getExtension('security');
|
||||||
|
$extension->addSecurityListenerFactory(new WsseFactory());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue