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