• 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Session serialized data... kinda? Dont understand the format

#21
(08-26-2015, 08:55 AM)mwhitney Wrote: It sounds like you've gotten to a point where things are more complicated than they should be.

If I understand this correctly, you want to know which sessions are open for a given account_id. When the session library calls sess_regenerate(), it is essentially saying that the old session is closed and a new session has been opened using the existing data. So, when sess_regenerate() is called, you basically just need to notify your Accounts_model of this situation, which might be something like this:





PHP Code:
public function sess_regenerate($destroy FALSE)
{
 
   $oldId session_id();
 
   parent::sess_regenerate($destroy);
 
   $newId session_id();

 
   if (! class_exists('accounts_model'false)) {
 
       $this->load->model('accounts_model');
 
   }
 
   $this->accounts_model->sessionRegenerate($oldId$newId);


How you handle this in your Accounts_model depends on how you're using the relations in the database, but you shouldn't need to touch the session. If you need to keep the entry with the $oldId value until a write occurs, then I would recommend adding a nullable column to your table for the $newId value. Then, in Accounts_model->sessionRegenerate(), you would update the entry with $oldId to add the $newId value to the new column, and insert a new entry with the $newId in the normal session column. Then, when the write occurs, you would notify the Accounts_model and it could go back through and delete the row with the $newId in the new column. If you have the potential for regenerating the session multiple times before writing the session, you would simply have to go back through the chain, finding the rows with each of the previous session IDs and deleting all of them.

If the Accounts_model doesn't access the session in its constructor, this won't be a problem. Further, the only thing the model should really do in terms of accessing the session is store/retrieve the account_id in the session. Any time you access the account_id in the model, it should be through get/set methods which access the session only when the value hasn't been retrieved (on get) or when it changes (on set). The session ID shouldn't be needed in the Accounts_model except for those relationships, so the session ID can be passed to the model from the session library or retrieved from the reference table.

Actually my issues a little different.

Let me explain the end goal here.

So, this app will be able to be used via API, CLI or HTTP, and it can be setup so people can use the login more than once (EG: an API account "Service account", or something). So I need it so someone can login and see all the active sessions associated to their account, as well as whats going on with them, live.

I have it setup so CI will delete the old session when the session is regenerated, so there wont be a ton of old sessions polluting the sessions table. 

I have a MY_Session:Confusedess_regenerate() function, which just stores the "old" session in a public property, executes parent:Confusedess_regenerate(), then stores the "new" session in a public property. Then whenever the Account_model finally loads, it will check if the $new and $old are both populated, if so, then that means the session was regenerated, so take all the "activity" (NOT in the ci_sessions table) and change the session ID to the $new session id, thus allowing the user to see the activity that was going on for the same Login, even though it was a different session.

I created a MY_Session_database_driver::write(), which will execute parent::write(), then execute Accounts_model::assoc_session($_SESSION['account_id'], $session_id); to set the 'account_id' column in the 'ci_sessions' table to the current account ID.

I believe the problem im running into, is when the session is regenerated, it doesnt right away create a row in the ci_sessions table...

I tried to put some code inside the MY_Session:Confusedess_regenerate() to update the ci_sessions.account_id column of the current session ID to the proper account_id, but that still didnt fix the issue.

I put a lot of logging into the logs, and heres the logs and some comments on what they mean...
Quote:1) DEBUG - 2015-08-26 10:38:09 --> SESSION MY_Session:Confusedess_regenerate - Regenrating Session.. with destroy as true
// The sess_regenerate has been called, about to change the session
2) DEBUG - 2015-08-26 10:38:09 --> SESSION MY_Session:Confusedess_regenerate - Old Session: e54fdec78995bd44bae961aec00829fd1f5daf01
// MY_Session::$sess_info['old'] has been set to e54fdec78995bd44bae961aec00829fd1f5daf01
3) DEBUG - 2015-08-26 10:38:09 --> SESSION MY_Session:Confusedess_regenerate - New Session: 552c80f856600fab69cffcfcf6839b812412cf9a
// MY_Session::$sess_info['new'] has been set to 552c80f856600fab69cffcfcf6839b812412cf9a
4) DEBUG - 2015-08-26 10:38:09 --> SESSION Accounts_model::_check_regenerated_sess - Session was found to be regenerated
// The _check_regenerated_sess (which is in the controller) sees that the session has been regenerated, so it executes self::assoc_session..
5) DEBUG - 2015-08-26 10:38:09 --> SESSION Accounts_model::assoc_session - Associating OLD session data e54fdec78995bd44bae961aec00829fd1f5daf01 with NEW session 552c80f856600fab69cffcfcf6839b812412cf9a
// Now, all the activity in the account_activity table has had the session_id updated to the new session ID
// Now, i am going to attempt to update the ci_sessions.account_id to the current account ID where the ci_sessions.id = 552c80f856600fab69cffcfcf6839b812412cf9a, but first, query for it to make sure the record exists
6) DEBUG - 2015-08-26 10:38:09 --> SESSION Accounts_model::assoc_session - NO sessions found for session ID 552c80f856600fab69cffcfcf6839b812412cf9a
// Record does NOT exist
7) DEBUG - 2015-08-26 10:38:09 --> SESSION Accounts_model::get_active_sessions_data - No sessions in query for account 1
// No active session data found for the account, even though account_activity has a ton of info, no record in the ci_sessions table exists yet

So basically, when sess_regenerate is ran, it will change the session ID, but it doesnt actually write the new session ID record in the ci_sessions table yet..

So my question is, how can I have it do that when the session is regenerated? 

I know I COULD have MY_Session:Confusedess_regenerate(); create the new record in the table, then basically re-write all of CI_Session_database_driver::write() to a new MY_Session_database_driver::write(), which pretty much does the exact same thing, except it checks for the row to be existing before trying to create it, if it exists, it just updates it instead. But id like to overwrite as little CI code as possible (And I know Narff agres, lol)

I know I could try to set the CI_Session_database_driver::$_row_exists to TRUE, so that CI_Session_database_driver::write() will update the row, instead of create it, but right when CI_Session_database_driver::write()  is executed, it will reset it to FALSE.

Basically, all I really need, is for the session to be written to the database, right as its regenerated, (but then CI_Session_database_driver::write() will throw a fit because its trying to create a row that already exists)

Is there a way to have CI_Session_database_driver::write() execute when the session is regenerated? I know the CI_Session::_construct() uses session_set_save_handler() to set the handler, which will have write() eventually executed, I just need to have it done earlier, somehow

If anywhere between lines #3 and #6, I could have MY_Session_database_driver::write() execute, that would save the day.

Hope I made this clear enough... and thank you for the help!

P.S. I know all the methods/properties I referenced arent static, I just referenced them like that to make it easier to understand Smile
Reply

#22
I think I understand most of what you're trying to do, I just don't understand the need to store the account_id in the session table. In my opinion, it would be easier to store the account_id in the session itself (not a new column in the session table) and create another table which would store the account_id and session_id values (with as many session_id entries per account_id as you need) so you could lookup the sessions associated with the account without having to get into the session data itself (except, of course, for getting the account_id from the current session).

Honestly, every time I think of some idea for modifying the session table, I eventually run into something that requires replacing the write() method on the driver or something even more drastic.

To address some specific items in your text above:

Quote:I have a MY_Session:Confusedess_regenerate() function, which just stores the "old" session in a public property, executes parent:Confusedess_regenerate(), then stores the "new" session in a public property. Then whenever the Account_model finally loads, it will check if the $new and $old are both populated, if so, then that means the session was regenerated, so take all the "activity" (NOT in the ci_sessions table) and change the session ID to the $new session id, thus allowing the user to see the activity that was going on for the same Login, even though it was a different session.

I think something just clicked for me here, but I'm not going to go back and change what I wrote above. It seems to me that the "activity" should be associated with the account, rather than the session. The relationships in the database should tie to the activities to the account, not to the session. It's best to leave the session alone to do its work and leave it out of your database relationships (and that also frees you up to use a different session driver).

If you want to record the session_id used for an activity as some sort of artifact for logging or whatever, that's your prerogative. If you want that session_id to reflect the chain of regenerated session_id values, then create a new table with an auto ID value and a session_id value, record the auto ID in your activity table and update the new table's session_id when the session is regenerated. Then you just join the activity table to the new table and read the session_id from the new table, but the activities themselves are still related to the account by the account_id, and you display activities based on the account_id, not the session_id.
Reply

#23
(08-26-2015, 12:05 PM)mwhitney Wrote: I think I understand most of what you're trying to do, I just don't understand the need to store the account_id in the session table. In my opinion, it would be easier to store the account_id in the session itself (not a new column in the session table) and create another table which would store the account_id and session_id values (with as many session_id entries per account_id as you need) so you could lookup the sessions associated with the account without having to get into the session data itself (except, of course, for getting the account_id from the current session).

Thats what I was doing originally, I believe Narf said that was over-complicating it, and it would be best to just create a new column in the ci_sessions table.

I do think its easier this way, because then to look for any active sessions for a given account, I can just select from the ci_sessions table where the account_id = $id.

The account_id is ALSO stored in the session data, I just cant select rows from the table based on a value of a variable in the session data though.


(08-26-2015, 12:05 PM)mwhitney Wrote: To address some specific items in your text above:


Quote:I have a MY_Session:Confusedess_regenerate() function, which just stores the "old" session in a public property, executes parent:Confusedess_regenerate(), then stores the "new" session in a public property. Then whenever the Account_model finally loads, it will check if the $new and $old are both populated, if so, then that means the session was regenerated, so take all the "activity" (NOT in the ci_sessions table) and change the session ID to the $new session id, thus allowing the user to see the activity that was going on for the same Login, even though it was a different session.

I think something just clicked for me here, but I'm not going to go back and change what I wrote above. It seems to me that the "activity" should be associated with the account, rather than the session. The relationships in the database should tie to the activities to the account, not to the session. It's best to leave the session alone to do its work and leave it out of your database relationships (and that also frees you up to use a different session driver).

The activity in the accounts_activity table is all associated to the account_id, it also however has a session_id column. When any activity takes place, it will create a new record in the account_activity table, with the account_id = $id and session_id = $current_session, then whenever the sess_regenerate() takes place, it will UPDATE account_activity SET session_id = $new_id WHERE session_id = $old_id, allowing the user to see everything that took place for that single login, even though it had multiple regenerated sessions.

(08-26-2015, 12:05 PM)mwhitney Wrote: If you want to record the session_id used for an activity as some sort of artifact for logging or whatever, that's your prerogative. If you want that session_id to reflect the chain of regenerated session_id values, then create a new table with an auto ID value and a session_id value, record the auto ID in your activity table and update the new table's session_id when the session is regenerated. Then you just join the activity table to the new table and read the session_id from the new table, but the activities themselves are still related to the account by the account_id, and you display activities based on the account_id, not the session_id.

That sounds like it might work.. Ill give it a shot! Thanks
Reply

#24
Question...

Is the CI_Session_database_driver::destroy() called when the session is regenerated, as well as logout?

I took your advice, and created a table, account_sessions_assoc, that has a unique automatically generated ID, which is the main point of reference, as well as account_id and session_id, when MY_Session_database_driver::write() is called, it will execute parent::write(), then it will execute another method, Accounts_model::account_session_assoc(), that checks if the old session ID is in the account_sessions_assoc table, if so, update the account_sessions_assoc.session_id to the new $session_id, if not, create a new record with the $account_id and the $session_id.

I created a MY_Session_database_driver::destroy() which just executes parent::destroy(), then executes a newly created MY_Session_database_driver::_assoc_gc(), which will delete any records in the account_sessions_assoc table if the current account_sessions_assoc.session_id is NOT in the ci_sessions table (This method is also executed by MY_Session_database_driver::gc(), which just executes parent::gc() then _assoc_gc())

The problem im running into, is records are getting deleted out of the account_sessions_assoc table, when sessions are still in ci_sessions, and after viewing some logs, it looks like the MY_Session_database_driver::destroy() method is being called when the MY_Session:Confusedess_regenerate() is being called.

If im right, and the destroy() is being called when the session is regenerated (probably because sess_regenerate_destroy = TRUE), then I need to somehow let the MY_Session_database_driver::_assoc_gc() method know that the session is being regenerated, and not to delete it.

Thank you for the suggestion about creating a new table for associations, that was what I originally did, and should have stuck with that, definitely works better. Now just got this one last issue to take care of!

Thank you for the help!

Update: I added a column, account_sessions_assoc.regens, which will start at 0, and +1 for every time a record is updated, I removed the reference to self::assoc_gc() from MY_Session_database_driver::destroy(), and the regens started climbing.. 1.. 2.. 3.. etc. Then I re-added the reference in destroy() to self::assoc_gc(), and the new records were all being added to the account_sessions_assoc table, new ID's, session ID's and regens are staying at 0.

So its definitive that destroy() is being called when sessions are regenerated.

Since MY_Session_database_driver::write() is what does all of the inserts/updates in account_sessions_assoc, I believe what im running into is ironically the EXACT same issue I had before I took your advice, which now, is...

1) MY_Session:Confusedess_regenerate() gets called, which updates the account_sessions_assoc.session_id from the old session ID to the new session ID
2) Then MY_Session_database_driver::destroy() gets called, which deletes the session_id from ci_sessions
3) MY_Session_database_driver::destroy() calls self::assoc_gc(), which will delete the record from account_sessions_assoc, since MY_Session_database_driver::write() has not been called to quickly create the new record.

If I can have MY_Session_database_driver::destroy() realize its being called after MY_Session:Confusedess_regenerate(), then I can have it not execute self::assoc_gc()


I tried doing this, but it doesnt seem to work:
PHP Code:
<?php
class MY_Session extends CI_Session
{
 
   public static $sess_info = array(
 
       'old' => NULL,
 
       'new' => NULL
    
);
 
   
    public 
function sess_regenerate$destroy FALSE )
 
   {
 
       self::$sess_info['old'] = session_id();

 
       parent::sess_regenerate$destroy );

 
       self::$sess_info['new'] = session_id();
 
   }
}

class 
MY_Session_database_driver extends CI_Session_database_driver
{
 
   public function destroy$session_id )
 
   {
 
       // Execute the real destroy first...
 
       if( ! parent::destroy$session_id ) )
 
       {
 
           return FALSE;
 
       }

 
       // Check if destroy() is being called after a regeneration, if so, just return true...
 
       if( ! is_nullMY_Session::$sess_info['old'] ) AND ! is_nullMY_Session::$sess_info['new'] ) )
 
       {
 
           return TRUE;
 
       }
 
       // If not, clean up `account_sessions_assoc` based on whats in `ci_sessions`
 
       else
        
{
 
           return $this->_assoc_gc();
 
       }
 
   }

MY_Session::$sess_info['old'] and MY_Session::$sess_info['old'] both start off as NULL, as set by the constructor, so it seems like they re getting set to null after MY_Session:Confusedess_regenerate(), but before MY_Session_database_driver::destroy(), which is throwing me off a little...

P.S. Sorry for the overwhelming amount of info.. Im just trying to be as precise about whats happening as I can be. I think its also making the situation seem more complicated than it really is, which I hate doing.
Reply

#25
I ended up using debug_backtrace() to see which function was calling it... Seems to work..


PHP Code:
<?php
defined
('BASEPATH') OR exit('No direct script access allowed');

class 
MY_Session_database_driver extends CI_Session_database_driver {

 
   public function write($session_id$session_data)
 
   {
 
       if( ! parent::write($session_id$session_data))
 
           return FALSE;

 
       // If theres an account ID set in the session data...
 
       if (isset($_SESSION['account_id']))
 
           Accounts_model::account_session_assoc();

 
       return TRUE;
 
   }

 
   public function gc($maxlifetime)
 
   {
 
       if( ! parent::gc($maxlifetime))
 
           return FALSE;
 
       
        return $this
->_assoc_gc();
 
   }

 
   public function destroy($session_id)
 
   {
 
       if( ! parent::destroy($session_id))
 
           return FALSE;
 
       
        return $this
->_assoc_gc();
 
   }


 
   private function _assoc_gc()
 
   {

 
       $backtrace debug_backtrace(2);

 
       // Check if this was executed by destroy or sessio regeneration
 
       if(@$backtrace[2]['function'] === 'session_regenerate_id')  // session_destroy = Logout or cleanup; session_regenerate_id = Regeneration
 
           return TRUE;
 
       

        $sess_query 
$this->_db->select('id')->from('ci_sessions')->get();
 
       
        $sess_result 
$sess_query->result();


 
       if(count($sess_result) === 0)
 
           return TRUE;

 
       $sessions = array();

 
       foreach($sess_result as $s)
 
           array_push($sessions$s->id);
 
       
        return $this
->_db->where_not_in('session_id'$sessions)->delete('account_sessions_assoc');
 
   }


If theres any CI best practices that I violated, lmk ;-)
Reply


Digg   Delicious   Reddit   Facebook   Twitter   StumbleUpon  


  Theme © 2014 iAndrew  
Powered By MyBB, © 2002-2020 MyBB Group.