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