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