user sessions: use database instead of filesystem

User sessions will be stored in database and not in files anymore.
CGI::Session is not used anymore for this purpose.  The new module
user_sessions.pm provides functions on new database table user_sessions.
This commit is contained in:
Milan
2019-10-06 16:58:25 +02:00
parent ce59e89420
commit a7828b52d9
4 changed files with 290 additions and 43 deletions

View File

@@ -1179,6 +1179,28 @@ CREATE TABLE `calcms_work_schedule` (
LOCK TABLES `calcms_work_schedule` WRITE;
/*!40000 ALTER TABLE `calcms_work_schedule` DISABLE KEYS */;
/*!40000 ALTER TABLE `calcms_work_schedule` ENABLE KEYS */;
DROP TABLE IF EXISTS `calcms_user_sessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `calcms_user_sessions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`session_id` varchar(64) NOT NULL,
`user` varchar(30) NOT NULL,
`start` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`end` timestamp NULL DEFAULT NULL,
`expires_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`timeout` int(10) unsigned NOT NULL,
`pid` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
UNIQUE KEY `session_id_UNIQUE` (`session_id`),
KEY `user` (`user`),
KEY `session_id` (`session_id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
@@ -1191,3 +1213,5 @@ UNLOCK TABLES;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2018-01-14 17:23:51

View File

@@ -5,22 +5,19 @@ use warnings;
no warnings 'redefine';
use CGI::Simple();
use CGI::Session qw(-ip-match);
use CGI::Cookie();
#$CGI::Session::IP_MATCH=1;
use Data::Dumper;
use Authen::Passphrase::BlowfishCrypt();
use time();
use user_sessions ();
use base 'Exporter';
our @EXPORT_OK = qw(get_user login logout crypt_password);
my $defaultExpiration = 60;
my $tmp_dir = '/var/tmp/calcms-session';
my $debug = 0;
sub debug;
my $debug = 0;
sub debug($);
#TODO: remove CGI
sub get_user($$$) {
@@ -38,7 +35,7 @@ sub get_user($$$) {
return $user;
} elsif ( $params->{authAction} eq 'logout' ) {
$cgi = new CGI::Simple() unless defined $cgi;
logout($cgi);
logout($config, $cgi);
$cgi->delete( 'user', 'password', 'uri', 'authAction' );
return undef;
}
@@ -51,14 +48,13 @@ sub get_user($$$) {
return show_login_form( $params->{user}, 'Please login' ) unless defined $session_id;
# read session
my $session = read_session($session_id);
my $session = read_session($config, $session_id);
# login if user not found
return show_login_form( $params->{user}, 'unknown User' ) unless defined $session;
$params->{user} = $session->{user};
$params->{expires} = $session->{expires};
debug( $params->{expires} );
return $session->{user}, $session->{expires};
}
@@ -82,29 +78,28 @@ sub login($$$) {
my $password = shift;
debug("login") if $debug;
#print STDERR "login $user $password\n";
my $result = authenticate( $config, $user, $password );
#print STDERR Dumper($result);
return show_login_form( $user, 'Could not authenticate you' ) unless defined $result;
return unless defined $result->{login} eq '1';
my $timeout = $result->{timeout} || $defaultExpiration;
$timeout = '+' . $timeout . 'm';
my $session_id = create_session( $config, $user, $timeout * 60 );
my $session_id = create_session( $user, $password, $timeout );
# here timeout is in minutes
$timeout = '+' . $timeout . 'm';
return $user if create_cookie( $session_id, $timeout );
return undef;
}
#TODO: remove cgi
sub logout($) {
sub logout($$) {
my $config = shift;
my $cgi = shift;
my $session_id = read_cookie();
debug("logout") if $debug;
unless ( delete_session($session_id) ) {
unless ( delete_session($config, $session_id) ) {
return show_login_form( 'Cant delete session', 'logged out' );
}
unless ( delete_cookie($cgi) ) {
@@ -127,8 +122,7 @@ sub create_cookie($$) {
-expires => $timeout,
-secure => 1
);
print "Set-Cookie: ", $cookie->as_string, "\n";
print STDERR "#Set-Cookie: ", $cookie->as_string, "\n";
print "Set-Cookie: " . $cookie->as_string . "\n";
return 1;
}
@@ -160,49 +154,46 @@ sub delete_cookie($) {
}
# read and write server-side session data
# expiration is in seconds
# timeout is in seconds
sub create_session ($$$) {
my $config = shift;
my $user = shift;
my $password = shift;
my $expiration = shift;
my $timeout = shift;
debug("create_session") if $debug;
mkdir $tmp_dir unless -e $tmp_dir;
my $session = CGI::Session->new( undef, undef, { Directory => $tmp_dir } );
$session->expire($expiration);
$session->param( "user", $user );
$session->param( "pid", $$ );
return $session->id();
my $session_id = user_sessions::start(
$config, {
user => $user,
timeout => $timeout,
}
);
return $session_id;
}
sub read_session($) {
sub read_session($$) {
my $config = shift;
my $session_id = shift;
debug("read_session") if $debug;
return undef unless defined $session_id;
debug("read_session2") if $debug;
my $session = CGI::Session->new( undef, $session_id, { Directory => $tmp_dir } );
my $session = user_sessions::check( $config, { session_id => $session_id } );
return undef unless defined $session;
debug("read_session3") if $debug;
my $user = $session->param("user") || undef;
return undef unless defined $user;
my $expires = time::time_to_datetime( $session->param("_SESSION_ATIME") + $session->param("_SESSION_ETIME") );
return {
user => $user,
expires => $expires
user => $session->{user},
expires => $session->{expires_at}
};
}
sub delete_session($) {
sub delete_session($$) {
my $config = shift;
my $session_id = shift;
debug("delete_session") if $debug;
return undef unless ( defined $session_id );
my $session = CGI::Session->new( undef, $session_id, { Directory => $tmp_dir } );
$session->delete();
return undef unless defined $session_id;
user_sessions::stop( $config, { session_id => $session_id } );
return 1;
}

231
lib/calcms/user_sessions.pm Normal file
View File

@@ -0,0 +1,231 @@
package user_sessions;
use strict;
use warnings;
no warnings 'redefine';
use Digest::MD5();
use time;
# access user name by session id
# table: calcms_user_sessions
# columns: id,
# user,
# timeout,
# pid,
# start (timestamp),
# end (timestamp),
use base 'Exporter';
our @EXPORT_OK = qw(get_columns get insert update delete);
sub debug;
sub get_columns($) {
my $config = shift;
my $dbh = db::connect($config);
my $cols = db::get_columns( $dbh, 'calcms_user_sessions' );
my $columns = {};
for my $col (@$cols) {
$columns->{$col} = 1;
}
return $columns;
}
#map schedule id to id
sub get($$) {
my $config = shift;
my $condition = shift;
my $dbh = db::connect($config);
my @conditions = ();
my @bind_values = ();
if ( ( defined $condition->{id} ) && ( $condition->{id} ne '' ) ) {
push @conditions, 'id=?';
push @bind_values, $condition->{id};
}
if ( ( defined $condition->{user} ) && ( $condition->{user} ne '' ) ) {
push @conditions, 'user=?';
push @bind_values, $condition->{user};
}
if ( ( defined $condition->{session_id} ) && ( $condition->{session_id} ne '' ) ) {
push @conditions, 'session_id=?';
push @bind_values, $condition->{session_id};
}
if ( ( defined $condition->{start} ) && ( $condition->{start} ne '' ) ) {
push @conditions, 'start>?';
push @bind_values, $condition->{start};
}
my $conditions = '';
$conditions = " where " . join( " and ", @conditions ) if @conditions;
my $query = qq{
select *
from calcms_user_sessions
$conditions
order by start
};
my $entries = db::get( $dbh, $query, \@bind_values );
return $entries;
}
# insert entry and return database id
sub insert ($$) {
my $config = shift;
my $entry = shift;
return undef unless defined $entry->{user};
return undef unless defined $entry->{timeout};
unless ( defined $entry->{session_id} ) {
my $md5 = Digest::MD5->new();
$md5->add( $$, time(), rand(time) );
$entry->{session_id} = $md5->hexdigest();
}
$entry->{pid} = $$;
$entry->{expires_at} = time::time_to_datetime( time() + $entry->{timeout} );
my $dbh = db::connect($config);
return db::insert( $dbh, 'calcms_user_sessions', $entry );
}
# start session and return generated session id
sub start($$) {
my $config = shift;
my $entry = shift;
return undef unless defined $entry->{user};
return undef unless defined $entry->{timeout};
my $id = insert(
$config,
{
user => $entry->{user},
timeout => $entry->{timeout},
}
);
return undef unless defined $id;
my $sessions = get( $config, { id => $id } );
return undef unless defined $sessions;
my $session = $sessions->[0];
return undef unless defined $session;
return $session->{session_id};
}
# expand session by timeout
sub keep_alive ($$) {
my $config = shift;
my $entry = shift;
return undef unless defined $entry;
$entry->{pid} = $$;
$entry->{expires_at} = time::time_to_datetime( time() + $entry->{timeout} );
my $dbh = db::connect($config);
return update( $config, $entry );
}
# get session by session id and expand session if valid
sub check($$) {
my $config = shift;
my $entry = shift;
return undef unless defined $entry;
my $entries = get( $config, { session_id => $entry->{session_id} } );
return undef unless defined $entry;
$entry = $entries->[0];
return undef unless defined $entry;
my $now = time::time_to_datetime( time() );
return undef unless defined $entry->{expires_at};
return undef unless defined $entry->{user};
return undef if $entry->{expires_at} le $now;
return undef if $entry->{end} && ( $entry->{end} le $now );
keep_alive( $config, $entry );
return $entry;
}
# stop session
sub stop ($$) {
my $config = shift;
my $entry = shift;
return undef unless defined $entry;
my $entries = get( $config, { session_id => $entry->{session_id} } );
return undef unless defined $entries;
$entry = $entries->[0];
return undef unless defined $entry;
$entry->{end} = time::time_to_datetime( time() );
my $dbh = db::connect($config);
return update( $config, $entry );
}
#schedule id to id
sub update ($$) {
my $config = shift;
my $entry = shift;
return undef unless defined $entry->{session_id};
my $dbh = db::connect($config);
my $values = join( ",", map { $_ . '=?' } ( keys %$entry ) );
my @bind_values = map { $entry->{$_} } ( keys %$entry );
push @bind_values, $entry->{session_id};
my $query = qq{
update calcms_user_sessions
set $values
where session_id=?
};
return db::put( $dbh, $query, \@bind_values );
}
#map schedule id to id
sub delete($$) {
my $config = shift;
my $entry = shift;
return undef unless defined $entry->{session_id};
my $dbh = db::connect($config);
my $query = qq{
delete
from calcms_user_sessions
where session_id=?
};
my $bind_values = [ $entry->{session_id} ];
return db::put( $dbh, $query, $bind_values );
}
sub error($) {
my $msg = shift;
print "ERROR: $msg<br/>\n";
}
#do not delete last line!
1;

View File

@@ -176,7 +176,8 @@ sub insert ($$) {
my $config = shift;
my $entry = shift;
return unless ( defined $entry->{user} );
return unless defined $entry->{user};
my $dbh = db::connect($config);
return db::insert( $dbh, 'calcms_user_settings', $entry );
}