File Coverage

blib/lib/WWW/MLite/AuthSsn.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package WWW::MLite::AuthSsn; # $Id: AuthSsn.pm 29 2014-08-01 06:53:56Z minus $
2 2     2   131868 use strict;
  2         6  
  2         177  
3              
4             =head1 NAME
5              
6             WWW::MLite::AuthSsn - AAA mechanism support via sessions
7              
8             =head1 VERSION
9              
10             Version 1.00
11              
12             =head1 SYNOPSIS
13              
14             use WWW::MLite::AuthSsn;
15            
16             my $ssn = new WWW::MLite::AuthSsn(
17             -dsn => "driver:sqlite",
18             -sid => ($q->param("SID") || $q->cookie("SID") || undef),
19             -key => "SID",
20             -expire => "+3M",
21             -args => { DataSource => '/my/folder/sessions.sqlt' },
22             );
23            
24             # Authentication && Authorization
25             $ssn->authn or die("Bad authentication");
26             $ssn->authz or die("Bad authorization");
27            
28             ...
29            
30             # Access/Accounting
31             $ssn->access or die("Access denied");
32              
33              
34             =head1 DESCRIPTION
35              
36             Authorisation/Authentication/Access (AAA) mechanism support via sessions
37              
38             =head2 METHODS
39              
40             =over 8
41              
42             =item B<new>
43              
44             my $ssn = new WWW::MLite::AuthSsn(
45             -dsn => $dsn, # See CGI::Session
46             -sid => $sid || undef), # Session IDentifier
47             -key => "SID", # Key name
48             -expire => "+3M", # Expires
49             -args => { ... args ... }, # See CGI::Session
50             );
51              
52             Creating AuthSsn object
53              
54             =item B<init>
55              
56             $ssn->init;
57              
58             Initialising the session. For internal use only. Please do not use it
59              
60             Method returns status operation: 1 - successfully; 0 - not successfully
61              
62             =item B<update>
63              
64             $ssn->update;
65              
66             Updating static data of the session. For internal use only. Please do not use it
67              
68             =item B<authen>
69              
70             $ssn->authen;
71             $ssn->authen( $callback, ...arguments... );
72              
73             AAA Authentication.
74              
75             The method returns status operation: 1 - successfully; 0 - not successfully
76              
77             =item B<authz>
78              
79             $ssn->authz;
80             $ssn->authz( $callback, ...arguments... );
81              
82             AAA Authorization.
83              
84             The method returns status operation: 1 - successfully; 0 - not successfully
85              
86             =item B<access>
87              
88             $ssn->access;
89             $ssn->access( $callback, ...arguments... );
90              
91             AAA Accounting (AAA Access).
92              
93             The method returns status operation: 1 - successfully; 0 - not successfully
94              
95             =item B<get>
96              
97             $ssn->get( $key );
98              
99             Returns user session value by $key
100              
101             =item B<set>
102              
103             $ssn->set( $key, $value );
104              
105             Sets user session value by $key
106              
107             =item B<delete>
108              
109             $ssn->delete;
110              
111             Delete the session
112              
113             =item B<sid, usid>
114              
115             $ssn->sid;
116              
117             Returns current usid value
118              
119             =item B<expires>
120              
121             $ssn->expires;
122              
123             Returns current expires value
124              
125             =item B<status>
126              
127             $ssn->status;
128             $ssn->status( $newstatus );
129              
130             Returns status of a previously executed operation. If you specify $newstatus, there will push installation $newstatus
131              
132             =item B<reason, reason_translate>
133              
134             $ssn->reason;
135             $ssn->reason( $newreason );
136             $ssn->reason_translate;
137              
138             Returns reason of a previously executed operation. If you specify $newreason, there will push installation $newreason
139              
140             Now supported following values: DEFAULT, OK, UNAUTHORIZED, ERROR, SERVER_ERROR, NEW, TIMEOUT, LOGIN_INCORRECT,
141             PASSWORD_INCORRECT, DECLINED, AUTH_REQUIRED, FORBIDDEN.
142              
143             For translating this values to regular form please use method reason_translate like that
144              
145             =item B<error>
146              
147             $ssn->error();
148             $ssn->error( $newerror );
149              
150             Returns error of a previously executed operation. If you specify $newerror, there will push installation $newerror
151              
152             =item B<toexpire>
153              
154             $ssn->toexpire( $time );
155              
156             Returns expiration interval relative to ctime() form.
157              
158             If used with no arguments, returns the expiration interval if it was ever set.
159             If no expiration was ever set, returns undef.
160              
161             All the time values should be given in the form of seconds.
162             Following keywords are also supported for your convenience:
163              
164             +-----------+---------------+
165             | alias | meaning |
166             +-----------+---------------+
167             | s | Second |
168             | m | Minute |
169             | h | Hour |
170             | d | Day |
171             | w | Week |
172             | M | Month |
173             | y | Year |
174             +-----------+---------------+
175              
176             Examples:
177              
178             $ssn->toexpire("2h"); # expires in two hours
179             $ssn->toexpire(3600); # expires in one hour
180              
181             Note: all the expiration times are relative to session's last access time, not to its creation time.
182             To expire a session immediately, call delete() method.
183              
184             =item B<get_atime, get_ctime>
185              
186             $ssn->get_atime;
187             $ssn->get_ctime;
188              
189             Returns current atime and ctime values. Value atime - access time; ctime - create time
190              
191             =item B<get_data>
192              
193             $ssn->get_data;
194              
195             Returns user data of current session as hash-ref
196              
197             =item B<get_expires>
198              
199             $ssn->get_expires;
200              
201             Returns expiring interval of current session
202              
203             =back
204              
205             =head1 CONFIGURATION
206              
207             Sample in file conf/auth.conf:
208              
209             <Auth>
210             expires +3m
211             #sidkey usid
212             </Auth>
213              
214             =head1 EXAMPLES
215              
216             =over 8
217              
218             =item B<SQLite>
219              
220             my $dbh = DBI->connect("dbi:SQLite:dbname=/tmp/sessions.db", "","", { RaiseError => 1, sqlite_unicode => 1, });
221             $dbh->do('CREATE TABLE IF NOT EXISTS sessions ( id CHAR(32) NOT NULL PRIMARY KEY, a_session TEXT NOT NULL )');
222             my $ssn = new WWW::MLite::AuthSsn(
223             -dsn => "driver:sqlite",
224             -sid => ($q->param("SID") || $q->cookie("SID") || undef),
225             -key => "SID",
226             -expire => "+3M",
227             -args => { Handle => $dbh },
228             );
229              
230             See L<CGI::Session::Driver::sqlite>
231              
232             =back
233              
234             =head1 HISTORY
235              
236             See C<CHANGES> file
237              
238             =head1 TO DO
239              
240             See C<TODO> file
241              
242             =head1 SEE ALSO
243              
244             L<WWW::MLite>, L<CGI::Session>
245              
246             =head1 AUTHOR
247              
248             Serz Minus (Lepenkov Sergey) L<http://www.serzik.com> E<lt>minus@mail333.comE<gt>
249              
250             =head1 COPYRIGHT
251              
252             Copyright (C) 1998-2014 D&D Corporation. All Rights Reserved
253              
254             =head1 LICENSE
255              
256             This program is free software: you can redistribute it and/or modify
257             it under the terms of the GNU General Public License as published by
258             the Free Software Foundation, either version 3 of the License, or
259             (at your option) any later version.
260              
261             This program is distributed in the hope that it will be useful,
262             but WITHOUT ANY WARRANTY; without even the implied warranty of
263             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
264             GNU General Public License for more details.
265              
266             See C<LICENSE> file
267              
268             =cut
269              
270 2     2   12 use vars qw($VERSION);
  2         4  
  2         110  
271             $VERSION = '1.00';
272              
273 2     2   4092 use CGI::Session;
  0            
  0            
274             use CTK::Util qw/ :API :FORMAT :DATE /;
275             use CTK::TFVals qw/ :ALL /;
276              
277             use constant {
278             SIDKEY => 'usid',
279             EXPIRES => '+1h', # 3600sec as default
280            
281             # Îïðåäåëÿåì òàáëèöó ïåðåâîäà ñîñòîÿíèé
282             STAT => {
283             DEFAULT => to_utf8('Ñòàòóñ íåîïðåäåëåí'),
284             NEW => to_utf8('Ñåñèèÿ ñîçäàíà óñïåøíî'), # new/authen
285             OK => to_utf8('Îïåðàöèÿ ïðîøëà óñïåøíî'), # new/authen/authez/access
286             ERROR => to_utf8('Ñåññèÿ íå ñóùåñòâóåò èëè âîçíèêëà îøèáêà ñîçäàíèÿ ñåññèè'), # new
287             SERVER_ERROR => to_utf8('Îøèáêà ñåðâåðà'),
288             TIMEOUT => to_utf8('Ïåðèîä æèçíè ñåññèè èñòåê'), # new/access
289             UNAUTHORIZED => to_utf8('Âû íåàâòîðèçèðîâàíû'), # authz/access/delete
290             AUTH_REQUIRED => to_utf8('Òðåáóåòñÿ àâòîðèçàöèÿ'), # new
291             FORBIDDEN => to_utf8('Äîñòóï çàïðåùåí'), # authz/access
292             DECLINED => to_utf8('Ó÷åòíàÿ çàïèñü îòñóòñòâóåò'), # authen
293             LOGIN_INCORRECT => to_utf8('Íåïðàâèëüíûé ëîãèí'), # authen
294             PASSWORD_INCORRECT => to_utf8('Íåïðàâèëüíûé ïàðîëü'), # authen
295             },
296             };
297              
298              
299             sub new { # Êîíñòðóêòîð: ñîçäàåì èëè èñïîëüçóåì ðàíåå ñîçäàííóþ ñåññèèþ
300             my $class = shift;
301             my @in = read_attributes(
302             [
303             [qw/ DSN DRIVE DRIVER /], # 0 - DSN (See CGI::Session)
304             [qw/ SID USID /], # 1 - USID User Session IDentifier
305             [qw/ DSNARGS DSN_ARGS ARGS /], # 2 - DSN_ARGS (See CGI::Session)
306             [qw/ EXPIRE EXPIRES TIME /], # 3 - Expires (Êîëè÷åñòâî ñåêóíä êîòîðîå ìîæåò ñóùåñòâîâàòü ñåññèÿ)
307             [qw/ KEY SIDKEY USIDKEY NAME /],# 4 - Name of session key (See CGI::Session)
308             ],
309             ,@_);
310            
311             # Defines & Checks
312             my $dsn = fv2null($in[0]);
313             my $sid = fv2undef($in[1]);
314             my $args = fv2undef($in[2]);
315             my $expires = $in[3] || EXPIRES;
316             my $sidkey = $in[4] || SIDKEY;
317             croak("Can't define DSN") unless $dsn;
318            
319             my $status = 0;
320             my $reason = "ERROR";
321             my $error = _translate($reason);
322            
323             CGI::Session->name($sidkey);
324             my $session = CGI::Session->load($dsn, $sid, $args);
325             if ($sid) {
326             # Áûë ïåðåäàí SID çíà÷èò îæèäàåì êàêóþ-íèáóäü ñåññèþ!
327             if ($session) {
328             #my $session = new CGI::Session($dsn, undef, $args);
329             if ( $session->is_expired ) {
330             $reason = "TIMEOUT";
331             $error = "Session timed out (expired)";
332             $sid = undef;
333             } else {
334             if ( $session->is_empty ) {
335             #carp(">>>>>!!!! SESSION EMPTY !!!<<<");
336             $reason = "TIMEOUT";
337             $error = "Session timed out (empty)";
338             $sid = undef;
339             } else {
340             $status = 1;
341             $reason = "OK";
342             $error = '';
343             $sid = $session->id() || undef;
344             }
345             }
346             } else {
347             $error = CGI::Session->errstr();
348             carp(sprintf(__PACKAGE__." LOAD ERROR>", $error));
349             $sid = undef;
350             }
351             } else {
352             # Íå ñîçäà¸ì ñåññèþ, åñëè íå ïåðåäàí USID. Ïðè àâòîðèçàöèè ñîçäà¸ì ñåññèþ îòäåëüíî
353             $error = "";
354             unless ($session) {
355             $error = CGI::Session->errstr();
356             carp(sprintf(__PACKAGE__." LOAD ERROR>", $error));
357             }
358             return bless {
359             status => 0,
360             reason => "AUTH_REQUIRED",
361             error => $error,
362             session => $session,
363             expire => $expires, # Îðèãèíàëüíîå çíà÷åíèå
364             expires => $class->toexpire($expires), # Ïðåîáðàçîâàííîå (ïåðåñ÷èòàííîå) çíà÷åíèå
365             $sidkey => undef,
366             sidkey => $sidkey,
367             predata => {},
368             }, $class;
369              
370             }
371             my $self = bless {
372             status => $status,
373             reason => $reason,
374             error => $error,
375             session => $session,
376             expire => $expires, # îðèãèíàëüíîå çíà÷åíèå
377             expires => $class->toexpire($expires), # ñåññèÿ ñ ìîìåíòà ïîñëåäíåãî äîñòóïà ê íåé!
378             $sidkey => $sid, # ñîçäàííûé USID (âñòàâëÿåòñÿ åñëè óäà÷íî ñîçäàëäñÿ èëè ïðî÷èòàëñÿ)
379             sidkey => $sidkey,
380             predata => {},
381             }, $class;
382              
383            
384             #
385             # !!! Èñïîëüçîâàòü ìåòîä expire ó îáúåêòà CGI::Session; åãî íóæíî óñòàíîâèòü êàê äëÿ êóêè, òàê è
386             # !!! äëÿ ñàìîé ñåññèè, îíî óäàëèòñÿ ñàìî. Óñòàíàâëèâàòü äàííîå çíà÷åíèå íóæíî òîëüêî ïðè ëîãèíå
387             #
388             #$self->init if $status && $reason eq "NEW";
389             return $self;
390             }
391             sub init { # Èíèöèàëèçàöèÿ ñòàòèñòè÷åñêèõ äàííûõ â ÑÅÑÑÈÈ
392             my $self = shift;
393             my $session = $self->{session};
394             $session->param("ctime", time()); # Âðåìÿ ñîçäàíèÿ
395             $session->param("atime", time()); # Âðåìÿ äîñòóïà
396             $session->param("expires", $self->{expires}); # Âðåìÿ êàñòîìèçèðîâàííîå äëÿ äàííîé ñåññèè
397             $session->param("data", $self->{predata});
398             return 1;
399             }
400             sub update { # Îáíîâëåíèå ñòàòèñòè÷åñêèõ äàííûõ â ÑÅÑÑÈÈ
401             my $self = shift;
402             my $session = $self->{session};
403            
404             $session->param("atime", time()); # Âðåìÿ äîñòóïà
405             return 1;
406             }
407             sub authen { # AAA-authen
408             #
409             # Àóòåíòèôèêàöèÿ. Ïðîâåðêà - ïðàâèëüíî ëè ââåäåíû ëîãèí è ïàðîëü
410             #
411             # Ìîæåò ïðèíèìàòü çíà÷åíèÿ:
412             # LOGIN_INCORRECT / PASSWORD_INCORRECT / DECLINED / OK
413             #
414            
415             my $self = shift;
416             my $callback = shift;
417              
418             # !!! callback here !!!
419             if ($callback && ref($callback) eq 'CODE') {
420             return 0 unless $callback->($self,@_);
421             }
422            
423             $self->status(1);
424             $self->reason("OK");
425             $self->error('');
426            
427             return 1;
428             }
429             sub authz { # AAA-authz
430             #
431             # Àâòîðèçàöèÿ. Ïðîâåðêà ðîëåé è áàí-ëèñòîâ óðîâíÿ ÁÄ
432             #
433             # Ìîæåò ïðèíèìàòü çíà÷åíèÿ:
434             # UNAUTHORIZED / FORBIDDEN / OK
435             #
436            
437             my $self = shift;
438             my $callback = shift;
439             my $_session = $self->{session};
440              
441             # !!! callback here !!!
442             if ($callback && ref($callback) eq 'CODE') {
443             return 0 unless $callback->($self,@_);
444             }
445            
446             # Àâòîðèçàöèÿ ïðîøëà óñïåøíî. Ìîæíî ñîçäàâàòü ñåññèþ
447             #return 0 unless $self->init(undef,1);
448             my $session;
449             if ($self->sid) {
450             $session = $_session;
451             } else {
452             $session = $_session->new();
453             $self->init if $session;
454             }
455             if ($session) {
456             $session->expire($self->{expire});
457             $self->status(1);
458             $self->reason("NEW");
459             $self->error('');
460             $self->{$self->{sidkey}} = $session->id() || undef;
461             return 1;
462             }
463            
464             my $error = $session->errstr();
465             carp(sprintf(__PACKAGE__." NEW ERROR>", $error));
466             $self->status(0);
467             $self->reason("UNAUTHORIZED");
468             $self->error($error);
469             $self->{sid} = undef;
470            
471             return 0;
472            
473             }
474             sub access { # AAA-access
475             #
476             # Ïðîâåðêà äàííûõ ñåññèè íà ïðåäìåò ðàçðåøåíèÿ äîñòóïà ê ðàñøèðåííîìó ôóíêöèîíàëó
477             # Áàí-ëèñòû è ïðî÷åå
478             #
479              
480             my $self = shift;
481             my $callback = shift;
482            
483             # Ïðîâåðêà - à åñòü ëè îøèáêè ïðè èíèöèàëèçàöèè ??
484             return 0 unless $self->status;
485              
486             # Ïðîâåðêà expires è îáíîâëåíèå äàííûõ ïîñëåäíåãî äîñòóïà.
487             my $expires = $self->get_expires;
488             my $lastaccess = $self->get_atime;
489             my $newaccess = time();
490             #carp(">> expires: $expires; lastaccess: $lastaccess; newaccess: $newaccess");
491             my $accessto = (($newaccess - $lastaccess) > $expires); # true - timeout
492             if ($accessto) {
493             $self->delete(); # Óäàëÿåì åñëè âðåìÿ èñòåêëî
494             $self->status(0);
495             $self->reason("TIMEOUT");
496             $self->error(_translate("TIMEOUT"));
497             return 0;
498             }
499            
500             # !!! callback here !!!
501             if ($callback && ref($callback) eq 'CODE') {
502             return 0 unless $callback->($self,@_);
503             }
504            
505             # Âñå ïðîâåðêè ïðîøëè óñïåøíî. Äîñòóï ðàçðåøåí!
506             $self->update; # îáíîâëÿåì âðåìÿ äîñòóïà
507             $self->status(1);
508             $self->reason("OK");
509             $self->error('');
510             return 1;
511             }
512              
513             sub sid { # Ïîëó÷åíèå USID
514             my $self = shift;
515             return $self->{$self->{sidkey}} || undef;
516             }
517             sub usid { goto &sid }
518             sub get_expires { # Ïîëó÷åíèå expires èç ñåññèè
519             my $self = shift;
520             my $session = $self->{session};
521             if (defined $session) {
522             return $session->param("expires") || $self->{expires} || 0;
523             }
524             return $self->{expires} || 0;
525             }
526             sub get_ctime { # Ïîëó÷åíèå ctime èç ñåññèè
527             my $self = shift;
528             my $session = $self->{session};
529             return 0 unless defined $session;
530             return $session->param("ctime") || 0;
531             }
532             sub get_atime { # Ïîëó÷åíèå atime èç ñåññèè
533             my $self = shift;
534             my $session = $self->{session};
535             return 0 unless defined $session;
536             return $session->param("atime") || 0;
537             }
538             sub get_data { # Ïîëó÷åíèå data èç ñåññèè êàê õýø (ïîëíîñòüþ âñå äàííûå)
539             my $self = shift;
540             my $session = $self->{session};
541             return undef unless defined $session;
542             return $session->param("data") || {};
543             }
544             sub status { # Ïîëó÷åíèå/óñòàíîâêà ñòàòóñà
545             my $self = shift;
546             my $ns = shift;
547             $self->{status} = fv2zero($ns) if defined $ns;
548             return $self->{status};
549             }
550             sub reason { # Ïîëó÷åíèå/óñòàíîâêà ïðè÷èíû
551             my $self = shift;
552             my $nr = shift;
553             $self->{reason} = fv2null($nr) if defined $nr;
554             return $self->{reason};
555             }
556             sub error { # Ïîëó÷åíèå/óñòàíîâêà îøèáêè
557             my $self = shift;
558             my $ne = shift;
559             $self->{error} = fv2null($ne) if defined $ne;
560             return $self->{error};
561             }
562             sub get { # Ïîëó÷åíèå óêàçàííîãî êëþ÷à èç ïîëüçîâàòåëüñêèõ äàííûõ ñåññèè
563             my $self = shift;
564             my $key = shift || return;
565             my $session = $self->{session};
566             my $data = $self->get_data;
567             my $value;
568             if ($data && (ref($data) eq 'HASH') && !$session->is_empty) {
569             $value = $data->{$key};
570             } else { # Áåð¸ì èç âðåìåííîé òàáëè÷êè
571             $value = $self->{predata}->{$key};
572             }
573             return defined($value) ? $value : undef;
574             }
575             sub set { # Çàïèñü óêàçàííîãî êëþ÷à â ïîëüçîâàòåëüñêèå äàííûå ñåññèè
576             my $self = shift;
577             my $key = shift || return 0;
578             my $value = shift;
579             my $session = $self->{session};
580             my $data = $self->get_data;
581             if ($data && (ref($data) eq 'HASH') && !$session->is_empty) {
582             $data->{$key} = $value;
583             $session->param("data", $data);
584             return 1;
585             } else { # Ñîõðàíÿåì âî âðåìåííóþ òàáëè÷êó
586             $data = $self->{predata};
587             $data->{$key} = $value;
588             return 1;
589             }
590             return 0;
591             }
592             sub delete { # Óäàëåíèå ñåññèè
593             my $self = shift;
594             my $session = $self->{session};
595             if ($self->status && $session && $session->id) {
596             $session->delete;
597             }
598             $self->{session} = undef ;
599             $self->status(0);
600             $self->reason("UNAUTHORIZED");
601             $self->error(_translate("UNAUTHORIZED"));
602             return 1;
603             }
604             sub reason_translate { # Ïåðåâîä îïèñàíèÿ reason íà ðóññêèé ÿçûê ñ ñ ðàñøèôðîâêîé
605             my $self = shift;
606             my $reason = shift || $self->reason();
607             return _translate($reason);
608             }
609             sub toexpire { # Ïåðåâîä â expires
610             my $self = shift;
611             my $str = shift || 0;
612              
613             return 0 unless defined $str;
614             return $1 if $str =~ m/^[-+]?(\d+)$/;
615              
616             my %_map = (
617             s => 1,
618             m => 60,
619             h => 3600,
620             d => 86400,
621             w => 604800,
622             M => 2592000,
623             y => 31536000
624             );
625              
626             my ($koef, $d) = $str =~ m/^([+-]?\d+)([smhdwMy])$/;
627             unless ( defined($koef) && defined($d) ) {
628             croak "toexpire(): couldn't parse '$str' into \$koef and \$d parts. Possible invalid syntax";
629             }
630             return $koef * $_map{ $d };
631             }
632             sub _translate { # Ïåðåâîä îïèñàíèÿ reason íà ðóññêèé ÿçûê ñ ñ ðàñøèôðîâêîé
633             my $reason = shift || 'DEFAULT';
634             my $transtable = STAT;
635              
636             return $transtable->{DEFAULT} unless
637             grep {$_ eq 'DEFAULT'} keys %$transtable;
638            
639             return $transtable->{$reason};
640             }
641             sub DESTROY {
642             my $self = shift;
643             my $session = $self->{session};
644             $session->flush() if $session;
645             undef $self;
646             }
647              
648             1;
649              
650             __END__