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