Move API stuff in ApiBundle

This commit is contained in:
Jeremy 2015-03-29 10:53:10 +02:00
parent e3c34bfc06
commit 769e19dc4a
25 changed files with 571 additions and 297 deletions

View file

@ -23,7 +23,8 @@ class AppKernel extends Kernel
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'))) {

View file

@ -1,3 +1,7 @@
wallabag_api:
resource: "@WallabagApiBundle/Resources/config/routing.yml"
prefix: /
app:
resource: @WallabagCoreBundle/Controller/
type: annotation

View file

@ -1,3 +1,3 @@
Rest_Wallabag:
type : rest
resource: "@WallabagCoreBundle/Resources/config/routing_rest.yml"
resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"

View file

@ -1,6 +1,6 @@
<?php
namespace Wallabag\CoreBundle\Controller;
namespace Wallabag\ApiBundle\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
@ -62,6 +62,7 @@ class WallabagRestController extends Controller
return array($user->getSalt() ?: null);
}
/**
* Retrieve all entries. It could be filtered by many options.
*
@ -86,17 +87,13 @@ class WallabagRestController extends Controller
$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);
@ -108,7 +105,7 @@ class WallabagRestController extends Controller
$json = $this->get('serializer')->serialize($paginatedCollection, 'json');
return new Response($json, 200, array('application/json'));
return $this->renderJsonResponse($json);
}
/**
@ -123,13 +120,11 @@ class WallabagRestController extends Controller
*/
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);
}
/**
@ -165,7 +160,7 @@ class WallabagRestController extends Controller
$json = $this->get('serializer')->serialize($entry, 'json');
return new Response($json, 200, array('application/json'));
return $this->renderJsonResponse($json);
}
/**
@ -186,9 +181,7 @@ class WallabagRestController extends Controller
*/
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");
@ -216,7 +209,7 @@ class WallabagRestController extends Controller
$json = $this->get('serializer')->serialize($entry, 'json');
return new Response($json, 200, array('application/json'));
return $this->renderJsonResponse($json);
}
/**
@ -231,9 +224,7 @@ class WallabagRestController extends Controller
*/
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);
@ -241,7 +232,7 @@ class WallabagRestController extends Controller
$json = $this->get('serializer')->serialize($entry, 'json');
return new Response($json, 200, array('application/json'));
return $this->renderJsonResponse($json);
}
/**
@ -255,13 +246,11 @@ class WallabagRestController extends Controller
*/
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);
}
/**
@ -278,9 +267,7 @@ class WallabagRestController extends Controller
*/
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)) {
@ -293,7 +280,7 @@ class WallabagRestController extends Controller
$json = $this->get('serializer')->serialize($entry, 'json');
return new Response($json, 200, array('application/json'));
return $this->renderJsonResponse($json);
}
/**
@ -301,16 +288,14 @@ class WallabagRestController extends Controller
*
* @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();
@ -319,7 +304,7 @@ class WallabagRestController extends Controller
$json = $this->get('serializer')->serialize($entry, 'json');
return new Response($json, 200, array('application/json'));
return $this->renderJsonResponse($json);
}
/**
@ -331,7 +316,7 @@ class WallabagRestController extends Controller
{
$json = $this->get('serializer')->serialize($this->getUser()->getTags(), 'json');
return new Response($json, 200, array('application/json'));
return $this->renderJsonResponse($json);
}
/**
@ -339,15 +324,13 @@ class WallabagRestController extends Controller
*
* @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);
@ -355,6 +338,33 @@ class WallabagRestController extends Controller
$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'));
}
}

View file

@ -0,0 +1,29 @@
<?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;
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Wallabag\CoreBundle\DependencyInjection\Security\Factory;
namespace Wallabag\ApiBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

View file

@ -0,0 +1,25 @@
<?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';
}
}

View file

@ -0,0 +1,4 @@
entries:
type: rest
resource: "WallabagApiBundle:WallabagRest"
name_prefix: api_

View file

@ -0,0 +1,12 @@
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']

View file

@ -1,12 +1,12 @@
<?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
{

View file

@ -1,5 +1,5 @@
<?php
namespace Wallabag\CoreBundle\Security\Authentication\Token;
namespace Wallabag\ApiBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;

View file

@ -1,6 +1,6 @@
<?php
namespace Wallabag\CoreBundle\Security\Firewall;
namespace Wallabag\ApiBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@ -8,7 +8,7 @@ use Symfony\Component\Security\Http\Firewall\ListenerInterface;
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

View file

@ -0,0 +1,410 @@
<?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']);
}
}

View file

@ -0,0 +1,18 @@
<?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());
}
}

View file

@ -3,15 +3,15 @@
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');
}

View file

@ -1,4 +0,0 @@
entries:
type: rest
resource: "WallabagCoreBundle:WallabagRest"
name_prefix: api_

View file

@ -4,18 +4,6 @@ services:
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:

View file

@ -2,7 +2,7 @@
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;
@ -12,7 +12,7 @@ use Symfony\Component\Console\Output\NullOutput;
use Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
class InstallCommandTest extends WallabagTestCase
class InstallCommandTest extends WallabagCoreTestCase
{
public static function tearDownAfterClass()
{

View file

@ -2,9 +2,9 @@
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()
{

View file

@ -2,10 +2,10 @@
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()
{

View file

@ -2,11 +2,11 @@
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()
{

View file

@ -1,214 +0,0 @@
<?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);
}
}
}

View file

@ -4,7 +4,7 @@ namespace Wallabag\CoreBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
abstract class WallabagTestCase extends WebTestCase
abstract class WallabagCoreTestCase extends WebTestCase
{
private $client = null;

View file

@ -3,16 +3,7 @@
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());
}
}