Various refactorings and fixes for the session plugin's lazy loading
[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 ();
d44bc687 13use Object::Signature ();
9e447f9d 14
5d8cc4c7 15our $VERSION = "0.05";
37160715 16
ab634fee 17my @session_data_accessors; # used in delete_session
9e447f9d 18BEGIN {
4207ce8d 19 __PACKAGE__->mk_accessors(
ab634fee 20 "_session_delete_reason",
21 @session_data_accessors = qw/
4207ce8d 22 _sessionid
23 _session
24 _session_expires
25 _session_data_sig
4207ce8d 26 _flash
f4d79f85 27 _flash_keep_keys
28 _flash_key_hashes
1dbf4cd5 29 _tried_loading_session_id
30 _tried_loading_session_data
31 _tried_loading_session_expires
32 _tried_loading_flash_data
4207ce8d 33 /
34 );
9e447f9d 35}
36
37sub setup {
9a9252c2 38 my $c = shift;
39
40 $c->NEXT::setup(@_);
41
42 $c->check_session_plugin_requirements;
43 $c->setup_session;
44
45 return $c;
9e447f9d 46}
47
48sub check_session_plugin_requirements {
9a9252c2 49 my $c = shift;
9e447f9d 50
9a9252c2 51 unless ( $c->isa("Catalyst::Plugin::Session::State")
52 && $c->isa("Catalyst::Plugin::Session::Store") )
53 {
54 my $err =
55 ( "The Session plugin requires both Session::State "
56 . "and Session::Store plugins to be used as well." );
9e447f9d 57
9a9252c2 58 $c->log->fatal($err);
59 Catalyst::Exception->throw($err);
60 }
9e447f9d 61}
62
63sub setup_session {
9a9252c2 64 my $c = shift;
9e447f9d 65
9a9252c2 66 my $cfg = ( $c->config->{session} ||= {} );
9e447f9d 67
9a9252c2 68 %$cfg = (
69 expires => 7200,
70 verify_address => 1,
71 %$cfg,
72 );
9e447f9d 73
9a9252c2 74 $c->NEXT::setup_session();
9e447f9d 75}
76
19c130c2 77sub prepare_action {
78 my $c = shift;
79
4207ce8d 80 if ( $c->config->{session}{flash_to_stash}
e5b2372a 81 and $c->sessionid
4207ce8d 82 and my $flash_data = $c->flash )
83 {
19c130c2 84 @{ $c->stash }{ keys %$flash_data } = values %$flash_data;
85 }
86
87 $c->NEXT::prepare_action(@_);
88}
89
9e447f9d 90sub finalize {
9a9252c2 91 my $c = shift;
9e447f9d 92
1dbf4cd5 93 $c->_save_session_expires;
9b0fa2a6 94 $c->_save_session;
95 $c->_save_flash;
96
97 $c->NEXT::finalize(@_);
98}
99
1dbf4cd5 100sub _save_session_expires {
9b0fa2a6 101 my $c = shift;
4207ce8d 102
1dbf4cd5 103 if ( defined(my $expires = $c->_session_expires) ) {
104 my $sid = $c->sessionid;
105 $c->store_session_data( "expires:$sid" => $expires );
9e447f9d 106
1dbf4cd5 107 $c->_session_expires(undef);
108 $c->_tried_loading_session_expires(undef);
109 }
110}
6687905d 111
1dbf4cd5 112sub _save_session {
113 my $c = shift;
7a02371f 114
1dbf4cd5 115 if ( my $session_data = $c->_session ) {
d44bc687 116
1dbf4cd5 117 no warnings 'uninitialized';
118 if ( Object::Signature::signature($session_data) ne
119 $c->_session_data_sig )
120 {
121 $session_data->{__updated} = time();
122 my $sid = $c->sessionid;
123 $c->store_session_data( "session:$sid" => $session_data );
ea972e9a 124 }
1dbf4cd5 125
126 $c->_session(undef);
127 $c->_tried_loading_session_data(undef);
9a9252c2 128 }
9b0fa2a6 129}
9a9252c2 130
9b0fa2a6 131sub _save_flash {
132 my $c = shift;
133
1dbf4cd5 134 if ( my $flash_data = $c->_flash ) {
4207ce8d 135
1dbf4cd5 136 my $hashes = $c->_flash_key_hashes || {};
137 my $keep = $c->_flash_keep_keys || {};
138 foreach my $key ( keys %$hashes ) {
139 if ( !exists $keep->{$key} and Object::Signature::signature( \$flash_data->{$key} ) eq $hashes->{$key} ) {
140 delete $flash_data->{$key};
23fbca00 141 }
ea972e9a 142 }
1dbf4cd5 143
144 my $sid = $c->sessionid;
145
146 if (%$flash_data) {
147 $c->store_session_data( "flash:$sid", $flash_data );
148 }
149 else {
150 $c->delete_session_data("flash:$sid");
151 }
152
153 $c->_flash(undef);
154 $c->_tried_loading_flash_data(undef);
9b0fa2a6 155 }
9e447f9d 156}
157
e5b2372a 158sub _load_session_expires {
159 my $c = shift;
1dbf4cd5 160 return if $c->_tried_loading_session_expires;
161 $c->_tried_loading_session_expires(1);
e5b2372a 162
163 if ( my $sid = $c->sessionid ) {
164 my $expires = $c->get_session_data("expires:$sid") || 0;
165
166 if ( $expires >= time() ) {
167 return $c->extend_session_expires( $expires );
168 } else {
169 $c->delete_session( "session expired" );
170 return 0;
171 }
e5b2372a 172 }
1dbf4cd5 173
174 return;
e5b2372a 175}
176
b7acf64e 177sub _load_session {
178 my $c = shift;
1dbf4cd5 179 return if $c->_tried_loading_session_data;
180 $c->_tried_loading_session_data(1);
b7acf64e 181
e5b2372a 182 if ( my $sid = $c->sessionid ) {
6687905d 183 if ( $c->session_expires ) { # > 0
0974ac06 184
2e412459 185 my $session_data = $c->get_session_data("session:$sid") || return;
6687905d 186 $c->_session($session_data);
3f182468 187
6687905d 188 no warnings 'uninitialized'; # ne __address
189 if ( $c->config->{session}{verify_address}
190 && $session_data->{__address} ne $c->request->address )
191 {
192 $c->log->warn(
193 "Deleting session $sid due to address mismatch ("
194 . $session_data->{__address} . " != "
47ca362e 195 . $c->request->address . ")"
6687905d 196 );
197 $c->delete_session("address mismatch");
198 return;
199 }
4207ce8d 200
6687905d 201 $c->log->debug(qq/Restored session "$sid"/) if $c->debug;
168d6819 202 $c->_session_data_sig( Object::Signature::signature($session_data) ) if $session_data;
6687905d 203 $c->_expire_session_keys;
4207ce8d 204
6687905d 205 return $session_data;
3f182468 206 }
9a9252c2 207 }
29d15411 208
4207ce8d 209 return;
b7acf64e 210}
9a9252c2 211
9b0fa2a6 212sub _load_flash {
213 my $c = shift;
1dbf4cd5 214 return if $c->_tried_loading_flash_data;
215 $c->_tried_loading_flash_data(1);
9b0fa2a6 216
e5b2372a 217 if ( my $sid = $c->sessionid ) {
4207ce8d 218 if ( my $flash_data = $c->_flash
219 || $c->_flash( $c->get_session_data("flash:$sid") ) )
220 {
f4d79f85 221 $c->_flash_key_hashes({ map { $_ => Object::Signature::signature( \$flash_data->{$_} ) } keys %$flash_data });
e5b2372a 222
9b0fa2a6 223 return $flash_data;
224 }
225 }
226
1dbf4cd5 227 return;
9b0fa2a6 228}
229
d44bc687 230sub _expire_session_keys {
b7acf64e 231 my ( $c, $data ) = @_;
232
233 my $now = time;
234
e5b2372a 235 my $expire_times = ( $data || $c->_session || {} )->{__expire_keys} || {};
236 foreach my $key ( grep { $expire_times->{$_} < $now } keys %$expire_times ) {
b7acf64e 237 delete $c->_session->{$key};
e5b2372a 238 delete $expire_times->{$key};
b7acf64e 239 }
9e447f9d 240}
241
242sub delete_session {
9a9252c2 243 my ( $c, $msg ) = @_;
9e447f9d 244
9a9252c2 245 # delete the session data
e5b2372a 246 my $sid = $c->sessionid || return;
4207ce8d 247 $c->delete_session_data("${_}:${sid}") for qw/session expires flash/;
9e447f9d 248
e5b2372a 249 $c->delete_session_id;
250
9a9252c2 251 # reset the values in the context object
ab634fee 252 # see the BEGIN block
253 $c->$_(undef) for @session_data_accessors;
6687905d 254
29d15411 255 $c->_session_delete_reason($msg);
256}
257
258sub session_delete_reason {
259 my $c = shift;
260
e5b2372a 261 $c->session_is_valid; # check that it was loaded
29d15411 262
4207ce8d 263 $c->_session_delete_reason(@_);
9e447f9d 264}
265
6687905d 266sub session_expires {
e5b2372a 267 my $c = shift;
6687905d 268
e5b2372a 269 if ( defined( my $expires = $c->_session_expires ) ) {
270 return $expires;
1dbf4cd5 271 } elsif ( defined( $expires = $c->_load_session_expires ) ) {
272 $c->_session_expires($expires);
273 return $expires;
e5b2372a 274 } else {
1dbf4cd5 275 return 0;
e5b2372a 276 }
277}
6687905d 278
e5b2372a 279sub extend_session_expires {
280 my ( $c, $expires ) = @_;
e5b2372a 281 $c->_session_expires( my $updated = $c->calculate_extended_session_expires( $expires ) );
4c79feb6 282 $c->extend_session_id( $c->sessionid, $updated );
e5b2372a 283 return $updated;
284}
6687905d 285
e5b2372a 286sub calculate_initial_session_expires {
287 my $c = shift;
288 return ( time() + $c->config->{session}{expires} );
289}
290
291sub calculate_extended_session_expires {
292 my ( $c, $prev ) = @_;
293 $c->calculate_initial_session_expires;
294}
295
296sub reset_session_expires {
297 my ( $c, $sid ) = @_;
298 $c->_session_expires( my $exp = $c->calculate_initial_session_expires );
299 $exp;
6687905d 300}
301
0974ac06 302sub sessionid {
4207ce8d 303 my $c = shift;
e5b2372a 304
1dbf4cd5 305 return $c->_sessionid || $c->_load_sessionid;
306}
307
308sub _load_sessionid {
309 my $c = shift;
310 return if $c->_tried_loading_session_id;
311 $c->_tried_loading_session_id(1);
312
313 if ( defined( my $sid = $c->get_session_id ) ) {
314 if ( $c->validate_session_id($sid) ) {
315 $c->_sessionid( $sid );
316 return $sid;
317 } else {
318 my $err = "Tried to set invalid session ID '$sid'";
319 $c->log->error($err);
320 Catalyst::Exception->throw($err);
4207ce8d 321 }
322 }
1dbf4cd5 323
324 return;
e5b2372a 325}
326
327sub session_is_valid {
328 my $c = shift;
4207ce8d 329
1dbf4cd5 330 $c->_load_session; # check expiry and also __address, etc
4207ce8d 331
e5b2372a 332 if ( $c->_session ) {
333 return 1;
334 } else {
335 return;
336 }
0974ac06 337}
338
339sub validate_session_id {
4207ce8d 340 my ( $c, $sid ) = @_;
0974ac06 341
4207ce8d 342 $sid and $sid =~ /^[a-f\d]+$/i;
0974ac06 343}
344
9e447f9d 345sub session {
9a9252c2 346 my $c = shift;
9e447f9d 347
29d15411 348 $c->_session || $c->_load_session || do {
1dbf4cd5 349 $c->create_session_id_if_needed;
29d15411 350 $c->initialize_session_data;
4207ce8d 351 };
9e447f9d 352}
353
f4d79f85 354sub keep_flash {
355 my ( $c, @keys ) = @_;
2e412459 356 my $href = $c->_flash_keep_keys || $c->_flash_keep_keys({});
357 (@{$href}{@keys}) = ((undef) x @keys);
f4d79f85 358}
359
873f7011 360sub flash {
361 my $c = shift;
78476ce0 362 $c->_flash || $c->_load_flash || do {
1dbf4cd5 363 $c->create_session_id_if_needed;
78476ce0 364 $c->_flash( {} );
1dbf4cd5 365 }
873f7011 366}
367
b7acf64e 368sub session_expire_key {
369 my ( $c, %keys ) = @_;
370
371 my $now = time;
4207ce8d 372 @{ $c->session->{__expire_keys} }{ keys %keys } =
373 map { $now + $_ } values %keys;
b7acf64e 374}
375
9e447f9d 376sub initialize_session_data {
9a9252c2 377 my $c = shift;
9e447f9d 378
9a9252c2 379 my $now = time;
9e447f9d 380
4207ce8d 381 return $c->_session(
382 {
383 __created => $now,
384 __updated => $now,
385
386 (
387 $c->config->{session}{verify_address}
388 ? ( __address => $c->request->address )
389 : ()
390 ),
391 }
392 );
9e447f9d 393}
394
9e447f9d 395sub generate_session_id {
396 my $c = shift;
397
398 my $digest = $c->_find_digest();
399 $digest->add( $c->session_hash_seed() );
400 return $digest->hexdigest;
401}
402
1dbf4cd5 403sub create_session_id_if_needed {
78476ce0 404 my $c = shift;
1dbf4cd5 405 $c->create_session_id unless $c->sessionid;
406}
78476ce0 407
1dbf4cd5 408sub create_session_id {
409 my $c = shift;
410
e5b2372a 411 my $sid = $c->generate_session_id;
78476ce0 412
e5b2372a 413 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
78476ce0 414
e5b2372a 415 $c->_sessionid($sid);
416 $c->reset_session_expires;
417 $c->set_session_id($sid);
418
419 return $sid;
78476ce0 420}
421
9e447f9d 422my $counter;
9a9252c2 423
9e447f9d 424sub session_hash_seed {
9a9252c2 425 my $c = shift;
426
427 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
9e447f9d 428}
429
430my $usable;
9a9252c2 431
9e447f9d 432sub _find_digest () {
9a9252c2 433 unless ($usable) {
d44bc687 434 foreach my $alg (qw/SHA-1 SHA-256 MD5/) {
4207ce8d 435 if ( eval { Digest->new($alg) } ) {
5faaa4b0 436 $usable = $alg;
437 last;
438 }
7d139eeb 439 }
4207ce8d 440 Catalyst::Exception->throw(
9a9252c2 441 "Could not find a suitable Digest module. Please install "
4207ce8d 442 . "Digest::SHA1, Digest::SHA, or Digest::MD5" )
443 unless $usable;
9a9252c2 444 }
9e447f9d 445
446 return Digest->new($usable);
447}
448
99b2191e 449sub dump_these {
450 my $c = shift;
451
452 (
453 $c->NEXT::dump_these(),
454
455 $c->sessionid
456 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
457 : ()
458 );
459}
460
e5b2372a 461
462sub get_session_id { shift->NEXT::get_session_id(@_) }
463sub set_session_id { shift->NEXT::set_session_id(@_) }
464sub delete_session_id { shift->NEXT::delete_session_id(@_) }
4c79feb6 465sub extend_session_id { shift->NEXT::extend_session_id(@_) }
e5b2372a 466
9e447f9d 467__PACKAGE__;
468
469__END__
470
471=pod
472
473=head1 NAME
474
475Catalyst::Plugin::Session - Generic Session plugin - ties together server side
fb1a4ac3 476storage and client side state required to maintain session data.
9e447f9d 477
478=head1 SYNOPSIS
479
8f0b4c16 480 # To get sessions to "just work", all you need to do is use these plugins:
481
482 use Catalyst qw/
483 Session
484 Session::Store::FastMmap
485 Session::State::Cookie
486 /;
487
488 # you can replace Store::FastMmap with Store::File - both have sensible
489 # default configurations (see their docs for details)
490
491 # more complicated backends are available for other scenarios (DBI storage,
492 # etc)
493
494
495 # after you've loaded the plugins you can save session data
496 # For example, if you are writing a shopping cart, it could be implemented
497 # like this:
9e447f9d 498
229a5b53 499 sub add_item : Local {
500 my ( $self, $c ) = @_;
501
502 my $item_id = $c->req->param("item");
503
8f0b4c16 504 # $c->session is a hash ref, a bit like $c->stash
505 # the difference is that it' preserved across requests
229a5b53 506
507 push @{ $c->session->{items} }, $item_id;
508
509 $c->forward("MyView");
510 }
511
512 sub display_items : Local {
513 my ( $self, $c ) = @_;
514
515 # values in $c->session are restored
516 $c->stash->{items_to_display} =
8f0b4c16 517 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
229a5b53 518
519 $c->forward("MyView");
520 }
521
9e447f9d 522=head1 DESCRIPTION
523
524The Session plugin is the base of two related parts of functionality required
525for session management in web applications.
526
527The first part, the State, is getting the browser to repeat back a session key,
528so that the web application can identify the client and logically string
529several requests together into a session.
530
531The second part, the Store, deals with the actual storage of information about
532the client. This data is stored so that the it may be revived for every request
533made by the same client.
534
535This plugin links the two pieces together.
536
8f0b4c16 537=head1 RECCOMENDED BACKENDS
538
539=over 4
540
541=item Session::State::Cookie
542
543The only really sane way to do state is using cookies.
544
545=item Session::Store::File
546
547A portable backend, based on Cache::File.
548
549=item Session::Store::FastMmap
550
551A fast and flexible backend, based on Cache::FastMmap.
552
553=back
554
9e447f9d 555=head1 METHODS
556
557=over 4
558
559=item sessionid
560
561An accessor for the session ID value.
562
563=item session
564
565Returns a hash reference that might contain unserialized values from previous
566requests in the same session, and whose modified value will be saved for future
567requests.
568
569This method will automatically create a new session and session ID if none
570exists.
571
ab634fee 572=item session_expires
573
574=item session_expires $reset
575
576This method returns the time when the current session will expire, or 0 if
577there is no current session. If there is a session and it already expired, it
578will delete the session and return 0 as well.
579
580If the C<$reset> parameter is true, and there is a session ID the expiry time
581will be reset to the current time plus the time to live (see
582L</CONFIGURATION>). This is used when creating a new session.
583
07e714d2 584=item flash
585
586This is like Ruby on Rails' flash data structure. Think of it as a stash that
44ab6d1c 587lasts for longer than one request, letting you redirect instead of forward.
588
589The flash data will be cleaned up only on requests on which actually use
590$c->flash (thus allowing multiple redirections), and the policy is to delete
bf6bd311 591all the keys which haven't changed since the flash data was loaded at the end
592of every request.
07e714d2 593
594 sub moose : Local {
595 my ( $self, $c ) = @_;
596
597 $c->flash->{beans} = 10;
598 $c->response->redirect( $c->uri_for("foo") );
599 }
600
601 sub foo : Local {
602 my ( $self, $c ) = @_;
603
604 my $value = $c->flash->{beans};
605
606 # ...
607
608 $c->response->redirect( $c->uri_for("bar") );
609 }
610
611 sub bar : Local {
612 my ( $self, $c ) = @_;
613
614 if ( exists $c->flash->{beans} ) { # false
615
616 }
617 }
618
bf6bd311 619=item keep_flash @keys
620
621If you wawnt to keep a flash key for the next request too, even if it hasn't
622changed, call C<keep_flash> and pass in the keys as arguments.
623
fffeb18f 624=item delete_session REASON
625
626This method is used to invalidate a session. It takes an optional parameter
627which will be saved in C<session_delete_reason> if provided.
628
9e447f9d 629=item session_delete_reason
630
631This accessor contains a string with the reason a session was deleted. Possible
632values include:
633
634=over 4
635
636=item *
637
638C<address mismatch>
639
640=item *
641
642C<session expired>
643
644=back
645
b7acf64e 646=item session_expire_key $key, $ttl
647
648Mark a key to expire at a certain time (only useful when shorter than the
649expiry time for the whole session).
650
651For example:
652
653 __PACKAGE__->config->{session}{expires} = 1000000000000; # forever
654
655 # later
656
657 $c->session_expire_key( __user => 3600 );
658
659Will make the session data survive, but the user will still be logged out after
660an hour.
661
662Note that these values are not auto extended.
663
8f0b4c16 664=back
665
10c72079 666=head1 INTERNAL METHODS
8f0b4c16 667
668=over 4
669
9e447f9d 670=item setup
671
672This method is extended to also make calls to
673C<check_session_plugin_requirements> and C<setup_session>.
674
675=item check_session_plugin_requirements
676
677This method ensures that a State and a Store plugin are also in use by the
678application.
679
680=item setup_session
681
682This method populates C<< $c->config->{session} >> with the default values
683listed in L</CONFIGURATION>.
684
685=item prepare_action
686
68fd02ae 687This methoid is extended.
688
689It's only effect is if the (off by default) C<flash_to_stash> configuration
690parameter is on - then it will copy the contents of the flash to the stash at
691prepare time.
9e447f9d 692
693=item finalize
694
695This method is extended and will extend the expiry time, as well as persist the
696session data if a session exists.
697
9e447f9d 698=item initialize_session_data
699
700This method will initialize the internal structure of the session, and is
701called by the C<session> method if appropriate.
702
68fd02ae 703=item create_session_id
704
705Creates a new session id using C<generate_session_id> if there is no session ID
706yet.
707
ab634fee 708=item validate_session_id SID
709
710Make sure a session ID is of the right format.
711
712This currently ensures that the session ID string is any amount of case
713insensitive hexadecimal characters.
714
229a5b53 715=item generate_session_id
716
717This method will return a string that can be used as a session ID. It is
718supposed to be a reasonably random string with enough bits to prevent
719collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
720MD5 or SHA-256, depending on the availibility of these modules.
721
722=item session_hash_seed
723
724This method is actually rather internal to generate_session_id, but should be
725overridable in case you want to provide more random data.
726
727Currently it returns a concatenated string which contains:
728
729=over 4
730
731=item *
732
733A counter
734
735=item *
736
737The current time
738
739=item *
740
741One value from C<rand>.
742
743=item *
744
745The stringified value of a newly allocated hash reference
746
747=item *
748
749The stringified value of the Catalyst context object
750
751=back
752
753In the hopes that those combined values are entropic enough for most uses. If
754this is not the case you can replace C<session_hash_seed> with e.g.
755
756 sub session_hash_seed {
757 open my $fh, "<", "/dev/random";
758 read $fh, my $bytes, 20;
759 close $fh;
760 return $bytes;
761 }
762
763Or even more directly, replace C<generate_session_id>:
764
765 sub generate_session_id {
766 open my $fh, "<", "/dev/random";
767 read $fh, my $bytes, 20;
768 close $fh;
769 return unpack("H*", $bytes);
770 }
771
772Also have a look at L<Crypt::Random> and the various openssl bindings - these
773modules provide APIs for cryptographically secure random data.
774
99b2191e 775=item dump_these
776
777See L<Catalyst/dump_these> - ammends the session data structure to the list of
778dumped objects if session ID is defined.
779
9e447f9d 780=back
781
a92c8aeb 782=head1 USING SESSIONS DURING PREPARE
783
784The earliest point in time at which you may use the session data is after
785L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
786
787State plugins must set $c->session ID before C<prepare_action>, and during
788C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
789the store.
790
791 sub prepare_action {
792 my $c = shift;
793
794 # don't touch $c->session yet!
b1cd7d77 795
a92c8aeb 796 $c->NEXT::prepare_action( @_ );
797
798 $c->session; # this is OK
799 $c->sessionid; # this is also OK
800 }
801
9e447f9d 802=head1 CONFIGURATION
803
229a5b53 804 $c->config->{session} = {
805 expires => 1234,
806 };
9e447f9d 807
808All configuation parameters are provided in a hash reference under the
809C<session> key in the configuration hash.
810
811=over 4
812
813=item expires
814
815The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
816hours).
817
818=item verify_address
819
8c7e922c 820When true, C<<$c->request->address>> will be checked at prepare time. If it is
821not the same as the address that initiated the session, the session is deleted.
9e447f9d 822
68fd02ae 823=item flash_to_stash
824
825This option makes it easier to have actions behave the same whether they were
826forwarded to or redirected to. On prepare time it copies the contents of
827C<flash> (if any) to the stash.
828
9e447f9d 829=back
830
831=head1 SPECIAL KEYS
832
833The hash reference returned by C<< $c->session >> contains several keys which
834are automatically set:
835
836=over 4
837
838=item __expires
839
ab634fee 840This key no longer exists. Use C<session_expires> instead.
9e447f9d 841
842=item __updated
843
d44bc687 844The last time a session was saved to the store.
9e447f9d 845
846=item __created
847
848The time when the session was first created.
849
850=item __address
851
852The value of C<< $c->request->address >> at the time the session was created.
8c7e922c 853This value is only populated if C<verify_address> is true in the configuration.
9e447f9d 854
855=back
856
c80e9f04 857=head1 CAVEATS
858
a552e4b5 859=head2 Round the Robin Proxies
860
c80e9f04 861C<verify_address> could make your site inaccessible to users who are behind
862load balanced proxies. Some ISPs may give a different IP to each request by the
863same client due to this type of proxying. If addresses are verified these
864users' sessions cannot persist.
865
866To let these users access your site you can either disable address verification
867as a whole, or provide a checkbox in the login dialog that tells the server
868that it's OK for the address of the client to change. When the server sees that
869this box is checked it should delete the C<__address> sepcial key from the
870session hash when the hash is first created.
871
a552e4b5 872=head2 Race Conditions
873
874In this day and age where cleaning detergents and dutch football (not the
875american kind) teams roam the plains in great numbers, requests may happen
876simultaneously. This means that there is some risk of session data being
877overwritten, like this:
878
879=over 4
880
881=item 1.
882
883request a starts, request b starts, with the same session id
884
885=item 2.
886
887session data is loaded in request a
888
889=item 3.
890
891session data is loaded in request b
892
893=item 4.
894
895session data is changed in request a
896
897=item 5.
898
899request a finishes, session data is updated and written to store
900
901=item 6.
902
903request b finishes, session data is updated and written to store, overwriting
904changes by request a
905
906=back
907
908If this is a concern in your application, a soon to be developed locking
909solution is the only safe way to go. This will have a bigger overhead.
910
911For applications where any given user is only making one request at a time this
912plugin should be safe enough.
913
d45028d6 914=head1 AUTHORS
915
baa9db9c 916=over 4
917
918=item Andy Grundman
919
920=item Christian Hansen
921
922=item Yuval Kogman, C<nothingmuch@woobling.org> (current maintainer)
923
924=item Sebastian Riedel
925
926=back
927
928And countless other contributers from #catalyst. Thanks guys!
d45028d6 929
cc40ae4b 930=head1 COPYRIGHT & LICENSE
d45028d6 931
932 Copyright (c) 2005 the aforementioned authors. All rights
933 reserved. This program is free software; you can redistribute
934 it and/or modify it under the same terms as Perl itself.
935
9e447f9d 936=cut
937
938