Pod coverage for C::P::Session::Store::Dummy
[catagits/Catalyst-Plugin-Session.git] / lib / Catalyst / Plugin / Session.pm
CommitLineData
9e447f9d 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Session;
4use base qw/Class::Accessor::Fast/;
5
6use strict;
7use warnings;
8
9use NEXT;
10use Catalyst::Exception ();
9a9252c2 11use Digest ();
12use overload ();
9e447f9d 13
37160715 14our $VERSION = "0.01";
15
9e447f9d 16BEGIN {
9a9252c2 17 __PACKAGE__->mk_accessors(qw/sessionid session_delete_reason/);
9e447f9d 18}
19
20sub setup {
9a9252c2 21 my $c = shift;
22
23 $c->NEXT::setup(@_);
24
25 $c->check_session_plugin_requirements;
26 $c->setup_session;
27
28 return $c;
9e447f9d 29}
30
31sub check_session_plugin_requirements {
9a9252c2 32 my $c = shift;
9e447f9d 33
9a9252c2 34 unless ( $c->isa("Catalyst::Plugin::Session::State")
35 && $c->isa("Catalyst::Plugin::Session::Store") )
36 {
37 my $err =
38 ( "The Session plugin requires both Session::State "
39 . "and Session::Store plugins to be used as well." );
9e447f9d 40
9a9252c2 41 $c->log->fatal($err);
42 Catalyst::Exception->throw($err);
43 }
9e447f9d 44}
45
46sub setup_session {
9a9252c2 47 my $c = shift;
9e447f9d 48
9a9252c2 49 my $cfg = ( $c->config->{session} ||= {} );
9e447f9d 50
9a9252c2 51 %$cfg = (
52 expires => 7200,
53 verify_address => 1,
54 %$cfg,
55 );
9e447f9d 56
9a9252c2 57 $c->NEXT::setup_session();
9e447f9d 58}
59
60sub finalize {
9a9252c2 61 my $c = shift;
9e447f9d 62
9a9252c2 63 if ( $c->{session} ) {
9e447f9d 64
9a9252c2 65 # all sessions are extended at the end of the request
66 my $now = time;
67 @{ $c->{session} }{qw/__updated __expires/} =
68 ( $now, $c->config->{session}{expires} + $now );
69 $c->store_session_data( $c->sessionid, $c->{session} );
70 }
71
72 $c->NEXT::finalize(@_);
9e447f9d 73}
74
75sub prepare_action {
76 my $c = shift;
77
3f182468 78 if ( my $sid = $c->sessionid ) {
3f182468 79 my $s = $c->{session} ||= $c->get_session_data($sid);
80 if ( !$s or $s->{__expires} < time ) {
81
82 # session expired
83 $c->log->debug("Deleting session $sid (expired)") if $c->debug;
84 $c->delete_session("session expired");
85 }
29543a62 86 elsif ($c->config->{session}{verify_address}
3f182468 87 && $c->{session}{__address}
88 && $c->{session}{__address} ne $c->request->address )
89 {
90 $c->log->warn(
91 "Deleting session $sid due to address mismatch ("
92 . $c->{session}{__address} . " != "
93 . $c->request->address . ")",
94 );
95 $c->delete_session("address mismatch");
96 }
29543a62 97 else {
98 $c->log->debug(qq/Restored session "$sid"/) if $c->debug;
99 }
9a9252c2 100 }
101
3f182468 102 $c->NEXT::prepare_action(@_);
9e447f9d 103}
104
105sub delete_session {
9a9252c2 106 my ( $c, $msg ) = @_;
9e447f9d 107
9a9252c2 108 # delete the session data
109 my $sid = $c->sessionid;
110 $c->delete_session_data($sid);
9e447f9d 111
9a9252c2 112 # reset the values in the context object
113 $c->{session} = undef;
114 $c->sessionid(undef);
115 $c->session_delete_reason($msg);
9e447f9d 116}
117
118sub session {
9a9252c2 119 my $c = shift;
9e447f9d 120
121 return $c->{session} if $c->{session};
122
9a9252c2 123 my $sid = $c->generate_session_id;
124 $c->sessionid($sid);
9e447f9d 125
9a9252c2 126 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
9e447f9d 127
9a9252c2 128 return $c->initialize_session_data;
9e447f9d 129}
130
131sub initialize_session_data {
9a9252c2 132 my $c = shift;
9e447f9d 133
9a9252c2 134 my $now = time;
9e447f9d 135
9a9252c2 136 return $c->{session} = {
137 __created => $now,
138 __updated => $now,
139 __expires => $now + $c->config->{session}{expires},
9e447f9d 140
9a9252c2 141 (
142 $c->config->{session}{verify_address}
143 ? ( __address => $c->request->address )
144 : ()
145 ),
146 };
9e447f9d 147}
148
9e447f9d 149sub generate_session_id {
150 my $c = shift;
151
152 my $digest = $c->_find_digest();
153 $digest->add( $c->session_hash_seed() );
154 return $digest->hexdigest;
155}
156
157my $counter;
9a9252c2 158
9e447f9d 159sub session_hash_seed {
9a9252c2 160 my $c = shift;
161
162 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
9e447f9d 163}
164
165my $usable;
9a9252c2 166
9e447f9d 167sub _find_digest () {
9a9252c2 168 unless ($usable) {
7d139eeb 169 foreach my $alg (qw/SHA-1 MD5 SHA-256/) {
170 eval {
29543a62 171 my $obj = Digest->new($alg);
172 $usable = $alg;
173 return $obj;
174 };
7d139eeb 175 }
176 $usable
9a9252c2 177 or Catalyst::Exception->throw(
178 "Could not find a suitable Digest module. Please install "
179 . "Digest::SHA1, Digest::SHA, or Digest::MD5" );
180 }
9e447f9d 181
182 return Digest->new($usable);
183}
184
99b2191e 185sub dump_these {
186 my $c = shift;
187
188 (
189 $c->NEXT::dump_these(),
190
191 $c->sessionid
192 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
193 : ()
194 );
195}
196
9e447f9d 197__PACKAGE__;
198
199__END__
200
201=pod
202
203=head1 NAME
204
205Catalyst::Plugin::Session - Generic Session plugin - ties together server side
206storage and client side tickets required to maintain session data.
207
208=head1 SYNOPSIS
209
210 use Catalyst qw/Session Session::Store::FastMmap Session::State::Cookie/;
211
229a5b53 212 sub add_item : Local {
213 my ( $self, $c ) = @_;
214
215 my $item_id = $c->req->param("item");
216
217 # $c->session is stored across requests, so
218 # other actions will see these values
219
220 push @{ $c->session->{items} }, $item_id;
221
222 $c->forward("MyView");
223 }
224
225 sub display_items : Local {
226 my ( $self, $c ) = @_;
227
228 # values in $c->session are restored
229 $c->stash->{items_to_display} =
230 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
231
232 $c->forward("MyView");
233 }
234
9e447f9d 235=head1 DESCRIPTION
236
237The Session plugin is the base of two related parts of functionality required
238for session management in web applications.
239
240The first part, the State, is getting the browser to repeat back a session key,
241so that the web application can identify the client and logically string
242several requests together into a session.
243
244The second part, the Store, deals with the actual storage of information about
245the client. This data is stored so that the it may be revived for every request
246made by the same client.
247
248This plugin links the two pieces together.
249
250=head1 METHODS
251
252=over 4
253
254=item sessionid
255
256An accessor for the session ID value.
257
258=item session
259
260Returns a hash reference that might contain unserialized values from previous
261requests in the same session, and whose modified value will be saved for future
262requests.
263
264This method will automatically create a new session and session ID if none
265exists.
266
267=item session_delete_reason
268
269This accessor contains a string with the reason a session was deleted. Possible
270values include:
271
272=over 4
273
274=item *
275
276C<address mismatch>
277
278=item *
279
280C<session expired>
281
282=back
283
284=item setup
285
286This method is extended to also make calls to
287C<check_session_plugin_requirements> and C<setup_session>.
288
289=item check_session_plugin_requirements
290
291This method ensures that a State and a Store plugin are also in use by the
292application.
293
294=item setup_session
295
296This method populates C<< $c->config->{session} >> with the default values
297listed in L</CONFIGURATION>.
298
299=item prepare_action
300
301This methoid is extended, and will restore session data and check it for
302validity if a session id is defined. It assumes that the State plugin will
303populate the C<sessionid> key beforehand.
304
305=item finalize
306
307This method is extended and will extend the expiry time, as well as persist the
308session data if a session exists.
309
310=item delete_session REASON
311
312This method is used to invalidate a session. It takes an optional parameter
313which will be saved in C<session_delete_reason> if provided.
314
315=item initialize_session_data
316
317This method will initialize the internal structure of the session, and is
318called by the C<session> method if appropriate.
319
229a5b53 320=item generate_session_id
321
322This method will return a string that can be used as a session ID. It is
323supposed to be a reasonably random string with enough bits to prevent
324collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
325MD5 or SHA-256, depending on the availibility of these modules.
326
327=item session_hash_seed
328
329This method is actually rather internal to generate_session_id, but should be
330overridable in case you want to provide more random data.
331
332Currently it returns a concatenated string which contains:
333
334=over 4
335
336=item *
337
338A counter
339
340=item *
341
342The current time
343
344=item *
345
346One value from C<rand>.
347
348=item *
349
350The stringified value of a newly allocated hash reference
351
352=item *
353
354The stringified value of the Catalyst context object
355
356=back
357
358In the hopes that those combined values are entropic enough for most uses. If
359this is not the case you can replace C<session_hash_seed> with e.g.
360
361 sub session_hash_seed {
362 open my $fh, "<", "/dev/random";
363 read $fh, my $bytes, 20;
364 close $fh;
365 return $bytes;
366 }
367
368Or even more directly, replace C<generate_session_id>:
369
370 sub generate_session_id {
371 open my $fh, "<", "/dev/random";
372 read $fh, my $bytes, 20;
373 close $fh;
374 return unpack("H*", $bytes);
375 }
376
377Also have a look at L<Crypt::Random> and the various openssl bindings - these
378modules provide APIs for cryptographically secure random data.
379
99b2191e 380=item dump_these
381
382See L<Catalyst/dump_these> - ammends the session data structure to the list of
383dumped objects if session ID is defined.
384
9e447f9d 385=back
386
a92c8aeb 387=head1 USING SESSIONS DURING PREPARE
388
389The earliest point in time at which you may use the session data is after
390L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
391
392State plugins must set $c->session ID before C<prepare_action>, and during
393C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
394the store.
395
396 sub prepare_action {
397 my $c = shift;
398
399 # don't touch $c->session yet!
400
401 $c->NEXT::prepare_action( @_ );
402
403 $c->session; # this is OK
404 $c->sessionid; # this is also OK
405 }
406
9e447f9d 407=head1 CONFIGURATION
408
229a5b53 409 $c->config->{session} = {
410 expires => 1234,
411 };
9e447f9d 412
413All configuation parameters are provided in a hash reference under the
414C<session> key in the configuration hash.
415
416=over 4
417
418=item expires
419
420The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
421hours).
422
423=item verify_address
424
425When false, C<< $c->request->address >> will be checked at prepare time. If it
426is not the same as the address that initiated the session, the session is
427deleted.
428
429=back
430
431=head1 SPECIAL KEYS
432
433The hash reference returned by C<< $c->session >> contains several keys which
434are automatically set:
435
436=over 4
437
438=item __expires
439
440A timestamp whose value is the last second when the session is still valid. If
441a session is restored, and __expires is less than the current time, the session
442is deleted.
443
444=item __updated
445
446The last time a session was saved. This is the value of
447C<< $c->{session}{__expires} - $c->config->{session}{expires} >>.
448
449=item __created
450
451The time when the session was first created.
452
453=item __address
454
455The value of C<< $c->request->address >> at the time the session was created.
456This value is only populated of C<verify_address> is true in the configuration.
457
458=back
459
c80e9f04 460=head1 CAVEATS
461
462C<verify_address> could make your site inaccessible to users who are behind
463load balanced proxies. Some ISPs may give a different IP to each request by the
464same client due to this type of proxying. If addresses are verified these
465users' sessions cannot persist.
466
467To let these users access your site you can either disable address verification
468as a whole, or provide a checkbox in the login dialog that tells the server
469that it's OK for the address of the client to change. When the server sees that
470this box is checked it should delete the C<__address> sepcial key from the
471session hash when the hash is first created.
472
9e447f9d 473=cut
474
475