aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--app/AppKernel.php1
-rw-r--r--app/build.xml7
-rw-r--r--app/config/security.yml62
-rw-r--r--app/config/services.yml3
-rw-r--r--composer.json1
-rw-r--r--composer.lock140
-rw-r--r--src/Wallabag/CoreBundle/Controller/EntryController.php21
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php35
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php34
-rw-r--r--src/Wallabag/CoreBundle/Entity/Entry.php41
-rw-r--r--src/Wallabag/CoreBundle/Entity/User.php35
-rw-r--r--src/Wallabag/CoreBundle/Repository/EntryRepository.php63
-rw-r--r--src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php88
-rw-r--r--src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php89
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php38
-rw-r--r--src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php40
-rw-r--r--src/Wallabag/CoreBundle/Tests/WallabagTestCase.php34
17 files changed, 620 insertions, 112 deletions
diff --git a/app/AppKernel.php b/app/AppKernel.php
index 515c79ec..3dfefd58 100644
--- a/app/AppKernel.php
+++ b/app/AppKernel.php
@@ -28,6 +28,7 @@ class AppKernel extends Kernel
28 $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 28 $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
29 $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); 29 $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
30 $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); 30 $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
31 $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
31 } 32 }
32 33
33 return $bundles; 34 return $bundles;
diff --git a/app/build.xml b/app/build.xml
index 6427867c..a8e43f0d 100644
--- a/app/build.xml
+++ b/app/build.xml
@@ -34,5 +34,12 @@
34 <arg value="cache:clear"/> 34 <arg value="cache:clear"/>
35 <arg value="--env=test"/> 35 <arg value="--env=test"/>
36 </exec> 36 </exec>
37 <exec executable="php">
38 <arg value="${basedir}/../app/console"/>
39 <arg value="doctrine:fixtures:load"/>
40 <arg value="--no-interaction"/>
41 <arg value="--purge-with-truncate"/>
42 <arg value="--env=test"/>
43 </exec>
37 </target> 44 </target>
38</project> 45</project>
diff --git a/app/config/security.yml b/app/config/security.yml
index c1b0fb77..e161c3b5 100644
--- a/app/config/security.yml
+++ b/app/config/security.yml
@@ -1,6 +1,6 @@
1security: 1security:
2 encoders: 2 encoders:
3 Wallabag\CoreBundle\Entity\Users: 3 Wallabag\CoreBundle\Entity\User:
4 algorithm: sha1 4 algorithm: sha1
5 encode_as_base64: false 5 encode_as_base64: false
6 iterations: 1 6 iterations: 1
@@ -11,7 +11,7 @@ security:
11 11
12 providers: 12 providers:
13 administrators: 13 administrators:
14 entity: { class: WallabagCoreBundle:Users, property: username } 14 entity: { class: WallabagCoreBundle:User, property: username }
15 15
16 # the main part of the security, where you can set up firewalls 16 # the main part of the security, where you can set up firewalls
17 # for specific sections of your app 17 # for specific sections of your app
@@ -23,35 +23,35 @@ security:
23 pattern: ^/login$ 23 pattern: ^/login$
24 anonymous: ~ 24 anonymous: ~
25 25
26# secured_area: 26 secured_area:
27# pattern: ^/ 27 pattern: ^/
28# anonymous: ~ 28 anonymous: ~
29# form_login: 29 form_login:
30# login_path: /login 30 login_path: /login
31# 31
32# use_forward: false 32 use_forward: false
33# 33
34# check_path: /login_check 34 check_path: /login_check
35# 35
36# post_only: true 36 post_only: true
37# 37
38# always_use_default_target_path: true 38 always_use_default_target_path: true
39# default_target_path: / 39 default_target_path: /
40# target_path_parameter: redirect_url 40 target_path_parameter: redirect_url
41# use_referer: true 41 use_referer: true
42# 42
43# failure_path: null 43 failure_path: null
44# failure_forward: false 44 failure_forward: false
45# 45
46# username_parameter: _username 46 username_parameter: _username
47# password_parameter: _password 47 password_parameter: _password
48# 48
49# csrf_parameter: _csrf_token 49 csrf_parameter: _csrf_token
50# intention: authenticate 50 intention: authenticate
51# 51
52# logout: 52 logout:
53# path: /logout 53 path: /logout
54# target: / 54 target: /
55 55
56 access_control: 56 access_control:
57 - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY } 57 - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
diff --git a/app/config/services.yml b/app/config/services.yml
index 5c76fc59..d4485e42 100644
--- a/app/config/services.yml
+++ b/app/config/services.yml
@@ -1,7 +1,8 @@
1# Learn more about services, parameters and containers at 1# Learn more about services, parameters and containers at
2# http://symfony.com/doc/current/book/service_container.html 2# http://symfony.com/doc/current/book/service_container.html
3parameters: 3parameters:
4# parameter_name: value 4 security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider
5 security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder
5 6
6services: 7services:
7# service_name: 8# service_name:
diff --git a/composer.json b/composer.json
index 8771ea90..0136ed2e 100644
--- a/composer.json
+++ b/composer.json
@@ -83,6 +83,7 @@
83 "wallabag/Fivefilters_Libraries": "dev-master" 83 "wallabag/Fivefilters_Libraries": "dev-master"
84 }, 84 },
85 "require-dev": { 85 "require-dev": {
86 "doctrine/doctrine-fixtures-bundle": "dev-master",
86 "sensio/generator-bundle": "~2.5", 87 "sensio/generator-bundle": "~2.5",
87 "phpunit/phpunit": "~4.4" 88 "phpunit/phpunit": "~4.4"
88 }, 89 },
diff --git a/composer.lock b/composer.lock
index dded6dd7..a7890ac2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
4 "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 4 "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 "This file is @generated automatically" 5 "This file is @generated automatically"
6 ], 6 ],
7 "hash": "5005a650aa4368bd3485939efd9e24ac", 7 "hash": "16c2143e6e1977d625f5557f07dfc118",
8 "packages": [ 8 "packages": [
9 { 9 {
10 "name": "doctrine/annotations", 10 "name": "doctrine/annotations",
@@ -1666,21 +1666,20 @@
1666 }, 1666 },
1667 { 1667 {
1668 "name": "robmorgan/phinx", 1668 "name": "robmorgan/phinx",
1669 "version": "v0.4.1", 1669 "version": "v0.4.2.1",
1670 "source": { 1670 "source": {
1671 "type": "git", 1671 "type": "git",
1672 "url": "https://github.com/robmorgan/phinx.git", 1672 "url": "https://github.com/robmorgan/phinx.git",
1673 "reference": "357210707c000f50edea802d84b74724ad122478" 1673 "reference": "1bc1396392d4073b8b29ee5289e445889cbc12b5"
1674 }, 1674 },
1675 "dist": { 1675 "dist": {
1676 "type": "zip", 1676 "type": "zip",
1677 "url": "https://api.github.com/repos/robmorgan/phinx/zipball/357210707c000f50edea802d84b74724ad122478", 1677 "url": "https://api.github.com/repos/robmorgan/phinx/zipball/1bc1396392d4073b8b29ee5289e445889cbc12b5",
1678 "reference": "357210707c000f50edea802d84b74724ad122478", 1678 "reference": "1bc1396392d4073b8b29ee5289e445889cbc12b5",
1679 "shasum": "" 1679 "shasum": ""
1680 }, 1680 },
1681 "require": { 1681 "require": {
1682 "php": ">=5.3.2", 1682 "php": ">=5.3.2",
1683 "symfony/class-loader": "~2.6.0",
1684 "symfony/config": "~2.6.0", 1683 "symfony/config": "~2.6.0",
1685 "symfony/console": "~2.6.0", 1684 "symfony/console": "~2.6.0",
1686 "symfony/yaml": "~2.6.0" 1685 "symfony/yaml": "~2.6.0"
@@ -1694,8 +1693,8 @@
1694 ], 1693 ],
1695 "type": "library", 1694 "type": "library",
1696 "autoload": { 1695 "autoload": {
1697 "psr-0": { 1696 "psr-4": {
1698 "Phinx": "src/" 1697 "Phinx\\": "src/Phinx"
1699 } 1698 }
1700 }, 1699 },
1701 "notification-url": "https://packagist.org/downloads/", 1700 "notification-url": "https://packagist.org/downloads/",
@@ -1708,6 +1707,12 @@
1708 "email": "robbym@gmail.com", 1707 "email": "robbym@gmail.com",
1709 "homepage": "http://robmorgan.id.au", 1708 "homepage": "http://robmorgan.id.au",
1710 "role": "Lead Developer" 1709 "role": "Lead Developer"
1710 },
1711 {
1712 "name": "Woody Gilk",
1713 "email": "woody.gilk@gmail.com",
1714 "homepage": "http://shadowhand.me",
1715 "role": "Developer"
1711 } 1716 }
1712 ], 1717 ],
1713 "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", 1718 "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
@@ -1719,7 +1724,7 @@
1719 "migrations", 1724 "migrations",
1720 "phinx" 1725 "phinx"
1721 ], 1726 ],
1722 "time": "2014-12-23 06:06:14" 1727 "time": "2015-02-08 03:41:44"
1723 }, 1728 },
1724 { 1729 {
1725 "name": "sensio/distribution-bundle", 1730 "name": "sensio/distribution-bundle",
@@ -2872,6 +2877,120 @@
2872 ], 2877 ],
2873 "packages-dev": [ 2878 "packages-dev": [
2874 { 2879 {
2880 "name": "doctrine/data-fixtures",
2881 "version": "v1.0.0",
2882 "source": {
2883 "type": "git",
2884 "url": "https://github.com/doctrine/data-fixtures.git",
2885 "reference": "b4a135c7db56ecc4602b54a2184368f440cac33e"
2886 },
2887 "dist": {
2888 "type": "zip",
2889 "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/b4a135c7db56ecc4602b54a2184368f440cac33e",
2890 "reference": "b4a135c7db56ecc4602b54a2184368f440cac33e",
2891 "shasum": ""
2892 },
2893 "require": {
2894 "doctrine/common": ">=2.2,<2.5-dev",
2895 "php": ">=5.3.2"
2896 },
2897 "require-dev": {
2898 "doctrine/orm": ">=2.2,<2.5-dev"
2899 },
2900 "suggest": {
2901 "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures",
2902 "doctrine/orm": "For loading ORM fixtures",
2903 "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures"
2904 },
2905 "type": "library",
2906 "extra": {
2907 "branch-alias": {
2908 "dev-master": "1.0.x-dev"
2909 }
2910 },
2911 "autoload": {
2912 "psr-0": {
2913 "Doctrine\\Common\\DataFixtures": "lib/"
2914 }
2915 },
2916 "notification-url": "https://packagist.org/downloads/",
2917 "license": [
2918 "MIT"
2919 ],
2920 "authors": [
2921 {
2922 "name": "Jonathan Wage",
2923 "email": "jonwage@gmail.com",
2924 "homepage": "http://www.jwage.com/",
2925 "role": "Creator"
2926 }
2927 ],
2928 "description": "Data Fixtures for all Doctrine Object Managers",
2929 "homepage": "http://www.doctrine-project.org",
2930 "keywords": [
2931 "database"
2932 ],
2933 "time": "2013-07-10 17:04:07"
2934 },
2935 {
2936 "name": "doctrine/doctrine-fixtures-bundle",
2937 "version": "dev-master",
2938 "target-dir": "Doctrine/Bundle/FixturesBundle",
2939 "source": {
2940 "type": "git",
2941 "url": "https://github.com/doctrine/DoctrineFixturesBundle.git",
2942 "reference": "c5ff0542772102ddd4e2fbe173e9ad40ad67c22f"
2943 },
2944 "dist": {
2945 "type": "zip",
2946 "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/c5ff0542772102ddd4e2fbe173e9ad40ad67c22f",
2947 "reference": "c5ff0542772102ddd4e2fbe173e9ad40ad67c22f",
2948 "shasum": ""
2949 },
2950 "require": {
2951 "doctrine/data-fixtures": "~1.0",
2952 "doctrine/doctrine-bundle": "~1.0",
2953 "php": ">=5.3.2",
2954 "symfony/doctrine-bridge": "~2.1"
2955 },
2956 "type": "symfony-bundle",
2957 "extra": {
2958 "branch-alias": {
2959 "dev-master": "2.2.x-dev"
2960 }
2961 },
2962 "autoload": {
2963 "psr-0": {
2964 "Doctrine\\Bundle\\FixturesBundle": ""
2965 }
2966 },
2967 "notification-url": "https://packagist.org/downloads/",
2968 "license": [
2969 "MIT"
2970 ],
2971 "authors": [
2972 {
2973 "name": "Symfony Community",
2974 "homepage": "http://symfony.com/contributors"
2975 },
2976 {
2977 "name": "Doctrine Project",
2978 "homepage": "http://www.doctrine-project.org"
2979 },
2980 {
2981 "name": "Fabien Potencier",
2982 "email": "fabien@symfony.com"
2983 }
2984 ],
2985 "description": "Symfony DoctrineFixturesBundle",
2986 "homepage": "http://www.doctrine-project.org",
2987 "keywords": [
2988 "Fixture",
2989 "persistence"
2990 ],
2991 "time": "2015-01-19 02:21:37"
2992 },
2993 {
2875 "name": "doctrine/instantiator", 2994 "name": "doctrine/instantiator",
2876 "version": "1.0.4", 2995 "version": "1.0.4",
2877 "source": { 2996 "source": {
@@ -3834,7 +3953,8 @@
3834 "wallabag/phpepub": 20, 3953 "wallabag/phpepub": 20,
3835 "wallabag/php-readability": 20, 3954 "wallabag/php-readability": 20,
3836 "wallabag/phpmobi": 20, 3955 "wallabag/phpmobi": 20,
3837 "wallabag/fivefilters_libraries": 20 3956 "wallabag/fivefilters_libraries": 20,
3957 "doctrine/doctrine-fixtures-bundle": 20
3838 }, 3958 },
3839 "prefer-stable": true, 3959 "prefer-stable": true,
3840 "prefer-lowest": false, 3960 "prefer-lowest": false,
diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php
index 6326d31f..e0697ca3 100644
--- a/src/Wallabag/CoreBundle/Controller/EntryController.php
+++ b/src/Wallabag/CoreBundle/Controller/EntryController.php
@@ -19,8 +19,7 @@ class EntryController extends Controller
19 */ 19 */
20 public function addEntryAction(Request $request) 20 public function addEntryAction(Request $request)
21 { 21 {
22 $entry = new Entry(); 22 $entry = new Entry($this->getUser());
23 $entry->setUserId(1);
24 23
25 $form = $this->createFormBuilder($entry) 24 $form = $this->createFormBuilder($entry)
26 ->add('url', 'url') 25 ->add('url', 'url')
@@ -60,10 +59,10 @@ class EntryController extends Controller
60 */ 59 */
61 public function showUnreadAction() 60 public function showUnreadAction()
62 { 61 {
63 $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
64 // TODO don't give the user ID like this
65 // TODO change pagination 62 // TODO change pagination
66 $entries = $repository->findUnreadByUser(1, 0); 63 $entries = $this->getDoctrine()
64 ->getRepository('WallabagCoreBundle:Entry')
65 ->findUnreadByUser($this->getUser()->getId(), 0);
67 66
68 return $this->render( 67 return $this->render(
69 'WallabagCoreBundle:Entry:entries.html.twig', 68 'WallabagCoreBundle:Entry:entries.html.twig',
@@ -79,10 +78,10 @@ class EntryController extends Controller
79 */ 78 */
80 public function showArchiveAction() 79 public function showArchiveAction()
81 { 80 {
82 $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
83 // TODO don't give the user ID like this
84 // TODO change pagination 81 // TODO change pagination
85 $entries = $repository->findArchiveByUser(1, 0); 82 $entries = $this->getDoctrine()
83 ->getRepository('WallabagCoreBundle:Entry')
84 ->findArchiveByUser($this->getUser()->getId(), 0);
86 85
87 return $this->render( 86 return $this->render(
88 'WallabagCoreBundle:Entry:entries.html.twig', 87 'WallabagCoreBundle:Entry:entries.html.twig',
@@ -98,10 +97,10 @@ class EntryController extends Controller
98 */ 97 */
99 public function showStarredAction() 98 public function showStarredAction()
100 { 99 {
101 $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
102 // TODO don't give the user ID like this
103 // TODO change pagination 100 // TODO change pagination
104 $entries = $repository->findStarredByUser(1, 0); 101 $entries = $this->getDoctrine()
102 ->getRepository('WallabagCoreBundle:Entry')
103 ->findStarredByUser($this->getUser()->getId(), 0);
105 104
106 return $this->render( 105 return $this->render(
107 'WallabagCoreBundle:Entry:entries.html.twig', 106 'WallabagCoreBundle:Entry:entries.html.twig',
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
new file mode 100644
index 00000000..fccd06be
--- /dev/null
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
@@ -0,0 +1,35 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures\ORM;
4
5use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\Entry;
9
10class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
11{
12 /**
13 * {@inheritDoc}
14 */
15 public function load(ObjectManager $manager)
16 {
17 $entry1 = new Entry($this->getReference('admin-user'));
18 $entry1->setUrl('http://0.0.0.0');
19 $entry1->setTitle('test title');
20 $entry1->setContent('This is my content /o/');
21
22 $manager->persist($entry1);
23 $manager->flush();
24
25 $this->addReference('entry1', $entry1);
26 }
27
28 /**
29 * {@inheritDoc}
30 */
31 public function getOrder()
32 {
33 return 20;
34 }
35}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php
new file mode 100644
index 00000000..da788218
--- /dev/null
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php
@@ -0,0 +1,34 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures\ORM;
4
5use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\User;
9
10class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
11{
12 /**
13 * {@inheritDoc}
14 */
15 public function load(ObjectManager $manager)
16 {
17 $userAdmin = new User();
18 $userAdmin->setUsername('admin');
19 $userAdmin->setPassword('test');
20
21 $manager->persist($userAdmin);
22 $manager->flush();
23
24 $this->addReference('admin-user', $userAdmin);
25 }
26
27 /**
28 * {@inheritDoc}
29 */
30 public function getOrder()
31 {
32 return 10;
33 }
34}
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php
index a00762ca..937213b4 100644
--- a/src/Wallabag/CoreBundle/Entity/Entry.php
+++ b/src/Wallabag/CoreBundle/Entity/Entry.php
@@ -84,13 +84,6 @@ class Entry
84 /** 84 /**
85 * @var string 85 * @var string
86 * 86 *
87 * @ORM\Column(name="user_id", type="decimal", precision=10, scale=0, nullable=true)
88 */
89 private $userId;
90
91 /**
92 * @var string
93 *
94 * @ORM\Column(name="comments", type="text", nullable=true) 87 * @ORM\Column(name="comments", type="text", nullable=true)
95 */ 88 */
96 private $comments; 89 private $comments;
@@ -124,6 +117,19 @@ class Entry
124 private $isPublic; 117 private $isPublic;
125 118
126 /** 119 /**
120 * @ORM\ManyToOne(targetEntity="User", inversedBy="entries")
121 */
122 private $user;
123
124 /*
125 * @param User $user
126 */
127 public function __construct(User $user)
128 {
129 $this->user = $user;
130 }
131
132 /**
127 * Get id 133 * Get id
128 * 134 *
129 * @return integer 135 * @return integer
@@ -263,26 +269,11 @@ class Entry
263 } 269 }
264 270
265 /** 271 /**
266 * Set userId 272 * @return User
267 *
268 * @param string $userId
269 * @return Entry
270 */
271 public function setUserId($userId)
272 {
273 $this->userId = $userId;
274
275 return $this;
276 }
277
278 /**
279 * Get userId
280 *
281 * @return string
282 */ 273 */
283 public function getUserId() 274 public function getUser()
284 { 275 {
285 return $this->userId; 276 return $this->user;
286 } 277 }
287 278
288 /** 279 /**
diff --git a/src/Wallabag/CoreBundle/Entity/User.php b/src/Wallabag/CoreBundle/Entity/User.php
index 6abfd3ae..c83250c3 100644
--- a/src/Wallabag/CoreBundle/Entity/User.php
+++ b/src/Wallabag/CoreBundle/Entity/User.php
@@ -2,6 +2,7 @@
2 2
3namespace Wallabag\CoreBundle\Entity; 3namespace Wallabag\CoreBundle\Entity;
4 4
5use Doctrine\Common\Collections\ArrayCollection;
5use Doctrine\ORM\Mapping as ORM; 6use Doctrine\ORM\Mapping as ORM;
6use Symfony\Component\Security\Core\User\UserInterface; 7use Symfony\Component\Security\Core\User\UserInterface;
7use Symfony\Component\Security\Core\User\AdvancedUserInterface; 8use Symfony\Component\Security\Core\User\AdvancedUserInterface;
@@ -78,10 +79,16 @@ class User implements AdvancedUserInterface, \Serializable
78 */ 79 */
79 private $updatedAt; 80 private $updatedAt;
80 81
82 /**
83 * @ORM\OneToMany(targetEntity="Entry", mappedBy="user", cascade={"remove"})
84 */
85 private $entries;
86
81 public function __construct() 87 public function __construct()
82 { 88 {
83 $this->isActive = true; 89 $this->isActive = true;
84 $this->salt = md5(uniqid(null, true)); 90 $this->salt = md5(uniqid(null, true));
91 $this->entries = new ArrayCollection();
85 } 92 }
86 93
87 /** 94 /**
@@ -154,7 +161,11 @@ class User implements AdvancedUserInterface, \Serializable
154 */ 161 */
155 public function setPassword($password) 162 public function setPassword($password)
156 { 163 {
157 $this->password = $password; 164 if (!$password && 0 === strlen($password)) {
165 return;
166 }
167
168 $this->password = sha1($password.$this->getUsername().$this->getSalt());
158 169
159 return $this; 170 return $this;
160 } 171 }
@@ -232,6 +243,26 @@ class User implements AdvancedUserInterface, \Serializable
232 } 243 }
233 244
234 /** 245 /**
246 * @param Entry $entry
247 *
248 * @return User
249 */
250 public function addEntry(Entry $entry)
251 {
252 $this->entries[] = $entry;
253
254 return $this;
255 }
256
257 /**
258 * @return ArrayCollection<Entry>
259 */
260 public function getEntries()
261 {
262 return $this->entries;
263 }
264
265 /**
235 * @inheritDoc 266 * @inheritDoc
236 */ 267 */
237 public function eraseCredentials() 268 public function eraseCredentials()
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index f4c803f9..5ae1337a 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -11,19 +11,20 @@ class EntryRepository extends EntityRepository
11 /** 11 /**
12 * Retrieves unread entries for a user 12 * Retrieves unread entries for a user
13 * 13 *
14 * @param $userId 14 * @param int $userId
15 * @param $firstResult 15 * @param int $firstResult
16 * @param int $maxResults 16 * @param int $maxResults
17 *
17 * @return Paginator 18 * @return Paginator
18 */ 19 */
19 public function findUnreadByUser($userId, $firstResult, $maxResults = 12) 20 public function findUnreadByUser($userId, $firstResult, $maxResults = 12)
20 { 21 {
21 $qb = $this->createQueryBuilder('e') 22 $qb = $this->createQueryBuilder('e')
22 ->select('e')
23 ->setFirstResult($firstResult) 23 ->setFirstResult($firstResult)
24 ->setMaxResults($maxResults) 24 ->setMaxResults($maxResults)
25 ->leftJoin('e.user', 'u')
25 ->where('e.isArchived = false') 26 ->where('e.isArchived = false')
26 ->andWhere('e.userId =:userId')->setParameter('userId', $userId) 27 ->andWhere('u.id =:userId')->setParameter('userId', $userId)
27 ->andWhere('e.isDeleted=false') 28 ->andWhere('e.isDeleted=false')
28 ->orderBy('e.createdAt', 'desc') 29 ->orderBy('e.createdAt', 'desc')
29 ->getQuery(); 30 ->getQuery();
@@ -36,9 +37,10 @@ class EntryRepository extends EntityRepository
36 /** 37 /**
37 * Retrieves read entries for a user 38 * Retrieves read entries for a user
38 * 39 *
39 * @param $userId 40 * @param int $userId
40 * @param $firstResult 41 * @param int $firstResult
41 * @param int $maxResults 42 * @param int $maxResults
43 *
42 * @return Paginator 44 * @return Paginator
43 */ 45 */
44 public function findArchiveByUser($userId, $firstResult, $maxResults = 12) 46 public function findArchiveByUser($userId, $firstResult, $maxResults = 12)
@@ -47,8 +49,9 @@ class EntryRepository extends EntityRepository
47 ->select('e') 49 ->select('e')
48 ->setFirstResult($firstResult) 50 ->setFirstResult($firstResult)
49 ->setMaxResults($maxResults) 51 ->setMaxResults($maxResults)
52 ->leftJoin('e.user', 'u')
50 ->where('e.isArchived = true') 53 ->where('e.isArchived = true')
51 ->andWhere('e.userId =:userId')->setParameter('userId', $userId) 54 ->andWhere('u.id =:userId')->setParameter('userId', $userId)
52 ->andWhere('e.isDeleted=false') 55 ->andWhere('e.isDeleted=false')
53 ->orderBy('e.createdAt', 'desc') 56 ->orderBy('e.createdAt', 'desc')
54 ->getQuery(); 57 ->getQuery();
@@ -61,9 +64,10 @@ class EntryRepository extends EntityRepository
61 /** 64 /**
62 * Retrieves starred entries for a user 65 * Retrieves starred entries for a user
63 * 66 *
64 * @param $userId 67 * @param int $userId
65 * @param $firstResult 68 * @param int $firstResult
66 * @param int $maxResults 69 * @param int $maxResults
70 *
67 * @return Paginator 71 * @return Paginator
68 */ 72 */
69 public function findStarredByUser($userId, $firstResult, $maxResults = 12) 73 public function findStarredByUser($userId, $firstResult, $maxResults = 12)
@@ -72,9 +76,10 @@ class EntryRepository extends EntityRepository
72 ->select('e') 76 ->select('e')
73 ->setFirstResult($firstResult) 77 ->setFirstResult($firstResult)
74 ->setMaxResults($maxResults) 78 ->setMaxResults($maxResults)
79 ->leftJoin('e.user', 'u')
75 ->where('e.isStarred = true') 80 ->where('e.isStarred = true')
76 ->andWhere('e.userId =:userId')->setParameter('userId', $userId) 81 ->andWhere('u.id =:userId')->setParameter('userId', $userId)
77 ->andWhere('e.isDeleted=false') 82 ->andWhere('e.isDeleted = false')
78 ->orderBy('e.createdAt', 'desc') 83 ->orderBy('e.createdAt', 'desc')
79 ->getQuery(); 84 ->getQuery();
80 85
@@ -83,22 +88,34 @@ class EntryRepository extends EntityRepository
83 return $paginator; 88 return $paginator;
84 } 89 }
85 90
86 public function findEntries($userId, $isArchived, $isStarred, $isDeleted, $sort, $order) 91 /**
92 * Find Entries
93 *
94 * @param int $userId
95 * @param bool $isArchived
96 * @param bool $isStarred
97 * @param bool $isDeleted
98 * @param string $sort
99 * @param string $order
100 *
101 * @return ArrayCollection
102 */
103 public function findEntries($userId, $isArchived = null, $isStarred = null, $isDeleted = null, $sort = 'created', $order = 'ASC')
87 { 104 {
88 $qb = $this->createQueryBuilder('e') 105 $qb = $this->createQueryBuilder('e')
89 ->select('e') 106 ->leftJoin('e.user', 'u')
90 ->where('e.userId =:userId')->setParameter('userId', $userId); 107 ->where('u.id =:userId')->setParameter('userId', $userId);
91 108
92 if (!is_null($isArchived)) { 109 if (null !== $isArchived) {
93 $qb->andWhere('e.isArchived =:isArchived')->setParameter('isArchived', $isArchived); 110 $qb->andWhere('e.isArchived =:isArchived')->setParameter('isArchived', (bool) $isArchived);
94 } 111 }
95 112
96 if (!is_null($isStarred)) { 113 if (null !== $isStarred) {
97 $qb->andWhere('e.isStarred =:isStarred')->setParameter('isStarred', $isStarred); 114 $qb->andWhere('e.isStarred =:isStarred')->setParameter('isStarred', (bool) $isStarred);
98 } 115 }
99 116
100 if (!is_null($isDeleted)) { 117 if (null !== $isDeleted) {
101 $qb->andWhere('e.isDeleted =:isDeleted')->setParameter('isDeleted', $isDeleted); 118 $qb->andWhere('e.isDeleted =:isDeleted')->setParameter('isDeleted', (bool) $isDeleted);
102 } 119 }
103 120
104 if ('created' === $sort) { 121 if ('created' === $sort) {
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php b/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php
new file mode 100644
index 00000000..56f1affe
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php
@@ -0,0 +1,88 @@
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 *
12 */
13class WallabagPasswordEncoder extends BasePasswordEncoder
14{
15 private $algorithm;
16 private $encodeHashAsBase64;
17 private $iterations;
18 private $username = null;
19
20 /**
21 * Constructor.
22 *
23 * @param string $algorithm The digest algorithm to use
24 * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash
25 * @param int $iterations The number of iterations to use to stretch the password hash
26 */
27 public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 5000)
28 {
29 $this->algorithm = $algorithm;
30 $this->encodeHashAsBase64 = $encodeHashAsBase64;
31 $this->iterations = $iterations;
32 }
33
34 public function setUsername($username)
35 {
36 $this->username = $username;
37 }
38
39 /**
40 * {@inheritdoc}
41 */
42 public function encodePassword($raw, $salt)
43 {
44 if (null === $this->username) {
45 throw new \LogicException('We can not check the password without a username.');
46 }
47
48 if ($this->isPasswordTooLong($raw)) {
49 throw new BadCredentialsException('Invalid password.');
50 }
51
52 if (!in_array($this->algorithm, hash_algos(), true)) {
53 throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
54 }
55
56 $salted = $this->mergePasswordAndSalt($raw, $salt);
57 $digest = hash($this->algorithm, $salted, true);
58
59 // "stretch" hash
60 for ($i = 1; $i < $this->iterations; $i++) {
61 $digest = hash($this->algorithm, $digest.$salted, true);
62 }
63
64 return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
65 }
66
67 /**
68 * {@inheritdoc}
69 *
70 * We inject the username inside the salted password
71 */
72 protected function mergePasswordAndSalt($password, $salt)
73 {
74 if (empty($salt)) {
75 return $password;
76 }
77
78 return $password.$this->username.$salt;
79 }
80
81 /**
82 * {@inheritdoc}
83 */
84 public function isPasswordValid($encoded, $raw, $salt)
85 {
86 return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
87 }
88}
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php
new file mode 100644
index 00000000..1c7c5fae
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php
@@ -0,0 +1,89 @@
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/Tests/Controller/EntryControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
index fde210c9..5d8daea3 100644
--- a/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
+++ b/src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
@@ -2,13 +2,24 @@
2 2
3namespace Wallabag\CoreBundle\Tests\Controller; 3namespace Wallabag\CoreBundle\Tests\Controller;
4 4
5use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 5use Wallabag\CoreBundle\Tests\WallabagTestCase;
6 6
7class EntryControllerTest extends WebTestCase 7class EntryControllerTest extends WallabagTestCase
8{ 8{
9 public function testLogin()
10 {
11 $client = $this->getClient();
12
13 $crawler = $client->request('GET', '/new');
14
15 $this->assertEquals(302, $client->getResponse()->getStatusCode());
16 $this->assertContains('login', $client->getResponse()->headers->get('location'));
17 }
18
9 public function testGetNew() 19 public function testGetNew()
10 { 20 {
11 $client = static::createClient(); 21 $this->logIn();
22 $client = $this->getClient();
12 23
13 $crawler = $client->request('GET', '/new'); 24 $crawler = $client->request('GET', '/new');
14 25
@@ -20,7 +31,8 @@ class EntryControllerTest extends WebTestCase
20 31
21 public function testPostNewEmpty() 32 public function testPostNewEmpty()
22 { 33 {
23 $client = static::createClient(); 34 $this->logIn();
35 $client = $this->getClient();
24 36
25 $crawler = $client->request('GET', '/new'); 37 $crawler = $client->request('GET', '/new');
26 38
@@ -37,7 +49,8 @@ class EntryControllerTest extends WebTestCase
37 49
38 public function testPostNewOk() 50 public function testPostNewOk()
39 { 51 {
40 $client = static::createClient(); 52 $this->logIn();
53 $client = $this->getClient();
41 54
42 $crawler = $client->request('GET', '/new'); 55 $crawler = $client->request('GET', '/new');
43 56
@@ -55,13 +68,14 @@ class EntryControllerTest extends WebTestCase
55 68
56 $crawler = $client->followRedirect(); 69 $crawler = $client->followRedirect();
57 70
58 $this->assertCount(1, $alert = $crawler->filter('h2 a')->extract(array('_text'))); 71 $this->assertGreaterThan(1, $alert = $crawler->filter('h2 a')->extract(array('_text')));
59 $this->assertContains('Mailjet', $alert[0]); 72 $this->assertContains('Mailjet', $alert[0]);
60 } 73 }
61 74
62 public function testArchive() 75 public function testArchive()
63 { 76 {
64 $client = static::createClient(); 77 $this->logIn();
78 $client = $this->getClient();
65 79
66 $crawler = $client->request('GET', '/archive'); 80 $crawler = $client->request('GET', '/archive');
67 81
@@ -70,7 +84,8 @@ class EntryControllerTest extends WebTestCase
70 84
71 public function testStarred() 85 public function testStarred()
72 { 86 {
73 $client = static::createClient(); 87 $this->logIn();
88 $client = $this->getClient();
74 89
75 $crawler = $client->request('GET', '/starred'); 90 $crawler = $client->request('GET', '/starred');
76 91
@@ -79,13 +94,18 @@ class EntryControllerTest extends WebTestCase
79 94
80 public function testView() 95 public function testView()
81 { 96 {
82 $client = static::createClient(); 97 $this->logIn();
98 $client = $this->getClient();
83 99
84 $content = $client->getContainer() 100 $content = $client->getContainer()
85 ->get('doctrine.orm.entity_manager') 101 ->get('doctrine.orm.entity_manager')
86 ->getRepository('WallabagCoreBundle:Entry') 102 ->getRepository('WallabagCoreBundle:Entry')
87 ->findOneByIsArchived(false); 103 ->findOneByIsArchived(false);
88 104
105 if (!$content) {
106 $this->markTestSkipped('No content found in db.');
107 }
108
89 $crawler = $client->request('GET', '/view/'.$content->getId()); 109 $crawler = $client->request('GET', '/view/'.$content->getId());
90 110
91 $this->assertEquals(200, $client->getResponse()->getStatusCode()); 111 $this->assertEquals(200, $client->getResponse()->getStatusCode());
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php
new file mode 100644
index 00000000..54cf5073
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php
@@ -0,0 +1,40 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tests\Controller;
4
5use Wallabag\CoreBundle\Tests\WallabagTestCase;
6
7class SecurityControllerTest extends WallabagTestCase
8{
9 public function testLogin()
10 {
11 $client = $this->getClient();
12
13 $crawler = $client->request('GET', '/new');
14
15 $this->assertEquals(302, $client->getResponse()->getStatusCode());
16 $this->assertContains('login', $client->getResponse()->headers->get('location'));
17 }
18
19 public function testLoginFail()
20 {
21 $client = $this->getClient();
22
23 $crawler = $client->request('GET', '/login');
24
25 $form = $crawler->filter('button[type=submit]')->form();
26 $data = array(
27 '_username' => 'admin',
28 '_password' => 'admin',
29 );
30
31 $client->submit($form, $data);
32
33 $this->assertEquals(302, $client->getResponse()->getStatusCode());
34 $this->assertContains('login', $client->getResponse()->headers->get('location'));
35
36 $crawler = $client->followRedirect();
37
38 $this->assertContains('Bad credentials', $client->getResponse()->getContent());
39 }
40}
diff --git a/src/Wallabag/CoreBundle/Tests/WallabagTestCase.php b/src/Wallabag/CoreBundle/Tests/WallabagTestCase.php
new file mode 100644
index 00000000..5f092318
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Tests/WallabagTestCase.php
@@ -0,0 +1,34 @@
1<?php
2
3namespace Wallabag\CoreBundle\Tests;
4
5use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
6use Symfony\Component\BrowserKit\Cookie;
7use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
8
9class WallabagTestCase extends WebTestCase
10{
11 private $client = null;
12
13 public function getClient()
14 {
15 return $this->client;
16 }
17
18 public function setUp()
19 {
20 $this->client = static::createClient();
21 }
22
23 public function logIn()
24 {
25 $crawler = $this->client->request('GET', '/login');
26 $form = $crawler->filter('button[type=submit]')->form();
27 $data = array(
28 '_username' => 'admin',
29 '_password' => 'test',
30 );
31
32 $this->client->submit($form, $data);
33 }
34}