File Coverage

blib/lib/Dancer/Session/Abstract.pm
Criterion Covered Total %
statement 58 63 92.0
branch 12 20 60.0
condition n/a
subroutine 20 21 95.2
pod 12 14 85.7
total 102 118 86.4


line stmt bran cond sub pod time code
1             package Dancer::Session::Abstract;
2             our $AUTHORITY = 'cpan:SUKRIA';
3             #ABSTRACT: abstract class for session engine
4             $Dancer::Session::Abstract::VERSION = '1.3514_04'; # TRIAL
5             $Dancer::Session::Abstract::VERSION = '1.351404';
6 95     95   1984 use strict;
  95         187  
  95         2328  
7 95     95   422 use warnings;
  95         171  
  95         2149  
8 95     95   416 use Carp;
  95         160  
  95         5621  
9              
10 95     95   595 use base 'Dancer::Engine';
  95         230  
  95         9994  
11              
12 95     95   626 use Dancer::Config 'setting';
  95         220  
  95         3893  
13 95     95   579 use Dancer::Cookies;
  95         214  
  95         2267  
14 95     95   526 use File::Spec;
  95         230  
  95         64657  
15              
16             __PACKAGE__->attributes('id');
17              
18             # args: ($class, $id)
19             # receives a session id and should return a session object if found, or undef
20             # otherwise.
21             sub retrieve {
22 1     1 1 1377 confess "retrieve not implemented";
23             }
24              
25             # args: ($class)
26             # create a new empty session, flush it and return it.
27             sub create {
28 1     1 1 856 confess "create not implemented";
29             }
30              
31             # args: ($self)
32             # write the (serialized) current session to the session storage
33             sub flush {
34 1     1 1 645 confess "flush not implemented";
35             }
36              
37             # args: ($self)
38             # remove the session from the session storage
39             sub destroy {
40 1     1 1 688 confess "destroy not implemented";
41             }
42              
43             # does nothing in most cases (exception is YAML)
44             sub reset {
45 0     0 0 0 return;
46             }
47              
48             # if subclass overrides to true, flush will not be called after write
49             # and subclass or application must call flush (perhaps in an after hook)
50 8     8 1 33 sub is_lazy { 0 };
51              
52             # This is the default constructor for the session object, the only mandatory
53             # attribute is 'id'. The whole object should be serialized by the session
54             # engine.
55             # If you override this constructor, remember to call $self->SUPER::init() so
56             # that the session ID is still generated.
57             sub init {
58 135     135 1 298 my ($self) = @_;
59 135 50       602 if (!$self->id) {
60 135         376 $self->id(build_id());
61             }
62             }
63              
64             # this method can be overwritten in any Dancer::Session::* module
65             sub session_name {
66 469 100   469 1 1079 setting('session_name') || 'dancer.session';
67             }
68              
69             # May be overriden if session key value pairs aren't stored in the
70             # session object's hash
71             sub get_value {
72 8     8 1 23 my ( $self, $key ) = @_;
73 8         29 return $self->{$key};
74             }
75              
76             # May be overriden if session key value pairs aren't stored in the
77             # session object's hash
78             sub set_value {
79 8     8 1 18 my ( $self, $key, $value ) = @_;
80 8         19 $self->{$key} = $value;
81             }
82              
83              
84             # Methods below this line should not be overloaded.
85              
86             # we try to make the best random number
87             # with native Perl 5 code.
88             # to rebuild a session id, an attacker should know:
89             # - the running PID of the server
90             # - the current timestamp of the time it was built
91             # - the path of the installation directory
92             # - guess the correct number between 0 and 1000000000
93             # - should be able to reproduce that 3 times
94             sub build_id {
95 135     135 1 246 my $session_id = "";
96 135         3005 foreach my $seed (rand(1000), rand(1000), rand(1000)) {
97 405         693 my $c = 0;
98 405         11372 $c += ord($_) for (split //, File::Spec->rel2abs(File::Spec->curdir));
99 405         2282 my $current = int($seed * 1000000000) + time + $$ + $c;
100 405         1002 $session_id .= $current;
101             }
102 135         591 return $session_id;
103             }
104              
105             sub read_session_id {
106 177     177 1 337 my ($class) = @_;
107              
108 177         437 my $name = $class->session_name();
109 177         764 my $c = Dancer::Cookies->cookies->{$name};
110 177 100       461 return unless defined $c;
111 143 50       427 if ($class->validate_session_id($c->value)) {
112 143         328 return $c->value;
113             } else {
114 0         0 warn "Rejecting invalid session ID ". $c->value;
115 0         0 return;
116             }
117             }
118              
119             # Validate session ID (base64 chars plus hyphen by default) to avoid potential
120             # issues, e.g. if the ID is used insecurely elsewhere. If a session provider
121             # expects more unusual IDs, it can override this class method with one that
122             # validates according to that provider's expectation of how a session ID should
123             # look.
124             sub validate_session_id {
125 143     143 0 267 my ($class, $id) = @_;
126 143         700 return $id =~ m{^[A-Za-z0-9_\-~]+$};
127             }
128              
129             sub write_session_id {
130 97     97 1 676 my ($class, $id) = @_;
131              
132 97         213 my $name = $class->session_name();
133              
134             # If we've already pushed the appropriate cookie to the response, then we
135             # don't need to do any more
136 97 100       327 if (my $cookie = Dancer::Cookies->cookie($name)) {
137 63 50       174 if ($cookie eq $id) {
138 63         127 return;
139             }
140             }
141              
142 34 50       138 my %cookie = (
    50          
143             name => $name,
144             value => $id,
145             domain => setting('session_domain'),
146             secure => setting('session_secure'),
147             http_only => defined(setting("session_is_http_only")) ?
148             setting("session_is_http_only") : 1,
149             same_site => defined(setting("session_same_site")) ?
150             setting("session_same_site") : 'None',
151             );
152 34 50       104 if (my $expires = setting('session_expires')) {
153             # It's # of seconds from the current time
154             # Otherwise just feed it through.
155 0 0       0 $expires = Dancer::Cookie::_epoch_to_gmtstring(time + $expires) if $expires =~ /^\d+$/;
156 0         0 $cookie{expires} = $expires;
157             }
158              
159 34         346 my $c = Dancer::Cookie->new(%cookie);
160 34         145 Dancer::Cookies->set_cookie_object($name => $c);
161             }
162              
163             1;
164              
165             __END__