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