File Coverage

blib/lib/Catalyst/Plugin/Session.pm
Criterion Covered Total %
statement 160 300 53.3
branch 36 114 31.5
condition 23 62 37.1
subroutine 36 63 57.1
pod 35 35 100.0
total 290 574 50.5


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