3 package Catalyst::Plugin::Session;
4 use base qw/Class::Accessor::Fast/;
10 use Catalyst::Exception ();
14 our $VERSION = "0.02";
17 __PACKAGE__->mk_accessors(qw/_sessionid _session session_delete_reason/);
25 $c->check_session_plugin_requirements;
31 sub check_session_plugin_requirements {
34 unless ( $c->isa("Catalyst::Plugin::Session::State")
35 && $c->isa("Catalyst::Plugin::Session::Store") )
38 ( "The Session plugin requires both Session::State "
39 . "and Session::Store plugins to be used as well." );
42 Catalyst::Exception->throw($err);
49 my $cfg = ( $c->config->{session} ||= {} );
57 $c->NEXT::setup_session();
63 if ( my $session_data = $c->_session ) {
65 # all sessions are extended at the end of the request
67 @{ $session_data }{qw/__updated __expires/} =
68 ( $now, $c->config->{session}{expires} + $now );
69 $c->store_session_data( $c->sessionid, $session_data );
72 $c->NEXT::finalize(@_);
78 if ( my $sid = $c->sessionid ) {
79 no warnings 'uninitialized'; # ne __address
81 my $session_data = $c->_session || $c->_session( $c->get_session_data($sid) );
82 if ( !$session_data or $session_data->{__expires} < time ) {
85 $c->log->debug("Deleting session $sid (expired)") if $c->debug;
86 $c->delete_session("session expired");
88 elsif ($c->config->{session}{verify_address}
89 && $session_data->{__address} ne $c->request->address )
92 "Deleting session $sid due to address mismatch ("
93 . $session_data->{__address} . " != "
94 . $c->request->address . ")",
96 $c->delete_session("address mismatch");
99 $c->log->debug(qq/Restored session "$sid"/) if $c->debug;
103 $c->NEXT::prepare_action(@_);
107 my ( $c, $msg ) = @_;
109 # delete the session data
110 my $sid = $c->sessionid;
111 $c->delete_session_data($sid);
113 # reset the values in the context object
115 $c->_sessionid(undef);
116 $c->session_delete_reason($msg);
123 if ( $c->validate_session_id( my $sid = shift ) ) {
124 return $c->_sessionid( $sid );
126 my $err = "Tried to set invalid session ID '$sid'";
127 $c->log->error( $err );
128 Catalyst::Exception->throw( $err );
132 return $c->_sessionid;
135 sub validate_session_id {
136 my ( $c, $sid ) = @_;
138 $sid =~ /^[a-f\d]+$/i;
145 my $sid = $c->generate_session_id;
148 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
150 $c->initialize_session_data;
154 sub initialize_session_data {
159 return $c->_session({
162 __expires => $now + $c->config->{session}{expires},
165 $c->config->{session}{verify_address}
166 ? ( __address => $c->request->address )
172 sub generate_session_id {
175 my $digest = $c->_find_digest();
176 $digest->add( $c->session_hash_seed() );
177 return $digest->hexdigest;
182 sub session_hash_seed {
185 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
190 sub _find_digest () {
192 foreach my $alg (qw/SHA-1 MD5 SHA-256/) {
194 my $obj = Digest->new($alg);
200 or Catalyst::Exception->throw(
201 "Could not find a suitable Digest module. Please install "
202 . "Digest::SHA1, Digest::SHA, or Digest::MD5" );
205 return Digest->new($usable);
212 $c->NEXT::dump_these(),
215 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
228 Catalyst::Plugin::Session - Generic Session plugin - ties together server side
229 storage and client side state required to maintain session data.
233 # To get sessions to "just work", all you need to do is use these plugins:
237 Session::Store::FastMmap
238 Session::State::Cookie
241 # you can replace Store::FastMmap with Store::File - both have sensible
242 # default configurations (see their docs for details)
244 # more complicated backends are available for other scenarios (DBI storage,
248 # after you've loaded the plugins you can save session data
249 # For example, if you are writing a shopping cart, it could be implemented
252 sub add_item : Local {
253 my ( $self, $c ) = @_;
255 my $item_id = $c->req->param("item");
257 # $c->session is a hash ref, a bit like $c->stash
258 # the difference is that it' preserved across requests
260 push @{ $c->session->{items} }, $item_id;
262 $c->forward("MyView");
265 sub display_items : Local {
266 my ( $self, $c ) = @_;
268 # values in $c->session are restored
269 $c->stash->{items_to_display} =
270 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
272 $c->forward("MyView");
277 The Session plugin is the base of two related parts of functionality required
278 for session management in web applications.
280 The first part, the State, is getting the browser to repeat back a session key,
281 so that the web application can identify the client and logically string
282 several requests together into a session.
284 The second part, the Store, deals with the actual storage of information about
285 the client. This data is stored so that the it may be revived for every request
286 made by the same client.
288 This plugin links the two pieces together.
290 =head1 RECCOMENDED BACKENDS
294 =item Session::State::Cookie
296 The only really sane way to do state is using cookies.
298 =item Session::Store::File
300 A portable backend, based on Cache::File.
302 =item Session::Store::FastMmap
304 A fast and flexible backend, based on Cache::FastMmap.
314 An accessor for the session ID value.
318 Returns a hash reference that might contain unserialized values from previous
319 requests in the same session, and whose modified value will be saved for future
322 This method will automatically create a new session and session ID if none
325 =item session_delete_reason
327 This accessor contains a string with the reason a session was deleted. Possible
344 =item INTERNAL METHODS
350 This method is extended to also make calls to
351 C<check_session_plugin_requirements> and C<setup_session>.
353 =item check_session_plugin_requirements
355 This method ensures that a State and a Store plugin are also in use by the
360 This method populates C<< $c->config->{session} >> with the default values
361 listed in L</CONFIGURATION>.
365 This methoid is extended, and will restore session data and check it for
366 validity if a session id is defined. It assumes that the State plugin will
367 populate the C<sessionid> key beforehand.
371 This method is extended and will extend the expiry time, as well as persist the
372 session data if a session exists.
374 =item delete_session REASON
376 This method is used to invalidate a session. It takes an optional parameter
377 which will be saved in C<session_delete_reason> if provided.
379 =item initialize_session_data
381 This method will initialize the internal structure of the session, and is
382 called by the C<session> method if appropriate.
384 =item generate_session_id
386 This method will return a string that can be used as a session ID. It is
387 supposed to be a reasonably random string with enough bits to prevent
388 collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
389 MD5 or SHA-256, depending on the availibility of these modules.
391 =item session_hash_seed
393 This method is actually rather internal to generate_session_id, but should be
394 overridable in case you want to provide more random data.
396 Currently it returns a concatenated string which contains:
398 =item validate_session_id SID
400 Make sure a session ID is of the right format.
402 This currently ensures that the session ID string is any amount of case
403 insensitive hexadecimal characters.
417 One value from C<rand>.
421 The stringified value of a newly allocated hash reference
425 The stringified value of the Catalyst context object
429 In the hopes that those combined values are entropic enough for most uses. If
430 this is not the case you can replace C<session_hash_seed> with e.g.
432 sub session_hash_seed {
433 open my $fh, "<", "/dev/random";
434 read $fh, my $bytes, 20;
439 Or even more directly, replace C<generate_session_id>:
441 sub generate_session_id {
442 open my $fh, "<", "/dev/random";
443 read $fh, my $bytes, 20;
445 return unpack("H*", $bytes);
448 Also have a look at L<Crypt::Random> and the various openssl bindings - these
449 modules provide APIs for cryptographically secure random data.
453 See L<Catalyst/dump_these> - ammends the session data structure to the list of
454 dumped objects if session ID is defined.
458 =head1 USING SESSIONS DURING PREPARE
460 The earliest point in time at which you may use the session data is after
461 L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
463 State plugins must set $c->session ID before C<prepare_action>, and during
464 C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
470 # don't touch $c->session yet!
472 $c->NEXT::prepare_action( @_ );
474 $c->session; # this is OK
475 $c->sessionid; # this is also OK
480 $c->config->{session} = {
484 All configuation parameters are provided in a hash reference under the
485 C<session> key in the configuration hash.
491 The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
496 When true, C<<$c->request->address>> will be checked at prepare time. If it is
497 not the same as the address that initiated the session, the session is deleted.
503 The hash reference returned by C<< $c->session >> contains several keys which
504 are automatically set:
510 A timestamp whose value is the last second when the session is still valid. If
511 a session is restored, and __expires is less than the current time, the session
516 The last time a session was saved. This is the value of
517 C<< $c->session->{__expires} - $c->config->session->{expires} >>.
521 The time when the session was first created.
525 The value of C<< $c->request->address >> at the time the session was created.
526 This value is only populated if C<verify_address> is true in the configuration.
532 C<verify_address> could make your site inaccessible to users who are behind
533 load balanced proxies. Some ISPs may give a different IP to each request by the
534 same client due to this type of proxying. If addresses are verified these
535 users' sessions cannot persist.
537 To let these users access your site you can either disable address verification
538 as a whole, or provide a checkbox in the login dialog that tells the server
539 that it's OK for the address of the client to change. When the server sees that
540 this box is checked it should delete the C<__address> sepcial key from the
541 session hash when the hash is first created.
547 Yuval Kogman, C<nothingmuch@woobling.org>
550 =head1 COPYRIGHT & LICENSE
552 Copyright (c) 2005 the aforementioned authors. All rights
553 reserved. This program is free software; you can redistribute
554 it and/or modify it under the same terms as Perl itself.