line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package MVC::Neaf::X::Session; |
2
|
|
|
|
|
|
|
|
3
|
10
|
|
|
10
|
|
75907
|
use strict; |
|
10
|
|
|
|
|
30
|
|
|
10
|
|
|
|
|
318
|
|
4
|
10
|
|
|
10
|
|
55
|
use warnings; |
|
10
|
|
|
|
|
18
|
|
|
10
|
|
|
|
|
493
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.29'; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
MVC::Neaf::X::Session - Session engine base class for Not Even A Framework |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 DESCRIPTION |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
A framework, even a toy one, is incomplete until it can handle user sessions. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
This class offers managing sessions via a cookie ("session" by default) |
16
|
|
|
|
|
|
|
plus a user-defined backend storage mechanism. |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
Whatever is stored in the session, stays in the session - until it's deleted. |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Within the application, session is available through Request methods |
21
|
|
|
|
|
|
|
session(), save_session(), and delete_session(). |
22
|
|
|
|
|
|
|
During the setup phase, MVC::Neaf->set_session_handler( $engine ) |
23
|
|
|
|
|
|
|
must be called in order to make use of those. |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
This class is base class for such $engine. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
To actually manage sessions, it MUST be subclassed with methods |
28
|
|
|
|
|
|
|
save_session() and load_session() implemented. |
29
|
|
|
|
|
|
|
For a working implementation, please see L. |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
This module's interface is still under development and details MAY |
32
|
|
|
|
|
|
|
change in the future. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 SINOPSYS |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
use MVC::Neaf; |
37
|
|
|
|
|
|
|
use MVC::Neaf::X::Session; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# somewhere in the beginning |
40
|
|
|
|
|
|
|
{ |
41
|
|
|
|
|
|
|
package My::Session; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub save_session { |
44
|
|
|
|
|
|
|
my ($self, $id, $data) = @_; |
45
|
|
|
|
|
|
|
$self->{data}{ $id } = $data; |
46
|
|
|
|
|
|
|
return { id => $id }; |
47
|
|
|
|
|
|
|
}; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub load_session { |
50
|
|
|
|
|
|
|
my ($self, $id) = @_; |
51
|
|
|
|
|
|
|
return { data => $self->{data}{ $id } }; |
52
|
|
|
|
|
|
|
}; |
53
|
|
|
|
|
|
|
}; |
54
|
|
|
|
|
|
|
MVC::Neaf->set_session_handler( My::Session->new ); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# somewhere in the controller |
57
|
|
|
|
|
|
|
sub { |
58
|
|
|
|
|
|
|
my $req = shift; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
$req->session; # {} 1st time, { user => ... } later on |
61
|
|
|
|
|
|
|
$req->session->{user} = $user; |
62
|
|
|
|
|
|
|
$req->save_session; |
63
|
|
|
|
|
|
|
}; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
This of course is only going to work as a standalone application server |
66
|
|
|
|
|
|
|
(plackup, twiggy...), but not CGI or Apache/mod_perl. |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head1 METHODS |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=cut |
71
|
|
|
|
|
|
|
|
72
|
10
|
|
|
10
|
|
64
|
use Digest::MD5; |
|
10
|
|
|
|
|
22
|
|
|
10
|
|
|
|
|
361
|
|
73
|
10
|
|
|
10
|
|
2184
|
use Time::HiRes qw(gettimeofday); |
|
10
|
|
|
|
|
5775
|
|
|
10
|
|
|
|
|
71
|
|
74
|
10
|
|
|
10
|
|
4021
|
use Sys::Hostname qw(hostname); |
|
10
|
|
|
|
|
4364
|
|
|
10
|
|
|
|
|
650
|
|
75
|
10
|
|
|
10
|
|
516
|
use MVC::Neaf::Util qw(encode_b64); |
|
10
|
|
|
|
|
33
|
|
|
10
|
|
|
|
|
469
|
|
76
|
|
|
|
|
|
|
|
77
|
10
|
|
|
10
|
|
66
|
use parent qw(MVC::Neaf::X); |
|
10
|
|
|
|
|
34
|
|
|
10
|
|
|
|
|
80
|
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head2 new( %options ) |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
%options may include |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=over |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=item * session_ttl, expire - the lifetime of session. |
86
|
|
|
|
|
|
|
Default is 24 hours. |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=back |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=cut |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub new { |
93
|
11
|
|
|
11
|
1
|
290
|
my ($class, %opt) = @_; |
94
|
|
|
|
|
|
|
|
95
|
11
|
|
50
|
|
|
103
|
$opt{session_ttl} ||= delete $opt{expire} || 24*60*60; |
|
|
|
66
|
|
|
|
|
96
|
|
|
|
|
|
|
|
97
|
11
|
|
|
|
|
101
|
$class->SUPER::new( %opt ); |
98
|
|
|
|
|
|
|
}; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=head2 session_id_regex() |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
This is supposed to be a constant regular expression |
103
|
|
|
|
|
|
|
compatible with whatever get_session_id generates. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
If none given, a sane default is supplied. |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=cut |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub session_id_regex { |
110
|
6
|
|
|
6
|
1
|
38
|
return qr([A-Za-z_\d\.\/\?\-\@+=~]+); |
111
|
|
|
|
|
|
|
}; |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head2 get_session_id( [$user_salt] ) |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Generate a new, shiny, unique, unpredictable session id. |
116
|
|
|
|
|
|
|
Id is base64-encoded. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
The default is using two rounds of md5 with time, process id, hostname, |
119
|
|
|
|
|
|
|
and random salt. Should be unique and reasonably hard to guess. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
If argument is given, it's also added to the mix. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Set $MVC::Neaf::X::Session::Hash to other function (e.g. Digest::SHA::sha224) |
124
|
|
|
|
|
|
|
if md5 is not secure enough. |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Set $MVC::Neaf::X::Session::Host to something unique if you know better. |
127
|
|
|
|
|
|
|
Default is hostname. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
Set $MVC::Neaf::X::Session::Truncate to the desired length |
130
|
|
|
|
|
|
|
(e.g. if length constraint in database). |
131
|
|
|
|
|
|
|
Default (0) means return however many chars are generated by hash+base64. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=cut |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# Premature optimisation at its best. |
136
|
|
|
|
|
|
|
# Should be more or less secure and unique though. |
137
|
|
|
|
|
|
|
my $max = 2*1024*1024*1024; |
138
|
|
|
|
|
|
|
my $count = 0; |
139
|
|
|
|
|
|
|
my $old_rand = 0; |
140
|
|
|
|
|
|
|
my $old_mix = ''; |
141
|
|
|
|
|
|
|
our $Host = hostname() || ''; |
142
|
|
|
|
|
|
|
our $Hash = \&Digest::MD5::md5; |
143
|
|
|
|
|
|
|
our $Truncate; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub get_session_id { |
146
|
1013
|
|
|
1013
|
1
|
3366
|
my ($self, $salt) = @_; |
147
|
|
|
|
|
|
|
|
148
|
1013
|
100
|
|
|
|
1853
|
$count = $max |
149
|
|
|
|
|
|
|
unless $count--; |
150
|
1013
|
|
|
|
|
2123
|
my $rand = int ( rand() * $max ); |
151
|
1013
|
|
|
|
|
2053
|
my ($time, $ms) = gettimeofday(); |
152
|
1013
|
50
|
|
|
|
1859
|
$salt = '' unless defined $salt; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
# using old entropy means attacker will have to guess ALL previous sessions |
155
|
1013
|
|
|
|
|
4287
|
$old_mix = $Hash->(pack "La*a*a*a*LLLLa*L" |
156
|
|
|
|
|
|
|
, $rand, $old_mix, "#" |
157
|
|
|
|
|
|
|
, $Host, '#', $$, $time, $ms, $count |
158
|
|
|
|
|
|
|
, $salt, $old_rand); |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# salt before second round of hashing |
161
|
|
|
|
|
|
|
# public data (session_id) should NOT be used for generation |
162
|
1013
|
|
|
|
|
1818
|
$old_rand = int (rand() * $max ); |
163
|
1013
|
|
|
|
|
3143
|
my $ret = encode_b64( $Hash->( pack "a*L", $old_mix, $old_rand ) ); |
164
|
1013
|
|
|
|
|
10339
|
$ret =~ s/[\s=]+//gs; |
165
|
1013
|
50
|
33
|
|
|
1915
|
$ret = substr( $ret, 0, $Truncate ) |
166
|
|
|
|
|
|
|
if $Truncate and $Truncate < length $ret; |
167
|
1013
|
|
|
|
|
3232
|
return $ret; |
168
|
|
|
|
|
|
|
}; |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# finally, bootstrap the session generator at startap |
171
|
|
|
|
|
|
|
get_session_id(); |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=head2 session_ttl() |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
Return session ttl. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=cut |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
sub session_ttl { |
180
|
19
|
|
|
19
|
1
|
36
|
my $self = shift; |
181
|
19
|
|
|
|
|
98
|
return $self->{session_ttl}; |
182
|
|
|
|
|
|
|
}; |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head2 create_session() |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Create a new session. The default is to return an empty hash. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=cut |
189
|
|
|
|
|
|
|
|
190
|
4
|
|
|
4
|
1
|
19
|
sub create_session { return {} }; |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head2 save_session( $id, $data ) |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Save session data in the storage. |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
This method MUST be implemented in specific session driver class. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
It MUST return a hashref with the following fields: |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=over |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=item * id - the id of session (either supplied, or a new one). |
203
|
|
|
|
|
|
|
If this value is absent or false, saving is considered unsuccessful. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=item * expire - the expiration time of the session as Unix time. |
206
|
|
|
|
|
|
|
This is optional. |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=back |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=cut |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head2 load_session( $id ) |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
Return session data from the storage. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
This MUST be implemented in specific session driver class. |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
It MUST return either false, or a hashref with the following fields: |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=over |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=item * data - the session data that was passed to corresponding save_session() |
223
|
|
|
|
|
|
|
call. If absent or false, loading is considered unsuccessful. |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
=item * id - if present, this means that session has to be refreshed. |
226
|
|
|
|
|
|
|
The session cookie will be sent again to the user. |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=item * expire - if id present, this would set new session expiration date. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=back |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=cut |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head2 delete_session( $id ) |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Remove session from storage. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
The default is do nothing and wait for session data to rot by itself. |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
B It is usually a good idea to cleanup session storage |
241
|
|
|
|
|
|
|
from time to time since some users may go away without logging out |
242
|
|
|
|
|
|
|
(cleaned cookies, laptop eaten by crocodiles etc). |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=cut |
245
|
|
|
|
|
|
|
|
246
|
1
|
|
|
1
|
1
|
4
|
sub delete_session { return }; |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
This module is part of L suite. |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
Copyright 2016-2023 Konstantin S. Uvarin C. |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
255
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
256
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
See L for more information. |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=cut |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
1; |