Test for verify address and fix reset __address
[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
f4f73302 16our $VERSION = '0.31';
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
d3c97126 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 ) ) {
260b14c4 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 ) = @_;
260b14c4 359 $c->_extended_session_expires( my $updated = $c->calculate_extended_session_expires( $expires ) );
4c79feb6 360 $c->extend_session_id( $c->sessionid, $updated );
e5b2372a 361 return $updated;
362}
6687905d 363
e5b2372a 364sub calculate_initial_session_expires {
365 my $c = shift;
064c3709 366 return ( time() + $c->_session_plugin_config->{expires} );
e5b2372a 367}
368
369sub calculate_extended_session_expires {
370 my ( $c, $prev ) = @_;
371 $c->calculate_initial_session_expires;
372}
373
374sub reset_session_expires {
375 my ( $c, $sid ) = @_;
af1e4bc8 376
260b14c4 377 my $exp = $c->calculate_initial_session_expires;
378 $c->_session_expires( $exp );
cb104024 379 #
380 # since we're setting _session_expires directly, make load_session_expires
381 # actually use that value.
382 #
383 $c->_tried_loading_session_expires(1);
260b14c4 384 $c->_extended_session_expires( $exp );
e5b2372a 385 $exp;
6687905d 386}
387
0974ac06 388sub sessionid {
4207ce8d 389 my $c = shift;
af1e4bc8 390
1dbf4cd5 391 return $c->_sessionid || $c->_load_sessionid;
392}
393
394sub _load_sessionid {
395 my $c = shift;
396 return if $c->_tried_loading_session_id;
397 $c->_tried_loading_session_id(1);
398
399 if ( defined( my $sid = $c->get_session_id ) ) {
400 if ( $c->validate_session_id($sid) ) {
ec299c02 401 # temporarily set the inner key, so that validation will work
402 $c->_sessionid($sid);
1dbf4cd5 403 return $sid;
404 } else {
405 my $err = "Tried to set invalid session ID '$sid'";
406 $c->log->error($err);
407 Catalyst::Exception->throw($err);
4207ce8d 408 }
409 }
1dbf4cd5 410
411 return;
e5b2372a 412}
413
414sub session_is_valid {
415 my $c = shift;
4207ce8d 416
b92ce604 417 # force a check for expiry, but also __address, etc
418 if ( $c->_load_session ) {
e5b2372a 419 return 1;
420 } else {
421 return;
422 }
0974ac06 423}
424
425sub validate_session_id {
4207ce8d 426 my ( $c, $sid ) = @_;
0974ac06 427
4207ce8d 428 $sid and $sid =~ /^[a-f\d]+$/i;
0974ac06 429}
430
9e447f9d 431sub session {
9a9252c2 432 my $c = shift;
9e447f9d 433
a491a59b 434 my $session = $c->_session || $c->_load_session || do {
1dbf4cd5 435 $c->create_session_id_if_needed;
29d15411 436 $c->initialize_session_data;
4207ce8d 437 };
a491a59b 438
439 if (@_) {
440 my $new_values = @_ > 1 ? { @_ } : $_[0];
441 croak('session takes a hash or hashref') unless ref $new_values;
442
443 for my $key (keys %$new_values) {
444 $session->{$key} = $new_values->{$key};
445 }
446 }
447
448 $session;
9e447f9d 449}
450
f4d79f85 451sub keep_flash {
452 my ( $c, @keys ) = @_;
2e412459 453 my $href = $c->_flash_keep_keys || $c->_flash_keep_keys({});
454 (@{$href}{@keys}) = ((undef) x @keys);
f4d79f85 455}
456
af1e4bc8 457sub _flash_data {
873f7011 458 my $c = shift;
78476ce0 459 $c->_flash || $c->_load_flash || do {
1dbf4cd5 460 $c->create_session_id_if_needed;
78476ce0 461 $c->_flash( {} );
c9396824 462 };
463}
464
465sub _set_flash {
466 my $c = shift;
467 if (@_) {
468 my $items = @_ > 1 ? {@_} : $_[0];
469 croak('flash takes a hash or hashref') unless ref $items;
470 @{ $c->_flash }{ keys %$items } = values %$items;
1dbf4cd5 471 }
873f7011 472}
473
c9396824 474sub flash {
475 my $c = shift;
476 $c->_flash_data;
f6009cac 477 $c->_set_flash(@_);
478 return $c->_flash;
c9396824 479}
480
49727697 481sub clear_flash {
482 my $c = shift;
af1e4bc8 483
5a1f6ed4 484 #$c->delete_session_data("flash:" . $c->sessionid); # should this be in here? or delayed till finalization?
49727697 485 $c->_flash_key_hashes({});
5a1f6ed4 486 $c->_flash_keep_keys({});
49727697 487 $c->_flash({});
488}
489
b7acf64e 490sub session_expire_key {
491 my ( $c, %keys ) = @_;
492
493 my $now = time;
4207ce8d 494 @{ $c->session->{__expire_keys} }{ keys %keys } =
495 map { $now + $_ } values %keys;
b7acf64e 496}
497
9e447f9d 498sub initialize_session_data {
9a9252c2 499 my $c = shift;
9e447f9d 500
9a9252c2 501 my $now = time;
9e447f9d 502
3b667f72 503 return $c->_session(
504 {
4207ce8d 505 __created => $now,
506 __updated => $now,
507
508 (
064c3709 509 $c->_session_plugin_config->{verify_address}
f8f81744 510 ? ( __address => $c->request->address||'' )
4207ce8d 511 : ()
512 ),
06c621b5 513 (
064c3709 514 $c->_session_plugin_config->{verify_user_agent}
f8f81744 515 ? ( __user_agent => $c->request->user_agent||'' )
06c621b5 516 : ()
517 ),
3b667f72 518 }
519 );
9e447f9d 520}
521
9e447f9d 522sub generate_session_id {
523 my $c = shift;
524
525 my $digest = $c->_find_digest();
526 $digest->add( $c->session_hash_seed() );
527 return $digest->hexdigest;
528}
529
1dbf4cd5 530sub create_session_id_if_needed {
78476ce0 531 my $c = shift;
1dbf4cd5 532 $c->create_session_id unless $c->sessionid;
533}
78476ce0 534
1dbf4cd5 535sub create_session_id {
536 my $c = shift;
af1e4bc8 537
e5b2372a 538 my $sid = $c->generate_session_id;
78476ce0 539
e5b2372a 540 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
78476ce0 541
e5b2372a 542 $c->_sessionid($sid);
543 $c->reset_session_expires;
544 $c->set_session_id($sid);
545
546 return $sid;
78476ce0 547}
548
9e447f9d 549my $counter;
9a9252c2 550
9e447f9d 551sub session_hash_seed {
9a9252c2 552 my $c = shift;
553
554 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
9e447f9d 555}
556
557my $usable;
9a9252c2 558
9e447f9d 559sub _find_digest () {
9a9252c2 560 unless ($usable) {
d44bc687 561 foreach my $alg (qw/SHA-1 SHA-256 MD5/) {
4207ce8d 562 if ( eval { Digest->new($alg) } ) {
5faaa4b0 563 $usable = $alg;
564 last;
565 }
7d139eeb 566 }
4207ce8d 567 Catalyst::Exception->throw(
9a9252c2 568 "Could not find a suitable Digest module. Please install "
4207ce8d 569 . "Digest::SHA1, Digest::SHA, or Digest::MD5" )
570 unless $usable;
9a9252c2 571 }
9e447f9d 572
573 return Digest->new($usable);
574}
575
99b2191e 576sub dump_these {
577 my $c = shift;
578
579 (
fe0d5ebe 580 $c->maybe::next::method(),
99b2191e 581
834ab0b8 582 $c->_sessionid
99b2191e 583 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
584 : ()
585 );
586}
587
e5b2372a 588
fe0d5ebe 589sub get_session_id { shift->maybe::next::method(@_) }
590sub set_session_id { shift->maybe::next::method(@_) }
591sub delete_session_id { shift->maybe::next::method(@_) }
592sub extend_session_id { shift->maybe::next::method(@_) }
e5b2372a 593
9e447f9d 594__PACKAGE__;
595
596__END__
597
598=pod
599
600=head1 NAME
601
7048c24e 602Catalyst::Plugin::Session - Generic Session plugin - ties together server side storage and client side state required to maintain session data.
9e447f9d 603
604=head1 SYNOPSIS
605
8f0b4c16 606 # To get sessions to "just work", all you need to do is use these plugins:
607
608 use Catalyst qw/
609 Session
610 Session::Store::FastMmap
611 Session::State::Cookie
612 /;
613
7048c24e 614 # you can replace Store::FastMmap with Store::File - both have sensible
615 # default configurations (see their docs for details)
8f0b4c16 616
7048c24e 617 # more complicated backends are available for other scenarios (DBI storage,
618 # etc)
8f0b4c16 619
620
621 # after you've loaded the plugins you can save session data
622 # For example, if you are writing a shopping cart, it could be implemented
623 # like this:
9e447f9d 624
229a5b53 625 sub add_item : Local {
626 my ( $self, $c ) = @_;
627
628 my $item_id = $c->req->param("item");
629
8f0b4c16 630 # $c->session is a hash ref, a bit like $c->stash
631 # the difference is that it' preserved across requests
229a5b53 632
633 push @{ $c->session->{items} }, $item_id;
634
635 $c->forward("MyView");
636 }
637
638 sub display_items : Local {
639 my ( $self, $c ) = @_;
640
641 # values in $c->session are restored
642 $c->stash->{items_to_display} =
8f0b4c16 643 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
229a5b53 644
645 $c->forward("MyView");
646 }
647
9e447f9d 648=head1 DESCRIPTION
649
650The Session plugin is the base of two related parts of functionality required
651for session management in web applications.
652
653The first part, the State, is getting the browser to repeat back a session key,
654so that the web application can identify the client and logically string
655several requests together into a session.
656
657The second part, the Store, deals with the actual storage of information about
658the client. This data is stored so that the it may be revived for every request
659made by the same client.
660
661This plugin links the two pieces together.
662
2a323f6f 663=head1 RECOMENDED BACKENDS
8f0b4c16 664
665=over 4
666
667=item Session::State::Cookie
668
669The only really sane way to do state is using cookies.
670
671=item Session::Store::File
672
673A portable backend, based on Cache::File.
674
675=item Session::Store::FastMmap
676
677A fast and flexible backend, based on Cache::FastMmap.
678
679=back
680
9e447f9d 681=head1 METHODS
682
683=over 4
684
685=item sessionid
686
687An accessor for the session ID value.
688
689=item session
690
691Returns a hash reference that might contain unserialized values from previous
692requests in the same session, and whose modified value will be saved for future
693requests.
694
695This method will automatically create a new session and session ID if none
696exists.
697
a491a59b 698You can also set session keys by passing a list of key/value pairs or a
699hashref.
700
701 $c->session->{foo} = "bar"; # This works.
702 $c->session(one => 1, two => 2); # And this.
703 $c->session({ answer => 42 }); # And this.
704
ab634fee 705=item session_expires
706
707=item session_expires $reset
708
709This method returns the time when the current session will expire, or 0 if
710there is no current session. If there is a session and it already expired, it
711will delete the session and return 0 as well.
712
713If the C<$reset> parameter is true, and there is a session ID the expiry time
714will be reset to the current time plus the time to live (see
715L</CONFIGURATION>). This is used when creating a new session.
716
07e714d2 717=item flash
718
719This is like Ruby on Rails' flash data structure. Think of it as a stash that
44ab6d1c 720lasts for longer than one request, letting you redirect instead of forward.
721
722The flash data will be cleaned up only on requests on which actually use
723$c->flash (thus allowing multiple redirections), and the policy is to delete
bf6bd311 724all the keys which haven't changed since the flash data was loaded at the end
725of every request.
07e714d2 726
727 sub moose : Local {
728 my ( $self, $c ) = @_;
729
730 $c->flash->{beans} = 10;
731 $c->response->redirect( $c->uri_for("foo") );
732 }
733
734 sub foo : Local {
735 my ( $self, $c ) = @_;
736
737 my $value = $c->flash->{beans};
738
739 # ...
740
741 $c->response->redirect( $c->uri_for("bar") );
742 }
743
744 sub bar : Local {
745 my ( $self, $c ) = @_;
746
747 if ( exists $c->flash->{beans} ) { # false
af1e4bc8 748
07e714d2 749 }
750 }
751
49727697 752=item clear_flash
753
754Zap all the keys in the flash regardless of their current state.
755
bf6bd311 756=item keep_flash @keys
757
1961343c 758If you want to keep a flash key for the next request too, even if it hasn't
bf6bd311 759changed, call C<keep_flash> and pass in the keys as arguments.
760
fffeb18f 761=item delete_session REASON
762
763This method is used to invalidate a session. It takes an optional parameter
764which will be saved in C<session_delete_reason> if provided.
765
eb250519 766NOTE: This method will B<also> delete your flash data.
767
9e447f9d 768=item session_delete_reason
769
770This accessor contains a string with the reason a session was deleted. Possible
771values include:
772
773=over 4
774
775=item *
776
777C<address mismatch>
778
779=item *
780
781C<session expired>
782
783=back
784
b7acf64e 785=item session_expire_key $key, $ttl
786
787Mark a key to expire at a certain time (only useful when shorter than the
788expiry time for the whole session).
789
790For example:
791
2110d078 792 __PACKAGE__->config('Plugin::Session' => { expires => 10000000000 }); # "forever"
793 (NB If this number is too large, Y2K38 breakage could result.)
b7acf64e 794
795 # later
796
797 $c->session_expire_key( __user => 3600 );
798
799Will make the session data survive, but the user will still be logged out after
800an hour.
801
802Note that these values are not auto extended.
803
0ade68bd 804=item change_session_id
805
806By calling this method you can force a session id change while keeping all
807session data. This method might come handy when you are paranoid about some
808advanced variations of session fixation attack.
809
810If you want to prevent this session fixation scenario:
811
812 0) let us have WebApp with anonymous and authenticated parts
af1e4bc8 813 1) a hacker goes to vulnerable WebApp and gets a real sessionid,
0ade68bd 814 just by browsing anonymous part of WebApp
815 2) the hacker inserts (somehow) this values into a cookie in victim's browser
816 3) after the victim logs into WebApp the hacker can enter his/her session
817
818you should call change_session_id in your login controller like this:
819
820 if ($c->authenticate( { username => $user, password => $pass } )) {
821 # login OK
822 $c->change_session_id;
823 ...
824 } else {
825 # login FAILED
826 ...
827 }
828
8f0b4c16 829=back
830
10c72079 831=head1 INTERNAL METHODS
8f0b4c16 832
833=over 4
834
9e447f9d 835=item setup
836
837This method is extended to also make calls to
838C<check_session_plugin_requirements> and C<setup_session>.
839
840=item check_session_plugin_requirements
841
842This method ensures that a State and a Store plugin are also in use by the
843application.
844
845=item setup_session
846
064c3709 847This method populates C<< $c->config('Plugin::Session') >> with the default values
9e447f9d 848listed in L</CONFIGURATION>.
849
850=item prepare_action
851
bfa4f9cc 852This method is extended.
68fd02ae 853
bfa4f9cc 854Its only effect is if the (off by default) C<flash_to_stash> configuration
68fd02ae 855parameter is on - then it will copy the contents of the flash to the stash at
856prepare time.
9e447f9d 857
ccc77553 858=item finalize_headers
9e447f9d 859
d3c97126 860This method is extended and will extend the expiry time before sending
861the response.
862
92eaec32 863=item finalize_body
d3c97126 864
92eaec32 865This method is extended and will call finalize_session before the other
866finalize_body methods run. Here we persist the session data if a session exists.
9e447f9d 867
9e447f9d 868=item initialize_session_data
869
870This method will initialize the internal structure of the session, and is
871called by the C<session> method if appropriate.
872
68fd02ae 873=item create_session_id
874
bfa4f9cc 875Creates a new session ID using C<generate_session_id> if there is no session ID
68fd02ae 876yet.
877
ab634fee 878=item validate_session_id SID
879
880Make sure a session ID is of the right format.
881
882This currently ensures that the session ID string is any amount of case
883insensitive hexadecimal characters.
884
229a5b53 885=item generate_session_id
886
887This method will return a string that can be used as a session ID. It is
888supposed to be a reasonably random string with enough bits to prevent
889collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
bfa4f9cc 890MD5 or SHA-256, depending on the availability of these modules.
229a5b53 891
892=item session_hash_seed
893
894This method is actually rather internal to generate_session_id, but should be
895overridable in case you want to provide more random data.
896
897Currently it returns a concatenated string which contains:
898
899=over 4
900
7048c24e 901=item * A counter
229a5b53 902
7048c24e 903=item * The current time
229a5b53 904
7048c24e 905=item * One value from C<rand>.
229a5b53 906
7048c24e 907=item * The stringified value of a newly allocated hash reference
229a5b53 908
7048c24e 909=item * The stringified value of the Catalyst context object
229a5b53 910
911=back
912
bfa4f9cc 913in the hopes that those combined values are entropic enough for most uses. If
229a5b53 914this is not the case you can replace C<session_hash_seed> with e.g.
915
916 sub session_hash_seed {
917 open my $fh, "<", "/dev/random";
918 read $fh, my $bytes, 20;
919 close $fh;
920 return $bytes;
921 }
922
923Or even more directly, replace C<generate_session_id>:
924
925 sub generate_session_id {
926 open my $fh, "<", "/dev/random";
927 read $fh, my $bytes, 20;
928 close $fh;
929 return unpack("H*", $bytes);
930 }
931
932Also have a look at L<Crypt::Random> and the various openssl bindings - these
933modules provide APIs for cryptographically secure random data.
934
8f236527 935=item finalize_session
936
937Clean up the session during C<finalize>.
938
939This clears the various accessors after saving to the store.
940
99b2191e 941=item dump_these
942
943See L<Catalyst/dump_these> - ammends the session data structure to the list of
944dumped objects if session ID is defined.
945
d3c97126 946
947=item calculate_extended_session_expires
948
949=item calculate_initial_session_expires
950
951=item create_session_id_if_needed
952
953=item delete_session_id
954
955=item extend_session_expires
956
957=item extend_session_id
958
959=item get_session_id
960
961=item reset_session_expires
962
963=item session_is_valid
964
965=item set_session_id
966
9e447f9d 967=back
968
a92c8aeb 969=head1 USING SESSIONS DURING PREPARE
970
971The earliest point in time at which you may use the session data is after
972L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
973
974State plugins must set $c->session ID before C<prepare_action>, and during
975C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
976the store.
977
7048c24e 978 sub prepare_action {
979 my $c = shift;
a92c8aeb 980
7048c24e 981 # don't touch $c->session yet!
b1cd7d77 982
7048c24e 983 $c->NEXT::prepare_action( @_ );
a92c8aeb 984
7048c24e 985 $c->session; # this is OK
986 $c->sessionid; # this is also OK
987 }
a92c8aeb 988
9e447f9d 989=head1 CONFIGURATION
990
064c3709 991 $c->config('Plugin::Session' => {
229a5b53 992 expires => 1234,
064c3709 993 });
9e447f9d 994
995All configuation parameters are provided in a hash reference under the
064c3709 996C<Plugin::Session> key in the configuration hash.
9e447f9d 997
998=over 4
999
1000=item expires
1001
1002The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
1003hours).
1004
1005=item verify_address
1006
8c7e922c 1007When true, C<<$c->request->address>> will be checked at prepare time. If it is
1008not the same as the address that initiated the session, the session is deleted.
9e447f9d 1009
33641c1d 1010Defaults to false.
1011
06c621b5 1012=item verify_user_agent
1013
1014When true, C<<$c->request->user_agent>> will be checked at prepare time. If it
af1e4bc8 1015is not the same as the user agent that initiated the session, the session is
06c621b5 1016deleted.
1017
1018Defaults to false.
1019
68fd02ae 1020=item flash_to_stash
1021
1022This option makes it easier to have actions behave the same whether they were
1023forwarded to or redirected to. On prepare time it copies the contents of
1024C<flash> (if any) to the stash.
1025
9e447f9d 1026=back
1027
1028=head1 SPECIAL KEYS
1029
1030The hash reference returned by C<< $c->session >> contains several keys which
1031are automatically set:
1032
1033=over 4
1034
1035=item __expires
1036
ab634fee 1037This key no longer exists. Use C<session_expires> instead.
9e447f9d 1038
1039=item __updated
1040
d44bc687 1041The last time a session was saved to the store.
9e447f9d 1042
1043=item __created
1044
1045The time when the session was first created.
1046
1047=item __address
1048
1049The value of C<< $c->request->address >> at the time the session was created.
8c7e922c 1050This value is only populated if C<verify_address> is true in the configuration.
9e447f9d 1051
06c621b5 1052=item __user_agent
1053
e79a686c 1054The value of C<< $c->request->user_agent >> at the time the session was created.
06c621b5 1055This value is only populated if C<verify_user_agent> is true in the configuration.
1056
9e447f9d 1057=back
1058
c80e9f04 1059=head1 CAVEATS
1060
a552e4b5 1061=head2 Round the Robin Proxies
1062
c80e9f04 1063C<verify_address> could make your site inaccessible to users who are behind
1064load balanced proxies. Some ISPs may give a different IP to each request by the
1065same client due to this type of proxying. If addresses are verified these
1066users' sessions cannot persist.
1067
1068To let these users access your site you can either disable address verification
1069as a whole, or provide a checkbox in the login dialog that tells the server
1070that it's OK for the address of the client to change. When the server sees that
bfa4f9cc 1071this box is checked it should delete the C<__address> special key from the
c80e9f04 1072session hash when the hash is first created.
1073
a552e4b5 1074=head2 Race Conditions
1075
bfa4f9cc 1076In this day and age where cleaning detergents and Dutch football (not the
1077American kind) teams roam the plains in great numbers, requests may happen
a552e4b5 1078simultaneously. This means that there is some risk of session data being
1079overwritten, like this:
1080
1081=over 4
1082
1083=item 1.
1084
bfa4f9cc 1085request a starts, request b starts, with the same session ID
a552e4b5 1086
1087=item 2.
1088
1089session data is loaded in request a
1090
1091=item 3.
1092
1093session data is loaded in request b
1094
1095=item 4.
1096
1097session data is changed in request a
1098
1099=item 5.
1100
1101request a finishes, session data is updated and written to store
1102
1103=item 6.
1104
1105request b finishes, session data is updated and written to store, overwriting
1106changes by request a
1107
1108=back
1109
b6dae3d3 1110For applications where any given user's session is only making one request
1111at a time this plugin should be safe enough.
a552e4b5 1112
d45028d6 1113=head1 AUTHORS
1114
7048c24e 1115Andy Grundman
baa9db9c 1116
7048c24e 1117Christian Hansen
baa9db9c 1118
2842d938 1119Yuval Kogman, C<nothingmuch@woobling.org>
baa9db9c 1120
7048c24e 1121Sebastian Riedel
baa9db9c 1122
2842d938 1123Tomas Doran (t0m) C<bobtfish@bobtfish.net> (current maintainer)
1124
1125Sergio Salvi
1126
af1e4bc8 1127kmx C<kmx@volny.cz>
1128
836b0a11 1129Florian Ragwitz (rafl) C<rafl@debian.org>
1130
1131Kent Fredric (kentnl)
1132
baa9db9c 1133And countless other contributers from #catalyst. Thanks guys!
d45028d6 1134
cc40ae4b 1135=head1 COPYRIGHT & LICENSE
d45028d6 1136
7048c24e 1137 Copyright (c) 2005 the aforementioned authors. All rights
1138 reserved. This program is free software; you can redistribute
1139 it and/or modify it under the same terms as Perl itself.
d45028d6 1140
9e447f9d 1141=cut
1142
1143