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