Add token entities

This commit is contained in:
Romain Neutron
2014-02-28 01:59:00 +01:00
parent d3bf3f747e
commit 8b6cb53a36
61 changed files with 1442 additions and 758 deletions

View File

@@ -72,6 +72,7 @@ use Alchemy\Phrasea\Controller\Utils\ConnectionTest;
use Alchemy\Phrasea\Controller\Utils\PathFileTest; use Alchemy\Phrasea\Controller\Utils\PathFileTest;
use Alchemy\Phrasea\Controller\User\Notifications; use Alchemy\Phrasea\Controller\User\Notifications;
use Alchemy\Phrasea\Controller\User\Preferences; use Alchemy\Phrasea\Controller\User\Preferences;
use Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider;
use Alchemy\Phrasea\Core\PhraseaExceptionHandler; use Alchemy\Phrasea\Core\PhraseaExceptionHandler;
use Alchemy\Phrasea\Core\Event\Subscriber\LogoutSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\LogoutSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaLocaleSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaLocaleSubscriber;
@@ -211,6 +212,7 @@ class Application extends SilexApplication
} }
$this->register(new BasketMiddlewareProvider()); $this->register(new BasketMiddlewareProvider());
$this->register(new TokenMiddlewareProvider());
$this->register(new ACLServiceProvider()); $this->register(new ACLServiceProvider());
$this->register(new AuthenticationManagerServiceProvider()); $this->register(new AuthenticationManagerServiceProvider());

View File

@@ -1,43 +0,0 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Authentication\Token;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class TokenValidator
{
private $random;
public function __construct(\random $random)
{
$this->random = $random;
}
/**
* Returns true if the token is valid
*
* @param type $token
* @return boolean
*/
public function isValid($token)
{
try {
$datas = $this->random->helloToken($token);
return $datas['usr_id'];
} catch (NotFoundHttpException $e) {
}
return false;
}
}

View File

@@ -26,6 +26,7 @@ use Alchemy\Phrasea\Model\Entities\LazaretSession;
use Alchemy\Phrasea\Model\Entities\Registration; use Alchemy\Phrasea\Model\Entities\Registration;
use Alchemy\Phrasea\Model\Entities\Session; use Alchemy\Phrasea\Model\Entities\Session;
use Alchemy\Phrasea\Model\Entities\Task; use Alchemy\Phrasea\Model\Entities\Task;
use Alchemy\Phrasea\Model\Entities\Token;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\ValidationData; use Alchemy\Phrasea\Model\Entities\ValidationData;
use Alchemy\Phrasea\Model\Entities\ValidationSession; use Alchemy\Phrasea\Model\Entities\ValidationSession;
@@ -35,6 +36,7 @@ use Alchemy\Phrasea\Model\Entities\UsrList;
use Alchemy\Phrasea\Model\Entities\UsrListEntry; use Alchemy\Phrasea\Model\Entities\UsrListEntry;
use Alchemy\Phrasea\Model\Entities\StoryWZ; use Alchemy\Phrasea\Model\Entities\StoryWZ;
use Alchemy\Phrasea\Core\Provider\ORMServiceProvider; use Alchemy\Phrasea\Core\Provider\ORMServiceProvider;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\SchemaTool;
use Gedmo\Timestampable\TimestampableListener; use Gedmo\Timestampable\TimestampableListener;
@@ -88,6 +90,8 @@ class RegenerateSqliteDb extends Command
$this->insertOauthApps($DI); $this->insertOauthApps($DI);
$this->generateCollection($DI); $this->generateCollection($DI);
$this->generateRecord($DI); $this->generateRecord($DI);
$this->insertTwoTasks($this->container['EM']);
$this->insertTwoBasket($this->container['EM'], $DI);
$this->insertOneStoryInWz($this->container['EM'], $DI); $this->insertOneStoryInWz($this->container['EM'], $DI);
$this->insertUsrLists($this->container['EM'], $DI); $this->insertUsrLists($this->container['EM'], $DI);
$this->insertOnePrivateFeed($this->container['EM'], $DI); $this->insertOnePrivateFeed($this->container['EM'], $DI);
@@ -99,7 +103,21 @@ class RegenerateSqliteDb extends Command
$this->insertOneRegistration($DI, $this->container['EM'], $DI['user_alt1'], $DI['coll'], 'now', 'registration_1'); $this->insertOneRegistration($DI, $this->container['EM'], $DI['user_alt1'], $DI['coll'], 'now', 'registration_1');
$this->insertOneRegistration($DI, $this->container['EM'], $DI['user_alt2'], $DI['coll'], '-3 months', 'registration_2'); $this->insertOneRegistration($DI, $this->container['EM'], $DI['user_alt2'], $DI['coll'], '-3 months', 'registration_2');
$this->insertOneRegistration($DI, $this->container['EM'], $DI['user_notAdmin'], $DI['coll'], 'now', 'registration_3'); $this->insertOneRegistration($DI, $this->container['EM'], $DI['user_notAdmin'], $DI['coll'], 'now', 'registration_3');
$this->insertTwoTokens($this->container['EM'], $DI);
$this->insertOneInvalidToken($this->container['EM'], $DI);
$this->insertOneValidationToken($this->container['EM'], $DI);
$this->container['EM']->flush();
$fixtures['basket']['basket_1'] = $DI['basket_1']->getId();
$fixtures['basket']['basket_2'] = $DI['basket_2']->getId();
$fixtures['basket']['basket_3'] = $DI['basket_3']->getId();
$fixtures['basket']['basket_4'] = $DI['basket_4']->getId();
$fixtures['token']['token_1'] = $DI['token_1']->getValue();
$fixtures['token']['token_2'] = $DI['token_2']->getValue();
$fixtures['token']['token_invalid'] = $DI['token_invalid']->getValue();
$fixtures['token']['token_validation'] = $DI['token_validation']->getValue();
$fixtures['user']['test_phpunit'] = $DI['user']->getId(); $fixtures['user']['test_phpunit'] = $DI['user']->getId();
$fixtures['user']['test_phpunit_not_admin'] = $DI['user_notAdmin']->getId(); $fixtures['user']['test_phpunit_not_admin'] = $DI['user_notAdmin']->getId();
$fixtures['user']['test_phpunit_alt1'] = $DI['user_alt1']->getId(); $fixtures['user']['test_phpunit_alt1'] = $DI['user_alt1']->getId();
@@ -140,10 +158,13 @@ class RegenerateSqliteDb extends Command
$fixtures['user']['user_3_deleted'] = $DI['user_3_deleted']->getId(); $fixtures['user']['user_3_deleted'] = $DI['user_3_deleted']->getId();
$fixtures['user']['user_template'] = $DI['user_template']->getId(); $fixtures['user']['user_template'] = $DI['user_template']->getId();
$this->insertTwoTasks($this->container['EM']); $fixtures['feed']['public']['feed'] = $DI['feed_public']->getId();
$this->insertTwoBasket($this->container['EM'], $DI); $fixtures['feed']['public']['entry'] = $DI['feed_public_entry']->getId();
$fixtures['feed']['public']['token'] = $DI['feed_public_token']->getId();
$this->container['EM']->flush(); $fixtures['feed']['private']['feed'] = $DI['feed_private']->getId();
$fixtures['feed']['private']['entry'] = $DI['feed_private_entry']->getId();
$fixtures['feed']['private']['token'] = $DI['feed_private_token']->getId();
} catch (\Exception $e) { } catch (\Exception $e) {
$output->writeln("<error>".$e->getMessage()."</error>"); $output->writeln("<error>".$e->getMessage()."</error>");
if ($renamed) { if ($renamed) {
@@ -200,7 +221,6 @@ class RegenerateSqliteDb extends Command
$session = new LazaretSession(); $session = new LazaretSession();
$session->setUser($DI['user']); $session->setUser($DI['user']);
$em->persist($session); $em->persist($session);
$em->flush();
$file = File::buildFromPathfile($this->container['root.path'] . '/tests/files/cestlafete.jpg', $DI['coll'], $this->container); $file = File::buildFromPathfile($this->container['root.path'] . '/tests/files/cestlafete.jpg', $DI['coll'], $this->container);
@@ -248,8 +268,6 @@ class RegenerateSqliteDb extends Command
$em->persist($user2Deleted); $em->persist($user2Deleted);
$em->persist($user3Deleted); $em->persist($user3Deleted);
$em->persist($template); $em->persist($template);
$em->flush();
} }
protected function insertOneUser($login, $email = null, $admin = false) protected function insertOneUser($login, $email = null, $admin = false)
@@ -385,6 +403,8 @@ class RegenerateSqliteDb extends Command
$basket1->setName('test'); $basket1->setName('test');
$basket1->setDescription('description test'); $basket1->setDescription('description test');
$DI['basket_1'] = $basket1;
$element = new BasketElement(); $element = new BasketElement();
$element->setRecord($DI['record_1']); $element->setRecord($DI['record_1']);
$basket1->addElement($element); $basket1->addElement($element);
@@ -395,11 +415,15 @@ class RegenerateSqliteDb extends Command
$basket2->setName('test'); $basket2->setName('test');
$basket2->setDescription('description test'); $basket2->setDescription('description test');
$DI['basket_2'] = $basket2;
$basket3 = new Basket(); $basket3 = new Basket();
$basket3->setUser($this->getUserAlt1()); $basket3->setUser($this->getUserAlt1());
$basket3->setName('test'); $basket3->setName('test');
$basket3->setDescription('description test'); $basket3->setDescription('description test');
$DI['basket_3'] = $basket3;
$em->persist($basket1); $em->persist($basket1);
$em->persist($element); $em->persist($element);
$em->persist($basket2); $em->persist($basket2);
@@ -442,6 +466,8 @@ class RegenerateSqliteDb extends Command
$em->persist($validationParticipant); $em->persist($validationParticipant);
} }
$DI['basket_4'] = $basket4;
$em->persist($basket4); $em->persist($basket4);
} }
@@ -524,8 +550,12 @@ class RegenerateSqliteDb extends Command
$em->persist($feed); $em->persist($feed);
$em->persist($publisher); $em->persist($publisher);
$this->insertOneFeedEntry($em, $DI, $feed, true); $entry = $this->insertOneFeedEntry($em, $DI, $feed, true);
$this->insertOneFeedToken($em, $DI, $feed); $token = $this->insertOneFeedToken($em, $DI, $feed);
$DI['feed_public'] = $feed;
$DI['feed_public_entry'] = $entry;
$DI['feed_public_token'] = $token;
} }
private function insertOnePrivateFeed(EntityManager $em, \Pimple $DI) private function insertOnePrivateFeed(EntityManager $em, \Pimple $DI)
@@ -547,8 +577,12 @@ class RegenerateSqliteDb extends Command
$em->persist($feed); $em->persist($feed);
$em->persist($publisher); $em->persist($publisher);
$this->insertOneFeedEntry($em, $DI, $feed, false); $entry = $this->insertOneFeedEntry($em, $DI, $feed, false);
$this->insertOneFeedToken($em, $DI, $feed); $token = $this->insertOneFeedToken($em, $DI, $feed);
$DI['feed_private'] = $feed;
$DI['feed_private_entry'] = $entry;
$DI['feed_private_token'] = $token;
} }
private function insertOneExtraFeed(EntityManager $em, \Pimple $DI) private function insertOneExtraFeed(EntityManager $em, \Pimple $DI)
@@ -595,12 +629,14 @@ class RegenerateSqliteDb extends Command
$em->persist($feed); $em->persist($feed);
$this->insertOneFeedItem($em, $DI, $entry, $public); $this->insertOneFeedItem($em, $DI, $entry, $public);
return $entry;
} }
private function insertOneFeedToken(EntityManager $em, \Pimple $DI, Feed $feed) private function insertOneFeedToken(EntityManager $em, \Pimple $DI, Feed $feed)
{ {
$token = new FeedToken(); $token = new FeedToken();
$token->setValue($this->container['random.low']->generateString(64, \random::LETTERS_AND_NUMBERS)); $token->setValue($this->container['random.low']->generateString(64, TokenManipulator::LETTERS_AND_NUMBERS));
$token->setFeed($feed); $token->setFeed($feed);
$token->setUser($DI['user']); $token->setUser($DI['user']);
@@ -608,6 +644,8 @@ class RegenerateSqliteDb extends Command
$em->persist($token); $em->persist($token);
$em->persist($feed); $em->persist($feed);
return $token;
} }
private function insertOneAggregateToken(EntityManager $em, \Pimple $DI) private function insertOneAggregateToken(EntityManager $em, \Pimple $DI)
@@ -615,12 +653,61 @@ class RegenerateSqliteDb extends Command
$user = $DI['user']; $user = $DI['user'];
$token = new AggregateToken(); $token = new AggregateToken();
$token->setValue($this->container['random.low']->generateString(64, \random::LETTERS_AND_NUMBERS)); $token->setValue($this->container['random.low']->generateString(64, TokenManipulator::LETTERS_AND_NUMBERS));
$token->setUser($user); $token->setUser($user);
$em->persist($token); $em->persist($token);
} }
private function insertTwoTokens(EntityManager $em, \Pimple $DI)
{
$user = $DI['user'];
$token = new Token();
$token->setValue($this->container['random.low']->generateString(12, TokenManipulator::LETTERS_AND_NUMBERS));
$token->setUser($user);
$token->setType(TokenManipulator::TYPE_RSS);
$token->setData('some data');
$DI['token_1'] = $token;
$em->persist($token);
$token = new Token();
$token->setValue($this->container['random.low']->generateString(12, TokenManipulator::LETTERS_AND_NUMBERS));
$token->setUser($user);
$token->setType(TokenManipulator::TYPE_RSS);
$token->setData('some data');
$token->setExpiration(new \DateTime('+1 year'));
$DI['token_2'] = $token;
$em->persist($token);
}
private function insertOneInvalidToken(EntityManager $em, \Pimple $DI)
{
$user = $DI['user'];
$token = new Token();
$token->setValue($this->container['random.low']->generateString(12, TokenManipulator::LETTERS_AND_NUMBERS));
$token->setUser($user);
$token->setType(TokenManipulator::TYPE_RSS);
$token->setData('some data');
$token->setExpiration(new \DateTime('-1 day'));
$DI['token_invalid'] = $token;
$em->persist($token);
}
private function insertOneValidationToken(EntityManager $em, \Pimple $DI)
{
$user = $DI['user'];
$token = new Token();
$token->setValue($this->container['random.low']->generateString(12, TokenManipulator::LETTERS_AND_NUMBERS));
$token->setUser($user);
$token->setType(TokenManipulator::TYPE_VALIDATE);
$token->setData($DI['basket_1']->getId());
$DI['token_validation'] = $token;
$em->persist($token);
}
private function insertOneFeedItem(EntityManager $em, \Pimple $DI, FeedEntry $entry, $public) private function insertOneFeedItem(EntityManager $em, \Pimple $DI, FeedEntry $entry, $public)
{ {
if ($public) { if ($public) {
@@ -656,7 +743,6 @@ class RegenerateSqliteDb extends Command
$registration->setUpdated(new \DateTime($when)); $registration->setUpdated(new \DateTime($when));
$registration->setCreated(new \DateTime($when)); $registration->setCreated(new \DateTime($when));
$em->persist($registration); $em->persist($registration);
$em->flush();
$em->getEventManager()->addEventSubscriber(new TimestampableListener()); $em->getEventManager()->addEventSubscriber(new TimestampableListener());
$DI[$name] = $registration; $DI[$name] = $registration;

View File

@@ -15,6 +15,7 @@ use Alchemy\Phrasea\Model\Entities\Basket;
use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\BasketElement;
use Alchemy\Phrasea\Exception\SessionNotFound; use Alchemy\Phrasea\Exception\SessionNotFound;
use Alchemy\Phrasea\Controller\Exception as ControllerException; use Alchemy\Phrasea\Controller\Exception as ControllerException;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Silex\ControllerProviderInterface; use Silex\ControllerProviderInterface;
use Silex\Application as SilexApplication; use Silex\Application as SilexApplication;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -38,26 +39,21 @@ class Lightbox implements ControllerProviderInterface
$app['authentication']->closeAccount(); $app['authentication']->closeAccount();
} }
if (false === $usr_id = $app['authentication.token-validator']->isValid($request->query->get('LOG'))) { if (null === $token = $app['repo.tokens']->findValidToken($request->query->get('LOG'))) {
$app->addFlash('error', $app->trans('The URL you used is out of date, please login')); $app->addFlash('error', $app->trans('The URL you used is out of date, please login'));
return $app->redirectPath('homepage'); return $app->redirectPath('homepage');
} }
$app['authentication']->openAccount($app['repo.users']->find($usr_id)); $app['authentication']->openAccount($token->getUser());
try { switch ($token->getType()) {
$datas = $app['tokens']->helloToken($request->query->get('LOG')); case TokenManipulator::TYPE_FEED_ENTRY:
} catch (NotFoundHttpException $e) { return $app->redirectPath('lightbox_feed_entry', ['entry_id' => $token->getData()]);
return;
}
switch ($datas['type']) {
case \random::TYPE_FEED_ENTRY:
return $app->redirectPath('lightbox_feed_entry', ['entry_id' => $datas['datas']]);
break; break;
case \random::TYPE_VALIDATE: case TokenManipulator::TYPE_VALIDATE:
case \random::TYPE_VIEW: case TokenManipulator::TYPE_VIEW:
return $app->redirectPath('lightbox_validation', ['basket' => $datas['datas']]); return $app->redirectPath('lightbox_validation', ['basket' => $token->getData()]);
break; break;
} }
}); });
@@ -464,13 +460,8 @@ class Lightbox implements ControllerProviderInterface
/* @var $basket Basket */ /* @var $basket Basket */
$participant = $basket->getValidation()->getParticipant($app['authentication']->getUser()); $participant = $basket->getValidation()->getParticipant($app['authentication']->getUser());
$expires = new \DateTime('+10 days'); $token = $app['manipulator.token']->createBasketValidationToken($basket);
$url = $app->url('lightbox', ['LOG' => $app['tokens']->getUrlToken( $url = $app->url('lightbox', ['LOG' => $token->getValue()]);
\random::TYPE_VALIDATE
, $basket->getValidation()->getInitiator($app)->getId()
, $expires
, $basket->getId()
)]);
$to = $basket->getValidation()->getInitiator($app)->getId(); $to = $basket->getValidation()->getInitiator($app)->getId();
$params = [ $params = [

View File

@@ -12,6 +12,7 @@
namespace Alchemy\Phrasea\Controller\Prod; namespace Alchemy\Phrasea\Controller\Prod;
use Alchemy\Phrasea\Http\DeliverDataInterface; use Alchemy\Phrasea\Http\DeliverDataInterface;
use Alchemy\Phrasea\Model\Entities\Token;
use Silex\Application; use Silex\Application;
use Silex\ControllerProviderInterface; use Silex\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -32,16 +33,19 @@ class DoDownload implements ControllerProviderInterface
$controllers = $app['controllers_factory']; $controllers = $app['controllers_factory'];
$controllers->get('/{token}/prepare/', 'controller.prod.do-download:prepareDownload') $controllers->get('/{token}/prepare/', 'controller.prod.do-download:prepareDownload')
->before($app['middleware.token.converter'])
->bind('prepare_download') ->bind('prepare_download')
->assert('token', '[a-zA-Z0-9\.\/]{8,16}'); ->assert('token', '[a-zA-Z0-9]{8,32}');
$controllers->match('/{token}/get/', 'controller.prod.do-download:downloadDocuments') $controllers->match('/{token}/get/', 'controller.prod.do-download:downloadDocuments')
->before($app['middleware.token.converter'])
->bind('document_download') ->bind('document_download')
->assert('token', '[a-zA-Z0-9\.\/]{8,16}'); ->assert('token', '[a-zA-Z0-9]{8,32}');
$controllers->post('/{token}/execute/', 'controller.prod.do-download:downloadExecute') $controllers->post('/{token}/execute/', 'controller.prod.do-download:downloadExecute')
->before($app['middleware.token.converter'])
->bind('execute_download') ->bind('execute_download')
->assert('token', '[a-zA-Z0-9\.\/]{8,16}'); ->assert('token', '[a-zA-Z0-9]{8,32}');
return $controllers; return $controllers;
} }
@@ -51,15 +55,13 @@ class DoDownload implements ControllerProviderInterface
* *
* @param Application $app * @param Application $app
* @param Request $request * @param Request $request
* @param String $token * @param Token $token
* *
* @return Response * @return Response
*/ */
public function prepareDownload(Application $app, Request $request, $token) public function prepareDownload(Application $app, Request $request, Token $token)
{ {
$datas = $app['tokens']->helloToken($token); if (false === $list = @unserialize($token->getData())) {
if (false === $list = @unserialize((string) $datas['datas'])) {
$app->abort(500, 'Invalid datas'); $app->abort(500, 'Invalid datas');
} }
@@ -96,15 +98,13 @@ class DoDownload implements ControllerProviderInterface
* *
* @param Application $app * @param Application $app
* @param Request $request * @param Request $request
* @param String $token * @param Token $token
* *
* @return Response * @return Response
*/ */
public function downloadDocuments(Application $app, Request $request, $token) public function downloadDocuments(Application $app, Request $request, Token $token)
{ {
$datas = $app['tokens']->helloToken($token); if (false === $list = @unserialize($token->getData())) {
if (false === $list = @unserialize((string) $datas['datas'])) {
$app->abort(500, 'Invalid datas'); $app->abort(500, 'Invalid datas');
} }
@@ -118,7 +118,7 @@ class DoDownload implements ControllerProviderInterface
$mime = $subdef['mime']; $mime = $subdef['mime'];
$list['complete'] = true; $list['complete'] = true;
} else { } else {
$exportFile = $app['root.path'] . '/tmp/download/' . $datas['value'] . '.zip'; $exportFile = $app['root.path'] . '/tmp/download/' . $token->getValue() . '.zip';
$mime = 'application/zip'; $mime = 'application/zip';
} }
@@ -144,22 +144,13 @@ class DoDownload implements ControllerProviderInterface
* *
* @param Application $app * @param Application $app
* @param Request $request * @param Request $request
* @param String $token * @param Token $token
* *
* @return Response * @return Response
*/ */
public function downloadExecute(Application $app, Request $request, $token) public function downloadExecute(Application $app, Request $request, Token $token)
{ {
try { if (false === $list = @unserialize($token->getData())) {
$datas = $app['tokens']->helloToken($token);
} catch (NotFoundHttpException $e) {
return $app->json([
'success' => false,
'message' => 'Invalid token'
]);
}
if (false === $list = @unserialize((string) $datas['datas'])) {
return $app->json([ return $app->json([
'success' => false, 'success' => false,
'message' => 'Invalid datas' 'message' => 'Invalid datas'
@@ -175,7 +166,7 @@ class DoDownload implements ControllerProviderInterface
$app, $app,
$token, $token,
$list, $list,
sprintf($app['root.path'] . '/tmp/download/%s.zip', $datas['value']) // Dest file sprintf($app['root.path'] . '/tmp/download/%s.zip', $token->getValue()) // Dest file
); );
return $app->json([ return $app->json([

View File

@@ -64,16 +64,7 @@ class Download implements ControllerProviderInterface
$list['export_name'] = sprintf('%s.zip', $download->getExportName()); $list['export_name'] = sprintf('%s.zip', $download->getExportName());
$token = $app['tokens']->getUrlToken( $token = $app['manipulator.token']->createDownloadToken($app['authentication']->getUser(), serialize($list));
\random::TYPE_DOWNLOAD,
$app['authentication']->getUser()->getId(),
new \DateTime('+3 hours'), // Token lifetime
serialize($list)
);
if (!$token) {
throw new \RuntimeException('Download token could not be generated');
}
$app['events-manager']->trigger('__DOWNLOAD__', [ $app['events-manager']->trigger('__DOWNLOAD__', [
'lst' => $lst, 'lst' => $lst,
@@ -83,6 +74,6 @@ class Download implements ControllerProviderInterface
'export_file' => $download->getExportName() 'export_file' => $download->getExportName()
]); ]);
return $app->redirectPath('prepare_download', ['token' => $token]); return $app->redirectPath('prepare_download', ['token' => $token->getValue()]);
} }
} }

View File

@@ -215,22 +215,20 @@ class Export implements ControllerProviderInterface
} }
} }
//generate validation token $token = $app['manipulator.token']->createEmailExportToken(serialize($list));
$endDateObject = new \DateTime('+1 day');
$token = $app['tokens']->getUrlToken(\random::TYPE_EMAIL, false, $endDateObject, serialize($list));
if (count($destMails) > 0 && $token) { if (count($destMails) > 0) {
//zip documents //zip documents
\set_export::build_zip( \set_export::build_zip(
$app, $app,
$token, $token,
$list, $list,
$app['root.path'] . '/tmp/download/' . $token . '.zip' $app['root.path'] . '/tmp/download/' . $token->getValue() . '.zip'
); );
$remaingEmails = $destMails; $remaingEmails = $destMails;
$url = $app->url('prepare_download', ['token' => $token, 'anonymous']); $url = $app->url('prepare_download', ['token' => $token->getValue(), 'anonymous']);
$emitter = new Emitter($app['authentication']->getUser()->getDisplayName(), $app['authentication']->getUser()->getEmail()); $emitter = new Emitter($app['authentication']->getUser()->getDisplayName(), $app['authentication']->getUser()->getEmail());
@@ -243,7 +241,7 @@ class Export implements ControllerProviderInterface
$mail = MailRecordsExport::create($app, $receiver, $emitter, $request->request->get('textmail')); $mail = MailRecordsExport::create($app, $receiver, $emitter, $request->request->get('textmail'));
$mail->setButtonUrl($url); $mail->setButtonUrl($url);
$mail->setExpiration($endDateObject); $mail->setExpiration($token->getExpiration());
$app['notification.deliverer']->deliver($mail); $app['notification.deliverer']->deliver($mail);
unset($remaingEmails[$key]); unset($remaingEmails[$key]);
@@ -261,16 +259,6 @@ class Export implements ControllerProviderInterface
]); ]);
} }
} }
} elseif (!$token && count($destMails) > 0) { //couldn't generate token
foreach ($destMails as $mail) {
$app['events-manager']->trigger('__EXPORT_MAIL_FAIL__', [
'usr_id' => $app['authentication']->getUser()->getId(),
'lst' => $lst,
'ssttid' => $ssttid,
'dest' => $mail,
'reason' => 0
]);
}
} }
return $app->json([ return $app->json([

View File

@@ -219,12 +219,7 @@ class Push implements ControllerProviderInterface
$url = $app->url('lightbox_compare', [ $url = $app->url('lightbox_compare', [
'basket' => $Basket->getId(), 'basket' => $Basket->getId(),
'LOG' => $app['tokens']->getUrlToken( 'LOG' => $app['manipulator.token']->createBasketAccessToken($Basket, $user_receiver),
\random::TYPE_VIEW,
$user_receiver->getId(),
null,
$Basket->getId()
)
]); ]);
$receipt = $request->get('recept') ? $app['authentication']->getUser()->getEmail() : ''; $receipt = $request->get('recept') ? $app['authentication']->getUser()->getEmail() : '';
@@ -414,12 +409,7 @@ class Push implements ControllerProviderInterface
$url = $app->url('lightbox_validation', [ $url = $app->url('lightbox_validation', [
'basket' => $Basket->getId(), 'basket' => $Basket->getId(),
'LOG' => $app['tokens']->getUrlToken( 'LOG' => $app['manipulator.token']->createBasketValidationToken($Basket, $participant_user),
\random::TYPE_VALIDATE,
$participant_user->getId(),
null,
$Basket->getId()
)
]); ]);
$receipt = $request->get('recept') ? $app['authentication']->getUser()->getEmail() : ''; $receipt = $request->get('recept') ? $app['authentication']->getUser()->getEmail() : '';

View File

@@ -138,9 +138,8 @@ class Account implements ControllerProviderInterface
return $app->redirectPath('account_reset_email'); return $app->redirectPath('account_reset_email');
} }
$date = new \DateTime('1 day'); $token = $app['manipulator.token']->createResetEmailToken($app['authentication']->getUser(), $email);
$token = $app['tokens']->getUrlToken(\random::TYPE_EMAIL, $app['authentication']->getUser()->getId(), $date, $app['authentication']->getUser()->getEmail()); $url = $app->url('account_reset_email', ['token' => $token->getValue()]);
$url = $app->url('account_reset_email', ['token' => $token]);
try { try {
$receiver = Receiver::fromUser($app['authentication']->getUser()); $receiver = Receiver::fromUser($app['authentication']->getUser());
@@ -152,7 +151,7 @@ class Account implements ControllerProviderInterface
$mail = MailRequestEmailUpdate::create($app, $receiver, null); $mail = MailRequestEmailUpdate::create($app, $receiver, null);
$mail->setButtonUrl($url); $mail->setButtonUrl($url);
$mail->setExpiration($date); $mail->setExpiration($token->getExpiration());
$app['notification.deliverer']->deliver($mail); $app['notification.deliverer']->deliver($mail);
@@ -170,21 +169,20 @@ class Account implements ControllerProviderInterface
*/ */
public function displayResetEmailForm(Application $app, Request $request) public function displayResetEmailForm(Application $app, Request $request)
{ {
if (null !== $token = $request->query->get('token')) { if (null !== $tokenValue = $request->query->get('token')) {
try { if (null === $token = $app['repo.tokens']->findValidToken($tokenValue)) {
$datas = $app['tokens']->helloToken($token);
$user = $app['repo.users']->find((int) $datas['usr_id']);
$user->setEmail($datas['datas']);
$app['tokens']->removeToken($token);
$app->addFlash('success', $app->trans('admin::compte-utilisateur: L\'email a correctement ete mis a jour'));
return $app->redirectPath('account');
} catch (\Exception $e) {
$app->addFlash('error', $app->trans('admin::compte-utilisateur: erreur lors de la mise a jour')); $app->addFlash('error', $app->trans('admin::compte-utilisateur: erreur lors de la mise a jour'));
return $app->redirectPath('account'); return $app->redirectPath('account');
} }
$user = $token->getUser();
$user->setEmail($token->getData());
$app['manipulator.token']->delete($token);
$app->addFlash('success', $app->trans('admin::compte-utilisateur: L\'email a correctement ete mis a jour'));
return $app->redirectPath('account');
} }
return $app['twig']->render('account/reset-email.html.twig', Login::getDefaultTemplateVariables($app)); return $app['twig']->render('account/reset-email.html.twig', Login::getDefaultTemplateVariables($app));

View File

@@ -505,12 +505,11 @@ class Login implements ControllerProviderInterface
{ {
$receiver = Receiver::fromUser($user); $receiver = Receiver::fromUser($user);
$expire = new \DateTime('+3 days'); $token = $app['manipulator.token']->createAccountUnlockToken($user);
$token = $app['tokens']->getUrlToken(\random::TYPE_PASSWORD, $user->getId(), $expire, $user->getEmail());
$mail = MailRequestEmailConfirmation::create($app, $receiver); $mail = MailRequestEmailConfirmation::create($app, $receiver);
$mail->setButtonUrl($app->url('login_register_confirm', ['code' => $token])); $mail->setButtonUrl($app->url('login_register_confirm', ['code' => $token->getValue()]));
$mail->setExpiration($expire); $mail->setExpiration($token->getExpiration());
$app['notification.deliverer']->deliver($mail); $app['notification.deliverer']->deliver($mail);
} }
@@ -530,19 +529,13 @@ class Login implements ControllerProviderInterface
return $app->redirectPath('homepage'); return $app->redirectPath('homepage');
} }
try { if (null === $token = $app['repo.tokens']->findValidToken($code)) {
$datas = $app['tokens']->helloToken($code);
} catch (NotFoundHttpException $e) {
$app->addFlash('error', $app->trans('Invalid unlock link.')); $app->addFlash('error', $app->trans('Invalid unlock link.'));
return $app->redirectPath('homepage'); return $app->redirectPath('homepage');
} }
if (null === $user = $app['repo.users']->find((int) $datas['usr_id'])) { $user = $token->getUser();
$app->addFlash('error', _('Invalid unlock link.'));
return $app->redirectPath('homepage');
}
if (!$user->isMailLocked()) { if (!$user->isMailLocked()) {
$app->addFlash('info', $app->trans('Account is already unlocked, you can login.')); $app->addFlash('info', $app->trans('Account is already unlocked, you can login.'));
@@ -550,7 +543,7 @@ class Login implements ControllerProviderInterface
return $app->redirectPath('homepage'); return $app->redirectPath('homepage');
} }
$app['tokens']->removeToken($code); $app['manipulator.token']->delete($token);
$user->setMailLocked(false); $user->setMailLocked(false);
try { try {
@@ -561,7 +554,7 @@ class Login implements ControllerProviderInterface
return $app->redirectPath('homepage'); return $app->redirectPath('homepage');
} }
$app['tokens']->removeToken($code); $app['manipulator.token']->delete($token);
if (count($app['acl']->get($user)->get_granted_base()) > 0) { if (count($app['acl']->get($user)->get_granted_base()) > 0) {
$mail = MailSuccessEmailConfirmationRegistered::create($app, $receiver); $mail = MailSuccessEmailConfirmationRegistered::create($app, $receiver);
@@ -580,38 +573,26 @@ class Login implements ControllerProviderInterface
public function renewPassword(PhraseaApplication $app, Request $request) public function renewPassword(PhraseaApplication $app, Request $request)
{ {
if (null === $token = $request->get('token')) { if (null === $tokenValue = $request->get('token')) {
$app->abort(401, 'A token is required'); $app->abort(401, 'A token is required');
} }
try { if (null === $token = $app['repo.tokens']->findValidToken($tokenValue)) {
$app['tokens']->helloToken($token);
} catch (\Exception $e) {
$app->abort(401, 'A token is required'); $app->abort(401, 'A token is required');
} }
$form = $app->form(new PhraseaRecoverPasswordForm($app['tokens'])); $form = $app->form(new PhraseaRecoverPasswordForm($app['repo.tokens']));
$form->setData(['token' => $token]); $form->setData(['token' => $token->getValue()]);
if ('POST' === $request->getMethod()) { if ('POST' === $request->getMethod()) {
$form->bind($request); $form->bind($request);
try { if ($form->isValid()) {
if ($form->isValid()) { $data = $form->getData();
$data = $form->getData(); $app['manipulator.user']->setPassword($token->getUser(), $data['password']);
$app['manipulator.token']->delete($token);
$app->addFlash('success', $app->trans('login::notification: Mise a jour du mot de passe avec succes'));
$datas = $app['tokens']->helloToken($token); return $app->redirectPath('homepage');
$user = $app['repo.users']->find($datas['usr_id']);
$app['manipulator.user']->setPassword($user, $data['password']);
$app['tokens']->removeToken($token);
$app->addFlash('success', $app->trans('login::notification: Mise a jour du mot de passe avec succes'));
return $app->redirectPath('homepage');
}
} catch (FormProcessingException $e) {
$app->addFlash('error', $e->getMessage());
} }
} }
@@ -649,13 +630,9 @@ class Login implements ControllerProviderInterface
throw new FormProcessingException($app->trans('Invalid email address')); throw new FormProcessingException($app->trans('Invalid email address'));
} }
$token = $app['tokens']->getUrlToken(\random::TYPE_PASSWORD, $user->getId(), new \DateTime('+1 day')); $token = $app['manipulator.token']->createResetPasswordToken($user);
if (!$token) { $url = $app->url('login_renew_password', ['token' => $token->getValue()], true);
return $app->abort(500, 'Unable to generate a token');
}
$url = $app->url('login_renew_password', ['token' => $token], true);
$mail = MailRequestPasswordUpdate::create($app, $receiver); $mail = MailRequestPasswordUpdate::create($app, $receiver);
$mail->setLogin($user->getLogin()); $mail->setLogin($user->getLogin());
@@ -837,20 +814,18 @@ class Login implements ControllerProviderInterface
$validationSession = $participant->getSession(); $validationSession = $participant->getSession();
$participantId = $participant->getUser()->getId(); $participantId = $participant->getUser()->getId();
$basketId = $validationSession->getBasket()->getId(); $basket = $validationSession->getBasket();
try { if (null === $token = $app['repo.tokens']->findValidationToken($basket, $participant->getUser())) {
$token = $app['tokens']->getValidationToken($participantId, $basketId);
} catch (NotFoundHttpException $e) {
continue; continue;
} }
$app['events-manager']->trigger('__VALIDATION_REMINDER__', [ $app['events-manager']->trigger('__VALIDATION_REMINDER__', [
'to' => $participantId, 'to' => $participantId,
'ssel_id' => $basketId, 'ssel_id' => $basket->getId(),
'from' => $validationSession->getInitiator()->getId(), 'from' => $validationSession->getInitiator()->getId(),
'validate_id' => $validationSession->getId(), 'validate_id' => $validationSession->getId(),
'url' => $app->url('lightbox_validation', ['basket' => $basketId, 'LOG' => $token]), 'url' => $app->url('lightbox_validation', ['basket' => $basket->getId(), 'LOG' => $token->getValue()]),
]); ]);
$participant->setReminded(new \DateTime('now')); $participant->setReminded(new \DateTime('now'));

View File

@@ -25,6 +25,7 @@ class JsonRequestSubscriber implements EventSubscriberInterface
if ((0 !== strpos($request->getPathInfo(), '/admin/') if ((0 !== strpos($request->getPathInfo(), '/admin/')
|| 0 === strpos($request->getPathInfo(), '/admin/collection/') || 0 === strpos($request->getPathInfo(), '/admin/collection/')
|| preg_match('/^\/download\/[a-zA-Z0-9]+\/execute\/$/', $request->getPathInfo())
|| 0 === strpos($request->getPathInfo(), '/admin/databox/')) || 0 === strpos($request->getPathInfo(), '/admin/databox/'))
&& $request->getRequestFormat() == 'json') { && $request->getRequestFormat() == 'json') {
$datas = [ $datas = [

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Middleware;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Symfony\Component\HttpFoundation\Request;
class TokenMiddlewareProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$app['middleware.token.converter'] = $app->protect(function (Request $request, Application $app) {
if ($request->attributes->has('token')) {
$request->attributes->set('token', $app['converter.token']->convert($request->attributes->get('token')));
}
});
}
public function boot(Application $app)
{
}
}

View File

@@ -36,10 +36,6 @@ class AuthenticationManagerServiceProvider implements ServiceProviderInterface
return new Authenticator($app, $app['browser'], $app['session'], $app['EM']); return new Authenticator($app, $app['browser'], $app['session'], $app['EM']);
}); });
$app['authentication.token-validator'] = $app->share(function (Application $app) {
return new TokenValidator($app['tokens']);
});
$app['authentication.persistent-manager'] = $app->share(function (Application $app) { $app['authentication.persistent-manager'] = $app->share(function (Application $app) {
return new CookieManager($app['auth.password-encoder'], $app['repo.sessions'], $app['browser']); return new CookieManager($app['auth.password-encoder'], $app['repo.sessions'], $app['browser']);
}); });

View File

@@ -15,6 +15,7 @@ use Alchemy\Phrasea\Setup\ConfigurationTester;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\PreSchemaUpgradeCollection; use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\PreSchemaUpgradeCollection;
use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Feeds; use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Feeds;
use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Tokens;
use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Users; use Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade\Upgrade39Users;
use Silex\Application as SilexApplication; use Silex\Application as SilexApplication;
use Silex\ServiceProviderInterface; use Silex\ServiceProviderInterface;
@@ -29,7 +30,7 @@ class ConfigurationTesterServiceProvider implements ServiceProviderInterface
}); });
$app['phraseanet.pre-schema-upgrader.upgrades'] = $app->share(function () { $app['phraseanet.pre-schema-upgrader.upgrades'] = $app->share(function () {
return [new Upgrade39Feeds(), new Upgrade39Users()]; return [new Upgrade39Feeds(), new Upgrade39Users(), new Upgrade39Tokens()];
}); });
$app['phraseanet.pre-schema-upgrader'] = $app->share(function (Application $app) { $app['phraseanet.pre-schema-upgrader'] = $app->share(function (Application $app) {

View File

@@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Core\Provider;
use Alchemy\Phrasea\Model\Converter\BasketConverter; use Alchemy\Phrasea\Model\Converter\BasketConverter;
use Alchemy\Phrasea\Model\Converter\TaskConverter; use Alchemy\Phrasea\Model\Converter\TaskConverter;
use Alchemy\Phrasea\Model\Converter\TokenConverter;
use Silex\Application; use Silex\Application;
use Silex\ServiceProviderInterface; use Silex\ServiceProviderInterface;
@@ -27,6 +28,10 @@ class ConvertersServiceProvider implements ServiceProviderInterface
$app['converter.basket'] = $app->share(function ($app) { $app['converter.basket'] = $app->share(function ($app) {
return new BasketConverter($app['EM']); return new BasketConverter($app['EM']);
}); });
$app['converter.token'] = $app->share(function ($app) {
return new TokenConverter($app['repo.tokens']);
});
} }
public function boot(Application $app) public function boot(Application $app)

View File

@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\Core\Provider;
use Alchemy\Phrasea\Model\Manipulator\ACLManipulator; use Alchemy\Phrasea\Model\Manipulator\ACLManipulator;
use Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator; use Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator;
use Alchemy\Phrasea\Model\Manipulator\TaskManipulator; use Alchemy\Phrasea\Model\Manipulator\TaskManipulator;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Alchemy\Phrasea\Model\Manipulator\UserManipulator; use Alchemy\Phrasea\Model\Manipulator\UserManipulator;
use Alchemy\Phrasea\Model\Manager\UserManager; use Alchemy\Phrasea\Model\Manager\UserManager;
use Silex\Application as SilexApplication; use Silex\Application as SilexApplication;
@@ -31,6 +32,10 @@ class ManipulatorServiceProvider implements ServiceProviderInterface
return new UserManipulator($app['model.user-manager'], $app['auth.password-encoder'], $app['geonames.connector'], $app['repo.users'], $app['random.low']); return new UserManipulator($app['model.user-manager'], $app['auth.password-encoder'], $app['geonames.connector'], $app['repo.users'], $app['random.low']);
}); });
$app['manipulator.token'] = $app->share(function ($app) {
return new TokenManipulator($app['EM'], $app['random.medium'], $app['repo.tokens']);
});
$app['manipulator.acl'] = $app->share(function ($app) { $app['manipulator.acl'] = $app->share(function ($app) {
return new ACLManipulator($app['acl'], $app['phraseanet.appbox']); return new ACLManipulator($app['acl'], $app['phraseanet.appbox']);
}); });

View File

@@ -91,6 +91,9 @@ class RepositoriesServiceProvider implements ServiceProviderInterface
$app['repo.user-queries'] = $app->share(function (PhraseaApplication $app) { $app['repo.user-queries'] = $app->share(function (PhraseaApplication $app) {
return $app['EM']->getRepository('Phraseanet:UserQuery'); return $app['EM']->getRepository('Phraseanet:UserQuery');
}); });
$app['repo.tokens'] = $app->share(function ($app) {
return $app['EM']->getRepository('Phraseanet:Token');
});
} }
public function boot(Application $app) public function boot(Application $app)

View File

@@ -16,6 +16,7 @@ use Alchemy\Phrasea\Feed\Aggregate;
use Alchemy\Phrasea\Feed\FeedInterface; use Alchemy\Phrasea\Feed\FeedInterface;
use Alchemy\Phrasea\Model\Entities\AggregateToken; use Alchemy\Phrasea\Model\Entities\AggregateToken;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use RandomLib\Generator; use RandomLib\Generator;
use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Routing\Generator\UrlGenerator;
@@ -141,7 +142,7 @@ class AggregateLinkGenerator implements LinkGeneratorInterface
$token->setUser($user); $token->setUser($user);
} }
$token->setValue($this->random->generateString(64, \random::LETTERS_AND_NUMBERS)); $token->setValue($this->random->generateString(64, TokenManipulator::LETTERS_AND_NUMBERS));
$this->em->persist($token); $this->em->persist($token);
$this->em->flush(); $this->em->flush();
} }

View File

@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\Feed\Link;
use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Feed\FeedInterface; use Alchemy\Phrasea\Feed\FeedInterface;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Alchemy\Phrasea\Model\Entities\Feed; use Alchemy\Phrasea\Model\Entities\Feed;
use Alchemy\Phrasea\Model\Entities\FeedToken; use Alchemy\Phrasea\Model\Entities\FeedToken;
@@ -153,7 +154,7 @@ class FeedLinkGenerator implements LinkGeneratorInterface
$this->em->persist($feed); $this->em->persist($feed);
} }
$token->setValue($this->random->generateString(64, \random::LETTERS_AND_NUMBERS)); $token->setValue($this->random->generateString(64, TokenManipulator::LETTERS_AND_NUMBERS));
$this->em->persist($token); $this->em->persist($token);
$this->em->flush(); $this->em->flush();
} }

View File

@@ -12,33 +12,33 @@
namespace Alchemy\Phrasea\Form\Constraint; namespace Alchemy\Phrasea\Form\Constraint;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class PasswordToken extends Constraint class PasswordToken extends Constraint
{ {
public $message = 'The token provided is not valid anymore'; public $message = 'The token provided is not valid anymore';
private $random; private $repository;
public function __construct(\random $random) public function __construct(EntityRepository $repository)
{ {
$this->random = $random; $this->repository = $repository;
parent::__construct(); parent::__construct();
} }
public function isValid($token) public function isValid($tokenValue)
{ {
try { if (null === $token = $this->repository->findValidToken($tokenValue)) {
$data = $this->random->helloToken($token);
} catch (NotFoundHttpException $e) {
return false; return false;
} }
return \random::TYPE_PASSWORD === $data['type']; return TokenManipulator::TYPE_PASSWORD === $token->getType();
} }
public static function create(Application $app) public static function create(Application $app)
{ {
return new static($app['tokens']); return new static($app['repo.tokens']);
} }
} }

View File

@@ -12,6 +12,7 @@
namespace Alchemy\Phrasea\Form\Login; namespace Alchemy\Phrasea\Form\Login;
use Alchemy\Phrasea\Form\Constraint\PasswordToken; use Alchemy\Phrasea\Form\Constraint\PasswordToken;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@@ -21,11 +22,11 @@ use Symfony\Component\Validator\Constraints as Assert;
*/ */
class PhraseaRecoverPasswordForm extends AbstractType class PhraseaRecoverPasswordForm extends AbstractType
{ {
private $tokens; private $repository;
public function __construct(\random $tokens) public function __construct(EntityRepository $repository)
{ {
$this->tokens = $tokens; $this->repository = $repository;
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
@@ -33,7 +34,7 @@ class PhraseaRecoverPasswordForm extends AbstractType
$builder->add('token', 'hidden', [ $builder->add('token', 'hidden', [
'required' => true, 'required' => true,
'constraints' => [ 'constraints' => [
new PasswordToken($this->tokens) new PasswordToken($this->repository)
] ]
]); ]);

View File

@@ -159,28 +159,22 @@ class Manage extends Helper
} }
if ($sendCredentials) { if ($sendCredentials && $receiver) {
$urlToken = $this->app['tokens']->getUrlToken(\random::TYPE_PASSWORD, $createdUser->getId()); $urlToken = $this->app['manipulator.token']->createResetPasswordToken($createdUser);
$url = $this->app->url('login_renew_password', ['token' => $urlToken->getValue()]);
if ($receiver && false !== $urlToken) { $mail = MailRequestPasswordSetup::create($this->app, $receiver, null, '', $url);
$url = $this->app->url('login_renew_password', ['token' => $urlToken]); $mail->setLogin($createdUser->getLogin());
$mail = MailRequestPasswordSetup::create($this->app, $receiver, null, '', $url); $this->app['notification.deliverer']->deliver($mail);
$mail->setLogin($createdUser->getLogin());
$this->app['notification.deliverer']->deliver($mail);
}
} }
if ($validateMail) { if ($validateMail && $receiver) {
$createdUser->setMailLocked(true); $createdUser->setMailLocked(true);
if ($receiver) { $token = $this->app['manipulator.token']->createAccountUnlockToken($createdUser);
$expire = new \DateTime('+3 days'); $url = $this->app->url('login_register_confirm', ['code' => $token]);
$token = $this->app['tokens']->getUrlToken(\random::TYPE_PASSWORD, $createdUser->getId(), $expire, $createdUser->getEmail());
$url = $this->app->url('login_register_confirm', ['code' => $token]);
$mail = MailRequestEmailConfirmation::create($this->app, $receiver, null, '', $url, $expire); $mail = MailRequestEmailConfirmation::create($this->app, $receiver, null, '', $url, $token->getExpiration());
$this->app['notification.deliverer']->deliver($mail); $this->app['notification.deliverer']->deliver($mail);
}
} }
} }

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Model\Converter;
use Alchemy\Phrasea\Model\Entities\Token;
use Alchemy\Phrasea\Model\Repositories\TokenRepository;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class TokenConverter implements ConverterInterface
{
private $repository;
public function __construct(TokenRepository $repository)
{
$this->repository = $repository;
}
/**
* {@inheritdoc}
*
* @return Token
*/
public function convert($value)
{
if (null === $token = $this->repository->findValidToken($value)) {
throw new NotFoundHttpException('Token is not valid.');
}
return $token;
}
}

View File

@@ -0,0 +1,222 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Model\Entities;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ORM\Table(name="Tokens")
* @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\TokenRepository")
*/
class Token
{
/**
* @ORM\Id
* @ORM\Column(type="string", length=16)
*/
private $value;
/**
* @ORM\ManyToOne(targetEntity="User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
**/
private $user;
/**
* @ORM\Column(type="string", length=32)
*/
private $type;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $data;
/**
* @Gedmo\Timestampable(on="create")
* @ORM\Column(type="datetime")
*/
private $created;
/**
* @Gedmo\Timestampable(on="update")
* @ORM\Column(type="datetime")
*/
private $updated;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $expiration;
/**
* Set value
*
* @param string $value
* @return Token
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Get value
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Set type
*
* @param string $type
* @return Token
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get type
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Set data
*
* @param string $data
* @return Token
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Get data
*
* @return string
*/
public function getData()
{
return $this->data;
}
/**
* Set created
*
* @param \DateTime $created
* @return Token
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* @return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Set updated
*
* @param \DateTime $updated
* @return Token
*/
public function setUpdated($updated)
{
$this->updated = $updated;
return $this;
}
/**
* Get updated
*
* @return \DateTime
*/
public function getUpdated()
{
return $this->updated;
}
/**
* Set expiration
*
* @param \DateTime $updated
* @return Token
*/
public function setExpiration(\DateTime $expiration = null)
{
$this->expiration = $expiration;
return $this;
}
/**
* Get expiration
*
* @return \DateTime
*/
public function getExpiration()
{
return $this->expiration;
}
/**
* Set user
*
* @param User $user
* @return Token
*/
public function setUser(User $user = null)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* @return User
*/
public function getUser()
{
return $this->user;
}
}

View File

@@ -101,6 +101,16 @@ class UserManager
$user->getSettings()->clear(); $user->getSettings()->clear();
} }
private function cleanTokens(User $user)
{
$elements = $this->objectManager->getRepository('Phraseanet:Token')
->findBy(['user' => $user]);
foreach ($elements as $element) {
$this->objectManager->remove($element);
}
}
/** /**
* Removes user queries. * Removes user queries.
* *
@@ -194,16 +204,13 @@ class UserManager
*/ */
private function cleanProperties(User $user) private function cleanProperties(User $user)
{ {
foreach ([ $sql = 'DELETE FROM `edit_presets` WHERE usr_id = :usr_id';
'DELETE FROM `edit_presets` WHERE usr_id = :usr_id', $stmt = $this->appboxConnection->prepare($sql);
'DELETE FROM `tokens` WHERE usr_id = :usr_id', $stmt->execute([':usr_id' => $user->getId()]);
] as $sql) { $stmt->closeCursor();
$stmt = $this->appboxConnection->prepare($sql);
$stmt->execute([':usr_id' => $user->getId()]);
$stmt->closeCursor();
}
$this->cleanSettings($user); $this->cleanSettings($user);
$this->cleanTokens($user);
$this->cleanQueries($user); $this->cleanQueries($user);
$this->cleanFtpCredentials($user); $this->cleanFtpCredentials($user);
$this->cleanOrders($user); $this->cleanOrders($user);

View File

@@ -0,0 +1,219 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Model\Manipulator;
use Alchemy\Phrasea\Model\Entities\Basket;
use Alchemy\Phrasea\Model\Entities\FeedEntry;
use Alchemy\Phrasea\Model\Entities\Token;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Repositories\TokenRepository;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\Query;
use RandomLib\Generator;
class TokenManipulator implements ManipulatorInterface
{
const LETTERS_AND_NUMBERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const TYPE_FEED_ENTRY = 'FEED_ENTRY';
const TYPE_PASSWORD = 'password';
const TYPE_ACCOUNT_UNLOCK = 'account-unlock';
const TYPE_DOWNLOAD = 'download';
const TYPE_MAIL_DOWNLOAD = 'mail-download';
const TYPE_EMAIL = 'email';
const TYPE_EMAIL_RESET = 'email-reset';
const TYPE_VIEW = 'view';
const TYPE_VALIDATE = 'validate';
const TYPE_RSS = 'rss';
/** @var Objectmanager */
private $om;
private $random;
private $repository;
public function __construct(ObjectManager $om, Generator $random, TokenRepository $repository)
{
$this->om = $om;
$this->random = $random;
$this->repository = $repository;
}
/**
* @param User|null $user
* @param string $type
* @param \DateTime|null $expiration
* @param mixed|null $data
*
* @return Token
*/
public function create(User $user = null, $type, \DateTime $expiration = null, $data = null)
{
$this->removeExpiredTokens();
$n = 0;
do {
if ($n++ > 1024) {
throw new \RuntimeException('Unable to create a token.');
}
$value = $this->random->generateString(32, self::LETTERS_AND_NUMBERS);
$found = null !== $this->om->getRepository('Phraseanet:Token')->find($value);
} while ($found);
$token = new Token();
$token->setUser($user)
->setType($type)
->setValue($value)
->setExpiration($expiration)
->setData($data);
$this->om->persist($token);
$this->om->flush();
return $token;
}
/**
* @param Basket $basket
* @param User $user
*
* @return Token
*/
public function createBasketValidationToken(Basket $basket, User $user = null)
{
if (null === $basket->getValidation()) {
throw new \InvalidArgumentException('A validation token requires a validation basket.');
}
return $this->create($user ?: $basket->getValidation()->getInitiator(), self::TYPE_VALIDATE, new \DateTime('+10 days'), $basket->getId());
}
/**
* @param Basket $basket
* @param User $user
*
* @return Token
*/
public function createBasketAccessToken(Basket $basket, User $user)
{
return $this->create($user, self::TYPE_VIEW, null, $basket->getId());
}
/**
* @param User $user
* @param FeedEntry $entry
*
* @return Token
*/
public function createFeedEntryToken(User $user, FeedEntry $entry)
{
return $this->create($user, self::TYPE_FEED_ENTRY, null, $entry->getId());
}
/**
* @param User $user
* @param $data
*
* @return Token
*/
public function createDownloadToken(User $user, $data)
{
return $this->create($user, self::TYPE_DOWNLOAD, new \DateTime('+3 hours'), $data);
}
/**
* @param $data
*
* @return Token
*/
public function createEmailExportToken($data)
{
return $this->create(null, self::TYPE_EMAIL, new \DateTime('+1 day'), $data);
}
/**
* @param User $user
* @param $email
*
* @return Token
*/
public function createResetEmailToken(User $user, $email)
{
return $this->create($user, self::TYPE_EMAIL_RESET, new \DateTime('+1 day'), $email);
}
/**
* @param User $user
*
* @return Token
*/
public function createAccountUnlockToken(User $user)
{
return $this->create($user, self::TYPE_ACCOUNT_UNLOCK, new \DateTime('+3 days'));
}
/**
* @param User $user
*
* @return Token
*/
public function createResetPasswordToken(User $user)
{
return $this->create($user, self::TYPE_PASSWORD, new \DateTime('+1 day'));
}
/**
* Updates a token.
*
* @param Token $token
*
* @return Token
*/
public function update(Token $token)
{
$this->om->persist($token);
$this->om->flush();
return $token;
}
/**
* Removes a token.
*
* @param Token $token
*/
public function delete(Token $token)
{
$this->om->remove($token);
$this->om->flush();
}
/**
* Removes expired tokens
*/
public function removeExpiredTokens()
{
foreach ($this->repository->findExpiredTokens() as $token) {
switch ($token->getType()) {
case 'download':
case 'email':
$file = $this->app['root.path'] . '/tmp/download/' . $token->getValue() . '.zip';
if (is_file($file)) {
unlink($file);
}
break;
}
$this->om->remove($token);
}
$this->om->flush();
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Alchemy\Phrasea\Model\Repositories;
use Alchemy\Phrasea\Model\Entities\Basket;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\ValidationParticipant;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Doctrine\ORM\EntityRepository;
/**
* TokenRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class TokenRepository extends EntityRepository
{
public function findValidationToken(Basket $basket, User $user)
{
$dql = 'SELECT t FROM Phraseanet:Token t
WHERE t.type = :type
AND t.user = :user
AND t.data = :basket_id
AND (t.expiration > CURRENT_TIMESTAMP() OR t.expiration IS NULL)';
$query = $this->_em->createQuery($dql);
$query->setParameters([
':type' => TokenManipulator::TYPE_VALIDATE,
':user' => $user,
':basket_id' => $basket->getId(),
]);
return $query->getOneOrNullResult();
}
public function findValidToken($value)
{
$dql = 'SELECT t FROM Phraseanet:Token t
WHERE t.value = :value
AND (t.expiration IS NULL OR t.expiration >= CURRENT_TIMESTAMP())';
$query = $this->_em->createQuery($dql);
$query->setParameters([':value' => $value]);
return $query->getOneOrNullResult();
}
public function findExpiredTokens()
{
$dql = 'SELECT t FROM Phraseanet:Token t
WHERE t.expiration < :date';
$query = $this->_em->createQuery($dql);
$query->setParameters([':date' => new \DateTime()]);
return $query->getResult();
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Setup\DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
class TokenMigration extends AbstractMigration
{
public function doUpSql(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql", "Migration can only be executed safely on 'mysql'.");
$this->addSql("CREATE TABLE Tokens (value VARCHAR(16) NOT NULL, user_id INT DEFAULT NULL, type VARCHAR(32) NOT NULL, data LONGTEXT DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, expiration DATETIME DEFAULT NULL, INDEX IDX_ADF614B8A76ED395 (user_id), PRIMARY KEY(value)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB");
$this->addSql("ALTER TABLE Tokens ADD CONSTRAINT FK_ADF614B8A76ED395 FOREIGN KEY (user_id) REFERENCES Users (id)");
}
public function doDownSql(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql", "Migration can only be executed safely on 'mysql'.");
$this->addSql("DROP TABLE Tokens");
}
}

View File

@@ -41,7 +41,7 @@ class Upgrade39Feeds implements PreSchemaUpgradeInterface
public function rollback(EntityManager $em, \appbox $appbox, Configuration $conf) public function rollback(EntityManager $em, \appbox $appbox, Configuration $conf)
{ {
if ($this->tableExists($em, 'feeds_backup')) { if ($this->tableExists($em, 'feeds_backup')) {
$em->getConnection()->executeUpdate('RENAME TABLE `feeds_backup` TO `feeds`'); $em->getConnection()->getSchemaManager()->renameTable('feeds_backup', 'feeds');
} }
} }
@@ -67,6 +67,6 @@ class Upgrade39Feeds implements PreSchemaUpgradeInterface
*/ */
private function doBackupFeedsTable(EntityManager $em) private function doBackupFeedsTable(EntityManager $em)
{ {
$em->getConnection()->executeUpdate('RENAME TABLE `feeds` TO `feeds_backup`'); $em->getConnection()->getSchemaManager()->renameTable('feeds', 'feeds_backup');
} }
} }

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Setup\Version\PreSchemaUpgrade;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\FtpCredential;
use Doctrine\DBAL\Migrations\Configuration\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
class Upgrade39Tokens implements PreSchemaUpgradeInterface
{
/**
* {@inheritdoc}
*/
public function apply(EntityManager $em, \appbox $appbox, Configuration $conf)
{
$this->doBackupFeedsTable($em);
}
/**
* {@inheritdoc}
*/
public function isApplyable(Application $app)
{
return $this->tableExists($app['EM'], 'tokens');
}
/**
* {@inheritdoc}
*/
public function rollback(EntityManager $em, \appbox $appbox, Configuration $conf)
{
if ($this->tableExists($em, 'tokens_backup')) {
$em->getConnection()->executeUpdate('RENAME TABLE `tokens_backup` TO `tokens`');
}
}
/**
* Checks whether the table exists or not.
*
* @param $tableName
*
* @return boolean
*/
private function tableExists(EntityManager $em, $table)
{
return (Boolean) $em->createNativeQuery(
'SHOW TABLE STATUS WHERE Name="'.$table.'" COLLATE utf8_bin ', (new ResultSetMapping())->addScalarResult('Name', 'Name')
)->getOneOrNullResult();
}
/**
* Renames feed table.
*
* @param EntityManager $em
*/
private function doBackupFeedsTable(EntityManager $em)
{
$em->getConnection()->executeUpdate('RENAME TABLE `tokens` TO `tokens_backup`');
}
}

View File

@@ -10,8 +10,9 @@
*/ */
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class API_OAuth2_Application class API_OAuth2_Application
{ {
@@ -606,8 +607,8 @@ class API_OAuth2_Application
)'; )';
$nonce = $app['random.medium']->generateString(64); $nonce = $app['random.medium']->generateString(64);
$client_secret = $app['random.medium']->generateString(32, \random::LETTERS_AND_NUMBERS); $client_secret = $app['random.medium']->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS);
$client_token = $app['random.medium']->generateString(32, \random::LETTERS_AND_NUMBERS); $client_token = $app['random.medium']->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS);
$params = [ $params = [
':usr_id' => $user ? $user->getId() : null, ':usr_id' => $user ? $user->getId() : null,

View File

@@ -10,6 +10,7 @@
*/ */
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use RandomLib\Generator; use RandomLib\Generator;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -230,7 +231,7 @@ class API_OAuth2_Token
$sql = 'UPDATE api_oauth_tokens SET oauth_token = :new_token $sql = 'UPDATE api_oauth_tokens SET oauth_token = :new_token
WHERE oauth_token = :old_token'; WHERE oauth_token = :old_token';
$new_token = $this->generator->generateString(32, \random::LETTERS_AND_NUMBERS); $new_token = $this->generator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS);
$params = [ $params = [
':new_token' => $new_token ':new_token' => $new_token
@@ -303,7 +304,7 @@ class API_OAuth2_Token
$expires = new \DateTime('+1 hour'); $expires = new \DateTime('+1 hour');
$params = [ $params = [
':token' => $generator->generateString(32, \random::LETTERS_AND_NUMBERS) ':token' => $generator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS)
, ':account_id' => $account->get_id() , ':account_id' => $account->get_id()
, ':expire' => $expires->format(DATE_ISO8601) , ':expire' => $expires->format(DATE_ISO8601)
, ':scope' => $scope , ':scope' => $scope

View File

@@ -88,14 +88,8 @@ class eventsmanager_notify_feed extends eventsmanager_notifyAbstract
if ($params['notify_email'] && $this->shouldSendNotificationFor($user_to_notif->getId())) { if ($params['notify_email'] && $this->shouldSendNotificationFor($user_to_notif->getId())) {
$readyToSend = false; $readyToSend = false;
try { try {
$token = $this->app['tokens']->getUrlToken( $token = $this->app['manipulator.token']->createFeedEntryToken($user_to_notif, $entry);
\random::TYPE_FEED_ENTRY $url = $this->app->url('lightbox', ['LOG' => $token->getValue()]);
, $user_to_notif->getId()
, null
, $entry->getId()
);
$url = $this->app->url('lightbox', ['LOG' => $token]);
$receiver = Receiver::fromUser($user_to_notif); $receiver = Receiver::fromUser($user_to_notif);
$readyToSend = true; $readyToSend = true;

View File

@@ -109,12 +109,7 @@ class eventsmanager_notify_orderdeliver extends eventsmanager_notifyAbstract
if ($readyToSend) { if ($readyToSend) {
$url = $this->app->url('lightbox_compare', [ $url = $this->app->url('lightbox_compare', [
'basket' => $basket->getId(), 'basket' => $basket->getId(),
'LOG' => $this->app['tokens']->getUrlToken( 'LOG' => $this->app['manipulator.token']->createBasketAccessToken($basket, $user_to)->getValue(),
\random::TYPE_VIEW,
$user_to->getId(),
null,
$basket->getId()
)
]); ]);
$mail = MailInfoOrderDelivered::create($this->app, $receiver, $emitter, null); $mail = MailInfoOrderDelivered::create($this->app, $receiver, $emitter, null);

View File

@@ -11,8 +11,9 @@
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Exception\RuntimeException;
use Guzzle\Http\Url; use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Doctrine\DBAL\DBALException; use Doctrine\DBAL\DBALException;
use Guzzle\Http\Url;
class media_Permalink_Adapter implements media_Permalink_Interface, cache_cacheableInterface class media_Permalink_Adapter implements media_Permalink_Interface, cache_cacheableInterface
{ {
@@ -327,7 +328,7 @@ class media_Permalink_Adapter implements media_Permalink_Interface, cache_cachea
$params = [ $params = [
':subdef_id' => $media_subdef->get_subdef_id() ':subdef_id' => $media_subdef->get_subdef_id()
, ':token' => $app['random.medium']->generateString(64, \random::LETTERS_AND_NUMBERS) , ':token' => $app['random.medium']->generateString(64, TokenManipulator::LETTERS_AND_NUMBERS)
, ':activated' => '1' , ':activated' => '1'
]; ];

View File

@@ -58,7 +58,7 @@ class patch_390alpha14a extends patchAbstract
{ {
$app['conf']->remove(['main', 'api-timers']); $app['conf']->remove(['main', 'api-timers']);
if ($this->tableHasField($appbox, 'api_logs', 'api_log_ressource')) { if ($this->tableHasField($app['EM'], 'api_logs', 'api_log_ressource')) {
$sql = 'UPDATE api_logs SET api_log_resource = api_log_ressource'; $sql = 'UPDATE api_logs SET api_log_resource = api_log_ressource';
$app['phraseanet.appbox']->get_connection()->executeUpdate($sql); $app['phraseanet.appbox']->get_connection()->executeUpdate($sql);
} }

View File

@@ -0,0 +1,70 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
class patch_390alpha15a extends patchAbstract
{
/** @var string */
private $release = '3.9.0-alpha.15';
/** @var array */
private $concern = [base::APPLICATION_BOX];
/**
* {@inheritdoc}
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return ['token'];
}
/**
* {@inheritdoc}
*/
public function apply(base $appbox, Application $app)
{
if (!$this->tableExists($app['EM'], 'tokens_backup')) {
return true;
}
$sql = 'INSERT INTO Tokens
(value, user_id, type, data, created, updated, expiration)
(SELECT value, usr_id, type, datas, created_on, created_on, expire_on FROM tokens_backup)';
$appbox->get_connection()->exec($sql);
return true;
}
}

View File

@@ -11,6 +11,7 @@
use Doctrine\ORM\NoResultException; use Doctrine\ORM\NoResultException;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query\ResultSetMapping;
abstract class patchAbstract implements patchInterface abstract class patchAbstract implements patchInterface
{ {
@@ -26,22 +27,21 @@ abstract class patchAbstract implements patchInterface
} }
} }
protected function tableExists(base $base, $tableName) protected function tableExists(EntityManager $em, $tableName)
{ {
return $base return (Boolean) $em->createNativeQuery(
->get_connection() 'SHOW TABLE STATUS WHERE Name="'.$tableName.'" COLLATE utf8_bin ', (new ResultSetMapping())->addScalarResult('Name', 'Name')
->getSchemaManager() )->getOneOrNullResult();
->tablesExist([$tableName]);
} }
protected function tableHasField(base $base, $tableName, $fieldName) protected function tableHasField(EntityManager $em, $tableName, $fieldName)
{ {
if (!$this->tableExists($base, $tableName)) { if (!$this->tableExists($em, $tableName)) {
return false; return false;
} }
return $base return $em
->get_connection() ->getConnection()
->getSchemaManager() ->getSchemaManager()
->listTableDetails($tableName) ->listTableDetails($tableName)
->hasColumn($fieldName); ->hasColumn($fieldName);

View File

@@ -10,31 +10,9 @@
*/ */
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class random class random
{ {
/**
*
*/
const NUMBERS = "0123456789";
/**
*
*/
const LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
/**
*
*/
const LETTERS_AND_NUMBERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const TYPE_FEED_ENTRY = 'FEED_ENTRY';
const TYPE_PASSWORD = 'password';
const TYPE_DOWNLOAD = 'download';
const TYPE_MAIL_DOWNLOAD = 'mail-download';
const TYPE_EMAIL = 'email';
const TYPE_VIEW = 'view';
const TYPE_VALIDATE = 'validate';
const TYPE_RSS = 'rss';
private $app; private $app;
public function __construct(Application $app) public function __construct(Application $app)
@@ -82,162 +60,4 @@ class random
return false; return false;
} }
/**
*
* @param string $type
* @param int $usr
* @param DateTime $end_date
* @param mixed content $datas
*
* @return boolean
*/
public function getUrlToken($type, $usr, DateTime $end_date = null, $datas = '')
{
$this->cleanTokens();
$conn = $this->app['phraseanet.appbox']->get_connection();
$token = $test = false;
switch ($type) {
case self::TYPE_DOWNLOAD:
case self::TYPE_MAIL_DOWNLOAD:
case self::TYPE_EMAIL:
case self::TYPE_PASSWORD:
case self::TYPE_VALIDATE:
case self::TYPE_VIEW:
case self::TYPE_RSS:
case self::TYPE_FEED_ENTRY:
break;
default:
throw new Exception_InvalidArgument();
break;
}
$n = 1;
$sql = 'SELECT id FROM tokens WHERE value = :test ';
$stmt = $conn->prepare($sql);
while ($n < 100) {
$test = $this->app['random.medium']->generateString(16, self::LETTERS_AND_NUMBERS);
$stmt->execute([':test' => $test]);
if ($stmt->rowCount() === 0) {
$token = $test;
break;
}
$n ++;
}
$stmt->closeCursor();
if ($token) {
$sql = 'INSERT INTO tokens (id, value, type, usr_id, created_on, expire_on, datas)
VALUES (null, :token, :type, :usr, NOW(), :end_date, :datas)';
$stmt = $conn->prepare($sql);
$params = [
':token' => $token
, ':type' => $type
, ':usr' => ($usr ? $usr : '-1')
, ':end_date' => ($end_date instanceof DateTime ? $end_date->format(DATE_ISO8601) : null)
, ':datas' => ((trim($datas) != '') ? $datas : null)
];
$stmt->execute($params);
$stmt->closeCursor();
}
return $token;
}
public function removeToken($token)
{
$this->cleanTokens();
try {
$conn = $this->app['phraseanet.appbox']->get_connection();
$sql = 'DELETE FROM tokens WHERE value = :token';
$stmt = $conn->prepare($sql);
$stmt->execute([':token' => $token]);
$stmt->closeCursor();
return true;
} catch (\Exception $e) {
}
return false;
}
public function updateToken($token, $datas)
{
try {
$conn = $this->app['phraseanet.appbox']->get_connection();
$sql = 'UPDATE tokens SET datas = :datas
WHERE value = :token';
$stmt = $conn->prepare($sql);
$stmt->execute([':datas' => $datas, ':token' => $token]);
$stmt->closeCursor();
return true;
} catch (\Exception $e) {
}
return false;
}
public function helloToken($token)
{
$this->cleanTokens();
$conn = $this->app['phraseanet.appbox']->get_connection();
$sql = 'SELECT * FROM tokens
WHERE value = :token
AND (expire_on > NOW() OR expire_on IS NULL)';
$stmt = $conn->prepare($sql);
$stmt->execute([':token' => $token]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt->closeCursor();
if ( ! $row)
throw new NotFoundHttpException('Token not found');
return $row;
}
/**
* Get the validation token for one user and one validation basket
*
* @param integer $userId
* @param integer $basketId
*
* @return string The token
*
* @throws NotFoundHttpException
*/
public function getValidationToken($userId, $basketId)
{
$conn = $this->app['phraseanet.appbox']->get_connection();
$sql = '
SELECT value FROM tokens
WHERE type = :type
AND usr_id = :usr_id
AND datas = :basket_id
AND (expire_on > NOW() OR expire_on IS NULL)';
$stmt = $conn->prepare($sql);
$stmt->execute([
':type' => self::TYPE_VALIDATE,
':usr_id' => (int) $userId,
':basket_id' => (int) $basketId,
]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt->closeCursor();
if (! $row) {
throw new NotFoundHttpException('Token not found');
}
return $row['value'];
}
} }

View File

@@ -11,6 +11,7 @@
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Model\Serializer\CaptionSerializer; use Alchemy\Phrasea\Model\Serializer\CaptionSerializer;
use Alchemy\Phrasea\Model\Entities\Token;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
@@ -693,7 +694,7 @@ class set_export extends set_abstract
* *
* @return string * @return string
*/ */
public static function build_zip(Application $app, $token, Array $list, $zipFile) public static function build_zip(Application $app, Token $token, Array $list, $zipFile)
{ {
if (isset($list['complete']) && $list['complete'] === true) { if (isset($list['complete']) && $list['complete'] === true) {
return; return;
@@ -703,7 +704,8 @@ class set_export extends set_abstract
$list['complete'] = false; $list['complete'] = false;
$app['tokens']->updateToken($token, serialize($list)); $token->setData(serialize($list));
$app['manipulator.token']->update($token);
$toRemove = []; $toRemove = [];
$archiveFiles = []; $archiveFiles = [];
@@ -734,7 +736,8 @@ class set_export extends set_abstract
$list['complete'] = true; $list['complete'] = true;
$app['tokens']->updateToken($token, serialize($list)); $token->setData(serialize($list));
$app['manipulator.token']->update($token);
$app['filesystem']->remove($toRemove); $app['filesystem']->remove($toRemove);
$app['filesystem']->chmod($zipFile, 0760); $app['filesystem']->chmod($zipFile, 0760);

View File

@@ -2124,85 +2124,6 @@
</defaults> </defaults>
<engine>InnoDB</engine> <engine>InnoDB</engine>
</table> </table>
<table name="tokens">
<fields>
<field>
<name>id</name>
<type>int(11) unsigned</type>
<null></null>
<extra>auto_increment</extra>
</field>
<field>
<name>value</name>
<type>char(16)</type>
<null></null>
<extra></extra>
<collation>ascii_bin</collation>
</field>
<field>
<name>type</name>
<type>enum('FEED_ENTRY', 'view','validate','password','rss','email','download')</type>
<null></null>
<extra></extra>
<collation>ascii_bin</collation>
</field>
<field>
<name>usr_id</name>
<type>int(11) unsigned</type>
<null></null>
<extra></extra>
</field>
<field>
<name>datas</name>
<type>longtext</type>
<null>YES</null>
<extra></extra>
</field>
<field>
<name>created_on</name>
<type>datetime</type>
<null></null>
<extra></extra>
</field>
<field>
<name>expire_on</name>
<type>datetime</type>
<null>YES</null>
<extra></extra>
</field>
</fields>
<indexes>
<index>
<name>PRIMARY</name>
<type>PRIMARY</type>
<fields>
<field>id</field>
</fields>
</index>
<index>
<name>value</name>
<type>UNIQUE</type>
<fields>
<field>value</field>
</fields>
</index>
<index>
<name>expire</name>
<type>INDEX</type>
<fields>
<field>expire_on</field>
</fields>
</index>
<index>
<name>type</name>
<type>INDEX</type>
<fields>
<field>type</field>
</fields>
</index>
</indexes>
<engine>InnoDB</engine>
</table>
</tables> </tables>
</appbox> </appbox>
<databox> <databox>

View File

@@ -57,3 +57,6 @@ migrations:
migration18: migration18:
version: registration version: registration
class: Alchemy\Phrasea\Setup\DoctrineMigrations\RegistrationMigration class: Alchemy\Phrasea\Setup\DoctrineMigrations\RegistrationMigration
migration19:
version: token
class: Alchemy\Phrasea\Setup\DoctrineMigrations\TokenMigration

View File

@@ -19,7 +19,7 @@
</div> </div>
{% elseif list['complete'] is defined and list['complete'] %} {% elseif list['complete'] is defined and list['complete'] %}
<div class="alert alert-success"> <div class="alert alert-success">
{% set url = path('document_download', {'token': token}) %} {% set url = path('document_download', {'token': token.getValue()}) %}
{% set before_link = '<a href="' ~ url ~ '" target="_self">' %} {% set before_link = '<a href="' ~ url ~ '" target="_self">' %}
{% set after_link = '</a>' %} {% set after_link = '</a>' %}
{% trans with {'%before_link%' : before_link, '%after_link%' : after_link} %}Your documents are ready. If the download does not start, %before_link%click here%after_link%{% endtrans %} {% trans with {'%before_link%' : before_link, '%after_link%' : after_link} %}Your documents are ready. If the download does not start, %before_link%click here%after_link%{% endtrans %}
@@ -91,7 +91,7 @@
</div> </div>
<div style="display:none"> <div style="display:none">
<form name="download" action="{{ path('document_download', {'token': token}) }}" method="post" target="file_frame"> <form name="download" action="{{ path('document_download', {'token': token.getValue()}) }}" method="post" target="file_frame">
{% if anonymous %} {% if anonymous %}
<input type="hidden" name="anonymous" value="1" /> <input type="hidden" name="anonymous" value="1" />
{% endif%} {% endif%}
@@ -105,7 +105,7 @@
{% set time = time < 1 ? 2 : (time > 10 ? 10 : time) %} {% set time = time < 1 ? 2 : (time > 10 ? 10 : time) %}
{% if list['complete'] is not defined %} {# Zip not done #} {% if list['complete'] is not defined %} {# Zip not done #}
$.post("{{ path('execute_download', {'token': token}) }}", function(data){ $.post("{{ path('execute_download', {'token': token.getValue()}) }}", function(data){
var data = $.parseJSON(data); var data = $.parseJSON(data);
if(data.success) { if(data.success) {
$('form[name=download]').submit(); $('form[name=download]').submit();

View File

@@ -49,9 +49,9 @@ class LightboxTest extends \PhraseanetAuthenticatedWebTestCase
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
$Basket = self::$DI['app']['EM']->find('Phraseanet:Basket', 1); $Basket = self::$DI['app']['EM']->find('Phraseanet:Basket', 1);
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_VIEW, self::$DI['user_alt2']->getId(), null, $Basket->getId()); $token = self::$DI['app']['manipulator.token']->createBasketAccessToken($Basket, self::$DI['user_alt2']);
self::$DI['client']->request('GET', '/lightbox/?LOG='.$token); self::$DI['client']->request('GET', '/lightbox/?LOG='.$token->getValue());
$this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); $this->assertTrue(self::$DI['client']->getResponse()->isRedirect());
$this->assertRegExp('/\/lightbox\/validate\/\d+\//', self::$DI['client']->getResponse()->headers->get('location')); $this->assertRegExp('/\/lightbox\/validate\/\d+\//', self::$DI['client']->getResponse()->headers->get('location'));

View File

@@ -1,31 +0,0 @@
<?php
namespace Alchemy\Tests\Phrasea\Authentication\Token;
use Alchemy\Phrasea\Authentication\Token\TokenValidator;
class TokenValidatorTest extends \PhraseanetTestCase
{
/**
* @covers Alchemy\Phrasea\Authentication\TokenValidator::isValid
*/
public function testValidTokenIsValid()
{
$usr_id = 42;
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_VALIDATE, $usr_id);
$validator = new TokenValidator(self::$DI['app']['tokens']);
$this->assertEquals($usr_id, $validator->isValid($token));
}
/**
* @covers Alchemy\Phrasea\Authentication\TokenValidator::isValid
*/
public function testInvalidTokenIsNotValid()
{
$usr_id = 42;
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_VALIDATE, $usr_id, new \DateTime('-2 hours'));
$validator = new TokenValidator(self::$DI['app']['tokens']);
$this->assertFalse($validator->isValid($token));
}
}

View File

@@ -37,7 +37,7 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
] ]
] ]
]); ]);
$url = sprintf('/download/%s/prepare/', $token); $url = sprintf('/download/%s/prepare/', $token->getValue());
self::$DI['client']->request('GET', $url); self::$DI['client']->request('GET', $url);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isOk()); $this->assertTrue($response->isOk());
@@ -61,7 +61,7 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
public function testPrepareDownloadInvalidData() public function testPrepareDownloadInvalidData()
{ {
$token = $this->getToken(['bad_string' => base64_decode(serialize(['fail']))]); $token = $this->getToken(['bad_string' => base64_decode(serialize(['fail']))]);
self::$DI['client']->request('GET', sprintf('/download/%s/prepare/', $token)); self::$DI['client']->request('GET', sprintf('/download/%s/prepare/', $token->getValue()));
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertEquals(500, $response->getStatusCode()); $this->assertEquals(500, $response->getStatusCode());
@@ -101,7 +101,7 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
] ]
]); ]);
$url = sprintf('/download/%s/get/', $token); $url = sprintf('/download/%s/get/', $token->getValue());
self::$DI['client']->request('POST', $url); self::$DI['client']->request('POST', $url);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isOk()); $this->assertTrue($response->isOk());
@@ -165,18 +165,16 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
]; ];
$token = $this->getToken($list); $token = $this->getToken($list);
// Get token
$datas = self::$DI['app']['tokens']->helloToken($token);
// Build zip // Build zip
\set_export::build_zip( \set_export::build_zip(
self::$DI['app'], self::$DI['app'],
$token, $token,
$list, $list,
sprintf('%s/../../../../../../tmp/download/%s.zip', __DIR__, $datas['value']) // Dest file sprintf('%s/../../../../../../tmp/download/%s.zip', __DIR__, $token->getValue()) // Dest file
); );
// Check response // Check response
$url = sprintf('/download/%s/get/', $token); $url = sprintf('/download/%s/get/', $token->getValue());
self::$DI['client']->request('POST', $url); self::$DI['client']->request('POST', $url);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isOk()); $this->assertTrue($response->isOk());
@@ -216,7 +214,7 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
] ]
] ]
]); ]);
$url = sprintf('/download/%s/get/', $token); $url = sprintf('/download/%s/get/', $token->getValue());
self::$DI['client']->request('POST', $url); self::$DI['client']->request('POST', $url);
$this->assertNotFoundResponse(self::$DI['client']->getResponse()); $this->assertNotFoundResponse(self::$DI['client']->getResponse());
@@ -239,7 +237,7 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
public function testDocumentsDownloadInvalidData() public function testDocumentsDownloadInvalidData()
{ {
$token = $this->getToken(['bad_string' => base64_decode(serialize(['fail']))]); $token = $this->getToken(['bad_string' => base64_decode(serialize(['fail']))]);
self::$DI['client']->request('POST', sprintf('/download/%s/get/', $token)); self::$DI['client']->request('POST', sprintf('/download/%s/get/', $token->getValue()));
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertEquals(500, $response->getStatusCode()); $this->assertEquals(500, $response->getStatusCode());
@@ -251,8 +249,11 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
*/ */
public function testExecuteDownloadInvalidData() public function testExecuteDownloadInvalidData()
{ {
$token = $this->getToken(['bad_string' => base64_decode(serialize(['fail']))]); $token = self::$DI['app']['manipulator.token']->createDownloadToken(
$url = sprintf('/download/%s/execute/', $token); self::$DI['user'],
base64_decode(serialize(['fail']))
);
$url = sprintf('/download/%s/execute/', $token->getValue());
self::$DI['client']->request('POST', $url); self::$DI['client']->request('POST', $url);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$datas = (array) json_decode($response->getContent()); $datas = (array) json_decode($response->getContent());
@@ -268,7 +269,7 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
{ {
$token = 'ABCDEFGHJaajKISU'; $token = 'ABCDEFGHJaajKISU';
$url = sprintf('/download/%s/execute/', $token); $url = sprintf('/download/%s/execute/', $token);
self::$DI['client']->request('POST', $url); self::$DI['client']->request('POST', $url, [], [], ["HTTP_ACCEPT" => "application/json"]);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$datas = (array) json_decode($response->getContent()); $datas = (array) json_decode($response->getContent());
$this->assertArrayHasKey('success', $datas); $this->assertArrayHasKey('success', $datas);
@@ -329,7 +330,7 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
$token = $this->getToken($list); $token = $this->getToken($list);
$url = sprintf('/download/%s/execute/', $token); $url = sprintf('/download/%s/execute/', $token->getValue());
self::$DI['client']->request('POST', $url); self::$DI['client']->request('POST', $url);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$datas = (array) json_decode($response->getContent()); $datas = (array) json_decode($response->getContent());
@@ -340,10 +341,8 @@ class DoDownloadTest extends \PhraseanetAuthenticatedWebTestCase
private function getToken($datas = []) private function getToken($datas = [])
{ {
return self::$DI['app']['tokens']->getUrlToken( return self::$DI['app']['manipulator.token']->createDownloadToken(
\random::TYPE_DOWNLOAD, self::$DI['user'],
self::$DI['user']->getId(),
new \DateTime('+10 seconds'), // Token lifetime
serialize($datas) serialize($datas)
); );
} }

View File

@@ -32,7 +32,7 @@ class DownloadTest extends \PhraseanetAuthenticatedWebTestCase
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
$this->assertRegExp('#/download/[a-zA-Z0-9\.\/]{8,16}/#', $response->headers->get('location')); $this->assertRegExp('#/download/[a-zA-Z0-9]{8,32}/#', $response->headers->get('location'));
unset($response, $eventManagerStub); unset($response, $eventManagerStub);
} }
@@ -52,7 +52,7 @@ class DownloadTest extends \PhraseanetAuthenticatedWebTestCase
self::$DI['app']['events-manager'] = $eventManagerStub; self::$DI['app']['events-manager'] = $eventManagerStub;
self::$DI['app']['authentication']->setUser($this->createUserMock()); self::$DI['app']['authentication']->setUser(self::$DI['user']);
$stubbedACL = $this->getMockBuilder('\ACL') $stubbedACL = $this->getMockBuilder('\ACL')
->disableOriginalConstructor() ->disableOriginalConstructor()
@@ -92,7 +92,7 @@ class DownloadTest extends \PhraseanetAuthenticatedWebTestCase
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
$this->assertRegExp('#/download/[a-zA-Z0-9\.\/]{8,16}/#', $response->headers->get('location')); $this->assertRegExp('#/download/[a-zA-Z0-9]{8,32}/#', $response->headers->get('location'));
unset($response, $eventManagerStub); unset($response, $eventManagerStub);
} }
@@ -124,7 +124,7 @@ class DownloadTest extends \PhraseanetAuthenticatedWebTestCase
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
$this->assertRegExp('#/download/[a-zA-Z0-9\.\/]{8,16}/#', $response->headers->get('location')); $this->assertRegExp('#/download/[a-zA-Z0-9]{8,32}/#', $response->headers->get('location'));
unset($response, $eventManagerStub); unset($response, $eventManagerStub);
} }
@@ -156,7 +156,7 @@ class DownloadTest extends \PhraseanetAuthenticatedWebTestCase
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
$this->assertRegExp('#/download/[a-zA-Z0-9\.\/]{8,16}/#', $response->headers->get('location')); $this->assertRegExp('#/download/[a-zA-Z0-9]{8,32}/#', $response->headers->get('location'));
unset($response, $eventManagerStub); unset($response, $eventManagerStub);
} }

View File

@@ -104,19 +104,16 @@ class AccountTest extends \PhraseanetAuthenticatedWebTestCase
*/ */
public function testGetResetMailWithToken() public function testGetResetMailWithToken()
{ {
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_EMAIL, self::$DI['user']->getId(), null, 'new_email@email.com'); $tokenValue = self::$DI['app']['manipulator.token']->createResetEmailToken(self::$DI['user'], 'new_email@email.com')->getValue();
$crawler = self::$DI['client']->request('GET', '/account/reset-email/', ['token' => $token]); $crawler = self::$DI['client']->request('GET', '/account/reset-email/', ['token' => $tokenValue]);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
$this->assertEquals('/account/', $response->headers->get('location')); $this->assertEquals('/account/', $response->headers->get('location'));
$this->assertEquals('new_email@email.com', self::$DI['user']->getEmail()); $this->assertEquals('new_email@email.com', self::$DI['user']->getEmail());
self::$DI['user']->setEmail('noone@example.com'); self::$DI['user']->setEmail('noone@example.com');
try { if (null !== self::$DI['app']['repo.tokens']->find($tokenValue)) {
self::$DI['app']['tokens']->helloToken($token);
$this->fail('Token has not been removed'); $this->fail('Token has not been removed');
} catch (NotFoundHttpException $e) {
} }
$this->assertFlashMessagePopulated(self::$DI['app'], 'success', 1); $this->assertFlashMessagePopulated(self::$DI['app'], 'success', 1);

View File

@@ -11,6 +11,7 @@ use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Authentication\ProvidersCollection; use Alchemy\Phrasea\Authentication\ProvidersCollection;
use Alchemy\Phrasea\Model\Entities\Registration; use Alchemy\Phrasea\Model\Entities\Registration;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use RandomLib\Factory; use RandomLib\Factory;
use Symfony\Component\HttpKernel\Client; use Symfony\Component\HttpKernel\Client;
use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpFoundation\ResponseHeaderBag;
@@ -170,9 +171,12 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
{ {
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
$email = $this->generateEmail(); $email = $this->generateEmail();
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_EMAIL, 0, null, $email); $token = self::$DI['app']['manipulator.token']->createResetEmailToken(self::$DI['user'], $email);
$tokenValue = $token->getValue();
self::$DI['app']['EM']->remove($token);
self::$DI['app']['EM']->flush();
self::$DI['client']->request('GET', '/login/register-confirm/', [ self::$DI['client']->request('GET', '/login/register-confirm/', [
'code' => $token 'code' => $tokenValue
]); ]);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
@@ -188,11 +192,11 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
{ {
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
$email = $this->generateEmail(); $email = $this->generateEmail();
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_EMAIL, self::$DI['user']->getId(), null, $email); $token = self::$DI['app']['manipulator.token']->createResetEmailToken(self::$DI['user'], $email);
self::$DI['user']->setMailLocked(false); self::$DI['user']->setMailLocked(false);
self::$DI['client']->request('GET', '/login/register-confirm/', ['code' => $token]); self::$DI['client']->request('GET', '/login/register-confirm/', ['code' => $token->getValue()]);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
@@ -207,7 +211,7 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
$email = $this->generateEmail(); $email = $this->generateEmail();
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_EMAIL, self::$DI['user']->getId(), null, $email); $token = self::$DI['app']['manipulator.token']->createResetEmailToken(self::$DI['user'], $email);
self::$DI['user']->setMailLocked(true); self::$DI['user']->setMailLocked(true);
$this->deleteRequest(); $this->deleteRequest();
@@ -218,7 +222,7 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
self::$DI['app']['EM']->persist($registration); self::$DI['app']['EM']->persist($registration);
self::$DI['app']['EM']->flush(); self::$DI['app']['EM']->flush();
self::$DI['client']->request('GET', '/login/register-confirm/', ['code' => $token]); self::$DI['client']->request('GET', '/login/register-confirm/', ['code' => $token->getValue()]);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
@@ -235,13 +239,13 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
$email = $this->generateEmail(); $email = $this->generateEmail();
$user = self::$DI['app']['manipulator.user']->createUser('test', 'test', $email); $user = self::$DI['app']['manipulator.user']->createUser('test', 'test', $email);
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_EMAIL, $user->getId(), null, $email); $token = self::$DI['app']['manipulator.token']->createResetEmailToken($user, $email);
$user->setMailLocked(true); $user->setMailLocked(true);
$this->deleteRequest(); $this->deleteRequest();
self::$DI['client']->request('GET', '/login/register-confirm/', ['code' => $token]); self::$DI['client']->request('GET', '/login/register-confirm/', ['code' => $token->getValue()]);
$response = self::$DI['client']->getResponse(); $response = self::$DI['client']->getResponse();
$this->assertTrue($response->isRedirect()); $this->assertTrue($response->isRedirect());
$this->assertFlashMessagePopulated(self::$DI['app'], 'info', 1); $this->assertFlashMessagePopulated(self::$DI['app'], 'info', 1);
@@ -304,9 +308,9 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
public function testRenewPasswordBadArguments() public function testRenewPasswordBadArguments()
{ {
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_PASSWORD, self::$DI['user']->getId()); $token = self::$DI['app']['manipulator.token']->createResetPasswordToken(self::$DI['user']);
$crawler = self::$DI['client']->request('POST', '/login/renew-password/', [ $crawler = self::$DI['client']->request('POST', '/login/renew-password/', [
'token' => $token, 'token' => $token->getValue(),
'_token' => 'token', '_token' => 'token',
'password' => ['password' => 'password', 'confirm' => 'not_identical'] 'password' => ['password' => 'password', 'confirm' => 'not_identical']
]); ]);
@@ -376,10 +380,10 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
public function testRenewPassword() public function testRenewPassword()
{ {
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_PASSWORD, self::$DI['user']->getId()); $token = self::$DI['app']['manipulator.token']->createResetPasswordToken(self::$DI['user']);
self::$DI['client']->request('POST', '/login/renew-password/', [ self::$DI['client']->request('POST', '/login/renew-password/', [
'token' => $token, 'token' => $token->getValue(),
'_token' => 'token', '_token' => 'token',
'password' => ['password' => 'password', 'confirm' => 'password'] 'password' => ['password' => 'password', 'confirm' => 'password']
]); ]);
@@ -400,10 +404,10 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
$this->logout(self::$DI['app']); $this->logout(self::$DI['app']);
self::$DI['app']->addFlash($type, $message); self::$DI['app']->addFlash($type, $message);
$token = self::$DI['app']['tokens']->getUrlToken(\random::TYPE_PASSWORD, self::$DI['user']->getId()); $token = self::$DI['app']['manipulator.token']->createResetPasswordToken(self::$DI['user']);
$crawler = self::$DI['client']->request('GET', '/login/renew-password/', [ $crawler = self::$DI['client']->request('GET', '/login/renew-password/', [
'token' => $token 'token' => $token->getValue()
]); ]);
$this->assertTrue(self::$DI['client']->getResponse()->isOk()); $this->assertTrue(self::$DI['client']->getResponse()->isOk());
@@ -1794,7 +1798,7 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
$factory = new Factory(); $factory = new Factory();
$generator = $factory->getLowStrengthGenerator(); $generator = $factory->getLowStrengthGenerator();
return $generator->generateString(8, \random::LETTERS_AND_NUMBERS) . '_email@email.com'; return $generator->generateString(8, TokenManipulator::LETTERS_AND_NUMBERS) . '_email@email.com';
} }
private function disableTOU() private function disableTOU()

View File

@@ -0,0 +1,39 @@
<?php
namespace Alchemy\Tests\Phrasea\Core\Middleware;
use Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider;
use Symfony\Component\HttpFoundation\Request;
class TokenMiddlewareProviderTest extends MiddlewareProviderTestCase
{
public function provideDescription()
{
return [
[
'Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider',
'middleware.token.converter'
],
];
}
public function testConverterWithNoParameter()
{
$this->authenticate(self::$DI['app']);
self::$DI['app']->register(new TokenMiddlewareProvider());
$request = new Request();
call_user_func(self::$DI['app']['middleware.token.converter'], $request, self::$DI['app']);
$this->assertNull($request->attributes->get('token'));
}
public function testConverterWithBasketParameter()
{
$this->authenticate(self::$DI['app']);
self::$DI['app']->register(new TokenMiddlewareProvider());
$request = new Request();
$token = self::$DI['token_1'];
$request->attributes->set('token', $token->getValue());
call_user_func(self::$DI['app']['middleware.token.converter'], $request, self::$DI['app']);
$this->assertSame($token, $request->attributes->get('token'));
}
}

View File

@@ -20,11 +20,6 @@ class AuthenticationManagerServiceProviderTest extends ServiceProviderTestCase
'authentication', 'authentication',
'Alchemy\\Phrasea\\Authentication\\Authenticator', 'Alchemy\\Phrasea\\Authentication\\Authenticator',
], ],
[
'Alchemy\Phrasea\Core\Provider\AuthenticationManagerServiceProvider',
'authentication.token-validator',
'Alchemy\Phrasea\Authentication\Token\TokenValidator'
],
[ [
'Alchemy\Phrasea\Core\Provider\AuthenticationManagerServiceProvider', 'Alchemy\Phrasea\Core\Provider\AuthenticationManagerServiceProvider',
'authentication.persistent-manager', 'authentication.persistent-manager',

View File

@@ -17,6 +17,11 @@ class ConvertersServiceProvider extends ServiceProviderTestCase
'converter.basket', 'converter.basket',
'Alchemy\Phrasea\Controller\Converter\BasketConverter' 'Alchemy\Phrasea\Controller\Converter\BasketConverter'
], ],
[
'Alchemy\Phrasea\Core\Provider\ConvertersServiceProvider',
'converter.token',
'Alchemy\Phrasea\Controller\Converter\TokenConverter'
],
]; ];
} }
} }

View File

@@ -22,6 +22,11 @@ class ManipulatorServiceProviderTest extends ServiceProviderTestCase
'manipulator.registration', 'manipulator.registration',
'Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator' 'Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator'
], ],
[
'Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider',
'manipulator.token',
'Alchemy\Phrasea\Model\Manipulator\TokenManipulator'
],
]; ];
} }
} }

View File

@@ -31,6 +31,7 @@ class RepositoriesServiceProviderTest extends ServiceProviderTestCase
['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.usr-auth-providers', 'Alchemy\Phrasea\Model\Repositories\UsrAuthProviderRepository'], ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.usr-auth-providers', 'Alchemy\Phrasea\Model\Repositories\UsrAuthProviderRepository'],
['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.ftp-exports', 'Alchemy\Phrasea\Model\Repositories\FtpExportRepository'], ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.ftp-exports', 'Alchemy\Phrasea\Model\Repositories\FtpExportRepository'],
['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.user-queries', 'Alchemy\Phrasea\Model\Repositories\UserQueryRepository'], ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.user-queries', 'Alchemy\Phrasea\Model\Repositories\UserQueryRepository'],
['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.tokens', 'Alchemy\Phrasea\Model\Repositories\TokenRepository'],
]; ];
} }
} }

View File

@@ -3,47 +3,51 @@
namespace Alchemy\Tests\Phrasea\Form\Constraint; namespace Alchemy\Tests\Phrasea\Form\Constraint;
use Alchemy\Phrasea\Form\Constraint\PasswordToken; use Alchemy\Phrasea\Form\Constraint\PasswordToken;
use Alchemy\Phrasea\Model\Entities\Token;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class PasswordTokenTest extends \PhraseanetTestCase class PasswordTokenTest extends \PhraseanetTestCase
{ {
public function testInvalidTokenIsNotValid() public function testInvalidTokenIsNotValid()
{ {
$random = $this $repo = $this
->getMockBuilder('random') ->getMockBuilder('Doctrine\ORM\EntityRepository')
->disableOriginalConstructor() ->disableOriginalConstructor()
->setMethods(['helloToken']) ->setMethods(['findValidToken'])
->getMock(); ->getMock();
$token = self::$DI['app']['random.low']->generateString(8); $tokenValue = self::$DI['app']['random.low']->generateString(8);
$random $repo
->expects($this->once()) ->expects($this->once())
->method('helloToken') ->method('findValidToken')
->with($token) ->with($tokenValue)
->will($this->throwException(new NotFoundHttpException('Token not found'))); ->will($this->returnValue(null));
$constraint = new PasswordToken($random); $constraint = new PasswordToken($repo);
$this->assertFalse($constraint->isValid($token)); $this->assertFalse($constraint->isValid($tokenValue));
} }
public function testValidTokenIsValid() public function testValidTokenIsValid()
{ {
$random = $this $repo = $this
->getMockBuilder('random') ->getMockBuilder('Doctrine\ORM\EntityRepository')
->disableOriginalConstructor() ->disableOriginalConstructor()
->setMethods(['helloToken']) ->setMethods(['findValidToken'])
->getMock(); ->getMock();
$token = self::$DI['app']['random.low']->generateString(8); $tokenValue = self::$DI['app']['random.low']->generateString(8);
$token = new Token();
$token->setType(TokenManipulator::TYPE_PASSWORD);
$random $repo
->expects($this->once()) ->expects($this->once())
->method('helloToken') ->method('findValidToken')
->with($token) ->with($tokenValue)
->will($this->returnValue(['usr_id' => mt_rand(), 'type' => \random::TYPE_PASSWORD])); ->will($this->returnValue($token));
$constraint = new PasswordToken($random); $constraint = new PasswordToken($repo);
$this->assertTrue($constraint->isValid($token)); $this->assertTrue($constraint->isValid($tokenValue));
} }
} }

View File

@@ -9,6 +9,6 @@ class PhraseaRecoverPasswordFormTest extends FormTestCase
{ {
protected function getForm() protected function getForm()
{ {
return new PhraseaRecoverPasswordForm(self::$DI['app']['tokens']); return new PhraseaRecoverPasswordForm(self::$DI['app']['repo.tokens']);
} }
} }

View File

@@ -0,0 +1,26 @@
<?php
namespace Alchemy\Tests\Phrasea\Model\Converter;
use Alchemy\Phrasea\Model\Converter\TokenConverter;
class TokenConverterTest extends \PhraseanetTestCase
{
public function testConvert()
{
$token = self::$DI['token_1'];
$converter = new TokenConverter(self::$DI['app']['repo.tokens']);
$this->assertSame($token, $converter->convert($token->getValue()));
}
/**
* @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @expectedExceptionMessage Token is not valid.
*/
public function testConvertFailure()
{
$converter = new TokenConverter(self::$DI['app']['repo.tokens']);
$converter->convert('prout');
}
}

View File

@@ -0,0 +1,185 @@
<?php
namespace Alchemy\Tests\Phrasea\Model\Manipulator;
use Alchemy\Phrasea\Model\Entities\Token;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
class TokenManipulatorTest extends \PhraseanetTestCase
{
/**
* @dataProvider provideConstructorArguments
*/
public function testCreate($user, $type, $expiration, $data)
{
$user = $user ? self::$DI['user'] : null;
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->create($user, $type, $expiration, $data);
$this->assertSame($user, $token->getUser());
$this->assertSame($type, $token->getType());
$this->assertSame($expiration, $token->getExpiration());
$this->assertSame($data, $token->getData());
$this->assertInternalType('string', $token->getValue());
$this->assertEquals(32, strlen($token->getValue()));
}
public function provideConstructorArguments()
{
return [
[true, TokenManipulator::TYPE_RSS, null, null],
[false, TokenManipulator::TYPE_RSS, null, null],
[false, TokenManipulator::TYPE_RSS, new \DateTime('-1 day'), 'data'],
];
}
public function testCreateBasketValidationToken()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createBasketValidationToken(self::$DI['basket_4'], self::$DI['user_1']);
$this->assertSame(self::$DI['basket_4']->getId(), $token->getData());
$this->assertSame(self::$DI['user_1'], $token->getUser());
$this->assertSame(TokenManipulator::TYPE_VALIDATE, $token->getType());
$this->assertDateNear('+10 days', $token->getExpiration());
}
public function testCreateBasketValidationTokenWithoutUser()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createBasketValidationToken(self::$DI['basket_4']);
$this->assertSame(self::$DI['basket_4']->getId(), $token->getData());
$this->assertSame(self::$DI['basket_4']->getValidation()->getInitiator(), $token->getUser());
$this->assertSame(TokenManipulator::TYPE_VALIDATE, $token->getType());
$this->assertDateNear('+10 days', $token->getExpiration());
}
public function testCreateBasketValidationTokenWithInvalidBasket()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$this->setExpectedException('InvalidArgumentException', 'A validation token requires a validation basket.');
$manipulator->createBasketValidationToken(self::$DI['basket_1']);
}
public function testCreateBasketAccessToken()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createBasketAccessToken(self::$DI['basket_4'], self::$DI['user']);
$this->assertSame(self::$DI['basket_4']->getId(), $token->getData());
$this->assertSame(self::$DI['user'], $token->getUser());
$this->assertSame(TokenManipulator::TYPE_VIEW, $token->getType());
$this->assertNull($token->getExpiration());
}
public function testCreateFeedEntryToken()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createFeedEntryToken(self::$DI['user'], self::$DI['feed_public_entry']);
$this->assertSame(self::$DI['feed_public_entry']->getId(), $token->getData());
$this->assertSame(self::$DI['user'], $token->getUser());
$this->assertSame(TokenManipulator::TYPE_FEED_ENTRY, $token->getType());
$this->assertNull($token->getExpiration());
}
public function testCreateDownloadToken()
{
$data = serialize(array('some' => 'data'));
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createDownloadToken(self::$DI['user'], $data);
$this->assertSame($data, $token->getData());
$this->assertSame(self::$DI['user'], $token->getUser());
$this->assertSame(TokenManipulator::TYPE_DOWNLOAD, $token->getType());
$this->assertDateNear('+3 hours', $token->getExpiration());
}
public function testCreateEmailExportToken()
{
$data = serialize(array('some' => 'data'));
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createEmailExportToken($data);
$this->assertSame($data, $token->getData());
$this->assertNull($token->getUser());
$this->assertSame(TokenManipulator::TYPE_EMAIL, $token->getType());
$this->assertDateNear('+1 day', $token->getExpiration());
}
public function testCreateResetEmailToken()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createResetEmailToken(self::$DI['user'], 'newemail@phraseanet.com');
$this->assertSame('newemail@phraseanet.com', $token->getData());
$this->assertSame(self::$DI['user'], $token->getUser());
$this->assertSame(TokenManipulator::TYPE_EMAIL_RESET, $token->getType());
$this->assertDateNear('+1 day', $token->getExpiration());
}
public function testCreateAccountUnlockToken()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createAccountUnlockToken(self::$DI['user']);
$this->assertNull($token->getData());
$this->assertSame(self::$DI['user'], $token->getUser());
$this->assertSame(TokenManipulator::TYPE_ACCOUNT_UNLOCK, $token->getType());
$this->assertDateNear('+3 days', $token->getExpiration());
}
public function testCreateResetPasswordToken()
{
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$token = $manipulator->createResetPasswordToken(self::$DI['user']);
$this->assertNull($token->getData());
$this->assertSame(self::$DI['user'], $token->getUser());
$this->assertSame(TokenManipulator::TYPE_PASSWORD, $token->getType());
$this->assertDateNear('+1 day', $token->getExpiration());
}
public function testUpdate()
{
$em = $this->createEntityManagerMock();
$token = new Token();
$em->expects($this->once())
->method('persist')
->with($token);
$em->expects($this->once())
->method('flush');
$manipulator = new TokenManipulator($em, self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$manipulator->update($token);
}
public function testDelete()
{
$em = $this->createEntityManagerMock();
$token = new Token();
$em->expects($this->once())
->method('remove')
->with($token);
$em->expects($this->once())
->method('flush');
$manipulator = new TokenManipulator($em, self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$manipulator->delete($token);
}
public function testRemoveExpiredToken()
{
$this->assertCount(4, self::$DI['app']['repo.tokens']->findAll());
$manipulator = new TokenManipulator(self::$DI['app']['EM'], self::$DI['app']['random.low'], self::$DI['app']['repo.tokens']);
$manipulator->removeExpiredTokens();
$this->assertCount(3, self::$DI['app']['repo.tokens']->findAll());
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Alchemy\Tests\Phrasea\Model\Repositories;
class TokenRepositoryTest extends \PhraseanetTestCase
{
public function testFindValidToken()
{
$repo = self::$DI['app']['repo.tokens'];
$this->assertSame(self::$DI['token_1'], $repo->findValidToken(self::$DI['token_1']->getValue()));
$this->assertSame(self::$DI['token_2'], $repo->findValidToken(self::$DI['token_2']->getValue()));
$this->assertNull($repo->findValidToken(self::$DI['token_invalid']->getValue()));
}
public function testFindValidationToken()
{
$repo = self::$DI['app']['repo.tokens'];
$this->assertSame(self::$DI['token_validation'], $repo->findValidationToken(self::$DI['basket_1'], self::$DI['user']));
}
public function testExpiredTokens()
{
$repo = self::$DI['app']['repo.tokens'];
$tokens = $repo->findExpiredTokens();
$this->assertCount(1, $tokens);
$this->assertSame(self::$DI['token_invalid'], array_pop($tokens));
}
}

View File

@@ -100,6 +100,58 @@ abstract class PhraseanetTestCase extends WebTestCase
return new Client($DI['app'], []); return new Client($DI['app'], []);
}); });
self::$DI['feed_public'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.feeds']->find(self::$fixtureIds['feed']['public']['feed']);
});
self::$DI['feed_public_entry'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.feed-entries']->find(self::$fixtureIds['feed']['public']['entry']);
});
self::$DI['feed_public_token'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.feed-tokens']->find(self::$fixtureIds['feed']['public']['token']);
});
self::$DI['feed_private'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.feeds']->find(self::$fixtureIds['feed']['private']['feed']);
});
self::$DI['feed_private_entry'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.feed-entries']->find(self::$fixtureIds['feed']['private']['entry']);
});
self::$DI['feed_private_token'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.feed-tokens']->find(self::$fixtureIds['feed']['private']['token']);
});
self::$DI['basket_1'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.baskets']->find(self::$fixtureIds['basket']['basket_1']);
});
self::$DI['basket_2'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.baskets']->find(self::$fixtureIds['basket']['basket_2']);
});
self::$DI['basket_3'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.baskets']->find(self::$fixtureIds['basket']['basket_3']);
});
self::$DI['basket_4'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.baskets']->find(self::$fixtureIds['basket']['basket_4']);
});
self::$DI['token_1'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.tokens']->find(self::$fixtureIds['token']['token_1']);
});
self::$DI['token_2'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.tokens']->find(self::$fixtureIds['token']['token_2']);
});
self::$DI['token_invalid'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.tokens']->find(self::$fixtureIds['token']['token_invalid']);
});
self::$DI['token_validation'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.tokens']->find(self::$fixtureIds['token']['token_validation']);
});
self::$DI['user'] = self::$DI->share(function ($DI) { self::$DI['user'] = self::$DI->share(function ($DI) {
return $DI['app']['repo.users']->find(self::$fixtureIds['user']['test_phpunit']); return $DI['app']['repo.users']->find(self::$fixtureIds['user']['test_phpunit']);
}); });
@@ -672,6 +724,14 @@ abstract class PhraseanetTestCase extends WebTestCase
->getMock(); ->getMock();
} }
protected function assertDateNear($expected, $tested, $precision = 1)
{
$tested = $tested instanceof \DateTime ? $tested : new \DateTime($tested);
$expected = $expected instanceof \DateTime ? $expected : new \DateTime($expected);
$this->assertLessThanOrEqual($precision, abs($expected->format('U') - $tested->format('U')));
}
protected function createSearchEngineMock() protected function createSearchEngineMock()
{ {
$mock = $this->getMock('Alchemy\Phrasea\SearchEngine\SearchEngineInterface'); $mock = $this->getMock('Alchemy\Phrasea\SearchEngine\SearchEngineInterface');

View File

@@ -1,107 +0,0 @@
<?php
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class randomTest extends \PhraseanetTestCase
{
protected $random;
public function setUp()
{
parent::setUp();
$this->random = new \random(self::$DI['app']);
}
public function testCleanTokens()
{
$expires_on = new DateTime('-5 minutes');
$usr_id = self::$DI['user']->getId();
$token = $this->random->getUrlToken(\random::TYPE_PASSWORD, $usr_id, $expires_on, 'some nice datas');
$this->random->cleanTokens(self::$DI['app']);
try {
$this->random->helloToken($token);
$this->fail();
} catch (NotFoundHttpException $e) {
}
}
public function testGetUrlToken()
{
$usr_id = self::$DI['user']->getId();
$token = $this->random->getUrlToken(\random::TYPE_PASSWORD, $usr_id, null, 'some nice datas');
$datas = $this->random->helloToken($token);
$this->assertEquals('some nice datas', $datas['datas']);
$this->random->updateToken($token, 'some very nice datas');
$datas = $this->random->helloToken($token);
$this->assertEquals('some very nice datas', $datas['datas']);
$this->random->removeToken($token);
}
public function testRemoveToken()
{
$this->testGetUrlToken();
}
public function testUpdateToken()
{
$this->testGetUrlToken();
}
public function testHelloToken()
{
$usr_id = self::$DI['user']->getId();
$token = $this->random->getUrlToken(\random::TYPE_PASSWORD, $usr_id, null, 'some nice datas');
$datas = $this->random->helloToken($token);
$this->assertEquals('some nice datas', $datas['datas']);
$this->assertNull($datas['expire_on']);
$created_on = new DateTime($datas['created_on']);
$date = new DateTime('-3 seconds');
$this->assertTrue($date < $created_on, "asserting that " . $date->format(DATE_ATOM) . " is before " . $created_on->format(DATE_ATOM));
$date = new DateTime();
$this->assertTrue($date >= $created_on);
$this->assertEquals('password', $datas['type']);
$this->random->removeToken($token);
try {
$this->random->helloToken($token);
$this->fail();
} catch (NotFoundHttpException $e) {
}
$expires_on = new DateTime('+5 minutes');
$usr_id = self::$DI['user']->getId();
$token = $this->random->getUrlToken(\random::TYPE_PASSWORD, $usr_id, $expires_on, 'some nice datas');
$datas = $this->random->helloToken($token);
$this->assertEquals('some nice datas', $datas['datas']);
$sql_expires = new DateTime($datas['expire_on']);
$this->assertTrue($sql_expires == $expires_on);
$created_on = new DateTime($datas['created_on']);
$date = new DateTime('-3 seconds');
$this->assertTrue($date < $created_on);
$date = new DateTime();
$this->assertTrue($date >= $created_on);
$this->assertEquals('password', $datas['type']);
$this->random->removeToken($token);
try {
$this->random->helloToken($token);
$this->fail();
} catch (NotFoundHttpException $e) {
}
$expires_on = new DateTime('-5 minutes');
$usr_id = self::$DI['user']->getId();
$token = $this->random->getUrlToken(\random::TYPE_PASSWORD, $usr_id, $expires_on, 'some nice datas');
try {
$this->random->helloToken($token);
$this->fail();
} catch (NotFoundHttpException $e) {
}
}
}