Bug 241900: Allow Bugzilla::Auth to have multiple login and validation styles

patch by erik
r=joel, kiko
a=myk
This commit is contained in:
bugreport%peshkin.net 2004-07-20 22:41:22 +00:00
Родитель b6b3158289
Коммит c70d1cf583
21 изменённых файлов: 1162 добавлений и 125 удалений

Просмотреть файл

@ -18,6 +18,7 @@
# Rights Reserved.
#
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
# Erik Stambaugh <erik@dasbistro.com>
#
package Bugzilla;
@ -25,6 +26,7 @@ package Bugzilla;
use strict;
use Bugzilla::Auth;
use Bugzilla::Auth::Login::WWW;
use Bugzilla::CGI;
use Bugzilla::Config;
use Bugzilla::Constants;
@ -54,39 +56,7 @@ sub 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;
}
$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;
# 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->login;
} else {
logout_request();
}
return $_user;
$_user = Bugzilla::Auth::Login::WWW->login($type);
}
sub logout {
@ -97,20 +67,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();
}
Bugzilla::Auth::Login::WWW->logout($_user, $option);
}
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);
# therefore avoid calling Bugzilla->logout() directly.
Bugzilla::Auth::Login::WWW->logout($user, LOGOUT_ALL);
}
# just a compatibility front-end to logout_user that gets a user by id
@ -290,7 +254,7 @@ or if the login code has not yet been run.
=item C<login>
Logs in a user, returning a C<Bugzilla::User> object, or C<undef> if there is
no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth> and
no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth>, and
L<Bugzilla::User|Bugzilla::User>.
=item C<logout($option)>
@ -315,7 +279,7 @@ Bugzilla::User instance.
Essentially, causes calls to C<Bugzilla->user> to return C<undef>. This has the
effect of logging out a user for the current request only; cookies and
database sessions are left intact.
database sessions are left intact.
=item C<dbh>

Просмотреть файл

@ -18,6 +18,7 @@
# Rights Reserved.
#
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
package Bugzilla::Auth;
@ -26,23 +27,34 @@ use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
# 'inherit' from the main loginmethod
BEGIN {
my $loginmethod = Param("loginmethod");
if ($loginmethod =~ /^([A-Za-z0-9_\.\-]+)$/) {
$loginmethod = $1;
}
else {
die "Badly-named loginmethod '$loginmethod'";
}
require "Bugzilla/Auth/" . $loginmethod . ".pm";
# The verification method that was successfully used upon login, if any
my $current_verify_class = undef;
our @ISA;
push (@ISA, "Bugzilla::Auth::" . $loginmethod);
# 'inherit' from the main verify method
BEGIN {
for my $verifyclass (split /,\s*/, Param("user_verify_class")) {
if ($verifyclass =~ /^([A-Za-z0-9_\.\-]+)$/) {
$verifyclass = $1;
} else {
die "Badly-named user_verify_class '$verifyclass'";
}
require "Bugzilla/Auth/Verify/" . $verifyclass . ".pm";
}
}
# PRIVATE
# A number of features, like password change requests, require the DB
# verification method to be on the list.
sub has_db {
for (split (/[\s,]+/, Param("user_verify_class"))) {
if (/^DB$/) {
return 1;
}
}
return 0;
}
# Returns the network address for a given ip
sub get_netaddr {
my $ipaddr = shift;
@ -61,6 +73,53 @@ 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 $class = shift;
my @args = @_;
my @firstresult = ();
my @result = ();
for my $method (split /,\s*/, Param("user_verify_class")) {
$method = "Bugzilla::Auth::Verify::" . $method;
@result = $method->authenticate(@args);
@firstresult = @result unless @firstresult;
if (($result[0] != AUTH_NODATA)&&($result[0] != AUTH_LOGINFAILED)) {
$current_verify_class = $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_class")) {
$method = "Bugzilla::Auth::Verify::" . $method;
if ($method->can_edit('new')) {
$current_verify_class = $method;
}
}
return @result;
}
sub can_edit {
my ($class, $type) = @_;
if ($current_verify_class) {
return $current_verify_class->can_edit($type);
}
# $current_verify_class will not be set if the user isn't logged in. That
# happens when the user is trying to create a new account, which (for now)
# is hard-coded to work with DB.
elsif (has_db) {
return Bugzilla::Auth::Verify::DB->can_edit($type);
}
return 0;
}
1;
__END__
@ -78,16 +137,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<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.
Modules for obtaining the data are located under L<Bugzilla::Auth::Login>, and
modules for authenticating are located in L<Bugzilla::Auth::Verify>.
=head1 METHODS
@ -108,7 +159,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<Bugzilla::Auth> uses for
authentication are wrappers that check all configured modules (via the
C<Param('user_info_class')> and C<Param('user_verify_class')>) in sequence.
=head2 METHODS
@ -175,19 +228,36 @@ Note that this argument is a string, not a tag.
=back
=item C<current_verify_class>
This scalar gets populated with the full name (eg.,
C<Bugzilla::Auth::Verify::DB>) 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<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|Bugzilla::Token>.
This determines if the user's account details can be modified. It returns a
reference to a hash with the keys C<userid>, C<login_name>, and C<realname>,
which determine whether their respective profile values may be altered, and
C<new>, which determines if new accounts may be created.
Each user verification method (chosen with C<Param('user_verify_class')> 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<can_edit> 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<Bugzilla::Auth::CGI|Bugzilla::Auth::CGI>
particular way. For example,
L<Bugzilla::Auth::Login::WWW::CGI|Bugzilla::Auth::Login::WWW::CGI>
logs in users from CGI scripts, first by using form variables, and then
by trying cookies as a fallback.
@ -250,5 +320,5 @@ user-performed password changes.
=head1 SEE ALSO
L<Bugzilla::Auth::CGI>, L<Bugzilla::Auth::Cookie>, L<Bugzilla::Auth::DB>
L<Bugzilla::Auth::Login::WWW::CGI>, L<Bugzilla::Auth::Login::WWW::CGI::Cookie>, L<Bugzilla::Auth::Verify::DB>

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -0,0 +1,109 @@
# -*- 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): Erik Stambaugh <erik@dasbistro.com>
package Bugzilla::Auth::Login::WWW;
use strict;
use Bugzilla::Constants;
use Bugzilla::Config;
# $current_login_class stores the name of the login style that succeeded.
my $current_login_class = undef;
sub login_class {
my ($class, $type) = @_;
if ($type) {
$current_login_class = $type;
}
return $current_login_class;
}
sub login {
my ($class, $type) = @_;
my $user = Bugzilla->user;
# 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;
}
$type = LOGIN_NORMAL unless defined $type;
# Log in using whatever methods are defined in user_info_class.
# Please note the particularly strange way require() and the function
# calls are being done, because we're calling a module that's named in
# a string. I assure you it works, and it avoids the need for an eval().
my $userid;
for my $login_class (split(/,\s*/, Param('user_info_class'))) {
require "Bugzilla/Auth/Login/WWW/" . $login_class . ".pm";
$userid = "Bugzilla::Auth::Login::WWW::$login_class"->login($type);
if ($userid) {
$class->login_class("Bugzilla::Auth::Login::WWW::$login_class");
last;
}
}
if ($userid) {
$user = new Bugzilla::User($userid);
# Compat stuff
$::userid = $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->login;
} else {
Bugzilla->logout_request();
}
return $user;
}
sub logout {
my ($class, $user, $option) = @_;
if ($class->login_class) {
$class->login_class->logout($user, $option);
}
}
1;
__END__
=head1 NAME
Bugzilla::Auth::Login::WWW - WWW login information gathering module
=head1 METHODS
=item C<login>
Passes C<login> calls to each class defined in the param C<user_info_class>
and returns a C<Bugzilla::User> object from the first one that successfully
gathers user login information.

Просмотреть файл

@ -0,0 +1,260 @@
# -*- 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>
# Erik Stambaugh <erik@dasbistro.com>
package Bugzilla::Auth::Login::WWW::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_class");
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::WWW::CGI::Cookie;
my $authmethod = "Cookie";
($authres, $userid, $extra) =
Bugzilla::Auth::Login::WWW::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'),
'has_db' => Bugzilla::Auth->has_db,
}
)
|| 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()");
}
if ($option != LOGOUT_KEEP_CURRENT) {
clear_browser_cookies();
Bugzilla->logout_request();
}
}
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::WWW::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. Logouts are also handled here.
=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::Login::WWW::CGI::Cookie>.
=head1 SEE ALSO
L<Bugzilla::Auth>

Просмотреть файл

@ -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 <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::Login::WWW::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::WWW::CGI::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::Login::WWW::CGI> handles this.
=head1 SEE ALSO
L<Bugzilla::Auth>, L<Bugzilla::Auth::Login::WWW::CGI>

Просмотреть файл

@ -0,0 +1,138 @@
How Auth Works
==============
Christian Reis <kiko@async.com.br>
Overview
--------
Authentication in Bugzilla is handled by a collection of modules that live in
the Bugzilla::Auth package. These modules are organized hierarchically based
upon their responsibility.
The authentication scheme is divided in two tasks: Login and Verify. Login
involves gathering credentials from a user, while Verify validates them
against an authentication service.
The Bugzilla parameters user_info_class and user_verify_class contain a
list of Login and Verify modules, respectively.
Task: Login
-----------
This task obtains user credentials based on a request. Examples of requests
include CGI access from the Bugzilla web interface, email submissions and
credentials supplied by standalone scripts.
Each type of Bugzilla front-end should have its own package. For instance,
access via the Bugzilla web pages should go through Bugzilla::Auth::WWW.
These packages would contain modules of their own to perform whatever extra
functions are needed, like the CGI and Cookie modules in the case of WWW.
Task: Verify
------------
This task validates user credentials against a user authentication service.
The default service in Bugzilla has been the database, which stores the
login_name and cryptpasswd fields in the profiles table. An alternative means
of validation, LDAP, is already supported, and other contributions would be
appreciated.
The module layout is similar to the Login package, but there is no need for a
sub-level as there is with Login request types.
Params
------
There are two params that define behaviour for each authentication task. Each
of them defines a comma-separated list of modules to be tried in order.
- user_info_class determines the module(s) used to obtain user
credentials. This param is specific to the requests from Bugzilla web
pages, so all of the listed modules live under
Bugzilla::Auth::Login::WWW
- user_verify_class determines the module(s) used to verify credentials.
This param is general and concerns the whole Bugzilla instance, since
the same back end should be used regardless of what front end is used.
Responsibilities
----------------
Bugzilla::Auth
This module is responsible for abstracting away as much as possible the
login and logout tasks in Bugzilla.
It offers login() and logout() methods that are proxied to the selected
login and verify packages.
Bugzilla::Auth::Login
This is a container to hold the various modules for each request type.
Bugzilla::Auth::Login::WWW
This module is responsible for abstracting away details of which web-based
login modules exist and are in use. It offers login() and logout() methods
that proxy through to whatever specific modules
Bugzilla::Auth::Verify
This module is responsible for abstracting away details of which
credential verification modules exist, and should proxy calls through to
them. There is a method that is particularly important, and which should
be proxied through to the specific:
can_edit($type)
This method takes an argument that specifies what sort of change
is being requested; the specific module should return 1 or 0 based
on the fact that it implements or not the required change.
Current values for $type are "new" for new accounts, and "userid",
"login_name", "realname" for their respective fields.
Specific Login Modules
----------------------
WWW
The main authentication frontend; regular pages (CGIs) should use only
this module. It offers a convenient frontend to the main functionality
that CGIs need, using form parameters and cookies.
- Cookie
Implements part of the backend code that deals with browser
cookies. It's actually tied in to DB.pm, so Cookie logins that use
LDAP won't work at all.
LDAP
The other authentication module is LDAP-based; it is *only* used for
password authentication and not for any other login-related task (it
actually relies on the database to handle the profile information).
Legacy
------
Bugzilla.pm
There is glue code that currently lives in the top-level module
Bugzilla.pm; this module handles backwards-compatibility data that is used
in a number of CGIs. This data has been slowly removed from the Bugzilla
pages and eventually should go away completely, at which point Bugzilla.pm
will be just a wrapper to conveniently offer template, cgi, dbh and user
variables.
This module is meant to be used only by Bugzilla pages, and in the case of
a reorganization which moves CGI-specific code to a subdirectory,
Bugzilla.pm should go with it.
$::COOKIE
There are still instances of use of $::COOKIE to obtain Logincookie
information; these should be removed as well.

Просмотреть файл

@ -0,0 +1,135 @@
# -*- 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>
# Erik Stambaugh <erik@dasbistro.com>
package Bugzilla::Auth::Verify::DB;
use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Util;
my $edit_options = {
'new' => 1,
'userid' => 0,
'login_name' => 1,
'realname' => 1,
};
sub can_edit {
my ($class, $type) = @_;
return $edit_options->{$type};
}
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<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>

Просмотреть файл

@ -0,0 +1,196 @@
# -*- 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>
# Erik Stambaugh <erik@dasbistro.com>
package Bugzilla::Auth::Verify::LDAP;
use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
use Net::LDAP;
my $edit_options = {
'new' => 0,
'userid' => 0,
'login_name' => 0,
'realname' => 0,
};
sub can_edit {
my ($class, $type) = @_;
return $edit_options->{$type};
}
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<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>

Просмотреть файл

@ -25,6 +25,7 @@
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Christopher Aillon <christopher@aillon.com>
# Erik Stambaugh <erik@dasbistro.com>
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_class'}) {
$param{'user_verify_class'} = $param{'loginmethod'};
delete $param{'loginmethod'};
}
# --- DEFAULTS FOR NEW PARAMS ---
foreach my $item (@param_list) {

Просмотреть файл

@ -27,6 +27,7 @@
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Tobias Burnus <burnus@net-b.de>
# Gervase Markham <gerv@gerv.net>
# Erik Stambaugh <erik@dasbistro.com>
#
#
# 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_class')) {
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";
}
}
}

Просмотреть файл

@ -37,7 +37,7 @@ use vars qw(
);
# If we're using LDAP for login, then we can't create a new account here.
unless (Bugzilla::Auth->can_edit) {
unless (Bugzilla::Auth->can_edit('new')) {
# Just in case someone already has an account, let them get the correct
# footer on the error message
Bugzilla->login();

Просмотреть файл

@ -25,6 +25,7 @@
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
#
# 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_class {
# 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 $class (split /,\s*/, $list) {
my $res = check_multi($class, $entry);
return $res if $res;
if ($class eq 'DB') {
# No params
} elsif ($class 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_class '$class' in check_user_verify_class";
}
}
return "";
}
@ -432,9 +435,40 @@ sub find_languages {
default => '',
},
# XXX in the future:
#
# user_verify_class and user_info_class 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 => 'loginmethod',
desc => 'The type of login authentication to use:
name => 'user_info_class',
desc => 'Mechanism(s) to be used for gathering a user\'s login information.
<add>
More than one may be selected. If the first one returns nothing,
the second is tried, and so on.<br />
The types are:
<dl>
<dt>CGI</dt>
<dd>
Asks for username and password via CGI form interface.
</dd>
</dl>',
type => 's',
choices => [ 'CGI' ],
default => 'CGI',
checker => \&check_multi
},
{
name => 'user_verify_class',
desc => 'Mechanism(s) to be used for verifying (authenticating) information
gathered by user_info_class.
More than one may be selected. If the first one cannot find the
user, the second is tried, and so on.<br />
The types are:
<dl>
<dt>DB</dt>
<dd>
@ -450,9 +484,9 @@ sub find_languages {
</dd>
</dl>',
type => 's',
choices => [ 'DB', 'LDAP' ],
choices => [ 'DB', 'LDAP', 'DB,LDAP', 'LDAP,DB' ],
default => 'DB',
checker => \&check_loginmethod
checker => \&check_user_verify_class
},
{

Просмотреть файл

@ -23,6 +23,7 @@
# Joe Robins <jmrobins@tgix.com>
# Dan Mosedale <dmose@mozilla.org>
# Joel Peshkin <bugreport@peshkin.net>
# Erik Stambaugh <erik@dasbistro.com>
#
# Direct any questions on this source code to
#
@ -114,15 +115,11 @@ sub EmitFormElements ($$$$)
if ($editall) {
print "</TR><TR>\n";
print " <TH ALIGN=\"right\">Password:</TH>\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>
(enter new password to change)
</TD>
|;
}
print "</TR><TR>\n";
print " <TH ALIGN=\"right\">Disable text:</TH>\n";
@ -209,7 +206,7 @@ sub EmitFormElements ($$$$)
sub PutTrailer (@)
{
my (@links) = ("Back to the <a href=\"./\">index</a>");
if($editall && Bugzilla::Auth->can_edit) {
if($editall) {
push(@links,
"<a href=\"editusers.cgi?action=add\">add</a> a new user");
}
@ -361,7 +358,7 @@ if ($action eq 'list') {
}
print "</TR>";
}
if ($editall && Bugzilla::Auth->can_edit) {
if ($editall) {
print "<TR>\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 "<FORM METHOD=POST ACTION=editusers.cgi>\n";
print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\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));

Просмотреть файл

@ -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'],
);

Просмотреть файл

@ -25,6 +25,7 @@
# form: hash; the form values which need to be submitted to the target script
# mform: hash; the form values with multiple values which need to be
# submitted to the target script
# has_db: true if DB is one of the available authentication mechanisms
#%]
[% PROCESS global/variables.none.tmpl %]
@ -101,23 +102,32 @@
#%]
[% IF caneditaccount %]
<hr>
[% IF Param("createemailregexp") %]
<hr>
<p>
If you don't have a [% terms.Bugzilla %] account, you can
<a href="createaccount.cgi">create a new account</a>.
</p>
[% END %]
<form method="get" action="token.cgi">
<input type="hidden" name="a" value="reqpw">
If you have an account, but have forgotten your password,
enter your login name below and submit a request
to change your password.<br>
<input size="35" name="loginname">
<input type="submit" value="Submit Request">
</form>
[%# For now, password change requests only apply to the DB
# verification method #%]
[% IF has_db != 0 %]
<hr>
<form method="get" action="token.cgi">
<input type="hidden" name="a" value="reqpw">
If you have an account, but have forgotten your password,
enter your login name below and submit a request
to change your password.<br>
<input size="35" name="loginname">
<input type="submit" value="Submit Request">
</form>
[% END %]
<hr>
[% END %]

Просмотреть файл

@ -613,6 +613,10 @@
[% title = "Old Password Required" %]
You must enter your old password to change your email address.
[% ELSIF error == "password_change_requests_not_allowed" %]
[% title = "Password Change Requests Not Allowed" %]
The system is not configured to allow password change requests.
[% ELSIF error == "passwords_dont_match" %]
[% title = "Passwords Don't Match" %]
The two passwords you entered did not match.

Просмотреть файл

@ -95,12 +95,19 @@ if ($cgi->param('t')) {
}
}
# If the user is requesting a password change, make sure they submitted
# their login name and it exists in the database.
# their login name and it exists in the database, and that the DB module is in
# the list of allowed verification methids.
if ( $::action eq 'reqpw' ) {
defined $cgi->param('loginname')
|| ThrowUserError("login_needed_for_password_change");
# check verification methods
unless (Bugzilla::Auth->has_db) {
ThrowUserError("password_change_requests_not_allowed");
}
# Make sure the login name looks like an email address. This function
# displays its own error and stops execution if the login name looks wrong.
CheckEmailSyntax($cgi->param('loginname'));