File Coverage

blib/lib/Catalyst/Plugin/Session/PerUser.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Catalyst::Plugin::Session::PerUser;
4 1     1   75667 use Moose;
  0            
  0            
5             use namespace::autoclean;
6              
7             our $VERSION = "0.05";
8              
9             use MRO::Compat;
10             use Hash::Merge ();
11             use Object::Signature ();
12              
13             has [qw/_user_session _user_session_data_sig/] => ( is => 'rw' );
14              
15             sub setup {
16             my $self = shift;
17              
18             my $cfg = $self->config->{user_session} ||= {};
19              
20             %$cfg = (
21             migrate => 1,
22             merge_type => "RIGHT_PRECEDENT",
23             %$cfg,
24             );
25              
26             $self->next::method(@_);
27             }
28              
29             sub set_authenticated {
30             my $c = shift;
31             $c->maybe::next::method(@_);
32              
33             if ( $c->config->{user_session}{migrate} ) {
34             $c->merge_session_to_user;
35             }
36             }
37              
38             sub logout {
39             my $c = shift;
40              
41             $c->_save_user_session;
42             $c->_user_session(undef);
43              
44             $c->maybe::next::method(@_);
45             }
46              
47             sub user_session {
48             my $c = shift;
49              
50             if ( $c->user_exists ) {
51             $c->log->debug("user logged in, using user session") if $c->debug;
52             return $c->_user_session || $c->_user_session($c->_load_user_session);
53             }
54             else {
55             $c->log->debug("no user logged in, using guest session") if $c->debug;
56             return $c->session;
57             }
58             }
59              
60             sub _load_user_session {
61             my $c = shift;
62             if ( my $user = $c->user ) {
63             my $session_data;
64             if ( $user->supports("session_data") ) {
65             $session_data = $user->get_session_data;
66             }
67             else {
68             $session_data = $c->get_session_data( $c->user_session_sid );
69             }
70              
71             $session_data ||= {};
72             $c->_user_session_data_sig( Object::Signature::signature($session_data) );
73             return $session_data;
74             }
75             return;
76             }
77              
78             sub _save_user_session {
79             my $c = shift;
80             if (my $data = $c->_user_session) {
81             no warnings 'uninitialized';
82             if ( Object::Signature::signature($data) ne $c->_user_session_data_sig
83             and ( my $user = $c->user ) )
84             {
85             if ( $user->supports("session_data") ) {
86             $user->store_session_data( $data );
87             }
88             else {
89             $c->store_session_data( $c->user_session_sid, $data );
90             }
91             }
92             }
93             }
94              
95             sub finalize {
96             my $c = shift;
97              
98             $c->_save_user_session;
99              
100             $c->maybe::next::method(@_);
101             }
102              
103             sub user_session_sid {
104             my $c = shift;
105             "user:" . $c->user->id;
106             }
107              
108             sub merge_session_to_user {
109             my $c = shift;
110              
111             $c->log->debug("merging guest session into per user session") if $c->debug;
112              
113             my $merge_behavior = Hash::Merge::get_behavior;
114             my $clone_behavior = Hash::Merge::get_clone_behavior;
115              
116             Hash::Merge::set_behavior( $c->config->{user_session}{merge_type} );
117             Hash::Merge::set_clone_behavior(0);
118              
119             my $s = $c->session;
120             my @keys =
121             grep { !/^__/ } keys %$s; # __user, __expires, etc don't apply here
122              
123             my %right;
124             @right{@keys} = delete @{$s}{@keys};
125              
126             %{ $c->user_session } =
127             %{ Hash::Merge::merge( $c->user_session || {}, \%right ) };
128              
129             Hash::Merge::set_behavior($merge_behavior);
130             Hash::Merge::set_clone_behavior($clone_behavior);
131             }
132              
133             __PACKAGE__;
134              
135             __END__
136              
137             =pod
138              
139             =head1 NAME
140              
141             Catalyst::Plugin::Session::PerUser - Per user sessions (instead of per browser sessions).
142              
143             =head1 SYNOPSIS
144              
145             use Catalyst qw/
146             Session
147             Authentication
148             Authentication::Store::Foo
149             Session::PerUser
150             /;
151              
152             sub action : Local {
153             my ( $self, $c ) = @_;
154             $c->user_session->{foo} = "bar";
155             }
156              
157             =head1 DESCRIPTION
158              
159             This plugin allows you to write e.g. shopping cart code which should behave
160             well for guests as well as permanent users.
161              
162             The basic idea is both logged in and not logged in users can get the same
163             benefits from sessions where it doesn't matter, but that logged in users can
164             keep their sessions accross logins, and will even get the data they
165             added/changed assimilated to their permanent account if they made the changes
166             as guests and then logged in.
167              
168             This is probably most useful for e-commerce sites, where the shopping cart is
169             typically used before login, and should be equally accessible to both guests
170             and logged in users.
171              
172             =head1 STORING SESSION DATA
173              
174             This module can store session data in two ways:
175              
176             =head2 Within the User
177              
178             If C<< $c->user->supports("session_data") >> then C<< $c->user->get_session_data >>
179             and C<< $c->user->store_session_data($data) >> are used to access and store the
180             per-user session hash reference.
181              
182             This is useful for L<Catalyst::Plugin::Authentication::Store> implementations
183             that rely on a database or another fast, extensible format.
184              
185             =head2 Within the Session Store
186              
187             If the user does not support the C<session_data> feature, the
188             L<Catalyst::Plugin::Session::Store> plugin in use will be used to save the
189             session data instead.
190              
191             The session ID used to save this data is set by C<user_session_sid>.
192              
193             Note that this method could potentially have security issues if you override
194             the default C<user_session_sid> or
195             L<Catalyst::Plugin::Session/validate_session_id>. See L</CAVEATS> for details.
196              
197             =head1 METHODS
198              
199             =over 4
200              
201             =item user_session
202              
203             If no user is logged in, returns C<< $c->session >>.
204              
205             If a user is logged in, and C<< $user->supports("session_data") >> it will return
206             C<< $c->user->get_session_data >>. Otherwise it will return data from the normal
207             session store, using C<user_session_sid> as a session ID.
208              
209             =back
210              
211             =head1 INTERNAL METHODS
212              
213             =over 4
214              
215             =item merge_session_to_user
216              
217             Uses L<Hash::Merge> to merge the browser session into the user session,
218             omitting the special keys from the browser session.
219              
220             Should be overloaded to e.g. merge shopping cart items more intelligently.
221              
222             =item user_session_sid
223              
224             By default returns
225              
226             "user:" . $c->user->id
227              
228             =back
229              
230             =head1 EXTENDED METHODS
231              
232             =over 4
233              
234             =item set_authenticated
235              
236             Calls C<merge_session_to_user>.
237              
238             =item setup
239              
240             =item finalize
241              
242             =item logout
243              
244             =back
245              
246             =head1 CONFIGURATION
247              
248             $c->config->{user_session} = {
249             ...
250             };
251              
252             =over 4
253              
254             =item migrate
255              
256             Whether C<< $c->session >> should be merged over C<< $c->user_session >> on
257             login. On by default.
258              
259             =item merge_type
260              
261             Passed to L<Hash::Merge/set_behavior>. Defaults to C<RIGHT_PRECEDENT>.
262              
263             =item
264              
265             =back
266              
267             =head1 CAVEATS
268              
269             If you override L<Catalyst::Plugin::Session/validate_session_id> make sure its
270             format B<DOES NOT ALLOW> the format returned by C<user_session_sid>, or
271             malicious users could potentially set their cookies to have sessions formatted
272             like a string returned by C<user_session_sid>, and steal or destroy another
273             user's session without authenticating.
274             =back
275              
276             =head1 SEE ALSO
277              
278             L<Catalyst::Plugin::Authentication>, L<Catalyst::Plugin::Session>
279              
280             =head1 AUTHORS
281              
282             David Kamholz, C<dkamholz@cpan.org>
283              
284             Yuval Kogman, C<nothingmuch@woobling.org>
285              
286             Tomas Doran, C<bobtfish@bobtfish.net>
287              
288             =head1 COPYRIGHT & LICENSE
289              
290             Copyright (c) 2005 the aforementioned authors. Some rights
291             reserved. This program is free software; you can redistribute
292             it and/or modify it under the same terms as Perl itself.
293              
294             =cut
295