File Coverage

blib/lib/Labyrinth/Session.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Labyrinth::Session;
2              
3 2     2   6059 use warnings;
  2         5  
  2         56  
4 2     2   9 use strict;
  2         2  
  2         51  
5              
6 2     2   7 use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT @EXPORT_OK);
  2         2  
  2         205  
7             $VERSION = '5.31';
8              
9             =head1 NAME
10              
11             Labyrinth::Session - Session Management for Labyrinth.
12              
13             =head1 SYNOPSIS
14              
15             use Labyrinth::Session;
16             Login($username,$password);
17             my $logged_in = 1 if(my $user = ValidSession());
18              
19             =head1 DESCRIPTION
20              
21             Provides the session management functionality, including Login & Logout
22             functions, to maintain a user's access to the system.
23              
24             =cut
25              
26             # -------------------------------------
27             # Export Details
28              
29             require Exporter;
30             @ISA = qw(Exporter);
31             %EXPORT_TAGS = (
32             'all' => [ qw(
33             ValidSession VerifyUser Authorised UserAccess FolderAccess
34             ResetLanguage UpdateSession
35             ) ]
36             );
37              
38             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
39             @EXPORT = ( @{ $EXPORT_TAGS{'all'} } );
40              
41             # -------------------------------------
42             # Library Modules
43              
44 2     2   10 use Labyrinth::Audit;
  2         7  
  2         265  
45 2     2   80 use Labyrinth::Globals;
  0            
  0            
46             use Labyrinth::DTUtils;
47             use Labyrinth::CookieLib;
48             use Labyrinth::Mailer;
49             use Labyrinth::Users;
50             use Labyrinth::Variables;
51              
52             use Session::Token;
53              
54             # -------------------------------------
55             # Variables
56              
57             my (%USERS,%FOLDERS);
58              
59             # -------------------------------------
60             # The Functional Interface
61              
62             =head1 FUNCTIONS
63              
64             =over 4
65              
66             =item Login
67              
68             Handles login capabilities, including bad logins.
69              
70             =item InternalLogin
71              
72             Saves the internal session of a successful login. Also used for automatic
73             authenticated logins.
74              
75             =item Logout
76              
77             Handles logout capabilities.
78              
79             =cut
80              
81             sub Login {
82             # forgotten password?
83             return _forgotten() if($cgiparams{cause} && $cgiparams{forgot});
84              
85             # values complete?
86             return SetError('ERROR',1) unless($cgiparams{cause} && $cgiparams{effect});
87              
88             # verify username/password
89             my @rows = CheckUser($cgiparams{cause},$cgiparams{effect});
90             return SetError('BADUSER',1) unless(@rows);
91              
92             InternalLogin($rows[0]);
93             }
94              
95             sub InternalLogin {
96             my $user = shift;
97              
98             $tvars{user} = $user;
99              
100             # add entry to session table
101             my $session;
102             ( $session,
103             $tvars{user}{name},
104             $tvars{'loginid'},
105             $tvars{realm},
106             $tvars{langcode}
107             ) = _save_session($user->{realname},$user->{userid},$user->{realm},$user->{langcode});
108              
109             # set template variables
110             $tvars{'loggedin'} = 1;
111             $tvars{user}{folder} = 1;
112             $tvars{user}{option} = 0;
113             $tvars{user}{userid} = $tvars{'loginid'};
114             $tvars{user}{access} = VerifyUser($tvars{'loginid'});
115              
116             $tvars{realm} = $user->{realm} || 'public';
117              
118             if($tvars{realm} ne 'public') {
119             SetCommand('home-' . $tvars{realm});
120             }
121             }
122              
123             sub Logout {
124             my @rows = CheckUser('GUEST','GUEST');
125             unless(@rows) {
126             push @rows, {realname => 'Guest', userid => 0, realm => 'public', langcode => 'en'};
127             }
128              
129             my $session;
130             ( $session,
131             $tvars{user}{name},
132             $tvars{'loginid'},
133             $tvars{realm},
134             $tvars{langcode}
135             ) = _save_session($rows[0]->{realname},$rows[0]->{userid},$rows[0]->{realm},$rows[0]->{langcode});
136             $tvars{loggedin} = 0;
137             $tvars{user}{folder} = 1;
138             $tvars{user}{option} = 0;
139             $tvars{user}{userid} = $tvars{'loginid'};
140             $tvars{user}{access} = VerifyUser($tvars{'loginid'});
141              
142             $tvars{redirect} = $settings{'logout-redirect'}
143             if($settings{'logout-redirect'} && $settings{'logout-redirect'} ne $cgiparams{act});
144             return($session,$tvars{user}{name},$tvars{'loginid'},$tvars{realm});
145             }
146              
147             =item ValidSession
148              
149             Reloads an existing session, or creates a new one.
150              
151             =item Store
152              
153             Stores the current request, while the user logs in. (A simple form of continuations)
154              
155             =item Retrieve
156              
157             Retrieves the last request, if the user has logged in. (A simple form of continuations)
158             If the user is already login will set according to their realm.
159              
160             =cut
161              
162             sub ValidSession {
163             # read cookie
164             my ($userid,$name,$realm,$folder,$langcode,$option) = _get_session();
165             $tvars{'loggedin'} = ($name && lc $name ne 'guest') ? 1 : 0;
166             $tvars{'loginid'} = $userid;
167             $tvars{'langcode'} = $langcode;
168              
169             $tvars{user}{name} = $name;
170             $tvars{user}{userid} = $userid;
171             $tvars{user}{folder} = $folder;
172             $tvars{user}{option} = $option;
173             $tvars{user}{access} = VerifyUser($userid);
174              
175             my $user = Labyrinth::Session->new($userid,$name,$realm);
176             return $user;
177             }
178              
179             sub Store {
180             # we don't want to continually logout!
181             return if($cgiparams{act} eq 'user-logout');
182              
183             # store ready for continuation after login
184             if (&GetCookies('sessionid')){
185             my $session = $main::Cookies{'sessionid'};
186             if($session && $session ne 'expired') {
187             if(my @rows = $dbi->GetQuery('array','CheckSession',$session)) {
188             my $query;
189             if($cgiparams{lastpage} && $settings{lastpagereturn}) {
190             $query = $cgiparams{lastpage};
191             $query =~ y/~/=/;
192             $query =~ y/ /&/;
193             } elsif($settings{lastpagereturn}) {
194             $query = join("&",map {"$_=$cgiparams{$_}"} keys %cgiparams);
195             }
196             $dbi->DoQuery('StoreSession',$query,$session);
197             }
198             }
199             }
200             }
201              
202             sub Retrieve {
203             my $act = 'home-' . $tvars{realm};
204             LogDebug("Retrieve: 1.=$act");
205              
206             if(my @rows = $dbi->GetQuery('hash','GetRealmByName',$tvars{realm})) {
207             $act = $rows[0]->{command};
208             }
209             LogDebug("Retrieve: 2.=$act");
210              
211             if (&GetCookies('sessionid')){
212             my $session = $main::Cookies{'sessionid'};
213             if($session && $session ne 'expired') {
214             if(my @rows = $dbi->GetQuery('array','RetrieveSession',$session)) {
215             LogDebug("Retrieve: 3.=[".($rows[0]->[0]||'')."]");
216             my @parts = $rows[0]->[0] ? split("&",$rows[0]->[0]) : ();
217             for my $part (@parts) {
218             $cgiparams{$1} = $2 if($part =~ /(.*?)=(.*)/);
219             }
220             $act = $cgiparams{act} if(@parts);
221             $dbi->DoQuery('StoreSession','',$session);
222             LogDebug("Retrieve: 4.=$act");
223             }
224             }
225             }
226              
227             LogDebug("Retrieve: NEXT=$act");
228             SetCommand($act);
229             }
230              
231             =item Authorised($level[,$userid])
232              
233             Verifies the user has authorisation to the requested level. If userid is
234             omitted, the current user is assumed.
235              
236             =item UserAccess
237              
238             Returns the folders the user (and associated groups) has access to.
239              
240             =item VerifyUser
241              
242             Looks up the user's authorisation level, based on their user id and any groups
243             they belong to.
244              
245             =item CheckUser
246              
247             Given a username and password checks the database to ensure that the user
248             exists. Note that this uses both SHA1 (new encryption) and OLD_PASSWORD (old
249             encyription) to find the user. The latter is preserved for older
250             implementations.
251              
252             =cut
253              
254             sub Authorised {
255             my $needed = shift;
256             return 0 if($needed && !$tvars{loggedin});
257              
258             my $userid = shift || $tvars{'loginid'};
259             my $actual = VerifyUser($userid);
260              
261             # LogDebug("Authorised - needed=[$needed], actual=[$actual], result=[".($actual >= $needed ? 1 : 0)."]");
262              
263             return $actual >= $needed ? 1 : 0;
264             }
265              
266             sub UserAccess {
267             my $folderid = shift;
268             my $groups = shift;
269              
270             my @rows = $dbi->GetQuery('array','FolderAccess',$tvars{loginid},$groups);
271             return 0 unless(@rows);
272             return $rows[0]->[0];
273             }
274              
275             my %folderaccess;
276              
277             sub VerifyUser {
278             my $userid = shift || 0;
279             my $folder = shift || 'public';
280             my $access = 0;
281             LogDebug("VerifyUser($userid,'$folder')");
282              
283             return $access unless($userid);
284              
285             # return if known
286             return $folderaccess{$userid}{$folder}
287             if($folderaccess{$userid}{$folder});
288              
289             # check base access
290             my $user = GetUser($userid);
291             $access = $user->{accessid};
292             $tvars{user}{$_} = $user->{$_} for(qw(realname nickname email));
293              
294             my @folders = ($folder ? GetFolderIDs( ref => $folder ) : (1));
295             my $folders = join(',',grep {$_} @folders);
296             my $groups = GetGroupIDs($userid);
297              
298             # check folder permissions
299             my @rows = $dbi->GetQuery('hash','GetPermission',{folders=>$folders,groups=>$groups,user=>$userid});
300             foreach my $rec (@rows) {
301             $access = $rec->{accessid} if($access < $rec->{accessid});
302             }
303              
304             LogDebug("-access=$access");
305              
306             $folderaccess{$userid}{$folder} = $access;
307             return $access;
308             }
309              
310             sub CheckUser {
311             my ($user,$pass) = @_;
312              
313             return @{$USERS{$user}} if($USERS{$user});
314              
315             # SHA1 encryption
316             my @rows = $dbi->GetQuery('hash','CheckUser',$user,$pass);
317             if(@rows) {
318             $USERS{$user} = \@rows;
319             return @rows;
320             }
321              
322             # OLD PASSWORD encryption
323             @rows = $dbi->GetQuery('hash','CheckUserOld',$user,$pass);
324             if(@rows) {
325             $USERS{$user} = \@rows;
326             return @rows;
327             }
328              
329             # user not found
330             return;
331             }
332              
333             =item LoadFolders
334              
335             Convienence function to load all folders when required.
336              
337             =item GetFolderIDs
338              
339             Returns the list of folders for the given leaf folder.
340              
341             =item FolderAccess
342              
343             Returns true or false as to whether the given user has access to the specified
344             folder. If no folder is given the default 'public' folder is used. If no user
345             is given the currently logged in user is used.
346              
347             =cut
348              
349             sub LoadFolders {
350             return if(%FOLDERS);
351              
352             my @rows = $dbi->GetQuery('hash','AllFolders');
353             for my $row (@rows) {
354             $FOLDERS{$row->{folderid}} = $row;
355             }
356             }
357              
358             sub GetFolderIDs {
359             my %hash = @_;
360             my ($id,%ids,@ids);
361              
362             LoadFolders();
363              
364             if($hash{id}) {
365             $id = $hash{id};
366              
367             } elsif($hash{ref}) {
368             for my $folderid (keys %FOLDERS) {
369             if($FOLDERS{$folderid}->{path} eq $hash{ref}) {
370             $id = $folderid;
371             last;
372             }
373             }
374             }
375              
376             return '0' unless($id);
377              
378             while($FOLDERS{$id} && $FOLDERS{$id}->{parent} > 0) {
379             $ids{$id} = 1;
380             $id = $FOLDERS{$hash{id}}->{parent};
381             }
382             $ids{$id} = 1;
383             @ids = keys %ids;
384              
385             return @ids if(wantarray);
386             return join(",",@ids);
387             }
388              
389             sub FolderAccess {
390             my $folder = shift || 'public';
391             my $userid = shift || $tvars{loginid};
392              
393             LogDebug("FolderAccess('$folder',$userid)");
394              
395             my @rows = $dbi->GetQuery('hash','GetFolderByPath',$folder);
396             return 0 unless(@rows);
397              
398             my $access = VerifyUser($userid,$folder);
399             return 1 if($access >= $rows[0]->{accessid});
400             return 0;
401             }
402              
403             =item GetGroupIDs
404              
405             Returns the list of groups the given user has access to.
406              
407             =cut
408              
409             sub GetGroupIDs {
410             my $userid = shift;
411             my %groups;
412              
413             $groups{1} = 1; # everyone is public
414              
415             # find primary groups for user
416             my @rows = $dbi->GetQuery('array','GetGroupUserMap',$userid);
417              
418             while(@rows) {
419             my (@parents);
420             foreach (@rows) {
421             next if($_->[0] == 0); # a bad entry
422             next if($groups{$_->[0]}); # already seen group
423             $groups{$_->[0]} = 1;
424             push @parents, $_->[0];
425             }
426              
427             last unless(@parents);
428              
429             # find associated groups for user
430             @rows = $dbi->GetQuery('array','GetGroupParents',{groups=>join(",",@parents)});
431             }
432              
433             return keys %groups if(wantarray);
434             return join(",",keys %groups);
435             }
436              
437             =item ResetLanguage
438              
439             Within the current session, this function allows the user to change the
440             language associated within the system.
441              
442             Currently this language element is under used, and could be used for error and
443             message strings pulled from a phrasebook.
444              
445             =cut
446              
447             sub ResetLanguage {
448             my $lang = shift;
449             return unless($lang);
450              
451             my @rows = $dbi->GetQuery('array','GetLang',$lang);
452             return unless(@rows);
453              
454             $dbi->DoQuery('SetLangUser',$lang,$tvars{loginid});
455             $dbi->DoQuery('SetLangSession',$lang,$settings{session});
456             $tvars{langcode} = $lang;
457             }
458              
459             =item UpdateSession
460              
461             Updates specific fields for the current session.
462              
463             =back
464              
465             =cut
466              
467             sub UpdateSession {
468             my %hash = @_;
469             my $session = delete $hash{session};
470             $session ||= $main::Cookies{'sessionid'};
471             for(keys %hash) {
472             next unless($hash{$_});
473             $dbi->DoQuery('UpdateSession',{field=>$_},$hash{$_},$session);
474             }
475              
476             if($hash{optionid}) {
477             $tvars{user}{option} = $hash{optionid};
478             }
479             }
480              
481             # -------------------------------------
482             # The Object Interface
483              
484             =head1 OBJECT METHODS
485              
486             In addition to the above functions, the Session Management also allows for an
487             object interface.
488              
489             =over 4
490              
491             =item new
492              
493             Create a new session object.
494              
495             =item realm
496              
497             Returns the current realm.
498              
499             =cut
500              
501             sub new {
502             my $self = shift;
503              
504             my $atts = {
505             'userid' => $_[0],
506             'name' => $_[1],
507             'realm' => $_[2],
508             };
509              
510             # create the object
511             bless $atts, $self;
512             return $atts;
513             }
514              
515             sub realm {
516             my $self = shift;
517             return $self->{realm};
518             }
519              
520             sub DESTROY {}
521              
522             # -------------------------------------
523             # Internal Functions
524              
525             sub _create_session_key {
526             my $gen = Session::Token->new(length => 24);
527             return $gen->get();
528             }
529              
530             sub _get_session {
531             my $tsnow = formatDate(0);
532              
533             if($settings{delete_sessions}) {
534             # delete timed out sessions, including this one if necessary (self cleaning)
535             my $timeout = $settings{timeout} || 0;
536             my $tsthen = $tsnow - $timeout;
537             $dbi->DoQuery('DeleteSessions',$tsthen);
538             }
539              
540             # default settings
541             my ($userid,$name,$realm,$folder,$langcode,$option) = (0,'guest','public',1,'en',0);
542             my $session;
543              
544             # retrieve the cookie
545             if($settings{testing}) {
546             $userid = $cgiparams{cluserid} if($cgiparams{cluserid});
547             $name = $cgiparams{clname} if($cgiparams{clname});
548             $realm = $cgiparams{clrealm} if($cgiparams{clrealm});
549             $folder = $cgiparams{clfolder} if($cgiparams{clfolder});
550             $langcode = $cgiparams{cllangcode} if($cgiparams{cllangcode});
551             #LogDebug("get_session: testing: ($userid,$name,$realm,$folder)");
552             } elsif (&GetCookies('sessionid')){
553             $session = $main::Cookies{'sessionid'};
554             LogDebug("session=$session");
555             } else {
556             LogDebug("session=");
557             }
558              
559             if(!$userid) {
560             my @rows = CheckUser('GUEST','GUEST');
561             $userid = $rows[0]->{userid};
562             }
563              
564             $session = undef if($session && $session eq 'expired');
565              
566             # try and time stamp the session
567             if($session) {
568             my @rows = $dbi->GetQuery('array','CheckSession',$session);
569             LogDebug("CheckSession: 1.".(@rows ? 'found' : 'no')." session");
570             if(@rows) {
571             ($userid,$name,$realm,$folder,$langcode,$option) = @{$rows[0]};
572             $option = $cgiparams{option} if($cgiparams{option});
573             UpdateSession(timeout => $tsnow, optionid => $option, session => $session);
574             } else {
575             $session = undef;
576             }
577             }
578              
579             # check we actually updated in time
580             if($session) {
581             my @rows = $dbi->GetQuery('array','CheckSession',$session);
582             LogDebug("CheckSession: 2.".(@rows ? 'found' : 'no')." session");
583             $session = undef unless(@rows);
584             }
585              
586             # create a new session if necessary
587             unless($session) {
588             if($settings{testing}) {
589             ($session) = Logout();
590             } else {
591             ($session,$name,$userid,$realm,$langcode) = Logout();
592             }
593             }
594             $settings{session} = $session;
595              
596             LogDebug('GetSession:name=['.($name||'').'], realm=['.($realm||'').']');
597              
598             return $userid,$name,$realm,$folder,$langcode,$option;
599             }
600              
601             sub _save_session {
602             my @fields = @_;
603             my $session;
604              
605             LogDebug('SaveSession:1 fields=['.join('][',map {$_ || ''} @fields).']');
606              
607             $fields[0] ||= 'guest';
608             $fields[1] ||= 0;
609             $fields[2] ||= 'public';
610             $fields[3] ||= 'en';
611             $fields[4] ||= 0;
612              
613             if($fields[1] == 0) {
614             my @rows = CheckUser('GUEST','GUEST');
615             $fields[1] = $rows[0]->{userid};
616             }
617              
618             LogDebug('SaveSession:2 fields=['.join('][',map {$_ || ''} @fields).']');
619              
620             $session = $main::Cookies{'sessionid'} if(GetCookies('sessionid'));
621             if($session && $session ne 'expired') {
622             # check the session has been recorded in case it's been reaped, a user
623             # can relogin with the same session key
624             my @rows = $dbi->GetQuery('array','CheckSession',$session);
625             LogDebug("CheckSession: 3.".(@rows ? 'found' : 'no')." session");
626             if(@rows) {
627             $dbi->DoQuery('UpdateSessionX',formatDate(0),@fields,$session);
628             } else {
629             $dbi->DoQuery('CreateSession',formatDate(0),@fields,$session);
630             }
631             } else {
632             # add entry to session table
633             $session = _create_session_key($cgiparams{cause});
634             $dbi->DoQuery('CreateSession',formatDate(0),@fields,$session);
635             }
636              
637             SetCookiePath('/');
638             $tvars{cookie} = SetCookie('sessionid',$session);
639             LogDebug('SaveSession:4 fields=['.join('][',map {$_ || ''} @fields).']');
640             return ($session,@fields);
641             }
642              
643             sub _forgotten {
644             my @rows = $dbi->GetQuery('hash','FindUser',$cgiparams{cause});
645             return SetError('BADUSER') unless(@rows);
646             return SetError('BANUSER') if($rows[0]->{password} eq '-banned-');
647              
648             my $password = FreshPassword();
649             my $name = $rows[0]->{'realname'} || 'User';
650              
651             $dbi->DoQuery('ChangePassword',$password,$rows[0]->{userid});
652             MailSend( template => 'mailer/forgot.eml',
653             name => $name,
654             password => $password,
655             email => $cgiparams{cause}
656             );
657              
658             if(MailSent()) {
659             SetCommand('user-forgot');
660             } else {
661             SetError('BADMAIL');
662             }
663             }
664              
665             1;
666              
667             __END__