3 package Catalyst::Plugin::Session;
4 use base qw/Class::Accessor::Fast/;
10 use Catalyst::Exception ();
15 our $VERSION = "0.01";
18 __PACKAGE__->mk_accessors(qw/sessionid session_delete_reason/);
26 $c->check_session_plugin_requirements;
32 sub check_session_plugin_requirements {
35 unless ( $c->isa("Catalyst::Plugin::Session::State")
36 && $c->isa("Catalyst::Plugin::Session::Store") )
39 ( "The Session plugin requires both Session::State "
40 . "and Session::Store plugins to be used as well." );
43 Catalyst::Exception->throw($err);
50 my $cfg = ( $c->config->{session} ||= {} );
58 $c->NEXT::setup_session();
64 if ( $c->{session} ) {
66 # all sessions are extended at the end of the request
68 @{ $c->{session} }{qw/__updated __expires/} =
69 ( $now, $c->config->{session}{expires} + $now );
70 $c->store_session_data( $c->sessionid, $c->{session} );
73 $c->NEXT::finalize(@_);
79 my $ret = $c->NEXT::prepare_action;
81 my $sid = $c->sessionid || return;
83 $c->log->debug(qq/Found session "$sid"/) if $c->debug;
85 my $s = $c->{session} ||= $c->get_session_data($sid);
86 if ( !$s or $s->{__expires} < time ) {
89 $c->log->debug("Deleting session $sid (expired)") if $c->debug;
90 $c->delete_session("session expired");
94 if ( $c->config->{session}{verify_address}
95 && $c->{session}{__address}
96 && $c->{session}{__address} ne $c->request->address )
99 "Deleting session $sid due to address mismatch ("
100 . $c->{session}{__address} . " != "
101 . $c->request->address . ")",
103 $c->delete_session("address mismatch");
109 my ( $c, $msg ) = @_;
111 # delete the session data
112 my $sid = $c->sessionid;
113 $c->delete_session_data($sid);
115 # reset the values in the context object
116 $c->{session} = undef;
117 $c->sessionid(undef);
118 $c->session_delete_reason($msg);
124 return $c->{session} if $c->{session};
126 my $sid = $c->generate_session_id;
129 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
131 return $c->initialize_session_data;
134 sub initialize_session_data {
139 return $c->{session} = {
142 __expires => $now + $c->config->{session}{expires},
145 $c->config->{session}{verify_address}
146 ? ( __address => $c->request->address )
152 sub generate_session_id {
155 my $digest = $c->_find_digest();
156 $digest->add( $c->session_hash_seed() );
157 return $digest->hexdigest;
162 sub session_hash_seed {
165 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
170 sub _find_digest () {
172 $usable = List::Util::first(
174 eval { Digest->new($_) };
176 qw/SHA-1 MD5 SHA-256/
178 or Catalyst::Exception->throw(
179 "Could not find a suitable Digest module. Please install "
180 . "Digest::SHA1, Digest::SHA, or Digest::MD5" );
183 return Digest->new($usable);
194 Catalyst::Plugin::Session - Generic Session plugin - ties together server side
195 storage and client side tickets required to maintain session data.
199 use Catalyst qw/Session Session::Store::FastMmap Session::State::Cookie/;
201 sub add_item : Local {
202 my ( $self, $c ) = @_;
204 my $item_id = $c->req->param("item");
206 # $c->session is stored across requests, so
207 # other actions will see these values
209 push @{ $c->session->{items} }, $item_id;
211 $c->forward("MyView");
214 sub display_items : Local {
215 my ( $self, $c ) = @_;
217 # values in $c->session are restored
218 $c->stash->{items_to_display} =
219 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
221 $c->forward("MyView");
226 The Session plugin is the base of two related parts of functionality required
227 for session management in web applications.
229 The first part, the State, is getting the browser to repeat back a session key,
230 so that the web application can identify the client and logically string
231 several requests together into a session.
233 The second part, the Store, deals with the actual storage of information about
234 the client. This data is stored so that the it may be revived for every request
235 made by the same client.
237 This plugin links the two pieces together.
245 An accessor for the session ID value.
249 Returns a hash reference that might contain unserialized values from previous
250 requests in the same session, and whose modified value will be saved for future
253 This method will automatically create a new session and session ID if none
256 =item session_delete_reason
258 This accessor contains a string with the reason a session was deleted. Possible
275 This method is extended to also make calls to
276 C<check_session_plugin_requirements> and C<setup_session>.
278 =item check_session_plugin_requirements
280 This method ensures that a State and a Store plugin are also in use by the
285 This method populates C<< $c->config->{session} >> with the default values
286 listed in L</CONFIGURATION>.
290 This methoid is extended, and will restore session data and check it for
291 validity if a session id is defined. It assumes that the State plugin will
292 populate the C<sessionid> key beforehand.
296 This method is extended and will extend the expiry time, as well as persist the
297 session data if a session exists.
299 =item delete_session REASON
301 This method is used to invalidate a session. It takes an optional parameter
302 which will be saved in C<session_delete_reason> if provided.
304 =item initialize_session_data
306 This method will initialize the internal structure of the session, and is
307 called by the C<session> method if appropriate.
309 =item generate_session_id
311 This method will return a string that can be used as a session ID. It is
312 supposed to be a reasonably random string with enough bits to prevent
313 collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
314 MD5 or SHA-256, depending on the availibility of these modules.
316 =item session_hash_seed
318 This method is actually rather internal to generate_session_id, but should be
319 overridable in case you want to provide more random data.
321 Currently it returns a concatenated string which contains:
335 One value from C<rand>.
339 The stringified value of a newly allocated hash reference
343 The stringified value of the Catalyst context object
347 In the hopes that those combined values are entropic enough for most uses. If
348 this is not the case you can replace C<session_hash_seed> with e.g.
350 sub session_hash_seed {
351 open my $fh, "<", "/dev/random";
352 read $fh, my $bytes, 20;
357 Or even more directly, replace C<generate_session_id>:
359 sub generate_session_id {
360 open my $fh, "<", "/dev/random";
361 read $fh, my $bytes, 20;
363 return unpack("H*", $bytes);
366 Also have a look at L<Crypt::Random> and the various openssl bindings - these
367 modules provide APIs for cryptographically secure random data.
373 $c->config->{session} = {
377 All configuation parameters are provided in a hash reference under the
378 C<session> key in the configuration hash.
384 The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
389 When false, C<< $c->request->address >> will be checked at prepare time. If it
390 is not the same as the address that initiated the session, the session is
397 The hash reference returned by C<< $c->session >> contains several keys which
398 are automatically set:
404 A timestamp whose value is the last second when the session is still valid. If
405 a session is restored, and __expires is less than the current time, the session
410 The last time a session was saved. This is the value of
411 C<< $c->{session}{__expires} - $c->config->{session}{expires} >>.
415 The time when the session was first created.
419 The value of C<< $c->request->address >> at the time the session was created.
420 This value is only populated of C<verify_address> is true in the configuration.
426 C<verify_address> could make your site inaccessible to users who are behind
427 load balanced proxies. Some ISPs may give a different IP to each request by the
428 same client due to this type of proxying. If addresses are verified these
429 users' sessions cannot persist.
431 To let these users access your site you can either disable address verification
432 as a whole, or provide a checkbox in the login dialog that tells the server
433 that it's OK for the address of the client to change. When the server sees that
434 this box is checked it should delete the C<__address> sepcial key from the
435 session hash when the hash is first created.