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