File Coverage

blib/lib/Catalyst/Plugin/Session.pm
Criterion Covered Total %
statement 160 288 55.5
branch 36 108 33.3
condition 23 62 37.1
subroutine 36 62 58.0
pod 35 35 100.0
total 290 555 52.2


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