I am making a session system for my website using PHP and MySQL. The idea is that a user session will last for around 5 minutes if they are inactive, and a CronJob runs every now and then and checks to see if sessions are expired, and if they are, removes the session.
The issue:
Every time someone loads their page it has to check the database to see if their session is still valid. I am wondering if in that CronJob task, I could make it find that users PHP Session and change a variable like $_SESSION['isValidSession'] and set it to false.
So once they load the page it just checks if that variable if the session is valid.
Sorry for the wall of text!
TL;DR: I want to modify session variables of different specified sessions.
Thanks.
Every time someone loads their page it has to check the database to
see if their session is still valid. I am wondering if in that CronJob
task, I could make it find that users PHP Session and change a
variable like $_SESSION['isValidSession'] and set it to false.
You have to do this regardless. When the users load their page, the system must verify whether the session exists in the database (I assume that you're using a DB).
If you run the cron job every minute, and expire all sessions older than five (which seems rather excessive? I often stay inactive on a site for five, ten, even fifteen minutes if I am reading a long page), this will automatically "mark invalid" (actually remove) the sessions.
Normally you would keep a TIMESTAMP column with the time of last update of that row (meaning that session), and the cron job would DELETE all rows with timestamp older than five minutes ago. When reloading the page, the system would no longer find the relevant session row, and deduce (correctly) that the session has expired.
However, what you want (reading a session knowing its SessionID) can be accomplished by reading in the session by the cron job (you can code the job in PHP) either loading as extant session given its ID, or by reading the DB column holding the serialized data with a SELECT SessionData FROM SessionTable WHERE id = 'SessionId'; and de-serializing it. Then you modify the inflated object, re-serialize it and store it back in the database with SQL UPDATE. Hey presto!, session has now been modified.
But be aware that this will likely cause concurrency problems with active clients, and cannot be done in SQL in one fell swoop - you can't execute UPDATE Sessions SET isInactive = 1 WHERE expiry... directly. Normally you need to read the rows of interest one by one, unserialize them and store them back, processing them with PHP code.
You can do it indirectly with two different workarounds.
One, you change your session code to use unserialized data. This will impact maintainability and performance (you can't "just add" something to a session: you have to create a column for it).
Two: you take advantage of the fact that in serialized form, "0" and "1" have the same length. That is, the serialized session containing isValidSession (name of 14 characters) will contain the text
...{s:14:"isValidSession";b:1;}...
and you can change that piece of string with {s:14:"isValidSession";b:0;}, thus making isValidSession become False. This is not particularly good practice - you're messing with the system's internals. Of course, I don't think anybody expects PHP's serialized data syntax to change anytime soon (...or do they?).
<?php var_dump($_SESSION); ?>
You should store the time of last request of the users in the database.
In the cornjob you should check users last view time and compare to current time, then check which user time has been expired.
And then update the column of database as false for expired users.
After than you can easily find out which user should be log out just by checking that colmn in database.
Related
We're setting up a system which allows a department to make edits to a record here. Their division of labor isn't clear, and they've had problems in the past where more than one individual loads data into a web form, makes edits, and then sends those edits to the record. Inevitably, the slower editor over-writes the faster editor's freshly edited data with the old data that had been loaded when the page loaded.
Currently, we have a white-board solution that would use changes to the last modified time of the data to reject the second request to write data and deliver an error message when that data POSTED.
The members of the department, however, would prefer a file-lock styled system--one where they were notified that another user was in the dataset prior to being allowed to access the data. Timeouts will inevitably be too short or too long depending on the day, and these particular users cannot be relied upon to "log out" somehow.
I'm just wondering if anyone else has implemented a solution to this, and, if so, how?
We're running PHP 5.6 on Apache 2.2 and building in Zend Framework 2.4. Not that any of that has any bearing on the question, but someone will inevitably ask.
Add 2 columns to your table locked_by_user_id and locked_time.
Before you allow a user to enter the "edit" view, check if those values are set and if locked_time is within the past 10 minutes. The reason you should record the locked time instead of a binary flag is because, as you say, some people forget to log out or might not log out cleanly (for example, they could just close their browser).
When someone is able to acquire a lock, set up a setInterval that runs every 5 minutes that reacquires the lock via an ajax call. Someone still might forget to leave the "edit" screen but in that case you can allow someone else to override a lock and if that happens you can have the ajax call exit the "edit" screen when it fails to reacquire the lock.
Why not set a flag on the table row for edit_in_progress? If a user clicks to edit and that flag is already set, fail out with a message saying someone else is editing it (perhaps consider also setting a field for WHO is editing it, so they can go back in before they've saved and continue their edits). Once saved, unset both fields, and allow the next user to lock the row.
Assuming that you're using a database, but not knowing the database schema you're using, this is going to be a generic answer.
You need to give each record an identifier which is set as non-unique. By this, I mean each record could be identified as record_1, record_2 ... record_n but this identifier can appear multiple times in the table.
When a record is edited, don't update the record directly but create a new record which is
timestamped
has the original record_n identifier
Also, add a field to the record to give its current state (e.g. stable, editing?) and a field which gives the edit start date/time if it is being edited
With this, when someone wants to edit a record (e.g., record_2), you retrieve the most recent data for this record and check its state (if marked as editing then report this and prevent concurrent editing).
When they submit changes, a new timestamped record is created in the database, you mark the old and new records as stable.
Using this, you also create a paper-trail for auditing changes
With regards to people who wander off/retire/die and leave records in an editied state, create a scheduled job which resets states to "stable" after a preset number of minutes (60?)
What would be the best way to achieve an undo function in a PHP CRUD application? The only solution I've been able to come up with is using a sort of buffer table in my database that is periodically wiped.
If the user clicks the "Undo" button after deleting a record for example, the id of the last change will be passed to a handler which will pull the buffer record and reinstate it into the main table for that data type. If the "Undo" is not done in say, 4 or 5 minutes, a reaper script will drop the entry.
Does this sound feasible? Is there a better way of accomplishing this?
You could use a flag field in your database to mark a row for delete.
And you can setup task (crontab in linux) to delete all rows with delete flag set to true and time difference > to 5 mins.
I've learned to not delete anything, but simply do as Ignacio Ocampo stated by using a flag column in your DB such as status. By default set the status column to open. If your client clicks your delete button, just update that records status column to void, or deleted..
In doing this, you'll need to update your data request to pull only those records with the status column set to open. This allows the data to not be lost, but also not seen.
all undo(s) or redo(s) if applicable can reset the open status to - or + 1 record sorted by a timestamp column.
If db space is at a premium, and you need to remove old data then crontab does work, but I prefer the simplicity phpmyadmin conjob to loop a file that will wipe all void or deleted records older than time()-'(last cron run).
Depending on what and how you're building, you might also want to consider using one of the following solutions.
1) A pure PHP CRUD solution would be something along the lines you've mentioned, with also possibly storing cookies on the client side to track which actions are being done. Every action a new cookie is created, then your application will only have to sort the cookies by date and time. You could also set the cookies to be automatically expire after x amount of time. (Although I would expire after a x amount of steps, instead of time)
2) If you are able to use HTML5 local storage (http://www.w3schools.com/html/html5_webstorage.asp) along with some Javascript would be perfect for this, since you wouldn't have to wait around for the server to respond everytime 'undo' is clicked since all the processing would be handled locally.
I am using CI's sessions in connection with a database. So all of our sessions are in this ci_sessions table on our database and it can get a lot of rows, considering that the session_id keep changing every 5 minutes.
Do we need to empty the table, say every one a month / week maybe?
While what #Marc-Audet said is true, if you take a look at the code, you can see it is a really lousy way to clean up sessions.
The constructor calls the _sess_gc function every time it is initiated. So, basically each request to your server if you have it autoloaded.
Then, it generates a random number below 100 and sees if that's below a certain value (by default it is 5). If this condition is met, then it will remove any rows on the session table with last_activity value less than current time minus your session expiration.
While this works for most cases, it is technically possible that (if the world is truly random) the random number generator does not generate a number below 5 for a long time, in which case, your sessions will not be cleaned up.
Also, if you have your session expiry time set to a long time (if you set to 0, CI will set it to 2 years) then those rows are not going to get deleted anyway. And if your site is good enough to get a decent amount of visitors, your DBA will be pointing fingers at the session table some time soon :)
It works for most cases - but I would not call it a proper solution. Their session id regeneration really should have been built to remove the records pertaining to the previous ids and the garbage collection really should not be left to a random number - in theory, it is possible that the required number is not generated as frequently as you wished.
In our case, I have removed the session garbage collection from the session library and I manually take care of it once a day (with a cron job .. and a reasonable session expiration time). This reduces the number of unnecessary hits to the DB and also does not leave a massive table in the DB. It is still a big table, but lot smaller than what it used to be.
Given the fact that the OP question doesn't have a CodeIgniter 2 tag, I'll answer how to deal with sessions cleanup when the database keeps growing for CodeIgniter 3.
Issue:
When you set (in the config.php file) sess_expiration key too high (let's say 1 year) and sess_time_to_update key low (let's say 5 min), the session table will keep growing as the users browse though your website, until sessions rows will expire and will be garbage collected (which is 1 year).
Solution:
Setting sess_regenerate_destroy key to TRUE (default set to FALSE) will delete an old session when it will regenerate itself with the new id, thus cleaning your table automatically.
No, CodeIgniter cleans up after itself...
Note
According to the CodeIgniter documentation:
The Session class has built-in garbage collection which clears out expired sessions so you do not need to write your own routine to do it.
CodeIgniter's Session Class probably checks the session table and cleans up expired entries. However, the documentation does not say when the clean up happens. Since there are no cron jobs as part of CodeIgniter, the clean up must occur when the Session class is invoked. I suppose if the site remains idle forever, the session table will never be cleared. But, this would be an unusual case.
CodeIgniter implements the SessionHandlerInterface (see the docs for the custom driver).
CodeIgniter defines a garbage collector method named gc() for each driver (database, file, redis, etc) or you can define your custom gc() for your custom driver.
The gc() method is passed to PHP with the session_set_save_handler() function, therefore the garbage collector is called internally by PHP based on session.gc_divisor, session.gc_probability settings.
For example, with the following settings:
session.gc_probability = 1
session.gc_divisor = 100
There is a 1% chance that the garbage collector process starts on each request.
So, you do not need to clean the session table if your settings are properly set.
When you call:
$this->session->sess_destroy();
It deletes the information in database by itself.
Since PHP7, the GC-based method is disabled by default, as per the documentation at https://www.php.net/manual/en/function.session-gc.php Stumbled upon this because a legacy application suddenly stopped working, reaching a system limitation since sessions are never ever cleaned up. A cronjob to clean up the sessions would be a good idea...
It is always good practice to clear the table. Otherwise, if your querying the session data for say creating reports or something, it will be slow and unreliable. Nevertheless, given the performance of mysql, yes do so.
I am confused about something. When I try to search how to count online users in PHP, all answers related with MySQL and many different ways.
In my script, any user that submits the login form creates a $_SESSION['$nickname']
So I thought, can I count login sessions with count($_SESSION['$nickname']); and show it in my page?
Or is this totally a wrong logic?
Totally wrong logic. $_SESSION is a per-user thing. One user's session is not shared with any other user's session. Think about it - an online bank written in PHP, all sharing a single $_SESSION - everyone would see everyone's account details.
Assuming you're on the standard PHP file-based sessions, you can count the session files in whatever directory they're stored, e.g.
$users = count(glob(session_save_path() . '/*'));
Note that this just counts session files - it will undoubtedly contain stale/dead sessions that haven't been garbage collected yet. If you want an actual "really is online right now", you'd have to parse each session file and examin its contents.
At first, you have to define what "to be online" means.
Should the user have clicked on a link within the last 5 minutes?
I assume that you already have a user table in your database.
So the simplest way is to add a new column, e.g. lastAction TIMESTAMP.
And when the user clicks on a link on your page, your script should update this value.
And on your statistics page or whatever, you get the number of online users with that code:
SELECT COUNT(*) FROM users WHERE lastAction > (NOW() - 60*5)
PHP is pretty flexible in terms of session storage, you can define your own session save/restore handlers.
However, the default session storage is files, where each session is individually stored to disk. Which means, that in order to find out how many users there are "online" (here, i assume "online = session exists with $_SESSION['$nickname'] set"), you would need to open all the session files stored on disk, and check how many unique nicknames exist within them. This is very heavy in both time and required resources.
Hence, most tutorials suggest counting this in the database, by maintaining a last-seen timestamp per user (and checking how many users were last seen in last X minutes).
If you wish to combine, that is doable via defining your own session save handler to store session information in the database...
Several additional notes on sessions:
Sessions are not destroyed immediately when closing a browser. In fact, the browser does not tell the server in any way that it is being closed. It means that the server should come up with some time-based algorithm to decide who's online and who is not.
Sessions are isolated from one another so $_SESSION["foo"] can not be shared by multiple different sessions - there is no equivalent to Global.asa in PHP. Not off the box.
This is the wrong logic and the session is only stored and immediately destroyed locally on the end user's PC, so it cannot be read from the server for any kind of confirmation without the user doing something. Server-side sessions are only destroyed on garbage cleanup.
Your best bet is to have a timestamp for each user which is updated whenever a user does anything. This means you will need to have this update triggered via JS/AJAX, PHP. Then you can check to see how many timestamps are within the last 5 minutes for example, and this would give you a rough idea of how many users are currently online.
You could also have a hidden iframe which continually refreshes every few seconds and updates the timestamp, or JS/AJAX which updates the timestamp every X seconds as well...
I have log table for all users of website
I'm recording various data about user righ after successfull login.
If signout_dt field not filled and status is 1 for some user_id, website prevents login automatically.
For that who have cookies - there is no problem.
The problem is,lets say user signed in without cookies: only sessions variables. I have no idea, how can I update db table and signout user let's say after 30 minute inactivity. Note that I can't create cron job or something serverside, because using shared hosting.
Heard that, it's possible to create some script like heartbeat that continously sends some data about user activity. But I think this will heavily load the server especially if there are more than 1000 users.. Any suggestion, tutorial, article, something else?
Update
Deceze tried to explain but I really need better explanation (better idea), with code.
To "timeout" a user, simply note the time he was last seen. Then, when necessary, check if the last time you've seen the user was over x minutes/hours/days, and consider the last session timed out. You don't need to run a cron job or anything that cleans up after users in realtime, you only need to be able to determine if some information should be considered stale when you need that information.
You may want to occasionally run a cron job or something to clean out old, unnecessary data, but that doesn't need to happen in realtime. You could even run this as part of a regular page request:
if (mt_rand(1, 1000) == 1) {
mysql_query('DELETE FROM `table` WHERE `last_seen` < some point in time');
}
To note the last seen time, just run this query on each page load:
UPDATE `table` SET `last_seen` = NOW() WHERE `user_id` = ...
To avoid thrashing the database with these queries, you can also just do it every so often. Keep a "last_seen_last_updated" timestamp in the user's session, then on each page load check if you might want to update the database:
if ($_SESSION['last_seen_last_updated'] < strtotime('-5 minutes')) {
mysql_query(...);
$_SESSION['last_seen_last_updated'] = time();
}
That gives you 5 minutes of jitter, but that's usually perfectly acceptable.
Your management of sessions is broken and does not conform to accepted stateless behaviour - in as much as you apparently require the user to sign out, which rarely is the case in web applications -- most people just closes the browser, and the cookies will just float around and appear next time the user accesses the website. If the system wants the user to sign in again, then the web server will have to validate the session -- for example using a timestamp and/or cookie signing etc, and invalidate the cookie to force the user to re-login if needed.
Hence you should treat cookies and sessions variables the same -- that is; have your server side generate a unique signed value. Keep an expiration time (for example now()+20min) either in the cookie/session variable or keep the expiration time in the database.
At each access check that the cookie/session-variable is correctly signed, and check that it is not beyond the expiration time, and update the expiration time to allow another 20min.
If the access is past the expiration time -- i.e. the user has been idle for too long, then clear the cookie/session-variable and force the user to login again.
If you keep the expiration time in the database, you simply write a small program which once and day or once an hour run though all records and remove those which you deem too old.
As per my understanding of your question, you want to address following things:
a. If for a given period of time, a user is inactive then he should be logged out and your database table gets updated. Here being inactive means, user has not used keyboard/mouse for a given period of time.
b. If a user closes the browser without logging out, then he should be forcefully logged out and database table gets updated.
Both these things can be accomplished using Javascript Functions and Ajax. Following is the flow which we have in our application for addressing above issues:
Create a Javascript function, say logoutUser(), which will send an Ajax request for updating the database tables and destroying the session.
Use Javascript function - setTimeOut - to call logoutUser() function after time period you have set for inactivity.
Use Javascript events to catch mouse movement and keyboard activity and in every such event call use successively clearTimeOut (in order to remove the old time for execution of logoutUser()) and setTimeOut (for setting the new time of execution of logoutUser()). This way you would be able to catch the inactivity and logout the user after a period of time.
For taking care of the issue related to closing of browser window use 'onbeforeunload' event of javascript and in this event send the Ajax request for updating the database tables.
As our application uses ExtJS, thus, we have used ExtJs library functions to detect events. You can also prefer using some Javascript library for catching the events and implemeting the above solution.
Hope this helps.