Perltidy + restore of lost test fixes
[catagits/Catalyst-Plugin-Session.git] / lib / Catalyst / Plugin / Session.pm
CommitLineData
9e447f9d 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Session;
4use base qw/Class::Accessor::Fast/;
5
6use strict;
7use warnings;
8
9use NEXT;
10use Catalyst::Exception ();
9a9252c2 11use Digest ();
12use overload ();
13use List::Util ();
9e447f9d 14
15BEGIN {
9a9252c2 16 __PACKAGE__->mk_accessors(qw/sessionid session_delete_reason/);
9e447f9d 17}
18
19sub setup {
9a9252c2 20 my $c = shift;
21
22 $c->NEXT::setup(@_);
23
24 $c->check_session_plugin_requirements;
25 $c->setup_session;
26
27 return $c;
9e447f9d 28}
29
30sub check_session_plugin_requirements {
9a9252c2 31 my $c = shift;
9e447f9d 32
9a9252c2 33 unless ( $c->isa("Catalyst::Plugin::Session::State")
34 && $c->isa("Catalyst::Plugin::Session::Store") )
35 {
36 my $err =
37 ( "The Session plugin requires both Session::State "
38 . "and Session::Store plugins to be used as well." );
9e447f9d 39
9a9252c2 40 $c->log->fatal($err);
41 Catalyst::Exception->throw($err);
42 }
9e447f9d 43}
44
45sub setup_session {
9a9252c2 46 my $c = shift;
9e447f9d 47
9a9252c2 48 my $cfg = ( $c->config->{session} ||= {} );
9e447f9d 49
9a9252c2 50 %$cfg = (
51 expires => 7200,
52 verify_address => 1,
53 %$cfg,
54 );
9e447f9d 55
9a9252c2 56 $c->NEXT::setup_session();
9e447f9d 57}
58
59sub finalize {
9a9252c2 60 my $c = shift;
9e447f9d 61
9a9252c2 62 if ( $c->{session} ) {
9e447f9d 63
9a9252c2 64 # all sessions are extended at the end of the request
65 my $now = time;
66 @{ $c->{session} }{qw/__updated __expires/} =
67 ( $now, $c->config->{session}{expires} + $now );
68 $c->store_session_data( $c->sessionid, $c->{session} );
69 }
70
71 $c->NEXT::finalize(@_);
9e447f9d 72}
73
74sub prepare_action {
75 my $c = shift;
76
9a9252c2 77 my $ret = $c->NEXT::prepare_action;
9e447f9d 78
9a9252c2 79 my $sid = $c->sessionid || return;
9e447f9d 80
81 $c->log->debug(qq/Found session "$sid"/) if $c->debug;
82
9a9252c2 83 my $s = $c->{session} ||= $c->get_session_data($sid);
84 if ( !$s or $s->{__expires} < time ) {
85
86 # session expired
87 $c->log->debug("Deleting session $sid (expired)") if $c->debug;
88 $c->delete_session("session expired");
89 return $ret;
90 }
91
92 if ( $c->config->{session}{verify_address}
93 && $c->{session}{__address}
94 && $c->{session}{__address} ne $c->request->address )
95 {
96 $c->log->warn(
97 "Deleting session $sid due to address mismatch ("
98 . $c->{session}{__address} . " != "
99 . $c->request->address . ")",
100 );
101 $c->delete_session("address mismatch");
102 return $ret;
103 }
9e447f9d 104}
105
106sub delete_session {
9a9252c2 107 my ( $c, $msg ) = @_;
9e447f9d 108
9a9252c2 109 # delete the session data
110 my $sid = $c->sessionid;
111 $c->delete_session_data($sid);
9e447f9d 112
9a9252c2 113 # reset the values in the context object
114 $c->{session} = undef;
115 $c->sessionid(undef);
116 $c->session_delete_reason($msg);
9e447f9d 117}
118
119sub session {
9a9252c2 120 my $c = shift;
9e447f9d 121
122 return $c->{session} if $c->{session};
123
9a9252c2 124 my $sid = $c->generate_session_id;
125 $c->sessionid($sid);
9e447f9d 126
9a9252c2 127 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
9e447f9d 128
9a9252c2 129 return $c->initialize_session_data;
9e447f9d 130}
131
132sub initialize_session_data {
9a9252c2 133 my $c = shift;
9e447f9d 134
9a9252c2 135 my $now = time;
9e447f9d 136
9a9252c2 137 return $c->{session} = {
138 __created => $now,
139 __updated => $now,
140 __expires => $now + $c->config->{session}{expires},
9e447f9d 141
9a9252c2 142 (
143 $c->config->{session}{verify_address}
144 ? ( __address => $c->request->address )
145 : ()
146 ),
147 };
9e447f9d 148}
149
9e447f9d 150# refactor into Catalyst::Plugin::Session::ID::Weak ?
151
152sub generate_session_id {
153 my $c = shift;
154
155 my $digest = $c->_find_digest();
156 $digest->add( $c->session_hash_seed() );
157 return $digest->hexdigest;
158}
159
160my $counter;
9a9252c2 161
9e447f9d 162sub session_hash_seed {
9a9252c2 163 my $c = shift;
164
165 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
9e447f9d 166}
167
168my $usable;
9a9252c2 169
9e447f9d 170sub _find_digest () {
9a9252c2 171 unless ($usable) {
172 $usable = List::Util::first(
173 sub {
174 eval { Digest->new($_) };
175 },
176 qw/SHA-1 MD5 SHA-256/
177 )
178 or Catalyst::Exception->throw(
179 "Could not find a suitable Digest module. Please install "
180 . "Digest::SHA1, Digest::SHA, or Digest::MD5" );
181 }
9e447f9d 182
183 return Digest->new($usable);
184}
185
9e447f9d 186__PACKAGE__;
187
188__END__
189
190=pod
191
192=head1 NAME
193
194Catalyst::Plugin::Session - Generic Session plugin - ties together server side
195storage and client side tickets required to maintain session data.
196
197=head1 SYNOPSIS
198
199 use Catalyst qw/Session Session::Store::FastMmap Session::State::Cookie/;
200
201=head1 DESCRIPTION
202
203The Session plugin is the base of two related parts of functionality required
204for session management in web applications.
205
206The first part, the State, is getting the browser to repeat back a session key,
207so that the web application can identify the client and logically string
208several requests together into a session.
209
210The second part, the Store, deals with the actual storage of information about
211the client. This data is stored so that the it may be revived for every request
212made by the same client.
213
214This plugin links the two pieces together.
215
216=head1 METHODS
217
218=over 4
219
220=item sessionid
221
222An accessor for the session ID value.
223
224=item session
225
226Returns a hash reference that might contain unserialized values from previous
227requests in the same session, and whose modified value will be saved for future
228requests.
229
230This method will automatically create a new session and session ID if none
231exists.
232
233=item session_delete_reason
234
235This accessor contains a string with the reason a session was deleted. Possible
236values include:
237
238=over 4
239
240=item *
241
242C<address mismatch>
243
244=item *
245
246C<session expired>
247
248=back
249
250=item setup
251
252This method is extended to also make calls to
253C<check_session_plugin_requirements> and C<setup_session>.
254
255=item check_session_plugin_requirements
256
257This method ensures that a State and a Store plugin are also in use by the
258application.
259
260=item setup_session
261
262This method populates C<< $c->config->{session} >> with the default values
263listed in L</CONFIGURATION>.
264
265=item prepare_action
266
267This methoid is extended, and will restore session data and check it for
268validity if a session id is defined. It assumes that the State plugin will
269populate the C<sessionid> key beforehand.
270
271=item finalize
272
273This method is extended and will extend the expiry time, as well as persist the
274session data if a session exists.
275
276=item delete_session REASON
277
278This method is used to invalidate a session. It takes an optional parameter
279which will be saved in C<session_delete_reason> if provided.
280
281=item initialize_session_data
282
283This method will initialize the internal structure of the session, and is
284called by the C<session> method if appropriate.
285
286=back
287
288=head1 CONFIGURATION
289
290 $c->config->{session} = {
291 expires => 1234,
292 };
293
294All configuation parameters are provided in a hash reference under the
295C<session> key in the configuration hash.
296
297=over 4
298
299=item expires
300
301The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
302hours).
303
304=item verify_address
305
306When false, C<< $c->request->address >> will be checked at prepare time. If it
307is not the same as the address that initiated the session, the session is
308deleted.
309
310=back
311
312=head1 SPECIAL KEYS
313
314The hash reference returned by C<< $c->session >> contains several keys which
315are automatically set:
316
317=over 4
318
319=item __expires
320
321A timestamp whose value is the last second when the session is still valid. If
322a session is restored, and __expires is less than the current time, the session
323is deleted.
324
325=item __updated
326
327The last time a session was saved. This is the value of
328C<< $c->{session}{__expires} - $c->config->{session}{expires} >>.
329
330=item __created
331
332The time when the session was first created.
333
334=item __address
335
336The value of C<< $c->request->address >> at the time the session was created.
337This value is only populated of C<verify_address> is true in the configuration.
338
339=back
340
341=cut
342
343