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