summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorbbaetz%acm.org <>2003-03-22 13:47:09 +0100
committerbbaetz%acm.org <>2003-03-22 13:47:09 +0100
commit681ce77bc0dc5828eae2bb48471db9e373437e4b (patch)
treea7c8ba0b1e070ea489c96246eca65fc7c36f6235 /Bugzilla
parent3f1f4e57809b2e3f42e637a86646e806470faec5 (diff)
downloadbugzilla-681ce77bc0dc5828eae2bb48471db9e373437e4b.tar.gz
bugzilla-681ce77bc0dc5828eae2bb48471db9e373437e4b.tar.xz
Bug 180642 - Move authentication code into a module
r=gerv, justdave a=justdave
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Auth.pm214
-rw-r--r--Bugzilla/Auth/CGI.pm195
-rw-r--r--Bugzilla/Auth/Cookie.pm119
-rw-r--r--Bugzilla/Auth/DB.pm102
-rw-r--r--Bugzilla/Auth/LDAP.pm185
-rw-r--r--Bugzilla/Config.pm5
-rw-r--r--Bugzilla/Constants.pm25
-rw-r--r--Bugzilla/DB.pm5
-rw-r--r--Bugzilla/Token.pm21
9 files changed, 855 insertions, 16 deletions
diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm
new file mode 100644
index 000000000..902ae0f05
--- /dev/null
+++ b/Bugzilla/Auth.pm
@@ -0,0 +1,214 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Bradley Baetz <bbaetz@acm.org>
+
+package Bugzilla::Auth;
+
+use strict;
+
+use Bugzilla::Config;
+use Bugzilla::Constants;
+
+# 'inherit' from the main loginmethod
+BEGIN {
+ my $loginmethod = Param("loginmethod");
+ require "Bugzilla/Auth/" . $loginmethod . ".pm";
+
+ our @ISA;
+ push (@ISA, "Bugzilla::Auth::" . $loginmethod);
+}
+
+# PRIVATE
+
+# Returns the network address for a given ip
+sub get_netaddr {
+ my $ipaddr = shift;
+
+ # Check for a valid IPv4 addr which we know how to parse
+ if (!$ipaddr || $ipaddr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
+ return undef;
+ }
+
+ my $addr = unpack("N", pack("CCCC", split(/\./, $ipaddr)));
+
+ my $maskbits = Param('loginnetmask');
+
+ $addr >>= (32-$maskbits);
+ $addr <<= (32-$maskbits);
+ return join(".", unpack("CCCC", pack("N", $addr)));
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Auth - Authentication handling for Bugzilla users
+
+=head1 DESCRIPTION
+
+Handles authentication for Bugzilla users.
+
+Authentication from Bugzilla involves two sets of modules. One set is used to
+obtain the data (from CGI, email, etc), and the other set uses this data to
+authenticate against the datasource (the Bugzilla DB, LDAP, cookies, etc).
+
+The handlers for the various types of authentication (DB/LDAP/cookies/etc)
+provide the actual code for each specific method of authentication.
+
+The source modules (currently, only L<Bugzilla::Auth::CGI|Bugzilla::Auth::CGI>
+then use those methods to do the authentication.
+
+I<Bugzilla::Auth> itself inherits from the default authentication handler,
+identified by the I<loginmethod> param.
+
+=head1 METHODS
+
+C<Bugzilla::Auth> contains several helper methods to be used by
+authentication or login modules.
+
+=over 4
+
+=item C<Bugzilla::Auth::get_netaddr($ipaddr)>
+
+Given an ip address, this returns the associated network address, using
+C<Param('loginnetmask')> at the netmask. This can be used to obtain data in
+order to restrict weak authentication methods (such as cookies) to only some
+addresses.
+
+=back
+
+=head1 AUTHENTICATION
+
+Authentication modules check a users's credentials (username, password, etc) to
+verify who the user is.
+
+=head2 METHODS
+
+=over 4
+
+=item C<authenticate($username, $pass)>
+
+This method is passed a username and a password, and returns a list containing
+up to four return values, depending on the results of the authentication.
+
+The first return value is one of the status codes defined in
+L<Bugzilla::Constants|Bugzilla::Constants> and described below. The rest of
+the return values are status code-specific and are explained in the status
+code descriptions.
+
+=over 4
+
+=item C<AUTH_OK>
+
+Authentication succeeded. The second variable is the userid of the new user.
+
+=item C<AUTH_NODATA>
+
+Insufficient login data was provided by the user. This may happen in several
+cases, such as cookie authentication when the cookie is not present.
+
+=item C<AUTH_ERROR>
+
+An error occurred when trying to use the login mechanism. The second return
+value may contain the Bugzilla userid, but will probably be C<undef>,
+signifiying that the userid is unknown. The third value is a tag describing
+the error used by the authentication error templates to print a description
+to the user. The optional fourth argument is a hashref of values used as part
+of the tag's error descriptions.
+
+This error template must have a name/location of
+I<account/auth/C<lc(authentication-type)>-error.html.tmpl>.
+
+=item C<AUTH_LOGINFAILED>
+
+An incorrect username or password was given. Note that for security reasons,
+both cases return the same error code. However, in the case of a valid
+username, the second argument may be the userid. The authentication
+mechanism may not always be able to discover the userid if the password is
+not known, so whether or not this argument is present is implementation
+specific. For security reasons, the presence or lack of a userid value should
+not be communicated to the user.
+
+The third argument is an optional tag from the authentication server
+describing the error. The tag can be used by a template to inform the user
+about the error. Similar to C<AUTH_ERROR>, an optional hashref may be
+present as a fourth argument, to be used by the tag to give more detailed
+information.
+
+=item C<AUTH_DISABLED>
+
+The user successfully logged in, but their account has been disabled. The
+second argument in the returned array is the userid, and the third is some
+text explaining why the account was disabled. This text would typically come
+from the C<disabledtext> field in the C<profiles> table. Note that this
+argument is a string, not a tag.
+
+=back
+
+=item C<can_edit>
+
+This determines if the user's account details can be modified. If this
+method returns a C<true> value, then accounts can be created and modified
+through the Bugzilla user interface. Forgotten passwords can also be
+retrieved through the L<Token interface|Token>.
+
+=back
+
+=head1 LOGINS
+
+A login module can be used to try to log in a Bugzilla user in a particular
+way. For example, L<Bugzilla::Auth::CGI|Bugzilla::Auth::CGI> logs in users
+from CGI scripts, first by trying database authentication against the
+Bugzilla C<profiles> table, and then by trying cookies as a fallback.
+
+A login module consists of a single method, C<login>, which takes a C<$type>
+argument, using constants found in C<Bugzilla::Constants>.
+
+=over 4
+
+=item C<LOGIN_OPTIONAL>
+
+A login is never required to access this data. Attempting to login is still
+useful, because this allows the page to be personalised. Note that an
+incorrect login will still trigger an error, even though the lack of a login
+will be OK.
+
+=item C<LOGIN_NORMAL>
+
+A login may or may not be required, depending on the setting of the
+I<requirelogin> parameter.
+
+=item C<LOGIN_REQUIRED>
+
+A login is always required to access this data.
+
+=back
+
+The login module uses various authentication modules to try to authenticate
+a user, and returns the userid on success, or C<undef> on failure.
+
+When a login is required, but data is not present, it is the job of the login
+module to prompt the user for this data.
+
+=head1 SEE ALSO
+
+L<Bugzilla::Auth::CGI>, L<Bugzilla::Auth::Cookie>, L<Bugzilla::Auth::DB>
diff --git a/Bugzilla/Auth/CGI.pm b/Bugzilla/Auth/CGI.pm
new file mode 100644
index 000000000..b7c2e6c42
--- /dev/null
+++ b/Bugzilla/Auth/CGI.pm
@@ -0,0 +1,195 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+
+package Bugzilla::Auth::CGI;
+
+use strict;
+
+use Bugzilla::Config;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+
+sub login {
+ my ($class, $type) = @_;
+
+ # 'NORMAL' logins depend on the 'requirelogin' param
+ if ($type == LOGIN_NORMAL) {
+ $type = Param('requirelogin') ? LOGIN_REQUIRED : LOGIN_OPTIONAL;
+ }
+
+ my $cgi = Bugzilla->cgi;
+
+ # First, try the actual login method against form variables
+ my $username = $cgi->param("Bugzilla_login");
+ my $passwd = $cgi->param("Bugzilla_password");
+
+ my $authmethod = Param("loginmethod");
+ my ($authres, $userid, $extra, $info) =
+ Bugzilla::Auth->authenticate($username, $passwd);
+
+ if ($authres == AUTH_OK) {
+ # Login via username/password was correct and valid, so create
+ # and send out the login cookies
+ my $ipaddr = $cgi->remote_addr;
+ unless ($cgi->param('Bugzilla_restrictlogin') ||
+ Param('loginnetmask') == 32) {
+ $ipaddr = get_netaddr($ipaddr);
+ }
+
+ # The IP address is valid, at least for comparing with itself in a
+ # subsequent login
+ trick_taint($ipaddr);
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT INTO logincookies (userid, ipaddr) VALUES (?, ?)",
+ undef,
+ $userid, $ipaddr);
+ my $logincookie = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
+ my $cookiepath = Param("cookiepath");
+ print "Set-Cookie: Bugzilla_login=$userid ; path=$cookiepath; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
+ print "Set-Cookie: Bugzilla_logincookie=$logincookie ; path=$cookiepath; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
+
+ # compat code. The cookie value is used for logouts, and that
+ # isn't generic yet.
+ $::COOKIE{'Bugzilla_logincookie'} = $logincookie;
+ } elsif ($authres == AUTH_NODATA) {
+ # No data from the form, so try to login via cookies
+ $username = $cgi->cookie("Bugzilla_login");
+ $passwd = $cgi->cookie("Bugzilla_logincookie");
+
+ require Bugzilla::Auth::Cookie;
+ my $authmethod = "Cookie";
+
+ ($authres, $userid, $extra) =
+ Bugzilla::Auth::Cookie->authenticate($username, $passwd);
+
+ # If the data for the cookie was incorrect, then treat that as
+ # NODATA. This could occur if the user's IP changed, for example.
+ # Give them un-loggedin access if allowed (checked below)
+ $authres = AUTH_NODATA if $authres == AUTH_LOGINFAILED;
+ }
+
+ # Now check the result
+
+ # An error may have occurred with the login mechanism
+ if ($authres == AUTH_ERROR) {
+ $::vars->{'authmethod'} = lc($authmethod);
+ $::vars->{'userid'} = $userid;
+ $::vars->{'auth_err_tag'} = $extra;
+ $::vars->{'info'} = $info;
+
+ &::ThrowCodeError("auth_err");
+ }
+
+ # We can load the page if the login was ok, or there was no data
+ # but a login wasn't required
+ if ($authres == AUTH_OK ||
+ ($authres == AUTH_NODATA && $type == LOGIN_OPTIONAL)) {
+
+ # login succeded, so we're done
+ return $userid;
+ }
+
+ # No login details were given, but we require a login if the
+ # page does
+ if ($authres == AUTH_NODATA && $type == LOGIN_REQUIRED) {
+ # Throw up the login page
+
+ print "Content-Type: text/html\n\n";
+
+ my $template = Bugzilla->template;
+ $template->process("account/auth/login.html.tmpl",
+ { 'target' => $cgi->url(-relative=>1),
+ 'form' => \%::FORM,
+ 'mform' => \%::MFORM,
+ 'caneditaccount' => Bugzilla::Auth->can_edit,
+ }
+ )
+ || &::ThrowTemplateError($template->error());
+
+ # This seems like as good as time as any to get rid of old
+ # crufty junk in the logincookies table. Get rid of any entry
+ # that hasn't been used in a month.
+ Bugzilla->dbh->do("DELETE FROM logincookies " .
+ "WHERE TO_DAYS(NOW()) - TO_DAYS(lastused) > 30");
+
+ exit;
+ }
+
+ # The username/password may be wrong
+ # Don't let the user know whether the username exists or whether
+ # the password was just wrong. (This makes it harder for a cracker
+ # to find account names by brute force)
+ if ($authres == AUTH_LOGINFAILED) {
+ &::ThrowUserError("invalid_username_or_password");
+ }
+
+ # The account may be disabled
+ if ($authres == AUTH_DISABLED) {
+ # Clear the cookie
+ my $cookiepath = Param("cookiepath");
+ print "Set-Cookie: Bugzilla_login= ; path=$cookiepath; expires=Sun, 30-Jun-80 00:00:00 GMT\n";
+ print "Set-Cookie: Bugzilla_logincookie= ; path=$cookiepath; expires=Sun, 30-Jun-80 00:00:00 GMT\n";
+ # and throw a user error
+ &::ThrowUserError("account_disabled",
+ {'disabled_reason' => $extra});
+ }
+
+ # If we get here, then we've run out of options, which shouldn't happen
+ &::ThrowCodeError("authres_unhandled",
+ { authres => $authres,
+ type => $type,
+ }
+ );
+
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Auth::CGI - CGI-based logins for Bugzilla
+
+=head1 SUMMARY
+
+This is a L<login module|Bugzilla::Auth/"LOGIN"> for Bugzilla. Users connecting
+from a CGI script use this module to authenticate.
+
+=head1 BEHAVIOUR
+
+Users are first authenticated against the default authentication handler,
+using the CGI parameters I<Bugzilla_login> and I<Bugzilla_password>.
+
+If no data is present for that, then cookies are tried, using
+L<Bugzilla::Auth::Cookie>.
+
+=head1 SEE ALSO
+
+L<Bugzilla::Auth>
diff --git a/Bugzilla/Auth/Cookie.pm b/Bugzilla/Auth/Cookie.pm
new file mode 100644
index 000000000..7dd2967fb
--- /dev/null
+++ b/Bugzilla/Auth/Cookie.pm
@@ -0,0 +1,119 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+
+package Bugzilla::Auth::Cookie;
+
+use strict;
+
+use Bugzilla::Auth;
+use Bugzilla::Config;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+
+sub authenticate {
+ my ($class, $login, $login_cookie) = @_;
+
+ return (AUTH_NODATA) unless defined $login && defined $login_cookie;
+
+ my $cgi = Bugzilla->cgi;
+
+ my $ipaddr = $cgi->remote_addr();
+ my $netaddr = Bugzilla::Auth::get_netaddr($ipaddr);
+
+ # Anything goes for these params - they're just strings which
+ # we're going to verify against the db
+ trick_taint($login);
+ trick_taint($login_cookie);
+ trick_taint($ipaddr);
+
+ my $query = "SELECT profiles.userid, profiles.disabledtext " .
+ "FROM logincookies, profiles " .
+ "WHERE logincookies.cookie=? AND " .
+ " logincookies.userid=profiles.userid AND " .
+ " logincookies.userid=? AND " .
+ " (logincookies.ipaddr=?";
+ if (defined $netaddr) {
+ trick_taint($netaddr);
+ $query .= " OR logincookies.ipaddr=?";
+ }
+ $query .= ")";
+
+ my $dbh = Bugzilla->dbh;
+ my ($userid, $disabledtext) = $dbh->selectrow_array($query, undef,
+ $login_cookie,
+ $login,
+ $ipaddr,
+ $netaddr);
+
+ return (AUTH_DISABLED, $userid, $disabledtext)
+ if ($disabledtext);
+
+ if ($userid) {
+ # If we logged in successfully, then update the lastused time on the
+ # login cookie
+ $dbh->do("UPDATE logincookies SET lastused=NULL WHERE cookie=?",
+ undef,
+ $login_cookie);
+
+ # compat code. The cookie value is used for logouts, and that
+ # isn't generic yet. Detaint it so that its usable
+ detaint_natural($::COOKIE{'Bugzilla_logincookie'});
+
+ return (AUTH_OK, $userid);
+ }
+
+ # If we get here, then the login failed.
+ return (AUTH_LOGINFAILED);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Cookie - cookie authentication for Bugzilla
+
+=head1 SUMMARY
+
+This is an L<authentication module|Bugzilla::Auth/"AUTHENTICATION"> for
+Bugzilla, which logs the user in using a persistent cookie stored in the
+C<logincookies> table.
+
+The actual password is not stored in the cookie; only the userid and a
+I<logincookie> (which is used to reverify the login without requiring the
+password to be sent over the network) are. These I<logincookies> are
+restricted to certain IP addresses as a security meaure. The exact
+restriction can be specified by the admin via the C<loginnetmask> parameter.
+
+This module does not ever send a cookie (It has no way of knowing when a user
+is successfully logged in). Instead L<Bugzilla::Auth::CGI> handles this.
+
+=head1 SEE ALSO
+
+L<Bugzilla::Auth>, L<Bugzilla::Auth::CGI>
diff --git a/Bugzilla/Auth/DB.pm b/Bugzilla/Auth/DB.pm
new file mode 100644
index 000000000..55e4bc7c0
--- /dev/null
+++ b/Bugzilla/Auth/DB.pm
@@ -0,0 +1,102 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+
+package Bugzilla::Auth::DB;
+
+use strict;
+
+use Bugzilla::Config;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+
+sub authenticate {
+ my ($class, $username, $passwd) = @_;
+
+ return (AUTH_NODATA) unless defined $username && defined $passwd;
+
+ my $dbh = Bugzilla->dbh;
+
+ # We're just testing against the db, so any value is ok
+ trick_taint($username);
+
+ # Retrieve the user's ID and crypted password from the database.
+ my $sth = $dbh->prepare_cached("SELECT userid,cryptpassword,disabledtext " .
+ "FROM profiles " .
+ "WHERE login_name=?");
+ my ($userid, $realcryptpwd, $disabledtext) =
+ $dbh->selectrow_array($sth,
+ undef,
+ $username);
+
+ # If the user doesn't exist, return now
+ return (AUTH_LOGINFAILED) unless defined $userid;
+
+ # OK, now authenticate the user
+
+ # Get the salt from the user's crypted password.
+ my $salt = $realcryptpwd;
+
+ # Using the salt, crypt the password the user entered.
+ my $enteredCryptedPassword = crypt($passwd, $salt);
+
+ # Make sure the passwords match or return an error
+ return (AUTH_LOGINFAILED, $userid) unless
+ ($enteredCryptedPassword eq $realcryptpwd);
+
+ # Now we know that the user has logged in successfully,
+ # so delete any password tokens for them
+ require Token;
+ Token::DeletePasswordTokens("user logged in");
+
+ # The user may have had their account disabled
+ return (AUTH_DISABLED, $userid, $disabledtext)
+ if $disabledtext ne '';
+
+ # If we get to here, then the user is allowed to login, so we're done!
+ return (AUTH_OK, $userid);
+}
+
+sub can_edit { return 1; }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::DB - database authentication for Bugzilla
+
+=head1 SUMMARY
+
+This is an L<authentication module|Bugzilla::Auth/"AUTHENTICATION"> for
+Bugzilla, which logs the user in using the password stored in the C<profiles>
+table. This is the most commonly used authentication module.
+
+=head1 SEE ALSO
+
+L<Bugzilla::Auth>
diff --git a/Bugzilla/Auth/LDAP.pm b/Bugzilla/Auth/LDAP.pm
new file mode 100644
index 000000000..4570bdde9
--- /dev/null
+++ b/Bugzilla/Auth/LDAP.pm
@@ -0,0 +1,185 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Dan Mosedale <dmose@mozilla.org>
+# Joe Robins <jmrobins@tgix.com>
+# Dave Miller <justdave@syndicomm.com>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Christian Reis <kiko@async.com.br>
+# Bradley Baetz <bbaetz@acm.org>
+
+package Bugzilla::Auth::LDAP;
+
+use strict;
+
+use Bugzilla::Config;
+use Bugzilla::Constants;
+
+use Net::LDAP;
+
+sub authenticate {
+ my ($class, $username, $passwd) = @_;
+
+ # If no password was provided, then fail the authentication.
+ # While it may be valid to not have an LDAP password, when you
+ # bind without a password (regardless of the binddn value), you
+ # will get an anonymous bind. I do not know of a way to determine
+ # whether a bind is anonymous or not without making changes to the
+ # LDAP access control settings
+ return (AUTH_NODATA) unless $username && $passwd;
+
+ # We need to bind anonymously to the LDAP server. This is
+ # because we need to get the Distinguished Name of the user trying
+ # to log in. Some servers (such as iPlanet) allow you to have unique
+ # uids spread out over a subtree of an area (such as "People"), so
+ # just appending the Base DN to the uid isn't sufficient to get the
+ # user's DN. For servers which don't work this way, there will still
+ # be no harm done.
+ my $LDAPserver = Param("LDAPserver");
+ if ($LDAPserver eq "") {
+ return (AUTH_ERROR, undef, "server_not_defined");
+ }
+
+ my $LDAPport = "389"; # default LDAP port
+ if($LDAPserver =~ /:/) {
+ ($LDAPserver, $LDAPport) = split(":",$LDAPserver);
+ }
+ my $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
+ if(!$LDAPconn) {
+ return (AUTH_ERROR, undef, "connect_failed");
+ }
+
+ my $mesg;
+ if (Param("LDAPbinddn")) {
+ my ($LDAPbinddn,$LDAPbindpass) = split(":",Param("LDAPbinddn"));
+ $mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass);
+ }
+ else {
+ $mesg = $LDAPconn->bind();
+ }
+ if($mesg->code) {
+ return (AUTH_ERROR, undef,
+ "connect_failed",
+ { errstr => $mesg->err });
+ }
+
+ # We've got our anonymous bind; let's look up this user.
+ $mesg = $LDAPconn->search( base => Param("LDAPBaseDN"),
+ scope => "sub",
+ filter => Param("LDAPuidattribute") . "=$username",
+ attrs => ['dn'],
+ );
+ return (AUTH_LOGINFAILED, undef, "lookup_failure")
+ unless $mesg->count;
+
+ # Now we get the DN from this search.
+ my $userDN = $mesg->shift_entry->dn;
+
+ # Now we attempt to bind as the specified user.
+ $mesg = $LDAPconn->bind( $userDN, password => $passwd);
+
+ return (AUTH_LOGINFAILED) if $mesg->code;
+
+ # And now we're going to repeat the search, so that we can get the
+ # mail attribute for this user.
+ $mesg = $LDAPconn->search( base => Param("LDAPBaseDN"),
+ scope => "sub",
+ filter => Param("LDAPuidattribute") . "=$username",
+ );
+ my $user_entry = $mesg->shift_entry if !$mesg->code && $mesg->count;
+ if(!$user_entry || !$user_entry->exists(Param("LDAPmailattribute"))) {
+ return (AUTH_ERROR, undef,
+ "cannot_retreive_attr",
+ { attr => Param("LDAPmailattribute") });
+ }
+
+ # get the mail attribute
+ $username = $user_entry->get_value(Param("LDAPmailattribute"));
+ # OK, so now we know that the user is valid. Lets try finding them in the
+ # Bugzilla database
+
+ # XXX - should this part be made more generic, and placed in
+ # Bugzilla::Auth? Lots of login mechanisms may have to do this, although
+ # until we actually get some more, its hard to know - BB
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare_cached("SELECT userid, disabledtext " .
+ "FROM profiles " .
+ "WHERE login_name=?");
+ my ($userid, $disabledtext) =
+ $dbh->selectrow_array($sth,
+ undef,
+ $username);
+
+ # If the user doesn't exist, then they need to be added
+ unless ($userid) {
+ # We'll want the user's name for this.
+ my $userRealName = $user_entry->get_value("displayName");
+ if($userRealName eq "") {
+ $userRealName = $user_entry->get_value("cn");
+ }
+ &::InsertNewUser($username, $userRealName);
+
+ my ($userid, $disabledtext) = $dbh->selectrow_array($sth,
+ undef,
+ $username);
+ return (AUTH_ERROR, $userid, "no_userid")
+ unless $userid;
+ }
+
+ # we're done, so disconnect
+ $LDAPconn->unbind;
+
+ # Test for disabled account
+ return (AUTH_DISABLED, $userid, $disabledtext)
+ if $disabledtext ne '';
+
+ # If we get to here, then the user is allowed to login, so we're done!
+ return (AUTH_OK, $userid);
+}
+
+sub can_edit { return 0; }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Auth::LDAP - LDAP based authentication for Bugzilla
+
+This is an L<authentication module|Bugzilla::Auth/"AUTHENTICATION"> for
+Bugzilla, which logs the user in using an LDAP directory.
+
+=head1 DISCLAIMER
+
+B<This module is experimental>. It is poorly documented, and not very flexible.
+Search L<http://bugzilla.mozilla.org/> for a list of known LDAP bugs.
+
+None of the core Bugzilla developers, nor any of the large installations, use
+this module, and so it has received less testing. (In fact, this iteration
+hasn't been tested at all)
+
+Patches are accepted.
+
+=head1 SEE ALSO
+
+L<Bugzilla::Auth>
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm
index 6a34396be..c1f3e9103 100644
--- a/Bugzilla/Config.pm
+++ b/Bugzilla/Config.pm
@@ -186,6 +186,11 @@ sub UpdateParams {
$param{'useentrygroupdefault'} = $param{'usebuggroupsentry'};
}
+ # Modularise auth code
+ if (exists $param{'useLDAP'} && !exists $param{'loginmethod'}) {
+ $param{'loginmethod'} = $param{'useLDAP'} ? "LDAP" : "DB";
+ }
+
# --- DEFAULTS FOR NEW PARAMS ---
foreach my $item (@param_list) {
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 70773e036..5e6b5365d 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -36,7 +36,17 @@ use base qw(Exporter);
CONTROLMAPSHOWN
CONTROLMAPDEFAULT
CONTROLMAPMANDATORY
- );
+
+ AUTH_OK
+ AUTH_NODATA
+ AUTH_ERROR
+ AUTH_LOGINFAILED
+ AUTH_DISABLED
+
+ LOGIN_OPTIONAL
+ LOGIN_NORMAL
+ LOGIN_REQUIRED
+);
# CONSTANTS
@@ -72,5 +82,16 @@ use constant CONTROLMAPSHOWN => 1;
use constant CONTROLMAPDEFAULT => 2;
use constant CONTROLMAPMANDATORY => 3;
-1;
+# See Bugzilla::Auth for docs for these
+use constant AUTH_OK => 0;
+use constant AUTH_NODATA => 1;
+use constant AUTH_ERROR => 2;
+use constant AUTH_LOGINFAILED => 3;
+use constant AUTH_DISABLED => 4;
+
+use constant LOGIN_OPTIONAL => 0;
+use constant LOGIN_NORMAL => 1;
+use constant LOGIN_REQUIRED => 2;
+
+1;
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
index 29935928d..1d2e96614 100644
--- a/Bugzilla/DB.pm
+++ b/Bugzilla/DB.pm
@@ -61,8 +61,6 @@ our @SQLStateStack = ();
sub SendSQL {
my ($str) = @_;
- require Bugzilla;
-
$_current_sth = Bugzilla->dbh->prepare($str);
$_current_sth->execute;
@@ -79,8 +77,6 @@ sub SqlQuote {
# Backwards compat code
return "''" if not defined $str;
- require Bugzilla;
-
my $res = Bugzilla->dbh->quote($str);
trick_taint($res);
@@ -156,6 +152,7 @@ sub _connect {
$db_pass,
{ RaiseError => 1,
PrintError => 0,
+ ShowErrorStatement => 1,
HandleError => \&_handle_error,
FetchHashKeyName => 'NAME_lc',
TaintIn => 1,
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
index c8132b804..97d2da41d 100644
--- a/Bugzilla/Token.pm
+++ b/Bugzilla/Token.pm
@@ -237,16 +237,17 @@ sub Cancel {
&::SendSQL("UNLOCK TABLES");
}
-sub HasPasswordToken {
- # Returns a password token if the user has one.
-
- my ($userid) = @_;
-
- &::SendSQL("SELECT token FROM tokens
- WHERE userid = $userid AND tokentype = 'password' LIMIT 1");
- my ($token) = &::FetchSQLData();
-
- return $token;
+sub DeletePasswordTokens {
+ my ($userid, $reason) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare("SELECT token " .
+ "FROM tokens " .
+ "WHERE userid=? AND tokentype='password'");
+ $sth->execute($userid);
+ while (my $token = $sth->fetchrow_array) {
+ Token::Cancel($token, "user_logged_in");
+ }
}
sub HasEmailChangeToken {