]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
Merge pull request #1062 from wallabag/v2-relation-entry-user
authorNicolas Lœuillet <nicolas@loeuillet.org>
Mon, 9 Feb 2015 11:52:06 +0000 (12:52 +0100)
committerNicolas Lœuillet <nicolas@loeuillet.org>
Mon, 9 Feb 2015 11:52:06 +0000 (12:52 +0100)
add a real relation between user and entry

17 files changed:
app/AppKernel.php
app/build.xml
app/config/security.yml
app/config/services.yml
composer.json
composer.lock
src/Wallabag/CoreBundle/Controller/EntryController.php
src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php [new file with mode: 0644]
src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Entity/Entry.php
src/Wallabag/CoreBundle/Entity/User.php
src/Wallabag/CoreBundle/Repository/EntryRepository.php
src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Tests/Controller/EntryControllerTest.php
src/Wallabag/CoreBundle/Tests/Controller/SecurityControllerTest.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Tests/WallabagTestCase.php [new file with mode: 0644]

index 515c79ecb26ba797323037bf8a2009dc66290571..3dfefd584a62c06f2d72d79a65d4b345173eee4b 100644 (file)
@@ -28,6 +28,7 @@ class AppKernel extends Kernel
             $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
             $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
             $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
+            $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
         }
 
         return $bundles;
index 6427867c4ff7f31a2964a26f3914041398c8782a..a8e43f0d8c74329498ae65413af29cdd30859291 100644 (file)
             <arg value="cache:clear"/>
             <arg value="--env=test"/>
         </exec>
+        <exec executable="php">
+            <arg value="${basedir}/../app/console"/>
+            <arg value="doctrine:fixtures:load"/>
+            <arg value="--no-interaction"/>
+            <arg value="--purge-with-truncate"/>
+            <arg value="--env=test"/>
+        </exec>
     </target>
 </project>
index c1b0fb7782df748a907b5209fc885cb96a733574..e161c3b53b911f70f77443f51c30609fa52263ff 100644 (file)
@@ -1,6 +1,6 @@
 security:
     encoders:
-        Wallabag\CoreBundle\Entity\Users:
+        Wallabag\CoreBundle\Entity\User:
             algorithm:        sha1
             encode_as_base64: false
             iterations:       1
@@ -11,7 +11,7 @@ security:
 
     providers:
         administrators:
-            entity: { class: WallabagCoreBundle:Users, property: username }
+            entity: { class: WallabagCoreBundle:User, property: username }
 
     # the main part of the security, where you can set up firewalls
     # for specific sections of your app
@@ -23,35 +23,35 @@ security:
             pattern:    ^/login$
             anonymous:  ~
 
-#        secured_area:
-#            pattern:    ^/
-#            anonymous: ~
-#            form_login:
-#                login_path:                     /login
-#
-#                use_forward:                    false
-#
-#                check_path:                     /login_check
-#
-#                post_only:                      true
-#
-#                always_use_default_target_path: true
-#                default_target_path:            /
-#                target_path_parameter:          redirect_url
-#                use_referer:                    true
-#
-#                failure_path:                   null
-#                failure_forward:                false
-#
-#                username_parameter:             _username
-#                password_parameter:             _password
-#
-#                csrf_parameter:                 _csrf_token
-#                intention:                      authenticate
-#
-#            logout:
-#                path:   /logout
-#                target: /
+        secured_area:
+            pattern:    ^/
+            anonymous: ~
+            form_login:
+                login_path:                     /login
+
+                use_forward:                    false
+
+                check_path:                     /login_check
+
+                post_only:                      true
+
+                always_use_default_target_path: true
+                default_target_path:            /
+                target_path_parameter:          redirect_url
+                use_referer:                    true
+
+                failure_path:                   null
+                failure_forward:                false
+
+                username_parameter:             _username
+                password_parameter:             _password
+
+                csrf_parameter:                 _csrf_token
+                intention:                      authenticate
+
+            logout:
+                path:   /logout
+                target: /
 
     access_control:
         - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
index 5c76fc5988534367e579dfddfb4a611e377c03f9..d4485e4290b20c672afdae4e53477ade0ea67508 100644 (file)
@@ -1,7 +1,8 @@
 # Learn more about services, parameters and containers at
 # http://symfony.com/doc/current/book/service_container.html
 parameters:
-#    parameter_name: value
+    security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider
+    security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder
 
 services:
 #    service_name:
index 8771ea90b69f8ac843361ac6d6ba58aa1ea0ae8c..0136ed2e4dbf90edb3b19838c11dfa30245901a6 100644 (file)
@@ -83,6 +83,7 @@
         "wallabag/Fivefilters_Libraries": "dev-master"
     },
     "require-dev": {
+        "doctrine/doctrine-fixtures-bundle": "dev-master",
         "sensio/generator-bundle": "~2.5",
         "phpunit/phpunit": "~4.4"
     },
index dded6dd76c56cd7fdcfa5b2bf36d51fd201fda2d..a7890ac2dfba080805de6680bbee31784f3188f7 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "5005a650aa4368bd3485939efd9e24ac",
+    "hash": "16c2143e6e1977d625f5557f07dfc118",
     "packages": [
         {
             "name": "doctrine/annotations",
         },
         {
             "name": "robmorgan/phinx",
-            "version": "v0.4.1",
+            "version": "v0.4.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/robmorgan/phinx.git",
-                "reference": "357210707c000f50edea802d84b74724ad122478"
+                "reference": "1bc1396392d4073b8b29ee5289e445889cbc12b5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/robmorgan/phinx/zipball/357210707c000f50edea802d84b74724ad122478",
-                "reference": "357210707c000f50edea802d84b74724ad122478",
+                "url": "https://api.github.com/repos/robmorgan/phinx/zipball/1bc1396392d4073b8b29ee5289e445889cbc12b5",
+                "reference": "1bc1396392d4073b8b29ee5289e445889cbc12b5",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.2",
-                "symfony/class-loader": "~2.6.0",
                 "symfony/config": "~2.6.0",
                 "symfony/console": "~2.6.0",
                 "symfony/yaml": "~2.6.0"
             ],
             "type": "library",
             "autoload": {
-                "psr-0": {
-                    "Phinx": "src/"
+                "psr-4": {
+                    "Phinx\\": "src/Phinx"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "email": "robbym@gmail.com",
                     "homepage": "http://robmorgan.id.au",
                     "role": "Lead Developer"
+                },
+                {
+                    "name": "Woody Gilk",
+                    "email": "woody.gilk@gmail.com",
+                    "homepage": "http://shadowhand.me",
+                    "role": "Developer"
                 }
             ],
             "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
                 "migrations",
                 "phinx"
             ],
-            "time": "2014-12-23 06:06:14"
+            "time": "2015-02-08 03:41:44"
         },
         {
             "name": "sensio/distribution-bundle",
         }
     ],
     "packages-dev": [
+        {
+            "name": "doctrine/data-fixtures",
+            "version": "v1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/data-fixtures.git",
+                "reference": "b4a135c7db56ecc4602b54a2184368f440cac33e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/b4a135c7db56ecc4602b54a2184368f440cac33e",
+                "reference": "b4a135c7db56ecc4602b54a2184368f440cac33e",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/common": ">=2.2,<2.5-dev",
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "doctrine/orm": ">=2.2,<2.5-dev"
+            },
+            "suggest": {
+                "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures",
+                "doctrine/orm": "For loading ORM fixtures",
+                "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Doctrine\\Common\\DataFixtures": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jonathan Wage",
+                    "email": "jonwage@gmail.com",
+                    "homepage": "http://www.jwage.com/",
+                    "role": "Creator"
+                }
+            ],
+            "description": "Data Fixtures for all Doctrine Object Managers",
+            "homepage": "http://www.doctrine-project.org",
+            "keywords": [
+                "database"
+            ],
+            "time": "2013-07-10 17:04:07"
+        },
+        {
+            "name": "doctrine/doctrine-fixtures-bundle",
+            "version": "dev-master",
+            "target-dir": "Doctrine/Bundle/FixturesBundle",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/DoctrineFixturesBundle.git",
+                "reference": "c5ff0542772102ddd4e2fbe173e9ad40ad67c22f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/c5ff0542772102ddd4e2fbe173e9ad40ad67c22f",
+                "reference": "c5ff0542772102ddd4e2fbe173e9ad40ad67c22f",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/data-fixtures": "~1.0",
+                "doctrine/doctrine-bundle": "~1.0",
+                "php": ">=5.3.2",
+                "symfony/doctrine-bridge": "~2.1"
+            },
+            "type": "symfony-bundle",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Doctrine\\Bundle\\FixturesBundle": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                },
+                {
+                    "name": "Doctrine Project",
+                    "homepage": "http://www.doctrine-project.org"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Symfony DoctrineFixturesBundle",
+            "homepage": "http://www.doctrine-project.org",
+            "keywords": [
+                "Fixture",
+                "persistence"
+            ],
+            "time": "2015-01-19 02:21:37"
+        },
         {
             "name": "doctrine/instantiator",
             "version": "1.0.4",
         "wallabag/phpepub": 20,
         "wallabag/php-readability": 20,
         "wallabag/phpmobi": 20,
-        "wallabag/fivefilters_libraries": 20
+        "wallabag/fivefilters_libraries": 20,
+        "doctrine/doctrine-fixtures-bundle": 20
     },
     "prefer-stable": true,
     "prefer-lowest": false,
index 6326d31fc5b2f4d2073cf71430e320b1b5e55716..e0697ca3ef3852af631eef32eb91813b8b7acdab 100644 (file)
@@ -19,8 +19,7 @@ class EntryController extends Controller
      */
     public function addEntryAction(Request $request)
     {
-        $entry = new Entry();
-        $entry->setUserId(1);
+        $entry = new Entry($this->getUser());
 
         $form = $this->createFormBuilder($entry)
             ->add('url', 'url')
@@ -60,10 +59,10 @@ class EntryController extends Controller
      */
     public function showUnreadAction()
     {
-        $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
-        // TODO don't give the user ID like this
         // TODO change pagination
-        $entries = $repository->findUnreadByUser(1, 0);
+        $entries = $this->getDoctrine()
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->findUnreadByUser($this->getUser()->getId(), 0);
 
         return $this->render(
             'WallabagCoreBundle:Entry:entries.html.twig',
@@ -79,10 +78,10 @@ class EntryController extends Controller
      */
     public function showArchiveAction()
     {
-        $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
-        // TODO don't give the user ID like this
         // TODO change pagination
-        $entries = $repository->findArchiveByUser(1, 0);
+        $entries = $this->getDoctrine()
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->findArchiveByUser($this->getUser()->getId(), 0);
 
         return $this->render(
             'WallabagCoreBundle:Entry:entries.html.twig',
@@ -98,10 +97,10 @@ class EntryController extends Controller
      */
     public function showStarredAction()
     {
-        $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
-        // TODO don't give the user ID like this
         // TODO change pagination
-        $entries = $repository->findStarredByUser(1, 0);
+        $entries = $this->getDoctrine()
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->findStarredByUser($this->getUser()->getId(), 0);
 
         return $this->render(
             '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 (file)
index 0000000..fccd06b
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Wallabag\CoreBundle\DataFixtures\ORM;
+
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Common\Persistence\ObjectManager;
+use Wallabag\CoreBundle\Entity\Entry;
+
+class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function load(ObjectManager $manager)
+    {
+        $entry1 = new Entry($this->getReference('admin-user'));
+        $entry1->setUrl('http://0.0.0.0');
+        $entry1->setTitle('test title');
+        $entry1->setContent('This is my content /o/');
+
+        $manager->persist($entry1);
+        $manager->flush();
+
+        $this->addReference('entry1', $entry1);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getOrder()
+    {
+        return 20;
+    }
+}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadUserData.php
new file mode 100644 (file)
index 0000000..da78821
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Wallabag\CoreBundle\DataFixtures\ORM;
+
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Common\Persistence\ObjectManager;
+use Wallabag\CoreBundle\Entity\User;
+
+class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function load(ObjectManager $manager)
+    {
+        $userAdmin = new User();
+        $userAdmin->setUsername('admin');
+        $userAdmin->setPassword('test');
+
+        $manager->persist($userAdmin);
+        $manager->flush();
+
+        $this->addReference('admin-user', $userAdmin);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getOrder()
+    {
+        return 10;
+    }
+}
index a00762cadfbc1a58a4250f09b1e803529fbd71af..937213b4477b85f294bf143529eaf38d032672e0 100644 (file)
@@ -81,13 +81,6 @@ class Entry
      */
     private $updatedAt;
 
-    /**
-     * @var string
-     *
-     * @ORM\Column(name="user_id", type="decimal", precision=10, scale=0, nullable=true)
-     */
-    private $userId;
-
     /**
      * @var string
      *
@@ -123,6 +116,19 @@ class Entry
      */
     private $isPublic;
 
+    /**
+     * @ORM\ManyToOne(targetEntity="User", inversedBy="entries")
+     */
+    private $user;
+
+    /*
+     * @param User     $user
+     */
+    public function __construct(User $user)
+    {
+        $this->user = $user;
+    }
+
     /**
      * Get id
      *
@@ -263,26 +269,11 @@ class Entry
     }
 
     /**
-     * Set userId
-     *
-     * @param  string $userId
-     * @return Entry
-     */
-    public function setUserId($userId)
-    {
-        $this->userId = $userId;
-
-        return $this;
-    }
-
-    /**
-     * Get userId
-     *
-     * @return string
+     * @return User
      */
-    public function getUserId()
+    public function getUser()
     {
-        return $this->userId;
+        return $this->user;
     }
 
     /**
index 6abfd3ae46374f2080c87d6ebcb25dea14b5c458..c83250c37e6d2a2bec25530c1a92667bc72904d1 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace Wallabag\CoreBundle\Entity;
 
+use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
 use Symfony\Component\Security\Core\User\UserInterface;
 use Symfony\Component\Security\Core\User\AdvancedUserInterface;
@@ -78,10 +79,16 @@ class User implements AdvancedUserInterface, \Serializable
      */
     private $updatedAt;
 
+    /**
+     * @ORM\OneToMany(targetEntity="Entry", mappedBy="user", cascade={"remove"})
+     */
+    private $entries;
+
     public function __construct()
     {
         $this->isActive = true;
-        $this->salt = md5(uniqid(null, true));
+        $this->salt     = md5(uniqid(null, true));
+        $this->entries  = new ArrayCollection();
     }
 
     /**
@@ -154,7 +161,11 @@ class User implements AdvancedUserInterface, \Serializable
      */
     public function setPassword($password)
     {
-        $this->password = $password;
+        if (!$password && 0 === strlen($password)) {
+            return;
+        }
+
+        $this->password = sha1($password.$this->getUsername().$this->getSalt());
 
         return $this;
     }
@@ -231,6 +242,26 @@ class User implements AdvancedUserInterface, \Serializable
         return $this->updatedAt;
     }
 
+    /**
+     * @param Entry $entry
+     *
+     * @return User
+     */
+    public function addEntry(Entry $entry)
+    {
+        $this->entries[] = $entry;
+
+        return $this;
+    }
+
+    /**
+     * @return ArrayCollection<Entry>
+     */
+    public function getEntries()
+    {
+        return $this->entries;
+    }
+
     /**
      * @inheritDoc
      */
index f4c803f991c2e38fb41d510aa5f44ff3e197e1ae..5ae1337a8ca44faecc9891fc2c6265e0bf7e6f09 100644 (file)
@@ -11,19 +11,20 @@ class EntryRepository extends EntityRepository
     /**
      * Retrieves unread entries for a user
      *
-     * @param $userId
-     * @param $firstResult
-     * @param  int       $maxResults
+     * @param int $userId
+     * @param int $firstResult
+     * @param int $maxResults
+     *
      * @return Paginator
      */
     public function findUnreadByUser($userId, $firstResult, $maxResults = 12)
     {
         $qb = $this->createQueryBuilder('e')
-            ->select('e')
             ->setFirstResult($firstResult)
             ->setMaxResults($maxResults)
+            ->leftJoin('e.user', 'u')
             ->where('e.isArchived = false')
-            ->andWhere('e.userId =:userId')->setParameter('userId', $userId)
+            ->andWhere('u.id =:userId')->setParameter('userId', $userId)
             ->andWhere('e.isDeleted=false')
             ->orderBy('e.createdAt', 'desc')
             ->getQuery();
@@ -36,9 +37,10 @@ class EntryRepository extends EntityRepository
     /**
      * Retrieves read entries for a user
      *
-     * @param $userId
-     * @param $firstResult
-     * @param  int       $maxResults
+     * @param int $userId
+     * @param int $firstResult
+     * @param int $maxResults
+     *
      * @return Paginator
      */
     public function findArchiveByUser($userId, $firstResult, $maxResults = 12)
@@ -47,8 +49,9 @@ class EntryRepository extends EntityRepository
             ->select('e')
             ->setFirstResult($firstResult)
             ->setMaxResults($maxResults)
+            ->leftJoin('e.user', 'u')
             ->where('e.isArchived = true')
-            ->andWhere('e.userId =:userId')->setParameter('userId', $userId)
+            ->andWhere('u.id =:userId')->setParameter('userId', $userId)
             ->andWhere('e.isDeleted=false')
             ->orderBy('e.createdAt', 'desc')
             ->getQuery();
@@ -61,9 +64,10 @@ class EntryRepository extends EntityRepository
     /**
      * Retrieves starred entries for a user
      *
-     * @param $userId
-     * @param $firstResult
-     * @param  int       $maxResults
+     * @param int $userId
+     * @param int $firstResult
+     * @param int $maxResults
+     *
      * @return Paginator
      */
     public function findStarredByUser($userId, $firstResult, $maxResults = 12)
@@ -72,9 +76,10 @@ class EntryRepository extends EntityRepository
             ->select('e')
             ->setFirstResult($firstResult)
             ->setMaxResults($maxResults)
+            ->leftJoin('e.user', 'u')
             ->where('e.isStarred = true')
-            ->andWhere('e.userId =:userId')->setParameter('userId', $userId)
-            ->andWhere('e.isDeleted=false')
+            ->andWhere('u.id =:userId')->setParameter('userId', $userId)
+            ->andWhere('e.isDeleted = false')
             ->orderBy('e.createdAt', 'desc')
             ->getQuery();
 
@@ -83,22 +88,34 @@ class EntryRepository extends EntityRepository
         return $paginator;
     }
 
-    public function findEntries($userId, $isArchived, $isStarred, $isDeleted, $sort, $order)
+    /**
+     * Find Entries
+     *
+     * @param  int    $userId
+     * @param  bool   $isArchived
+     * @param  bool   $isStarred
+     * @param  bool   $isDeleted
+     * @param  string $sort
+     * @param  string $order
+     *
+     * @return ArrayCollection
+     */
+    public function findEntries($userId, $isArchived = null, $isStarred = null, $isDeleted = null, $sort = 'created', $order = 'ASC')
     {
         $qb = $this->createQueryBuilder('e')
-            ->select('e')
-            ->where('e.userId =:userId')->setParameter('userId', $userId);
+            ->leftJoin('e.user', 'u')
+            ->where('u.id =:userId')->setParameter('userId', $userId);
 
-        if (!is_null($isArchived)) {
-            $qb->andWhere('e.isArchived =:isArchived')->setParameter('isArchived', $isArchived);
+        if (null !== $isArchived) {
+            $qb->andWhere('e.isArchived =:isArchived')->setParameter('isArchived', (bool) $isArchived);
         }
 
-        if (!is_null($isStarred)) {
-            $qb->andWhere('e.isStarred =:isStarred')->setParameter('isStarred', $isStarred);
+        if (null !== $isStarred) {
+            $qb->andWhere('e.isStarred =:isStarred')->setParameter('isStarred', (bool) $isStarred);
         }
 
-        if (!is_null($isDeleted)) {
-            $qb->andWhere('e.isDeleted =:isDeleted')->setParameter('isDeleted', $isDeleted);
+        if (null !== $isDeleted) {
+            $qb->andWhere('e.isDeleted =:isDeleted')->setParameter('isDeleted', (bool) $isDeleted);
         }
 
         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 (file)
index 0000000..56f1aff
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+namespace Wallabag\CoreBundle\Security\Authentication\Encoder;
+
+use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+
+/**
+ * This override just add en extra variable (username) to be able to salt the password
+ * the way Wallabag v1 does. It will avoid to break compatibility with Wallabag v1
+ *
+ */
+class WallabagPasswordEncoder extends BasePasswordEncoder
+{
+    private $algorithm;
+    private $encodeHashAsBase64;
+    private $iterations;
+    private $username = null;
+
+    /**
+     * Constructor.
+     *
+     * @param string $algorithm          The digest algorithm to use
+     * @param bool   $encodeHashAsBase64 Whether to base64 encode the password hash
+     * @param int    $iterations         The number of iterations to use to stretch the password hash
+     */
+    public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 5000)
+    {
+        $this->algorithm = $algorithm;
+        $this->encodeHashAsBase64 = $encodeHashAsBase64;
+        $this->iterations = $iterations;
+    }
+
+    public function setUsername($username)
+    {
+        $this->username = $username;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function encodePassword($raw, $salt)
+    {
+        if (null === $this->username) {
+            throw new \LogicException('We can not check the password without a username.');
+        }
+
+        if ($this->isPasswordTooLong($raw)) {
+            throw new BadCredentialsException('Invalid password.');
+        }
+
+        if (!in_array($this->algorithm, hash_algos(), true)) {
+            throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
+        }
+
+        $salted = $this->mergePasswordAndSalt($raw, $salt);
+        $digest = hash($this->algorithm, $salted, true);
+
+        // "stretch" hash
+        for ($i = 1; $i < $this->iterations; $i++) {
+            $digest = hash($this->algorithm, $digest.$salted, true);
+        }
+
+        return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * We inject the username inside the salted password
+     */
+    protected function mergePasswordAndSalt($password, $salt)
+    {
+        if (empty($salt)) {
+            return $password;
+        }
+
+        return $password.$this->username.$salt;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isPasswordValid($encoded, $raw, $salt)
+    {
+        return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
+    }
+}
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..1c7c5fa
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace Wallabag\CoreBundle\Security\Authentication\Provider;
+
+use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
+use Symfony\Component\Security\Core\User\UserProviderInterface;
+use Symfony\Component\Security\Core\User\UserCheckerInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
+use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider;
+
+class WallabagAuthenticationProvider extends UserAuthenticationProvider
+{
+    private $encoderFactory;
+    private $userProvider;
+
+    /**
+     * Constructor.
+     *
+     * @param UserProviderInterface   $userProvider               An UserProviderInterface instance
+     * @param UserCheckerInterface    $userChecker                An UserCheckerInterface instance
+     * @param string                  $providerKey                The provider key
+     * @param EncoderFactoryInterface $encoderFactory             An EncoderFactoryInterface instance
+     * @param bool                    $hideUserNotFoundExceptions Whether to hide user not found exception or not
+     */
+    public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true)
+    {
+        parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
+
+        $this->encoderFactory = $encoderFactory;
+        $this->userProvider = $userProvider;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
+    {
+        $currentUser = $token->getUser();
+        if ($currentUser instanceof UserInterface) {
+            if ($currentUser->getPassword() !== $user->getPassword()) {
+                throw new BadCredentialsException('The credentials were changed from another session.');
+            }
+        } else {
+            if ("" === ($presentedPassword = $token->getCredentials())) {
+                throw new BadCredentialsException('The presented password cannot be empty.');
+            }
+
+            // give username, it's used to hash the password
+            $encoder = $this->encoderFactory->getEncoder($user);
+            $encoder->setUsername($user->getUsername());
+
+            if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
+                throw new BadCredentialsException('The presented password is invalid.');
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function retrieveUser($username, UsernamePasswordToken $token)
+    {
+        $user = $token->getUser();
+        if ($user instanceof UserInterface) {
+            return $user;
+        }
+
+        try {
+            $user = $this->userProvider->loadUserByUsername($username);
+
+            if (!$user instanceof UserInterface) {
+                throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
+            }
+
+            return $user;
+        } catch (UsernameNotFoundException $notFound) {
+            $notFound->setUsername($username);
+            throw $notFound;
+        } catch (\Exception $repositoryProblem) {
+            $ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem);
+            $ex->setToken($token);
+            throw $ex;
+        }
+    }
+}
index fde210c99d4ceec2cf70a5dcab0fed339c8dd206..5d8daea39342b48cc0e12975765a1cc9800b4bf6 100644 (file)
@@ -2,13 +2,24 @@
 
 namespace Wallabag\CoreBundle\Tests\Controller;
 
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+use Wallabag\CoreBundle\Tests\WallabagTestCase;
 
-class EntryControllerTest extends WebTestCase
+class EntryControllerTest extends WallabagTestCase
 {
+    public function testLogin()
+    {
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/new');
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+        $this->assertContains('login', $client->getResponse()->headers->get('location'));
+    }
+
     public function testGetNew()
     {
-        $client = static::createClient();
+        $this->logIn();
+        $client = $this->getClient();
 
         $crawler = $client->request('GET', '/new');
 
@@ -20,7 +31,8 @@ class EntryControllerTest extends WebTestCase
 
     public function testPostNewEmpty()
     {
-        $client = static::createClient();
+        $this->logIn();
+        $client = $this->getClient();
 
         $crawler = $client->request('GET', '/new');
 
@@ -37,7 +49,8 @@ class EntryControllerTest extends WebTestCase
 
     public function testPostNewOk()
     {
-        $client = static::createClient();
+        $this->logIn();
+        $client = $this->getClient();
 
         $crawler = $client->request('GET', '/new');
 
@@ -55,13 +68,14 @@ class EntryControllerTest extends WebTestCase
 
         $crawler = $client->followRedirect();
 
-        $this->assertCount(1, $alert = $crawler->filter('h2 a')->extract(array('_text')));
+        $this->assertGreaterThan(1, $alert = $crawler->filter('h2 a')->extract(array('_text')));
         $this->assertContains('Mailjet', $alert[0]);
     }
 
     public function testArchive()
     {
-        $client = static::createClient();
+        $this->logIn();
+        $client = $this->getClient();
 
         $crawler = $client->request('GET', '/archive');
 
@@ -70,7 +84,8 @@ class EntryControllerTest extends WebTestCase
 
     public function testStarred()
     {
-        $client = static::createClient();
+        $this->logIn();
+        $client = $this->getClient();
 
         $crawler = $client->request('GET', '/starred');
 
@@ -79,13 +94,18 @@ class EntryControllerTest extends WebTestCase
 
     public function testView()
     {
-        $client = static::createClient();
+        $this->logIn();
+        $client = $this->getClient();
 
         $content = $client->getContainer()
             ->get('doctrine.orm.entity_manager')
             ->getRepository('WallabagCoreBundle:Entry')
             ->findOneByIsArchived(false);
 
+        if (!$content) {
+            $this->markTestSkipped('No content found in db.');
+        }
+
         $crawler = $client->request('GET', '/view/'.$content->getId());
 
         $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 (file)
index 0000000..54cf507
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Wallabag\CoreBundle\Tests\Controller;
+
+use Wallabag\CoreBundle\Tests\WallabagTestCase;
+
+class SecurityControllerTest extends WallabagTestCase
+{
+    public function testLogin()
+    {
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/new');
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+        $this->assertContains('login', $client->getResponse()->headers->get('location'));
+    }
+
+    public function testLoginFail()
+    {
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/login');
+
+        $form = $crawler->filter('button[type=submit]')->form();
+        $data = array(
+            '_username' => 'admin',
+            '_password' => 'admin',
+        );
+
+        $client->submit($form, $data);
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+        $this->assertContains('login', $client->getResponse()->headers->get('location'));
+
+        $crawler = $client->followRedirect();
+
+        $this->assertContains('Bad credentials', $client->getResponse()->getContent());
+    }
+}
diff --git a/src/Wallabag/CoreBundle/Tests/WallabagTestCase.php b/src/Wallabag/CoreBundle/Tests/WallabagTestCase.php
new file mode 100644 (file)
index 0000000..5f09231
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Wallabag\CoreBundle\Tests;
+
+use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+use Symfony\Component\BrowserKit\Cookie;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+
+class WallabagTestCase extends WebTestCase
+{
+    private $client = null;
+
+    public function getClient()
+    {
+        return $this->client;
+    }
+
+    public function setUp()
+    {
+        $this->client = static::createClient();
+    }
+
+    public function logIn()
+    {
+        $crawler = $this->client->request('GET', '/login');
+        $form = $crawler->filter('button[type=submit]')->form();
+        $data = array(
+            '_username' => 'admin',
+            '_password' => 'test',
+        );
+
+        $this->client->submit($form, $data);
+    }
+}