Commit 681ce77b authored by bbaetz%acm.org's avatar bbaetz%acm.org

Bug 180642 - Move authentication code into a module

r=gerv, justdave a=justdave
parent 3f1f4e57
......@@ -24,10 +24,13 @@ package Bugzilla;
use strict;
use Bugzilla::Auth;
use Bugzilla::CGI;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::DB;
use Bugzilla::Template;
use Bugzilla::User;
my $_template;
sub template {
......@@ -43,6 +46,60 @@ sub cgi {
return $_cgi;
}
my $_user;
sub user {
my $class = shift;
return $_user;
}
sub login {
my ($class, $type) = @_;
# Avoid double-logins, which may confuse the auth code
# (double cookies, odd compat code settings, etc)
# This is particularly important given the munging for
# $::COOKIE{'Bugzilla_login'} from a userid to a loginname
# (for backwards compat)
if (defined $_user) {
return $_user->{id};
}
$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
use Bugzilla::Auth::CGI;
my $userid = Bugzilla::Auth::CGI->login($type);
if ($userid) {
$_user = new Bugzilla::User($userid);
# Compat stuff
$::userid = $userid;
&::ConfirmGroup($userid);
# Evil compat hack. The cookie stores the id now, not the name, but
# old code still looks at this to get the current user's email
# so it needs to be set.
$::COOKIE{'Bugzilla_login'} = $_user->{email};
$::vars->{'user'} = &::GetUserInfo($userid);
} else {
# Old compat stuff
$::userid = 0;
delete $::COOKIE{'Bugzilla_login'};
delete $::COOKIE{'Bugzilla_logincookie'};
# NB - Can't delete from $cgi->cookie, so the cookie data will
# remain there
# People shouldn't rely on the cookie param for the username
# - use Bugzilla->user instead!
}
return $userid || 0;
}
my $_dbh;
my $_dbh_main;
my $_dbh_shadow;
......@@ -93,6 +150,7 @@ sub switch_to_main_db {
# Per process cleanup
sub _cleanup {
undef $_cgi;
undef $_user;
# See bug 192531. If we don't clear the possibly active statement handles,
# then when this is called from the END block, it happens _before_ the
......@@ -192,6 +250,16 @@ The current C<cgi> object. Note that modules should B<not> be using this in
general. Not all Bugzilla actions are cgi requests. Its useful as a convenience
method for those scripts/templates which are only use via CGI, though.
=item C<user>
The current L<Bugzilla::User>. C<undef> if there is no currently logged in user
or if the login code has not yet been run.
=item C<login>
Logs in a user, returning the userid, or C<0> if there is no logged in user.
See L<Bugzilla::Auth>.
=item C<dbh>
The current database handle. See L<DBI>.
......
# -*- 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>
# -*- 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>
# -*- 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>
# -*- 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>
# -*- 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>
......@@ -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) {
......
......@@ -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;
......@@ -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,
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -675,28 +675,6 @@ EOF
}
###########################################################################
# Global Utility Library
###########################################################################
# globals.pl clears the PATH, but File::Find uses Cwd::cwd() instead of
# Cwd::getcwd(), which we need to do because `pwd` isn't in the path - see
# http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2001-09/msg00115.html
# As a workaround, since we only use File::Find in checksetup, which doesn't
# run in taint mode anyway, preserve the path...
my $origPath = $::ENV{'PATH'};
# Use the Bugzilla utility library for various functions. We do this
# here rather than at the top of the file so globals.pl doesn't define
# localconfig variables for us before we get a chance to check for
# their existence and create them if they don't exist. Also, globals.pl
# removes $ENV{'path'}, which we need in order to run `which mysql` above.
require "globals.pl";
# ...and restore it. This doesn't change tainting, so this will still cause
# errors if this script ever does run with -T.
$::ENV{'PATH'} = $origPath;
###########################################################################
# Check data directory
###########################################################################
......@@ -1215,6 +1193,33 @@ if ($my_webservergroup) {
chmod 01777, 'graphs';
}
###########################################################################
# Global Utility Library
###########################################################################
# This is done here, because some modules require params to be set up, which
# won't have happened earlier.
# The only use for loading globals.pl is for Crypt(), which should at some
# point probably be factored out into Bugzilla::Auth::*
# globals.pl clears the PATH, but File::Find uses Cwd::cwd() instead of
# Cwd::getcwd(), which we need to do because `pwd` isn't in the path - see
# http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2001-09/msg00115.html
# As a workaround, since we only use File::Find in checksetup, which doesn't
# run in taint mode anyway, preserve the path...
my $origPath = $::ENV{'PATH'};
# Use the Bugzilla utility library for various functions. We do this
# here rather than at the top of the file so globals.pl doesn't define
# localconfig variables for us before we get a chance to check for
# their existence and create them if they don't exist. Also, globals.pl
# removes $ENV{'path'}, which we need in order to run `which mysql` above.
require "globals.pl";
# ...and restore it. This doesn't change tainting, so this will still cause
# errors if this script ever does run with -T.
$::ENV{'PATH'} = $origPath;
###########################################################################
# Check MySQL setup
......@@ -1300,6 +1305,16 @@ my $dbh = DBI->connect($connectstring, $my_db_user, $my_db_pass)
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";
}
}
###########################################################################
# Check GraphViz setup
......
......@@ -40,11 +40,11 @@ use vars qw(
ConnectToDatabase();
# If we're using LDAP for login, then we can't create a new account here.
if(Param('useLDAP')) {
unless (Bugzilla::Auth->can_edit) {
# Just in case someone already has an account, let them get the correct
# footer on the error message
quietly_check_login();
ThrowUserError("ldap_cant_create_account");
ThrowUserError("auth_cant_create_account");
}
# Clear out the login cookies. Make people log in again if they create an
......
......@@ -123,6 +123,31 @@ sub check_netmask {
return "";
}
sub check_loginmethod {
# 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
# params mutually dependant on each other.
# This means that if someone clears the LDAP config params after setting
# 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";
}
return "";
}
# OK, here are the parameter definitions themselves.
#
# Each definition is a hash with keys:
......@@ -323,16 +348,6 @@ sub check_netmask {
},
{
name => 'useLDAP',
desc => 'Turn this on to use an LDAP directory for user authentication ' .
'instead of the Bugzilla database. (User profiles will still be ' .
'stored in the database, and will match against the LDAP user by ' .
'email address.)',
type => 'b',
default => 0
},
{
name => 'LDAPserver',
desc => 'The name (and optionally port) of your LDAP server. (e.g. ' .
'ldap.company.com, or ldap.company.com:portnum)',
......@@ -341,6 +356,16 @@ sub check_netmask {
},
{
name => 'LDAPbinddn',
desc => 'If your LDAP server requires that you use a binddn and password ' .
'instead of binding anonymously, enter it here ' .
'(e.g. cn=default,cn=user:password). ' .
'Leave this empty for the normal case of an anonymous bind.',
type => 't',
default => ''
},
{
name => 'LDAPBaseDN',
desc => 'The BaseDN for authenticating users against. (e.g. ' .
'"ou=People,o=Company")',
......@@ -349,6 +374,13 @@ sub check_netmask {
},
{
name => 'LDAPuidattribute',
desc => 'The name of the attribute containing the user\'s login name.',
type => 't',
default => 'uid'
},
{
name => 'LDAPmailattribute',
desc => 'The name of the attribute of a user in your directory that ' .
'contains the email address.',
......@@ -357,6 +389,29 @@ sub check_netmask {
},
{
name => 'loginmethod',
desc => 'The type of login authentication to use:
<dl>
<dt>DB</dt>
<dd>
Bugzilla\'s builtin authentication. This is the most common
choice.
</dd>
<dt>LDAP</dt>
<dd>
LDAP authentication using an LDAP server. This method is
experimental; please see the Bugzilla documentation for more
information. Using this method requires additional parameters
to be set above.
</dd>
</dl>',
type => 's',
choices => [ 'DB', 'LDAP' ],
default => 'DB',
checker => \&check_loginmethod
},
{
name => 'mostfreqthreshold',
desc => 'The minimum number of duplicates a bug needs to show up on the ' .
'<a href="duplicates.cgi">most frequently reported bugs page</a>. ' .
......
......@@ -110,8 +110,8 @@ sub EmitFormElements ($$$$)
if ($editall) {
print "</TR><TR>\n";
print " <TH ALIGN=\"right\">Password:</TH>\n";
if(Param('useLDAP')) {
print " <TD><FONT COLOR=RED>This site is using LDAP for authentication!</FONT></TD>\n";
if(!Bugzilla::Auth->can_edit) {
print " <TD><FONT COLOR=RED>This site's authentication method does not allow password changes through Bugzilla!</FONT></TD>\n";
} else {
print qq|
<TD><INPUT TYPE="PASSWORD" SIZE="16" MAXLENGTH="16" NAME="password" VALUE=""><br>
......@@ -357,7 +357,7 @@ if ($action eq 'list') {
}
print "</TR>";
}
if ($editall && !Param('useLDAP')) {
if ($editall && Bugzilla::Auth->can_edit) {
print "<TR>\n";
my $span = $candelete ? 3 : 2;
print qq{
......@@ -391,9 +391,8 @@ if ($action eq 'add') {
exit;
}
if(Param('useLDAP')) {
print "This site is using LDAP for authentication. To add a new user, ";
print "please contact the LDAP administrators.";
if(!Bugzilla::Auth->can_edit) {
print "The authentication mechanism you are using does not permit accounts to be created from Bugzilla";
PutTrailer();
exit;
}
......@@ -429,9 +428,8 @@ if ($action eq 'new') {
exit;
}
if(Param('useLDAP')) {
print "This site is using LDAP for authentication. To add a new user, ";
print "please contact the LDAP administrators.";
if (!Bugzilla::Auth->can_edit) {
print "This site's authentication mechanism does not allow new users to be added.";
PutTrailer();
exit;
}
......@@ -791,7 +789,7 @@ if ($action eq 'update') {
# Update the database with the user's new password if they changed it.
if ( !Param('useLDAP') && $editall && $password ) {
if ( Bugzilla::Auth->can_edit && $editall && $password ) {
my $passworderror = ValidatePassword($password);
if ( !$passworderror ) {
my $cryptpassword = SqlQuote(Crypt($password));
......
......@@ -29,12 +29,13 @@ package Support::Files;
@additional_files = ();
%exclude_deps = (
'XML::Parser' => ['importxml.pl'],
'Net::LDAP' => ['Bugzilla/Auth/LDAP.pm'],
);
# XXX - this file should be rewritten to use File::Find or similar
# XXX - this file should really be rewritten to use File::Find or similar
$file = '*';
@files = (glob($file), glob('Bugzilla/*.pm'));
@files = (glob($file), glob('Bugzilla/*.pm'), glob('Bugzilla/*/*.pm'));
sub have_pkg {
my ($pkg) = @_;
......
[%# 1.0@bugzilla.org %]
[%# 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>
#%]
[%# INTERFACE:
# auth_err_tag: string. The tag for the error
# info: hash. Additional variables which may be used when printing details
# of the error.
#%]
[% SWITCH auth_err_tag %]
[% CASE "cannot_retreive_attr" %]
The specified LDAP attribute [% info.attr FILTER html %] was not found.
[% CASE "connect_failed" %]
An error occurred while trying to connect to the LDAP server.
[% IF info.errstr %]
The error from the server was: <tt>[% info.errstr FILTER html %]</tt>.
[% END %]
[% CASE "no_userid" %]
Bugzilla created a new account for you, but then could not find the
new userid.
[% CASE "server_not_defined" %]
The LDAP server for authentication has not been defined.
[% CASE %]
Unhandled authentication error: [% auth_err_tag FILTER html %]
[% END %]
......@@ -31,32 +31,14 @@
%]
<p>
I need a legitimate
[% Param('useLDAP') ? "LDAP username" : "email address" %]
and password to continue.
I need a legitimate login and password to continue.
</p>
<form action="[% target %]" method="POST">
<table>
<tr>
[% IF Param("useLDAP") %]
<td align="right">
<b>Username:</b>
</td>
<td>
<input size="10" name="LDAP_login">
</td>
</tr>
<tr>
<td align="right">
<b>Password:</b>
</td>
<td>
<input type="password" size="10" name="LDAP_password">
</td>
[% ELSE %]
<td align="right">
<b>E-mail address:</b>
<b>Login:</b>
</td>
<td>
<input size="35" name="Bugzilla_login">
......@@ -69,7 +51,6 @@
<td>
<input type="password" size="35" name="Bugzilla_password">
</td>
[% END %]
[% IF Param('loginnetmask') < 32 %]
<tr>
<td align="right">
......@@ -89,17 +70,16 @@
</table>
[% PROCESS "global/hidden-fields.html.tmpl"
exclude="^(Bugzilla|LDAP)_(login|password)$" %]
exclude="^Bugzilla_(login|password|restrictlogin)$" %]
<input type="submit" name="GoAheadAndLogIn" value="Login">
</form>
[%# Allow the user to create a new account, or request a token to change
# their password (unless we are using LDAP, in which case the user must
# use LDAP to change it).
# their password, assuming that our auth method allows that.
#%]
[% UNLESS Param("useLDAP") %]
[% IF caneditaccount %]
<hr>
[% IF Param("createemailregexp") %]
......
......@@ -36,7 +36,7 @@ group '[% group.name FILTER html %]' impacts [% group.count %] bugs for which th
[% END %]
<form method="post" >
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^(Bugzilla|LDAP)_(login|password)$" %]
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
<br>
Click "Continue" to proceed with the change including the changes
......
......@@ -52,7 +52,7 @@
<form method="post" action="process_bug.cgi">
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^(Bugzilla|LDAP)_(login|password)$" %]
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
<p>
<input type="radio" name="confirm_add_duplicate" value="1">
......
......@@ -65,7 +65,7 @@ You have the following choices:
<ul>
<li>
<form method="post" action="process_bug.cgi">
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^(Bugzilla|LDAP)_(login|password)$" %]
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
<input type="submit" value="Submit my changes anyway">
This will cause all of the above changes to be overwritten
[% ", except for the added comment(s)" IF comments.size > start_at %].
......
......@@ -48,6 +48,16 @@
Attachment #[% attachid FILTER html %] ([% description FILTER html %])
is already obsolete.
[% ELSIF error == "auth_err" %]
[% title = "Internal Authentication Error" %]
[%# Authentication errors are in a template depending on the auth method,
for pluggability.
#%]
[% INCLUDE "account/auth/$authmethod-error.html.tmpl" %]
[% ELSIF error == "authres_unhandled" %]
An authorization handler return value was not handled by the login code.
[% ELSIF error == "bug_error" %]
Trying to retrieve bug [% bug.bug_id %] returned the error
[% bug.error FILTER html %]
......
......@@ -155,7 +155,7 @@
[% IF matchsuccess == 1 %]
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^(Bugzilla|LDAP)_(login|password)$" %]
[% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
<p>
<input type="submit" value="Continue">
......
......@@ -92,6 +92,12 @@
Bug aliases cannot be longer than 20 characters.
Please choose a shorter alias.
[% ELSIF error == "auth_cant_create_account" %]
[% title = "Can't create accounts" %]
This site is using an authentication scheme which does not permit
account creation. Please contact an administrator to get a new account
created.
[% ELSIF error == "authorization_failure" %]
[% title = "Authorization Failed" %]
You are not allowed to [% action %].
......@@ -313,11 +319,6 @@
[% title = "Invalid Username Or Password" %]
The username or password you entered is not valid.
[% ELSIF error == "ldap_cant_create_account" %]
[% title = "Can't create LDAP accounts" %]
This site is using LDAP for authentication. Please contact
an LDAP administrator to get a new account created.
[% ELSIF error == "login_needed_for_password_change" %]
[% title = "Login Name Required" %]
You must enter a login name when requesting to change your password.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment