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