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);
190 $c->NEXT::dump_these(),
193 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
206 Catalyst::Plugin::Session - Generic Session plugin - ties together server side
207 storage and client side tickets required to maintain session data.
211 use Catalyst qw/Session Session::Store::FastMmap Session::State::Cookie/;
213 sub add_item : Local {
214 my ( $self, $c ) = @_;
216 my $item_id = $c->req->param("item");
218 # $c->session is stored across requests, so
219 # other actions will see these values
221 push @{ $c->session->{items} }, $item_id;
223 $c->forward("MyView");
226 sub display_items : Local {
227 my ( $self, $c ) = @_;
229 # values in $c->session are restored
230 $c->stash->{items_to_display} =
231 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
233 $c->forward("MyView");
238 The Session plugin is the base of two related parts of functionality required
239 for session management in web applications.
241 The first part, the State, is getting the browser to repeat back a session key,
242 so that the web application can identify the client and logically string
243 several requests together into a session.
245 The second part, the Store, deals with the actual storage of information about
246 the client. This data is stored so that the it may be revived for every request
247 made by the same client.
249 This plugin links the two pieces together.
257 An accessor for the session ID value.
261 Returns a hash reference that might contain unserialized values from previous
262 requests in the same session, and whose modified value will be saved for future
265 This method will automatically create a new session and session ID if none
268 =item session_delete_reason
270 This accessor contains a string with the reason a session was deleted. Possible
287 This method is extended to also make calls to
288 C<check_session_plugin_requirements> and C<setup_session>.
290 =item check_session_plugin_requirements
292 This method ensures that a State and a Store plugin are also in use by the
297 This method populates C<< $c->config->{session} >> with the default values
298 listed in L</CONFIGURATION>.
302 This methoid is extended, and will restore session data and check it for
303 validity if a session id is defined. It assumes that the State plugin will
304 populate the C<sessionid> key beforehand.
308 This method is extended and will extend the expiry time, as well as persist the
309 session data if a session exists.
311 =item delete_session REASON
313 This method is used to invalidate a session. It takes an optional parameter
314 which will be saved in C<session_delete_reason> if provided.
316 =item initialize_session_data
318 This method will initialize the internal structure of the session, and is
319 called by the C<session> method if appropriate.
321 =item generate_session_id
323 This method will return a string that can be used as a session ID. It is
324 supposed to be a reasonably random string with enough bits to prevent
325 collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
326 MD5 or SHA-256, depending on the availibility of these modules.
328 =item session_hash_seed
330 This method is actually rather internal to generate_session_id, but should be
331 overridable in case you want to provide more random data.
333 Currently it returns a concatenated string which contains:
347 One value from C<rand>.
351 The stringified value of a newly allocated hash reference
355 The stringified value of the Catalyst context object
359 In the hopes that those combined values are entropic enough for most uses. If
360 this is not the case you can replace C<session_hash_seed> with e.g.
362 sub session_hash_seed {
363 open my $fh, "<", "/dev/random";
364 read $fh, my $bytes, 20;
369 Or even more directly, replace C<generate_session_id>:
371 sub generate_session_id {
372 open my $fh, "<", "/dev/random";
373 read $fh, my $bytes, 20;
375 return unpack("H*", $bytes);
378 Also have a look at L<Crypt::Random> and the various openssl bindings - these
379 modules provide APIs for cryptographically secure random data.
383 See L<Catalyst/dump_these> - ammends the session data structure to the list of
384 dumped objects if session ID is defined.
390 $c->config->{session} = {
394 All configuation parameters are provided in a hash reference under the
395 C<session> key in the configuration hash.
401 The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
406 When false, C<< $c->request->address >> will be checked at prepare time. If it
407 is not the same as the address that initiated the session, the session is
414 The hash reference returned by C<< $c->session >> contains several keys which
415 are automatically set:
421 A timestamp whose value is the last second when the session is still valid. If
422 a session is restored, and __expires is less than the current time, the session
427 The last time a session was saved. This is the value of
428 C<< $c->{session}{__expires} - $c->config->{session}{expires} >>.
432 The time when the session was first created.
436 The value of C<< $c->request->address >> at the time the session was created.
437 This value is only populated of C<verify_address> is true in the configuration.
443 C<verify_address> could make your site inaccessible to users who are behind
444 load balanced proxies. Some ISPs may give a different IP to each request by the
445 same client due to this type of proxying. If addresses are verified these
446 users' sessions cannot persist.
448 To let these users access your site you can either disable address verification
449 as a whole, or provide a checkbox in the login dialog that tells the server
450 that it's OK for the address of the client to change. When the server sees that
451 this box is checked it should delete the C<__address> sepcial key from the
452 session hash when the hash is first created.