X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FPlugin%2FSession.pm;h=a1d625c0a964200a31be430967cb4200c73b51a7;hb=8f0b4c16f7be960ef9c9eff3a770603722f341aa;hp=2bf9d2dc583c74a4c738e0f34ad7c215b3bd7377;hpb=37160715078411c082eee48a111faaab39484fd8;p=catagits%2FCatalyst-Plugin-Session.git diff --git a/lib/Catalyst/Plugin/Session.pm b/lib/Catalyst/Plugin/Session.pm index 2bf9d2d..a1d625c 100644 --- a/lib/Catalyst/Plugin/Session.pm +++ b/lib/Catalyst/Plugin/Session.pm @@ -10,7 +10,6 @@ use NEXT; use Catalyst::Exception (); use Digest (); use overload (); -use List::Util (); our $VERSION = "0.01"; @@ -76,33 +75,31 @@ sub finalize { sub prepare_action { my $c = shift; - my $ret = $c->NEXT::prepare_action; - - my $sid = $c->sessionid || return; - - $c->log->debug(qq/Found session "$sid"/) if $c->debug; - - my $s = $c->{session} ||= $c->get_session_data($sid); - if ( !$s or $s->{__expires} < time ) { - - # session expired - $c->log->debug("Deleting session $sid (expired)") if $c->debug; - $c->delete_session("session expired"); - return $ret; + if ( my $sid = $c->sessionid ) { + my $s = $c->{session} ||= $c->get_session_data($sid); + if ( !$s or $s->{__expires} < time ) { + + # session expired + $c->log->debug("Deleting session $sid (expired)") if $c->debug; + $c->delete_session("session expired"); + } + elsif ($c->config->{session}{verify_address} + && $c->{session}{__address} + && $c->{session}{__address} ne $c->request->address ) + { + $c->log->warn( + "Deleting session $sid due to address mismatch (" + . $c->{session}{__address} . " != " + . $c->request->address . ")", + ); + $c->delete_session("address mismatch"); + } + else { + $c->log->debug(qq/Restored session "$sid"/) if $c->debug; + } } - if ( $c->config->{session}{verify_address} - && $c->{session}{__address} - && $c->{session}{__address} ne $c->request->address ) - { - $c->log->warn( - "Deleting session $sid due to address mismatch (" - . $c->{session}{__address} . " != " - . $c->request->address . ")", - ); - $c->delete_session("address mismatch"); - return $ret; - } + $c->NEXT::prepare_action(@_); } sub delete_session { @@ -149,8 +146,6 @@ sub initialize_session_data { }; } -# refactor into Catalyst::Plugin::Session::ID::Weak ? - sub generate_session_id { my $c = shift; @@ -171,12 +166,14 @@ my $usable; sub _find_digest () { unless ($usable) { - $usable = List::Util::first( - sub { - eval { Digest->new($_) }; - }, - qw/SHA-1 MD5 SHA-256/ - ) + foreach my $alg (qw/SHA-1 MD5 SHA-256/) { + eval { + my $obj = Digest->new($alg); + $usable = $alg; + return $obj; + }; + } + $usable or Catalyst::Exception->throw( "Could not find a suitable Digest module. Please install " . "Digest::SHA1, Digest::SHA, or Digest::MD5" ); @@ -185,6 +182,18 @@ sub _find_digest () { return Digest->new($usable); } +sub dump_these { + my $c = shift; + + ( + $c->NEXT::dump_these(), + + $c->sessionid + ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], ) + : () + ); +} + __PACKAGE__; __END__ @@ -198,7 +207,47 @@ storage and client side tickets required to maintain session data. =head1 SYNOPSIS - use Catalyst qw/Session Session::Store::FastMmap Session::State::Cookie/; + # To get sessions to "just work", all you need to do is use these plugins: + + use Catalyst qw/ + Session + Session::Store::FastMmap + Session::State::Cookie + /; + + # you can replace Store::FastMmap with Store::File - both have sensible + # default configurations (see their docs for details) + + # more complicated backends are available for other scenarios (DBI storage, + # etc) + + + # after you've loaded the plugins you can save session data + # For example, if you are writing a shopping cart, it could be implemented + # like this: + + sub add_item : Local { + my ( $self, $c ) = @_; + + my $item_id = $c->req->param("item"); + + # $c->session is a hash ref, a bit like $c->stash + # the difference is that it' preserved across requests + + push @{ $c->session->{items} }, $item_id; + + $c->forward("MyView"); + } + + sub display_items : Local { + my ( $self, $c ) = @_; + + # values in $c->session are restored + $c->stash->{items_to_display} = + [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ]; + + $c->forward("MyView"); + } =head1 DESCRIPTION @@ -215,6 +264,24 @@ made by the same client. This plugin links the two pieces together. +=head1 RECCOMENDED BACKENDS + +=over 4 + +=item Session::State::Cookie + +The only really sane way to do state is using cookies. + +=item Session::Store::File + +A portable backend, based on Cache::File. + +=item Session::Store::FastMmap + +A fast and flexible backend, based on Cache::FastMmap. + +=back + =head1 METHODS =over 4 @@ -249,6 +316,12 @@ C =back +=back + +=item INTERNAL METHODS + +=over 4 + =item setup This method is extended to also make calls to @@ -285,13 +358,98 @@ which will be saved in C if provided. This method will initialize the internal structure of the session, and is called by the C method if appropriate. +=item generate_session_id + +This method will return a string that can be used as a session ID. It is +supposed to be a reasonably random string with enough bits to prevent +collision. It basically takes C and hashes it using SHA-1, +MD5 or SHA-256, depending on the availibility of these modules. + +=item session_hash_seed + +This method is actually rather internal to generate_session_id, but should be +overridable in case you want to provide more random data. + +Currently it returns a concatenated string which contains: + +=over 4 + +=item * + +A counter + +=item * + +The current time + +=item * + +One value from C. + +=item * + +The stringified value of a newly allocated hash reference + +=item * + +The stringified value of the Catalyst context object + =back +In the hopes that those combined values are entropic enough for most uses. If +this is not the case you can replace C with e.g. + + sub session_hash_seed { + open my $fh, "<", "/dev/random"; + read $fh, my $bytes, 20; + close $fh; + return $bytes; + } + +Or even more directly, replace C: + + sub generate_session_id { + open my $fh, "<", "/dev/random"; + read $fh, my $bytes, 20; + close $fh; + return unpack("H*", $bytes); + } + +Also have a look at L and the various openssl bindings - these +modules provide APIs for cryptographically secure random data. + +=item dump_these + +See L - ammends the session data structure to the list of +dumped objects if session ID is defined. + +=back + +=head1 USING SESSIONS DURING PREPARE + +The earliest point in time at which you may use the session data is after +L's C has finished. + +State plugins must set $c->session ID before C, and during +C L will actually load the data from +the store. + + sub prepare_action { + my $c = shift; + + # don't touch $c->session yet! + + $c->NEXT::prepare_action( @_ ); + + $c->session; # this is OK + $c->sessionid; # this is also OK + } + =head1 CONFIGURATION - $c->config->{session} = { - expires => 1234, - }; + $c->config->{session} = { + expires => 1234, + }; All configuation parameters are provided in a hash reference under the C key in the configuration hash. @@ -340,6 +498,32 @@ This value is only populated of C is true in the configuration. =back +=head1 CAVEATS + +C could make your site inaccessible to users who are behind +load balanced proxies. Some ISPs may give a different IP to each request by the +same client due to this type of proxying. If addresses are verified these +users' sessions cannot persist. + +To let these users access your site you can either disable address verification +as a whole, or provide a checkbox in the login dialog that tells the server +that it's OK for the address of the client to change. When the server sees that +this box is checked it should delete the C<__address> sepcial key from the +session hash when the hash is first created. + +=head1 AUTHORS + +Andy Grundman +Christian Hansen +Yuval Kogman, C +Sebastian Riedel + +=head1 COPYRIGHT & LICNESE + + Copyright (c) 2005 the aforementioned authors. All rights + reserved. This program is free software; you can redistribute + it and/or modify it under the same terms as Perl itself. + =cut