From a mail report (translated from french):

When using session_proxy and redis module altogether, the session cookie was not destroyed upon user logout:

$conf['session_inc'] = 'sites/all/modules/contrib/session_proxy/session.inc';

$conf['session_storage_force_default'] = FALSE;
$conf['session_storage_class'] = 'SessionProxy_Storage_Cache';
$conf['session_storage_options']['cache_backend'] = 'Redis_Cache';

In order to destroy this cookie, the session_proxy\lib\SessionProxy\Storage\Cache.php was modified:

public function destroy($sessionId) {
     // Delete session data.
+ SessionProxy_Helper::deleteSessionCookie($this->sessionName);
...
}

This probably isn't the best place to destroy this cookie, nor the best way to do it...

Comments

pounard’s picture

Issue summary: View changes
yveslaroche’s picture

I have the same issue when using the native PHP session handling with Redis. The session cookie is never destroyed.

shubhraprakash’s picture

Well as I see it, session_proxy module has not implemented any session cookie deletion functions before session_destroy() is called due to it's design as a proxy. The below patch will delete the session cookie during hook_user_logout but may cause issues if someone implements this hook and looks for the session cookie. But otherwise this will do the job.

Index: session_proxy.module
===================================================================
--- session_proxy.module	(revision 1)
+++ session_proxy.module	(revision 2)
@@ -4,3 +4,27 @@
  * @file
  * Empty placeholder module file.
  */
+
+/**
+ *	Copied core session.inc session cookie delete function
+**/
+function _session_proxy_session_delete_cookie($name, $secure = NULL) {
+  global $is_https;
+  if (isset($_COOKIE[$name]) || (!$is_https && $secure === TRUE)) {
+    $params = session_get_cookie_params();
+    if ($secure !== NULL) {
+      $params['secure'] = $secure;
+    }
+    setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
+    unset($_COOKIE[$name]);
+  }
+}
+
+/**
+ *	Implementing hook_user_logout
+**/
+function session_proxy_user_logout($account)
+{
+	// Delete the session cookie to sync with core session handling
+	_session_proxy_session_delete_cookie(session_name());
+}
\ No newline at end of file
shubhraprakash’s picture

Please see above comment as I posted the same comment twice.

pounard’s picture

I have to figure out something better regarding the design, but thanks for spotting that. I'll try to see that asap.

pounard’s picture

Ok indeed, problem is I didn't have a cookie delete handler on the session handler, which means that when session_destroy() is called, setcookie() was not.

The actual code is present but commented out, I guess I had problems with it, I'm investigating.

pounard’s picture

And I understood where it fails, it happens only when session_destroy() is called using the native implementation.

pounard’s picture

Ok, and that's because, as documented in https://secure.php.net/manual/en/function.session-destroy.php session_destroy() does not handle the cookie itself, but native implementation has no handler on destroy.

pounard’s picture

It took me a while to arrive to same conclusion as you, I have no other choice than react upon the user_logout() hook I guess. There are a few use case where it will prove impossible to delete the session cookie at all, for example: we ourselves do not enable the module but just use the proxy as-is. Still searching for a better way.

  • Pierre.R committed 9dc9c85 on 7.x-1.x
    Issue #2513816 by pounard, shubhraprakash: When using the cache session...

pounard credited Pierre.R.

pounard’s picture

Status: Active » Fixed

I did some fixes and rewrote a few things, but basically it's thanks to you that this fix exists, thanks a lot !

shubhraprakash’s picture

Well there could be another way of doing it as below and is a better way to deal with it. I avoided it since it was tweaking the original code.

Session handler functions can be registered for native session handling also using session_set_save_handler. Let other functions just return TRUE and let the destroy session handling function call the delete cookie function. This will be very similar to how lib/SessionProxy/Backend/Default.php is doing it.

pounard’s picture

Oh, I didn't know that, if I have some spare time, some day, I'll try to improve this. Thanks anyway!

shubhraprakash’s picture

Perhaps registering session handler functions may hijack native session handling altogether even if we are trying to override only the session destroy part. I will update if I find a better way to deal to delete session cookies once session is destroyed. The current commit should be good enough for now.

I have one more suggestion. That is to use cache to store $user global data even when we are using native session handling, this will help in bypassing DB altogether when using native session handling with a non-DB cache like memcache. May be we can utilize a hook to clear the session user data cache when a user is updated. This will be great performance wise for authenticated users.

shubhraprakash’s picture

May be we can try this patch. I have redone the cookie deletion part and it may be different from what is committed in the previous comment but should get the idea across to modify accordingly.

Index: lib/SessionProxy/Backend/Native.php
===================================================================
--- lib/SessionProxy/Backend/Native.php	(revision 1)
+++ lib/SessionProxy/Backend/Native.php	(revision 2)
@@ -60,4 +60,81 @@
   public function destroyAllForUser($uid) {
     return;
   }
+  
+  /**
+   * Refresh global user data following the actual session state.
+   */
+  protected function updateUser() {
+    global $user;
+
+    $uid = $this->getSessionUid();
+
+    if (!empty($uid)) {
+	  $user = NULL;
+	  
+	  $cached_user_entity = cache_get($this->cacheCidPrefix() . $uid, $this->cacheBin() );
+	  
+	  $user = empty($cached_user_entity) ? NULL : $cached_user_entity->data;
+	  
+	  if (empty($user))
+	  {
+		  $user = db_query("SELECT u.* FROM {users} u WHERE u.uid = :uid", array(':uid' => $uid))->fetchObject();
+		 
+		  if (1 == $user->status) {
+			$user->data = unserialize($user->data);
+			$user->roles = array();
+			$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
+			$user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1);
+			
+			// Set user entity fetched from DB into cache
+			cache_set($this->cacheCidPrefix() . $user->uid, $user, $this->cacheBin());
+		  } else {
+			$user = drupal_anonymous_user();
+		  }
+	  }elseif(1 != $user->status)
+	  {
+		  $user = drupal_anonymous_user();
+	  }
+    } else {
+      $user = drupal_anonymous_user();
+    }
+
+    // The 'session' attribute is an insanity and should be removed.
+    $user->session = '';
+    $user->timestamp = REQUEST_TIME;
+
+    // Avoid some PHP warnings with backends using it (mongodb module does
+    // check it). Some backends may set this variable, if they keep it, some
+    // other won't.
+    if (!isset($user->cache)) {
+      $user->cache = 0;
+    }
+
+    // Do not update access time more than once per 180 seconds. Also check
+    // for an active database connection: actual core will have one, but in
+    // the late future we may have session handling without database at all.
+    if (!$this->userAccessUpdated && Database::isActiveConnection() && $user->uid && (REQUEST_TIME - $user->access > variable_get('session_write_interval', 180))) {
+      db_update('users')
+        ->fields(array('access' => REQUEST_TIME))
+        ->condition('uid', $user->uid)
+        ->execute();
+      $this->userAccessUpdated = TRUE;
+    }
+  }
+  
+	/**
+	 * Get cache bin for cached data
+	**/
+	public static function cacheBin()
+	{
+		return 'cache_sessions';
+	}
+
+	/**
+	 * Get prefix for cid of cached data
+	**/
+	public static function cacheCidPrefix()
+	{
+		return 'session_user_entity_';
+	}
 }
Index: session_proxy.module
===================================================================
--- session_proxy.module	(revision 1)
+++ session_proxy.module	(revision 2)
@@ -4,3 +4,63 @@
  * @file
  * Empty placeholder module file.
  */
+
+/**
+ *	Copied core session.inc session cookie delete function
+**/
+function _session_proxy_session_delete_cookie($name, $secure = NULL) {
+  global $is_https;
+  if (isset($_COOKIE[$name]) || (!$is_https && $secure === TRUE)) {
+    $params = session_get_cookie_params();
+    if ($secure !== NULL) {
+      $params['secure'] = $secure;
+    }
+    setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
+    unset($_COOKIE[$name]);
+  }
+}
+
+/**
+ * Clear cache related to sessions data
+**/
+function _session_proxy_clear_cache($uid)
+{
+	// Delete session related cache data
+	if( !empty($uid) )
+	{
+		cache_clear_all(SessionProxy_Backend_Native::cacheCidPrefix() . $uid, SessionProxy_Backend_Native::cacheBin());
+	}
+}
+
+/**
+ *	Implementing hook_user_logout
+**/
+function session_proxy_user_logout($account)
+{
+	// Delete session related cache data
+	_session_proxy_clear_cache($account->uid);
+		
+	if($account->uid == $_SESSION['uid'])
+	{
+		// Delete the session cookie to sync with core session handling
+		_session_proxy_session_delete_cookie(session_name());
+	}
+}
+
+/**
+ *	Implementing hook_user_update
+**/
+function session_proxy_user_update(&$edit, $account, $category)
+{
+	// Delete session related cache data
+	_session_proxy_clear_cache($account->uid);
+}
+
+/**
+ *	Implementing hook_user_delete
+**/
+function session_proxy_user_delete($account)
+{
+	// Delete session related cache data
+	_session_proxy_clear_cache($account->uid);
+}
\ No newline at end of file

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.