aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php62
-rw-r--r--src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php40
-rw-r--r--src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php5
-rw-r--r--src/Wallabag/ApiBundle/Entity/AccessToken.php31
-rw-r--r--src/Wallabag/ApiBundle/Entity/AuthCode.php31
-rw-r--r--src/Wallabag/ApiBundle/Entity/Client.php25
-rw-r--r--src/Wallabag/ApiBundle/Entity/RefreshToken.php31
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/services.yml12
-rw-r--r--src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php79
-rw-r--r--src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php24
-rw-r--r--src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php62
-rw-r--r--src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php46
-rw-r--r--src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php238
-rw-r--r--src/Wallabag/ApiBundle/WallabagApiBundle.php9
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php7
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php17
-rw-r--r--src/Wallabag/CoreBundle/Controller/RssController.php8
-rw-r--r--src/Wallabag/CoreBundle/Controller/SecurityController.php153
-rw-r--r--src/Wallabag/CoreBundle/Entity/Config.php10
-rw-r--r--src/Wallabag/CoreBundle/Entity/Entry.php5
-rw-r--r--src/Wallabag/CoreBundle/Entity/Tag.php4
-rw-r--r--src/Wallabag/CoreBundle/EventListener/RegistrationConfirmedListener.php50
-rw-r--r--src/Wallabag/CoreBundle/Filter/EntryFilterType.php2
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/ForgotPasswordType.php54
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/NewUserType.php5
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/ResetPasswordType.php35
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/UserInformationType.php2
-rw-r--r--src/Wallabag/CoreBundle/Helper/DetectActiveTheme.php2
-rw-r--r--src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php4
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml19
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/base.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig16
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/forgotPassword.html.twig31
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/reset.html.twig35
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig19
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig3
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig3
-rwxr-xr-xsrc/Wallabag/CoreBundle/Resources/views/themes/material/public/css/main.css9
-rwxr-xr-xsrc/Wallabag/CoreBundle/Resources/views/themes/material/public/img/logo-other_themes.pngbin3922 -> 3058 bytes
-rw-r--r--src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php87
-rw-r--r--src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php89
-rw-r--r--src/Wallabag/CoreBundle/Security/Validator/WallabagUserPasswordValidator.php51
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php28
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php6
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php201
-rw-r--r--src/Wallabag/CoreBundle/Tests/EventListener/RegistrationConfirmedListenerTest.php92
-rw-r--r--src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php2
-rw-r--r--src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php26
-rw-r--r--src/Wallabag/UserBundle/Controller/ResettingController.php75
-rw-r--r--src/Wallabag/UserBundle/DataFixtures/ORM/LoadUserData.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php)9
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php (renamed from src/Wallabag/CoreBundle/Entity/User.php)41
-rw-r--r--src/Wallabag/UserBundle/Repository/UserRepository.php (renamed from src/Wallabag/CoreBundle/Repository/UserRepository.php)2
-rw-r--r--src/Wallabag/UserBundle/Resources/config/services.yml0
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register.html.twig20
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register_content.html.twig37
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/checkEmail.html.twig (renamed from src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/checkEmail.html.twig)0
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request.html.twig20
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request_content.html.twig17
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/baggy/Security/login.html.twig (renamed from src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/login.html.twig)16
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/baggy/layout.html.twig16
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/ChangePassword/changePassword_content.html.twig12
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Registration/checkEmail.html.twig11
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Registration/confirmed.html.twig17
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Registration/register_content.html.twig45
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/checkEmail.html.twig11
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/passwordAlreadyRequested.html.twig11
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/request_content.html.twig26
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/reset_content.html.twig15
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/Security/login.html.twig46
-rw-r--r--src/Wallabag/UserBundle/Resources/views/themes/material/layout.html.twig23
-rw-r--r--src/Wallabag/UserBundle/WallabagUserBundle.php13
72 files changed, 964 insertions, 1295 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
index 349229f3..1fee56ad 100644
--- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController;
5use Nelmio\ApiDocBundle\Annotation\ApiDoc; 6use Nelmio\ApiDocBundle\Annotation\ApiDoc;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpFoundation\Response; 8use Symfony\Component\HttpFoundation\Response;
9use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
@@ -11,7 +11,7 @@ use Wallabag\CoreBundle\Entity\Tag;
11use Hateoas\Configuration\Route; 11use Hateoas\Configuration\Route;
12use Hateoas\Representation\Factory\PagerfantaFactory; 12use Hateoas\Representation\Factory\PagerfantaFactory;
13 13
14class WallabagRestController extends Controller 14class WallabagRestController extends FOSRestController
15{ 15{
16 /** 16 /**
17 * @param Entry $entry 17 * @param Entry $entry
@@ -38,29 +38,11 @@ class WallabagRestController extends Controller
38 } 38 }
39 } 39 }
40 40
41 /** 41 private function validateAuthentication()
42 * Retrieve salt for a giver user.
43 *
44 * @ApiDoc(
45 * parameters={
46 * {"name"="username", "dataType"="string", "required"=true, "description"="username"}
47 * }
48 * )
49 *
50 * @return array
51 */
52 public function getSaltAction($username)
53 { 42 {
54 $user = $this 43 if (false === $this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')) {
55 ->getDoctrine() 44 throw new AccessDeniedException();
56 ->getRepository('WallabagCoreBundle:User')
57 ->findOneByUsername($username);
58
59 if (is_null($user)) {
60 throw $this->createNotFoundException();
61 } 45 }
62
63 return array($user->getSalt() ?: null);
64 } 46 }
65 47
66 /** 48 /**
@@ -82,6 +64,8 @@ class WallabagRestController extends Controller
82 */ 64 */
83 public function getEntriesAction(Request $request) 65 public function getEntriesAction(Request $request)
84 { 66 {
67 $this->validateAuthentication();
68
85 $isArchived = $request->query->get('archive'); 69 $isArchived = $request->query->get('archive');
86 $isStarred = $request->query->get('star'); 70 $isStarred = $request->query->get('star');
87 $sort = $request->query->get('sort', 'created'); 71 $sort = $request->query->get('sort', 'created');
@@ -122,7 +106,8 @@ class WallabagRestController extends Controller
122 */ 106 */
123 public function getEntryAction(Entry $entry) 107 public function getEntryAction(Entry $entry)
124 { 108 {
125 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 109 $this->validateAuthentication();
110 $this->validateUserAccess($entry->getUser()->getId());
126 111
127 $json = $this->get('serializer')->serialize($entry, 'json'); 112 $json = $this->get('serializer')->serialize($entry, 'json');
128 113
@@ -144,6 +129,8 @@ class WallabagRestController extends Controller
144 */ 129 */
145 public function postEntriesAction(Request $request) 130 public function postEntriesAction(Request $request)
146 { 131 {
132 $this->validateAuthentication();
133
147 $url = $request->request->get('url'); 134 $url = $request->request->get('url');
148 135
149 $entry = $this->get('wallabag_core.content_proxy')->updateEntry( 136 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
@@ -184,7 +171,8 @@ class WallabagRestController extends Controller
184 */ 171 */
185 public function patchEntriesAction(Entry $entry, Request $request) 172 public function patchEntriesAction(Entry $entry, Request $request)
186 { 173 {
187 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 174 $this->validateAuthentication();
175 $this->validateUserAccess($entry->getUser()->getId());
188 176
189 $title = $request->request->get('title'); 177 $title = $request->request->get('title');
190 $isArchived = $request->request->get('is_archived'); 178 $isArchived = $request->request->get('is_archived');
@@ -228,7 +216,8 @@ class WallabagRestController extends Controller
228 */ 216 */
229 public function deleteEntriesAction(Entry $entry) 217 public function deleteEntriesAction(Entry $entry)
230 { 218 {
231 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 219 $this->validateAuthentication();
220 $this->validateUserAccess($entry->getUser()->getId());
232 221
233 $em = $this->getDoctrine()->getManager(); 222 $em = $this->getDoctrine()->getManager();
234 $em->remove($entry); 223 $em->remove($entry);
@@ -250,7 +239,8 @@ class WallabagRestController extends Controller
250 */ 239 */
251 public function getEntriesTagsAction(Entry $entry) 240 public function getEntriesTagsAction(Entry $entry)
252 { 241 {
253 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 242 $this->validateAuthentication();
243 $this->validateUserAccess($entry->getUser()->getId());
254 244
255 $json = $this->get('serializer')->serialize($entry->getTags(), 'json'); 245 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
256 246
@@ -271,7 +261,8 @@ class WallabagRestController extends Controller
271 */ 261 */
272 public function postEntriesTagsAction(Request $request, Entry $entry) 262 public function postEntriesTagsAction(Request $request, Entry $entry)
273 { 263 {
274 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 264 $this->validateAuthentication();
265 $this->validateUserAccess($entry->getUser()->getId());
275 266
276 $tags = $request->request->get('tags', ''); 267 $tags = $request->request->get('tags', '');
277 if (!empty($tags)) { 268 if (!empty($tags)) {
@@ -299,7 +290,8 @@ class WallabagRestController extends Controller
299 */ 290 */
300 public function deleteEntriesTagsAction(Entry $entry, Tag $tag) 291 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
301 { 292 {
302 $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId()); 293 $this->validateAuthentication();
294 $this->validateUserAccess($entry->getUser()->getId());
303 295
304 $entry->removeTag($tag); 296 $entry->removeTag($tag);
305 $em = $this->getDoctrine()->getManager(); 297 $em = $this->getDoctrine()->getManager();
@@ -318,6 +310,7 @@ class WallabagRestController extends Controller
318 */ 310 */
319 public function getTagsAction() 311 public function getTagsAction()
320 { 312 {
313 $this->validateAuthentication();
321 $json = $this->get('serializer')->serialize($this->getUser()->getTags(), 'json'); 314 $json = $this->get('serializer')->serialize($this->getUser()->getTags(), 'json');
322 315
323 return $this->renderJsonResponse($json); 316 return $this->renderJsonResponse($json);
@@ -334,7 +327,8 @@ class WallabagRestController extends Controller
334 */ 327 */
335 public function deleteTagAction(Tag $tag) 328 public function deleteTagAction(Tag $tag)
336 { 329 {
337 $this->validateUserAccess($tag->getUser()->getId(), $this->getUser()->getId()); 330 $this->validateAuthentication();
331 $this->validateUserAccess($tag->getUser()->getId());
338 332
339 $em = $this->getDoctrine()->getManager(); 333 $em = $this->getDoctrine()->getManager();
340 $em->remove($tag); 334 $em->remove($tag);
@@ -350,12 +344,12 @@ class WallabagRestController extends Controller
350 * If not, throw exception. It means a user try to access information from an other user. 344 * If not, throw exception. It means a user try to access information from an other user.
351 * 345 *
352 * @param int $requestUserId User id from the requested source 346 * @param int $requestUserId User id from the requested source
353 * @param int $currentUserId User id from the retrieved source
354 */ 347 */
355 private function validateUserAccess($requestUserId, $currentUserId) 348 private function validateUserAccess($requestUserId)
356 { 349 {
357 if ($requestUserId != $currentUserId) { 350 $user = $this->get('security.context')->getToken()->getUser();
358 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$currentUserId); 351 if ($requestUserId != $user->getId()) {
352 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId());
359 } 353 }
360 } 354 }
361 355
diff --git a/src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php b/src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php
deleted file mode 100644
index 402eb869..00000000
--- a/src/Wallabag/ApiBundle/DependencyInjection/Security/Factory/WsseFactory.php
+++ /dev/null
@@ -1,40 +0,0 @@
1<?php
2
3namespace Wallabag\ApiBundle\DependencyInjection\Security\Factory;
4
5use Symfony\Component\DependencyInjection\ContainerBuilder;
6use Symfony\Component\DependencyInjection\Reference;
7use Symfony\Component\DependencyInjection\DefinitionDecorator;
8use Symfony\Component\Config\Definition\Builder\NodeDefinition;
9use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
10
11class WsseFactory implements SecurityFactoryInterface
12{
13 public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
14 {
15 $providerId = 'security.authentication.provider.wsse.'.$id;
16 $container
17 ->setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider'))
18 ->replaceArgument(0, new Reference($userProvider))
19 ;
20
21 $listenerId = 'security.authentication.listener.wsse.'.$id;
22 $listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener'));
23
24 return array($providerId, $listenerId, $defaultEntryPoint);
25 }
26
27 public function getPosition()
28 {
29 return 'pre_auth';
30 }
31
32 public function getKey()
33 {
34 return 'wsse';
35 }
36
37 public function addConfiguration(NodeDefinition $node)
38 {
39 }
40}
diff --git a/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php b/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php
index c5cc204e..cde43aed 100644
--- a/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php
+++ b/src/Wallabag/ApiBundle/DependencyInjection/WallabagApiExtension.php
@@ -3,9 +3,7 @@
3namespace Wallabag\ApiBundle\DependencyInjection; 3namespace Wallabag\ApiBundle\DependencyInjection;
4 4
5use Symfony\Component\DependencyInjection\ContainerBuilder; 5use Symfony\Component\DependencyInjection\ContainerBuilder;
6use Symfony\Component\Config\FileLocator;
7use Symfony\Component\HttpKernel\DependencyInjection\Extension; 6use Symfony\Component\HttpKernel\DependencyInjection\Extension;
8use Symfony\Component\DependencyInjection\Loader;
9 7
10class WallabagApiExtension extends Extension 8class WallabagApiExtension extends Extension
11{ 9{
@@ -13,9 +11,6 @@ class WallabagApiExtension extends Extension
13 { 11 {
14 $configuration = new Configuration(); 12 $configuration = new Configuration();
15 $config = $this->processConfiguration($configuration, $configs); 13 $config = $this->processConfiguration($configuration, $configs);
16
17 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
18 $loader->load('services.yml');
19 } 14 }
20 15
21 public function getAlias() 16 public function getAlias()
diff --git a/src/Wallabag/ApiBundle/Entity/AccessToken.php b/src/Wallabag/ApiBundle/Entity/AccessToken.php
new file mode 100644
index 00000000..b1f4e7de
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Entity/AccessToken.php
@@ -0,0 +1,31 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\AccessToken as BaseAccessToken;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_access_tokens")
10 * @ORM\Entity
11 */
12class AccessToken extends BaseAccessToken
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 /**
22 * @ORM\ManyToOne(targetEntity="Client")
23 * @ORM\JoinColumn(nullable=false)
24 */
25 protected $client;
26
27 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
29 */
30 protected $user;
31}
diff --git a/src/Wallabag/ApiBundle/Entity/AuthCode.php b/src/Wallabag/ApiBundle/Entity/AuthCode.php
new file mode 100644
index 00000000..81398158
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Entity/AuthCode.php
@@ -0,0 +1,31 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_auth_codes")
10 * @ORM\Entity
11 */
12class AuthCode extends BaseAuthCode
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 /**
22 * @ORM\ManyToOne(targetEntity="Client")
23 * @ORM\JoinColumn(nullable=false)
24 */
25 protected $client;
26
27 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
29 */
30 protected $user;
31}
diff --git a/src/Wallabag/ApiBundle/Entity/Client.php b/src/Wallabag/ApiBundle/Entity/Client.php
new file mode 100644
index 00000000..d449870a
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Entity/Client.php
@@ -0,0 +1,25 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\Client as BaseClient;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_clients")
10 * @ORM\Entity
11 */
12class Client extends BaseClient
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 public function __construct()
22 {
23 parent::__construct();
24 }
25}
diff --git a/src/Wallabag/ApiBundle/Entity/RefreshToken.php b/src/Wallabag/ApiBundle/Entity/RefreshToken.php
new file mode 100644
index 00000000..be2c1d2e
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Entity/RefreshToken.php
@@ -0,0 +1,31 @@
1<?php
2
3namespace Wallabag\ApiBundle\Entity;
4
5use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * @ORM\Table("oauth2_refresh_tokens")
10 * @ORM\Entity
11 */
12class RefreshToken extends BaseRefreshToken
13{
14 /**
15 * @ORM\Id
16 * @ORM\Column(type="integer")
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 /**
22 * @ORM\ManyToOne(targetEntity="Client")
23 * @ORM\JoinColumn(nullable=false)
24 */
25 protected $client;
26
27 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
29 */
30 protected $user;
31}
diff --git a/src/Wallabag/ApiBundle/Resources/config/services.yml b/src/Wallabag/ApiBundle/Resources/config/services.yml
deleted file mode 100644
index 6854a444..00000000
--- a/src/Wallabag/ApiBundle/Resources/config/services.yml
+++ /dev/null
@@ -1,12 +0,0 @@
1services:
2 wsse.security.authentication.provider:
3 class: Wallabag\ApiBundle\Security\Authentication\Provider\WsseProvider
4 public: false
5 arguments: ['', '%kernel.cache_dir%/security/nonces']
6
7 wsse.security.authentication.listener:
8 class: Wallabag\ApiBundle\Security\Firewall\WsseListener
9 public: false
10 tags:
11 - { name: monolog.logger, channel: wsse }
12 arguments: ['@security.context', '@security.authentication.manager', '@logger']
diff --git a/src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php b/src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php
deleted file mode 100644
index 9bf8b377..00000000
--- a/src/Wallabag/ApiBundle/Security/Authentication/Provider/WsseProvider.php
+++ /dev/null
@@ -1,79 +0,0 @@
1<?php
2
3namespace Wallabag\ApiBundle\Security\Authentication\Provider;
4
5use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
6use Symfony\Component\Security\Core\User\UserProviderInterface;
7use Symfony\Component\Security\Core\Exception\AuthenticationException;
8use Symfony\Component\Security\Core\Exception\NonceExpiredException;
9use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
10use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
11
12class WsseProvider implements AuthenticationProviderInterface
13{
14 private $userProvider;
15 private $cacheDir;
16
17 public function __construct(UserProviderInterface $userProvider, $cacheDir)
18 {
19 $this->userProvider = $userProvider;
20 $this->cacheDir = $cacheDir;
21
22 // If cache directory does not exist we create it
23 if (!is_dir($this->cacheDir)) {
24 mkdir($this->cacheDir, 0777, true);
25 }
26 }
27
28 public function authenticate(TokenInterface $token)
29 {
30 $user = $this->userProvider->loadUserByUsername($token->getUsername());
31
32 if (!$user) {
33 throw new AuthenticationException('Bad credentials. Did you forgot your username?');
34 }
35
36 if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) {
37 $authenticatedToken = new WsseUserToken($user->getRoles());
38 $authenticatedToken->setUser($user);
39
40 return $authenticatedToken;
41 }
42
43 throw new AuthenticationException('The WSSE authentication failed.');
44 }
45
46 protected function validateDigest($digest, $nonce, $created, $secret)
47 {
48 // Check created time is not in the future
49 if (strtotime($created) > time()) {
50 throw new AuthenticationException('Back to the future...');
51 }
52
53 // Expire timestamp after 5 minutes
54 if (time() - strtotime($created) > 300) {
55 throw new AuthenticationException('Too late for this timestamp... Watch your watch.');
56 }
57
58 // Validate nonce is unique within 5 minutes
59 if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
60 throw new NonceExpiredException('Previously used nonce detected');
61 }
62
63 file_put_contents($this->cacheDir.'/'.$nonce, time());
64
65 // Validate Secret
66 $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
67
68 if ($digest !== $expected) {
69 throw new AuthenticationException('Bad credentials ! Digest is not as expected.');
70 }
71
72 return $digest === $expected;
73 }
74
75 public function supports(TokenInterface $token)
76 {
77 return $token instanceof WsseUserToken;
78 }
79}
diff --git a/src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php b/src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php
deleted file mode 100644
index e6d30224..00000000
--- a/src/Wallabag/ApiBundle/Security/Authentication/Token/WsseUserToken.php
+++ /dev/null
@@ -1,24 +0,0 @@
1<?php
2
3namespace Wallabag\ApiBundle\Security\Authentication\Token;
4
5use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
6
7class WsseUserToken extends AbstractToken
8{
9 public $created;
10 public $digest;
11 public $nonce;
12
13 public function __construct(array $roles = array())
14 {
15 parent::__construct($roles);
16
17 $this->setAuthenticated(count($roles) > 0);
18 }
19
20 public function getCredentials()
21 {
22 return '';
23 }
24}
diff --git a/src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php b/src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php
deleted file mode 100644
index 2fcbe014..00000000
--- a/src/Wallabag/ApiBundle/Security/Firewall/WsseListener.php
+++ /dev/null
@@ -1,62 +0,0 @@
1<?php
2
3namespace Wallabag\ApiBundle\Security\Firewall;
4
5use Symfony\Component\HttpFoundation\Response;
6use Symfony\Component\HttpKernel\Event\GetResponseEvent;
7use Symfony\Component\Security\Http\Firewall\ListenerInterface;
8use Symfony\Component\Security\Core\Exception\AuthenticationException;
9use Symfony\Component\Security\Core\SecurityContextInterface;
10use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
11use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
12use Psr\Log\LoggerInterface;
13
14class WsseListener implements ListenerInterface
15{
16 protected $securityContext;
17 protected $authenticationManager;
18 protected $logger;
19
20 public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger)
21 {
22 $this->securityContext = $securityContext;
23 $this->authenticationManager = $authenticationManager;
24 $this->logger = $logger;
25 }
26
27 public function handle(GetResponseEvent $event)
28 {
29 $request = $event->getRequest();
30
31 $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
32 if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
33 return;
34 }
35
36 $token = new WsseUserToken();
37 $token->setUser($matches[1]);
38
39 $token->digest = $matches[2];
40 $token->nonce = $matches[3];
41 $token->created = $matches[4];
42
43 try {
44 $authToken = $this->authenticationManager->authenticate($token);
45
46 $this->securityContext->setToken($authToken);
47
48 return;
49 } catch (AuthenticationException $failed) {
50 $failedMessage = 'WSSE Login failed for '.$token->getUsername().'. Why ? '.$failed->getMessage();
51 $this->logger->err($failedMessage);
52
53 // Deny authentication with a '403 Forbidden' HTTP response
54 $response = new Response();
55 $response->setStatusCode(403);
56 $response->setContent($failedMessage);
57 $event->setResponse($response);
58
59 return;
60 }
61 }
62}
diff --git a/src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php b/src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php
new file mode 100644
index 00000000..119889b3
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Tests/AbstractControllerTest.php
@@ -0,0 +1,46 @@
1<?php
2
3namespace Wallabag\ApiBundle\Tests;
4
5use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
6use Symfony\Component\BrowserKit\Cookie;
7
8abstract class AbstractControllerTest extends WebTestCase
9{
10 /**
11 * @var Client
12 */
13 protected $client = null;
14
15 public function setUp()
16 {
17 $this->client = $this->createAuthorizedClient();
18 }
19
20 /**
21 * @return Client
22 */
23 protected function createAuthorizedClient()
24 {
25 $client = static::createClient();
26 $container = $client->getContainer();
27
28 $session = $container->get('session');
29 /** @var $userManager \FOS\UserBundle\Doctrine\UserManager */
30 $userManager = $container->get('fos_user.user_manager');
31 /** @var $loginManager \FOS\UserBundle\Security\LoginManager */
32 $loginManager = $container->get('fos_user.security.login_manager');
33 $firewallName = $container->getParameter('fos_user.firewall_name');
34
35 $user = $userManager->findUserBy(array('username' => 'admin'));
36 $loginManager->loginUser($firewallName, $user);
37
38 // save the login token into the session and put it in a cookie
39 $container->get('session')->set('_security_'.$firewallName,
40 serialize($container->get('security.context')->getToken()));
41 $container->get('session')->save();
42 $client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
43
44 return $client;
45 }
46}
diff --git a/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php b/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php
index 7ae54b57..bc7ef489 100644
--- a/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php
+++ b/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php
@@ -2,99 +2,15 @@
2 2
3namespace Wallabag\ApiBundle\Tests\Controller; 3namespace Wallabag\ApiBundle\Tests\Controller;
4 4
5use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 5use Wallabag\ApiBundle\Tests\AbstractControllerTest;
6 6
7class WallabagRestControllerTest extends WebTestCase 7class WallabagRestControllerTest extends AbstractControllerTest
8{ 8{
9 protected static $salt; 9 protected static $salt;
10 10
11 /**
12 * Grab the salt once and store it to be available for all tests.
13 */
14 public static function setUpBeforeClass()
15 {
16 $client = self::createClient();
17
18 $user = $client->getContainer()
19 ->get('doctrine.orm.entity_manager')
20 ->getRepository('WallabagCoreBundle:User')
21 ->findOneByUsername('admin');
22
23 self::$salt = $user->getSalt();
24 }
25
26 /**
27 * Generate HTTP headers for authenticate user on API.
28 *
29 * @param string $username
30 * @param string $password
31 *
32 * @return array
33 */
34 private function generateHeaders($username, $password)
35 {
36 $encryptedPassword = sha1($password.$username.self::$salt);
37 $nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
38
39 $now = new \DateTime('now', new \DateTimeZone('UTC'));
40 $created = (string) $now->format('Y-m-d\TH:i:s\Z');
41 $digest = base64_encode(sha1(base64_decode($nonce).$created.$encryptedPassword, true));
42
43 return array(
44 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
45 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="'.$username.'", PasswordDigest="'.$digest.'", Nonce="'.$nonce.'", Created="'.$created.'"',
46 );
47 }
48
49 public function testGetSalt()
50 {
51 $client = $this->createClient();
52 $client->request('GET', '/api/salts/admin.json');
53
54 $user = $client->getContainer()
55 ->get('doctrine.orm.entity_manager')
56 ->getRepository('WallabagCoreBundle:User')
57 ->findOneByUsername('admin');
58
59 $this->assertEquals(200, $client->getResponse()->getStatusCode());
60
61 $content = json_decode($client->getResponse()->getContent(), true);
62
63 $this->assertArrayHasKey(0, $content);
64 $this->assertEquals($user->getSalt(), $content[0]);
65
66 $client->request('GET', '/api/salts/notfound.json');
67 $this->assertEquals(404, $client->getResponse()->getStatusCode());
68 }
69
70 public function testWithBadHeaders()
71 {
72 $client = $this->createClient();
73
74 $entry = $client->getContainer()
75 ->get('doctrine.orm.entity_manager')
76 ->getRepository('WallabagCoreBundle:Entry')
77 ->findOneByIsArchived(false);
78
79 if (!$entry) {
80 $this->markTestSkipped('No content found in db.');
81 }
82
83 $badHeaders = array(
84 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
85 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="admin", PasswordDigest="Wr0ngDig3st", Nonce="n0Nc3", Created="2015-01-01T13:37:00Z"',
86 );
87
88 $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $badHeaders);
89 $this->assertEquals(403, $client->getResponse()->getStatusCode());
90 }
91
92 public function testGetOneEntry() 11 public function testGetOneEntry()
93 { 12 {
94 $client = $this->createClient(); 13 $entry = $this->client->getContainer()
95 $headers = $this->generateHeaders('admin', 'mypassword');
96
97 $entry = $client->getContainer()
98 ->get('doctrine.orm.entity_manager') 14 ->get('doctrine.orm.entity_manager')
99 ->getRepository('WallabagCoreBundle:Entry') 15 ->getRepository('WallabagCoreBundle:Entry')
100 ->findOneBy(array('user' => 1, 'isArchived' => false)); 16 ->findOneBy(array('user' => 1, 'isArchived' => false));
@@ -103,18 +19,17 @@ class WallabagRestControllerTest extends WebTestCase
103 $this->markTestSkipped('No content found in db.'); 19 $this->markTestSkipped('No content found in db.');
104 } 20 }
105 21
106 $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); 22 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
23 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
107 24
108 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 25 $content = json_decode($this->client->getResponse()->getContent(), true);
109
110 $content = json_decode($client->getResponse()->getContent(), true);
111 26
112 $this->assertEquals($entry->getTitle(), $content['title']); 27 $this->assertEquals($entry->getTitle(), $content['title']);
113 $this->assertEquals($entry->getUrl(), $content['url']); 28 $this->assertEquals($entry->getUrl(), $content['url']);
114 $this->assertCount(count($entry->getTags()), $content['tags']); 29 $this->assertCount(count($entry->getTags()), $content['tags']);
115 30
116 $this->assertTrue( 31 $this->assertTrue(
117 $client->getResponse()->headers->contains( 32 $this->client->getResponse()->headers->contains(
118 'Content-Type', 33 'Content-Type',
119 'application/json' 34 'application/json'
120 ) 35 )
@@ -123,10 +38,7 @@ class WallabagRestControllerTest extends WebTestCase
123 38
124 public function testGetOneEntryWrongUser() 39 public function testGetOneEntryWrongUser()
125 { 40 {
126 $client = $this->createClient(); 41 $entry = $this->client->getContainer()
127 $headers = $this->generateHeaders('admin', 'mypassword');
128
129 $entry = $client->getContainer()
130 ->get('doctrine.orm.entity_manager') 42 ->get('doctrine.orm.entity_manager')
131 ->getRepository('WallabagCoreBundle:Entry') 43 ->getRepository('WallabagCoreBundle:Entry')
132 ->findOneBy(array('user' => 2, 'isArchived' => false)); 44 ->findOneBy(array('user' => 2, 'isArchived' => false));
@@ -135,21 +47,18 @@ class WallabagRestControllerTest extends WebTestCase
135 $this->markTestSkipped('No content found in db.'); 47 $this->markTestSkipped('No content found in db.');
136 } 48 }
137 49
138 $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); 50 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
139 51
140 $this->assertEquals(403, $client->getResponse()->getStatusCode()); 52 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
141 } 53 }
142 54
143 public function testGetEntries() 55 public function testGetEntries()
144 { 56 {
145 $client = $this->createClient(); 57 $this->client->request('GET', '/api/entries');
146 $headers = $this->generateHeaders('admin', 'mypassword');
147
148 $client->request('GET', '/api/entries', array(), array(), $headers);
149 58
150 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 59 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
151 60
152 $content = json_decode($client->getResponse()->getContent(), true); 61 $content = json_decode($this->client->getResponse()->getContent(), true);
153 62
154 $this->assertGreaterThanOrEqual(1, count($content)); 63 $this->assertGreaterThanOrEqual(1, count($content));
155 $this->assertNotEmpty($content['_embedded']['items']); 64 $this->assertNotEmpty($content['_embedded']['items']);
@@ -158,7 +67,7 @@ class WallabagRestControllerTest extends WebTestCase
158 $this->assertGreaterThanOrEqual(1, $content['pages']); 67 $this->assertGreaterThanOrEqual(1, $content['pages']);
159 68
160 $this->assertTrue( 69 $this->assertTrue(
161 $client->getResponse()->headers->contains( 70 $this->client->getResponse()->headers->contains(
162 'Content-Type', 71 'Content-Type',
163 'application/json' 72 'application/json'
164 ) 73 )
@@ -167,14 +76,11 @@ class WallabagRestControllerTest extends WebTestCase
167 76
168 public function testGetStarredEntries() 77 public function testGetStarredEntries()
169 { 78 {
170 $client = $this->createClient(); 79 $this->client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated'));
171 $headers = $this->generateHeaders('admin', 'mypassword');
172 80
173 $client->request('GET', '/api/entries', array('star' => 1, 'sort' => 'updated'), array(), $headers); 81 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
174 82
175 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 83 $content = json_decode($this->client->getResponse()->getContent(), true);
176
177 $content = json_decode($client->getResponse()->getContent(), true);
178 84
179 $this->assertGreaterThanOrEqual(1, count($content)); 85 $this->assertGreaterThanOrEqual(1, count($content));
180 $this->assertNotEmpty($content['_embedded']['items']); 86 $this->assertNotEmpty($content['_embedded']['items']);
@@ -183,7 +89,7 @@ class WallabagRestControllerTest extends WebTestCase
183 $this->assertGreaterThanOrEqual(1, $content['pages']); 89 $this->assertGreaterThanOrEqual(1, $content['pages']);
184 90
185 $this->assertTrue( 91 $this->assertTrue(
186 $client->getResponse()->headers->contains( 92 $this->client->getResponse()->headers->contains(
187 'Content-Type', 93 'Content-Type',
188 'application/json' 94 'application/json'
189 ) 95 )
@@ -192,14 +98,11 @@ class WallabagRestControllerTest extends WebTestCase
192 98
193 public function testGetArchiveEntries() 99 public function testGetArchiveEntries()
194 { 100 {
195 $client = $this->createClient(); 101 $this->client->request('GET', '/api/entries', array('archive' => 1));
196 $headers = $this->generateHeaders('admin', 'mypassword');
197
198 $client->request('GET', '/api/entries', array('archive' => 1), array(), $headers);
199 102
200 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 103 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
201 104
202 $content = json_decode($client->getResponse()->getContent(), true); 105 $content = json_decode($this->client->getResponse()->getContent(), true);
203 106
204 $this->assertGreaterThanOrEqual(1, count($content)); 107 $this->assertGreaterThanOrEqual(1, count($content));
205 $this->assertNotEmpty($content['_embedded']['items']); 108 $this->assertNotEmpty($content['_embedded']['items']);
@@ -208,7 +111,7 @@ class WallabagRestControllerTest extends WebTestCase
208 $this->assertGreaterThanOrEqual(1, $content['pages']); 111 $this->assertGreaterThanOrEqual(1, $content['pages']);
209 112
210 $this->assertTrue( 113 $this->assertTrue(
211 $client->getResponse()->headers->contains( 114 $this->client->getResponse()->headers->contains(
212 'Content-Type', 115 'Content-Type',
213 'application/json' 116 'application/json'
214 ) 117 )
@@ -217,10 +120,7 @@ class WallabagRestControllerTest extends WebTestCase
217 120
218 public function testDeleteEntry() 121 public function testDeleteEntry()
219 { 122 {
220 $client = $this->createClient(); 123 $entry = $this->client->getContainer()
221 $headers = $this->generateHeaders('admin', 'mypassword');
222
223 $entry = $client->getContainer()
224 ->get('doctrine.orm.entity_manager') 124 ->get('doctrine.orm.entity_manager')
225 ->getRepository('WallabagCoreBundle:Entry') 125 ->getRepository('WallabagCoreBundle:Entry')
226 ->findOneByUser(1); 126 ->findOneByUser(1);
@@ -229,36 +129,31 @@ class WallabagRestControllerTest extends WebTestCase
229 $this->markTestSkipped('No content found in db.'); 129 $this->markTestSkipped('No content found in db.');
230 } 130 }
231 131
232 $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers); 132 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
233 133
234 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 134 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
235 135
236 $content = json_decode($client->getResponse()->getContent(), true); 136 $content = json_decode($this->client->getResponse()->getContent(), true);
237 137
238 $this->assertEquals($entry->getTitle(), $content['title']); 138 $this->assertEquals($entry->getTitle(), $content['title']);
239 $this->assertEquals($entry->getUrl(), $content['url']); 139 $this->assertEquals($entry->getUrl(), $content['url']);
240 140
241 // We'll try to delete this entry again 141 // We'll try to delete this entry again
242 $headers = $this->generateHeaders('admin', 'mypassword'); 142 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
243
244 $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
245 143
246 $this->assertEquals(404, $client->getResponse()->getStatusCode()); 144 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
247 } 145 }
248 146
249 public function testPostEntry() 147 public function testPostEntry()
250 { 148 {
251 $client = $this->createClient(); 149 $this->client->request('POST', '/api/entries.json', array(
252 $headers = $this->generateHeaders('admin', 'mypassword');
253
254 $client->request('POST', '/api/entries.json', array(
255 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', 150 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
256 'tags' => 'google', 151 'tags' => 'google',
257 ), array(), $headers); 152 ));
258 153
259 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 154 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
260 155
261 $content = json_decode($client->getResponse()->getContent(), true); 156 $content = json_decode($this->client->getResponse()->getContent(), true);
262 157
263 $this->assertGreaterThan(0, $content['id']); 158 $this->assertGreaterThan(0, $content['id']);
264 $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']); 159 $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
@@ -269,10 +164,7 @@ class WallabagRestControllerTest extends WebTestCase
269 164
270 public function testPatchEntry() 165 public function testPatchEntry()
271 { 166 {
272 $client = $this->createClient(); 167 $entry = $this->client->getContainer()
273 $headers = $this->generateHeaders('admin', 'mypassword');
274
275 $entry = $client->getContainer()
276 ->get('doctrine.orm.entity_manager') 168 ->get('doctrine.orm.entity_manager')
277 ->getRepository('WallabagCoreBundle:Entry') 169 ->getRepository('WallabagCoreBundle:Entry')
278 ->findOneByUser(1); 170 ->findOneByUser(1);
@@ -284,16 +176,16 @@ class WallabagRestControllerTest extends WebTestCase
284 // hydrate the tags relations 176 // hydrate the tags relations
285 $nbTags = count($entry->getTags()); 177 $nbTags = count($entry->getTags());
286 178
287 $client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array( 179 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array(
288 'title' => 'New awesome title', 180 'title' => 'New awesome title',
289 'tags' => 'new tag '.uniqid(), 181 'tags' => 'new tag '.uniqid(),
290 'star' => true, 182 'star' => true,
291 'archive' => false, 183 'archive' => false,
292 ), array(), $headers); 184 ));
293 185
294 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 186 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
295 187
296 $content = json_decode($client->getResponse()->getContent(), true); 188 $content = json_decode($this->client->getResponse()->getContent(), true);
297 189
298 $this->assertEquals($entry->getId(), $content['id']); 190 $this->assertEquals($entry->getId(), $content['id']);
299 $this->assertEquals($entry->getUrl(), $content['url']); 191 $this->assertEquals($entry->getUrl(), $content['url']);
@@ -303,10 +195,7 @@ class WallabagRestControllerTest extends WebTestCase
303 195
304 public function testGetTagsEntry() 196 public function testGetTagsEntry()
305 { 197 {
306 $client = $this->createClient(); 198 $entry = $this->client->getContainer()
307 $headers = $this->generateHeaders('admin', 'mypassword');
308
309 $entry = $client->getContainer()
310 ->get('doctrine.orm.entity_manager') 199 ->get('doctrine.orm.entity_manager')
311 ->getRepository('WallabagCoreBundle:Entry') 200 ->getRepository('WallabagCoreBundle:Entry')
312 ->findOneWithTags(1); 201 ->findOneWithTags(1);
@@ -322,17 +211,14 @@ class WallabagRestControllerTest extends WebTestCase
322 $tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel()); 211 $tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel());
323 } 212 }
324 213
325 $client->request('GET', '/api/entries/'.$entry->getId().'/tags', array(), array(), $headers); 214 $this->client->request('GET', '/api/entries/'.$entry->getId().'/tags');
326 215
327 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $client->getResponse()->getContent()); 216 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $this->client->getResponse()->getContent());
328 } 217 }
329 218
330 public function testPostTagsOnEntry() 219 public function testPostTagsOnEntry()
331 { 220 {
332 $client = $this->createClient(); 221 $entry = $this->client->getContainer()
333 $headers = $this->generateHeaders('admin', 'mypassword');
334
335 $entry = $client->getContainer()
336 ->get('doctrine.orm.entity_manager') 222 ->get('doctrine.orm.entity_manager')
337 ->getRepository('WallabagCoreBundle:Entry') 223 ->getRepository('WallabagCoreBundle:Entry')
338 ->findOneByUser(1); 224 ->findOneByUser(1);
@@ -345,16 +231,16 @@ class WallabagRestControllerTest extends WebTestCase
345 231
346 $newTags = 'tag1,tag2,tag3'; 232 $newTags = 'tag1,tag2,tag3';
347 233
348 $client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags), array(), $headers); 234 $this->client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags));
349 235
350 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 236 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
351 237
352 $content = json_decode($client->getResponse()->getContent(), true); 238 $content = json_decode($this->client->getResponse()->getContent(), true);
353 239
354 $this->assertArrayHasKey('tags', $content); 240 $this->assertArrayHasKey('tags', $content);
355 $this->assertEquals($nbTags + 3, count($content['tags'])); 241 $this->assertEquals($nbTags + 3, count($content['tags']));
356 242
357 $entryDB = $client->getContainer() 243 $entryDB = $this->client->getContainer()
358 ->get('doctrine.orm.entity_manager') 244 ->get('doctrine.orm.entity_manager')
359 ->getRepository('WallabagCoreBundle:Entry') 245 ->getRepository('WallabagCoreBundle:Entry')
360 ->find($entry->getId()); 246 ->find($entry->getId());
@@ -369,15 +255,13 @@ class WallabagRestControllerTest extends WebTestCase
369 } 255 }
370 } 256 }
371 257
372 public function testDeleteOneTagEntrie() 258 public function testDeleteOneTagEntry()
373 { 259 {
374 $client = $this->createClient(); 260 $entry = $this->client->getContainer()
375 $headers = $this->generateHeaders('admin', 'mypassword');
376
377 $entry = $client->getContainer()
378 ->get('doctrine.orm.entity_manager') 261 ->get('doctrine.orm.entity_manager')
379 ->getRepository('WallabagCoreBundle:Entry') 262 ->getRepository('WallabagCoreBundle:Entry')
380 ->findOneByUser(1); 263 ->findOneWithTags(1);
264 $entry = $entry[0];
381 265
382 if (!$entry) { 266 if (!$entry) {
383 $this->markTestSkipped('No content found in db.'); 267 $this->markTestSkipped('No content found in db.');
@@ -387,11 +271,11 @@ class WallabagRestControllerTest extends WebTestCase
387 $nbTags = count($entry->getTags()); 271 $nbTags = count($entry->getTags());
388 $tag = $entry->getTags()[0]; 272 $tag = $entry->getTags()[0];
389 273
390 $client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json', array(), array(), $headers); 274 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json');
391 275
392 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 276 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
393 277
394 $content = json_decode($client->getResponse()->getContent(), true); 278 $content = json_decode($this->client->getResponse()->getContent(), true);
395 279
396 $this->assertArrayHasKey('tags', $content); 280 $this->assertArrayHasKey('tags', $content);
397 $this->assertEquals($nbTags - 1, count($content['tags'])); 281 $this->assertEquals($nbTags - 1, count($content['tags']));
@@ -399,14 +283,11 @@ class WallabagRestControllerTest extends WebTestCase
399 283
400 public function testGetUserTags() 284 public function testGetUserTags()
401 { 285 {
402 $client = $this->createClient(); 286 $this->client->request('GET', '/api/tags.json');
403 $headers = $this->generateHeaders('admin', 'mypassword');
404
405 $client->request('GET', '/api/tags.json', array(), array(), $headers);
406 287
407 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 288 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
408 289
409 $content = json_decode($client->getResponse()->getContent(), true); 290 $content = json_decode($this->client->getResponse()->getContent(), true);
410 291
411 $this->assertGreaterThan(0, $content); 292 $this->assertGreaterThan(0, $content);
412 $this->assertArrayHasKey('id', $content[0]); 293 $this->assertArrayHasKey('id', $content[0]);
@@ -420,14 +301,11 @@ class WallabagRestControllerTest extends WebTestCase
420 */ 301 */
421 public function testDeleteUserTag($tag) 302 public function testDeleteUserTag($tag)
422 { 303 {
423 $client = $this->createClient(); 304 $this->client->request('DELETE', '/api/tags/'.$tag['id'].'.json');
424 $headers = $this->generateHeaders('admin', 'mypassword');
425
426 $client->request('DELETE', '/api/tags/'.$tag['id'].'.json', array(), array(), $headers);
427 305
428 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 306 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
429 307
430 $content = json_decode($client->getResponse()->getContent(), true); 308 $content = json_decode($this->client->getResponse()->getContent(), true);
431 309
432 $this->assertArrayHasKey('label', $content); 310 $this->assertArrayHasKey('label', $content);
433 $this->assertEquals($tag['label'], $content['label']); 311 $this->assertEquals($tag['label'], $content['label']);
diff --git a/src/Wallabag/ApiBundle/WallabagApiBundle.php b/src/Wallabag/ApiBundle/WallabagApiBundle.php
index 2484f277..19d887ab 100644
--- a/src/Wallabag/ApiBundle/WallabagApiBundle.php
+++ b/src/Wallabag/ApiBundle/WallabagApiBundle.php
@@ -3,16 +3,7 @@
3namespace Wallabag\ApiBundle; 3namespace Wallabag\ApiBundle;
4 4
5use Symfony\Component\HttpKernel\Bundle\Bundle; 5use Symfony\Component\HttpKernel\Bundle\Bundle;
6use Wallabag\ApiBundle\DependencyInjection\Security\Factory\WsseFactory;
7use Symfony\Component\DependencyInjection\ContainerBuilder;
8 6
9class WallabagApiBundle extends Bundle 7class WallabagApiBundle extends Bundle
10{ 8{
11 public function build(ContainerBuilder $container)
12 {
13 parent::build($container);
14
15 $extension = $container->getExtension('security');
16 $extension->addSecurityListenerFactory(new WsseFactory());
17 }
18} 9}
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index 1bd76ae3..6ebbd93c 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -8,7 +8,7 @@ use Symfony\Component\Console\Input\InputOption;
8use Symfony\Component\Console\Input\ArrayInput; 8use Symfony\Component\Console\Input\ArrayInput;
9use Symfony\Component\Console\Output\OutputInterface; 9use Symfony\Component\Console\Output\OutputInterface;
10use Symfony\Component\Console\Output\NullOutput; 10use Symfony\Component\Console\Output\NullOutput;
11use Wallabag\CoreBundle\Entity\User; 11use Wallabag\UserBundle\Entity\User;
12use Wallabag\CoreBundle\Entity\Config; 12use Wallabag\CoreBundle\Entity\Config;
13 13
14class InstallCommand extends ContainerAwareCommand 14class InstallCommand extends ContainerAwareCommand
@@ -188,9 +188,10 @@ class InstallCommand extends ContainerAwareCommand
188 188
189 $em = $this->getContainer()->get('doctrine.orm.entity_manager'); 189 $em = $this->getContainer()->get('doctrine.orm.entity_manager');
190 190
191 $user = new User(); 191 $userManager = $this->getContainer()->get('fos_user.user_manager');
192 $user = $userManager->createUser();
192 $user->setUsername($dialog->ask($this->defaultOutput, '<question>Username</question> <comment>(default: wallabag)</comment> :', 'wallabag')); 193 $user->setUsername($dialog->ask($this->defaultOutput, '<question>Username</question> <comment>(default: wallabag)</comment> :', 'wallabag'));
193 $user->setPassword($dialog->ask($this->defaultOutput, '<question>Password</question> <comment>(default: wallabag)</comment> :', 'wallabag')); 194 $user->setPlainPassword($dialog->ask($this->defaultOutput, '<question>Password</question> <comment>(default: wallabag)</comment> :', 'wallabag'));
194 $user->setEmail($dialog->ask($this->defaultOutput, '<question>Email:</question>', '')); 195 $user->setEmail($dialog->ask($this->defaultOutput, '<question>Email:</question>', ''));
195 $user->setEnabled(true); 196 $user->setEnabled(true);
196 197
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index 5affdee8..ecfecc66 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -7,7 +7,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpFoundation\JsonResponse; 8use Symfony\Component\HttpFoundation\JsonResponse;
9use Wallabag\CoreBundle\Entity\Config; 9use Wallabag\CoreBundle\Entity\Config;
10use Wallabag\CoreBundle\Entity\User; 10use Wallabag\UserBundle\Entity\User;
11use Wallabag\CoreBundle\Form\Type\ChangePasswordType; 11use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
12use Wallabag\CoreBundle\Form\Type\UserInformationType; 12use Wallabag\CoreBundle\Form\Type\UserInformationType;
13use Wallabag\CoreBundle\Form\Type\NewUserType; 13use Wallabag\CoreBundle\Form\Type\NewUserType;
@@ -25,6 +25,7 @@ class ConfigController extends Controller
25 { 25 {
26 $em = $this->getDoctrine()->getManager(); 26 $em = $this->getDoctrine()->getManager();
27 $config = $this->getConfig(); 27 $config = $this->getConfig();
28 $userManager = $this->container->get('fos_user.user_manager');
28 $user = $this->getUser(); 29 $user = $this->getUser();
29 30
30 // handle basic config detail (this form is defined as a service) 31 // handle basic config detail (this form is defined as a service)
@@ -52,9 +53,8 @@ class ConfigController extends Controller
52 $pwdForm->handleRequest($request); 53 $pwdForm->handleRequest($request);
53 54
54 if ($pwdForm->isValid()) { 55 if ($pwdForm->isValid()) {
55 $user->setPassword($pwdForm->get('new_password')->getData()); 56 $user->setPlainPassword($pwdForm->get('new_password')->getData());
56 $em->persist($user); 57 $userManager->updateUser($user, true);
57 $em->flush();
58 58
59 $this->get('session')->getFlashBag()->add( 59 $this->get('session')->getFlashBag()->add(
60 'notice', 60 'notice',
@@ -69,8 +69,7 @@ class ConfigController extends Controller
69 $userForm->handleRequest($request); 69 $userForm->handleRequest($request);
70 70
71 if ($userForm->isValid()) { 71 if ($userForm->isValid()) {
72 $em->persist($user); 72 $userManager->updateUser($user, true);
73 $em->flush();
74 73
75 $this->get('session')->getFlashBag()->add( 74 $this->get('session')->getFlashBag()->add(
76 'notice', 75 'notice',
@@ -97,14 +96,14 @@ class ConfigController extends Controller
97 } 96 }
98 97
99 // handle adding new user 98 // handle adding new user
100 $newUser = new User(); 99 $newUser = $userManager->createUser();
101 // enable created user by default 100 // enable created user by default
102 $newUser->setEnabled(true); 101 $newUser->setEnabled(true);
103 $newUserForm = $this->createForm(new NewUserType(), $newUser, array('validation_groups' => array('Profile'))); 102 $newUserForm = $this->createForm(new NewUserType(), $newUser, array('validation_groups' => array('Profile')));
104 $newUserForm->handleRequest($request); 103 $newUserForm->handleRequest($request);
105 104
106 if ($newUserForm->isValid()) { 105 if ($newUserForm->isValid() && $this->get('security.authorization_checker')->isGranted('ROLE_SUPER_ADMIN')) {
107 $em->persist($newUser); 106 $userManager->updateUser($newUser, true);
108 107
109 $config = new Config($newUser); 108 $config = new Config($newUser);
110 $config->setTheme($this->container->getParameter('theme')); 109 $config->setTheme($this->container->getParameter('theme'));
diff --git a/src/Wallabag/CoreBundle/Controller/RssController.php b/src/Wallabag/CoreBundle/Controller/RssController.php
index 6121f361..023a6228 100644
--- a/src/Wallabag/CoreBundle/Controller/RssController.php
+++ b/src/Wallabag/CoreBundle/Controller/RssController.php
@@ -5,7 +5,7 @@ namespace Wallabag\CoreBundle\Controller;
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 6use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
7use Symfony\Bundle\FrameworkBundle\Controller\Controller; 7use Symfony\Bundle\FrameworkBundle\Controller\Controller;
8use Wallabag\CoreBundle\Entity\User; 8use Wallabag\UserBundle\Entity\User;
9use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
10use Pagerfanta\Adapter\DoctrineORMAdapter; 10use Pagerfanta\Adapter\DoctrineORMAdapter;
11use Pagerfanta\Pagerfanta; 11use Pagerfanta\Pagerfanta;
@@ -16,7 +16,7 @@ class RssController extends Controller
16 * Shows unread entries for current user. 16 * Shows unread entries for current user.
17 * 17 *
18 * @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"}) 18 * @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"})
19 * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter") 19 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
20 * 20 *
21 * @return \Symfony\Component\HttpFoundation\Response 21 * @return \Symfony\Component\HttpFoundation\Response
22 */ 22 */
@@ -29,7 +29,7 @@ class RssController extends Controller
29 * Shows read entries for current user. 29 * Shows read entries for current user.
30 * 30 *
31 * @Route("/{username}/{token}/archive.xml", name="archive_rss") 31 * @Route("/{username}/{token}/archive.xml", name="archive_rss")
32 * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter") 32 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
33 * 33 *
34 * @return \Symfony\Component\HttpFoundation\Response 34 * @return \Symfony\Component\HttpFoundation\Response
35 */ 35 */
@@ -42,7 +42,7 @@ class RssController extends Controller
42 * Shows starred entries for current user. 42 * Shows starred entries for current user.
43 * 43 *
44 * @Route("/{username}/{token}/starred.xml", name="starred_rss") 44 * @Route("/{username}/{token}/starred.xml", name="starred_rss")
45 * @ParamConverter("user", class="WallabagCoreBundle:User", converter="username_rsstoken_converter") 45 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
46 * 46 *
47 * @return \Symfony\Component\HttpFoundation\Response 47 * @return \Symfony\Component\HttpFoundation\Response
48 */ 48 */
diff --git a/src/Wallabag/CoreBundle/Controller/SecurityController.php b/src/Wallabag/CoreBundle/Controller/SecurityController.php
deleted file mode 100644
index f0a7ab6d..00000000
--- a/src/Wallabag/CoreBundle/Controller/SecurityController.php
+++ /dev/null
@@ -1,153 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Controller;
4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
7use Symfony\Bundle\FrameworkBundle\Controller\Controller;
8use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\Security\Core\SecurityContext;
10use Wallabag\CoreBundle\Form\Type\ResetPasswordType;
11
12class SecurityController extends Controller
13{
14 public function loginAction(Request $request)
15 {
16 $session = $request->getSession();
17 // get the login error if there is one
18 if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
19 $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
20 } else {
21 $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
22 $session->remove(SecurityContext::AUTHENTICATION_ERROR);
23 }
24
25 return $this->render('WallabagCoreBundle:Security:login.html.twig', array(
26 // last username entered by the user
27 'last_username' => $session->get(SecurityContext::LAST_USERNAME),
28 'error' => $error,
29 ));
30 }
31
32 /**
33 * Request forgot password: show form.
34 *
35 * @Route("/forgot-password", name="forgot_password")
36 *
37 * @Method({"GET", "POST"})
38 */
39 public function forgotPasswordAction(Request $request)
40 {
41 $form = $this->createForm('forgot_password');
42 $form->handleRequest($request);
43
44 if ($form->isValid()) {
45 $user = $this->getDoctrine()->getRepository('WallabagCoreBundle:User')->findOneByEmail($form->get('email')->getData());
46
47 // generate "hard" token
48 $user->setConfirmationToken(rtrim(strtr(base64_encode(hash('sha256', uniqid(mt_rand(), true), true)), '+/', '-_'), '='));
49 $user->setPasswordRequestedAt(new \DateTime());
50
51 $em = $this->getDoctrine()->getManager();
52 $em->persist($user);
53 $em->flush();
54
55 $message = \Swift_Message::newInstance()
56 ->setSubject('Reset Password')
57 ->setFrom($this->container->getParameter('from_email'))
58 ->setTo($user->getEmail())
59 ->setBody($this->renderView('WallabagCoreBundle:Mail:forgotPassword.txt.twig', array(
60 'username' => $user->getUsername(),
61 'confirmationUrl' => $this->generateUrl('forgot_password_reset', array('token' => $user->getConfirmationToken()), true),
62 )))
63 ;
64 $this->get('mailer')->send($message);
65
66 return $this->redirect($this->generateUrl('forgot_password_check_email',
67 array('email' => $this->getObfuscatedEmail($user->getEmail()))
68 ));
69 }
70
71 return $this->render('WallabagCoreBundle:Security:forgotPassword.html.twig', array(
72 'form' => $form->createView(),
73 ));
74 }
75
76 /**
77 * Tell the user to check his email provider.
78 *
79 * @Route("/forgot-password/check-email", name="forgot_password_check_email")
80 *
81 * @Method({"GET"})
82 */
83 public function checkEmailAction(Request $request)
84 {
85 $email = $request->query->get('email');
86
87 if (empty($email)) {
88 // the user does not come from the forgotPassword action
89 return $this->redirect($this->generateUrl('forgot_password'));
90 }
91
92 return $this->render('WallabagCoreBundle:Security:checkEmail.html.twig', array(
93 'email' => $email,
94 ));
95 }
96
97 /**
98 * Reset user password.
99 *
100 * @Route("/forgot-password/{token}", name="forgot_password_reset")
101 *
102 * @Method({"GET", "POST"})
103 */
104 public function resetAction(Request $request, $token)
105 {
106 $user = $this->getDoctrine()->getRepository('WallabagCoreBundle:User')->findOneByConfirmationToken($token);
107
108 if (null === $user) {
109 throw $this->createNotFoundException(sprintf('No user found with token "%s"', $token));
110 }
111
112 $form = $this->createForm(new ResetPasswordType());
113 $form->handleRequest($request);
114
115 if ($form->isValid()) {
116 $user->setPassword($form->get('new_password')->getData());
117
118 $em = $this->getDoctrine()->getManager();
119 $em->persist($user);
120 $em->flush();
121
122 $this->get('session')->getFlashBag()->add(
123 'notice',
124 'The password has been reset successfully'
125 );
126
127 return $this->redirect($this->generateUrl('login'));
128 }
129
130 return $this->render('WallabagCoreBundle:Security:reset.html.twig', array(
131 'token' => $token,
132 'form' => $form->createView(),
133 ));
134 }
135
136 /**
137 * Get the truncated email displayed when requesting the resetting.
138 *
139 * Keeping only the part following @ in the address.
140 *
141 * @param string $email
142 *
143 * @return string
144 */
145 protected function getObfuscatedEmail($email)
146 {
147 if (false !== $pos = strpos($email, '@')) {
148 $email = '...'.substr($email, $pos);
149 }
150
151 return $email;
152 }
153}
diff --git a/src/Wallabag/CoreBundle/Entity/Config.php b/src/Wallabag/CoreBundle/Entity/Config.php
index 025d94ef..2390bfe1 100644
--- a/src/Wallabag/CoreBundle/Entity/Config.php
+++ b/src/Wallabag/CoreBundle/Entity/Config.php
@@ -72,14 +72,14 @@ class Config
72 private $rssLimit; 72 private $rssLimit;
73 73
74 /** 74 /**
75 * @ORM\OneToOne(targetEntity="User", inversedBy="config") 75 * @ORM\OneToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="config")
76 */ 76 */
77 private $user; 77 private $user;
78 78
79 /* 79 /*
80 * @param User $user 80 * @param User $user
81 */ 81 */
82 public function __construct(User $user) 82 public function __construct(\Wallabag\UserBundle\Entity\User $user)
83 { 83 {
84 $this->user = $user; 84 $this->user = $user;
85 } 85 }
@@ -169,11 +169,11 @@ class Config
169 /** 169 /**
170 * Set user. 170 * Set user.
171 * 171 *
172 * @param \Wallabag\CoreBundle\Entity\User $user 172 * @param User $user
173 * 173 *
174 * @return Config 174 * @return Config
175 */ 175 */
176 public function setUser(\Wallabag\CoreBundle\Entity\User $user = null) 176 public function setUser(User $user = null)
177 { 177 {
178 $this->user = $user; 178 $this->user = $user;
179 179
@@ -183,7 +183,7 @@ class Config
183 /** 183 /**
184 * Get user. 184 * Get user.
185 * 185 *
186 * @return \Wallabag\CoreBundle\Entity\User 186 * @return User
187 */ 187 */
188 public function getUser() 188 public function getUser()
189 { 189 {
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php
index 9e81ba12..4fd74001 100644
--- a/src/Wallabag/CoreBundle/Entity/Entry.php
+++ b/src/Wallabag/CoreBundle/Entity/Entry.php
@@ -7,6 +7,7 @@ use Doctrine\ORM\Mapping as ORM;
7use Symfony\Component\Validator\Constraints as Assert; 7use Symfony\Component\Validator\Constraints as Assert;
8use Hateoas\Configuration\Annotation as Hateoas; 8use Hateoas\Configuration\Annotation as Hateoas;
9use JMS\Serializer\Annotation\XmlRoot; 9use JMS\Serializer\Annotation\XmlRoot;
10use Wallabag\UserBundle\Entity\User;
10 11
11/** 12/**
12 * Entry. 13 * Entry.
@@ -129,7 +130,7 @@ class Entry
129 private $isPublic; 130 private $isPublic;
130 131
131 /** 132 /**
132 * @ORM\ManyToOne(targetEntity="User", inversedBy="entries") 133 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="entries")
133 */ 134 */
134 private $user; 135 private $user;
135 136
@@ -142,7 +143,7 @@ class Entry
142 /* 143 /*
143 * @param User $user 144 * @param User $user
144 */ 145 */
145 public function __construct(User $user) 146 public function __construct(\Wallabag\UserBundle\Entity\User $user)
146 { 147 {
147 $this->user = $user; 148 $this->user = $user;
148 $this->tags = new ArrayCollection(); 149 $this->tags = new ArrayCollection();
diff --git a/src/Wallabag/CoreBundle/Entity/Tag.php b/src/Wallabag/CoreBundle/Entity/Tag.php
index 97c4579f..5b571823 100644
--- a/src/Wallabag/CoreBundle/Entity/Tag.php
+++ b/src/Wallabag/CoreBundle/Entity/Tag.php
@@ -42,11 +42,11 @@ class Tag
42 private $entries; 42 private $entries;
43 43
44 /** 44 /**
45 * @ORM\ManyToOne(targetEntity="User", inversedBy="tags") 45 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="tags")
46 */ 46 */
47 private $user; 47 private $user;
48 48
49 public function __construct(User $user) 49 public function __construct(\Wallabag\UserBundle\Entity\User $user)
50 { 50 {
51 $this->user = $user; 51 $this->user = $user;
52 $this->entries = new ArrayCollection(); 52 $this->entries = new ArrayCollection();
diff --git a/src/Wallabag/CoreBundle/EventListener/RegistrationConfirmedListener.php b/src/Wallabag/CoreBundle/EventListener/RegistrationConfirmedListener.php
new file mode 100644
index 00000000..68c25f1f
--- /dev/null
+++ b/src/Wallabag/CoreBundle/EventListener/RegistrationConfirmedListener.php
@@ -0,0 +1,50 @@
1<?php
2
3namespace Wallabag\CoreBundle\EventListener;
4
5use Symfony\Component\EventDispatcher\EventDispatcherInterface;
6use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7use Doctrine\ORM\EntityManager;
8use FOS\UserBundle\Event\FilterUserResponseEvent;
9use FOS\UserBundle\FOSUserEvents;
10use Wallabag\CoreBundle\Entity\Config;
11
12class RegistrationConfirmedListener implements EventSubscriberInterface
13{
14 private $em;
15 private $theme;
16 private $itemsOnPage;
17 private $rssLimit;
18 private $language;
19
20 public function __construct(EntityManager $em, $theme, $itemsOnPage, $rssLimit, $language)
21 {
22 $this->em = $em;
23 $this->theme = $theme;
24 $this->itemsOnPage = $itemsOnPage;
25 $this->rssLimit = $rssLimit;
26 $this->language = $language;
27 }
28
29 public static function getSubscribedEvents()
30 {
31 return array(
32 FOSUserEvents::REGISTRATION_CONFIRMED => 'authenticate',
33 );
34 }
35
36 public function authenticate(FilterUserResponseEvent $event, $eventName = null, EventDispatcherInterface $eventDispatcher = null)
37 {
38 if (!$event->getUser()->isEnabled()) {
39 return;
40 }
41
42 $config = new Config($event->getUser());
43 $config->setTheme($this->theme);
44 $config->setItemsPerPage($this->itemsOnPage);
45 $config->setRssLimit($this->rssLimit);
46 $config->setLanguage($this->language);
47 $this->em->persist($config);
48 $this->em->flush();
49 }
50}
diff --git a/src/Wallabag/CoreBundle/Filter/EntryFilterType.php b/src/Wallabag/CoreBundle/Filter/EntryFilterType.php
index 2e6d6ff7..32de21ca 100644
--- a/src/Wallabag/CoreBundle/Filter/EntryFilterType.php
+++ b/src/Wallabag/CoreBundle/Filter/EntryFilterType.php
@@ -7,7 +7,7 @@ use Symfony\Component\Form\FormBuilderInterface;
7use Symfony\Component\OptionsResolver\OptionsResolver; 7use Symfony\Component\OptionsResolver\OptionsResolver;
8use Lexik\Bundle\FormFilterBundle\Filter\Query\QueryInterface; 8use Lexik\Bundle\FormFilterBundle\Filter\Query\QueryInterface;
9use Doctrine\ORM\EntityRepository; 9use Doctrine\ORM\EntityRepository;
10use Wallabag\CoreBundle\Entity\User; 10use Wallabag\UserBundle\Entity\User;
11 11
12class EntryFilterType extends AbstractType 12class EntryFilterType extends AbstractType
13{ 13{
diff --git a/src/Wallabag/CoreBundle/Form/Type/ForgotPasswordType.php b/src/Wallabag/CoreBundle/Form/Type/ForgotPasswordType.php
deleted file mode 100644
index 9e95eb47..00000000
--- a/src/Wallabag/CoreBundle/Form/Type/ForgotPasswordType.php
+++ /dev/null
@@ -1,54 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Form\Type;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\FormBuilderInterface;
7use Symfony\Component\Validator\Constraints;
8use Symfony\Component\Validator\ExecutionContextInterface;
9use Doctrine\Bundle\DoctrineBundle\Registry;
10
11class ForgotPasswordType extends AbstractType
12{
13 private $doctrine = null;
14
15 public function __construct(Registry $doctrine)
16 {
17 $this->doctrine = $doctrine;
18 }
19
20 public function buildForm(FormBuilderInterface $builder, array $options)
21 {
22 $builder
23 ->add('email', 'email', array(
24 'required' => true,
25 'constraints' => array(
26 new Constraints\Email(),
27 new Constraints\NotBlank(),
28 new Constraints\Callback(array(array($this, 'validateEmail'))),
29 ),
30 ))
31 ;
32 }
33
34 public function getName()
35 {
36 return 'forgot_password';
37 }
38
39 public function validateEmail($email, ExecutionContextInterface $context)
40 {
41 $user = $this->doctrine
42 ->getRepository('WallabagCoreBundle:User')
43 ->findOneByEmail($email);
44
45 if (!$user) {
46 $context->addViolationAt(
47 'email',
48 'No user found with this email',
49 array(),
50 $email
51 );
52 }
53 }
54}
diff --git a/src/Wallabag/CoreBundle/Form/Type/NewUserType.php b/src/Wallabag/CoreBundle/Form/Type/NewUserType.php
index 985cb55b..8aabc8bb 100644
--- a/src/Wallabag/CoreBundle/Form/Type/NewUserType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/NewUserType.php
@@ -13,7 +13,8 @@ class NewUserType extends AbstractType
13 { 13 {
14 $builder 14 $builder
15 ->add('username', 'text', array('required' => true)) 15 ->add('username', 'text', array('required' => true))
16 ->add('password', 'password', array( 16 ->add('plainPassword', 'repeated', array(
17 'type' => 'password',
17 'constraints' => array( 18 'constraints' => array(
18 new Constraints\Length(array( 19 new Constraints\Length(array(
19 'min' => 8, 20 'min' => 8,
@@ -30,7 +31,7 @@ class NewUserType extends AbstractType
30 public function configureOptions(OptionsResolver $resolver) 31 public function configureOptions(OptionsResolver $resolver)
31 { 32 {
32 $resolver->setDefaults(array( 33 $resolver->setDefaults(array(
33 'data_class' => 'Wallabag\CoreBundle\Entity\User', 34 'data_class' => 'Wallabag\UserBundle\Entity\User',
34 )); 35 ));
35 } 36 }
36 37
diff --git a/src/Wallabag/CoreBundle/Form/Type/ResetPasswordType.php b/src/Wallabag/CoreBundle/Form/Type/ResetPasswordType.php
deleted file mode 100644
index 38f1a105..00000000
--- a/src/Wallabag/CoreBundle/Form/Type/ResetPasswordType.php
+++ /dev/null
@@ -1,35 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Form\Type;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\FormBuilderInterface;
7use Symfony\Component\Validator\Constraints;
8
9class ResetPasswordType extends AbstractType
10{
11 public function buildForm(FormBuilderInterface $builder, array $options)
12 {
13 $builder
14 ->add('new_password', 'repeated', array(
15 'type' => 'password',
16 'invalid_message' => 'The password fields must match.',
17 'required' => true,
18 'first_options' => array('label' => 'New password'),
19 'second_options' => array('label' => 'Repeat new password'),
20 'constraints' => array(
21 new Constraints\Length(array(
22 'min' => 8,
23 'minMessage' => 'Password should by at least 8 chars long',
24 )),
25 new Constraints\NotBlank(),
26 ),
27 ))
28 ;
29 }
30
31 public function getName()
32 {
33 return 'change_passwd';
34 }
35}
diff --git a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
index e3196d9c..84f02013 100644
--- a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
@@ -27,7 +27,7 @@ class UserInformationType extends AbstractType
27 public function configureOptions(OptionsResolver $resolver) 27 public function configureOptions(OptionsResolver $resolver)
28 { 28 {
29 $resolver->setDefaults(array( 29 $resolver->setDefaults(array(
30 'data_class' => 'Wallabag\CoreBundle\Entity\User', 30 'data_class' => 'Wallabag\UserBundle\Entity\User',
31 )); 31 ));
32 } 32 }
33 33
diff --git a/src/Wallabag/CoreBundle/Helper/DetectActiveTheme.php b/src/Wallabag/CoreBundle/Helper/DetectActiveTheme.php
index 446629db..679186c0 100644
--- a/src/Wallabag/CoreBundle/Helper/DetectActiveTheme.php
+++ b/src/Wallabag/CoreBundle/Helper/DetectActiveTheme.php
@@ -4,7 +4,7 @@ namespace Wallabag\CoreBundle\Helper;
4 4
5use Liip\ThemeBundle\Helper\DeviceDetectionInterface; 5use Liip\ThemeBundle\Helper\DeviceDetectionInterface;
6use Symfony\Component\Security\Core\SecurityContextInterface; 6use Symfony\Component\Security\Core\SecurityContextInterface;
7use Wallabag\CoreBundle\Entity\User; 7use Wallabag\UserBundle\Entity\User;
8 8
9/** 9/**
10 * This class intend to detect the active theme for the logged in user. 10 * This class intend to detect the active theme for the logged in user.
diff --git a/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php b/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
index 2771cf11..f7faa2c1 100644
--- a/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
+++ b/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
@@ -7,7 +7,7 @@ use Doctrine\Common\Persistence\ManagerRegistry;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
8use Symfony\Component\HttpFoundation\Request; 8use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 9use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
10use Wallabag\CoreBundle\Entity\User; 10use Wallabag\UserBundle\Entity\User;
11 11
12/** 12/**
13 * ParamConverter used in the RSS controller to retrieve the right user according to 13 * ParamConverter used in the RSS controller to retrieve the right user according to
@@ -49,7 +49,7 @@ class UsernameRssTokenConverter implements ParamConverterInterface
49 $em = $this->registry->getManagerForClass($configuration->getClass()); 49 $em = $this->registry->getManagerForClass($configuration->getClass());
50 50
51 // Check, if class name is what we need 51 // Check, if class name is what we need
52 if ('Wallabag\CoreBundle\Entity\User' !== $em->getClassMetadata($configuration->getClass())->getName()) { 52 if ('Wallabag\UserBundle\Entity\User' !== $em->getClassMetadata($configuration->getClass())->getName()) {
53 return false; 53 return false;
54 } 54 }
55 55
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 3beb5d0e..c38787de 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -13,6 +13,11 @@ services:
13 tags: 13 tags:
14 - { name: form.type, alias: config } 14 - { name: form.type, alias: config }
15 15
16 wallabag_core.form.registration:
17 class: Wallabag\CoreBundle\Form\Type\RegistrationType
18 tags:
19 - { name: form.type, alias: wallabag_user_registration }
20
16 wallabag_core.form.type.forgot_password: 21 wallabag_core.form.type.forgot_password:
17 class: Wallabag\CoreBundle\Form\Type\ForgotPasswordType 22 class: Wallabag\CoreBundle\Form\Type\ForgotPasswordType
18 arguments: 23 arguments:
@@ -29,7 +34,8 @@ services:
29 34
30 wallabag_core.doctrine.prefixed_naming_strategy: 35 wallabag_core.doctrine.prefixed_naming_strategy:
31 class: Wallabag\CoreBundle\Doctrine\Mapping\PrefixedNamingStrategy 36 class: Wallabag\CoreBundle\Doctrine\Mapping\PrefixedNamingStrategy
32 arguments: [%database_table_prefix%] 37 arguments:
38 - %database_table_prefix%
33 39
34 wallabag_core.graby: 40 wallabag_core.graby:
35 class: Graby\Graby 41 class: Graby\Graby
@@ -40,3 +46,14 @@ services:
40 class: Wallabag\CoreBundle\Helper\ContentProxy 46 class: Wallabag\CoreBundle\Helper\ContentProxy
41 arguments: 47 arguments:
42 - @wallabag_core.graby 48 - @wallabag_core.graby
49
50 wallabag_core.registration_confirmed:
51 class: Wallabag\CoreBundle\EventListener\RegistrationConfirmedListener
52 arguments:
53 - @doctrine.orm.entity_manager
54 - %theme%
55 - %items_on_page%
56 - %rss_limit%
57 - %language%
58 tags:
59 - { name: kernel.event_subscriber }
diff --git a/src/Wallabag/CoreBundle/Resources/views/base.html.twig b/src/Wallabag/CoreBundle/Resources/views/base.html.twig
index 152c5c28..c94c0044 100644
--- a/src/Wallabag/CoreBundle/Resources/views/base.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/base.html.twig
@@ -45,7 +45,7 @@
45 <script src="{{ asset('themes/_global/js/bookmarklet.js') }}"></script> 45 <script src="{{ asset('themes/_global/js/bookmarklet.js') }}"></script>
46 {% endblock %} 46 {% endblock %}
47 47
48 <title>{% block title %}{% endblock %} - wallabag</title> 48 <title>{% block title %}{% endblock %}</title>
49 {% endblock %} 49 {% endblock %}
50 </head> 50 </head>
51 51
@@ -60,7 +60,7 @@
60 60
61 {% block messages %}{% endblock %} 61 {% block messages %}{% endblock %}
62 62
63 <div id="content" class="w600p"> 63 <div id="content">
64 {% block content %}{% endblock %} 64 {% block content %}{% endblock %}
65 </div> 65 </div>
66 </main> 66 </main>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
index c90bb2e3..64305b16 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
@@ -135,6 +135,7 @@
135 {{ form_rest(form.pwd) }} 135 {{ form_rest(form.pwd) }}
136 </form> 136 </form>
137 137
138 {% if is_granted('ROLE_SUPER_ADMIN') %}
138 <h2>{% trans %}Add a user{% endtrans %}</h2> 139 <h2>{% trans %}Add a user{% endtrans %}</h2>
139 140
140 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.new_user) }}> 141 <form action="{{ path('config') }}" method="post" {{ form_enctype(form.new_user) }}>
@@ -150,9 +151,17 @@
150 151
151 <fieldset class="w500p inline"> 152 <fieldset class="w500p inline">
152 <div class="row"> 153 <div class="row">
153 {{ form_label(form.new_user.password) }} 154 {{ form_label(form.new_user.plainPassword.first) }}
154 {{ form_errors(form.new_user.password) }} 155 {{ form_errors(form.new_user.plainPassword.first) }}
155 {{ form_widget(form.new_user.password) }} 156 {{ form_widget(form.new_user.plainPassword.first) }}
157 </div>
158 </fieldset>
159
160 <fieldset class="w500p inline">
161 <div class="row">
162 {{ form_label(form.new_user.plainPassword.second) }}
163 {{ form_errors(form.new_user.plainPassword.second) }}
164 {{ form_widget(form.new_user.plainPassword.second) }}
156 </div> 165 </div>
157 </fieldset> 166 </fieldset>
158 167
@@ -165,5 +174,6 @@
165 </fieldset> 174 </fieldset>
166 175
167 {{ form_rest(form.new_user) }} 176 {{ form_rest(form.new_user) }}
177 {% endif %}
168 </form> 178 </form>
169{% endblock %} 179{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/forgotPassword.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/forgotPassword.html.twig
deleted file mode 100644
index 4476ea7b..00000000
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/forgotPassword.html.twig
+++ /dev/null
@@ -1,31 +0,0 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{% trans %}Forgot password{% endtrans %}{% endblock %}
4
5{% block body_class %}login{% endblock %}
6
7{% block menu %}{% endblock %}
8
9{% block content %}
10 <form action="{{ path('forgot_password') }}" method="post" name="forgotPasswordform">
11 <fieldset class="w500p center">
12 <h2 class="mbs txtcenter">{% trans %}Forgot password{% endtrans %}</h2>
13
14 {{ form_errors(form) }}
15
16 <p>Enter your email address below and we'll send you password reset instructions.</p>
17
18 <div class="row">
19 {{ form_label(form.email) }}
20 {{ form_errors(form.email) }}
21 {{ form_widget(form.email) }}
22 </div>
23
24 <div class="row mts txtcenter">
25 <button type="submit">Send me reset instructions</button>
26 </div>
27 </fieldset>
28
29 {{ form_rest(form) }}
30 </form>
31{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/reset.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/reset.html.twig
deleted file mode 100644
index fda88af2..00000000
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/reset.html.twig
+++ /dev/null
@@ -1,35 +0,0 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{% trans %}Change password{% endtrans %}{% endblock %}
4
5{% block body_class %}login{% endblock %}
6
7{% block menu %}{% endblock %}
8
9{% block content %}
10 <form action="{{ path('forgot_password_reset', {'token': token}) }}" method="post" name="loginform">
11 <fieldset class="w500p center">
12 <h2 class="mbs txtcenter">{% trans %}Change password{% endtrans %}</h2>
13
14 {{ form_errors(form) }}
15
16 <div class="row">
17 {{ form_label(form.new_password.first) }}
18 {{ form_errors(form.new_password.first) }}
19 {{ form_widget(form.new_password.first) }}
20 </div>
21
22 <div class="row">
23 {{ form_label(form.new_password.second) }}
24 {{ form_errors(form.new_password.second) }}
25 {{ form_widget(form.new_password.second) }}
26 </div>
27
28 <div class="row mts txtcenter">
29 <button type="submit">Change password</button>
30 </div>
31 </fieldset>
32
33 {{ form_rest(form) }}
34 </form>
35{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
index 3d573eaa..de4ed2e7 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
@@ -52,7 +52,7 @@
52 </li> 52 </li>
53 <li><a href="{{ path('config') }}">{% trans %}config{% endtrans %}</a></li> 53 <li><a href="{{ path('config') }}">{% trans %}config{% endtrans %}</a></li>
54 <li><a href="{{ path('about') }}">{% trans %}about{% endtrans %}</a></li> 54 <li><a href="{{ path('about') }}">{% trans %}about{% endtrans %}</a></li>
55 <li><a class="icon icon-power" href="{{ path('logout') }}" title="{% trans %}logout{% endtrans %}">{% trans %}logout{% endtrans %}</a></li> 55 <li><a class="icon icon-power" href="{{ path('fos_user_security_logout') }}" title="{% trans %}logout{% endtrans %}">{% trans %}logout{% endtrans %}</a></li>
56 </ul> 56 </ul>
57{% endblock %} 57{% endblock %}
58 58
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
index 0ff21f22..0d8e9f24 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
@@ -15,7 +15,9 @@
15 <li class="tab col s3"><a href="#set2">{% trans %}RSS{% endtrans %}</a></li> 15 <li class="tab col s3"><a href="#set2">{% trans %}RSS{% endtrans %}</a></li>
16 <li class="tab col s3"><a href="#set3">{% trans %}User information{% endtrans %}</a></li> 16 <li class="tab col s3"><a href="#set3">{% trans %}User information{% endtrans %}</a></li>
17 <li class="tab col s3"><a href="#set4">{% trans %}Password{% endtrans %}</a></li> 17 <li class="tab col s3"><a href="#set4">{% trans %}Password{% endtrans %}</a></li>
18 {% if is_granted('ROLE_SUPER_ADMIN') %}
18 <li class="tab col s3"><a href="#set5">{% trans %}Add a user{% endtrans %}</a></li> 19 <li class="tab col s3"><a href="#set5">{% trans %}Add a user{% endtrans %}</a></li>
20 {% endif %}
19 </ul> 21 </ul>
20 </div> 22 </div>
21 23
@@ -175,7 +177,7 @@
175 </form> 177 </form>
176 </div> 178 </div>
177 179
178 180 {% if is_granted('ROLE_SUPER_ADMIN') %}
179 <div id="set5" class="col s12"> 181 <div id="set5" class="col s12">
180 <form action="{{ path('config') }}#set5" method="post" {{ form_enctype(form.new_user) }}> 182 <form action="{{ path('config') }}#set5" method="post" {{ form_enctype(form.new_user) }}>
181 {{ form_errors(form.new_user) }} 183 {{ form_errors(form.new_user) }}
@@ -190,9 +192,17 @@
190 192
191 <div class="row"> 193 <div class="row">
192 <div class="input-field col s12"> 194 <div class="input-field col s12">
193 {{ form_label(form.new_user.password) }} 195 {{ form_label(form.new_user.plainPassword.first) }}
194 {{ form_errors(form.new_user.password) }} 196 {{ form_errors(form.new_user.plainPassword.first) }}
195 {{ form_widget(form.new_user.password) }} 197 {{ form_widget(form.new_user.plainPassword.first) }}
198 </div>
199 </div>
200
201 <div class="row">
202 <div class="input-field col s12">
203 {{ form_label(form.new_user.plainPassword.second) }}
204 {{ form_errors(form.new_user.plainPassword.second) }}
205 {{ form_widget(form.new_user.plainPassword.second) }}
196 </div> 206 </div>
197 </div> 207 </div>
198 208
@@ -211,6 +221,7 @@
211 221
212 </form> 222 </form>
213 </div> 223 </div>
224 {% endif %}
214 </div> 225 </div>
215 226
216 </div> 227 </div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig
index 4eb6d2b8..b0da42ce 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Security/login.html.twig
@@ -49,11 +49,12 @@
49 {% trans %}Login{% endtrans %} 49 {% trans %}Login{% endtrans %}
50 <i class="mdi-content-send right"></i> 50 <i class="mdi-content-send right"></i>
51 </button> 51 </button>
52 <a href="{{ path('fos_user_registration_register') }}">{% trans %}Register{% endtrans %}</a>
52 </div> 53 </div>
53 </form> 54 </form>
54 </div> 55 </div>
55 <div class="center"> 56 <div class="center">
56 <a href="{{ path('forgot_password') }}">{% trans %}Forgot your password?{% endtrans %}</a> 57 <a href="{{ path('fos_user_resetting_request') }}">{% trans %}Forgot your password?{% endtrans %}</a>
57 </div> 58 </div>
58 </div> 59 </div>
59 </main> 60 </main>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
index 10dede8a..36e276f9 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
@@ -46,8 +46,7 @@
46 <li class="bold border-bottom {% if currentRoute == 'tags' %}active{% endif %}"><a class="waves-effect" href="{{ path('tag') }}">{% trans %}tags{% endtrans %}</a></li> 46 <li class="bold border-bottom {% if currentRoute == 'tags' %}active{% endif %}"><a class="waves-effect" href="{{ path('tag') }}">{% trans %}tags{% endtrans %}</a></li>
47 <li class="bold {% if currentRoute == 'config' %}active{% endif %}"><a class="waves-effect" href="{{ path('config') }}">{% trans %}config{% endtrans %}</a></li> 47 <li class="bold {% if currentRoute == 'config' %}active{% endif %}"><a class="waves-effect" href="{{ path('config') }}">{% trans %}config{% endtrans %}</a></li>
48 <li class="bold {% if currentRoute == 'howto' %}active{% endif %}"><a class="waves-effect" href="{{ path('howto') }}">{% trans %}howto{% endtrans %}</a></li> 48 <li class="bold {% if currentRoute == 'howto' %}active{% endif %}"><a class="waves-effect" href="{{ path('howto') }}">{% trans %}howto{% endtrans %}</a></li>
49 <li class="bold border-bottom {% if currentRoute == 'about' %}active{% endif %}"><a class="waves-effect" href="{{ path('about') }}">{% trans %}About{% endtrans %}</a></li> 49 <li class="bold"><a class="waves-effect" class="icon icon-power" href="{{ path('fos_user_security_logout') }}" title="{% trans %}logout{% endtrans %}">{% trans %}logout{% endtrans %}</a></li>
50 <li class="bold"><a class="waves-effect" class="icon icon-power" href="{{ path('logout') }}" title="{% trans %}logout{% endtrans %}">{% trans %}logout{% endtrans %}</a></li>
51 </ul> 50 </ul>
52 <div class="nav-wrapper nav-panels"> 51 <div class="nav-wrapper nav-panels">
53 <a href="#" data-activates="slide-out" class="nav-panel-menu button-collapse"><i class="mdi-navigation-menu"></i></a> 52 <a href="#" data-activates="slide-out" class="nav-panel-menu button-collapse"><i class="mdi-navigation-menu"></i></a>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/css/main.css b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/css/main.css
index a5742905..1fe4a533 100755
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/css/main.css
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/css/main.css
@@ -24,6 +24,10 @@ body {
24 background: #f0f0f0; 24 background: #f0f0f0;
25} 25}
26 26
27body.login main {
28 padding: 0;
29}
30
27#warning_message { 31#warning_message {
28 position: fixed; 32 position: fixed;
29 background-color: #ff6347; 33 background-color: #ff6347;
@@ -250,9 +254,14 @@ main ul.row {
250} 254}
251 255
252.card .card-action a { 256.card .card-action a {
257 color: #ffffff;
253 margin: 0; 258 margin: 0;
254} 259}
255 260
261.card .card-action a:hover {
262 color: #ffffff;
263}
264
256.settings .div_tabs { 265.settings .div_tabs {
257 padding-bottom: 15px; 266 padding-bottom: 15px;
258} 267}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/img/logo-other_themes.png b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/img/logo-other_themes.png
index 32543a44..c90aa46f 100755
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/img/logo-other_themes.png
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/img/logo-other_themes.png
Binary files differ
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php b/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php
deleted file mode 100644
index 98b4e86b..00000000
--- a/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php
+++ /dev/null
@@ -1,87 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Security\Authentication\Encoder;
4
5use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
6use Symfony\Component\Security\Core\Exception\BadCredentialsException;
7
8/**
9 * This override just add en extra variable (username) to be able to salt the password
10 * the way Wallabag v1 does. It will avoid to break compatibility with Wallabag v1.
11 */
12class WallabagPasswordEncoder extends BasePasswordEncoder
13{
14 private $algorithm;
15 private $encodeHashAsBase64;
16 private $iterations;
17 private $username = null;
18
19 /**
20 * Constructor.
21 *
22 * @param string $algorithm The digest algorithm to use
23 * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash
24 * @param int $iterations The number of iterations to use to stretch the password hash
25 */
26 public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 5000)
27 {
28 $this->algorithm = $algorithm;
29 $this->encodeHashAsBase64 = $encodeHashAsBase64;
30 $this->iterations = $iterations;
31 }
32
33 public function setUsername($username)
34 {
35 $this->username = $username;
36 }
37
38 /**
39 * {@inheritdoc}
40 */
41 public function encodePassword($raw, $salt)
42 {
43 if ($this->isPasswordTooLong($raw)) {
44 throw new BadCredentialsException('Invalid password.');
45 }
46
47 if (!in_array($this->algorithm, hash_algos(), true)) {
48 throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
49 }
50
51 $salted = $this->mergePasswordAndSalt($raw, $salt);
52 $digest = hash($this->algorithm, $salted, true);
53
54 // "stretch" hash
55 for ($i = 1; $i < $this->iterations; ++$i) {
56 $digest = hash($this->algorithm, $digest.$salted, true);
57 }
58
59 return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
60 }
61
62 /**
63 * {@inheritdoc}
64 *
65 * We inject the username inside the salted password
66 */
67 protected function mergePasswordAndSalt($password, $salt)
68 {
69 if (null === $this->username) {
70 throw new \LogicException('We can not check the password without a username.');
71 }
72
73 if (empty($salt)) {
74 return $password;
75 }
76
77 return $password.$this->username.$salt;
78 }
79
80 /**
81 * {@inheritdoc}
82 */
83 public function isPasswordValid($encoded, $raw, $salt)
84 {
85 return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
86 }
87}
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php
deleted file mode 100644
index cf3cb051..00000000
--- a/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php
+++ /dev/null
@@ -1,89 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Security\Authentication\Provider;
4
5use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
6use Symfony\Component\Security\Core\User\UserProviderInterface;
7use Symfony\Component\Security\Core\User\UserCheckerInterface;
8use Symfony\Component\Security\Core\User\UserInterface;
9use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
10use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
11use Symfony\Component\Security\Core\Exception\BadCredentialsException;
12use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
13use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider;
14
15class WallabagAuthenticationProvider extends UserAuthenticationProvider
16{
17 private $encoderFactory;
18 private $userProvider;
19
20 /**
21 * Constructor.
22 *
23 * @param UserProviderInterface $userProvider An UserProviderInterface instance
24 * @param UserCheckerInterface $userChecker An UserCheckerInterface instance
25 * @param string $providerKey The provider key
26 * @param EncoderFactoryInterface $encoderFactory An EncoderFactoryInterface instance
27 * @param bool $hideUserNotFoundExceptions Whether to hide user not found exception or not
28 */
29 public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true)
30 {
31 parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
32
33 $this->encoderFactory = $encoderFactory;
34 $this->userProvider = $userProvider;
35 }
36
37 /**
38 * {@inheritdoc}
39 */
40 protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
41 {
42 $currentUser = $token->getUser();
43 if ($currentUser instanceof UserInterface) {
44 if ($currentUser->getPassword() !== $user->getPassword()) {
45 throw new BadCredentialsException('The credentials were changed from another session.');
46 }
47 } else {
48 if ('' === ($presentedPassword = $token->getCredentials())) {
49 throw new BadCredentialsException('The presented password cannot be empty.');
50 }
51
52 // give username, it's used to hash the password
53 $encoder = $this->encoderFactory->getEncoder($user);
54 $encoder->setUsername($user->getUsername());
55
56 if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
57 throw new BadCredentialsException('The presented password is invalid.');
58 }
59 }
60 }
61
62 /**
63 * {@inheritdoc}
64 */
65 protected function retrieveUser($username, UsernamePasswordToken $token)
66 {
67 $user = $token->getUser();
68 if ($user instanceof UserInterface) {
69 return $user;
70 }
71
72 try {
73 $user = $this->userProvider->loadUserByUsername($username);
74
75 if (!$user instanceof UserInterface) {
76 throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
77 }
78
79 return $user;
80 } catch (UsernameNotFoundException $notFound) {
81 $notFound->setUsername($username);
82 throw $notFound;
83 } catch (\Exception $repositoryProblem) {
84 $ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem);
85 $ex->setToken($token);
86 throw $ex;
87 }
88 }
89}
diff --git a/src/Wallabag/CoreBundle/Security/Validator/WallabagUserPasswordValidator.php b/src/Wallabag/CoreBundle/Security/Validator/WallabagUserPasswordValidator.php
deleted file mode 100644
index 52062773..00000000
--- a/src/Wallabag/CoreBundle/Security/Validator/WallabagUserPasswordValidator.php
+++ /dev/null
@@ -1,51 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Security\Validator;
4
5use Symfony\Component\Security\Core\User\UserInterface;
6use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
7use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
8use Symfony\Component\Validator\Constraint;
9use Symfony\Component\Validator\ConstraintValidator;
10use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
11use Symfony\Component\Validator\Exception\UnexpectedTypeException;
12use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
13
14/**
15 * @see Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator
16 */
17class WallabagUserPasswordValidator extends ConstraintValidator
18{
19 private $securityContext;
20 private $encoderFactory;
21
22 public function __construct(TokenStorageInterface $tokenStorage, EncoderFactoryInterface $encoderFactory)
23 {
24 $this->tokenStorage = $tokenStorage;
25 $this->encoderFactory = $encoderFactory;
26 }
27
28 /**
29 * {@inheritdoc}
30 */
31 public function validate($password, Constraint $constraint)
32 {
33 if (!$constraint instanceof UserPassword) {
34 throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UserPassword');
35 }
36
37 $user = $this->tokenStorage->getToken()->getUser();
38
39 if (!$user instanceof UserInterface) {
40 throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.');
41 }
42
43 // give username, it's used to hash the password
44 $encoder = $this->encoderFactory->getEncoder($user);
45 $encoder->setUsername($user->getUsername());
46
47 if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
48 $this->context->addViolation($constraint->message);
49 }
50 }
51}
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
index 3407fc5e..3da5e8b7 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/ConfigControllerTest.php
@@ -258,7 +258,8 @@ class ConfigControllerTest extends WallabagCoreTestCase
258 array( 258 array(
259 array( 259 array(
260 'new_user[username]' => '', 260 'new_user[username]' => '',
261 'new_user[password]' => '', 261 'new_user[plainPassword][first]' => '',
262 'new_user[plainPassword][second]' => '',
262 'new_user[email]' => '', 263 'new_user[email]' => '',
263 ), 264 ),
264 'Please enter a username', 265 'Please enter a username',
@@ -266,7 +267,8 @@ class ConfigControllerTest extends WallabagCoreTestCase
266 array( 267 array(
267 array( 268 array(
268 'new_user[username]' => 'a', 269 'new_user[username]' => 'a',
269 'new_user[password]' => 'mypassword', 270 'new_user[plainPassword][first]' => 'mypassword',
271 'new_user[plainPassword][second]' => 'mypassword',
270 'new_user[email]' => '', 272 'new_user[email]' => '',
271 ), 273 ),
272 'The username is too short', 274 'The username is too short',
@@ -274,7 +276,8 @@ class ConfigControllerTest extends WallabagCoreTestCase
274 array( 276 array(
275 array( 277 array(
276 'new_user[username]' => 'wallace', 278 'new_user[username]' => 'wallace',
277 'new_user[password]' => 'mypassword', 279 'new_user[plainPassword][first]' => 'mypassword',
280 'new_user[plainPassword][second]' => 'mypassword',
278 'new_user[email]' => 'test', 281 'new_user[email]' => 'test',
279 ), 282 ),
280 'The email is not valid', 283 'The email is not valid',
@@ -282,11 +285,21 @@ class ConfigControllerTest extends WallabagCoreTestCase
282 array( 285 array(
283 array( 286 array(
284 'new_user[username]' => 'admin', 287 'new_user[username]' => 'admin',
285 'new_user[password]' => 'wallacewallace', 288 'new_user[plainPassword][first]' => 'wallacewallace',
289 'new_user[plainPassword][second]' => 'wallacewallace',
286 'new_user[email]' => 'wallace@wallace.me', 290 'new_user[email]' => 'wallace@wallace.me',
287 ), 291 ),
288 'The username is already used', 292 'The username is already used',
289 ), 293 ),
294 array(
295 array(
296 'new_user[username]' => 'wallace',
297 'new_user[plainPassword][first]' => 'mypassword1',
298 'new_user[plainPassword][second]' => 'mypassword2',
299 'new_user[email]' => 'wallace@wallace.me',
300 ),
301 'This value is not valid',
302 ),
290 ); 303 );
291 } 304 }
292 305
@@ -325,7 +338,8 @@ class ConfigControllerTest extends WallabagCoreTestCase
325 338
326 $data = array( 339 $data = array(
327 'new_user[username]' => 'wallace', 340 'new_user[username]' => 'wallace',
328 'new_user[password]' => 'wallace1', 341 'new_user[plainPassword][first]' => 'wallace1',
342 'new_user[plainPassword][second]' => 'wallace1',
329 'new_user[email]' => 'wallace@wallace.me', 343 'new_user[email]' => 'wallace@wallace.me',
330 ); 344 );
331 345
@@ -340,7 +354,7 @@ class ConfigControllerTest extends WallabagCoreTestCase
340 354
341 $em = $client->getContainer()->get('doctrine.orm.entity_manager'); 355 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
342 $user = $em 356 $user = $em
343 ->getRepository('WallabagCoreBundle:User') 357 ->getRepository('WallabagUserBundle:User')
344 ->findOneByUsername('wallace'); 358 ->findOneByUsername('wallace');
345 359
346 $this->assertTrue(false !== $user); 360 $this->assertTrue(false !== $user);
@@ -355,7 +369,7 @@ class ConfigControllerTest extends WallabagCoreTestCase
355 // reset the token 369 // reset the token
356 $em = $client->getContainer()->get('doctrine.orm.entity_manager'); 370 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
357 $user = $em 371 $user = $em
358 ->getRepository('WallabagCoreBundle:User') 372 ->getRepository('WallabagUserBundle:User')
359 ->findOneByUsername('admin'); 373 ->findOneByUsername('admin');
360 374
361 if (!$user) { 375 if (!$user) {
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php
index b7c162a7..45a74c43 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/RssControllerTest.php
@@ -64,7 +64,7 @@ class RssControllerTest extends WallabagCoreTestCase
64 $client = $this->getClient(); 64 $client = $this->getClient();
65 $em = $client->getContainer()->get('doctrine.orm.entity_manager'); 65 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
66 $user = $em 66 $user = $em
67 ->getRepository('WallabagCoreBundle:User') 67 ->getRepository('WallabagUserBundle:User')
68 ->findOneByUsername('admin'); 68 ->findOneByUsername('admin');
69 69
70 $config = $user->getConfig(); 70 $config = $user->getConfig();
@@ -85,7 +85,7 @@ class RssControllerTest extends WallabagCoreTestCase
85 $client = $this->getClient(); 85 $client = $this->getClient();
86 $em = $client->getContainer()->get('doctrine.orm.entity_manager'); 86 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
87 $user = $em 87 $user = $em
88 ->getRepository('WallabagCoreBundle:User') 88 ->getRepository('WallabagUserBundle:User')
89 ->findOneByUsername('admin'); 89 ->findOneByUsername('admin');
90 90
91 $config = $user->getConfig(); 91 $config = $user->getConfig();
@@ -107,7 +107,7 @@ class RssControllerTest extends WallabagCoreTestCase
107 $client = $this->getClient(); 107 $client = $this->getClient();
108 $em = $client->getContainer()->get('doctrine.orm.entity_manager'); 108 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
109 $user = $em 109 $user = $em
110 ->getRepository('WallabagCoreBundle:User') 110 ->getRepository('WallabagUserBundle:User')
111 ->findOneByUsername('admin'); 111 ->findOneByUsername('admin');
112 112
113 $config = $user->getConfig(); 113 $config = $user->getConfig();
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php
deleted file mode 100644
index 759ef01b..00000000
--- a/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php
+++ /dev/null
@@ -1,201 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tests\Controller;
4
5use Symfony\Component\Filesystem\Filesystem;
6use Symfony\Component\Finder\Finder;
7use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
8
9class SecurityControllerTest extends WallabagCoreTestCase
10{
11 public function testLogin()
12 {
13 $client = $this->getClient();
14
15 $crawler = $client->request('GET', '/new');
16
17 $this->assertEquals(302, $client->getResponse()->getStatusCode());
18 $this->assertContains('login', $client->getResponse()->headers->get('location'));
19 }
20
21 public function testLoginFail()
22 {
23 $client = $this->getClient();
24
25 $crawler = $client->request('GET', '/login');
26
27 $form = $crawler->filter('button[type=submit]')->form();
28 $data = array(
29 '_username' => 'admin',
30 '_password' => 'admin',
31 );
32
33 $client->submit($form, $data);
34
35 $this->assertEquals(302, $client->getResponse()->getStatusCode());
36 $this->assertContains('login', $client->getResponse()->headers->get('location'));
37
38 $crawler = $client->followRedirect();
39
40 $this->assertContains('Bad credentials', $client->getResponse()->getContent());
41 }
42
43 public function testRedirectionAfterLogin()
44 {
45 $client = $this->getClient();
46 $client->followRedirects();
47
48 $crawler = $client->request('GET', '/config');
49
50 $form = $crawler->filter('button[type=submit]')->form();
51
52 $data = array(
53 '_username' => 'admin',
54 '_password' => 'mypassword',
55 );
56
57 $client->submit($form, $data);
58
59 $this->assertContains('RSS', $client->getResponse()->getContent());
60 }
61
62 public function testForgotPassword()
63 {
64 $client = $this->getClient();
65
66 $crawler = $client->request('GET', '/forgot-password');
67
68 $this->assertEquals(200, $client->getResponse()->getStatusCode());
69
70 $this->assertContains('Forgot password', $client->getResponse()->getContent());
71
72 $form = $crawler->filter('button[type=submit]');
73
74 $this->assertCount(1, $form);
75
76 return array(
77 'form' => $form->form(),
78 'client' => $client,
79 );
80 }
81
82 /**
83 * @depends testForgotPassword
84 */
85 public function testSubmitForgotPasswordFail($parameters)
86 {
87 $form = $parameters['form'];
88 $client = $parameters['client'];
89
90 $data = array(
91 'forgot_password[email]' => 'material',
92 );
93
94 $client->submit($form, $data);
95
96 $this->assertEquals(200, $client->getResponse()->getStatusCode());
97 $this->assertContains('No user found with this email', $client->getResponse()->getContent());
98 }
99
100 /**
101 * @depends testForgotPassword
102 *
103 * Instead of using collector which slow down the test suite
104 * http://symfony.com/doc/current/cookbook/email/testing.html
105 *
106 * Use a different way where Swift store email as file
107 */
108 public function testSubmitForgotPassword($parameters)
109 {
110 $form = $parameters['form'];
111 $client = $parameters['client'];
112
113 $spoolDir = $client->getKernel()->getContainer()->getParameter('swiftmailer.spool.default.file.path');
114
115 // cleanup pool dir
116 $filesystem = new Filesystem();
117 $filesystem->remove($spoolDir);
118
119 // to use `getCollector` since `collect: false` in config_test.yml
120 $client->enableProfiler();
121
122 $data = array(
123 'forgot_password[email]' => 'bobby@wallabag.org',
124 );
125
126 $client->submit($form, $data);
127
128 $this->assertEquals(302, $client->getResponse()->getStatusCode());
129
130 $crawler = $client->followRedirect();
131
132 $this->assertContains('An email has been sent to', $client->getResponse()->getContent());
133
134 // find every files (ie: emails) inside the spool dir except hidden files
135 $finder = new Finder();
136 $finder
137 ->in($spoolDir)
138 ->ignoreDotFiles(true)
139 ->files();
140
141 $this->assertCount(1, $finder, 'Only one email has been sent');
142
143 foreach ($finder as $file) {
144 $message = unserialize(file_get_contents($file));
145
146 $this->assertInstanceOf('Swift_Message', $message);
147 $this->assertEquals('Reset Password', $message->getSubject());
148 $this->assertEquals('no-reply@wallabag.org', key($message->getFrom()));
149 $this->assertEquals('bobby@wallabag.org', key($message->getTo()));
150 $this->assertContains(
151 'To reset your password - please visit',
152 $message->getBody()
153 );
154 }
155 }
156
157 public function testReset()
158 {
159 $client = $this->getClient();
160 $user = $client->getContainer()
161 ->get('doctrine.orm.entity_manager')
162 ->getRepository('WallabagCoreBundle:User')
163 ->findOneByEmail('bobby@wallabag.org');
164
165 $crawler = $client->request('GET', '/forgot-password/'.$user->getConfirmationToken());
166
167 $this->assertEquals(200, $client->getResponse()->getStatusCode());
168 $this->assertCount(2, $crawler->filter('input[type=password]'));
169 $this->assertCount(1, $form = $crawler->filter('button[type=submit]'));
170 $this->assertCount(1, $form);
171
172 $data = array(
173 'change_passwd[new_password][first]' => 'mypassword',
174 'change_passwd[new_password][second]' => 'mypassword',
175 );
176
177 $client->submit($form->form(), $data);
178
179 $this->assertEquals(302, $client->getResponse()->getStatusCode());
180 $this->assertContains('login', $client->getResponse()->headers->get('location'));
181 }
182
183 public function testResetBadToken()
184 {
185 $client = $this->getClient();
186
187 $client->request('GET', '/forgot-password/UIZOAU29UE902IEPZO');
188
189 $this->assertEquals(404, $client->getResponse()->getStatusCode());
190 }
191
192 public function testCheckEmailWithoutEmail()
193 {
194 $client = $this->getClient();
195
196 $client->request('GET', '/forgot-password/check-email');
197
198 $this->assertEquals(302, $client->getResponse()->getStatusCode());
199 $this->assertContains('forgot-password', $client->getResponse()->headers->get('location'));
200 }
201}
diff --git a/src/Wallabag/CoreBundle/Tests/EventListener/RegistrationConfirmedListenerTest.php b/src/Wallabag/CoreBundle/Tests/EventListener/RegistrationConfirmedListenerTest.php
new file mode 100644
index 00000000..df94fad2
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/EventListener/RegistrationConfirmedListenerTest.php
@@ -0,0 +1,92 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tests\EventListener;
4
5use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
6use Symfony\Component\EventDispatcher\EventDispatcher;
7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpFoundation\Response;
9use FOS\UserBundle\FOSUserEvents;
10use FOS\UserBundle\Event\FilterUserResponseEvent;
11use Wallabag\CoreBundle\EventListener\RegistrationConfirmedListener;
12use Wallabag\CoreBundle\Entity\Config;
13use Wallabag\UserBundle\Entity\User;
14
15class RegistrationConfirmedListenerTest extends KernelTestCase
16{
17 private $em;
18 private $listener;
19 private $dispatcher;
20 private $request;
21 private $response;
22
23 protected function setUp()
24 {
25 $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
26 ->disableOriginalConstructor()
27 ->getMock();
28
29 $this->listener = new RegistrationConfirmedListener(
30 $this->em,
31 'baggy',
32 20,
33 50,
34 'fr'
35 );
36
37 $this->dispatcher = new EventDispatcher();
38 $this->dispatcher->addSubscriber($this->listener);
39
40 $this->request = Request::create('/');
41 $this->response = Response::create();
42 }
43
44 public function testWithInvalidUser()
45 {
46 $user = new User();
47 $user->setEnabled(false);
48
49 $event = new FilterUserResponseEvent(
50 $user,
51 $this->request,
52 $this->response
53 );
54
55 $this->em->expects($this->never())->method('persist');
56 $this->em->expects($this->never())->method('flush');
57
58 $this->dispatcher->dispatch(
59 FOSUserEvents::REGISTRATION_CONFIRMED,
60 $event
61 );
62 }
63
64 public function testWithValidUser()
65 {
66 $user = new User();
67 $user->setEnabled(true);
68
69 $event = new FilterUserResponseEvent(
70 $user,
71 $this->request,
72 $this->response
73 );
74
75 $config = new Config($user);
76 $config->setTheme('baggy');
77 $config->setItemsPerPage(20);
78 $config->setRssLimit(50);
79 $config->setLanguage('fr');
80
81 $this->em->expects($this->once())
82 ->method('persist')
83 ->will($this->returnValue($config));
84 $this->em->expects($this->once())
85 ->method('flush');
86
87 $this->dispatcher->dispatch(
88 FOSUserEvents::REGISTRATION_CONFIRMED,
89 $event
90 );
91 }
92}
diff --git a/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php b/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php
index 0d338389..1d0d4062 100644
--- a/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php
@@ -4,7 +4,7 @@ namespace Wallabag\CoreBundle\Tests\Helper;
4 4
5use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; 5use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
6use Wallabag\CoreBundle\Entity\Entry; 6use Wallabag\CoreBundle\Entity\Entry;
7use Wallabag\CoreBundle\Entity\User; 7use Wallabag\UserBundle\Entity\User;
8use Wallabag\CoreBundle\Helper\ContentProxy; 8use Wallabag\CoreBundle\Helper\ContentProxy;
9 9
10class ContentProxyTest extends KernelTestCase 10class ContentProxyTest extends KernelTestCase
diff --git a/src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php b/src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php
index ebb550b5..e28dc4ba 100644
--- a/src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php
+++ b/src/Wallabag/CoreBundle/Tests/ParamConverter/UsernameRssTokenConverterTest.php
@@ -6,7 +6,7 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
6use Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter; 6use Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
8use Symfony\Component\HttpFoundation\Request; 8use Symfony\Component\HttpFoundation\Request;
9use Wallabag\CoreBundle\Entity\User; 9use Wallabag\UserBundle\Entity\User;
10 10
11class UsernameRssTokenConverterTest extends KernelTestCase 11class UsernameRssTokenConverterTest extends KernelTestCase
12{ 12{
@@ -96,7 +96,7 @@ class UsernameRssTokenConverterTest extends KernelTestCase
96 96
97 $meta->expects($this->once()) 97 $meta->expects($this->once())
98 ->method('getName') 98 ->method('getName')
99 ->will($this->returnValue('Wallabag\CoreBundle\Entity\User')); 99 ->will($this->returnValue('Wallabag\UserBundle\Entity\User'));
100 100
101 $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager') 101 $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')
102 ->disableOriginalConstructor() 102 ->disableOriginalConstructor()
@@ -104,7 +104,7 @@ class UsernameRssTokenConverterTest extends KernelTestCase
104 104
105 $em->expects($this->once()) 105 $em->expects($this->once())
106 ->method('getClassMetadata') 106 ->method('getClassMetadata')
107 ->with('WallabagCoreBundle:User') 107 ->with('WallabagUserBundle:User')
108 ->will($this->returnValue($meta)); 108 ->will($this->returnValue($meta));
109 109
110 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry') 110 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
@@ -117,10 +117,10 @@ class UsernameRssTokenConverterTest extends KernelTestCase
117 117
118 $registry->expects($this->once()) 118 $registry->expects($this->once())
119 ->method('getManagerForClass') 119 ->method('getManagerForClass')
120 ->with('WallabagCoreBundle:User') 120 ->with('WallabagUserBundle:User')
121 ->will($this->returnValue($em)); 121 ->will($this->returnValue($em));
122 122
123 $params = new ParamConverter(array('class' => 'WallabagCoreBundle:User')); 123 $params = new ParamConverter(array('class' => 'WallabagUserBundle:User'));
124 $converter = new UsernameRssTokenConverter($registry); 124 $converter = new UsernameRssTokenConverter($registry);
125 125
126 $this->assertTrue($converter->supports($params)); 126 $this->assertTrue($converter->supports($params));
@@ -144,7 +144,7 @@ class UsernameRssTokenConverterTest extends KernelTestCase
144 */ 144 */
145 public function testApplyUserNotFound() 145 public function testApplyUserNotFound()
146 { 146 {
147 $repo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\UserRepository') 147 $repo = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
148 ->disableOriginalConstructor() 148 ->disableOriginalConstructor()
149 ->getMock(); 149 ->getMock();
150 150
@@ -159,7 +159,7 @@ class UsernameRssTokenConverterTest extends KernelTestCase
159 159
160 $em->expects($this->once()) 160 $em->expects($this->once())
161 ->method('getRepository') 161 ->method('getRepository')
162 ->with('WallabagCoreBundle:User') 162 ->with('WallabagUserBundle:User')
163 ->will($this->returnValue($repo)); 163 ->will($this->returnValue($repo));
164 164
165 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry') 165 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
@@ -168,10 +168,10 @@ class UsernameRssTokenConverterTest extends KernelTestCase
168 168
169 $registry->expects($this->once()) 169 $registry->expects($this->once())
170 ->method('getManagerForClass') 170 ->method('getManagerForClass')
171 ->with('WallabagCoreBundle:User') 171 ->with('WallabagUserBundle:User')
172 ->will($this->returnValue($em)); 172 ->will($this->returnValue($em));
173 173
174 $params = new ParamConverter(array('class' => 'WallabagCoreBundle:User')); 174 $params = new ParamConverter(array('class' => 'WallabagUserBundle:User'));
175 $converter = new UsernameRssTokenConverter($registry); 175 $converter = new UsernameRssTokenConverter($registry);
176 $request = new Request(array(), array(), array('username' => 'test', 'token' => 'test')); 176 $request = new Request(array(), array(), array('username' => 'test', 'token' => 'test'));
177 177
@@ -182,7 +182,7 @@ class UsernameRssTokenConverterTest extends KernelTestCase
182 { 182 {
183 $user = new User(); 183 $user = new User();
184 184
185 $repo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\UserRepository') 185 $repo = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
186 ->disableOriginalConstructor() 186 ->disableOriginalConstructor()
187 ->getMock(); 187 ->getMock();
188 188
@@ -197,7 +197,7 @@ class UsernameRssTokenConverterTest extends KernelTestCase
197 197
198 $em->expects($this->once()) 198 $em->expects($this->once())
199 ->method('getRepository') 199 ->method('getRepository')
200 ->with('WallabagCoreBundle:User') 200 ->with('WallabagUserBundle:User')
201 ->will($this->returnValue($repo)); 201 ->will($this->returnValue($repo));
202 202
203 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry') 203 $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
@@ -206,10 +206,10 @@ class UsernameRssTokenConverterTest extends KernelTestCase
206 206
207 $registry->expects($this->once()) 207 $registry->expects($this->once())
208 ->method('getManagerForClass') 208 ->method('getManagerForClass')
209 ->with('WallabagCoreBundle:User') 209 ->with('WallabagUserBundle:User')
210 ->will($this->returnValue($em)); 210 ->will($this->returnValue($em));
211 211
212 $params = new ParamConverter(array('class' => 'WallabagCoreBundle:User', 'name' => 'user')); 212 $params = new ParamConverter(array('class' => 'WallabagUserBundle:User', 'name' => 'user'));
213 $converter = new UsernameRssTokenConverter($registry); 213 $converter = new UsernameRssTokenConverter($registry);
214 $request = new Request(array(), array(), array('username' => 'test', 'token' => 'test')); 214 $request = new Request(array(), array(), array('username' => 'test', 'token' => 'test'));
215 215
diff --git a/src/Wallabag/UserBundle/Controller/ResettingController.php b/src/Wallabag/UserBundle/Controller/ResettingController.php
new file mode 100644
index 00000000..8aa1e230
--- /dev/null
+++ b/src/Wallabag/UserBundle/Controller/ResettingController.php
@@ -0,0 +1,75 @@
1<?php
2
3namespace Wallabag\UserBundle\Controller;
4
5use FOS\UserBundle\FOSUserEvents;
6use FOS\UserBundle\Event\FormEvent;
7use FOS\UserBundle\Event\GetResponseUserEvent;
8use FOS\UserBundle\Event\FilterUserResponseEvent;
9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpFoundation\RedirectResponse;
11use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
12
13class ResettingController extends \FOS\UserBundle\Controller\ResettingController
14{
15 /**
16 * Extends ResettingController to change the redirection after success.
17 *
18 * @param Request $request
19 * @param $token
20 *
21 * @return null|RedirectResponse|\Symfony\Component\HttpFoundation\Response
22 */
23 public function resetAction(Request $request, $token)
24 {
25 /** @var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
26 $formFactory = $this->get('fos_user.resetting.form.factory');
27 /** @var $userManager \FOS\UserBundle\Model\UserManagerInterface */
28 $userManager = $this->get('fos_user.user_manager');
29 /** @var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
30 $dispatcher = $this->get('event_dispatcher');
31
32 $user = $userManager->findUserByConfirmationToken($token);
33
34 if (null === $user) {
35 throw new NotFoundHttpException(sprintf('The user with "confirmation token" does not exist for value "%s"', $token));
36 }
37
38 $event = new GetResponseUserEvent($user, $request);
39 $dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_INITIALIZE, $event);
40
41 if (null !== $event->getResponse()) {
42 return $event->getResponse();
43 }
44
45 $form = $formFactory->createForm();
46 $form->setData($user);
47
48 $form->handleRequest($request);
49
50 if ($form->isValid()) {
51 $event = new FormEvent($form, $request);
52 $dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_SUCCESS, $event);
53
54 $userManager->updateUser($user);
55
56 if (null === $response = $event->getResponse()) {
57 $this->get('session')->getFlashBag()->add(
58 'notice',
59 'Password updated'
60 );
61 $url = $this->generateUrl('homepage');
62 $response = new RedirectResponse($url);
63 }
64
65 $dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
66
67 return $response;
68 }
69
70 return $this->render('FOSUserBundle:Resetting:reset.html.twig', array(
71 'token' => $token,
72 'form' => $form->createView(),
73 ));
74 }
75}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php b/src/Wallabag/UserBundle/DataFixtures/ORM/LoadUserData.php
index 4ef53329..d48855da 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php
+++ b/src/Wallabag/UserBundle/DataFixtures/ORM/LoadUserData.php
@@ -1,11 +1,11 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\DataFixtures\ORM; 3namespace Wallabag\UserBundle\DataFixtures\ORM;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\User; 8use Wallabag\UserBundle\Entity\User;
9 9
10class LoadUserData extends AbstractFixture implements OrderedFixtureInterface 10class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
11{ 11{
@@ -18,8 +18,9 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
18 $userAdmin->setName('Big boss'); 18 $userAdmin->setName('Big boss');
19 $userAdmin->setEmail('bigboss@wallabag.org'); 19 $userAdmin->setEmail('bigboss@wallabag.org');
20 $userAdmin->setUsername('admin'); 20 $userAdmin->setUsername('admin');
21 $userAdmin->setPassword('mypassword'); 21 $userAdmin->setPlainPassword('mypassword');
22 $userAdmin->setEnabled(true); 22 $userAdmin->setEnabled(true);
23 $userAdmin->addRole('ROLE_SUPER_ADMIN');
23 24
24 $manager->persist($userAdmin); 25 $manager->persist($userAdmin);
25 26
@@ -29,7 +30,7 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
29 $bobUser->setName('Bobby'); 30 $bobUser->setName('Bobby');
30 $bobUser->setEmail('bobby@wallabag.org'); 31 $bobUser->setEmail('bobby@wallabag.org');
31 $bobUser->setUsername('bob'); 32 $bobUser->setUsername('bob');
32 $bobUser->setPassword('mypassword'); 33 $bobUser->setPlainPassword('mypassword');
33 $bobUser->setEnabled(true); 34 $bobUser->setEnabled(true);
34 35
35 $manager->persist($bobUser); 36 $manager->persist($bobUser);
diff --git a/src/Wallabag/CoreBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index a6002352..8f02e070 100644
--- a/src/Wallabag/CoreBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -1,20 +1,22 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\Entity; 3namespace Wallabag\UserBundle\Entity;
4 4
5use Doctrine\Common\Collections\ArrayCollection; 5use Doctrine\Common\Collections\ArrayCollection;
6use Doctrine\ORM\Mapping as ORM; 6use Doctrine\ORM\Mapping as ORM;
7use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 7use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
8use Symfony\Component\Security\Core\User\UserInterface; 8use Symfony\Component\Security\Core\User\UserInterface;
9use Symfony\Component\Security\Core\User\AdvancedUserInterface;
10use JMS\Serializer\Annotation\ExclusionPolicy; 9use JMS\Serializer\Annotation\ExclusionPolicy;
11use JMS\Serializer\Annotation\Expose; 10use JMS\Serializer\Annotation\Expose;
12use FOS\UserBundle\Model\User as BaseUser; 11use FOS\UserBundle\Model\User as BaseUser;
12use Wallabag\CoreBundle\Entity\Config;
13use Wallabag\CoreBundle\Entity\Entry;
14use Wallabag\CoreBundle\Entity\Tag;
13 15
14/** 16/**
15 * User. 17 * User.
16 * 18 *
17 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\UserRepository") 19 * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository")
18 * @ORM\Table 20 * @ORM\Table
19 * @ORM\HasLifecycleCallbacks() 21 * @ORM\HasLifecycleCallbacks()
20 * @ExclusionPolicy("all") 22 * @ExclusionPolicy("all")
@@ -22,7 +24,7 @@ use FOS\UserBundle\Model\User as BaseUser;
22 * @UniqueEntity("email") 24 * @UniqueEntity("email")
23 * @UniqueEntity("username") 25 * @UniqueEntity("username")
24 */ 26 */
25class User extends BaseUser implements AdvancedUserInterface, \Serializable 27class User extends BaseUser
26{ 28{
27 /** 29 /**
28 * @var int 30 * @var int
@@ -56,17 +58,17 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable
56 protected $updatedAt; 58 protected $updatedAt;
57 59
58 /** 60 /**
59 * @ORM\OneToMany(targetEntity="Entry", mappedBy="user", cascade={"remove"}) 61 * @ORM\OneToMany(targetEntity="Wallabag\CoreBundle\Entity\Entry", mappedBy="user", cascade={"remove"})
60 */ 62 */
61 protected $entries; 63 protected $entries;
62 64
63 /** 65 /**
64 * @ORM\OneToOne(targetEntity="Config", mappedBy="user") 66 * @ORM\OneToOne(targetEntity="Wallabag\CoreBundle\Entity\Config", mappedBy="user")
65 */ 67 */
66 protected $config; 68 protected $config;
67 69
68 /** 70 /**
69 * @ORM\OneToMany(targetEntity="Tag", mappedBy="user", cascade={"remove"}) 71 * @ORM\OneToMany(targetEntity="Wallabag\CoreBundle\Entity\Tag", mappedBy="user", cascade={"remove"})
70 */ 72 */
71 protected $tags; 73 protected $tags;
72 74
@@ -75,6 +77,7 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable
75 parent::__construct(); 77 parent::__construct();
76 $this->entries = new ArrayCollection(); 78 $this->entries = new ArrayCollection();
77 $this->tags = new ArrayCollection(); 79 $this->tags = new ArrayCollection();
80 $this->roles = array('ROLE_USER');
78 } 81 }
79 82
80 /** 83 /**
@@ -91,24 +94,6 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable
91 } 94 }
92 95
93 /** 96 /**
94 * Set password.
95 *
96 * @param string $password
97 *
98 * @return User
99 */
100 public function setPassword($password)
101 {
102 if (!$password && 0 === strlen($password)) {
103 return;
104 }
105
106 $this->password = sha1($password.$this->getUsername().$this->getSalt());
107
108 return $this;
109 }
110
111 /**
112 * Set name. 97 * Set name.
113 * 98 *
114 * @param string $name 99 * @param string $name
@@ -196,11 +181,11 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable
196 /** 181 /**
197 * Set config. 182 * Set config.
198 * 183 *
199 * @param \Wallabag\CoreBundle\Entity\Config $config 184 * @param Config $config
200 * 185 *
201 * @return User 186 * @return User
202 */ 187 */
203 public function setConfig(\Wallabag\CoreBundle\Entity\Config $config = null) 188 public function setConfig(Config $config = null)
204 { 189 {
205 $this->config = $config; 190 $this->config = $config;
206 191
@@ -210,7 +195,7 @@ class User extends BaseUser implements AdvancedUserInterface, \Serializable
210 /** 195 /**
211 * Get config. 196 * Get config.
212 * 197 *
213 * @return \Wallabag\CoreBundle\Entity\Config 198 * @return Config
214 */ 199 */
215 public function getConfig() 200 public function getConfig()
216 { 201 {
diff --git a/src/Wallabag/CoreBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php
index 968d0b49..c020f3ca 100644
--- a/src/Wallabag/CoreBundle/Repository/UserRepository.php
+++ b/src/Wallabag/UserBundle/Repository/UserRepository.php
@@ -1,6 +1,6 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\Repository; 3namespace Wallabag\UserBundle\Repository;
4 4
5use Doctrine\ORM\EntityRepository; 5use Doctrine\ORM\EntityRepository;
6 6
diff --git a/src/Wallabag/UserBundle/Resources/config/services.yml b/src/Wallabag/UserBundle/Resources/config/services.yml
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/config/services.yml
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register.html.twig
new file mode 100644
index 00000000..e5c1876b
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register.html.twig
@@ -0,0 +1,20 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{% trans %}create an account{% endtrans %}{% endblock %}
4
5{% block body_class %}login{% endblock %}
6
7{% block menu %}{% endblock %}
8{% block messages %}{% endblock %}
9
10{% block content %}
11 <form action="{{ path('fos_user_registration_register') }}" {{ form_enctype(form) }} method="POST" class="fos_user_registration_register">
12 <fieldset class="w500p center">
13 <h2 class="mbs txtcenter">{% trans %}create an account{% endtrans %}</h2>
14 {% include "FOSUserBundle:Registration:register_content.html.twig" %}
15 </fieldset>
16 </form>
17{% endblock %}
18
19{% block footer %}
20{% endblock %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register_content.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register_content.html.twig
new file mode 100644
index 00000000..41f94006
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Registration/register_content.html.twig
@@ -0,0 +1,37 @@
1{% trans_default_domain 'FOSUserBundle' %}
2
3{{ form_widget(form._token) }}
4
5{% for flashMessage in app.session.flashbag.get('notice') %}
6 <span><p>{{ flashMessage }}</p></span>
7{% endfor %}
8
9<div class="row">
10 {{ form_errors(form.email) }}
11 {{ form_label(form.email) }}
12 {{ form_widget(form.email) }}
13</div>
14
15<div class="row">
16 {{ form_errors(form.username) }}
17 {{ form_label(form.username) }}
18 {{ form_widget(form.username) }}
19</div>
20
21<div class="row">
22 {{ form_errors(form.plainPassword.first) }}
23 {{ form_label(form.plainPassword.first) }}
24 {{ form_widget(form.plainPassword.first) }}
25</div>
26
27<div class="row">
28 {{ form_errors(form.plainPassword.second) }}
29 {{ form_label(form.plainPassword.second) }}
30 {{ form_widget(form.plainPassword.second) }}
31</div>
32
33
34<div class="row mts txtcenter">
35 <button type="submit">{{ 'registration.submit'|trans({}, 'FOSUserBundle') }}</button>
36 <a href="{{ path('fos_user_security_login') }}" class="button">{% trans %}Login{% endtrans %}</a>
37</div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/checkEmail.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/checkEmail.html.twig
index 056d65b5..056d65b5 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/checkEmail.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/checkEmail.html.twig
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request.html.twig
new file mode 100644
index 00000000..10094e83
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request.html.twig
@@ -0,0 +1,20 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{% trans %}Forgot password{% endtrans %}{% endblock %}
4
5{% block body_class %}login{% endblock %}
6
7{% block menu %}{% endblock %}
8{% block messages %}{% endblock %}
9
10{% block content %}
11 <form action="{{ path('fos_user_resetting_send_email') }}" method="post" name="forgotPasswordform">
12 <fieldset class="w500p center">
13 <h2 class="mbs txtcenter">{% trans %}Forgot password{% endtrans %}</h2>
14 {% include "FOSUserBundle:Resetting:request_content.html.twig" %}
15 </fieldset>
16 </form>
17{% endblock %}
18
19{% block footer %}
20{% endblock %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request_content.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request_content.html.twig
new file mode 100644
index 00000000..1f8da8d7
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Resetting/request_content.html.twig
@@ -0,0 +1,17 @@
1{% trans_default_domain 'FOSUserBundle' %}
2
3{% trans %}Enter your email address below and we'll send you password reset instructions.{% endtrans %}
4
5{% if invalid_username is defined %}
6 <p>{{ 'resetting.request.invalid_username'|trans({'%username%': invalid_username}) }}</p>
7{% endif %}
8
9<div class="row">
10 <label for="username">{{ 'resetting.request.username'|trans }}</label>
11 <input type="text" id="username" name="username" required="required" />
12</div>
13
14<div class="row mts txtcenter">
15 <button type="submit">{{ 'resetting.request.submit'|trans }}</button>
16 <a href="{{ path('fos_user_security_login') }}" class="button">{% trans %}Login{% endtrans %}</a>
17</div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/login.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Security/login.html.twig
index 5437d20c..d52c3662 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Security/login.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/themes/baggy/Security/login.html.twig
@@ -1,14 +1,7 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %} 1{% extends "FOSUserBundle::layout.html.twig" %}
2 2
3{% block title %}{% trans %}login to your wallabag{% endtrans %}{% endblock %} 3{% block fos_user_content %}
4 4 <form action="{{ path('fos_user_security_check') }}" method="post" name="loginform">
5{% block body_class %}login{% endblock %}
6
7{% block menu %}{% endblock %}
8{% block messages %}{% endblock %}
9
10{% block content %}
11 <form action="{{ path('login_check') }}" method="post" name="loginform">
12 <fieldset class="w500p center"> 5 <fieldset class="w500p center">
13 <h2 class="mbs txtcenter">{% trans %}Login to wallabag{% endtrans %}</h2> 6 <h2 class="mbs txtcenter">{% trans %}Login to wallabag{% endtrans %}</h2>
14 {% if error %} 7 {% if error %}
@@ -32,7 +25,8 @@
32 <div class="row mts txtcenter"> 25 <div class="row mts txtcenter">
33 <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" /> 26 <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" />
34 <button type="submit">Login</button> 27 <button type="submit">Login</button>
35 <a href="{{ path('forgot_password') }}" class="small">Forgot your password?</a> 28 <a href="{{ path('fos_user_registration_register') }}" class="button">{% trans %}Register{% endtrans %}</a>
29 <a href="{{ path('fos_user_resetting_request') }}" class="small">Forgot your password?</a>
36 </div> 30 </div>
37 </fieldset> 31 </fieldset>
38 </form> 32 </form>
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/baggy/layout.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/baggy/layout.html.twig
new file mode 100644
index 00000000..ff5b6583
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/baggy/layout.html.twig
@@ -0,0 +1,16 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}Welcome on wallabag!{% endblock %}
4
5{% block body_class %}login{% endblock %}
6
7{% block menu %}{% endblock %}
8{% block messages %}{% endblock %}
9
10{% block content %}
11 {% block fos_user_content %}
12 {% endblock fos_user_content %}
13{% endblock %}
14
15{% block footer %}
16{% endblock %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/ChangePassword/changePassword_content.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/ChangePassword/changePassword_content.html.twig
new file mode 100644
index 00000000..e7b7318b
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/ChangePassword/changePassword_content.html.twig
@@ -0,0 +1,12 @@
1{% trans_default_domain 'FOSUserBundle' %}
2
3<form action="{{ path('fos_user_change_password') }}" {{ form_enctype(form) }} method="POST" class="fos_user_change_password">
4 <div class="card-content">
5 <div class="row">
6 {{ form_widget(form) }}
7 <div>
8 <input type="submit" value="{{ 'change_password.submit'|trans }}" />
9 </div>
10 </div>
11 </div>
12</form>
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/checkEmail.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/checkEmail.html.twig
new file mode 100644
index 00000000..50937276
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/checkEmail.html.twig
@@ -0,0 +1,11 @@
1{% extends "FOSUserBundle::layout.html.twig" %}
2
3{% trans_default_domain 'FOSUserBundle' %}
4
5{% block fos_user_content %}
6<div class="card-content">
7 <div class="row">
8 <p>{{ 'registration.check_email'|trans({'%email%': user.email}) }}</p>
9 </div>
10</div>
11{% endblock fos_user_content %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/confirmed.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/confirmed.html.twig
new file mode 100644
index 00000000..c6d4d3d2
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/confirmed.html.twig
@@ -0,0 +1,17 @@
1{% extends "FOSUserBundle::layout.html.twig" %}
2
3{% trans_default_domain 'FOSUserBundle' %}
4
5{% block fos_user_content %}
6<div class="card-content">
7 <div class="row">
8 <p>{{ 'registration.confirmed'|trans({'%username%': user.username}) }}</p>
9 {% if targetUrl %}
10 <p><a href="{{ targetUrl }}">{{ 'registration.back'|trans }}</a></p>
11 {% endif %}
12 </div>
13 <div class="card-action center">
14 <a href="{{ path('homepage') }}" class="waves-effect waves-light btn"><i class="material-icons left"></i> {% trans %}Go to your account{% endtrans %}</a>
15 </div>
16</div>
17{% endblock fos_user_content %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/register_content.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/register_content.html.twig
new file mode 100644
index 00000000..865a24ae
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Registration/register_content.html.twig
@@ -0,0 +1,45 @@
1{% trans_default_domain 'FOSUserBundle' %}
2
3<form action="{{ path('fos_user_registration_register') }}" {{ form_enctype(form) }} method="POST" class="fos_user_registration_register">
4 <div class="card-content">
5 <div class="row">
6
7 {{ form_widget(form._token) }}
8
9 {% for flashMessage in app.session.flashbag.get('notice') %}
10 <span class="black-text"><p>{{ flashMessage }}</p></span>
11 {% endfor %}
12
13 <div class="input-field col s12">
14 {{ form_errors(form.email) }}
15 {{ form_label(form.email) }}
16 {{ form_widget(form.email) }}
17 </div>
18
19 <div class="input-field col s12">
20 {{ form_errors(form.username) }}
21 {{ form_label(form.username) }}
22 {{ form_widget(form.username) }}
23 </div>
24
25 <div class="input-field col s12">
26 {{ form_errors(form.plainPassword.first) }}
27 {{ form_label(form.plainPassword.first) }}
28 {{ form_widget(form.plainPassword.first) }}
29 </div>
30
31 <div class="input-field col s12">
32 {{ form_errors(form.plainPassword.second) }}
33 {{ form_label(form.plainPassword.second) }}
34 {{ form_widget(form.plainPassword.second) }}
35 </div>
36 </div>
37 </div>
38 <div class="card-action center">
39 <a href="{{ path('fos_user_security_login') }}" class="waves-effect waves-light grey btn"><i class="material-icons left"></i> {% trans %}Login{% endtrans %}</a>
40 <button class="btn waves-effect waves-light" type="submit" name="send">
41 {{ 'registration.submit'|trans({}, 'FOSUserBundle') }}
42 <i class="mdi-content-send right"></i>
43 </button>
44 </div>
45</form>
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/checkEmail.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/checkEmail.html.twig
new file mode 100644
index 00000000..66cbdc28
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/checkEmail.html.twig
@@ -0,0 +1,11 @@
1{% extends "FOSUserBundle::layout.html.twig" %}
2
3{% trans_default_domain 'FOSUserBundle' %}
4
5{% block fos_user_content %}
6<div class="card-content">
7 <div class="row">
8 {{ 'resetting.check_email'|trans({'%email%': email}) }}
9 </div>
10</div>
11{% endblock fos_user_content %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/passwordAlreadyRequested.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/passwordAlreadyRequested.html.twig
new file mode 100644
index 00000000..0eec4301
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/passwordAlreadyRequested.html.twig
@@ -0,0 +1,11 @@
1{% extends "FOSUserBundle::layout.html.twig" %}
2
3{% trans_default_domain 'FOSUserBundle' %}
4
5{% block fos_user_content %}
6<div class="card-content">
7 <div class="row">
8 {{ 'resetting.password_already_requested'|trans }}
9 </div>
10</div>
11{% endblock fos_user_content %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/request_content.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/request_content.html.twig
new file mode 100644
index 00000000..e871d7be
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/request_content.html.twig
@@ -0,0 +1,26 @@
1{% trans_default_domain 'FOSUserBundle' %}
2<form action="{{ path('fos_user_resetting_send_email') }}" method="POST" class="fos_user_resetting_request">
3 <div class="card-content">
4 <div class="row">
5 <p>{% trans %}Enter your email address below and we'll send you password reset instructions.{% endtrans %}</p>
6 {% for flashMessage in app.session.flashbag.get('notice') %}
7 <span class="black-text"><p>{{ flashMessage }}</p></span>
8 {% endfor %}
9
10 {% if invalid_username is defined %}
11 <p>{{ 'resetting.request.invalid_username'|trans({'%username%': invalid_username}) }}</p>
12 {% endif %}
13
14 <div class="input-field col s12">
15 <label for="username">{{ 'resetting.request.username'|trans }}</label>
16 <input type="text" id="username" name="username" required="required" />
17 </div>
18 </div>
19 </div>
20 <div class="card-action center">
21 <a href="{{ path('fos_user_security_login') }}" class="waves-effect waves-light grey btn"><i class="material-icons left"></i> {% trans %}Login{% endtrans %}</a>
22 <button class="btn waves-effect waves-light" type="submit" name="send">
23 {{ 'resetting.request.submit'|trans }}
24 </button>
25 </div>
26</form>
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/reset_content.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/reset_content.html.twig
new file mode 100644
index 00000000..f7e061dd
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Resetting/reset_content.html.twig
@@ -0,0 +1,15 @@
1{% trans_default_domain 'FOSUserBundle' %}
2
3<form action="{{ path('fos_user_resetting_reset', {'token': token}) }}" {{ form_enctype(form) }} method="POST" class="fos_user_resetting_reset">
4 <div class="card-content">
5 <div class="row">
6 {{ form_widget(form) }}
7 </div>
8 <div class="card-action center">
9 <button class="btn waves-effect waves-light" type="submit" name="send">
10 {{ 'resetting.reset.submit'|trans }}
11 <i class="mdi-content-send right"></i>
12 </button>
13 </div>
14 </div>
15</form>
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/Security/login.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/Security/login.html.twig
new file mode 100644
index 00000000..6bf99bf8
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/Security/login.html.twig
@@ -0,0 +1,46 @@
1{% extends "FOSUserBundle::layout.html.twig" %}
2
3{% block fos_user_content %}
4<form action="{{ path('fos_user_security_check') }}" method="post" name="loginform">
5 <div class="card-content">
6
7 {% if error %}
8 <span class="black-text">{{ error.message }}</span>
9 {% endif %}
10
11 {% for flashMessage in app.session.flashbag.get('notice') %}
12 <span class="black-text"><p>{{ flashMessage }}</p></span>
13 {% endfor %}
14
15 <div class="row">
16
17 <div class="input-field col s12">
18 <label for="username">{% trans %}Username{% endtrans %}</label>
19 <input type="text" id="username" name="_username" value="{{ last_username }}" />
20 </div>
21
22 <div class="input-field col s12">
23 <label for="password">{% trans %}Password{% endtrans %}</label>
24 <input type="password" id="password" name="_password" />
25 </div>
26
27 <div class="input-field col s12">
28 <input type="checkbox" id="remember_me" name="_remember_me" checked />
29 <label for="remember_me">{% trans %}Keep me logged in{% endtrans %}</label>
30 </div>
31
32 </div>
33 </div>
34 <div class="card-action center">
35 <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" />
36 <a href="{{ path('fos_user_registration_register') }}" class="waves-effect waves-light grey btn"><i class="material-icons left"></i> {% trans %}Register{% endtrans %}</a>
37 <button class="btn waves-effect waves-light" type="submit" name="send">
38 {% trans %}Login{% endtrans %}
39 <i class="mdi-content-send right"></i>
40 </button>
41 </div>
42 <div class="center">
43 <a href="{{ path('fos_user_resetting_request') }}">{% trans %}Forgot your password?{% endtrans %}</a>
44 </div>
45</form>
46{% endblock fos_user_content %}
diff --git a/src/Wallabag/UserBundle/Resources/views/themes/material/layout.html.twig b/src/Wallabag/UserBundle/Resources/views/themes/material/layout.html.twig
new file mode 100644
index 00000000..a69e68c2
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/views/themes/material/layout.html.twig
@@ -0,0 +1,23 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}Welcome on wallabag!{% endblock %}
4
5{% block body_class %}login{% endblock %}
6
7{% block menu %}{% endblock %}
8{% block messages %}{% endblock %}
9
10{% block content %}
11<main class="valign-wrapper">
12 <div class="valign row">
13 <div class="card sw">
14 <div class="center"><img src="{{ asset('themes/material/img/logo-other_themes.png') }}" alt="wallabag logo" /></div>
15 {% block fos_user_content %}
16 {% endblock fos_user_content %}
17 </div>
18 </div>
19</main>
20{% endblock %}
21
22{% block footer %}
23{% endblock %}
diff --git a/src/Wallabag/UserBundle/WallabagUserBundle.php b/src/Wallabag/UserBundle/WallabagUserBundle.php
new file mode 100644
index 00000000..d9180b3b
--- /dev/null
+++ b/src/Wallabag/UserBundle/WallabagUserBundle.php
@@ -0,0 +1,13 @@
1<?php
2
3namespace Wallabag\UserBundle;
4
5use Symfony\Component\HttpKernel\Bundle\Bundle;
6
7class WallabagUserBundle extends Bundle
8{
9 public function getParent()
10 {
11 return 'FOSUserBundle';
12 }
13}