Version requirement bump
[catagits/Catalyst-Plugin-Session-State-Cookie.git] / lib / Catalyst / Plugin / Session / State / Cookie.pm
1 package Catalyst::Plugin::Session::State::Cookie;
2 use base qw/Catalyst::Plugin::Session::State Class::Accessor::Fast/;
3
4 use strict;
5 use warnings;
6
7 use MRO::Compat;
8 use Catalyst::Utils ();
9
10 our $VERSION = "0.11";
11
12 BEGIN { __PACKAGE__->mk_accessors(qw/_deleted_session_id/) }
13
14 sub setup_session {
15     my $c = shift;
16
17     $c->maybe::next::method(@_);
18
19     $c->config->{session}{cookie_name}
20         ||= Catalyst::Utils::appprefix($c) . '_session';
21 }
22
23 sub extend_session_id {
24     my ( $c, $sid, $expires ) = @_;
25
26     if ( my $cookie = $c->get_session_cookie ) {
27         $c->update_session_cookie( $c->make_session_cookie( $sid ) );
28     }
29
30     $c->maybe::next::method( $sid, $expires );
31 }
32
33 sub set_session_id {
34     my ( $c, $sid ) = @_;
35
36     $c->update_session_cookie( $c->make_session_cookie( $sid ) );
37
38     return $c->maybe::next::method($sid);
39 }
40
41 sub update_session_cookie {
42     my ( $c, $updated ) = @_;
43     
44     unless ( $c->cookie_is_rejecting( $updated ) ) {
45         my $cookie_name = $c->config->{session}{cookie_name};
46         $c->response->cookies->{$cookie_name} = $updated;
47     }
48 }
49
50 sub cookie_is_rejecting {
51     my ( $c, $cookie ) = @_;
52     
53     if ( $cookie->{path} ) {
54         return 1 if index '/'.$c->request->path, $cookie->{path};
55     }
56     
57     return 0;
58 }
59
60 sub make_session_cookie {
61     my ( $c, $sid, %attrs ) = @_;
62
63     my $cfg    = $c->config->{session};
64     my $cookie = {
65         value => $sid,
66         ( $cfg->{cookie_domain} ? ( domain => $cfg->{cookie_domain} ) : () ),
67         ( $cfg->{cookie_path} ? ( path => $cfg->{cookie_path} ) : () ),
68         %attrs,
69     };
70
71     unless ( exists $cookie->{expires} ) {
72         $cookie->{expires} = $c->calculate_session_cookie_expires();
73     }
74
75     #beware: we have to accept also the old syntax "cookie_secure = true"
76     my $sec = $cfg->{cookie_secure} || 0; # default = 0 (not set)
77     $cookie->{secure} = 1 unless ( ($sec==0) || ($sec==2) );
78     $cookie->{secure} = 1 if ( ($sec==2) && $c->req->secure );      
79     
80     my $hto = $cookie->{httponly} || 1; # default = 1 (set httponly)
81     $cookie->{httponly} = 1 unless ($hto==0);
82
83     return $cookie;
84 }
85
86 sub calc_expiry { # compat
87     my $c = shift;
88     $c->maybe::next::method( @_ ) || $c->calculate_session_cookie_expires( @_ );
89 }
90
91 sub calculate_session_cookie_expires {
92     my $c   = shift;
93     my $cfg = $c->config->{session};
94
95     my $value = $c->maybe::next::method(@_);
96     return $value if $value;
97
98     if ( exists $cfg->{cookie_expires} ) {
99         if ( $cfg->{cookie_expires} > 0 ) {
100             return time() + $cfg->{cookie_expires};
101         }
102         else {
103             return undef;
104         }
105     }
106     else {
107         return $c->session_expires;
108     }
109 }
110
111 sub get_session_cookie {
112     my $c = shift;
113
114     my $cookie_name = $c->config->{session}{cookie_name};
115
116     return $c->request->cookies->{$cookie_name};
117 }
118
119 sub get_session_id {
120     my $c = shift;
121
122     if ( !$c->_deleted_session_id and my $cookie = $c->get_session_cookie ) { 
123         my $sid = $cookie->value;
124         $c->log->debug(qq/Found sessionid "$sid" in cookie/) if $c->debug;
125         return $sid if $sid;
126     }
127
128     $c->maybe::next::method(@_);
129 }
130
131 sub delete_session_id {
132     my ( $c, $sid ) = @_;
133     
134     $c->_deleted_session_id(1); # to prevent get_session_id from returning it
135
136     $c->update_session_cookie( $c->make_session_cookie( $sid, expires => 0 ) );
137
138     $c->maybe::next::method($sid);
139 }
140
141 __PACKAGE__
142
143 __END__
144
145 =pod
146
147 =head1 NAME
148
149 Catalyst::Plugin::Session::State::Cookie - Maintain session IDs using cookies.
150
151 =head1 SYNOPSIS
152
153     use Catalyst qw/Session Session::State::Cookie Session::Store::Foo/;
154
155 =head1 DESCRIPTION
156
157 In order for L<Catalyst::Plugin::Session> to work the session ID needs to be
158 stored on the client, and the session data needs to be stored on the server.
159
160 This plugin stores the session ID on the client using the cookie mechanism.
161
162 =head1 METHODS
163
164 =over 4
165
166 =item make_session_cookie
167
168 Returns a hash reference with the default values for new cookies.
169
170 =item update_session_cookie $hash_ref
171
172 Sets the cookie based on C<cookie_name> in the response object.
173
174 =item calc_expiry
175
176 =item calculate_session_cookie_expires
177
178 =item cookie_is_rejecting
179
180 =item delete_session_id
181
182 =item extend_session_id
183
184 =item get_session_cookie
185
186 =item get_session_id
187
188 =item set_session_id
189
190 =back
191
192 =head1 EXTENDED METHODS
193
194 =over 4
195
196 =item prepare_cookies
197
198 Will restore if an appropriate cookie is found.
199
200 =item finalize_cookies
201
202 Will set a cookie called C<session> if it doesn't exist or if its value is not
203 the current session id.
204
205 =item setup_session
206
207 Will set the C<cookie_name> parameter to its default value if it isn't set.
208
209 =back
210
211 =head1 CONFIGURATION
212
213 =over 4
214
215 =item cookie_name
216
217 The name of the cookie to store (defaults to C<Catalyst::Utils::apprefix($c) . '_session'>).
218
219 =item cookie_domain
220
221 The name of the domain to store in the cookie (defaults to current host)
222
223 =item cookie_expires
224
225 Number of seconds from now you want to elapse before cookie will expire. 
226 Set to 0 to create a session cookie, ie one which will die when the 
227 user's browser is shut down.
228
229 =item cookie_secure
230
231 If this attribute B<set to 0> the cookie will not have the secure flag.
232
233 If this attribute B<set to 1> (or true for backward compatibility) - the cookie 
234 send by the server to the client will got the secure flag that tells the browser 
235 to send this cookies back to the server only via HTTPS.
236
237 If this attribute B<set to 2> then the cookie will got the secure flag only if
238 the request that caused cookie generation was sent over https (this option is 
239 not good if you are mixing https and http in you application).
240
241 Default vaule is 0.
242
243 =item cookie_httponly
244
245 If this attribute B<set to 0>, the cookie will not have HTTPOnly flag.
246
247 If this attribute B<set to 1>, the cookie will got HTTPOnly flag that should 
248 prevent client side Javascript accessing the cookie value - this makes some
249 sort of session hijacking attacks significantly harder. Unfortunately not all
250 browsers support this flag (MSIE 6 SP1+, Firefox 3.0.0.6+, Opera 9.5+); if 
251 a browser is not aware of HTTPOnly the flag will be ignored.
252
253 Default value is 1.
254
255 Note1: Many peole are confused by the name "HTTPOnly" - it B<does not mean>
256 that this cookie works only over HTTP and not over HTTPS. 
257
258 Note2: This paramater requires Catalyst::Runtime 5.80005 otherwise is skipped.
259
260 =item cookie_path
261
262 The path of the request url where cookie should be baked.
263
264 =back
265
266 For example, you could stick this in MyApp.pm:
267
268   __PACKAGE__->config( session => {
269      cookie_domain  => '.mydomain.com',
270   });
271
272 =head1 CAVEATS
273
274 Sessions have to be created before the first write to be saved. For example:
275
276         sub action : Local {
277                 my ( $self, $c ) = @_;
278                 $c->res->write("foo");
279                 $c->session( ... );
280                 ...
281         }
282
283 Will cause a session ID to not be set, because by the time a session is
284 actually created the headers have already been sent to the client.
285
286 =head1 SEE ALSO
287
288 L<Catalyst>, L<Catalyst::Plugin::Session>.
289
290 =head1 AUTHORS
291
292 Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
293
294 =head1 CONTRIBUTORS
295
296 This module is derived from L<Catalyst::Plugin::Session::FastMmap> code, and
297 has been heavily modified since.
298
299   Andrew Ford
300   Andy Grundman
301   Christian Hansen
302   Marcus Ramberg
303   Jonathan Rockway E<lt>jrockway@cpan.orgE<gt>
304   Sebastian Riedel
305
306 =head1 COPYRIGHT
307
308 This program is free software, you can redistribute it and/or modify it
309 under the same terms as Perl itself.
310
311 =cut
312
313 1;