From ce983f5d751e61c5500eac45e5db29eee7309520 Mon Sep 17 00:00:00 2001 From: "bugreport%peshkin.net" <> Date: Mon, 12 Jul 2004 06:36:42 +0000 Subject: Bug 241900: Allow Bugzilla::Auth to have multiple login and validation styles patch by erik r=joel a=justdave --- Bugzilla.pm | 41 ++++-- Bugzilla/Auth.pm | 108 ++++++++++++---- Bugzilla/Auth/CGI.pm | 253 ------------------------------------- Bugzilla/Auth/Cookie.pm | 115 ----------------- Bugzilla/Auth/DB.pm | 124 ------------------- Bugzilla/Auth/LDAP.pm | 185 --------------------------- Bugzilla/Auth/Login/CGI.pm | 254 ++++++++++++++++++++++++++++++++++++++ Bugzilla/Auth/Login/CGI/Cookie.pm | 115 +++++++++++++++++ Bugzilla/Auth/Verify/DB.pm | 132 ++++++++++++++++++++ Bugzilla/Auth/Verify/LDAP.pm | 193 +++++++++++++++++++++++++++++ Bugzilla/Config.pm | 7 ++ checksetup.pl | 11 +- defparams.pl | 68 +++++++--- editusers.cgi | 23 +--- t/Support/Files.pm | 2 +- 15 files changed, 875 insertions(+), 756 deletions(-) delete mode 100644 Bugzilla/Auth/CGI.pm delete mode 100644 Bugzilla/Auth/Cookie.pm delete mode 100644 Bugzilla/Auth/DB.pm delete mode 100644 Bugzilla/Auth/LDAP.pm create mode 100644 Bugzilla/Auth/Login/CGI.pm create mode 100644 Bugzilla/Auth/Login/CGI/Cookie.pm create mode 100644 Bugzilla/Auth/Verify/DB.pm create mode 100644 Bugzilla/Auth/Verify/LDAP.pm diff --git a/Bugzilla.pm b/Bugzilla.pm index 5cee520c7..84cc2d721 100644 --- a/Bugzilla.pm +++ b/Bugzilla.pm @@ -18,6 +18,7 @@ # Rights Reserved. # # Contributor(s): Bradley Baetz +# Erik Stambaugh # package Bugzilla; @@ -52,6 +53,10 @@ sub user { return $_user; } + + +my $current_login_method = undef; + sub login { my ($class, $type) = @_; @@ -66,12 +71,18 @@ sub login { $type = LOGIN_NORMAL unless defined $type; - # For now, we can only log in from a cgi - # One day, we'll be able to log in via apache auth, an email message's - # PGP signature, and so on + # Log in using whatever methods are defined in user_info_method + + my $userid; + for my $method (split(/,\s*/, Param('user_info_method'))) { + require "Bugzilla/Auth/Login/" . $method . ".pm"; + $userid = "Bugzilla::Auth::Login::$method"->login($type); + if ($userid) { + $current_login_method = "Bugzilla::Auth::Login::$method"; + last; + } + } - use Bugzilla::Auth::CGI; - my $userid = Bugzilla::Auth::CGI->login($type); if ($userid) { $_user = new Bugzilla::User($userid); @@ -97,11 +108,14 @@ sub logout { } $option = LOGOUT_CURRENT unless defined $option; - use Bugzilla::Auth::CGI; - Bugzilla::Auth::CGI->logout($_user, $option); - if ($option != LOGOUT_KEEP_CURRENT) { - Bugzilla::Auth::CGI->clear_browser_cookies(); - logout_request(); + # $current_login_method is defined when the user's login information is + # found. If it's not defined, the user shouldn't be logged in. + if ($current_login_method) { + $current_login_method->logout($_user, $option); + if ($option != LOGOUT_KEEP_CURRENT) { + $current_login_method->clear_browser_cookies(); + logout_request(); + } } } @@ -109,8 +123,9 @@ sub logout_user { my ($class, $user) = @_; # When we're logging out another user we leave cookies alone, and # therefore avoid calling logout() directly. - use Bugzilla::Auth::CGI; - Bugzilla::Auth::CGI->logout($user, LOGOUT_ALL); + if ($current_login_method) { + $current_login_method->logout($_user, LOGOUT_ALL); + } } # just a compatibility front-end to logout_user that gets a user by id @@ -127,7 +142,7 @@ sub logout_request { # XXX clean these up eventually delete $::COOKIE{"Bugzilla_login"}; # NB - Can't delete from $cgi->cookie, so the logincookie data will - # remain there; it's only used in Bugzilla::Auth::CGI->logout anyway + # remain there; it's only used in Bugzilla::Auth::Login::CGI->logout anyway # People shouldn't rely on the cookie param for the username # - use Bugzilla->user instead! } diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm index dcea8189a..e6cf27963 100644 --- a/Bugzilla/Auth.pm +++ b/Bugzilla/Auth.pm @@ -18,6 +18,7 @@ # Rights Reserved. # # Contributor(s): Bradley Baetz +# Erik Stambaugh package Bugzilla::Auth; @@ -26,19 +27,25 @@ use strict; use Bugzilla::Config; use Bugzilla::Constants; -# 'inherit' from the main loginmethod +# This is here for lack of a better place for it. I considered making it +# part of the user object, but that object doesn't necessarily point to a +# currently authenticated user. +# +# I'm willing to accept suggestions for somewhere else to put it. +my $current_verify_method = undef; + +# 'inherit' from the main verify method BEGIN { - my $loginmethod = Param("loginmethod"); - if ($loginmethod =~ /^([A-Za-z0-9_\.\-]+)$/) { - $loginmethod = $1; + for my $verifymethod (split /,\s*/, Param("user_verify_method")) { + if ($verifymethod =~ /^([A-Za-z0-9_\.\-]+)$/) { + $verifymethod = $1; } else { - die "Badly-named loginmethod '$loginmethod'"; + die "Badly-named user_verify_method '$verifymethod'"; } - require "Bugzilla/Auth/" . $loginmethod . ".pm"; + require "Bugzilla/Auth/Verify/" . $verifymethod . ".pm"; - our @ISA; - push (@ISA, "Bugzilla::Auth::" . $loginmethod); + } } # PRIVATE @@ -61,6 +68,46 @@ sub get_netaddr { return join(".", unpack("CCCC", pack("N", $addr))); } +# This is a replacement for the inherited authenticate function +# go through each of the available methods for each function +sub authenticate { + my $self = shift; + my @args = @_; + my @firstresult = (); + my @result = (); + for my $method (split /,\s*/, Param("user_verify_method")) { + $method = "Bugzilla::Auth::Verify::" . $method; + @result = $method->authenticate(@args); + @firstresult = @result unless @firstresult; + + if (($result[0] != AUTH_NODATA)&&($result[0] != AUTH_LOGINFAILED)) { + $current_verify_method = $method; + return @result; + } + } + @result = @firstresult; + # no auth match + + # see if we can set $current to the first verify method that + # will allow a new login + + for my $method (split /,\s*/, Param("user_verify_method")) { + $method = "Bugzilla::Auth::Verify::" . $method; + if ($method::can_edit->{'new'}) { + $current_verify_method = $method; + } + } + + return @result; +} + +sub can_edit { + if ($current_verify_method) { + return $current_verify_method->{'can_edit'}; + } + return {}; +} + 1; __END__ @@ -78,16 +125,8 @@ 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) then use those methods to do -the authentication. - -I itself inherits from the default authentication handler, -identified by the I param. +Modules for obtaining the data are located under L, and +modules for authenticating are located in L. =head1 METHODS @@ -108,7 +147,9 @@ only some addresses. =head1 AUTHENTICATION Authentication modules check a user's credentials (username, password, -etc) to verify who the user is. +etc) to verify who the user is. The methods that C uses for +authentication are wrappers that check all configured modules (via the +C and C) in sequence. =head2 METHODS @@ -175,19 +216,36 @@ Note that this argument is a string, not a tag. =back +=item C + +This scalar gets populated with the full name (eg., +C) of the verification method being used by the +current user. If no user is logged in, it will contain the name of the first +method that allows new users, if any. Otherwise, it carries an undefined +value. + =item C -This determines if the user's account details can be modified. If this -method returns a C value, then accounts can be created and -modified through the Bugzilla user interface. Forgotten passwords can -also be retrieved through the L. +This determines if the user's account details can be modified. It returns a +reference to a hash with the keys C, C, and C, +which determine whether their respective profile values may be altered, and +C, which determines if new accounts may be created. + +Each user verification method (chosen with C has +its own set of can_edit values. Calls to can_edit return the appropriate +values for the current user's login method. + +If a user is not logged in, C will contain the values of the first +verify method that allows new users to be created, if available. Otherwise it +returns an empty hash. =back =head1 LOGINS A login module can be used to try to log in a Bugzilla user in a -particular way. For example, L +particular way. For example, +L logs in users from CGI scripts, first by using form variables, and then by trying cookies as a fallback. @@ -250,5 +308,5 @@ user-performed password changes. =head1 SEE ALSO -L, L, L +L, L, L diff --git a/Bugzilla/Auth/CGI.pm b/Bugzilla/Auth/CGI.pm deleted file mode 100644 index 471e538e9..000000000 --- a/Bugzilla/Auth/CGI.pm +++ /dev/null @@ -1,253 +0,0 @@ -# -*- 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 -# Dan Mosedale -# Joe Robins -# Dave Miller -# Christopher Aillon -# Gervase Markham -# Christian Reis -# Bradley Baetz - -package Bugzilla::Auth::CGI; - -use strict; - -use Bugzilla::Config; -use Bugzilla::Constants; -use Bugzilla::Error; -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 = Bugzilla::Auth::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()"); - - # Remember cookie only if admin has told so - # or admin didn't forbid it and user told to remember. - if ((Param('rememberlogin') eq 'on') || - ((Param('rememberlogin') ne 'off') && - ($cgi->param('Bugzilla_remember') eq 'on'))) { - $cgi->send_cookie(-name => 'Bugzilla_login', - -value => $userid, - -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'); - $cgi->send_cookie(-name => 'Bugzilla_logincookie', - -value => $logincookie, - -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'); - - } - else { - $cgi->send_cookie(-name => 'Bugzilla_login', - -value => $userid); - $cgi->send_cookie(-name => 'Bugzilla_logincookie', - -value => $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) { - ThrowCodeError("auth_err", - { authmethod => lc($authmethod), - userid => $userid, - auth_err_tag => $extra, - info => $info - }); - } - - # 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 Bugzilla->cgi->header(); - - 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 - - $cgi->send_cookie(-name => 'Bugzilla_login', - -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); - $cgi->send_cookie(-name => 'Bugzilla_logincookie', - -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); - - # 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, }); -} - -# Logs user out, according to the option provided; this consists of -# removing entries from logincookies for the specified $user. -sub logout { - my ($class, $user, $option) = @_; - my $dbh = Bugzilla->dbh; - $option = LOGOUT_ALL unless defined $option; - - if ($option == LOGOUT_ALL) { - $dbh->do("DELETE FROM logincookies WHERE userid = ?", - undef, $user->id); - return; - } - - # The LOGOUT_*_CURRENT options require a cookie - my $cookie = Bugzilla->cgi->cookie("Bugzilla_logincookie"); - detaint_natural($cookie); - - # These queries use both the cookie ID and the user ID as keys. Even - # though we know the userid must match, we still check it in the SQL - # as a sanity check, since there is no locking here, and if the user - # logged out from two machines simultaneously, while someone else - # logged in and got the same cookie, we could be logging the other - # user out here. Yes, this is very very very unlikely, but why take - # chances? - bbaetz - if ($option == LOGOUT_KEEP_CURRENT) { - $dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?", - undef, $cookie, $user->id); - } elsif ($option == LOGOUT_CURRENT) { - $dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?", - undef, $cookie, $user->id); - } else { - die("Invalid option $option supplied to logout()"); - } -} - -sub clear_browser_cookies { - my $cgi = Bugzilla->cgi; - $cgi->send_cookie(-name => "Bugzilla_login", - -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); - $cgi->send_cookie(-name => "Bugzilla_logincookie", - -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); -} - -1; - -__END__ - -=head1 NAME - -Bugzilla::Auth::CGI - CGI-based logins for Bugzilla - -=head1 SUMMARY - -This is a L for Bugzilla. Users connecting -from a CGI script use this module to authenticate. Logouts are also handled here. - -=head1 BEHAVIOUR - -Users are first authenticated against the default authentication handler, -using the CGI parameters I and I. - -If no data is present for that, then cookies are tried, using -L. - -=head1 SEE ALSO - -L diff --git a/Bugzilla/Auth/Cookie.pm b/Bugzilla/Auth/Cookie.pm deleted file mode 100644 index b50acbe24..000000000 --- a/Bugzilla/Auth/Cookie.pm +++ /dev/null @@ -1,115 +0,0 @@ -# -*- 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 -# Dan Mosedale -# Joe Robins -# Dave Miller -# Christopher Aillon -# Gervase Markham -# Christian Reis -# Bradley Baetz - -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); - - 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 for -Bugzilla, which logs the user in using a persistent cookie stored in the -C table. - -The actual password is not stored in the cookie; only the userid and a -I (which is used to reverify the login without requiring the -password to be sent over the network) are. These I are -restricted to certain IP addresses as a security meaure. The exact -restriction can be specified by the admin via the C parameter. - -This module does not ever send a cookie (It has no way of knowing when a user -is successfully logged in). Instead L handles this. - -=head1 SEE ALSO - -L, L diff --git a/Bugzilla/Auth/DB.pm b/Bugzilla/Auth/DB.pm deleted file mode 100644 index dee3b5db9..000000000 --- a/Bugzilla/Auth/DB.pm +++ /dev/null @@ -1,124 +0,0 @@ -# -*- 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 -# Dan Mosedale -# Joe Robins -# Dave Miller -# Christopher Aillon -# Gervase Markham -# Christian Reis -# Bradley Baetz - -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; - - # We're just testing against the db: any value is ok - trick_taint($username); - - my $userid = $class->get_id_from_username($username); - return (AUTH_LOGINFAILED) unless defined $userid; - - return (AUTH_LOGINFAILED, $userid) - unless $class->check_password($userid, $passwd); - - # The user's credentials are okay, so delete any outstanding - # password tokens they may have generated. - require Bugzilla::Token; - Bugzilla::Token::DeletePasswordTokens($userid, "user_logged_in"); - - # Account may have been disabled - my $disabledtext = $class->get_disabled($userid); - return (AUTH_DISABLED, $userid, $disabledtext) - if $disabledtext ne ''; - - return (AUTH_OK, $userid); -} - -sub can_edit { return 1; } - -sub get_id_from_username { - my ($class, $username) = @_; - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare_cached("SELECT userid FROM profiles " . - "WHERE login_name=?"); - my ($userid) = $dbh->selectrow_array($sth, undef, $username); - return $userid; -} - -sub get_disabled { - my ($class, $userid) = @_; - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare_cached("SELECT disabledtext FROM profiles " . - "WHERE userid=?"); - my ($text) = $dbh->selectrow_array($sth, undef, $userid); - return $text; -} - -sub check_password { - my ($class, $userid, $passwd) = @_; - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare_cached("SELECT cryptpassword FROM profiles " . - "WHERE userid=?"); - my ($realcryptpwd) = $dbh->selectrow_array($sth, undef, $userid); - - # 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); - - return $enteredCryptedPassword eq $realcryptpwd; -} - -sub change_password { - my ($class, $userid, $password) = @_; - my $dbh = Bugzilla->dbh; - my $cryptpassword = Crypt($password); - $dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?", - undef, $cryptpassword, $userid); -} - -1; - -__END__ - -=head1 NAME - -Bugzilla::Auth::DB - database authentication for Bugzilla - -=head1 SUMMARY - -This is an L for -Bugzilla, which logs the user in using the password stored in the C -table. This is the most commonly used authentication module. - -=head1 SEE ALSO - -L diff --git a/Bugzilla/Auth/LDAP.pm b/Bugzilla/Auth/LDAP.pm deleted file mode 100644 index c34c3698f..000000000 --- a/Bugzilla/Auth/LDAP.pm +++ /dev/null @@ -1,185 +0,0 @@ -# -*- 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 -# Dan Mosedale -# Joe Robins -# Dave Miller -# Christopher Aillon -# Gervase Markham -# Christian Reis -# Bradley Baetz - -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->error }); - } - - # We've got our anonymous bind; let's look up this user. - $mesg = $LDAPconn->search( base => Param("LDAPBaseDN"), - scope => "sub", - filter => '(&(' . Param("LDAPuidattribute") . "=$username)" . Param("LDAPfilter") . ')', - 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)" . Param("LDAPfilter") . ')', - ); - 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); - - ($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 for -Bugzilla, which logs the user in using an LDAP directory. - -=head1 DISCLAIMER - -B. It is poorly documented, and not very flexible. -Search L 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 diff --git a/Bugzilla/Auth/Login/CGI.pm b/Bugzilla/Auth/Login/CGI.pm new file mode 100644 index 000000000..2f8ca071d --- /dev/null +++ b/Bugzilla/Auth/Login/CGI.pm @@ -0,0 +1,254 @@ +# -*- 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 +# Dan Mosedale +# Joe Robins +# Dave Miller +# Christopher Aillon +# Gervase Markham +# Christian Reis +# Bradley Baetz +# Erik Stambaugh + +package Bugzilla::Auth::Login::CGI; + +use strict; + +use Bugzilla::Config; +use Bugzilla::Constants; +use Bugzilla::Error; +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("user_verify_method"); + 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 = Bugzilla::Auth::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()"); + + # Remember cookie only if admin has told so + # or admin didn't forbid it and user told to remember. + if ((Param('rememberlogin') eq 'on') || + ((Param('rememberlogin') ne 'off') && + ($cgi->param('Bugzilla_remember') eq 'on'))) { + $cgi->send_cookie(-name => 'Bugzilla_login', + -value => $userid, + -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'); + $cgi->send_cookie(-name => 'Bugzilla_logincookie', + -value => $logincookie, + -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'); + + } + else { + $cgi->send_cookie(-name => 'Bugzilla_login', + -value => $userid); + $cgi->send_cookie(-name => 'Bugzilla_logincookie', + -value => $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::Login::CGI::Cookie; + my $authmethod = "Cookie"; + + ($authres, $userid, $extra) = + Bugzilla::Auth::Login::CGI::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) { + ThrowCodeError("auth_err", + { authmethod => lc($authmethod), + userid => $userid, + auth_err_tag => $extra, + info => $info + }); + } + + # 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 Bugzilla->cgi->header(); + + 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->{'new'}, + } + ) + || 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 + + $cgi->send_cookie(-name => 'Bugzilla_login', + -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); + $cgi->send_cookie(-name => 'Bugzilla_logincookie', + -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); + + # 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, }); +} + +# Logs user out, according to the option provided; this consists of +# removing entries from logincookies for the specified $user. +sub logout { + my ($class, $user, $option) = @_; + my $dbh = Bugzilla->dbh; + $option = LOGOUT_ALL unless defined $option; + + if ($option == LOGOUT_ALL) { + $dbh->do("DELETE FROM logincookies WHERE userid = ?", + undef, $user->id); + return; + } + + # The LOGOUT_*_CURRENT options require a cookie + my $cookie = Bugzilla->cgi->cookie("Bugzilla_logincookie"); + detaint_natural($cookie); + + # These queries use both the cookie ID and the user ID as keys. Even + # though we know the userid must match, we still check it in the SQL + # as a sanity check, since there is no locking here, and if the user + # logged out from two machines simultaneously, while someone else + # logged in and got the same cookie, we could be logging the other + # user out here. Yes, this is very very very unlikely, but why take + # chances? - bbaetz + if ($option == LOGOUT_KEEP_CURRENT) { + $dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?", + undef, $cookie, $user->id); + } elsif ($option == LOGOUT_CURRENT) { + $dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?", + undef, $cookie, $user->id); + } else { + die("Invalid option $option supplied to logout()"); + } +} + +sub clear_browser_cookies { + my $cgi = Bugzilla->cgi; + $cgi->send_cookie(-name => "Bugzilla_login", + -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); + $cgi->send_cookie(-name => "Bugzilla_logincookie", + -expires => "Tue, 15-Sep-1998 21:49:00 GMT"); +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Auth::Login::CGI - CGI-based logins for Bugzilla + +=head1 SUMMARY + +This is a L for Bugzilla. Users connecting +from a CGI script use this module to authenticate. Logouts are also handled here. + +=head1 BEHAVIOUR + +Users are first authenticated against the default authentication handler, +using the CGI parameters I and I. + +If no data is present for that, then cookies are tried, using +L. + +=head1 SEE ALSO + +L diff --git a/Bugzilla/Auth/Login/CGI/Cookie.pm b/Bugzilla/Auth/Login/CGI/Cookie.pm new file mode 100644 index 000000000..9c0e2e566 --- /dev/null +++ b/Bugzilla/Auth/Login/CGI/Cookie.pm @@ -0,0 +1,115 @@ +# -*- 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 +# Dan Mosedale +# Joe Robins +# Dave Miller +# Christopher Aillon +# Gervase Markham +# Christian Reis +# Bradley Baetz + +package Bugzilla::Auth::Login::CGI::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); + + return (AUTH_OK, $userid); + } + + # If we get here, then the login failed. + return (AUTH_LOGINFAILED); +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Auth::Login::CGI::Cookie - cookie authentication for Bugzilla + +=head1 SUMMARY + +This is an L for +Bugzilla, which logs the user in using a persistent cookie stored in the +C table. + +The actual password is not stored in the cookie; only the userid and a +I (which is used to reverify the login without requiring the +password to be sent over the network) are. These I are +restricted to certain IP addresses as a security meaure. The exact +restriction can be specified by the admin via the C parameter. + +This module does not ever send a cookie (It has no way of knowing when a user +is successfully logged in). Instead L handles this. + +=head1 SEE ALSO + +L, L diff --git a/Bugzilla/Auth/Verify/DB.pm b/Bugzilla/Auth/Verify/DB.pm new file mode 100644 index 000000000..4db34b5cf --- /dev/null +++ b/Bugzilla/Auth/Verify/DB.pm @@ -0,0 +1,132 @@ +# -*- 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 +# Dan Mosedale +# Joe Robins +# Dave Miller +# Christopher Aillon +# Gervase Markham +# Christian Reis +# Bradley Baetz +# Erik Stambaugh + +package Bugzilla::Auth::Verify::DB; + +use strict; + +use Bugzilla::Config; +use Bugzilla::Constants; +use Bugzilla::Util; + +# can_edit is now a hash. + +my $can_edit = { + 'new' => 1, + 'userid' => 0, + 'login_name' => 1, + 'realname' => 1, +}; + +sub authenticate { + my ($class, $username, $passwd) = @_; + + return (AUTH_NODATA) unless defined $username && defined $passwd; + + # We're just testing against the db: any value is ok + trick_taint($username); + + my $userid = $class->get_id_from_username($username); + return (AUTH_LOGINFAILED) unless defined $userid; + + return (AUTH_LOGINFAILED, $userid) + unless $class->check_password($userid, $passwd); + + # The user's credentials are okay, so delete any outstanding + # password tokens they may have generated. + require Bugzilla::Token; + Bugzilla::Token::DeletePasswordTokens($userid, "user_logged_in"); + + # Account may have been disabled + my $disabledtext = $class->get_disabled($userid); + return (AUTH_DISABLED, $userid, $disabledtext) + if $disabledtext ne ''; + + return (AUTH_OK, $userid); +} + +sub get_id_from_username { + my ($class, $username) = @_; + my $dbh = Bugzilla->dbh; + my $sth = $dbh->prepare_cached("SELECT userid FROM profiles " . + "WHERE login_name=?"); + my ($userid) = $dbh->selectrow_array($sth, undef, $username); + return $userid; +} + +sub get_disabled { + my ($class, $userid) = @_; + my $dbh = Bugzilla->dbh; + my $sth = $dbh->prepare_cached("SELECT disabledtext FROM profiles " . + "WHERE userid=?"); + my ($text) = $dbh->selectrow_array($sth, undef, $userid); + return $text; +} + +sub check_password { + my ($class, $userid, $passwd) = @_; + my $dbh = Bugzilla->dbh; + my $sth = $dbh->prepare_cached("SELECT cryptpassword FROM profiles " . + "WHERE userid=?"); + my ($realcryptpwd) = $dbh->selectrow_array($sth, undef, $userid); + + # 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); + + return $enteredCryptedPassword eq $realcryptpwd; +} + +sub change_password { + my ($class, $userid, $password) = @_; + my $dbh = Bugzilla->dbh; + my $cryptpassword = Crypt($password); + $dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?", + undef, $cryptpassword, $userid); +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Auth::Verify::DB - database authentication for Bugzilla + +=head1 SUMMARY + +This is an L for +Bugzilla, which logs the user in using the password stored in the C +table. This is the most commonly used authentication module. + +=head1 SEE ALSO + +L diff --git a/Bugzilla/Auth/Verify/LDAP.pm b/Bugzilla/Auth/Verify/LDAP.pm new file mode 100644 index 000000000..737827ee0 --- /dev/null +++ b/Bugzilla/Auth/Verify/LDAP.pm @@ -0,0 +1,193 @@ +# -*- 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 +# Dan Mosedale +# Joe Robins +# Dave Miller +# Christopher Aillon +# Gervase Markham +# Christian Reis +# Bradley Baetz +# Erik Stambaugh + +package Bugzilla::Auth::Verify::LDAP; + +use strict; + +use Bugzilla::Config; +use Bugzilla::Constants; + +use Net::LDAP; + +# can_edit is now a hash. + +my $can_edit = { + 'new' => 0, + 'userid' => 0, + 'login_name' => 0, + 'realname' => 0, +}; + +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->error }); + } + + # We've got our anonymous bind; let's look up this user. + $mesg = $LDAPconn->search( base => Param("LDAPBaseDN"), + scope => "sub", + filter => '(&(' . Param("LDAPuidattribute") . "=$username)" . Param("LDAPfilter") . ')', + 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)" . Param("LDAPfilter") . ')', + ); + 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); + + ($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); +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Auth::Verify::LDAP - LDAP based authentication for Bugzilla + +This is an L for +Bugzilla, which logs the user in using an LDAP directory. + +=head1 DISCLAIMER + +B. It is poorly documented, and not very flexible. +Search L 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 diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm index b568918e3..d73f22875 100644 --- a/Bugzilla/Config.pm +++ b/Bugzilla/Config.pm @@ -25,6 +25,7 @@ # J. Paul Reed # Bradley Baetz # Christopher Aillon +# Erik Stambaugh package Bugzilla::Config; @@ -217,6 +218,12 @@ sub UpdateParams { $param{'loginmethod'} = $param{'useLDAP'} ? "LDAP" : "DB"; } + # set verify method to whatever loginmethod was + if (exists $param{'loginmethod'} && !exists $param{'user_verify_method'}) { + $param{'user_verify_method'} = $param{'loginmethod'}; + delete $param{'loginmethod'}; + } + # --- DEFAULTS FOR NEW PARAMS --- foreach my $item (@param_list) { diff --git a/checksetup.pl b/checksetup.pl index 51a1bb2f4..9961009e8 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -27,6 +27,7 @@ # Bradley Baetz # Tobias Burnus # Gervase Markham +# Erik Stambaugh # # # Direct any questions on this source code to @@ -1492,10 +1493,12 @@ END { $dbh->disconnect if $dbh } # Check for LDAP ########################################################################### -if (Param('loginmethod') eq 'LDAP') { - my $netLDAP = have_vers("Net::LDAP", 0); - if (!$netLDAP && !$silent) { - print "If you wish to use LDAP authentication, then you must install Net::LDAP\n\n"; +for my $verifymethod (split /,\s*/, Param('user_verify_method')) { + if ($verifymethod eq 'LDAP') { + my $netLDAP = have_vers("Net::LDAP", 0); + if (!$netLDAP && !$silent) { + print "If you wish to use LDAP authentication, then you must install Net::LDAP\n\n"; + } } } diff --git a/defparams.pl b/defparams.pl index 6861d0447..b067d583a 100644 --- a/defparams.pl +++ b/defparams.pl @@ -25,6 +25,7 @@ # J. Paul Reed # Bradley Baetz # Joseph Heenan +# Erik Stambaugh # # This file defines all the parameters that we have a GUI to edit within @@ -127,7 +128,7 @@ sub check_netmask { return ""; } -sub check_loginmethod { +sub check_user_verify_method { # doeditparams traverses the list of params, and for each one it checks, # then updates. This means that if one param checker wants to look at # other params, it must be below that other one. So you can't have two @@ -136,18 +137,20 @@ sub check_loginmethod { # the login method as LDAP, we won't notice, but all logins will fail. # So don't do that. - my ($method, $entry) = @_; - my $res = check_multi($method, $entry); - return $res if $res; - if ($method eq 'DB') { - # No params - } elsif ($method eq 'LDAP') { - eval "require Net::LDAP"; - return "Error requiring Net::LDAP: '$@'" if $@; - return "LDAP servername is missing" unless Param("LDAPserver"); - return "LDAPBaseDN is empty" unless Param("LDAPBaseDN"); - } else { - return "Unknown loginmethod '$method' in check_loginmethod"; + my ($list, $entry) = @_; + for my $method (split /,\s*/, $list) { + my $res = check_multi($method, $entry); + return $res if $res; + if ($method eq 'DB') { + # No params + } elsif ($method eq 'LDAP') { + eval "require Net::LDAP"; + return "Error requiring Net::LDAP: '$@'" if $@; + return "LDAP servername is missing" unless Param("LDAPserver"); + return "LDAPBaseDN is empty" unless Param("LDAPBaseDN"); + } else { + return "Unknown user_verify_method '$method' in check_user_verify_method"; + } } return ""; } @@ -432,9 +435,40 @@ sub find_languages { default => '', }, + # in the future: + # + # user_verify_method and user_info_method should have choices gathered from + # whatever sits in their respective directories + # + # rather than comma-separated lists, these two should eventually become + # arrays, but that requires alterations to editparams first + + { + name => 'user_info_method', + desc => 'Methods to be used for gathering a user\'s login information. + + More than one may be selected. If the first one returns nothing, + the second is tried, and so on.
+ The types are: +
+
CGI
+
+ Asks for username and password via CGI form interface. +
+
', + type => 's', + choices => [ 'CGI' ], + default => 'CGI', + checker => \&check_multi + }, + { - name => 'loginmethod', - desc => 'The type of login authentication to use: + name => 'user_verify_method', + desc => 'Methods to be used for verifying (authenticating) information + gathered by user_info_method. + More than one may be selected. If the first one cannot find the + user, the second is tried, and so on.
+ The types are:
DB
@@ -450,9 +484,9 @@ sub find_languages {
', type => 's', - choices => [ 'DB', 'LDAP' ], + choices => [ 'DB', 'LDAP', 'DB,LDAP', 'LDAP,DB' ], default => 'DB', - checker => \&check_loginmethod + checker => \&check_user_verify_method }, { diff --git a/editusers.cgi b/editusers.cgi index 826bb4b34..fa3efbf8f 100755 --- a/editusers.cgi +++ b/editusers.cgi @@ -23,6 +23,7 @@ # Joe Robins # Dan Mosedale # Joel Peshkin +# Erik Stambaugh # # Direct any questions on this source code to # @@ -114,15 +115,11 @@ sub EmitFormElements ($$$$) if ($editall) { print "\n"; print " Password:\n"; - if(!Bugzilla::Auth->can_edit) { - print " This site's authentication method does not allow password changes through Bugzilla!\n"; - } else { print qq|
(enter new password to change) |; - } print "\n"; print " Disable text:\n"; @@ -209,7 +206,7 @@ sub EmitFormElements ($$$$) sub PutTrailer (@) { my (@links) = ("Back to the index"); - if($editall && Bugzilla::Auth->can_edit) { + if($editall) { push(@links, "add a new user"); } @@ -361,7 +358,7 @@ if ($action eq 'list') { } print ""; } - if ($editall && Bugzilla::Auth->can_edit) { + if ($editall) { print "\n"; my $span = $candelete ? 3 : 2; print qq{ @@ -395,12 +392,6 @@ if ($action eq 'add') { exit; } - if(!Bugzilla::Auth->can_edit) { - print "The authentication mechanism you are using does not permit accounts to be created from Bugzilla"; - PutTrailer(); - exit; - } - print "
\n"; print "\n"; @@ -432,12 +423,6 @@ if ($action eq 'new') { exit; } - if (!Bugzilla::Auth->can_edit) { - print "This site's authentication mechanism does not allow new users to be added."; - PutTrailer(); - exit; - } - # Cleanups and valididy checks my $realname = trim($::FORM{realname} || ''); # We don't trim the password since that could falsely lead the user @@ -814,7 +799,7 @@ if ($action eq 'update') { # Update the database with the user's new password if they changed it. - if ( Bugzilla::Auth->can_edit && $editall && $password ) { + if ( $editall && $password ) { my $passworderror = ValidatePassword($password); if ( !$passworderror ) { my $cryptpassword = SqlQuote(Crypt($password)); diff --git a/t/Support/Files.pm b/t/Support/Files.pm index ffadc562c..de5b598c5 100644 --- a/t/Support/Files.pm +++ b/t/Support/Files.pm @@ -29,7 +29,7 @@ package Support::Files; @additional_files = (); %exclude_deps = ( 'XML::Parser' => ['importxml.pl'], - 'Net::LDAP' => ['Bugzilla/Auth/LDAP.pm'], + 'Net::LDAP' => ['Bugzilla/Auth/Verify/LDAP.pm'], ); -- cgit v1.2.3-24-g4f1b