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