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(@_);
80 $c->NEXT::prepare_action(@_);
86 if ( my $sid = $c->sessionid ) {
87 no warnings 'uninitialized'; # ne __address
89 my $session_data = $c->_session || $c->_session( $c->get_session_data($sid) );
90 if ( !$session_data or $session_data->{__expires} < time ) {
93 $c->log->debug("Deleting session $sid (expired)") if $c->debug;
94 $c->delete_session("session expired");
96 elsif ($c->config->{session}{verify_address}
97 && $session_data->{__address} ne $c->request->address )
100 "Deleting session $sid due to address mismatch ("
101 . $session_data->{__address} . " != "
102 . $c->request->address . ")",
104 $c->delete_session("address mismatch");
107 $c->log->debug(qq/Restored session "$sid"/) if $c->debug;
110 $c->_expire_ession_keys;
115 sub _expire_ession_keys {
116 my ( $c, $data ) = @_;
120 my $expiry = ($data || $c->_session || {})->{__expire_keys} || {};
121 foreach my $key (grep { $expiry->{$_} < $now } keys %$expiry ) {
122 delete $c->_session->{$key};
123 delete $expiry->{$key};
128 my ( $c, $msg ) = @_;
130 # delete the session data
131 my $sid = $c->sessionid;
132 $c->delete_session_data($sid);
134 # reset the values in the context object
136 $c->_sessionid(undef);
137 $c->session_delete_reason($msg);
144 if ( $c->validate_session_id( my $sid = shift ) ) {
145 return $c->_sessionid( $sid );
147 my $err = "Tried to set invalid session ID '$sid'";
148 $c->log->error( $err );
149 Catalyst::Exception->throw( $err );
153 return $c->_sessionid;
156 sub validate_session_id {
157 my ( $c, $sid ) = @_;
159 $sid =~ /^[a-f\d]+$/i;
166 my $sid = $c->generate_session_id;
169 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
171 $c->initialize_session_data;
175 sub session_expire_key {
176 my ( $c, %keys ) = @_;
179 @{ $c->session->{__expire_keys} }{keys %keys} = map { $now + $_ } values %keys;
182 sub initialize_session_data {
187 return $c->_session({
190 __expires => $now + $c->config->{session}{expires},
193 $c->config->{session}{verify_address}
194 ? ( __address => $c->request->address )
200 sub generate_session_id {
203 my $digest = $c->_find_digest();
204 $digest->add( $c->session_hash_seed() );
205 return $digest->hexdigest;
210 sub session_hash_seed {
213 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
218 sub _find_digest () {
220 foreach my $alg (qw/SHA-1 MD5 SHA-256/) {
222 my $obj = Digest->new($alg);
228 or Catalyst::Exception->throw(
229 "Could not find a suitable Digest module. Please install "
230 . "Digest::SHA1, Digest::SHA, or Digest::MD5" );
233 return Digest->new($usable);
240 $c->NEXT::dump_these(),
243 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
256 Catalyst::Plugin::Session - Generic Session plugin - ties together server side
257 storage and client side state required to maintain session data.
261 # To get sessions to "just work", all you need to do is use these plugins:
265 Session::Store::FastMmap
266 Session::State::Cookie
269 # you can replace Store::FastMmap with Store::File - both have sensible
270 # default configurations (see their docs for details)
272 # more complicated backends are available for other scenarios (DBI storage,
276 # after you've loaded the plugins you can save session data
277 # For example, if you are writing a shopping cart, it could be implemented
280 sub add_item : Local {
281 my ( $self, $c ) = @_;
283 my $item_id = $c->req->param("item");
285 # $c->session is a hash ref, a bit like $c->stash
286 # the difference is that it' preserved across requests
288 push @{ $c->session->{items} }, $item_id;
290 $c->forward("MyView");
293 sub display_items : Local {
294 my ( $self, $c ) = @_;
296 # values in $c->session are restored
297 $c->stash->{items_to_display} =
298 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
300 $c->forward("MyView");
305 The Session plugin is the base of two related parts of functionality required
306 for session management in web applications.
308 The first part, the State, is getting the browser to repeat back a session key,
309 so that the web application can identify the client and logically string
310 several requests together into a session.
312 The second part, the Store, deals with the actual storage of information about
313 the client. This data is stored so that the it may be revived for every request
314 made by the same client.
316 This plugin links the two pieces together.
318 =head1 RECCOMENDED BACKENDS
322 =item Session::State::Cookie
324 The only really sane way to do state is using cookies.
326 =item Session::Store::File
328 A portable backend, based on Cache::File.
330 =item Session::Store::FastMmap
332 A fast and flexible backend, based on Cache::FastMmap.
342 An accessor for the session ID value.
346 Returns a hash reference that might contain unserialized values from previous
347 requests in the same session, and whose modified value will be saved for future
350 This method will automatically create a new session and session ID if none
353 =item session_delete_reason
355 This accessor contains a string with the reason a session was deleted. Possible
370 =item session_expire_key $key, $ttl
372 Mark a key to expire at a certain time (only useful when shorter than the
373 expiry time for the whole session).
377 __PACKAGE__->config->{session}{expires} = 1000000000000; # forever
381 $c->session_expire_key( __user => 3600 );
383 Will make the session data survive, but the user will still be logged out after
386 Note that these values are not auto extended.
390 =item INTERNAL METHODS
396 This method is extended to also make calls to
397 C<check_session_plugin_requirements> and C<setup_session>.
399 =item check_session_plugin_requirements
401 This method ensures that a State and a Store plugin are also in use by the
406 This method populates C<< $c->config->{session} >> with the default values
407 listed in L</CONFIGURATION>.
411 This methoid is extended, and will restore session data and check it for
412 validity if a session id is defined. It assumes that the State plugin will
413 populate the C<sessionid> key beforehand.
417 This method is extended and will extend the expiry time, as well as persist the
418 session data if a session exists.
420 =item delete_session REASON
422 This method is used to invalidate a session. It takes an optional parameter
423 which will be saved in C<session_delete_reason> if provided.
425 =item initialize_session_data
427 This method will initialize the internal structure of the session, and is
428 called by the C<session> method if appropriate.
430 =item generate_session_id
432 This method will return a string that can be used as a session ID. It is
433 supposed to be a reasonably random string with enough bits to prevent
434 collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
435 MD5 or SHA-256, depending on the availibility of these modules.
437 =item session_hash_seed
439 This method is actually rather internal to generate_session_id, but should be
440 overridable in case you want to provide more random data.
442 Currently it returns a concatenated string which contains:
444 =item validate_session_id SID
446 Make sure a session ID is of the right format.
448 This currently ensures that the session ID string is any amount of case
449 insensitive hexadecimal characters.
463 One value from C<rand>.
467 The stringified value of a newly allocated hash reference
471 The stringified value of the Catalyst context object
475 In the hopes that those combined values are entropic enough for most uses. If
476 this is not the case you can replace C<session_hash_seed> with e.g.
478 sub session_hash_seed {
479 open my $fh, "<", "/dev/random";
480 read $fh, my $bytes, 20;
485 Or even more directly, replace C<generate_session_id>:
487 sub generate_session_id {
488 open my $fh, "<", "/dev/random";
489 read $fh, my $bytes, 20;
491 return unpack("H*", $bytes);
494 Also have a look at L<Crypt::Random> and the various openssl bindings - these
495 modules provide APIs for cryptographically secure random data.
499 See L<Catalyst/dump_these> - ammends the session data structure to the list of
500 dumped objects if session ID is defined.
504 =head1 USING SESSIONS DURING PREPARE
506 The earliest point in time at which you may use the session data is after
507 L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
509 State plugins must set $c->session ID before C<prepare_action>, and during
510 C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
516 # don't touch $c->session yet!
518 $c->NEXT::prepare_action( @_ );
520 $c->session; # this is OK
521 $c->sessionid; # this is also OK
526 $c->config->{session} = {
530 All configuation parameters are provided in a hash reference under the
531 C<session> key in the configuration hash.
537 The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
542 When true, C<<$c->request->address>> will be checked at prepare time. If it is
543 not the same as the address that initiated the session, the session is deleted.
549 The hash reference returned by C<< $c->session >> contains several keys which
550 are automatically set:
556 A timestamp whose value is the last second when the session is still valid. If
557 a session is restored, and __expires is less than the current time, the session
562 The last time a session was saved. This is the value of
563 C<< $c->session->{__expires} - $c->config->session->{expires} >>.
567 The time when the session was first created.
571 The value of C<< $c->request->address >> at the time the session was created.
572 This value is only populated if C<verify_address> is true in the configuration.
578 C<verify_address> could make your site inaccessible to users who are behind
579 load balanced proxies. Some ISPs may give a different IP to each request by the
580 same client due to this type of proxying. If addresses are verified these
581 users' sessions cannot persist.
583 To let these users access your site you can either disable address verification
584 as a whole, or provide a checkbox in the login dialog that tells the server
585 that it's OK for the address of the client to change. When the server sees that
586 this box is checked it should delete the C<__address> sepcial key from the
587 session hash when the hash is first created.
595 =item Christian Hansen
597 =item Yuval Kogman, C<nothingmuch@woobling.org> (current maintainer)
599 =item Sebastian Riedel
603 And countless other contributers from #catalyst. Thanks guys!
605 =head1 COPYRIGHT & LICENSE
607 Copyright (c) 2005 the aforementioned authors. All rights
608 reserved. This program is free software; you can redistribute
609 it and/or modify it under the same terms as Perl itself.