diff --git a/install/create.sql b/install/create.sql index 3c23393..9adf0ef 100644 --- a/install/create.sql +++ b/install/create.sql @@ -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 + + diff --git a/lib/calcms/auth.pm b/lib/calcms/auth.pm index c8df203..6fbc270 100644 --- a/lib/calcms/auth.pm +++ b/lib/calcms/auth.pm @@ -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; } diff --git a/lib/calcms/user_sessions.pm b/lib/calcms/user_sessions.pm new file mode 100644 index 0000000..8954eda --- /dev/null +++ b/lib/calcms/user_sessions.pm @@ -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
\n"; +} + +#do not delete last line! +1; diff --git a/lib/calcms/user_settings.pm b/lib/calcms/user_settings.pm index 5e71dca..732d8c5 100644 --- a/lib/calcms/user_settings.pm +++ b/lib/calcms/user_settings.pm @@ -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 ); }