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.3520';
5 94     94   1844 use strict;
  94         270  
  94         2759  
6 94     94   555 use warnings;
  94         314  
  94         2348  
7 94     94   567 use Carp;
  94         254  
  94         6053  
8              
9 94     94   741 use base 'Dancer::Engine';
  94         266  
  94         11807  
10              
11 94     94   740 use Dancer::Config 'setting';
  94         344  
  94         4881  
12 94     94   711 use Dancer::Cookies;
  94         274  
  94         3160  
13 94     94   624 use File::Spec;
  94         362  
  94         83007  
14              
15             __PACKAGE__->attributes('id');
16              
17             # args: ($class, $id)
18             # receives a session id and should return a session object if found, or undef
19             # otherwise.
20             sub retrieve {
21 1     1 1 1231 confess "retrieve not implemented";
22             }
23              
24             # args: ($class)
25             # create a new empty session, flush it and return it.
26             sub create {
27 1     1 1 738 confess "create not implemented";
28             }
29              
30             # args: ($self)
31             # write the (serialized) current session to the session storage
32             sub flush {
33 1     1 1 691 confess "flush not implemented";
34             }
35              
36             # args: ($self)
37             # remove the session from the session storage
38             sub destroy {
39 1     1 1 675 confess "destroy not implemented";
40             }
41              
42             # does nothing in most cases (exception is YAML)
43             sub reset {
44 0     0 0 0 return;
45             }
46              
47             # if subclass overrides to true, flush will not be called after write
48             # and subclass or application must call flush (perhaps in an after hook)
49 8     8 1 39 sub is_lazy { 0 };
50              
51             # This is the default constructor for the session object, the only mandatory
52             # attribute is 'id'. The whole object should be serialized by the session
53             # engine.
54             # If you override this constructor, remember to call $self->SUPER::init() so
55             # that the session ID is still generated.
56             sub init {
57 134     134 1 380 my ($self) = @_;
58 134 50       730 if (!$self->id) {
59 134         496 $self->id(build_id());
60             }
61             }
62              
63             # this method can be overwritten in any Dancer::Session::* module
64             sub session_name {
65 469 100   469 1 1293 setting('session_name') || 'dancer.session';
66             }
67              
68             # May be overriden if session key value pairs aren't stored in the
69             # session object's hash
70             sub get_value {
71 8     8 1 22 my ( $self, $key ) = @_;
72 8         48 return $self->{$key};
73             }
74              
75             # May be overriden if session key value pairs aren't stored in the
76             # session object's hash
77             sub set_value {
78 8     8 1 27 my ( $self, $key, $value ) = @_;
79 8         25 $self->{$key} = $value;
80             }
81              
82              
83             # Methods below this line should not be overloaded.
84              
85             # we try to make the best random number
86             # with native Perl 5 code.
87             # to rebuild a session id, an attacker should know:
88             # - the running PID of the server
89             # - the current timestamp of the time it was built
90             # - the path of the installation directory
91             # - guess the correct number between 0 and 1000000000
92             # - should be able to reproduce that 3 times
93             sub build_id {
94 134     134 1 309 my $session_id = "";
95 134         3889 foreach my $seed (rand(1000), rand(1000), rand(1000)) {
96 402         826 my $c = 0;
97 402         13833 $c += ord($_) for (split //, File::Spec->rel2abs(File::Spec->curdir));
98 402         2944 my $current = int($seed * 1000000000) + time + $$ + $c;
99 402         1232 $session_id .= $current;
100             }
101 134         766 return $session_id;
102             }
103              
104             sub read_session_id {
105 177     177 1 417 my ($class) = @_;
106              
107 177         523 my $name = $class->session_name();
108 177         806 my $c = Dancer::Cookies->cookies->{$name};
109 177 100       537 return unless defined $c;
110 143 50       464 if ($class->validate_session_id($c->value)) {
111 143         393 return $c->value;
112             } else {
113 0         0 warn "Rejecting invalid session ID ". $c->value;
114 0         0 return;
115             }
116             }
117              
118             # Validate session ID (base64 chars plus hyphen by default) to avoid potential
119             # issues, e.g. if the ID is used insecurely elsewhere. If a session provider
120             # expects more unusual IDs, it can override this class method with one that
121             # validates according to that provider's expectation of how a session ID should
122             # look.
123             sub validate_session_id {
124 143     143 0 325 my ($class, $id) = @_;
125 143         790 return $id =~ m{^[A-Za-z0-9_\-~]+$};
126             }
127              
128             sub write_session_id {
129 97     97 1 1012 my ($class, $id) = @_;
130              
131 97         237 my $name = $class->session_name();
132              
133             # If we've already pushed the appropriate cookie to the response, then we
134             # don't need to do any more
135 97 100       429 if (my $cookie = Dancer::Cookies->cookie($name)) {
136 63 50       207 if ($cookie eq $id) {
137 63         145 return;
138             }
139             }
140              
141 34 50       158 my %cookie = (
    50          
142             name => $name,
143             value => $id,
144             domain => setting('session_domain'),
145             secure => setting('session_secure'),
146             http_only => defined(setting("session_is_http_only")) ?
147             setting("session_is_http_only") : 1,
148             same_site => defined(setting("session_same_site")) ?
149             setting("session_same_site") : 'None',
150             );
151 34 50       143 if (my $expires = setting('session_expires')) {
152             # It's # of seconds from the current time
153             # Otherwise just feed it through.
154 0 0       0 $expires = Dancer::Cookie::_epoch_to_gmtstring(time + $expires) if $expires =~ /^\d+$/;
155 0         0 $cookie{expires} = $expires;
156             }
157              
158 34         463 my $c = Dancer::Cookie->new(%cookie);
159 34         224 Dancer::Cookies->set_cookie_object($name => $c);
160             }
161              
162             1;
163              
164             __END__