4 # Copyright 2000-2002 Katipo Communications
6 # This file is part of Koha.
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along with
18 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
19 # Suite 330, Boston, MA 02111-1307 USA
22 use Digest::MD5 qw(md5_base64);
28 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
30 # set the version for version checking
35 C4::Auth - Authenticates Koha users
43 ($userid, $cookie, $sessionID) = &checkauth($query);
47 This module provides authentication for Koha users.
62 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth);
64 Verifies that the user is authorized to run this script. Note that
65 C<&checkauth> will return if and only if the user is authorized, so it
66 should be called early on, before any unfinished operations (i.e., if
67 you've opened a file, then C<&checkauth> won't close it for you).
69 C<$query> is the CGI object for the script calling C<&checkauth>.
71 The C<$noauth> argument is optional. If it is set, then no
72 authorization is required for the script.
74 C<&checkauth> fetches user and session information from C<$query> and
75 ensures that the user is authorized to run scripts that require
78 If C<$query> does not have a valid session ID associated with it
79 (i.e., the user has not logged in) or if the session has expired,
80 C<&checkauth> presents the user with a login page (from the point of
81 view of the original script, C<&checkauth> does not return). Once the
82 user has authenticated, C<&checkauth> restarts the original script
83 (this time, C<&checkauth> returns).
85 C<&checkauth> returns a user ID, a cookie, and a session ID. The
86 cookie should be sent back to the browser; it verifies that the user
91 # FIXME - (Or rather, proofreadme)
92 # As I understand it, the 'sessionqueries' table in the Koha database
93 # is supposed to save state while the user authenticates. If
94 # (re-)authentication is required, &checkauth saves the browser's
95 # original call to a new entry in sessionqueries, then presents a form
96 # for the user to authenticate. Once the user has authenticated
97 # visself, &checkauth retrieves the stored information from
98 # sessionqueries and allows the original request to proceed.
100 # One problem, however, is that sessionqueries only stores the URL,
101 # not the various values passed along from an HTML form. Thus, if the
102 # request came from a form and contains information on stuff to change
103 # (e.g., modify the contents of a virtual bookshelf), but the session
104 # has timed out, then when &checkauth finally allows the request to
105 # proceed, it will not contain the user's modifications. This is bad.
107 # Another problem is that entries in sessionqueries are supposed to be
108 # temporary, but there's no mechanism for removing them in case of
109 # error (e.g., the user can't remember vis password and walks away, or
110 # if the user's machine crashes in the middle of authentication).
112 # Perhaps a better implementation would be to use $query->param to get
113 # the parameter with which the original script was invoked, and pass
114 # that along through all of the authentication pages. That way, all of
115 # the pertinent information would be preserved, and the sessionqueries
116 # table could be removed.
120 # $authnotrequired will be set for scripts which will run without authentication
121 my $authnotrequired=shift;
122 if (my $userid=$ENV{'REMOTE_USERNAME'}) {
123 # Using Basic Authentication, no cookies required
124 my $cookie=$query->cookie(-name => 'sessionID',
127 return ($userid, $cookie, '');
129 # Get session ID from cookie.
130 my $sessionID=$query->cookie('sessionID');
131 # FIXME - Error-checking: if the user isn't allowing cookies,
132 # $sessionID will be undefined. Don't confuse this with an
137 # Make sure the session ID is (still) good.
138 my $dbh = C4::Context->dbh;
139 my $sth=$dbh->prepare("select userid,ip,lasttime from sessions where sessionid=?");
140 $sth->execute($sessionID);
142 my ($userid, $ip, $lasttime) = $sth->fetchrow;
143 # FIXME - Back door for tonnensen
144 if ($lasttime<time()-45 && $userid ne 'tonnesen') {
145 # This session has been inactive for >45 seconds, and
146 # doesn't belong to user tonnensen. It has expired.
147 $message="You have been logged out due to inactivity.";
149 # Remove this session ID from the list of active sessions.
150 # FIXME - Ought to have a cron job clean this up as well.
151 my $sti=$dbh->prepare("delete from sessions where sessionID=?");
152 $sti->execute($sessionID);
154 # Add an entry to sessionqueries, so that we can restart
155 # the script once the user has authenticated.
156 my $scriptname=$ENV{'SCRIPT_NAME'}; # FIXME - Unused
157 my $selfurl=$query->self_url();
158 $sti=$dbh->prepare("insert into sessionqueries (sessionID, userid, value) values (?, ?, ?)");
159 $sti->execute($sessionID, $userid, $selfurl);
161 # Log the fact that someone tried to use an expired session ID.
162 # FIXME - Ought to have a better logging mechanism,
163 # ideally some wrapper that logs either to a
164 # user-specified file, or to syslog, as determined by
165 # either an entry in /etc/koha.conf, or a system
167 open L, ">>/tmp/sessionlog";
168 my $time=localtime(time());
169 printf L "%20s from %16s logged out at %30s (inactivity).\n", $userid, $ip, $time;
171 } elsif ($ip ne $ENV{'REMOTE_ADDR'}) {
172 # This session is coming from an IP address other than the
173 # one where it was set. The user might be doing something
175 my $newip=$ENV{'REMOTE_ADDR'};
177 $message="ERROR ERROR ERROR ERROR<br>Attempt to re-use a cookie from a different ip address.<br>(authenticated from $ip, this request from $newip)";
179 # This appears to be a valid session. Update the time
180 # stamp on it and return.
181 my $cookie=$query->cookie(-name => 'sessionID',
182 -value => $sessionID,
184 my $sti=$dbh->prepare("update sessions set lasttime=? where sessionID=?");
185 $sti->execute(time(), $sessionID);
186 return ($userid, $cookie, $sessionID);
189 # If we get this far, it's because we haven't received a cookie
190 # with a valid session ID. Need to start a new session and set a
193 my $insecure = C4::Context->preference("insecure");
195 if ($authnotrequired ||
196 (defined($insecure) && $insecure eq "yes")) {
197 # This script doesn't require the user to be logged in. Return
198 # just the cookie, without user ID or session ID information.
199 my $cookie=$query->cookie(-name => 'sessionID',
202 return('', $cookie, '');
204 # This script requires authorization. Assume that we were
205 # given user and password information; generate a new session.
207 # Generate a new session ID.
208 ($sessionID) || ($sessionID=int(rand()*100000).'-'.time());
209 my $userid=$query->param('userid');
210 my $password=$query->param('password');
211 if (checkpw($dbh, $userid, $password)) {
212 # The given password is valid
213 # Delete any old copies of this session.
214 my $sti=$dbh->prepare("delete from sessions where sessionID=? and userid=?");
215 $sti->execute($sessionID, $userid);
217 # Add this new session to the 'sessions' table.
218 $sti=$dbh->prepare("insert into sessions (sessionID, userid, ip,lasttime) values (?, ?, ?, ?)");
219 $sti->execute($sessionID, $userid, $ENV{'REMOTE_ADDR'}, time());
221 # See if there's an entry for this session ID and user in
222 # the 'sessionqueries' table. If so, then use that entry
223 # to generate an HTTP redirect that'll take the user to
224 # where ve wanted to go in the first place.
225 $sti=$dbh->prepare("select value from sessionqueries where sessionID=? and userid=?");
226 # FIXME - There is no sessionqueries.value
227 $sti->execute($sessionID, $userid);
229 my $stj=$dbh->prepare("delete from sessionqueries where sessionID=?");
230 $stj->execute($sessionID);
231 my ($selfurl) = $sti->fetchrow;
232 print $query->redirect($selfurl);
235 open L, ">>/tmp/sessionlog";
236 my $time=localtime(time());
237 printf L "%20s from %16s logged in at %30s.\n", $userid, $ENV{'REMOTE_ADDR'}, $time;
239 my $cookie=$query->cookie(-name => 'sessionID',
240 -value => $sessionID,
242 return ($userid, $cookie, $sessionID);
244 # Either we weren't given a user id and password, or else
245 # the password was invalid.
247 $message="Invalid userid or password entered.";
250 foreach (param $query) {
251 $parameters->{$_}=$query->{$_};
253 my $cookie=$query->cookie(-name => 'sessionID',
254 -value => $sessionID,
256 return ("",$cookie,$sessionID);
262 # Takes a database handle, user ID, and password, and verifies that
263 # the password is good. The user ID may be either a user ID or a card
265 # Returns 1 if the password is good, or 0 otherwise.
268 # This should be modified to allow a select of authentication schemes (ie LDAP)
269 # as well as local authentication through the borrowers tables passwd field
271 my ($dbh, $userid, $password) = @_;
275 $sth = $dbh->prepare("select password from borrowers where userid=?");
276 $sth->execute($userid);
278 my ($md5password) = $sth->fetchrow;
279 if (md5_base64($password) eq $md5password) {
280 return 1; # The password matches
284 # Try the card number.
285 $sth = $dbh->prepare("select password from borrowers where cardnumber=?");
286 $sth->execute($userid);
288 my ($md5password) = $sth->fetchrow;
289 if (md5_base64($password) eq $md5password) {
290 return 1; # The password matches
293 if ($userid eq C4::Context->config('user') && $password eq C4::Context->config('pass')) {
294 # Koha superuser account
297 return 0; # Either there's no such user, or the password
302 END { } # module clean-up code here (global destructor)