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_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 ( $c->{session} ) {
65 # all sessions are extended at the end of the request
67 @{ $c->{session} }{qw/__updated __expires/} =
68 ( $now, $c->config->{session}{expires} + $now );
69 $c->store_session_data( $c->sessionid, $c->{session} );
72 $c->NEXT::finalize(@_);
78 if ( my $sid = $c->sessionid ) {
79 my $s = $c->{session} ||= $c->get_session_data($sid);
80 if ( !$s or $s->{__expires} < time ) {
83 $c->log->debug("Deleting session $sid (expired)") if $c->debug;
84 $c->delete_session("session expired");
86 elsif ($c->config->{session}{verify_address}
87 && $c->{session}{__address}
88 && $c->{session}{__address} ne $c->request->address )
91 "Deleting session $sid due to address mismatch ("
92 . $c->{session}{__address} . " != "
93 . $c->request->address . ")",
95 $c->delete_session("address mismatch");
98 $c->log->debug(qq/Restored session "$sid"/) if $c->debug;
102 $c->NEXT::prepare_action(@_);
106 my ( $c, $msg ) = @_;
108 # delete the session data
109 my $sid = $c->sessionid;
110 $c->delete_session_data($sid);
112 # reset the values in the context object
113 $c->{session} = undef;
114 $c->sessionid(undef);
115 $c->session_delete_reason($msg);
121 return $c->{session} if $c->{session};
123 my $sid = $c->generate_session_id;
126 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
128 return $c->initialize_session_data;
131 sub initialize_session_data {
136 return $c->{session} = {
139 __expires => $now + $c->config->{session}{expires},
142 $c->config->{session}{verify_address}
143 ? ( __address => $c->request->address )
149 sub generate_session_id {
152 my $digest = $c->_find_digest();
153 $digest->add( $c->session_hash_seed() );
154 return $digest->hexdigest;
159 sub session_hash_seed {
162 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
167 sub _find_digest () {
169 foreach my $alg (qw/SHA-1 MD5 SHA-256/) {
171 my $obj = Digest->new($alg);
177 or Catalyst::Exception->throw(
178 "Could not find a suitable Digest module. Please install "
179 . "Digest::SHA1, Digest::SHA, or Digest::MD5" );
182 return Digest->new($usable);
189 $c->NEXT::dump_these(),
192 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
205 Catalyst::Plugin::Session - Generic Session plugin - ties together server side
206 storage and client side state required to maintain session data.
210 # To get sessions to "just work", all you need to do is use these plugins:
214 Session::Store::FastMmap
215 Session::State::Cookie
218 # you can replace Store::FastMmap with Store::File - both have sensible
219 # default configurations (see their docs for details)
221 # more complicated backends are available for other scenarios (DBI storage,
225 # after you've loaded the plugins you can save session data
226 # For example, if you are writing a shopping cart, it could be implemented
229 sub add_item : Local {
230 my ( $self, $c ) = @_;
232 my $item_id = $c->req->param("item");
234 # $c->session is a hash ref, a bit like $c->stash
235 # the difference is that it' preserved across requests
237 push @{ $c->session->{items} }, $item_id;
239 $c->forward("MyView");
242 sub display_items : Local {
243 my ( $self, $c ) = @_;
245 # values in $c->session are restored
246 $c->stash->{items_to_display} =
247 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
249 $c->forward("MyView");
254 The Session plugin is the base of two related parts of functionality required
255 for session management in web applications.
257 The first part, the State, is getting the browser to repeat back a session key,
258 so that the web application can identify the client and logically string
259 several requests together into a session.
261 The second part, the Store, deals with the actual storage of information about
262 the client. This data is stored so that the it may be revived for every request
263 made by the same client.
265 This plugin links the two pieces together.
267 =head1 RECCOMENDED BACKENDS
271 =item Session::State::Cookie
273 The only really sane way to do state is using cookies.
275 =item Session::Store::File
277 A portable backend, based on Cache::File.
279 =item Session::Store::FastMmap
281 A fast and flexible backend, based on Cache::FastMmap.
291 An accessor for the session ID value.
295 Returns a hash reference that might contain unserialized values from previous
296 requests in the same session, and whose modified value will be saved for future
299 This method will automatically create a new session and session ID if none
302 =item session_delete_reason
304 This accessor contains a string with the reason a session was deleted. Possible
321 =item INTERNAL METHODS
327 This method is extended to also make calls to
328 C<check_session_plugin_requirements> and C<setup_session>.
330 =item check_session_plugin_requirements
332 This method ensures that a State and a Store plugin are also in use by the
337 This method populates C<< $c->config->{session} >> with the default values
338 listed in L</CONFIGURATION>.
342 This methoid is extended, and will restore session data and check it for
343 validity if a session id is defined. It assumes that the State plugin will
344 populate the C<sessionid> key beforehand.
348 This method is extended and will extend the expiry time, as well as persist the
349 session data if a session exists.
351 =item delete_session REASON
353 This method is used to invalidate a session. It takes an optional parameter
354 which will be saved in C<session_delete_reason> if provided.
356 =item initialize_session_data
358 This method will initialize the internal structure of the session, and is
359 called by the C<session> method if appropriate.
361 =item generate_session_id
363 This method will return a string that can be used as a session ID. It is
364 supposed to be a reasonably random string with enough bits to prevent
365 collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
366 MD5 or SHA-256, depending on the availibility of these modules.
368 =item session_hash_seed
370 This method is actually rather internal to generate_session_id, but should be
371 overridable in case you want to provide more random data.
373 Currently it returns a concatenated string which contains:
387 One value from C<rand>.
391 The stringified value of a newly allocated hash reference
395 The stringified value of the Catalyst context object
399 In the hopes that those combined values are entropic enough for most uses. If
400 this is not the case you can replace C<session_hash_seed> with e.g.
402 sub session_hash_seed {
403 open my $fh, "<", "/dev/random";
404 read $fh, my $bytes, 20;
409 Or even more directly, replace C<generate_session_id>:
411 sub generate_session_id {
412 open my $fh, "<", "/dev/random";
413 read $fh, my $bytes, 20;
415 return unpack("H*", $bytes);
418 Also have a look at L<Crypt::Random> and the various openssl bindings - these
419 modules provide APIs for cryptographically secure random data.
423 See L<Catalyst/dump_these> - ammends the session data structure to the list of
424 dumped objects if session ID is defined.
428 =head1 USING SESSIONS DURING PREPARE
430 The earliest point in time at which you may use the session data is after
431 L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
433 State plugins must set $c->session ID before C<prepare_action>, and during
434 C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
440 # don't touch $c->session yet!
442 $c->NEXT::prepare_action( @_ );
444 $c->session; # this is OK
445 $c->sessionid; # this is also OK
450 $c->config->{session} = {
454 All configuation parameters are provided in a hash reference under the
455 C<session> key in the configuration hash.
461 The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
466 When false, C<< $c->request->address >> will be checked at prepare time. If it
467 is not the same as the address that initiated the session, the session is
474 The hash reference returned by C<< $c->session >> contains several keys which
475 are automatically set:
481 A timestamp whose value is the last second when the session is still valid. If
482 a session is restored, and __expires is less than the current time, the session
487 The last time a session was saved. This is the value of
488 C<< $c->{session}{__expires} - $c->config->{session}{expires} >>.
492 The time when the session was first created.
496 The value of C<< $c->request->address >> at the time the session was created.
497 This value is only populated of C<verify_address> is true in the configuration.
503 C<verify_address> could make your site inaccessible to users who are behind
504 load balanced proxies. Some ISPs may give a different IP to each request by the
505 same client due to this type of proxying. If addresses are verified these
506 users' sessions cannot persist.
508 To let these users access your site you can either disable address verification
509 as a whole, or provide a checkbox in the login dialog that tells the server
510 that it's OK for the address of the client to change. When the server sees that
511 this box is checked it should delete the C<__address> sepcial key from the
512 session hash when the hash is first created.
518 Yuval Kogman, C<nothingmuch@woobling.org>
521 =head1 COPYRIGHT & LICENSE
523 Copyright (c) 2005 the aforementioned authors. All rights
524 reserved. This program is free software; you can redistribute
525 it and/or modify it under the same terms as Perl itself.