From: Tatsuhiko Miyagawa Date: Sat, 9 Jan 2010 16:17:49 +0000 (-0800) Subject: Added Session::Cookie middleware which utilizes Cookie as both state and store. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FWeb-Session.git;a=commitdiff_plain;h=d326e755594b17370ddbba2a2cafc14e9d391a66 Added Session::Cookie middleware which utilizes Cookie as both state and store. Fixes gh-1 --- diff --git a/lib/Plack/Middleware/Session/Cookie.pm b/lib/Plack/Middleware/Session/Cookie.pm new file mode 100644 index 0000000..7fa4bd7 --- /dev/null +++ b/lib/Plack/Middleware/Session/Cookie.pm @@ -0,0 +1,119 @@ +package Plack::Middleware::Session::Cookie; +use strict; +use parent qw(Plack::Middleware::Session); + +use Plack::Util::Accessor qw(secret session_key domain expires path secure); + +use Digest::HMAC_SHA1; +use MIME::Base64 (); +use Storable (); +use Time::HiRes; +use Plack::Util; + +use Plack::Session::State::Cookie; + +sub prepare_app { + my $self = shift; + + $self->session_class("Plack::Session"); + $self->session_key("plack_session") unless $self->session_key; + + my $state_cookie = Plack::Session::State::Cookie->new; + for my $attr (qw(session_key path domain expires secure)) { + $state_cookie->$attr($self->$attr); + } + + my $state = Plack::Util::inline_object + generate => sub { $self->_serialize({}) }, + extract => sub { + my $cookie = $state_cookie->get_session_id(@_) or return; + + my($time, $b64, $sig) = split /:/, $cookie, 3; + $self->sig($b64) eq $sig or return; + + return $cookie; + }, + expire_session_id => sub { $state_cookie->expire_session_id(@_) }, + finalize => sub { + my($id, $response, $session) = @_; + my $cookie = $self->_serialize($session->dump); + $state_cookie->finalize($cookie, $response); + }; + + my $store = Plack::Util::inline_object + fetch => sub { + my $id = shift; + my($time, $b64, $sig) = split /:/, $id, 3; + Storable::thaw(MIME::Base64::decode($b64)); + }, + store => sub { }, + cleanup => sub { }; + + $self->state($state); + $self->store($store); +} + +sub _serialize { + my($self, $session) = @_; + + my $now = Time::HiRes::gettimeofday; + my $b64 = MIME::Base64::encode( Storable::freeze($session), '' ); + join ":", $now, $b64, $self->sig($b64); +} + +sub sig { + my($self, $b64) = @_; + return '.' unless $self->secret; + Digest::HMAC_SHA1::hmac_sha1_hex($b64, $self->secret); +} + +1; + +__END__ + +=head1 NAME + +Plack::Middleware::Session::Cookie - Session middleware that saves session data in the cookie + +=head1 SYNOPSIS + + enable "Session::Cookie"; + +=head1 DESCRIPTION + +This middleware component allows you to use the cookie as a sole +cookie state and store, without any server side storage to do the +session management. This middleware utilizes its own state and store +automatically for you, so you can't override the objects. + +=head1 CONFIGURATIONS + +This middleware is a subclass of L and +accepts most configuration of the parent class. In addition, following +options are accepted. + +=over 4 + +=item secret + +Server side secret to sign the session data using HMAC SHA1. Defaults +to nothing (i.e. do not sign) but B to set your +own secret string. + +=item session_key, domain, expires, path, secure + +Accessors for the cookie attribuets. See +L for these options. + +=back + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=head1 SEE ALSO + +Rack::Session::Cookie L + +=cut + diff --git a/t/013_cookiestore.t b/t/013_cookiestore.t new file mode 100644 index 0000000..2e7f753 --- /dev/null +++ b/t/013_cookiestore.t @@ -0,0 +1,47 @@ +use strict; +use Test::More; +use Test::Requires qw(Digest::HMAC_SHA1); +use Plack::Test; +use Plack::Middleware::Session::Cookie; +use HTTP::Request::Common; +use LWP::UserAgent; +use HTTP::Cookies; + +$Plack::Test::Impl = 'Server'; + +my $app = sub { + my $env = shift; + my $session = $env->{'psgix.session'}; + + my $counter = $session->get('counter') || 0; + if ($counter >= 2) { + $session->expire; + } else { + $session->set(counter => $counter + 1); + } + + return [ 200, [], [ "counter=$counter" ] ]; +}; + +$app = Plack::Middleware::Session::Cookie->wrap($app, secret => "foobar"); + +my $ua = LWP::UserAgent->new; +$ua->cookie_jar( HTTP::Cookies->new ); + +test_psgi ua => $ua, app => $app, client => sub { + my $cb = shift; + + my $res = $cb->(GET "/"); + is $res->content, "counter=0"; + + $res = $cb->(GET "/"); + is $res->content, "counter=1"; + + $res = $cb->(GET "/"); + is $res->content, "counter=2"; + + my $res = $cb->(GET "/"); + is $res->content, "counter=0"; +}; + +done_testing;