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