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