File Coverage

blib/lib/Catalyst/Plugin/Session/State/Cookie.pm
Criterion Covered Total %
statement 62 67 92.5
branch 21 36 58.3
condition 11 14 78.5
subroutine 14 15 93.3
pod 11 11 100.0
total 119 143 83.2


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::Session::State::Cookie;
2 3     3   2188037 use Moose;
  3         393857  
  3         20  
3 3     3   18430 use namespace::autoclean;
  3         8288  
  3         15  
4              
5             extends 'Catalyst::Plugin::Session::State';
6              
7 3     3   193 use MRO::Compat;
  3         6  
  3         65  
8 3     3   876 use Catalyst::Utils ();
  3         112337  
  3         2216  
9              
10             our $VERSION = '0.18';
11             $VERSION =~ tr/_//d;
12              
13             has _deleted_session_id => ( is => 'rw' );
14              
15             sub setup_session {
16 2     2 1 346317 my $c = shift;
17              
18 2         11 $c->maybe::next::method(@_);
19              
20             $c->_session_plugin_config->{cookie_name}
21 2   33     23 ||= Catalyst::Utils::appprefix($c) . '_session';
22             }
23              
24             sub extend_session_id {
25 6     6 1 30219 my ( $c, $sid, $expires ) = @_;
26              
27 6 50       20 if ( my $cookie = $c->get_session_cookie ) {
28 6         1055 $c->update_session_cookie( $c->make_session_cookie( $sid ) );
29             }
30              
31 6         212 $c->maybe::next::method( $sid, $expires );
32             }
33              
34             sub set_session_id {
35 5     5 1 8196 my ( $c, $sid ) = @_;
36              
37 5         27 $c->update_session_cookie( $c->make_session_cookie( $sid ) );
38              
39 5         122 return $c->maybe::next::method($sid);
40             }
41              
42             sub update_session_cookie {
43 12     12 1 29 my ( $c, $updated ) = @_;
44              
45 12 50       39 unless ( $c->cookie_is_rejecting( $updated ) ) {
46 12         41 my $cookie_name = $c->_session_plugin_config->{cookie_name};
47 12         969 $c->response->cookies->{$cookie_name} = $updated;
48             }
49             }
50              
51             sub cookie_is_rejecting {
52 14     14 1 28 my ( $c, $cookie ) = @_;
53              
54 14 100       41 if ( $cookie->{path} ) {
55 2 100       19 return 1 if index '/'.$c->request->path, $cookie->{path};
56             }
57              
58 13         39 return 0;
59             }
60              
61             sub make_session_cookie {
62 12     12 1 36 my ( $c, $sid, %attrs ) = @_;
63              
64 12         48 my $cfg = $c->_session_plugin_config;
65             my $cookie = {
66             value => $sid,
67             ( $cfg->{cookie_domain} ? ( domain => $cfg->{cookie_domain} ) : () ),
68 12 50       826 ( $cfg->{cookie_path} ? ( path => $cfg->{cookie_path} ) : () ),
    50          
69             %attrs,
70             };
71              
72 12 100       42 unless ( exists $cookie->{expires} ) {
73 11         42 $cookie->{expires} = $c->calculate_session_cookie_expires();
74             }
75              
76             #beware: we have to accept also the old syntax "cookie_secure = true"
77 12   100     1071 my $sec = $cfg->{cookie_secure} || 0; # default = 0 (not set)
78 12 50 66     60 $cookie->{secure} = 1 unless ( ($sec==0) || ($sec==2) );
79 12 100 100     56 $cookie->{secure} = 1 if ( ($sec==2) && $c->req->secure );
80              
81 12         547 $cookie->{httponly} = $cfg->{cookie_httponly};
82             $cookie->{httponly} = 1
83 12 50       39 unless defined $cookie->{httponly}; # default = 1 (set httponly)
84              
85 12         27 $cookie->{samesite} = $cfg->{cookie_samesite};
86             $cookie->{samesite} = "Lax"
87 12 50       42 unless defined $cookie->{ samesite}; # default = Lax
88              
89 12         64 return $cookie;
90             }
91              
92             sub calc_expiry { # compat
93 0     0 1 0 my $c = shift;
94 0 0       0 $c->maybe::next::method( @_ ) || $c->calculate_session_cookie_expires( @_ );
95             }
96              
97             sub calculate_session_cookie_expires {
98 11     11 1 40 my $c = shift;
99 11         31 my $cfg = $c->_session_plugin_config;
100              
101 11         635 my $value = $c->maybe::next::method(@_);
102 11 50       120 return $value if $value;
103              
104 11 50       40 if ( exists $cfg->{cookie_expires} ) {
105 0 0       0 if ( $cfg->{cookie_expires} > 0 ) {
106 0         0 return time() + $cfg->{cookie_expires};
107             }
108             else {
109 0         0 return undef;
110             }
111             }
112             else {
113 11         34 return $c->session_expires;
114             }
115             }
116              
117             sub get_session_cookie {
118 19     19 1 36 my $c = shift;
119              
120 19         76 my $cookie_name = $c->_session_plugin_config->{cookie_name};
121              
122 19         1700 return $c->request->cookies->{$cookie_name};
123             }
124              
125             sub get_session_id {
126 15     15 1 2263008 my $c = shift;
127              
128 15 100 100     538 if ( !$c->_deleted_session_id and my $cookie = $c->get_session_cookie ) {
129 9         2055 my $sid = $cookie->value;
130 9 50       68 $c->log->debug(qq/Found sessionid "$sid" in cookie/) if $c->debug;
131 9 50       66 return $sid if $sid;
132             }
133              
134 6         122 $c->maybe::next::method(@_);
135             }
136              
137             sub delete_session_id {
138 1     1 1 322 my ( $c, $sid ) = @_;
139              
140 1         35 $c->_deleted_session_id(1); # to prevent get_session_id from returning it
141              
142 1         5 $c->update_session_cookie( $c->make_session_cookie( $sid, expires => 0 ) );
143              
144 1         35 $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