Doc improvements for C::P::Session
[catagits/Catalyst-Plugin-Session.git] / lib / Catalyst / Plugin / Session.pm
index 2bf9d2d..a1d625c 100644 (file)
@@ -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<session expired>
 
 =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<session_delete_reason> if provided.
 This method will initialize the internal structure of the session, and is
 called by the C<session> 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<session_hash_seed> 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<rand>.
+
+=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<session_hash_seed> 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<generate_session_id>:
+
+    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<Crypt::Random> and the various openssl bindings - these
+modules provide APIs for cryptographically secure random data.
+
+=item dump_these
+
+See L<Catalyst/dump_these> - 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<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
+
+State plugins must set $c->session ID before C<prepare_action>, and during
+C<prepare_action> L<Catalyst::Plugin::Session> 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<session> key in the configuration hash.
@@ -340,6 +498,32 @@ This value is only populated of C<verify_address> is true in the configuration.
 
 =back
 
+=head1 CAVEATS
+
+C<verify_address> 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<nothingmuch@woobling.org>
+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