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 if ($authnotrequired) {
194 # This script doesn't require the user to be logged in. Return
195 # just the cookie, without user ID or session ID information.
196 my $cookie=$query->cookie(-name => 'sessionID',
199 return('', $cookie, '');
201 # This script requires authorization. Assume that we were
202 # given user and password information; generate a new session.
204 # Generate a new session ID.
205 ($sessionID) || ($sessionID=int(rand()*100000).'-'.time());
206 my $userid=$query->param('userid');
207 my $password=$query->param('password');
208 if (checkpw($dbh, $userid, $password)) {
209 # The given password is valid
210 # Delete any old copies of this session.
211 my $sti=$dbh->prepare("delete from sessions where sessionID=? and userid=?");
212 $sti->execute($sessionID, $userid);
214 # Add this new session to the 'sessions' table.
215 $sti=$dbh->prepare("insert into sessions (sessionID, userid, ip,lasttime) values (?, ?, ?, ?)");
216 $sti->execute($sessionID, $userid, $ENV{'REMOTE_ADDR'}, time());
218 # See if there's an entry for this session ID and user in
219 # the 'sessionqueries' table. If so, then use that entry
220 # to generate an HTTP redirect that'll take the user to
221 # where ve wanted to go in the first place.
222 $sti=$dbh->prepare("select value from sessionqueries where sessionID=? and userid=?");
223 # FIXME - There is no sessionqueries.value
224 $sti->execute($sessionID, $userid);
226 my $stj=$dbh->prepare("delete from sessionqueries where sessionID=?");
227 $stj->execute($sessionID);
228 my ($selfurl) = $sti->fetchrow;
229 print $query->redirect($selfurl);
232 open L, ">>/tmp/sessionlog";
233 my $time=localtime(time());
234 printf L "%20s from %16s logged in at %30s.\n", $userid, $ENV{'REMOTE_ADDR'}, $time;
236 my $cookie=$query->cookie(-name => 'sessionID',
237 -value => $sessionID,
239 return ($userid, $cookie, $sessionID);
241 # Either we weren't given a user id and password, or else
242 # the password was invalid.
244 $message="Invalid userid or password entered.";
247 foreach (param $query) {
248 $parameters->{$_}=$query->{$_};
250 my $cookie=$query->cookie(-name => 'sessionID',
251 -value => $sessionID,
253 return ("",$cookie,$sessionID);
259 # Takes a database handle, user ID, and password, and verifies that
260 # the password is good. The user ID may be either a user ID or a card
262 # Returns 1 if the password is good, or 0 otherwise.
265 # This should be modified to allow a select of authentication schemes (ie LDAP)
266 # as well as local authentication through the borrowers tables passwd field
268 my ($dbh, $userid, $password) = @_;
272 $sth = $dbh->prepare("select password from borrowers where userid=?");
273 $sth->execute($userid);
275 my ($md5password) = $sth->fetchrow;
276 if (md5_base64($password) eq $md5password) {
277 return 1; # The password matches
281 # Try the card number.
282 $sth = $dbh->prepare("select password from borrowers where cardnumber=?");
283 $sth->execute($userid);
285 my ($md5password) = $sth->fetchrow;
286 if (md5_base64($password) eq $md5password) {
287 return 1; # The password matches
290 if ($userid eq C4::Context->config('user') && $password eq C4::Context->config('pass')) {
291 # Koha superuser account
294 return 0; # Either there's no such user, or the password
299 END { } # module clean-up code here (global destructor)