File Coverage

blib/lib/CGI/Authen/Simple.pm
Criterion Covered Total %
statement 12 71 16.9
branch 0 52 0.0
condition 0 9 0.0
subroutine 4 7 57.1
pod 3 3 100.0
total 19 142 13.3


line stmt bran cond sub pod time code
1             package CGI::Authen::Simple;
2              
3 1     1   23449 use strict;
  1         2  
  1         31  
4 1     1   5749 use CGI;
  1         29172  
  1         7  
5 1     1   1970 use CGI::Cookie;
  1         2686  
  1         31  
6 1     1   3035 use Template;
  1         31832  
  1         1233  
7              
8             =head1 NAME
9              
10             CGI::Authen::Simple - Simple cookie-driven unsessioned form-based authentication
11              
12             =head1 SYNOPSIS
13              
14             use CGI::Authen::Simple;
15              
16             my $auth = CGI::Authen::Simple->new();
17             $auth->logged_in() || $auth->auth();
18              
19             # do stuff here
20              
21             # if you need it, you can access the user's credentials like so:
22             my $username = $auth->{'profile'}->{'username'};
23              
24             # assume your account table had other attributes, like full_name char(64)
25             my $fullname = $auth->{'profile'}->{'full_name'};
26              
27             # their password is never returned in plain text
28             print $auth->{'profile'}->{'password'};
29             # prints the MySQL hash of their password
30              
31             =head1 DESCRIPTION
32              
33             This module provides extremely simple forms-based authentication for web
34             applications. It has reasonable defaults set, and if your database conforms
35             to those defaults, you can instantiate a new object with no parameters, and
36             it will handle all the authentication and cookie settings for you.
37              
38             =head1 METHODS
39              
40             =cut
41              
42             our $VERSION = '1.0';
43              
44             =over
45              
46             =item B
47              
48             Returns a new CGI::Authen::Simple object. Accepts a single hashref as a parameter. The hashref contains config information:
49              
50             =over
51              
52             =item *
53             dbh - a DBI database handle to the database containing the account information. REQUIRED.
54              
55             =item *
56             EXIT_ON_DISPLAY - if auth() is required to draw a page, should it exit()? Defaults to true.
57             If you are running mod_perl, I recommend you set this to 0, and wrap your auth-protected code
58             in a logged_in() check. See the documentation for auth().
59              
60             =item *
61             USERID - the database column containing a unique account ID. The ID can be anything, however I
62             recommend a unique integer ID.
63              
64             =item *
65             USERNAME - the column corresponding to their username. Usernames do not have to be unique, however
66             username/password pairs must be unique or you will get potentially unexpected results.
67              
68             =item *
69             PASSWORD - the column in the database corresponding to the user's password.
70              
71             =item *
72             HASH_FUNC - one of ('none','old_password','password','md5','sha','sha1').
73             These correspond to their named hashing functions in mysql. If your passwords are stored as
74             plaintext in the database, use none. Encrypted passwords are not currently supported.
75             Default: none
76              
77             =item *
78             TABLE - the name of the table that contains the above three columns.
79              
80             =item *
81             HTML_TITLE - the title for the page. Defaults to lc($ENV{'HTTP_HOST'}) . ' : please log in';
82              
83             =item *
84             HTML_HEADER - HTML that will be printed inside a header block for the page. Same default as HTML_TITLE
85              
86             =item *
87             HTML_FOOTER - HTML that will be printed inside a footer block for the page. Defaults to
88             Login handled by CGI::Authen::Simple version $VERSION
89              
90             =item *
91             ext_auth - code reference. The function called by this reference can do anything it has access to do,
92             and is expected to return a username and password to be authenticated. This is useful for example, if
93             you wanted to log people in via SSL certificates or UserAgent settings. For example, you could check
94             their UserAgent in the function, and derive a username and password from it -- or you could find out what
95             client certificate someone has connected using on an SSL-enabled webserver, and derive a username and
96             password from that.
97              
98             =back
99              
100             =cut
101              
102             sub new
103             {
104 0     0 1   my ($pkg, $args) = @_;
105              
106             # a DBH is necessary
107 0 0         die "You must pass in a database handle" if !defined $args->{'dbh'};
108              
109             # do we exit if auth is required to display an HTML page?
110 0 0         $args->{'EXIT_ON_DISPLAY'} = 1 if !defined $args->{'EXIT_ON_DISPLAY'};
111              
112             # database settings
113 0 0         $args->{'USERID'} = 'id' if !defined $args->{'USERID'};
114 0 0         $args->{'USERNAME'} = 'username' if !defined $args->{'USERNAME'};
115 0 0         $args->{'PASSWORD'} = 'password' if !defined $args->{'PASSWORD'};
116 0 0         $args->{'HASH_FUNC'} = 'none' if !defined $args->{'HASH_FUNC'};
117 0 0         if($args->{'HASH_FUNC'} !~ /^(?:none|(?:old_)password|md5|sha1?)$/i)
118             {
119 0           warn "Invalid hash function passed in, defaulting to 'none'";
120 0           $args->{'HASH_FUNC'} = 'none';
121             }
122 0 0         $args->{'TABLE'} = 'accounts' if !defined $args->{'TABLE'};
123              
124             # HTML things
125 0 0         $args->{'HTML_TITLE'} = lc($ENV{'HTTP_HOST'}) . ' : please log in' if !defined $args->{'HTML_TITLE'};
126 0 0         $args->{'HTML_HEADER'} = '

' . lc($ENV{'HTTP_HOST'}) . ' : please log in

' if !defined $args->{'HTML_HEADER'};
127 0 0         $args->{'HTML_FOOTER'} = '

Login handled by CGI::Authen::Simple '

128             . 'version ' . $VERSION . '

' if !defined $args->{'HTML_FOOTER'};
129              
130 0           my $self = bless { %$args, logged_in => 0, profile => {} }, $pkg;
131              
132 0           return $self;
133             }
134              
135             =item B
136              
137             Uses cookies to determine if a user is logged in. Returns true if user is logged in. If a row is retrieved from the DB,
138             then all the columns making up the row for that user in the accounts table will be pulled and stored as the user's profile,
139             which is accessible as a hashref via $auth->{'profile'}.
140              
141             =cut
142              
143             sub logged_in
144             {
145 0     0 1   my $self = shift;
146 0           my $to_return = 1;
147              
148 0 0         if(!$self->{'logged_in'})
149             {
150 0           my (%cookie) = fetch CGI::Cookie;
151              
152 0           foreach ( qw(userid username password) )
153             {
154 0 0 0       if(!exists($cookie{$_}) || $cookie{$_}->value eq '')
155             {
156 0           $to_return = 0;
157 0           last;
158             }
159             }
160              
161 0 0         if($to_return == 1)
162             {
163 0 0         my $ph = ($self->{'HASH_FUNC'} =~ /none/i)
164             ? ", " . uc($self->{'HASH_FUNC'}) . "($self->{'PASSWORD'}) AS $self->{'PASSWORD'}"
165             : '';
166              
167 0 0         my $wph = ($self->{'HASH_FUNC'} !~ /none/i)
168             ? "$self->{'PASSWORD'} = ?"
169             : uc($self->{'HASH_FUNC'}) . "($self->{'PASSWORD'}) = ?";
170              
171 0           my $profile = $self->{'dbh'}->selectrow_hashref('SELECT *' . $ph . ' FROM ' . $self->{'TABLE'} . ' WHERE ' . $self->{'USERID'} . ' = ? AND ' . $self->{'USERNAME'} . ' = ? AND ' . $wph, undef, $cookie{'userid'}->value, $cookie{'username'}->value, $cookie{'password'}->value);
172              
173 0 0         if(!$profile)
174             {
175 0           $to_return = 0;
176             }
177             else
178             {
179 0           $self->{'profile'} = $profile;
180             }
181             }
182              
183 0           $self->{'logged_in'} = $to_return;
184             }
185              
186 0           return $to_return;
187             }
188              
189             =item B
190              
191             Authenticates a user if data was posted containing a username and password pair. If authentication was unsuccessful or
192             they did not pass a username/password pair, they are displayed a login screen. If we retrieve a row (valid username
193             and password), then grab the rest of the columns from that table, and store them internally as the user's profile.
194              
195             Note: If a login screen is displayed, the value of EXIT_ON_DISPLAY is checked. B
196             then the function will exit. This is the default behaviour.> As far as I am aware, this is highly undesirable in
197             mod_perl applications, so please be sure you've taken that into consideration. If EXIT_ON_DISPLAY is set to false,
198             the function will not exit, and control will be returned to the calling script. In this case, please wrap your code
199             in a surrounding:
200              
201             if($auth->logged_in())
202             {
203             # do stuff here
204             }
205              
206             code block, or else you will be displaying not only the auth screen, but anything that would be displayed by your code.
207              
208             =cut
209              
210             sub auth
211             {
212 0     0 1   my $self = shift;
213 0           my $cgi = new CGI;
214              
215 0           my $vars = {
216             HTML_HEADER => $self->{'HTML_HEADER'},
217             HTML_FOOTER => $self->{'HTML_FOOTER'},
218             HTML_TITLE => $self->{'HTML_TITLE'},
219             };
220              
221 0           my $username = $cgi->param('username');
222 0           my $password = $cgi->param('password');
223              
224             # if we don't have a username and password from CGI, check for an external auth mechanism to provide a username and password
225 0 0 0       if(!$username || !$password)
226             {
227 0 0         if(defined $self->{'ext_auth'})
228             {
229 0           ($username, $password) = $self->{'ext_auth'}->();
230             }
231             }
232              
233 0 0 0       if($username && $password)
234             {
235 0 0         my $ph = ($self->{'HASH_FUNC'} =~ /none/i)
236             ? ", " . uc($self->{'HASH_FUNC'}) . "($self->{'PASSWORD'}) AS $self->{'PASSWORD'}"
237             : '';
238              
239 0 0         my $wph = ($self->{'HASH_FUNC'} !~ /none/i)
240             ? "$self->{'PASSWORD'} = " . uc($self->{'HASH_FUNC'}) . "(?)"
241             : "$self->{'PASSWORD'} = ?";
242              
243 0           my $profile = $self->{'dbh'}->selectrow_hashref('SELECT *' . $ph
244             . ' FROM ' . $self->{'TABLE'} . ' WHERE '
245             . $self->{'USERNAME'} . ' = ? AND ' . $wph,
246             undef, $username, $password);
247              
248 0 0         if($profile)
249             {
250 0           my $username_cookie = new CGI::Cookie( -name=> 'username', -value => $profile->{'username'} );
251 0           my $password_cookie = new CGI::Cookie( -name=> 'password', -value => $profile->{'password'} );
252 0           my $userid_cookie = new CGI::Cookie( -name=> 'userid', -value => $profile->{'id'} );
253              
254 0           print qq!Set-Cookie: $username_cookie\nSet-Cookie: $password_cookie\nSet-Cookie: $userid_cookie\n!;
255 0           $self->{'logged_in'} = 1;
256 0           $self->{'profile'} = $profile;
257             }
258             else
259             {
260 0           $vars->{'login_failed'} = 1;
261             }
262             }
263              
264 0 0         if(!$self->logged_in)
265             {
266 0           my $template = Template->new();
267 0           print $cgi->header;
268 0 0         $template->process(\*DATA, $vars) or die $template->error();
269              
270 0 0         if($self->{'EXIT_ON_DISPLAY'})
271             {
272 0           exit;
273             }
274             }
275             }
276              
277             1;
278              
279             =back
280              
281             =head1 TODO
282              
283             - template / CSS overrides
284             - needs to work with any DB software (since it just takes a DBH, maybe use SQL::Abstract to generate a
285             cross DB compatible query.
286              
287             =head1 SEE ALSO
288              
289             CGI::Cookie, CGI, Template
290              
291             =head1 AUTHOR
292              
293             Shane Allen Eopiate@gmail.comE
294              
295             =head1 ACKNOWLEDGEMENTS
296              
297             =over
298              
299             =item *
300             This core functionality of this module was developed during my employ at
301             HRsmart, Inc. L and its public release was
302             graciously approved.
303              
304             =back
305              
306             =head1 COPYRIGHT
307              
308             Copyright 2005, Shane Allen. All rights reserved.
309              
310             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
311              
312             =cut
313              
314             __DATA__