mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-23 18:03:17 +00:00
Use subscriber to check session idle time
This commit is contained in:
@@ -0,0 +1,126 @@
|
|||||||
|
<?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\Event\Subscriber;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Application;
|
||||||
|
use Entities\SessionModule;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
|
|
||||||
|
/**log real human activity on application, to keep session alive*/
|
||||||
|
class SessionManagerSubscriber implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
public function __construct(Application $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
KernelEvents::REQUEST => array('onKernelRequest', -112),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onKernelRequest(GetResponseEvent $event)
|
||||||
|
{
|
||||||
|
$modulesIds = array(
|
||||||
|
"prod" => 1,
|
||||||
|
"client" => 2,
|
||||||
|
"admin" => 3,
|
||||||
|
"thesaurus" => 5,
|
||||||
|
"report" => 10,
|
||||||
|
"lightbox" => 6,
|
||||||
|
);
|
||||||
|
|
||||||
|
$pathInfo = array_filter(explode('/', $event->getRequest()->getPathInfo()));
|
||||||
|
|
||||||
|
if(count($pathInfo) < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$moduleName = strtolower($pathInfo[1]);
|
||||||
|
if (!array_key_exists($moduleName, $modulesIds) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this route is polled by js in admin/databox to refresh infos (progress bar...)
|
||||||
|
if (preg_match("#^/admin/databox/[0-9]+/informations/documents/#", $event->getRequest()->getPathInfo()) == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this route is polled by js in admin/tasks to refresh tasks status
|
||||||
|
if ($event->getRequest()->getPathInfo() == "/admin/task-manager/tasks/" && $event->getRequest()->getContentType() == 'json') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are already disconnected (ex. from another window), quit immediatly
|
||||||
|
if (!($this->app['authentication']->isAuthenticated())) {
|
||||||
|
if ($event->getRequest()->isXmlHttpRequest()) {
|
||||||
|
$response = new Response("End-Session", 403);
|
||||||
|
} else {
|
||||||
|
$response = new RedirectResponse($this->app["url_generator"]->generate("homepage", array("redirect"=>'..' . $event->getRequest()->getPathInfo())));
|
||||||
|
}
|
||||||
|
$response->headers->set('X-Phraseanet-End-Session', '1');
|
||||||
|
|
||||||
|
$event->setResponse($response);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$session = $this->app['EM']->find('Entities\Session', $this->app['session']->get('session_id'));
|
||||||
|
|
||||||
|
$idle = 0;
|
||||||
|
if (isset($this->app["phraseanet.configuration"]["session"]["idle"])) {
|
||||||
|
$idle = (int)($this->app["phraseanet.configuration"]["session"]["idle"]);
|
||||||
|
}
|
||||||
|
$now = new \DateTime();
|
||||||
|
$dt = $now->getTimestamp() - $session->getUpdated()->getTimestamp();
|
||||||
|
if ($idle > 0 && $dt > $idle) {
|
||||||
|
// we must disconnet due to idletime
|
||||||
|
$this->app['authentication']->closeAccount();
|
||||||
|
if ($event->getRequest()->isXmlHttpRequest()) {
|
||||||
|
$response = new Response("End-Session", 403);
|
||||||
|
} else {
|
||||||
|
$response = new RedirectResponse($this->app["url_generator"]->generate("homepage", array("redirect"=>'..' . $event->getRequest()->getPathInfo())));
|
||||||
|
}
|
||||||
|
$response->headers->set('X-Phraseanet-End-Session', '1');
|
||||||
|
|
||||||
|
$event->setResponse($response);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$moduleId = $modulesIds[$moduleName];
|
||||||
|
|
||||||
|
$session->setUpdated(new \DateTime());
|
||||||
|
|
||||||
|
if (!$session->hasModuleId($moduleId)) {
|
||||||
|
$module = new SessionModule();
|
||||||
|
$module->setModuleId($moduleId);
|
||||||
|
$module->setSession($session);
|
||||||
|
$session->addModule($module);
|
||||||
|
|
||||||
|
$this->app['EM']->persist($module);
|
||||||
|
} else {
|
||||||
|
$this->app['EM']->persist($session->getModuleById($moduleId)->setUpdated(new \DateTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->app['EM']->persist($session);
|
||||||
|
$this->app['EM']->flush();
|
||||||
|
}
|
||||||
|
}
|
@@ -45,14 +45,22 @@ class PhraseaAuthenticationForm extends AbstractType
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
$builder->add('remember-me', $this->app['phraseanet.configuration']['session']['idle'] < 1 ? 'checkbox' : 'hidden', array(
|
if ($this->app['phraseanet.configuration']['session']['idle'] < 1) {
|
||||||
'label' => $this->app['phraseanet.configuration']['session']['idle'] < 1 ? _('Remember me') : "",
|
$builder->add('remember-me', 'checkbox' , array(
|
||||||
'mapped' => false,
|
'label' => _('Remember me'),
|
||||||
'required' => false,
|
'mapped' => false,
|
||||||
'attr' => array(
|
'required' => false,
|
||||||
'value' => '1',
|
'attr' => array(
|
||||||
)
|
'value' => '1',
|
||||||
));
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
$builder->add('remember-me', 'hidden' , array(
|
||||||
|
'label' => '',
|
||||||
|
'mapped' => false,
|
||||||
|
'required' => false
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
$builder->add('redirect', 'hidden', array(
|
$builder->add('redirect', 'hidden', array(
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@@ -1738,6 +1738,34 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
|
|||||||
$this->assertSame(200, self::$DI['client']->getResponse()->getStatusCode());
|
$this->assertSame(200, self::$DI['client']->getResponse()->getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testLoginPageWithIdleSessionTime()
|
||||||
|
{
|
||||||
|
$this->logout(self::$DI['app']);
|
||||||
|
self::$DI['app']['phraseanet.configuration']['session'] = array(
|
||||||
|
'idle' =>10,
|
||||||
|
'lifetime' => 60475,
|
||||||
|
);
|
||||||
|
|
||||||
|
$crawler = self::$DI['client']->request('GET', '/login/');
|
||||||
|
|
||||||
|
$this->assertSame(200, self::$DI['client']->getResponse()->getStatusCode());
|
||||||
|
$this->assertEquals('hidden', $crawler->filter('input[name="remember-me"]')->attr('type'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoginPageWithNoIdleSessionTime()
|
||||||
|
{
|
||||||
|
$this->logout(self::$DI['app']);
|
||||||
|
self::$DI['app']['phraseanet.configuration']['session'] = array(
|
||||||
|
'idle' => 0,
|
||||||
|
'lifetime' => 60475,
|
||||||
|
);
|
||||||
|
|
||||||
|
$crawler = self::$DI['client']->request('GET', '/login/');
|
||||||
|
|
||||||
|
$this->assertSame(200, self::$DI['client']->getResponse()->getStatusCode());
|
||||||
|
$this->assertEquals('checkbox', $crawler->filter('input[name="remember-me"]')->attr('type'));
|
||||||
|
}
|
||||||
|
|
||||||
private function addUsrAuthDoctrineEntitySupport($id, $out, $participants = false)
|
private function addUsrAuthDoctrineEntitySupport($id, $out, $participants = false)
|
||||||
{
|
{
|
||||||
$repo = $this->getMockBuilder('Doctrine\ORM\EntityRepository\UsrAuthProviderRepository')
|
$repo = $this->getMockBuilder('Doctrine\ORM\EntityRepository\UsrAuthProviderRepository')
|
||||||
|
@@ -0,0 +1,227 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Tests\Phrasea\Core\Event\Subscriber;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Core\Event\Subscriber\SessionManagerSubscriber;
|
||||||
|
use Alchemy\Phrasea\Application;
|
||||||
|
use Entities\Session;
|
||||||
|
use Symfony\Component\HttpKernel\Client;
|
||||||
|
|
||||||
|
class SessionManagerSubscriberTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
|
||||||
|
{
|
||||||
|
public function testEndSession()
|
||||||
|
{
|
||||||
|
$app = new Application('test');
|
||||||
|
$app['dispatcher']->addSubscriber(new SessionManagerSubscriber($app));
|
||||||
|
$app['phraseanet.configuration']['session'] = array(
|
||||||
|
'idle' => 0,
|
||||||
|
'lifetime' => 60475,
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->get('/login', function () {
|
||||||
|
return '';
|
||||||
|
})->bind("homepage");
|
||||||
|
|
||||||
|
$app->get('/prod', function () {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$client = new Client($app);
|
||||||
|
$client->request('GET', '/prod');
|
||||||
|
|
||||||
|
$this->assertTrue($client->getResponse()->isRedirect());
|
||||||
|
$this->assertNotNUll($client->getResponse()->headers->get('x-phraseanet-end-session'));
|
||||||
|
$this->assertNotNUll($client->getResponse()->headers->get('location'));
|
||||||
|
$this->assertEquals('/login?redirect=..%2Fprod', $client->getResponse()->headers->get('location'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndSessionXmlXhttpRequest()
|
||||||
|
{
|
||||||
|
$app = new Application('test');
|
||||||
|
$app['dispatcher']->addSubscriber(new SessionManagerSubscriber($app));
|
||||||
|
$app['phraseanet.configuration']['session'] = array(
|
||||||
|
'idle' => 0,
|
||||||
|
'lifetime' => 60475,
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->get('/login', function () {
|
||||||
|
return '';
|
||||||
|
})->bind("homepage");
|
||||||
|
|
||||||
|
$app->get('/prod', function () {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$client = new Client($app);
|
||||||
|
$client->request('GET', '/prod', array(), array(), array(
|
||||||
|
'HTTP_ACCEPT' => 'application/json',
|
||||||
|
'HTTP_X-Requested-With' => 'XMLHttpRequest',
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertTrue($client->getResponse()->isClientError());
|
||||||
|
$this->assertNotNUll($client->getResponse()->headers->get('x-phraseanet-end-session'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndSessionAuthenticated()
|
||||||
|
{
|
||||||
|
$app = new Application('test');
|
||||||
|
$app['dispatcher']->addSubscriber(new SessionManagerSubscriber($app));
|
||||||
|
$app['authentication'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\Authenticator')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['authentication']->expects($this->any())->method('isAuthenticated')->will($this->returnValue(true));
|
||||||
|
|
||||||
|
$session = new Session();
|
||||||
|
$session->setUpdated(new \DateTime());
|
||||||
|
|
||||||
|
$app['EM'] = $this->getMockBuilder('Doctrine\ORM\EntityManager')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['EM']->expects($this->once())->method('find')->with($this->equalTo('Entities\Session'))->will($this->returnValue($session));
|
||||||
|
$app['EM']->expects($this->exactly(2))->method('persist')->will($this->returnValue(null));
|
||||||
|
$app['EM']->expects($this->once())->method('flush')->will($this->returnValue(null));
|
||||||
|
|
||||||
|
$app['phraseanet.configuration']['session'] = array(
|
||||||
|
'idle' => 0,
|
||||||
|
'lifetime' => 60475,
|
||||||
|
);
|
||||||
|
$app->get('/login', function () {
|
||||||
|
return '';
|
||||||
|
})->bind("homepage");
|
||||||
|
|
||||||
|
$app->get('/prod', function () {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$client = new Client($app);
|
||||||
|
$client->request('GET', '/prod');
|
||||||
|
|
||||||
|
$this->assertTrue($client->getResponse()->isOK());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndSessionAuthenticatedWithOutdatedIdle()
|
||||||
|
{
|
||||||
|
$app = new Application('test');
|
||||||
|
$app['dispatcher']->addSubscriber(new SessionManagerSubscriber($app));
|
||||||
|
$app['authentication'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\Authenticator')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['authentication']->expects($this->any())->method('isAuthenticated')->will($this->returnValue(true));
|
||||||
|
$app['authentication']->expects($this->once())->method('closeAccount')->will($this->returnValue(null));
|
||||||
|
|
||||||
|
$session = new Session();
|
||||||
|
$session->setUpdated(new \DateTime('-1 hour'));
|
||||||
|
|
||||||
|
$app['EM'] = $this->getMockBuilder('Doctrine\ORM\EntityManager')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['EM']->expects($this->once())->method('find')->with($this->equalTo('Entities\Session'))->will($this->returnValue($session));
|
||||||
|
|
||||||
|
$app['phraseanet.configuration']['session'] = array(
|
||||||
|
'idle' => 10,
|
||||||
|
'lifetime' => 60475,
|
||||||
|
);
|
||||||
|
$app->get('/login', function () {
|
||||||
|
return '';
|
||||||
|
})->bind("homepage");
|
||||||
|
|
||||||
|
$app->get('/prod', function () {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$client = new Client($app);
|
||||||
|
$client->request('GET', '/prod');
|
||||||
|
|
||||||
|
$this->assertTrue($client->getResponse()->isRedirect());
|
||||||
|
$this->assertNotNUll($client->getResponse()->headers->get('x-phraseanet-end-session'));
|
||||||
|
$this->assertNotNUll($client->getResponse()->headers->get('location'));
|
||||||
|
$this->assertEquals('/login?redirect=..%2Fprod', $client->getResponse()->headers->get('location'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndSessionAuthenticatedWithOutdatedIdleXmlHttpRequest()
|
||||||
|
{
|
||||||
|
$app = new Application('test');
|
||||||
|
$app['dispatcher']->addSubscriber(new SessionManagerSubscriber($app));
|
||||||
|
$app['authentication'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\Authenticator')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['authentication']->expects($this->any())->method('isAuthenticated')->will($this->returnValue(true));
|
||||||
|
$app['authentication']->expects($this->once())->method('closeAccount')->will($this->returnValue(null));
|
||||||
|
|
||||||
|
$session = new Session();
|
||||||
|
$session->setUpdated(new \DateTime('-1 hour'));
|
||||||
|
|
||||||
|
$app['EM'] = $this->getMockBuilder('Doctrine\ORM\EntityManager')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['EM']->expects($this->once())->method('find')->with($this->equalTo('Entities\Session'))->will($this->returnValue($session));
|
||||||
|
|
||||||
|
$app['phraseanet.configuration']['session'] = array(
|
||||||
|
'idle' => 10,
|
||||||
|
'lifetime' => 60475,
|
||||||
|
);
|
||||||
|
$app->get('/login', function () {
|
||||||
|
return '';
|
||||||
|
})->bind("homepage");
|
||||||
|
|
||||||
|
$app->get('/prod', function () {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$client = new Client($app);
|
||||||
|
$client->request('GET', '/prod', array(), array(), array(
|
||||||
|
'HTTP_ACCEPT' => 'application/json',
|
||||||
|
'HTTP_X-Requested-With' => 'XMLHttpRequest',
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertTrue($client->getResponse()->isClientError());
|
||||||
|
$this->assertNotNUll($client->getResponse()->headers->get('x-phraseanet-end-session'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUndefinedModule()
|
||||||
|
{
|
||||||
|
$app = new Application('test');
|
||||||
|
$app['dispatcher']->addSubscriber(new SessionManagerSubscriber($app));
|
||||||
|
|
||||||
|
$app->get('/login', function () {
|
||||||
|
return '';
|
||||||
|
})->bind("homepage");
|
||||||
|
|
||||||
|
$app->get('/undefined-module', function () {
|
||||||
|
return 'undefined-module';
|
||||||
|
});
|
||||||
|
|
||||||
|
$client = new Client($app);
|
||||||
|
$client->request('GET', '/undefined-module');
|
||||||
|
|
||||||
|
$this->assertTrue($client->getResponse()->isOk());
|
||||||
|
$this->assertEquals('undefined-module', $client->getResponse()->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider forbiddenRouteProvider
|
||||||
|
*/
|
||||||
|
public function testForbiddenRoutes($route)
|
||||||
|
{
|
||||||
|
$app = new Application('test');
|
||||||
|
$app['dispatcher']->addSubscriber(new SessionManagerSubscriber($app));
|
||||||
|
$app['authentication'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\Authenticator')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['authentication']->expects($this->never())->method('isAuthenticated');
|
||||||
|
|
||||||
|
$app['EM'] = $this->getMockBuilder('Doctrine\ORM\EntityManager')->disableOriginalConstructor()->getMock();
|
||||||
|
$app['EM']->expects($this->never())->method('flush');
|
||||||
|
|
||||||
|
|
||||||
|
$app->get('/login', function () {
|
||||||
|
return '';
|
||||||
|
})->bind("homepage");
|
||||||
|
|
||||||
|
$app->get($route, function () {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$client = new Client($app);
|
||||||
|
$client->request('GET', $route, array(), array(), array(
|
||||||
|
'HTTP_CONTENT-TYPE' => 'application/json',
|
||||||
|
'HTTP_ACCEPT' => 'application/json',
|
||||||
|
'HTTP_X-Requested-With' => 'XMLHttpRequest',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forbiddenRouteProvider()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('/admin/databox/17/informations/documents/'),
|
||||||
|
array('/admin/task-manager/tasks/'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,6 @@ class PhraseaAuthenticationFormTest extends FormTestCase
|
|||||||
{
|
{
|
||||||
protected function getForm()
|
protected function getForm()
|
||||||
{
|
{
|
||||||
return new PhraseaAuthenticationForm();
|
return new PhraseaAuthenticationForm(self::$DI['app']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user