File Coverage

blib/lib/Plack/Middleware/Session/SerializedCookie.pm
Criterion Covered Total %
statement 44 47 93.6
branch 7 12 58.3
condition 7 18 38.8
subroutine 11 11 100.0
pod 2 2 100.0
total 71 90 78.8


line stmt bran cond sub pod time code
1             package Plack::Middleware::Session::SerializedCookie;
2 4     4   83954 use strict;
  4         11  
  4         176  
3 4     4   22 use warnings;
  4         7  
  4         117  
4            
5 4     4   115 use 5.008;
  4         14  
  4         161  
6            
7 4     4   3947 use parent 'Plack::Middleware';
  4         1390  
  4         21  
8 4     4   124285 use Plack::Request;
  4         374018  
  4         143  
9 4     4   4199 use Plack::Response;
  4         6023  
  4         123  
10 4     4   27 use Carp;
  4         9  
  4         2861  
11            
12             our $VERSION = 1.03;
13            
14             sub prepare_app {
15 6     6 1 102357 my $self = shift;
16            
17 6 50       44 $self->{session_key} = 'plack_session' if ! defined $self->{session_key};
18            
19 6         19 for my $fname ( qw( serialize deserialize ) ) {
20 12   33 24   153 $self->{$fname} ||= $self->{serializer} && $self->{serializer}->can($fname) && sub { $self->{serializer}->$fname(@_) } || croak __PACKAGE__.": No '$fname' installed!!"
  24   66     103  
21             }
22            
23 6         17 $self->{cookie_options} = +{ map { $_ => delete $self->{$_} } grep { exists $self->{$_} } qw(path domain secure) };
  0         0  
  18         50  
24             }
25            
26             sub call {
27 24     24 1 11596 my($self, $env) = @_;
28            
29 24         111 my $cookie = Plack::Request->new($env)->cookies->{$self->{session_key}};
30 24 100       1225 $env->{'psgix.session'} = eval { $self->{deserialize}->( defined($cookie) ? $cookie : undef ) };
  24         93  
31 24 50 33     879 $self->{deserialize_exception}->($@) if $@ && $self->{deserialize_exception};
32            
33 24         87 my $res = $self->app->($env);
34            
35             $self->response_cb( $res, sub {
36 24     24   326 my $res = shift;
37 24         100 my $response = Plack::Response->new(@$res);
38 24 50       2023 $self->{cookie_options}->{expires} = time + $self->{expires} if exists $self->{expires};
39 24         96 $response->cookies->{$self->{session_key}} = $self->{cookie_options};
40 24 50 33     299 if( !defined($env->{'psgix.session'}) || $env->{'psgix.session.option'} && $env->{'psgix.session.option'}{expire} ) {
      33        
41 0         0 local $self->{cookie_options}{expires} = 1;
42 0         0 $res->[1] = $response->finalize->[1];
43             }
44             else {
45 24         36 eval {
46 24         82 local $self->{cookie_options}{value} = $self->{serialize}->($env->{'psgix.session'});
47 24         1262 $res->[1] = $response->finalize->[1];
48             };
49 24 50 33     10403 $self->{serialize_exception}->($@) if $@ && $self->{serialize_exception};
50             }
51 24         412 } );
52             }
53            
54             1;
55            
56             =head1 NAME
57            
58             Plack::Middleware::Session::SerializedCookie -
59             Session middleware that saves session data in
60             the customizable serialized cookie
61            
62             =head1 SYNOPSIS
63            
64             # With serialize/deserialize subs
65            
66             enable "Session::SerializedCookie",
67             serialize => sub {
68             my $session = shift;
69             ...
70             return $serialized_session
71             },
72             deserialize => sub {
73             my $serialized_session = shift;
74             ...
75             return $session };
76            
77            
78             # With Serializer (object that
79             # implements 'serialize' and 'deserialize')
80            
81             enable "Session::SerializedCookie",
82             serializer => Data::Serializer->new(...);
83            
84            
85             # Mixed case
86            
87             my $serializer = Data::Serializer->new(...);
88             enable "Session::SerializedCookie",
89             serializer => $serializer,
90             deserialize => sub {
91             my $session = eval { $serializer->deserialize(@_) };
92             $session = { ... (initial session) ... } if $@;
93             return $session;
94             };
95             # The missing sub 'serialize' will fall back
96             # to use the serializer's one.
97            
98            
99             # Additional exception handler
100            
101             enable "Session::SerializedCookie",
102             serializer => Data::Serializer->new(...),
103             serialize_exception => sub { my $error_msg = shift; ... },
104             deserialize_exception => sub { my $error_msg = shift; ... };
105            
106            
107             # In the app
108            
109             sub {
110             my $env = shift;
111            
112             # Retrieve the session by $env->{'psgix.session'}
113             # If the session is not presented, or something goes wrong when retrieving,
114             # it'll be set to {}, an empty hash reference.
115             my $session = $env->{'psgix.session'};
116             ...
117            
118             $session->{blah} = 'blah blah';
119            
120             # At the end of the app, $env->{'psgix.session'} will be stored
121             # to the cookie automatically.
122             # To expire the session, undef the $env->{'psgix.session'} or
123             # set $env->{'psgix.session.option'}{expire} = 1
124             };
125            
126            
127             # For full and more examples, take a look on eg/ directory.
128            
129             =head1 DESCRIPTION
130            
131             This middleware component works like L,
132             that it provide a simple way to retrieve and store session data
133             via $env->{'psgix.session'}. It store the session data in the cookie
134             like what L do, that doesn't
135             need to store any data in the server side.
136            
137             In addition, this module provide a convenient way to customize
138             the way to serialize / deserialize the session data.
139            
140             It is sometimes important to customize the serializer because of
141             various application. Someone might need to store critical data in
142             the session. Someone might need to store large data there.
143             Some one might need just a simplest and fastest one, and
144             don't care if the user can arbitrarily modify the session data.
145            
146             =head1 CONFIGURATIONS
147            
148             =over 4
149            
150             =item session_key
151            
152             This is used as the cookie name
153            
154             =item path, domain, secure
155            
156             These are used as the cookie params.
157             See L for these options.
158            
159             =item expires
160            
161             This one is used as an advance time from the request time.
162            
163             =item serialize
164            
165             Set this attribute to a sub reference. It will be called
166             when serializing. The only argument is the session data,
167             $env->{'psgix.session'} in fact. And this sub should return
168             the serialized one.
169            
170             If this attribute is not presented. This module will try
171             to use the serializer's member sub 'serialize'.
172            
173             =item deserialize
174            
175             Set this attribute to a sub reference. It is the inverse of
176             serialize. It will get the serialized session as the only
177             argument, and should return the session data.
178            
179             Like 'serialize', if this attribute is missed, the module
180             will try to use serializer's one.
181            
182             =item serializer
183            
184             Set this attribute to an object. When 'serialize' or 'deserialize'
185             is missed, the module will use this object's.
186            
187             =item serialize_exception, deserialize_exception
188            
189             If there is anything wrong when serializing or deserializing,
190             the coresponding sub will be called with the error message ($@)
191             as the only argument.
192            
193             Note that exception is expensive. You should avoid exception
194             when possible, if the efficiency is important at your application.
195            
196             =back
197            
198             =head1 CONSIDERATION
199            
200             When customizing your own serializing method, there are some issues
201             that you might need to consider.
202            
203             =over 4
204            
205             =item serializer
206            
207             This is the primary part of serialization, to transform a bunch of data
208             into a string, and to transform a string back to the original data.
209            
210             There are several well-known serialization method, such as L,
211             L, L, L, L, etc. Each of them
212             has different benefits and different limits. You should read their documents
213             for more information.
214            
215             My favorite one is L. It will try to use L when availible.
216             This one is both efficient and simple. Though you can only store opaque
217             data structure with array reference, hash reference, string, and number
218             data types. You can't store other types such as code reference, blessed object,
219             tied data, nor references that refer to the same variable or cyclic references (L
220             will extract them independently and completely).
221            
222             =item base64
223            
224             It's not allowed to use all the octet codes as the cookie value.
225             It's a safer way to encode your serialized string into base64 form.
226             You may take a look on L.
227            
228             Note, you should use this as the final filter, or it will be useless.
229            
230             =item encryption
231            
232             Sometimes, you may want to store critical data in the session,
233             that you don't want the user to know what you have store there.
234             You may consider to encrypt the session.
235             My favorite one is AES (L).
236            
237             Besides, to encrypt session can avoid the user to change the session.
238             If you just want to prevent the user to change the session, but don't
239             care if the user could read its content, it's not neccessary to encrypt it.
240             You can use signature instead. See the section L.
241            
242             =item compression
243            
244             The browser will not allow to store very large cookie data.
245             You may need to compress it. Though, to think of storing smaller data in session
246             might be a much better approach.
247            
248             You may use L to achieve this.
249            
250             =item additional data
251            
252             Besides to filter the whole session, you may want to just put additional data,
253             that could help you to do some verification.
254            
255             You can store the additional data
256             by concating them with the serialized string (if they are string form already),
257            
258             $serialized_session = join ',', $serialized_session, $addition1, $addition2
259            
260             or more generally injecting into the session data structure before serialization.
261            
262             $session = [$session, $addition1, $addition2]
263            
264             =over 4
265            
266             =item signature
267            
268             Use a one-way hash function to generate the signature from the serialized session
269             string and a secret string. The users cannot generate the signatures by themselves
270             without knowing about the secret string. So they can't generate arbitrary session
271             data with correct signatures.
272            
273             There are many one-way hash functions availible, such as SHA1, SHA2, MD5, MD6, etc.
274            
275             It's no need to use signature, if you've encrypted the session.
276            
277             =item timestamp
278            
279             With timestamp, you can expire old session strictly. That is, even the browser
280             never expires the cookie, you can still expire it by rejecting old timestamp.
281            
282             =item ip address
283            
284             This is a more aggresive way to prevent the unwelcome bad guy from
285             stealing the session.
286            
287             Use this feature cautiously. It will block users who change their ip address rapidly.
288             On the other hand, it's useless if the bad guy is using the same ip address as the
289             user, 'cause they are at the same intranet, or the bad guy is the spyware on the
290             same computer.
291            
292             =back
293            
294             =back
295            
296             =head1 AUTHOR
297            
298             Cindy Wang (CindyLinz)
299            
300             =head1 SEE ALSO
301            
302             L
303             L
304            
305             =cut