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