aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJérémy Benoist <j0k3r@users.noreply.github.com>2019-01-03 09:14:26 +0100
committerGitHub <noreply@github.com>2019-01-03 09:14:26 +0100
commit2378fd6347dd1a824c8e1f4f7c3892c6eccddc85 (patch)
treecd039dc92f7a7a0dde5c6ca7484b8a9eefc359ca
parent4d0c632c70ea50d459c3c55ddda2e0f394dd51cb (diff)
parent6c40d7fc85b98e335adf765d1c6b4465647da62c (diff)
downloadwallabag-2378fd6347dd1a824c8e1f4f7c3892c6eccddc85.tar.gz
wallabag-2378fd6347dd1a824c8e1f4f7c3892c6eccddc85.tar.zst
wallabag-2378fd6347dd1a824c8e1f4f7c3892c6eccddc85.zip
Merge pull request #3823 from wallabag/fix-tag-api-leak
Fix tag API leak
-rw-r--r--src/Wallabag/ApiBundle/Controller/TagRestController.php22
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php197
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php43
-rw-r--r--src/Wallabag/CoreBundle/Repository/TagRepository.php53
-rw-r--r--tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php46
5 files changed, 214 insertions, 147 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php
index c6d6df6a..f3498f55 100644
--- a/src/Wallabag/ApiBundle/Controller/TagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php
@@ -46,12 +46,14 @@ class TagRestController extends WallabagRestController
46 $this->validateAuthentication(); 46 $this->validateAuthentication();
47 $label = $request->get('tag', ''); 47 $label = $request->get('tag', '');
48 48
49 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label); 49 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$label], $this->getUser()->getId());
50 50
51 if (empty($tag)) { 51 if (empty($tags)) {
52 throw $this->createNotFoundException('Tag not found'); 52 throw $this->createNotFoundException('Tag not found');
53 } 53 }
54 54
55 $tag = $tags[0];
56
55 $this->getDoctrine() 57 $this->getDoctrine()
56 ->getRepository('WallabagCoreBundle:Entry') 58 ->getRepository('WallabagCoreBundle:Entry')
57 ->removeTag($this->getUser()->getId(), $tag); 59 ->removeTag($this->getUser()->getId(), $tag);
@@ -80,15 +82,7 @@ class TagRestController extends WallabagRestController
80 82
81 $tagsLabels = $request->get('tags', ''); 83 $tagsLabels = $request->get('tags', '');
82 84
83 $tags = []; 85 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser(explode(',', $tagsLabels), $this->getUser()->getId());
84
85 foreach (explode(',', $tagsLabels) as $tagLabel) {
86 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
87
88 if (!empty($tagEntity)) {
89 $tags[] = $tagEntity;
90 }
91 }
92 86
93 if (empty($tags)) { 87 if (empty($tags)) {
94 throw $this->createNotFoundException('Tags not found'); 88 throw $this->createNotFoundException('Tags not found');
@@ -120,6 +114,12 @@ class TagRestController extends WallabagRestController
120 { 114 {
121 $this->validateAuthentication(); 115 $this->validateAuthentication();
122 116
117 $tagFromDb = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$tag->getLabel()], $this->getUser()->getId());
118
119 if (empty($tagFromDb)) {
120 throw $this->createNotFoundException('Tag not found');
121 }
122
123 $this->getDoctrine() 123 $this->getDoctrine()
124 ->getRepository('WallabagCoreBundle:Entry') 124 ->getRepository('WallabagCoreBundle:Entry')
125 ->removeTag($this->getUser()->getId(), $tag); 125 ->removeTag($this->getUser()->getId(), $tag);
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
index 0e1510a2..8e7a1d2a 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
@@ -14,97 +14,112 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
14 */ 14 */
15 public function load(ObjectManager $manager) 15 public function load(ObjectManager $manager)
16 { 16 {
17 $entry1 = new Entry($this->getReference('admin-user')); 17 $entries = [
18 $entry1->setUrl('http://0.0.0.0/entry1'); 18 'entry1' => [
19 $entry1->setReadingTime(11); 19 'user' => 'admin-user',
20 $entry1->setDomainName('domain.io'); 20 'url' => 'http://0.0.0.0/entry1',
21 $entry1->setMimetype('text/html'); 21 'reading_time' => 11,
22 $entry1->setTitle('test title entry1'); 22 'domain' => 'domain.io',
23 $entry1->setContent('This is my content /o/'); 23 'mime' => 'text/html',
24 $entry1->setLanguage('en'); 24 'title' => 'test title entry1',
25 25 'content' => 'This is my content /o/',
26 $entry1->addTag($this->getReference('foo-tag')); 26 'language' => 'en',
27 $entry1->addTag($this->getReference('baz-tag')); 27 'tags' => ['foo-tag', 'baz-tag'],
28 28 ],
29 $manager->persist($entry1); 29 'entry2' => [
30 30 'user' => 'admin-user',
31 $this->addReference('entry1', $entry1); 31 'url' => 'http://0.0.0.0/entry2',
32 32 'reading_time' => 1,
33 $entry2 = new Entry($this->getReference('admin-user')); 33 'domain' => 'domain.io',
34 $entry2->setUrl('http://0.0.0.0/entry2'); 34 'mime' => 'text/html',
35 $entry2->setReadingTime(1); 35 'title' => 'test title entry2',
36 $entry2->setDomainName('domain.io'); 36 'content' => 'This is my content /o/',
37 $entry2->setMimetype('text/html'); 37 'origin' => 'ftp://oneftp.tld',
38 $entry2->setTitle('test title entry2'); 38 'language' => 'fr',
39 $entry2->setContent('This is my content /o/'); 39 ],
40 $entry2->setOriginUrl('ftp://oneftp.tld'); 40 'entry3' => [
41 $entry2->setLanguage('fr'); 41 'user' => 'bob-user',
42 42 'url' => 'http://0.0.0.0/entry3',
43 $manager->persist($entry2); 43 'reading_time' => 1,
44 44 'domain' => 'domain.io',
45 $this->addReference('entry2', $entry2); 45 'mime' => 'text/html',
46 46 'title' => 'test title entry3',
47 $entry3 = new Entry($this->getReference('bob-user')); 47 'content' => 'This is my content /o/',
48 $entry3->setUrl('http://0.0.0.0/entry3'); 48 'language' => 'en',
49 $entry3->setReadingTime(1); 49 'tags' => ['foo-tag', 'bar-tag', 'bob-tag'],
50 $entry3->setDomainName('domain.io'); 50 ],
51 $entry3->setMimetype('text/html'); 51 'entry4' => [
52 $entry3->setTitle('test title entry3'); 52 'user' => 'admin-user',
53 $entry3->setContent('This is my content /o/'); 53 'url' => 'http://0.0.0.0/entry4',
54 $entry3->setLanguage('en'); 54 'reading_time' => 12,
55 55 'domain' => 'domain.io',
56 $entry3->addTag($this->getReference('foo-tag')); 56 'mime' => 'text/html',
57 $entry3->addTag($this->getReference('bar-tag')); 57 'title' => 'test title entry4',
58 58 'content' => 'This is my content /o/',
59 $manager->persist($entry3); 59 'language' => 'en',
60 60 'tags' => ['foo-tag', 'bar-tag'],
61 $this->addReference('entry3', $entry3); 61 ],
62 62 'entry5' => [
63 $entry4 = new Entry($this->getReference('admin-user')); 63 'user' => 'admin-user',
64 $entry4->setUrl('http://0.0.0.0/entry4'); 64 'url' => 'http://0.0.0.0/entry5',
65 $entry4->setReadingTime(12); 65 'reading_time' => 12,
66 $entry4->setDomainName('domain.io'); 66 'domain' => 'domain.io',
67 $entry4->setMimetype('text/html'); 67 'mime' => 'text/html',
68 $entry4->setTitle('test title entry4'); 68 'title' => 'test title entry5',
69 $entry4->setContent('This is my content /o/'); 69 'content' => 'This is my content /o/',
70 $entry4->setLanguage('en'); 70 'language' => 'fr',
71 71 'starred' => true,
72 $entry4->addTag($this->getReference('foo-tag')); 72 'preview' => 'http://0.0.0.0/image.jpg',
73 $entry4->addTag($this->getReference('bar-tag')); 73 ],
74 74 'entry6' => [
75 $manager->persist($entry4); 75 'user' => 'admin-user',
76 76 'url' => 'http://0.0.0.0/entry6',
77 $this->addReference('entry4', $entry4); 77 'reading_time' => 12,
78 78 'domain' => 'domain.io',
79 $entry5 = new Entry($this->getReference('admin-user')); 79 'mime' => 'text/html',
80 $entry5->setUrl('http://0.0.0.0/entry5'); 80 'title' => 'test title entry6',
81 $entry5->setReadingTime(12); 81 'content' => 'This is my content /o/',
82 $entry5->setDomainName('domain.io'); 82 'language' => 'de',
83 $entry5->setMimetype('text/html'); 83 'archived' => true,
84 $entry5->setTitle('test title entry5'); 84 'tags' => ['bar-tag'],
85 $entry5->setContent('This is my content /o/'); 85 ],
86 $entry5->setStarred(true); 86 ];
87 $entry5->setLanguage('fr'); 87
88 $entry5->setPreviewPicture('http://0.0.0.0/image.jpg'); 88 foreach ($entries as $reference => $item) {
89 89 $entry = new Entry($this->getReference($item['user']));
90 $manager->persist($entry5); 90 $entry->setUrl($item['url']);
91 91 $entry->setReadingTime($item['reading_time']);
92 $this->addReference('entry5', $entry5); 92 $entry->setDomainName($item['domain']);
93 93 $entry->setMimetype($item['mime']);
94 $entry6 = new Entry($this->getReference('admin-user')); 94 $entry->setTitle($item['title']);
95 $entry6->setUrl('http://0.0.0.0/entry6'); 95 $entry->setContent($item['content']);
96 $entry6->setReadingTime(12); 96 $entry->setLanguage($item['language']);
97 $entry6->setDomainName('domain.io'); 97
98 $entry6->setMimetype('text/html'); 98 if (isset($item['tags'])) {
99 $entry6->setTitle('test title entry6'); 99 foreach ($item['tags'] as $tag) {
100 $entry6->setContent('This is my content /o/'); 100 $entry->addTag($this->getReference($tag));
101 $entry6->setArchived(true); 101 }
102 $entry6->setLanguage('de'); 102 }
103 $entry6->addTag($this->getReference('bar-tag')); 103
104 104 if (isset($item['origin'])) {
105 $manager->persist($entry6); 105 $entry->setOriginUrl($item['origin']);
106 106 }
107 $this->addReference('entry6', $entry6); 107
108 if (isset($item['starred'])) {
109 $entry->setStarred($item['starred']);
110 }
111
112 if (isset($item['archived'])) {
113 $entry->setArchived($item['archived']);
114 }
115
116 if (isset($item['preview'])) {
117 $entry->setPreviewPicture($item['preview']);
118 }
119
120 $manager->persist($entry);
121 $this->addReference($reference, $entry);
122 }
108 123
109 $manager->flush(); 124 $manager->flush();
110 } 125 }
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
index 0ecfd18b..485445c1 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
@@ -14,33 +14,22 @@ class LoadTagData extends AbstractFixture implements OrderedFixtureInterface
14 */ 14 */
15 public function load(ObjectManager $manager) 15 public function load(ObjectManager $manager)
16 { 16 {
17 $tag1 = new Tag(); 17 $tags = [
18 $tag1->setLabel('foo bar'); 18 'foo-bar-tag' => 'foo bar', //tag used for EntryControllerTest
19 19 'bar-tag' => 'bar',
20 $manager->persist($tag1); 20 'baz-tag' => 'baz', // tag used for ExportControllerTest
21 21 'foo-tag' => 'foo',
22 $this->addReference('foo-bar-tag', $tag1); 22 'bob-tag' => 'bob', // tag used for TagRestControllerTest
23 23 ];
24 $tag2 = new Tag(); 24
25 $tag2->setLabel('bar'); 25 foreach ($tags as $reference => $label) {
26 26 $tag = new Tag();
27 $manager->persist($tag2); 27 $tag->setLabel($label);
28 28
29 $this->addReference('bar-tag', $tag2); 29 $manager->persist($tag);
30 30
31 $tag3 = new Tag(); 31 $this->addReference($reference, $tag);
32 $tag3->setLabel('baz'); 32 }
33
34 $manager->persist($tag3);
35
36 $this->addReference('baz-tag', $tag3);
37
38 $tag4 = new Tag();
39 $tag4->setLabel('foo');
40
41 $manager->persist($tag4);
42
43 $this->addReference('foo-tag', $tag4);
44 33
45 $manager->flush(); 34 $manager->flush();
46 } 35 }
diff --git a/src/Wallabag/CoreBundle/Repository/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php
index 3ae9d414..8464a6a5 100644
--- a/src/Wallabag/CoreBundle/Repository/TagRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php
@@ -3,6 +3,7 @@
3namespace Wallabag\CoreBundle\Repository; 3namespace Wallabag\CoreBundle\Repository;
4 4
5use Doctrine\ORM\EntityRepository; 5use Doctrine\ORM\EntityRepository;
6use Doctrine\ORM\QueryBuilder;
6use Wallabag\CoreBundle\Entity\Tag; 7use Wallabag\CoreBundle\Entity\Tag;
7 8
8class TagRepository extends EntityRepository 9class TagRepository extends EntityRepository
@@ -45,12 +46,8 @@ class TagRepository extends EntityRepository
45 */ 46 */
46 public function findAllTags($userId) 47 public function findAllTags($userId)
47 { 48 {
48 $ids = $this->createQueryBuilder('t') 49 $ids = $this->getQueryBuilderByUser($userId)
49 ->select('t.id') 50 ->select('t.id')
50 ->leftJoin('t.entries', 'e')
51 ->where('e.user = :userId')->setParameter('userId', $userId)
52 ->groupBy('t.id')
53 ->orderBy('t.slug')
54 ->getQuery() 51 ->getQuery()
55 ->getArrayResult(); 52 ->getArrayResult();
56 53
@@ -71,18 +68,30 @@ class TagRepository extends EntityRepository
71 */ 68 */
72 public function findAllFlatTagsWithNbEntries($userId) 69 public function findAllFlatTagsWithNbEntries($userId)
73 { 70 {
74 return $this->createQueryBuilder('t') 71 return $this->getQueryBuilderByUser($userId)
75 ->select('t.id, t.label, t.slug, count(e.id) as nbEntries') 72 ->select('t.id, t.label, t.slug, count(e.id) as nbEntries')
76 ->distinct(true) 73 ->distinct(true)
77 ->leftJoin('t.entries', 'e')
78 ->where('e.user = :userId')
79 ->groupBy('t.id')
80 ->orderBy('t.slug')
81 ->setParameter('userId', $userId)
82 ->getQuery() 74 ->getQuery()
83 ->getArrayResult(); 75 ->getArrayResult();
84 } 76 }
85 77
78 public function findByLabelsAndUser($labels, $userId)
79 {
80 $qb = $this->getQueryBuilderByUser($userId)
81 ->select('t.id');
82
83 $ids = $qb->andWhere($qb->expr()->in('t.label', $labels))
84 ->getQuery()
85 ->getArrayResult();
86
87 $tags = [];
88 foreach ($ids as $id) {
89 $tags[] = $this->find($id);
90 }
91
92 return $tags;
93 }
94
86 /** 95 /**
87 * Used only in test case to get a tag for our entry. 96 * Used only in test case to get a tag for our entry.
88 * 97 *
@@ -101,13 +110,9 @@ class TagRepository extends EntityRepository
101 110
102 public function findForArchivedArticlesByUser($userId) 111 public function findForArchivedArticlesByUser($userId)
103 { 112 {
104 $ids = $this->createQueryBuilder('t') 113 $ids = $this->getQueryBuilderByUser($userId)
105 ->select('t.id') 114 ->select('t.id')
106 ->leftJoin('t.entries', 'e')
107 ->where('e.user = :userId')->setParameter('userId', $userId)
108 ->andWhere('e.isArchived = true') 115 ->andWhere('e.isArchived = true')
109 ->groupBy('t.id')
110 ->orderBy('t.slug')
111 ->getQuery() 116 ->getQuery()
112 ->getArrayResult(); 117 ->getArrayResult();
113 118
@@ -118,4 +123,20 @@ class TagRepository extends EntityRepository
118 123
119 return $tags; 124 return $tags;
120 } 125 }
126
127 /**
128 * Retrieve a sorted list of tags used by a user.
129 *
130 * @param int $userId
131 *
132 * @return QueryBuilder
133 */
134 private function getQueryBuilderByUser($userId)
135 {
136 return $this->createQueryBuilder('t')
137 ->leftJoin('t.entries', 'e')
138 ->where('e.user = :userId')->setParameter('userId', $userId)
139 ->groupBy('t.id')
140 ->orderBy('t.slug');
141 }
121} 142}
diff --git a/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
index 430e548d..9daa94cd 100644
--- a/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
+++ b/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
@@ -7,6 +7,8 @@ use Wallabag\CoreBundle\Entity\Tag;
7 7
8class TagRestControllerTest extends WallabagApiTestCase 8class TagRestControllerTest extends WallabagApiTestCase
9{ 9{
10 private $otherUserTagLabel = 'bob';
11
10 public function testGetUserTags() 12 public function testGetUserTags()
11 { 13 {
12 $this->client->request('GET', '/api/tags.json'); 14 $this->client->request('GET', '/api/tags.json');
@@ -19,17 +21,33 @@ class TagRestControllerTest extends WallabagApiTestCase
19 $this->assertArrayHasKey('id', $content[0]); 21 $this->assertArrayHasKey('id', $content[0]);
20 $this->assertArrayHasKey('label', $content[0]); 22 $this->assertArrayHasKey('label', $content[0]);
21 23
24 $tagLabels = array_map(function ($i) {
25 return $i['label'];
26 }, $content);
27
28 $this->assertNotContains($this->otherUserTagLabel, $tagLabels, 'There is a possible tag leak');
29
22 return end($content); 30 return end($content);
23 } 31 }
24 32
25 public function testDeleteUserTag() 33 public function testDeleteUserTag()
26 { 34 {
35 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
36 $entry = $this->client->getContainer()
37 ->get('doctrine.orm.entity_manager')
38 ->getRepository('WallabagCoreBundle:Entry')
39 ->findOneWithTags($this->user->getId());
40
41 $entry = $entry[0];
42
27 $tagLabel = 'tagtest'; 43 $tagLabel = 'tagtest';
28 $tag = new Tag(); 44 $tag = new Tag();
29 $tag->setLabel($tagLabel); 45 $tag->setLabel($tagLabel);
30
31 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
32 $em->persist($tag); 46 $em->persist($tag);
47
48 $entry->addTag($tag);
49
50 $em->persist($entry);
33 $em->flush(); 51 $em->flush();
34 $em->clear(); 52 $em->clear();
35 53
@@ -53,6 +71,16 @@ class TagRestControllerTest extends WallabagApiTestCase
53 $this->assertNull($tag, $tagLabel . ' was removed because it begun an orphan tag'); 71 $this->assertNull($tag, $tagLabel . ' was removed because it begun an orphan tag');
54 } 72 }
55 73
74 public function testDeleteOtherUserTag()
75 {
76 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
77 $tag = $em->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($this->otherUserTagLabel);
78
79 $this->client->request('DELETE', '/api/tags/' . $tag->getId() . '.json');
80
81 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
82 }
83
56 public function dataForDeletingTagByLabel() 84 public function dataForDeletingTagByLabel()
57 { 85 {
58 return [ 86 return [
@@ -112,6 +140,13 @@ class TagRestControllerTest extends WallabagApiTestCase
112 $this->assertSame(404, $this->client->getResponse()->getStatusCode()); 140 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
113 } 141 }
114 142
143 public function testDeleteTagByLabelOtherUser()
144 {
145 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => $this->otherUserTagLabel]);
146
147 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
148 }
149
115 /** 150 /**
116 * @dataProvider dataForDeletingTagByLabel 151 * @dataProvider dataForDeletingTagByLabel
117 */ 152 */
@@ -180,4 +215,11 @@ class TagRestControllerTest extends WallabagApiTestCase
180 215
181 $this->assertSame(404, $this->client->getResponse()->getStatusCode()); 216 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
182 } 217 }
218
219 public function testDeleteTagsByLabelOtherUser()
220 {
221 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => $this->otherUserTagLabel]);
222
223 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
224 }
183} 225}