File Coverage

blib/lib/Maypole/Authentication/UserSessionCookie.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Maypole::Authentication::UserSessionCookie;
2 1     1   5462 use strict;
  1         2  
  1         30  
3 1     1   6 use warnings;
  1         1  
  1         47  
4             our $VERSION = '1.4';
5 1     1   1277 use Apache::Cookie;
  0            
  0            
6             use URI;
7              
8             =head1 NAME
9              
10             Maypole::Authentication::UserSessionCookie - Track sessions and, optionally, users
11              
12             =head1 SYNOPSIS
13              
14             use base qw(Apache::MVC Maypole::Authentication::UserSessionCookie);
15              
16             sub authenticate {
17             my ($self, $r) = @_;
18             $r->get_user;
19             return OK if $r->{user};
20             return OK if $r->{table} eq "user" and $r->{action} eq "subscribe";
21             # Force them to the login page.
22             $r->{template} = "login";
23             return OK;
24             }
25              
26             =head1 DESCRIPTION
27              
28             This module allows Maypole applications to have the concept of a user,
29             and to track that user using cookies and sessions.
30              
31             It provides a number of methods to be inherited by a Maypole class. The
32             first is C, which tries to populate the C slot of the
33             Maypole request object.
34              
35             =head2 get_user
36              
37             $r->get_user;
38              
39             C does this first by checking for a session cookie from the
40             user's browser, and if one is not found, calling C,
41             whose behaviour will be described momentarily. If a session cookie is
42             found, the userid (C) is extracted and passing to C
43             which is expected to return a value (typically a C object from the
44             model class representing the users of your system) to be stored in the
45             C slot. The session hash is also placed in the C slot of
46             the Maypole request for passing around user-specific session data.
47              
48             =cut
49              
50             sub get_user {
51             my $r = shift;
52             my $ar = $r->{ar};
53             my $sid;
54             my %jar = Apache::Cookie->new($ar)->parse;
55             my $cookie_name = $r->config->{auth}{cookie_name} || "sessionid";
56             if (exists $jar{$cookie_name}) { $sid = $jar{$cookie_name}->value(); }
57             warn "SID from cookie: $sid";
58             $sid = undef unless $sid; # Clear it, as 0 is a valid sid.
59             my $new = !(defined $sid);
60             my ($uid, $user);
61              
62             if ($new) {
63             # Go no further unless login credentials are right.
64             ($uid, $r->{user}) = $r->check_credentials;
65             warn "Credentials OK";
66             return 0 unless $uid;
67             }
68             warn "Giving cookie";
69             $r->login_user($uid, $sid) or return 0;
70             $r->{user} ||= $r->uid_to_user($r->{session}{uid});
71             warn "User is : ".$r->{user};
72             }
73              
74             =head2 login_user
75              
76             This method is useful for the situation in which you've just created a user
77             from scratch, and want them to be logged in. You should pass in the user
78             ID of the user you want to log in.
79              
80             =cut
81              
82             sub login_user {
83             my ($r, $uid, $sid) = @_;
84             $sid = 0 unless defined $sid;
85             my %session = ();
86             my $session_class = $r->{config}{auth}{session_class} || 'Apache::Session::File';
87             $session_class->require || die "Couldn't load session class $session_class";
88             my $session_args = $r->{config}{auth}{session_args} || {
89             Directory => "/tmp/sessions",
90             LockDirectory => "/tmp/sessionlock",
91             };
92             eval {
93             tie %session, $session_class, $sid, $session_args;
94             };
95             if ($@) { # Object does not exist in data store!
96             if ($@ =~ /does not exist in data store/) {
97             $r->_logout_cookie;
98             return 0;
99             } else { die $@ }
100             }
101             # Store the userid, and bake the cookie
102             $session{uid} = $uid if $uid and not exists $session{uid};
103             warn "Session's uid is $session{uid}";
104             my $cookie_name = $r->config->{auth}{cookie_name} || "sessionid";
105             my $cookie = Apache::Cookie->new($r->{ar},
106             -name => $cookie_name,
107             -value => $session{_session_id},
108             -expires => $r->config->{auth}{cookie_expiry} || '',
109             -path => URI->new($r->config->{base_uri})->path,
110             );
111             $cookie->bake();
112             $r->{session} = \%session;
113             return 1;
114             }
115              
116             =head2 check_credentials
117              
118             The C method is expected to be overriden, but the
119             default implementation does what most people expect: it checks for the
120             two form parameters (typically C and C but configurable)
121             and does a C on the user class for those values. See
122             L for how the user class is determined. This method
123             works well if the model class is C-based and may not work so
124             well otherwise.
125              
126             C is expected to return two values: the first will be
127             placed in the C slot of the session, the second is the user object
128             to be placed in C<$r->{user}>.
129              
130             If the credentials are wrong, then C<$r->{template_args}{login_error}>
131             is set to an error string.
132              
133             =cut
134              
135             sub check_credentials {
136             my $r = shift;
137             my $user_class = $r->config->{auth}{user_class} || ((ref $r)."::User");
138             $user_class->require || die "Couldn't load user class $user_class";
139             my $user_field = $r->config->{auth}{user_field} || "user";
140             my $pw_field = $r->config->{auth}{password_field} || "password";
141             return unless exists $r->{params}{$user_field} and exists $r->{params}{$pw_field};
142             my @users = $user_class->search(
143             $user_field => $r->{params}{$user_field},
144             $pw_field => $r->{params}{$pw_field},
145             );
146             if (!@users) {
147             $r->{template_args}{login_error} = "Bad username or password";
148             return;
149             }
150             return ($users[0]->id, $users[0]);
151             }
152              
153             =head2 uid_to_user
154              
155             By default, this returns the result of a C on the UID from the
156             user class. Again, see L.
157              
158             =cut
159              
160             sub uid_to_user {
161             my $r = shift;
162             my $user_class = $r->config->{auth}{user_class} || ((ref $r)."::User");
163             $user_class->require || die "Couldn't load user class $user_class";
164             $user_class->retrieve(shift);
165             }
166              
167             =head2 logout
168              
169             This method removes a user's session from the store and issues him a
170             cookie which expires the old cookie.
171              
172             =cut
173              
174             sub logout {
175             my $r = shift;
176             delete $r->{user};
177             tied(%{$r->{session}})->delete;
178             $r->_logout_cookie;
179             }
180              
181             sub _logout_cookie {
182             my $r = shift;
183             my $cookie = Apache::Cookie->new($r->{ar},
184             -name => ($r->config->{auth}{cookie_name} || "session_id"),
185             -value => undef,
186             -path => URI->new($r->config->{base_uri})->path,
187             -expires => "-10m"
188             );
189             $cookie->bake();
190             }
191              
192             =head1 Session tracking without user authentication
193              
194             For some application you may be interested in tracking sessions without
195             forcing users to log in. The way to do this would be to override
196             C to always return a new ID and an entry into some
197             shared storage, and C to look the user up in that shared
198             storage.
199              
200             =head1 Configuration
201              
202             The class provides sensible defaults for all that it does, but you can
203             change its operation through Maypole configuration parameters.
204              
205             First, the session data. This is retrieved as follows. The Maypole
206             configuration parameter C<{auth}{session_class}> is used as a class to tie the session
207             hash, and this defaults to C. The parameters to the tie
208             are the session ID and the value of the C<{auth}{session_args}> configuration
209             parameter. This defaults to:
210              
211             { Directory => "/tmp/sessions", LockDirectory => "/tmp/sessionlock" }
212              
213             For instance, you might instead want to say:
214              
215             $r->config->{auth} = {
216             session_class => "Apache::Session::Flex",
217             session_args => {
218             Store => 'DB_File',
219             Lock => 'Null',
220             Generate => 'MD5',
221             Serialize => 'Storable'
222             }
223             };
224              
225             The cookie name is retrieved from C<{auth}{cookie_name}> but defaults to
226             "sessionid". It defaults to expiry at the end of the session, and this
227             can be set in C<{auth}{cookie_expiry}>.
228              
229             The user class is determined by C<{auth}{user_class}> in the
230             configuration, but attempts to guess the right user class for your
231             application otherwise. Probably best not to depend on that working.
232              
233             The field in the user class which holds the username is stored in
234             C<{auth}{user_field}>, defaulting to "user"; similarly, the
235             C<{auth}{password_field}> defaults to password.
236              
237             =head1 AUTHOR
238              
239             Simon Cozens, C
240              
241             This may be distributed and modified under the same terms as Maypole itself.
242              
243             =head1 SEE ALSO
244              
245             L
246              
247             =cut