new Nelmio\CorsBundle\NelmioCorsBundle(),
new Liip\ThemeBundle\LiipThemeBundle(),
new Wallabag\CoreBundle\WallabagCoreBundle(),
- new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle()
+ new Wallabag\ApiBundle\WallabagApiBundle(),
+ new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),
);
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
+wallabag_api:
+ resource: "@WallabagApiBundle/Resources/config/routing.yml"
+ prefix: /
+
app:
resource: @WallabagCoreBundle/Controller/
type: annotation
Rest_Wallabag:
type : rest
- resource: "@WallabagCoreBundle/Resources/config/routing_rest.yml"
\ No newline at end of file
+ resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"
<?php
-namespace Wallabag\CoreBundle\Controller;
+namespace Wallabag\ApiBundle\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
return array($user->getSalt() ?: null);
}
+
/**
* Retrieve all entries. It could be filtered by many options.
*
$order = $request->query->get('order', 'desc');
$page = (int) $request->query->get('page', 1);
$perPage = (int) $request->query->get('perPage', 30);
- $tags = $request->query->get('tags', array());
+ $tags = $request->query->get('tags', []);
$pager = $this
->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order);
- if (0 === $pager->getNbResults()) {
- throw $this->createNotFoundException();
- }
-
$pager->setCurrentPage($page);
$pager->setMaxPerPage($perPage);
$json = $this->get('serializer')->serialize($paginatedCollection, 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
*/
public function getEntryAction(Entry $entry)
{
- if ($entry->getUser()->getId() != $this->getUser()->getId()) {
- throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$entry->getUser()->getId().', logged user id: '.$this->getUser()->getId());
- }
+ $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
$json = $this->get('serializer')->serialize($entry, 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
$json = $this->get('serializer')->serialize($entry, 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
*/
public function patchEntriesAction(Entry $entry, Request $request)
{
- if ($entry->getUser()->getId() != $this->getUser()->getId()) {
- throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$entry->getUser()->getId().', logged user id: '.$this->getUser()->getId());
- }
+ $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
$title = $request->request->get("title");
$isArchived = $request->request->get("archive");
$json = $this->get('serializer')->serialize($entry, 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
*/
public function deleteEntriesAction(Entry $entry)
{
- if ($entry->getUser()->getId() != $this->getUser()->getId()) {
- throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$entry->getUser()->getId().', logged user id: '.$this->getUser()->getId());
- }
+ $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
$em = $this->getDoctrine()->getManager();
$em->remove($entry);
$json = $this->get('serializer')->serialize($entry, 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
*/
public function getEntriesTagsAction(Entry $entry)
{
- if ($entry->getUser()->getId() != $this->getUser()->getId()) {
- throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$entry->getUser()->getId().', logged user id: '.$this->getUser()->getId());
- }
+ $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
$json = $this->get('serializer')->serialize($entry->getTags(), 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
*/
public function postEntriesTagsAction(Request $request, Entry $entry)
{
- if ($entry->getUser()->getId() != $this->getUser()->getId()) {
- throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$entry->getUser()->getId().', logged user id: '.$this->getUser()->getId());
- }
+ $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
$tags = $request->request->get('tags', '');
if (!empty($tags)) {
$json = $this->get('serializer')->serialize($entry, 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
*
* @ApiDoc(
* requirements={
- * {"name"="tag", "dataType"="string", "requirement"="\w+", "description"="The tag"},
+ * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
* }
* )
*/
public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
{
- if ($entry->getUser()->getId() != $this->getUser()->getId()) {
- throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$entry->getUser()->getId().', logged user id: '.$this->getUser()->getId());
- }
+ $this->validateUserAccess($entry->getUser()->getId(), $this->getUser()->getId());
$entry->removeTag($tag);
$em = $this->getDoctrine()->getManager();
$json = $this->get('serializer')->serialize($entry, 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
{
$json = $this->get('serializer')->serialize($this->getUser()->getTags(), 'json');
- return new Response($json, 200, array('application/json'));
+ return $this->renderJsonResponse($json);
}
/**
*
* @ApiDoc(
* requirements={
- * {"name"="tag", "dataType"="string", "requirement"="\w+", "description"="The tag"}
+ * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
* }
* )
*/
public function deleteTagAction(Tag $tag)
{
- if ($tag->getUser()->getId() != $this->getUser()->getId()) {
- throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$tag->getUser()->getId().', logged user id: '.$this->getUser()->getId());
- }
+ $this->validateUserAccess($tag->getUser()->getId(), $this->getUser()->getId());
$em = $this->getDoctrine()->getManager();
$em->remove($tag);
$json = $this->get('serializer')->serialize($tag, 'json');
+ return $this->renderJsonResponse($json);
+ }
+
+ /**
+ * Validate that the first id is equal to the second one.
+ * If not, throw exception. It means a user try to access information from an other user
+ *
+ * @param integer $requestUserId User id from the requested source
+ * @param integer $currentUserId User id from the retrieved source
+ */
+ private function validateUserAccess($requestUserId, $currentUserId)
+ {
+ if ($requestUserId != $currentUserId) {
+ throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$currentUserId);
+ }
+ }
+
+ /**
+ * Send a JSON Response.
+ * We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string
+ *
+ * @param string $json
+ *
+ * @return Response
+ */
+ private function renderJsonResponse($json)
+ {
return new Response($json, 200, array('application/json'));
}
}
--- /dev/null
+<?php
+
+namespace Wallabag\ApiBundle\DependencyInjection;
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+/**
+ * This is the class that validates and merges configuration from your app/config files
+ *
+ * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
+ */
+class Configuration implements ConfigurationInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfigTreeBuilder()
+ {
+ $treeBuilder = new TreeBuilder();
+ $rootNode = $treeBuilder->root('wallabag_api');
+
+ // Here you should define the parameters that are allowed to
+ // configure your bundle. See the documentation linked above for
+ // more information on that topic.
+
+ return $treeBuilder;
+ }
+}
<?php
-namespace Wallabag\CoreBundle\DependencyInjection\Security\Factory;
+namespace Wallabag\ApiBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
--- /dev/null
+<?php
+
+namespace Wallabag\ApiBundle\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\DependencyInjection\Loader;
+
+class WallabagApiExtension extends Extension
+{
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $configuration = new Configuration();
+ $config = $this->processConfiguration($configuration, $configs);
+
+ $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader->load('services.yml');
+ }
+
+ public function getAlias()
+ {
+ return 'wallabag_api';
+ }
+}
--- /dev/null
+entries:
+ type: rest
+ resource: "WallabagApiBundle:WallabagRest"
+ name_prefix: api_
--- /dev/null
+services:
+ wsse.security.authentication.provider:
+ class: Wallabag\ApiBundle\Security\Authentication\Provider\WsseProvider
+ public: false
+ arguments: ['', '%kernel.cache_dir%/security/nonces']
+
+ wsse.security.authentication.listener:
+ class: Wallabag\ApiBundle\Security\Firewall\WsseListener
+ public: false
+ tags:
+ - { name: monolog.logger, channel: wsse }
+ arguments: ['@security.context', '@security.authentication.manager', '@logger']
<?php
-namespace Wallabag\CoreBundle\Security\Authentication\Provider;
+namespace Wallabag\ApiBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
-use Wallabag\CoreBundle\Security\Authentication\Token\WsseUserToken;
+use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
class WsseProvider implements AuthenticationProviderInterface
{
<?php
-namespace Wallabag\CoreBundle\Security\Authentication\Token;
+namespace Wallabag\ApiBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
<?php
-namespace Wallabag\CoreBundle\Security\Firewall;
+namespace Wallabag\ApiBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
-use Wallabag\CoreBundle\Security\Authentication\Token\WsseUserToken;
+use Wallabag\ApiBundle\Security\Authentication\Token\WsseUserToken;
use Psr\Log\LoggerInterface;
class WsseListener implements ListenerInterface
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Tests\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+
+class WallabagRestControllerTest extends WebTestCase
+{
+ protected static $salt;
+
+ /**
+ * Grab the salt once and store it to be available for all tests
+ */
+ public static function setUpBeforeClass()
+ {
+ $client = self::createClient();
+
+ $user = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:User')
+ ->findOneByUsername('admin');
+
+ self::$salt = $user->getSalt();
+ }
+
+ /**
+ * Generate HTTP headers for authenticate user on API
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return array
+ */
+ private function generateHeaders($username, $password)
+ {
+ $encryptedPassword = sha1($password.$username.self::$salt);
+ $nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
+
+ $now = new \DateTime('now', new \DateTimeZone('UTC'));
+ $created = (string) $now->format('Y-m-d\TH:i:s\Z');
+ $digest = base64_encode(sha1(base64_decode($nonce).$created.$encryptedPassword, true));
+
+ return array(
+ 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
+ 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="'.$username.'", PasswordDigest="'.$digest.'", Nonce="'.$nonce.'", Created="'.$created.'"',
+ );
+ }
+
+ public function testGetSalt()
+ {
+ $client = $this->createClient();
+ $client->request('GET', '/api/salts/admin.json');
+
+ $user = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:User')
+ ->findOneByUsername('admin');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertArrayHasKey(0, $content);
+ $this->assertEquals($user->getSalt(), $content[0]);
+
+ $client->request('GET', '/api/salts/notfound.json');
+ $this->assertEquals(404, $client->getResponse()->getStatusCode());
+ }
+
+ public function testWithBadHeaders()
+ {
+ $client = $this->createClient();
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByIsArchived(false);
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ $badHeaders = array(
+ 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
+ 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="admin", PasswordDigest="Wr0ngDig3st", Nonce="n0Nc3", Created="2015-01-01T13:37:00Z"',
+ );
+
+ $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $badHeaders);
+ $this->assertEquals(403, $client->getResponse()->getStatusCode());
+ }
+
+ public function testGetOneEntry()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneBy(array('user' => 1, 'isArchived' => false));
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertEquals($entry->getTitle(), $content['title']);
+ $this->assertEquals($entry->getUrl(), $content['url']);
+ $this->assertCount(count($entry->getTags()), $content['tags']);
+
+ $this->assertTrue(
+ $client->getResponse()->headers->contains(
+ 'Content-Type',
+ 'application/json'
+ )
+ );
+ }
+
+ public function testGetOneEntryWrongUser()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneBy(array('user' => 2, 'isArchived' => false));
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
+
+ $this->assertEquals(403, $client->getResponse()->getStatusCode());
+ }
+
+ public function testGetEntries()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $client->request('GET', '/api/entries', array(), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertGreaterThanOrEqual(1, count($content));
+ $this->assertNotEmpty($content['_embedded']['items']);
+ $this->assertGreaterThanOrEqual(1, $content['total']);
+ $this->assertEquals(1, $content['page']);
+ $this->assertGreaterThanOrEqual(1, $content['pages']);
+
+ $this->assertTrue(
+ $client->getResponse()->headers->contains(
+ 'Content-Type',
+ 'application/json'
+ )
+ );
+ }
+
+ public function testGetStarredEntries()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $client->request('GET', '/api/entries', array('archive' => 1), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertGreaterThanOrEqual(1, count($content));
+ $this->assertEmpty($content['_embedded']['items']);
+ $this->assertEquals(0, $content['total']);
+ $this->assertEquals(1, $content['page']);
+ $this->assertEquals(1, $content['pages']);
+
+ $this->assertTrue(
+ $client->getResponse()->headers->contains(
+ 'Content-Type',
+ 'application/json'
+ )
+ );
+ }
+
+ public function testDeleteEntry()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUser(1);
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertEquals($entry->getTitle(), $content['title']);
+ $this->assertEquals($entry->getUrl(), $content['url']);
+
+ // We'll try to delete this entry again
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
+
+ $this->assertEquals(404, $client->getResponse()->getStatusCode());
+ }
+
+ public function testPostEntry()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $client->request('POST', '/api/entries.json', array(
+ 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
+ 'tags' => 'google',
+ ), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertGreaterThan(0, $content['id']);
+ $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
+ $this->assertEquals(false, $content['is_archived']);
+ $this->assertEquals(false, $content['is_starred']);
+ $this->assertCount(1, $content['tags']);
+ }
+
+ public function testPatchEntry()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUser(1);
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ // hydrate the tags relations
+ $nbTags = count($entry->getTags());
+
+ $client->request('PATCH', '/api/entries/'.$entry->getId().'.json', array(
+ 'title' => 'New awesome title',
+ 'tags' => 'new tag '.uniqid(),
+ 'star' => true,
+ 'archive' => false,
+ ), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertEquals($entry->getId(), $content['id']);
+ $this->assertEquals($entry->getUrl(), $content['url']);
+ $this->assertEquals('New awesome title', $content['title']);
+ $this->assertGreaterThan($nbTags, count($content['tags']));
+ }
+
+ public function testGetTagsEntry()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneWithTags(1);
+
+ $entry = $entry[0];
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ $tags = array();
+ foreach ($entry->getTags() as $tag) {
+ $tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel());
+ }
+
+ $client->request('GET', '/api/entries/'.$entry->getId().'/tags', array(), array(), $headers);
+
+ $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $client->getResponse()->getContent());
+ }
+
+ public function testPostTagsOnEntry()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUser(1);
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ $nbTags = count($entry->getTags());
+
+ $newTags = 'tag1,tag2,tag3';
+
+ $client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertArrayHasKey('tags', $content);
+ $this->assertEquals($nbTags+3, count($content['tags']));
+
+ $entryDB = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->find($entry->getId());
+
+ $tagsInDB = array();
+ foreach ($entryDB->getTags()->toArray() as $tag) {
+ $tagsInDB[$tag->getId()] = $tag->getLabel();
+ }
+
+ foreach (explode(',', $newTags) as $tag) {
+ $this->assertContains($tag, $tagsInDB);
+ }
+ }
+
+ public function testDeleteOneTagEntrie()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $entry = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUser(1);
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ // hydrate the tags relations
+ $nbTags = count($entry->getTags());
+ $tag = $entry->getTags()[0];
+
+ $client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json', array(), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertArrayHasKey('tags', $content);
+ $this->assertEquals($nbTags-1, count($content['tags']));
+ }
+
+ public function testGetUserTags()
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $client->request('GET', '/api/tags.json', array(), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertGreaterThan(0, $content);
+ $this->assertArrayHasKey('id', $content[0]);
+ $this->assertArrayHasKey('label', $content[0]);
+
+ return end($content);
+ }
+
+ /**
+ * @depends testGetUserTags
+ */
+ public function testDeleteUserTag($tag)
+ {
+ $client = $this->createClient();
+ $headers = $this->generateHeaders('admin', 'mypassword');
+
+ $client->request('DELETE', '/api/tags/'.$tag['id'].'.json', array(), array(), $headers);
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $content = json_decode($client->getResponse()->getContent(), true);
+
+ $this->assertArrayHasKey('label', $content);
+ $this->assertEquals($tag['label'], $content['label']);
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\ApiBundle;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+use Wallabag\ApiBundle\DependencyInjection\Security\Factory\WsseFactory;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+class WallabagApiBundle extends Bundle
+{
+ public function build(ContainerBuilder $container)
+ {
+ parent::build($container);
+
+ $extension = $container->getExtension('security');
+ $extension->addSecurityListenerFactory(new WsseFactory());
+ }
+}
namespace Wallabag\CoreBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
-use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Config\FileLocator;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\DependencyInjection\Loader;
class WallabagCoreExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
- $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
+++ /dev/null
-entries:
- type: rest
- resource: "WallabagCoreBundle:WallabagRest"
- name_prefix: api_
tags:
- { name: twig.extension }
- wsse.security.authentication.provider:
- class: Wallabag\CoreBundle\Security\Authentication\Provider\WsseProvider
- public: false
- arguments: ['', '%kernel.cache_dir%/security/nonces']
-
- wsse.security.authentication.listener:
- class: Wallabag\CoreBundle\Security\Firewall\WsseListener
- public: false
- tags:
- - { name: monolog.logger, channel: wsse }
- arguments: ['@security.context', '@security.authentication.manager', '@logger']
-
wallabag_core.helper.detect_active_theme:
class: Wallabag\CoreBundle\Helper\DetectActiveTheme
arguments:
namespace Wallabag\CoreBundle\Tests\Command;
-use Wallabag\CoreBundle\Tests\WallabagTestCase;
+use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
use Wallabag\CoreBundle\Command\InstallCommand;
use Wallabag\CoreBundle\Tests\Mock\InstallCommandMock;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
-class InstallCommandTest extends WallabagTestCase
+class InstallCommandTest extends WallabagCoreTestCase
{
public static function tearDownAfterClass()
{
namespace Wallabag\CoreBundle\Tests\Controller;
-use Wallabag\CoreBundle\Tests\WallabagTestCase;
+use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
-class ConfigControllerTest extends WallabagTestCase
+class ConfigControllerTest extends WallabagCoreTestCase
{
public function testLogin()
{
namespace Wallabag\CoreBundle\Tests\Controller;
-use Wallabag\CoreBundle\Tests\WallabagTestCase;
+use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
use Doctrine\ORM\AbstractQuery;
-class EntryControllerTest extends WallabagTestCase
+class EntryControllerTest extends WallabagCoreTestCase
{
public function testLogin()
{
namespace Wallabag\CoreBundle\Tests\Controller;
-use Wallabag\CoreBundle\Tests\WallabagTestCase;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
+use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
-class SecurityControllerTest extends WallabagTestCase
+class SecurityControllerTest extends WallabagCoreTestCase
{
public function testLogin()
{
+++ /dev/null
-<?php
-
-namespace Wallabag\CoreBundle\Tests\Controller;
-
-use Wallabag\CoreBundle\Tests\WallabagTestCase;
-
-class WallabagRestControllerTest extends WallabagTestCase
-{
- /**
- * Generate HTTP headers for authenticate user on API
- *
- * @param $username
- * @param $password
- * @param $salt
- *
- * @return array
- */
- private function generateHeaders($username, $password, $salt)
- {
- $encryptedPassword = sha1($password.$username.$salt);
- $nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
-
- $now = new \DateTime('now', new \DateTimeZone('UTC'));
- $created = (string) $now->format('Y-m-d\TH:i:s\Z');
- $digest = base64_encode(sha1(base64_decode($nonce).$created.$encryptedPassword, true));
-
- return array(
- 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
- 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="'.$username.'", PasswordDigest="'.$digest.'", Nonce="'.$nonce.'", Created="'.$created.'"',
- );
- }
-
- public function testGetSalt()
- {
- $client = $this->createClient();
- $client->request('GET', '/api/salts/admin.json');
- $this->assertEquals(200, $client->getResponse()->getStatusCode());
- $this->assertNotEmpty(json_decode($client->getResponse()->getContent()));
-
- $client->request('GET', '/api/salts/notfound.json');
- $this->assertEquals(404, $client->getResponse()->getStatusCode());
- }
-
- public function testWithBadHeaders()
- {
- $client = $this->createClient();
-
- $entry = $client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagCoreBundle:Entry')
- ->findOneByIsArchived(false);
-
- if (!$entry) {
- $this->markTestSkipped('No content found in db.');
- }
-
- $badHeaders = array(
- 'HTTP_AUTHORIZATION' => 'Authorization profile="UsernameToken"',
- 'HTTP_x-wsse' => 'X-WSSE: UsernameToken Username="admin", PasswordDigest="Wr0ngDig3st", Nonce="n0Nc3", Created="2015-01-01T13:37:00Z"',
- );
-
- $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $badHeaders);
- $this->assertEquals(403, $client->getResponse()->getStatusCode());
- }
-
- public function testGetOneEntry()
- {
- $client = $this->createClient();
- $client->request('GET', '/api/salts/admin.json');
- $salt = json_decode($client->getResponse()->getContent());
-
- $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
-
- $entry = $client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagCoreBundle:Entry')
- ->findOneByIsArchived(false);
-
- if (!$entry) {
- $this->markTestSkipped('No content found in db.');
- }
-
- $client->request('GET', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
- $this->assertContains($entry->getTitle(), $client->getResponse()->getContent());
-
- $this->assertTrue(
- $client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
- }
-
- public function testGetEntries()
- {
- $client = $this->createClient();
- $client->request('GET', '/api/salts/admin.json');
- $salt = json_decode($client->getResponse()->getContent());
-
- $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
-
- $client->request('GET', '/api/entries', array(), array(), $headers);
-
- $this->assertEquals(200, $client->getResponse()->getStatusCode());
-
- $this->assertGreaterThanOrEqual(1, count(json_decode($client->getResponse()->getContent())));
-
- $this->assertContains('Google', $client->getResponse()->getContent());
-
- $this->assertTrue(
- $client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
- }
-
- public function testDeleteEntry()
- {
- $client = $this->createClient();
- $client->request('GET', '/api/salts/admin.json');
- $salt = json_decode($client->getResponse()->getContent());
-
- $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
-
- $entry = $client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagCoreBundle:Entry')
- ->findOneByUser(1);
-
- if (!$entry) {
- $this->markTestSkipped('No content found in db.');
- }
-
- $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
-
- $this->assertEquals(200, $client->getResponse()->getStatusCode());
-
- // We'll try to delete this entry again
- $client->request('GET', '/api/salts/admin.json');
- $salt = json_decode($client->getResponse()->getContent());
-
- $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
-
- $client->request('DELETE', '/api/entries/'.$entry->getId().'.json', array(), array(), $headers);
-
- $this->assertEquals(404, $client->getResponse()->getStatusCode());
- }
-
- public function testGetTagsEntry()
- {
- $client = $this->createClient();
- $client->request('GET', '/api/salts/admin.json');
- $salt = json_decode($client->getResponse()->getContent());
- $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
-
- $entry = $client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagCoreBundle:Entry')
- ->findOneWithTags(1);
-
- $entry = $entry[0];
-
- if (!$entry) {
- $this->markTestSkipped('No content found in db.');
- }
-
- $tags = array();
- foreach ($entry->getTags() as $tag) {
- $tags[] = array('id' => $tag->getId(), 'label' => $tag->getLabel());
- }
-
- $client->request('GET', '/api/entries/'.$entry->getId().'/tags', array(), array(), $headers);
-
- $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $client->getResponse()->getContent());
- }
-
- public function testPostTagsOnEntry()
- {
- $client = $this->createClient();
- $client->request('GET', '/api/salts/admin.json');
- $salt = json_decode($client->getResponse()->getContent());
- $headers = $this->generateHeaders('admin', 'mypassword', $salt[0]);
-
- $entry = $client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagCoreBundle:Entry')
- ->findOneByUser(1);
-
- if (!$entry) {
- $this->markTestSkipped('No content found in db.');
- }
-
- $newTags = 'tag1,tag2,tag3';
-
- $client->request('POST', '/api/entries/'.$entry->getId().'/tags', array('tags' => $newTags), array(), $headers);
-
- $this->assertEquals(200, $client->getResponse()->getStatusCode());
-
- $entryDB = $client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagCoreBundle:Entry')
- ->find($entry->getId());
-
- $tagsInDB = array();
- foreach ($entryDB->getTags()->toArray() as $tag) {
- $tagsInDB[$tag->getId()] = $tag->getLabel();
- }
-
- foreach (explode(',', $newTags) as $tag) {
- $this->assertContains($tag, $tagsInDB);
- }
- }
-}
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-abstract class WallabagTestCase extends WebTestCase
+abstract class WallabagCoreTestCase extends WebTestCase
{
private $client = null;
namespace Wallabag\CoreBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
-use Wallabag\CoreBundle\DependencyInjection\Security\Factory\WsseFactory;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
class WallabagCoreBundle extends Bundle
{
- public function build(ContainerBuilder $container)
- {
- parent::build($container);
-
- $extension = $container->getExtension('security');
- $extension->addSecurityListenerFactory(new WsseFactory());
- }
}