php - Prevent simultaneous user sessions in Symfony2 -


the goal

we providing client solution multiple-choice practice system students pay monthly membership in order test knowledge , prepare medical-related examinations. major issue providing solution in symfony2 students can buy 1 subscription, share credentials classmates , colleagues, , split cost of subscription on multiple concurrent logins.

in order minimize problem, we wish prevent more 1 simultaneous session being maintained in our symfony2 project.

research

massive amounts of google-fu led me sparse google group thread op briefly told use pdosessionhandler store sessions in database.

here's another question else worked around same thing, no explanation on how it.

progress far

i've implemented handler project , have security.interactive_login listener stores resulting session id user in database. progress here

public function __construct(securitycontext $securitycontext, doctrine $doctrine, container $container) {     $this->securitycontext = $securitycontext;     $this->doc = $doctrine;     $this->em              = $doctrine->getmanager();     $this->container        = $container; }  /**  * magic.  *   * @param interactiveloginevent $event  */ public function onsecurityinteractivelogin(interactiveloginevent $event) {     if ($this->securitycontext->isgranted('is_authenticated_fully')) {         // user has logged in     }      if ($this->securitycontext->isgranted('is_authenticated_remembered')) {         // user has logged in using remember_me cookie     }      // first user object can work     $user = $event->getauthenticationtoken()->getuser();      // check see if they're subscriber     if ($this->securitycontext->isgranted('role_subscribed')) {         // check expiry date versus         if ($user->getexpiry() < new \datetime('now')) { // if expiry date past now, need remove role             $user->removerole('role_subscribed');             $this->em->persist($user);             $this->em->flush();             // we've removed role, have make new token , load session             $token = new \symfony\component\security\core\authentication\token\usernamepasswordtoken(                 $user,                 null,                 'main',                 $user->getroles()             );             $this->securitycontext->settoken($token);         }     }      // current session , associate user     $sessionid = $this->container->get('session')->getid();     $user->setsessionid($sessionid);     $this->em->persist($user);     $s = $this->doc->getrepository('imcqbundle:session')->find($sessionid);     if ($s) { // $s = false, part doesn't execute         $s->setuserid($user->getid());         $this->em->persist($s);     }     $this->em->flush();      // have log out other users sharing same username outside of current session token     // ... code detach other `imcqbundle:session` entities userid = logged in user } 

the problem

the session isn't stored database pdosessionhandler until after security.interactive_login listener finished, therefore user id never ends getting stored session table. how can make work? can have user id store in session table?

alternatively, there better way of going this? turning out extremely frustrating symfony don't think ever designed have exclusive single user sessions each user.

i've solved own problem, leave question open dialogue (if any) before i'm able accept own answer.

i created kernel.request listener check user's current session id latest session id associated user upon each login.

here's code:

<?php  namespace acme\bundle\listener;  use symfony\component\httpkernel\event\getresponseevent; use symfony\component\httpkernel\httpkernel; use symfony\component\httpfoundation\redirectresponse; use symfony\component\security\core\securitycontext; use symfony\component\dependencyinjection\container; use symfony\component\routing\router;  /**  * custom session listener.  */ class sessionlistener {      private $securitycontext;      private $container;      private $router;      public function __construct(securitycontext $securitycontext, container $container, router $router)     {         $this->securitycontext = $securitycontext;         $this->container = $container;         $this->router = $router;     }      public function onkernelrequest(getresponseevent $event)     {         if (!$event->ismasterrequest()) {             return;         }          if ($token = $this->securitycontext->gettoken()) { // check token - or else isgranted() fail on assets             if ($this->securitycontext->isgranted('is_authenticated_fully') || $this->securitycontext->isgranted('is_authenticated_remembered')) { // check if there authenticated user                 // compare stored session id current session id user                  if ($token->getuser() && $token->getuser()->getsessionid() !== $this->container->get('session')->getid()) {                     // tell user else has logged on different device                     $this->container->get('session')->getflashbag()->set(                         'error',                         'another device has logged on username , password. log in again, please enter credentials below. please note other device logged out.'                     );                     // kick user out, because new user has logged in                     $this->securitycontext->settoken(null);                     // redirect user login page, or else they'll still trying access dashboard (which no longer have access to)                     $response = new redirectresponse($this->router->generate('sonata_user_security_login'));                     $event->setresponse($response);                     return $event;                 }             }         }     } } 

and services.yml entry:

services:     acme.session.listener:         class: acme\bundle\listener\sessionlistener         arguments: ['@security.context', '@service_container', '@router']         tags:             - { name: kernel.event_listener, event: kernel.request, method: onkernelrequest } 

it's interesting note spent embarrassing amount of time wondering why listener making application break when realized had named imcq.session.listener session_listener. turns out symfony (or other bundle) using name, , therefore overriding behaviour.

be careful! break implicit login functionality on fosuserbundle 1.3.x. should either upgrade 2.0.x-dev , use implicit login event or replace loginlistener own fos_user.security.login_manager service. (i did latter because i'm using sonatauserbundle)

by request, here's full solution fosuserbundle 1.3.x:

for implicit logins, add services.yml:

fos_user.security.login_manager:     class: acme\bundle\security\loginmanager     arguments: ['@security.context', '@security.user_checker', '@security.authentication.session_strategy', '@service_container', '@doctrine'] 

and make file under acme\bundle\security named loginmanager.php code:

<?php  namespace acme\bundle\security;  use fos\userbundle\security\loginmanagerinterface;  use fos\userbundle\model\userinterface; use symfony\component\dependencyinjection\containerinterface; use symfony\component\httpfoundation\response; use symfony\component\security\core\authentication\token\usernamepasswordtoken; use symfony\component\security\core\user\usercheckerinterface; use symfony\component\security\core\securitycontextinterface; use symfony\component\security\http\rememberme\remembermeservicesinterface; use symfony\component\security\http\session\sessionauthenticationstrategyinterface;  use doctrine\bundle\doctrinebundle\registry doctrine; // symfony 2.1.0+  class loginmanager implements loginmanagerinterface {     private $securitycontext;     private $userchecker;     private $sessionstrategy;     private $container;     private $em;      public function __construct(securitycontextinterface $context, usercheckerinterface $userchecker,                                 sessionauthenticationstrategyinterface $sessionstrategy,                                 containerinterface $container,                                 doctrine $doctrine)     {         $this->securitycontext = $context;         $this->userchecker = $userchecker;         $this->sessionstrategy = $sessionstrategy;         $this->container = $container;         $this->em = $doctrine->getmanager();     }      final public function loginuser($firewallname, userinterface $user, response $response = null)     {         $this->userchecker->checkpostauth($user);          $token = $this->createtoken($firewallname, $user);          if ($this->container->isscopeactive('request')) {             $this->sessionstrategy->onauthentication($this->container->get('request'), $token);              if (null !== $response) {                 $remembermeservices = null;                 if ($this->container->has('security.authentication.rememberme.services.persistent.'.$firewallname)) {                     $remembermeservices = $this->container->get('security.authentication.rememberme.services.persistent.'.$firewallname);                 } elseif ($this->container->has('security.authentication.rememberme.services.simplehash.'.$firewallname)) {                     $remembermeservices = $this->container->get('security.authentication.rememberme.services.simplehash.'.$firewallname);                 }                  if ($remembermeservices instanceof remembermeservicesinterface) {                     $remembermeservices->loginsuccess($this->container->get('request'), $response, $token);                 }             }         }          $this->securitycontext->settoken($token);          // here's custom part, need current session , associate user         $sessionid = $this->container->get('session')->getid();         $user->setsessionid($sessionid);         $this->em->persist($user);         $this->em->flush();     }      protected function createtoken($firewall, userinterface $user)     {         return new usernamepasswordtoken($user, null, $firewall, $user->getroles());     } } 

for more important interactive logins, should add services.yml:

login_listener:     class: acme\bundle\listener\loginlistener     arguments: ['@security.context', '@doctrine', '@service_container']     tags:         - { name: kernel.event_listener, event: security.interactive_login, method: onsecurityinteractivelogin } 

and subsequent loginlistener.php interactive login events:

<?php  namespace acme\bundle\listener;  use symfony\component\security\http\event\interactiveloginevent; use symfony\component\security\core\securitycontext; use symfony\component\dependencyinjection\container; use doctrine\bundle\doctrinebundle\registry doctrine; // symfony 2.1.0+  /**  * custom login listener.  */ class loginlistener {     /** @var \symfony\component\security\core\securitycontext */     private $securitycontext;      /** @var \doctrine\orm\entitymanager */     private $em;      private $container;      private $doc;      /**      * constructor      *       * @param securitycontext $securitycontext      * @param doctrine        $doctrine      */     public function __construct(securitycontext $securitycontext, doctrine $doctrine, container $container)     {         $this->securitycontext = $securitycontext;         $this->doc = $doctrine;         $this->em              = $doctrine->getmanager();         $this->container        = $container;     }      /**      * magic.      *       * @param interactiveloginevent $event      */     public function onsecurityinteractivelogin(interactiveloginevent $event)     {         if ($this->securitycontext->isgranted('is_authenticated_fully')) {             // user has logged in         }          if ($this->securitycontext->isgranted('is_authenticated_remembered')) {             // user has logged in using remember_me cookie         }          // first user object can work         $user = $event->getauthenticationtoken()->getuser();          // current session , associate user         //$user->setsessionid($this->securitycontext->gettoken()->getcredentials());         $sessionid = $this->container->get('session')->getid();         $user->setsessionid($sessionid);         $this->em->persist($user);         $this->em->flush();          // ...     } } 

Comments

Popular posts from this blog

javascript - Jquery show_hide, what to add in order to make the page scroll to the bottom of the hidden field once button is clicked -

javascript - Highcharts multi-color line -

javascript - Enter key does not work in search box -