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