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 E |
||||||
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 |
||||||
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__ |