X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FInterface%2FWeb%2FSession.pm;fp=rt%2Flib%2FRT%2FInterface%2FWeb%2FSession.pm;h=4998c34f9e0d95c3310ba98c719f84309a2942c2;hb=b4b0c7e72d7eaee2fbfc7022022c9698323203dd;hp=0000000000000000000000000000000000000000;hpb=2dfda73eeb3eae2d4f894099754794ef07d060dd;p=freeside.git diff --git a/rt/lib/RT/Interface/Web/Session.pm b/rt/lib/RT/Interface/Web/Session.pm new file mode 100644 index 000000000..4998c34f9 --- /dev/null +++ b/rt/lib/RT/Interface/Web/Session.pm @@ -0,0 +1,285 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: +# +# This work is made available to you under the terms of Version 2 of +# the GNU General Public License. A copy of that license should have +# been provided with this software, but in any event can be snarfed +# from www.gnu.org. +# +# This work is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# END BPS TAGGED BLOCK }}} + +package RT::Interface::Web::Session; +use warnings; +use strict; + +use RT::CurrentUser; + +=head1 NAME + +RT::Interface::Web::Session - RT web session class + +=head1 SYNOPSYS + + +=head1 DESCRIPTION + +RT session class and utilities. + +CLASS METHODS can be used without creating object instances, +it's mainly utilities to clean unused session records. + +Object is tied hash and can be used to access session data. + +=head1 METHODS + +=head2 CLASS METHODS + +=head3 Class + +Returns name of the class that is used as sessions storage. + +=cut + +sub Class { + my $self = shift; + + my $class = RT->Config->Get('WebSessionClass') + || $self->Backends->{RT->Config->Get('DatabaseType')} + || 'Apache::Session::File'; + eval "require $class"; + die $@ if $@; + return $class; +} + +=head3 Backends + +Returns hash reference with names of the databases as keys and +sessions class names as values. + +=cut + +sub Backends { + return { + mysql => 'Apache::Session::MySQL', + Pg => 'Apache::Session::Postgres', + }; +} + +=head3 Attributes + +Returns hash reference with attributes that are used to create +new session objects. + +=cut + +sub Attributes { + + return $_[0]->Backends->{RT->Config->Get('DatabaseType')} ? { + Handle => $RT::Handle->dbh, + LockHandle => $RT::Handle->dbh, + Transaction => 1, + } : { + Directory => $RT::MasonSessionDir, + LockDirectory => $RT::MasonSessionDir, + Transaction => 1, + }; +} + +=head3 Ids + +Returns array ref with list of the session IDs. + +=cut + +sub Ids { + my $self = shift || __PACKAGE__; + my $attributes = $self->Attributes; + if( $attributes->{Directory} ) { + return $self->_IdsDir( $attributes->{Directory} ); + } else { + return $self->_IdsDB( $RT::Handle->dbh ); + } +} + +sub _IdsDir { + my ($self, $dir) = @_; + require File::Find; + my %file; + File::Find::find( + sub { return unless /^[a-zA-Z0-9]+$/; + $file{$_} = (stat($_))[9]; + }, + $dir, + ); + + return [ sort { $file{$a} <=> $file{$b} } keys %file ]; +} + +sub _IdsDB { + my ($self, $dbh) = @_; + my $ids = $dbh->selectcol_arrayref("SELECT id FROM sessions ORDER BY LastUpdated DESC"); + die "couldn't get ids: ". $dbh->errstr if $dbh->errstr; + return $ids; +} + +=head3 ClearOld + +Takes seconds and deletes all sessions that are older. + +=cut + +sub ClearOld { + my $class = shift || __PACKAGE__; + my $attributes = $class->Attributes; + if( $attributes->{Directory} ) { + return $class->_CleariOldDir( $attributes->{Directory}, @_ ); + } else { + return $class->_ClearOldDB( $RT::Handle->dbh, @_ ); + } +} + +sub _ClearOldDB { + my ($self, $dbh, $older_than) = @_; + my $rows; + unless( int $older_than ) { + $rows = $dbh->do("DELETE FROM sessions"); + die "couldn't delete sessions: ". $dbh->errstr unless defined $rows; + } else { + require POSIX; + my $date = POSIX::strftime("%Y-%m-%d %H:%M", localtime( time - int $older_than ) ); + + my $sth = $dbh->prepare("DELETE FROM sessions WHERE LastUpdated < ?"); + die "couldn't prepare query: ". $dbh->errstr unless $sth; + $rows = $sth->execute( $date ); + die "couldn't execute query: ". $dbh->errstr unless defined $rows; + } + + $RT::Logger->info("successfuly deleted $rows sessions"); + return; +} + +sub _ClearOldDir { + my ($self, $dir, $older_than) = @_; + + require File::Spec if int $older_than; + + my $now = time; + my $class = $self->Class; + my $attrs = $self->Attributes; + + foreach my $id( @{ $self->Ids } ) { + if( int $older_than ) { + my $ctime = (stat(File::Spec->catfile($dir,$id)))[9]; + if( $ctime > $now - $older_than ) { + $RT::Logger->debug("skipped session '$id', isn't old"); + next; + } + } + + my %session; + local $@; + eval { tie %session, $class, $id, $attrs }; + if( $@ ) { + $RT::Logger->debug("skipped session '$id', couldn't load: $@"); + next; + } + tied(%session)->delete; + $RT::Logger->info("successfuly deleted session '$id'"); + } + return; +} + +=head3 ClearByUser + +Checks all sessions and if user has more then one session +then leave only the latest one. + +=cut + +sub ClearByUser { + my $self = shift || __PACKAGE__; + my $class = $self->Class; + my $attrs = $self->Attributes; + + my %seen = (); + foreach my $id( @{ $self->Ids } ) { + my %session; + local $@; + eval { tie %session, $class, $id, $attrs }; + if( $@ ) { + $RT::Logger->debug("skipped session '$id', couldn't load: $@"); + next; + } + if( $session{'CurrentUser'} && $session{'CurrentUser'}->id ) { + unless( $seen{ $session{'CurrentUser'}->id }++ ) { + $RT::Logger->debug("skipped session '$id', first user's session"); + next; + } + } + tied(%session)->delete; + $RT::Logger->info("successfuly deleted session '$id'"); + } +} + +sub TIEHASH { + my $self = shift; + my $id = shift; + + my $class = $self->Class; + my $attrs = $self->Attributes; + + my %session; + + local $@; + eval { tie %session, $class, $id, $attrs }; + eval { tie %session, $class, undef, $attrs } if $@; + if ( $@ ) { + die loc("RT couldn't store your session.") . "\n" + . loc("This may mean that that the directory '[_1]' isn't writable or a database table is missing or corrupt.", + $RT::MasonSessionDir) + . "\n\n" + . $@; + } + + return tied %session; +} + +1;