7

We are locking some inventory for clients, the table that locks the inventory contains the session ID of who locked it together with other information about the client. When the session expires we want to unlock that inventory so other people can purchase it. Because we register the session_id() in the table, knowing it, is there a way to check if the session is still active in PHP?

If we are using the DB to keep the session we can probably check if the row is till there and when was the last activity was, in memcached we can probably figure out the key of the session and check it like that, for the file session we can probably do the same, figure out the file name for the session and check if the file exists.

Is there something that works everywhere no matter where you keep the session?

Mihai P.
  • 9,307
  • 3
  • 38
  • 49
  • 1
    Why you can't use database? – sumit Feb 19 '17 at 22:43
  • I'm not the one to ask about something like this, but I would start with this and go from there.`if(session_status() === PHP_SESSION_ACTIVE){}`. What I mean is, if this does not work, I'd have to dig deeper. :-) That might help with the 1st part of the question. There is also `if(session_status() === PHP_SESSION_NONE){}` – Anthony Rutledge Feb 26 '17 at 03:34

8 Answers8

3

You can't use session_id, session_start etc.

These functions handle session of a user, that requests a page. You can't use them to handle a list of all users' sessions easily. You have to worry about the same rights for a user-created sessions (www-data) and a cli script, that will be executed to find which should be unlocked - sessions' files are for owners only.

In adddition session's access time is changed, so every check for a session you change it's time and influence sessions' behaviour.

How to perform locking easier

Usually locking of goods is made just for a fixed amount of time, say 20min. But if you wish to hold inventory selected while session is alive, you can use a slight modification of existing code.

  1. Add a new field last_activity next to session_id field (one simple migration)
  2. Every request update it to NOW (just call $row->touch() on a model - docs).
  3. Make a crontab task to remove every line with last_activity < NOW - delta, where delta is a session timeout.

Using this approach you can avoid dealing with every session driver.

P.S. You will probably want to be sure that session is dead after a certain amount of time, read this question. If this job is done by a garbage collector, session lifetime can vary.

Community
  • 1
  • 1
shukshin.ivan
  • 11,075
  • 4
  • 53
  • 69
  • I know about the garbage collector and that I should not count on it. I tried to convince business that it should be locked for a period of time and not while a user is logged in, however this was promised to the client before I came aboard so we just need to do this. The way to suggested the session is exactly how I wanted to implement it but I was told we have commitments. – Mihai P. Feb 23 '17 at 04:06
  • @MihaiP. What If you extend the "lock" time while the user is logged in and active? It would match the requirements of the business. – A.L Feb 23 '17 at 15:20
  • Nevertheless you need to configure your sessions. You can't allow to users to have items locked forever if they doesn't open any page. What does it mean 'while a user is logged in'? Can he lock items forever in case he checked "remember me"? – shukshin.ivan Feb 23 '17 at 15:41
  • "Remember me" logs you back in when you come to the website, it does not prolong your session in any way. Another solution was on every page load to update a time for the record and delete all records after a while (lets say 40 minutes). That requires up update on every page load and I am trying to make as few calls to the DB as possible. And yes, according to business the inventory is locked while the user is actively on the website. – Mihai P. Feb 23 '17 at 22:18
  • What does `is locked while the user is actively on the website` mean? You have to define it. If it means unlock at the session timeout - my solution fits it. `logs you back` - right, but are you sure that businessman knows it? – shukshin.ivan Feb 23 '17 at 22:23
  • @shukshin.ivan Hey, can this help at all for the first part of the question? `if(session_status() === PHP_SESSION_ACTIVE){}` – Anthony Rutledge Feb 26 '17 at 03:48
3

In summary, you are asking if there is a generic approach within PHP to access any session. Although the use case in the question is plausible, the impact of being able to do what you suggest poses a massive security risk.

That is one reason why the inbuilt session functionality within PHP makes doing what you require difficult.

In theory you may be able to use the inbuilt PHP session functions to specify specific session ID's and look them up. I have just done a few simple tests and not had much success. There is only one inbuilt function for loading sessions 'session_start' which would need to be called repeatedly. The manual specifically says this won't work:

As of PHP 4.3.3, calling session_start() after the session was previously started will result in an error of level E_NOTICE. Also, the second session start will simply be ignored.

It may still be possible to work around this, perhaps with forking or other clever fiddles. But your code would be working in an obscure way which could break with future PHP updates or possibly interfere with existing live sessions.

The best solution, would be to write a tool specific to the session handler in use that allows read only access to the session. The scenario in the question doesn't even need access to the session data, just the timestamp information which can be used to calculate the expiry time.

In the case of 'files' session handling. The path to the session file store can be discovered with ini_get('session.save_path');. The checking script may need to run with the same permissions as the web server in order to access this location.

The script would need to run frequently on a schedule to check for expired sessions and remove the locks from the inventory.

Steve E.
  • 9,003
  • 6
  • 39
  • 57
  • I agree, what I am trying to do does not feel right :). I will feel dirty afterwards. What we did right now is to use memcached both for the session and for caching. Because Laravel uses the session ID as the memcached key, I believe we can just check if the key exists and figure out that way if the session is still active or not. – Mihai P. Feb 23 '17 at 05:26
  • That should work. The memcached session key will be (Should be to prevent collisions) more than just the session ID. [Dump the keys](http://stackoverflow.com/questions/19560150/get-all-keys-set-in-memcached) to work out the pattern you are looking for. – Steve E. Feb 23 '17 at 06:19
  • From my tests it is the same the session key is the same as the memcached key. It should work easy. – Mihai P. Feb 23 '17 at 23:24
3

You can actually use session_id and session_start for this purpose.

$ids = [
            '135b29ef958a23418f2a804474787305', // active session
            '135b29ef958a23418f2a804474787306', // inactive session
            '135b29ef958a23418f2a804474787305', // active session
        ];

foreach($ids as $id)
{
    session_id($id);
    session_start(); 

    $status = isset($_SESSION['logged_in']); 

    print( ($status ? 1 : 0) . PHP_EOL);
    session_abort();
}

Check if an session variable that is always set exists. To make sure this isn't a newly created session.

You'll have to check if this doesn't reset the lifetime-counter on the session. On my system it doesn't impact the lifetime until something changes in the session

Edit: updated with session_abort to loop and check multiple session-ids

Saa
  • 1,540
  • 10
  • 22
  • Thank you, I did not know about session_abort(). Are you sure when you open the session it is not extended even more? I would not want to keep session extended forever. – Mihai P. Feb 23 '17 at 04:02
  • @Sander Backus. I'm curious, did you test this? It didn't work when I tried it, I would be super interested to know if there's a way to make it work. – Steve E. Feb 23 '17 at 04:13
  • I did test this. As @shukshin.ivan mentioned you have to make sure the process has permissions to read the session (e.g. run it via your webserver, not in cli). On my system the timestamps of the session files don't change. And with custom session handlers the timestamp shouldn't change as well. I'm not sure how this is handled in for example memcached. Maybe you could ini_set session.gc_probability, session.gc_divisor before running this to make sure garbage collection is run before you check if sessions exists? Then check each session just after timeout to make sure they won't extend – Saa Feb 23 '17 at 07:14
  • My debian changes session's access time of a file. – shukshin.ivan Feb 26 '17 at 10:45
3

Check is session exist in session folder:

echo ini_get('session.save_path');

for example in my folder session id file:

sess_l75lm4ktlhvlu566n8i6do8k71

Set session parametrs (for example):

ini_set('session.cookie_lifetime', 100)

After session_destroy(); file was deleted from session folder:

        if (file_exists($sessionfile)) {
        echo "exists";
    }else{
        echo "not exist"
    }

Or You can send js request when close browser and unlock product (firefox tested):

    <script type="text/javascript">
    // alert(document.cookie);

    window.onbeforeunload = function () {
    return "Do you really want to close?";
    };
    window.onunload = function () {
    return "Do you really want to close?";
    };

    $(window).on('beforeunload', function(){ alert ('Send post to unlock product')});
    $(window).unload( function () { alert("'Send post to unlock product'"); } );
</script>
  • This is a solution yes, however this only takes into consideration the file session. The session can be anywhere (DB, file, memcached) I was hoping there is a solution that would be general. I understand that I can do something similar if the session is in the DB or memcached. – Mihai P. Feb 23 '17 at 03:59
  • PHP writes the session information to all servers specified in session.save_path; similar to a RAID-1. You need check session cookies. –  Feb 23 '17 at 08:15
  • Or add id (locked items) to session cookie from PHP and then all users know –  Feb 23 '17 at 09:04
  • Or delete session id when close the browser (tab) (from javascript event) –  Feb 23 '17 at 10:52
2

You can put code in your function which logs out the user, that should handle the unlocking of the inventory related to that session.

Chava Geldzahler
  • 3,605
  • 1
  • 18
  • 32
  • 1
    Your suggestion would not handle closing the browser window. I still need to unlock it afterwards. – Mihai P. Feb 17 '17 at 03:57
  • Refer to this for handling that situation: http://stackoverflow.com/questions/13443503/run-javascript-code-on-window-close-or-page-refresh – Chava Geldzahler Feb 17 '17 at 04:00
  • Also, timeout on the session should handle logging the use out. – Chava Geldzahler Feb 17 '17 at 04:05
  • I have done that in the past, it is rather buggy, also you are still counting on something from the browser. What if the internet goes down, the user closes the laptop leaving his browser open, they are on their phone and they just forget about it, etc. – Mihai P. Feb 17 '17 at 04:06
  • Session timeout – Chava Geldzahler Feb 17 '17 at 04:07
  • The user is logged out (server side) when the session expires. I just want to know when that happens. Is there a way to let PHP handle it and I just query to see if it is still active? – Mihai P. Feb 17 '17 at 04:07
2

Closing of the browser window triggers the event beforeunload, something that can only be handled by Javascript. You could put in a Javascript event handler for that event which makes an AJAX call back to the site, like so (jQuery example given):

$(window).bind("beforeunload", function() { 
    $.ajax({
        url: "http://example.com/destroySession.php"
    });
});

That call back to the site could be against a page that unlocks the inventory items against the user's sessionID, after which the PHP command session_destroy() is called. For example:

<?php

// Load your config bootstrap / etc.

session_start();

// Unlock target inventory items

session_destroy();

?>
e_i_pi
  • 4,590
  • 4
  • 27
  • 45
  • So if the user keep its browser open, the item will never be unlocked? – A.L Feb 20 '17 at 18:19
  • Not necessarily, the session may well have a timeout. The OP hasn't provided the code that handles session insertion / checking with the database table. Without that information, I can only postulate. – e_i_pi Feb 20 '17 at 22:15
2

What you could do is keep a flag(eg. last_activity), in each user session, that to check your session was active and find out whether to unlock the item or keep it locked. Here is what worked for me:

$sessionId = get_session_id_that_locked_the_item_from_db();

if(session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}
// get current session id.
$current_id = session_id();
// renew activity.
echo 'Renewed my activity.';
$_SESSION['active'] = 1;
// Close the current session
session_write_close();

if($current_id === $sessionId) {
    // the current user has locked the item.
}
// Check if the var active of this session exists.
elseif(!checkSessionlastActivity($sessionId, $current_id)) {
    // Show the inventory item.
    showInventoryItem();
    // Lock inventory when the item is selected(eg added to basket).
    if('I want to lock the item') {
       lockInventoryItem($current_id);
    }
}
else {
    echo 'Item is locked.';
}

function checkSessionlastActivity($id, $current_id) {
    // switch session to the one that locked the item.
    session_id($id);
    session_start();
    // get session is active.
    $active = ( isset($_SESSION['active'])? $_SESSION['active']: null);
    // Close the session.
    session_abort();
    // restore current session.
    session_id($current_id);
    session_start();

    if($active !== null) {
        return true;
    }
    return false;
}

function lockInventoryItem($current_id) {
    put_my_session_id_to_db($current_id);
}

function showInventoryItem() {
    echo 'You can select item';
}

Note: I am not sure if this is going to work for different systems. It may be depended on your php session settings.

Opinion: Session is used for a specific functionality. I think switching between sessions that belong to different users is not what sessions were designed for. Anyway, I would advice you to use this workaround until you implement a locking solution for your inventory.

In the case you will only rely whether the session has expired or not, check your session settings(session.gc_maxlifetime, session.gc_probability & session.gc_divisor) and also this can helpful.

Community
  • 1
  • 1
Jannes Botis
  • 11,154
  • 3
  • 21
  • 39
  • If I start the session to check it if it is active would that not prelong the session indefinitely? – Mihai P. Feb 23 '17 at 03:54
  • Yes, actually it will renew the timestamp of the session, but in my solution you do not rely on the updated timestamp of the session but "last_activity". If you care about that, then use session_abort() instead of session_write_close() for the db session. I will update my answer, there are a few more things you need to consider. – Jannes Botis Feb 23 '17 at 10:09
1
// correct way
$id = 'abc';
session_id($id);
session_start();
$isActive = (session_status() === PHP_SESSION_ACTIVE);
session_write_close();
var_dump($isActive);

// tricky way (root required)
$id = 'abc';
$isActive = false;
$sessionTimeout = (int)ini_get('session.gc_maxlifetime');
$rdi = new RecursiveDirectoryIterator(session_save_path(), FilesystemIterator::SKIP_DOTS);
$rii = new RecursiveIteratorIterator($rdi);
$ri = new RegexIterator($rii, preg_quote("/sess_$id/"), RegexIterator::MATCH);
/**
 * @var $fileInfo SplFileInfo
 */
foreach ($ri as $fileInfo) {
    $expiredAt = $fileInfo->getMTime() + $sessionTimeout;
    $isActive = $expiredAt > time();
    if ($isActive) {
        break;
    } 
}
var_dump($isActive);

Generally, to store the session id in DB and not to keep session there - it's a bad idea.

Take a look at http://php.net/manual/en/function.session-set-save-handler.php

Based on your profile, you know yii. There is a good example of implementation:

https://github.com/yiisoft/yii2/blob/master/framework/web/DbSession.php

https://github.com/yiisoft/yii2/blob/master/framework/web/Session.php#L97

https://github.com/yiisoft/yii2/blob/master/framework/web/Session.php#L148

cetver
  • 11,279
  • 5
  • 36
  • 56