File Coverage

blib/lib/Catalyst/Plugin/Session/Store/Cookie.pm
Criterion Covered Total %
statement 27 29 93.1
branch 2 2 100.0
condition 5 11 45.4
subroutine 7 9 77.7
pod 4 5 80.0
total 45 56 80.3


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::Session::Store::Cookie;
2              
3 1     1   2822 use Moose;
  1         2  
  1         6  
4 1     1   5175 use Session::Storage::Secure;
  1         43684  
  1         35  
5 1     1   6 use MRO::Compat;
  1         13  
  1         20  
6 1     1   4 use Catalyst::Utils;
  1         0  
  1         322  
7              
8             extends 'Catalyst::Plugin::Session::Store';
9             with 'Catalyst::ClassData';
10              
11             our $VERSION = '0.003';
12              
13             __PACKAGE__->mk_classdata($_)
14             for qw/_secure_store _store_cookie_name _store_cookie_expires/;
15              
16             sub get_session_data {
17 22     22 1 228399 my ($self, $key) = @_;
18 22         57 $self->_needs_early_session_finalization(1);
19 22         4683 my $cookie = $self->req->cookie($self->_store_cookie_name);
20 22 100       1822 $self->{__cookie_session_store_cache__} = defined($cookie) ?
21             $self->_secure_store->decode($cookie->value) : {};
22              
23 22         7607 return $self->{__cookie_session_store_cache__}->{$key};
24             }
25              
26             sub store_session_data {
27 3     3 1 8533 my ($self, $key, $data) = @_;
28              
29             $self->{__cookie_session_store_cache__} = +{
30 3         5 %{$self->{__cookie_session_store_cache__}},
  3         14  
31             $key => $data};
32              
33             return $self->res->cookies->{$self->_store_cookie_name} = {
34 3         16 value => $self->_secure_store->encode($self->{__cookie_session_store_cache__}),
35             expires => $self->_store_cookie_expires};
36             }
37              
38             sub delete_session_data {
39 0     0 1 0 my ($self, $key) = @_;
40 0         0 delete $self->{__cookie_session_store_cache__}->{$key};
41             }
42              
43             # Docs say 'this may be used in the future', like 10 years ago...
44       0 1   sub delete_expired_sessions { }
45              
46             sub setup_session {
47 1     1 0 255217 my $class = shift;
48 1         3 my $cfg = $class->_session_plugin_config;
49 1   33     54 $class->_store_cookie_name($cfg->{storage_cookie_name} || Catalyst::Utils::appprefix($class) . '_store');
50 1   50     38 $class->_store_cookie_expires($cfg->{storage_cookie_expires} || '+1d');
51             $class->_secure_store(
52             Session::Storage::Secure->new(
53             secret_key => ($cfg->{storage_secret_key} ||
54             die "storage_secret_key' configuration param for 'Catalyst::Plugin::Session::Store::Cookie' is missing!"),
55             sereal_encoder_options => ($cfg->{sereal_encoder_options} || +{ snappy => 1, stringify_unknown => 1 }),
56 1   50     36 sereal_decoder_options => ($cfg->{sereal_decoder_options} || +{ validate_utf8 => 1 })
      50        
      50        
57             )
58             );
59              
60 1         2983 return $class->maybe::next::method(@_);
61             }
62              
63             __PACKAGE__->meta->make_immutable;
64              
65             =head1 NAME
66              
67             Catalyst::Plugin::Session::Store::Cookie - Store session data in the cookie
68              
69             =head1 SYNOPSIS
70              
71             package MyApp;
72              
73             use Catalyst qw/
74             Session
75             Session::State::Cookie
76             Session::Store::Cookie
77             /;
78              
79             __PACKAGE__->config(
80             'Plugin::Session' => {
81             storage_cookie_name => ...,
82             storage_cookie_expires => ...,
83             storage_secret_key => ...,
84             },
85             ## More configuration
86             );
87              
88             __PACKAGE__->setup;
89              
90             =head1 DESCRIPTION
91              
92             What's old is new again...
93              
94             Store session data in the client cookie, like in 1995. Handy when you don't
95             want to setup yet another storage system just for supporting sessions and
96             authentication. Can be very fast since you avoid the overhead of requesting and
97             deserializing session information from whatever you are using to store it.
98             Since Sessions in L<Catalyst> are global you can use this to reduce per request
99             overhead. On the other hand you may just use this for early prototying and
100             then move onto something else for production. I'm sure you'll do the right
101             thing ;)
102              
103             The downsides are that you can really only count on about 4Kb of storage space
104             on the cookie. Also, that cookie data becomes part of every request so that
105             will increase overhead on the request side of the network. In other words a big
106             cookie means more data over the wire (maybe you are paying by the byte...?)
107              
108             Also there are some questions as to the security of this approach. We encrypt
109             information with L<Session::Storage::Secure> so you should review that and the
110             notes that it includes. Using this without SSL/HTTPS is not recommended. Buyer
111             beware.
112              
113             In any case if all you are putting in the session is a user id and a few basic
114             things this will probably be totally fine and likely a lot more sane that using
115             something non persistant like memcached. On the other hand if you like to dump
116             a bunch of stuff into the user session, this will likely not work out.
117              
118             B<NOTE> Since we need to store all the session info in the cookie, the session
119             state will be set at ->finalize_headers stage (rather than at ->finalize_body
120             which is the default for session storage plugins). What this means is that if
121             you use the streaming or socket interfaces ($c->response->write, $c->response->write_fh
122             and $c->req->io_fh) your session state will get saved early. For example you
123             cannot do this:
124              
125             $c->res->write("some stuff");
126             $c->session->{key} = "value";
127              
128             That key 'key' will not be recalled when the session is recovered for the following
129             request. In general this should be an easy issue to work around, but you need
130             to be aware of it.
131              
132             =head1 CONFIGURATION
133              
134             This plugin supports the following configuration settings, which are stored as
135             a hash ref under the configuration key 'Plugin::Session::Store::Cookie'. See
136             L</SYNOPSIS> for example.
137              
138             =head2 storage_cookie_name
139              
140             The name of the cookie that stores your session data on the client. Defaults
141             to '${$myapp}_sstore' (where $myappp is the lowercased version of your application
142             subclass). You may wish something less obvious.
143              
144             =head2 storage_cookie_expires
145              
146             How long before the cookie that is storing the session info expires. defaults
147             to '+1d'. Lower is more secure but bigger hassle for your user. You choose the
148             right balance.
149              
150             =head2 storage_secret_key
151              
152             Used to fill the 'secret_key' initialization parameter for L<Session::Storage::Secure>.
153             Don't let this be something you can guess or something that escapes into the
154             wild...
155              
156             There is no default for this, you need to supply.
157              
158             =head2 sereal_decoder_options
159             =head2 sereal_encoder_options
160              
161             This should be a hashref of options passed to init args of same name in
162             L<Session::Storage::Secure>. Defaults to:
163              
164             sereal_encoder_options => +{ snappy => 1, stringify_unknown => 1 },
165             sereal_decoder_options => +{ validate_utf8 => 1 },
166              
167             Please note the default B<allows> object serealization. You may wish to
168             not allow this for production setups.
169              
170             =head1 AUTHOR
171            
172             John Napiorkowski L<email:jjnapiork@cpan.org>
173            
174             =head1 SEE ALSO
175            
176             L<Catalyst>, L<Catalyst::Plugin::Session>, L<Session::Storage::Secure>
177              
178             =head1 COPYRIGHT & LICENSE
179            
180             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
181            
182             This library is free software; you can redistribute it and/or modify it under
183             the same terms as Perl itself.
184              
185             =cut
186