File Coverage

blib/lib/ASP4/SessionStateManager.pm
Criterion Covered Total %
statement 114 120 95.0
branch 6 6 100.0
condition 8 13 61.5
subroutine 24 25 96.0
pod 2 12 16.6
total 154 176 87.5


line stmt bran cond sub pod time code
1              
2             package ASP4::SessionStateManager;
3              
4 9     9   38 use strict;
  9         13  
  9         211  
5 9     9   38 use warnings 'all';
  9         169  
  9         252  
6 9     9   33 use base 'Ima::DBI';
  9         11  
  9         3938  
7 9     9   118972 use HTTP::Date qw( time2iso time2str str2time );
  9         17  
  9         456  
8 9     9   34 use Digest::MD5 'md5_hex';
  9         13  
  9         441  
9 9     9   33 use Storable qw( freeze thaw );
  9         11  
  9         514  
10 9     9   34 use Scalar::Util 'weaken';
  9         11  
  9         330  
11 9     9   33 use ASP4::ConfigLoader;
  9         14  
  9         5837  
12              
13              
14             sub new
15             {
16 3121     3121 0 2818 my ($class, $r) = @_;
17 3121         3955 my $s = bless { }, $class;
18 3121         7346 my $conn = ASP4::ConfigLoader->load->data_connections->session;
19            
20 3121         9031 local $^W = 0;
21 3121         8818 __PACKAGE__->set_db('Main',
22             $conn->dsn,
23             $conn->username,
24             $conn->password
25             );
26            
27 3121         148826 my $id = $s->parse_session_id();
28 3121 100 66     13126 unless( $id && $s->verify_session_id( $id, $conn->session_timeout ) )
29             {
30 11         37 $s->{SessionID} = $s->new_session_id();
31 11         46 $s->write_session_cookie($r);
32 11         60 return $s->create( $s->{SessionID} );
33             }# end unless()
34            
35 3110         8118 return $s->retrieve( $id );
36             }# end new()
37              
38              
39 3121     3121 0 8502 sub context { ASP4::HTTPContext->current }
40              
41              
42             sub parse_session_id
43             {
44 3121     3121 0 6315 my $session_config = ASP4::ConfigLoader->load->data_connections->session;
45 3121         8472 my $cookie_name = $session_config->cookie_name;
46 3121   100     19586 my ($id) = ($ENV{HTTP_COOKIE}||'') =~ m/\b\Q$cookie_name\E\=([a-f0-9]{32,32})/s;
47              
48 3121         5175 return $id;
49             }# end parse_session_id()
50              
51              
52 11     11 0 531 sub new_session_id { md5_hex( rand() . time() . \"" ) }
53              
54              
55             sub write_session_cookie
56             {
57 11     11 0 28 my ($s, $r) = @_;
58            
59 11         59 my $context = ASP4::HTTPContext->current;
60 11         49 my $config = ASP4::ConfigLoader->load->data_connections->session;
61 11         88 my $expires = time2str( time() + ( $config->session_timeout * 60 ) );
62 11   33     258 my $domain = $config->cookie_domain || $ENV{HTTP_HOST};
63 11         54 my $name = $config->cookie_name;
64            
65 11         120 $r->headers_out->{'Set-Cookie'} = "$name=$s->{SessionID}; path=/; domain=$domain; expires=$expires;";
66             }# end write_session_cookie()
67              
68              
69             sub verify_session_id
70             {
71 3110     3110 0 3263 my ($s, $id, $timeout ) = @_;
72            
73 3110         4031 my $range_start = time() - ( $timeout * 60 );
74 3110         6302 local $s->db_Main->{AutoCommit} = 1;
75 3110         695971 my $sth = $s->db_Main->prepare_cached(<<"");
76             SELECT *
77             FROM asp_sessions
78             WHERE session_id = ?
79             AND modified_on BETWEEN ? AND ?
80              
81 3110         117873 $sth->execute( $id, time2iso($range_start), time2iso() );
82 3110         398331 my ($active) = $sth->fetchrow();
83 3110         9017 $sth->finish();
84            
85 3110         25955 return $active;
86             }# end verify_session_id()
87              
88              
89             sub create
90             {
91 11     11 0 32 my ($s, $id) = @_;
92            
93 11         47 local $s->db_Main->{AutoCommit} = 1;
94 11         23892 my $sth = $s->db_Main->prepare_cached(<<"");
95             INSERT INTO asp_sessions (
96             session_id,
97             session_data,
98             created_on,
99             modified_on
100             )
101             VALUES (
102             ?, ?, ?, ?
103             )
104            
105 11         3853 my $now = time2iso();
106            
107 11         825 $s->sign();
108            
109 11         51 my %clone = %$s;
110            
111 11         93 $sth->execute(
112             $id,
113             freeze( \%clone ),
114             $now,
115             $now,
116             );
117 11         343663 $sth->finish();
118            
119 11         105 return $s->retrieve( $id );
120             }# end create()
121              
122              
123             sub retrieve
124             {
125 3121     3121 0 4122 my ($s, $id) = @_;
126              
127 3121         6754 local $s->db_Main->{AutoCommit} = 1;
128 3121         103128 my $sth = $s->db_Main->prepare_cached(<<"");
129             SELECT session_data, modified_on
130             FROM asp_sessions
131             WHERE session_id = ?
132              
133 3121         95739 my $now = time2iso();
134 3121         91668 $sth->execute( $id );
135 3121         196153 my ($data, $modified_on) = $sth->fetchrow;
136 3121   50     8606 $data = thaw($data) || { SessionID => $id };
137 3121         52110 $sth->finish();
138              
139 3121         7372 my $seconds_since_last_modified = time() - str2time($modified_on);
140 3121         292316 my $timeout_seconds = $s->context->config->data_connections->session->session_timeout * 60;
141 3121 100 66     7580 if( $seconds_since_last_modified >= 1 && $seconds_since_last_modified < $timeout_seconds )
142             {
143 14         39 local $s->db_Main->{AutoCommit} = 1;
144 14         522 my $sth = $s->db_Main->prepare_cached(<<"");
145             UPDATE asp_sessions SET
146             modified_on = ?
147             WHERE session_id = ?
148              
149 14         645 $sth->execute( time2iso(), $id );
150 14         202416 $sth->finish();
151             }# end if()
152            
153 3121         14665 undef(%$s);
154 3121         5184 $s = bless $data, ref($s);
155 3121         7192 weaken($s);
156            
157 3121         31625 return $s;
158             }# end retrieve()
159              
160              
161             sub save
162             {
163 9356     9356 1 8634 my ($s) = @_;
164            
165 9     9   42 no warnings 'uninitialized';
  9         15  
  9         1352  
166 9356 100       11477 return unless $s->is_changed;
167 3132         4726 $s->sign;
168            
169 3132         6902 local $s->db_Main->{AutoCommit} = 1;
170 3132         110198 my $sth = $s->db_Main->prepare_cached(<<"");
171             UPDATE asp_sessions SET
172             session_data = ?,
173             modified_on = ?
174             WHERE session_id = ?
175            
176 3132         99028 my %clone = %$s;
177 3132         8620 my $data = freeze( \%clone );
178 3132         88466 $sth->execute( $data, time2iso(), $s->{SessionID} );
179 3132         448328 $sth->finish();
180            
181 3132         16030 1;
182             }# end save()
183              
184              
185             sub sign
186             {
187 3143     3143 0 2417 my $s = shift;
188            
189 3143         2860 $s->{__signature} = $s->_hash;
190             }# end sign()
191              
192              
193             sub _hash
194             {
195 12499     12499   8378 my $s = shift;
196            
197 9     9   38 no warnings 'uninitialized';
  9         12  
  9         826  
198             md5_hex(
199             join ":",
200 12503         51226 map { "$_:$s->{$_}" }
201 12499         49357 grep { $_ ne '__signature' } sort keys(%$s)
  18749         21757  
202             );
203             }# end _hash()
204              
205              
206             sub is_changed
207             {
208 9356     9356 0 6761 my $s = shift;
209            
210 9     9   81 no warnings 'uninitialized';
  9         16  
  9         1011  
211 9356         10397 $s->_hash ne $s->{__signature};
212             }# end is_changed()
213              
214              
215             sub reset
216             {
217 0     0 1 0 my $s = shift;
218            
219 0         0 map { delete($s->{$_}) } grep { $_ ne 'SessionID' } keys %$s;
  0         0  
  0         0  
220 0         0 $s->save;
221 0         0 return;
222             }# end reset()
223              
224              
225             sub DESTROY
226             {
227 8225     8225   6922 my $s = shift;
228 8225         12761 $s->save;
229 8225         26993 undef(%$s);
230             }# end DESTROY()
231              
232             1;# return true:
233              
234             =pod
235              
236             =head1 NAME
237              
238             ASP4::SessionStateManager - Per-user state persistence
239              
240             =head1 SYNOPSIS
241              
242             You've seen this page <%= $Session->{counter}++ %> times before.
243              
244             =head1 DESCRIPTION
245              
246             Web applications require session state management - and the simpler, the better.
247              
248             C is a simple blessed hash. When it goes out of scope,
249             it is saved to the database (or whatever).
250              
251             If no changes were made to the session, it is not saved.
252              
253             =head1 PUBLIC METHODS
254              
255             =head2 save( )
256              
257             Causes the session data to be saved.
258              
259             =head2 reset( )
260              
261             Causes the session data to be emptied.
262              
263             =cut
264