Commit d9cbb0f0 authored by mkanat%bugzilla.org's avatar mkanat%bugzilla.org

Bug 300410: Bugzilla::Auth needs to be restructured to not require a BEGIN block

Patch By Max Kanat-Alexander <mkanat@bugzilla.org> r=LpSolit, a=myk
parent d7447bf9
......@@ -26,7 +26,7 @@ package Bugzilla;
use strict;
use Bugzilla::Auth;
use Bugzilla::Auth::Login::WWW;
use Bugzilla::Auth::Persist::Cookie;
use Bugzilla::CGI;
use Bugzilla::Config;
use Bugzilla::Constants;
......@@ -160,7 +160,13 @@ sub sudo_request {
sub login {
my ($class, $type) = @_;
my $authenticated_user = Bugzilla::Auth::Login::WWW->login($type);
my $authorizer = new Bugzilla::Auth();
$type = LOGIN_REQUIRED if Bugzilla->cgi->param('GoAheadAndLogIn');
if (!defined $type || $type == LOGIN_NORMAL) {
$type = Param('requirelogin') ? LOGIN_REQUIRED : LOGIN_NORMAL;
}
my $authenticated_user = $authorizer->login($type);
# At this point, we now know if a real person is logged in.
# We must now check to see if an sudo session is in progress.
......@@ -200,14 +206,15 @@ sub logout {
return unless user->id;
$option = LOGOUT_CURRENT unless defined $option;
Bugzilla::Auth::Login::WWW->logout($_user, $option);
Bugzilla::Auth::Persist::Cookie->logout({type => $option});
Bugzilla->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
}
sub logout_user {
my ($class, $user) = @_;
# When we're logging out another user we leave cookies alone, and
# therefore avoid calling Bugzilla->logout() directly.
Bugzilla::Auth::Login::WWW->logout($user, LOGOUT_ALL);
Bugzilla::Auth::Persist::Cookie->logout({user => $user});
}
# just a compatibility front-end to logout_user that gets a user by id
......
......@@ -19,40 +19,181 @@
#
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth;
use strict;
use fields qw(
_info_getter
_verifier
_persister
);
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Config;
use Bugzilla::Auth::Login::Stack;
use Bugzilla::Auth::Verify::Stack;
use Bugzilla::Auth::Persist::Cookie;
use Switch;
sub new {
my ($class, $params) = @_;
my $self = fields::new($class);
$params ||= {};
$params->{Login} ||= Param('user_info_class') . ',Cookie';
$params->{Verify} ||= Param('user_verify_class');
$self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
$self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
# If we ever have any other login persistence methods besides cookies,
# this could become more configurable.
$self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
return $self;
}
# The verification method that was successfully used upon login, if any
my $current_verify_class = undef;
sub login {
my ($self, $type) = @_;
my $dbh = Bugzilla->dbh;
# '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'";
# Get login info from the cookie, form, environment variables, etc.
my $login_info = $self->{_info_getter}->get_login_info();
if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type);
}
# Now verify his username and password against the DB, LDAP, etc.
if ($self->{_info_getter}->{successful}->requires_verification) {
$login_info = $self->{_verifier}->check_credentials($login_info);
if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type);
}
require "Bugzilla/Auth/Verify/" . $verifyclass . ".pm";
$login_info =
$self->{_verifier}->{successful}->create_or_update_user($login_info);
}
else {
$login_info = $self->{_verifier}->create_or_update_user($login_info);
}
if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type);
}
# Make sure the user isn't disabled.
my $user = $login_info->{user};
if ($user->disabledtext) {
return $self->_handle_login_result({ failure => AUTH_DISABLED,
user => $user }, $type);
}
$user->set_authorizer($self);
return $self->_handle_login_result($login_info, $type);
}
sub can_change_password {
my ($self) = @_;
my $verifier = $self->{_verifier}->{successful};
$verifier ||= $self->{_verifier};
my $getter = $self->{_info_getter}->{successful};
$getter = $self->{_info_getter}
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
return $verifier->can_change_password &&
$getter->user_can_create_account;
}
sub can_login {
my ($self) = @_;
return $self->{_info_getter}->can_login;
}
sub can_logout {
my ($self) = @_;
my $getter = $self->{_info_getter}->{successful};
# If there's no successful getter, we're not logged in, so of
# course we can't log out!
return 0 unless $getter;
return $getter->can_logout;
}
# PRIVATE
sub user_can_create_account {
my ($self) = @_;
my $verifier = $self->{_verifier}->{successful};
$verifier ||= $self->{_verifier};
my $getter = $self->{_info_getter}->{successful};
$getter = $self->{_info_getter}
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
return $verifier->user_can_create_account
&& $getter->user_can_create_account;
}
# 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;
sub can_change_email {
return $_[0]->user_can_create_account;
}
sub _handle_login_result {
my ($self, $result, $login_type) = @_;
my $dbh = Bugzilla->dbh;
my $user = $result->{user};
my $fail_code = $result->{failure};
if (!$fail_code) {
if ($self->{_info_getter}->{successful}->requires_persistence) {
$self->{_persister}->persist_login($user);
}
}
return 0;
else {
switch ($fail_code) {
case AUTH_ERROR {
ThrowCodeError($result->{error}, $result->{details});
}
case AUTH_NODATA {
if ($login_type == LOGIN_REQUIRED) {
# 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.
$dbh->do("DELETE FROM logincookies WHERE " .
$dbh->sql_to_days('NOW()') . " - " .
$dbh->sql_to_days('lastused') . " > 30");
$self->{_info_getter}->fail_nodata($self);
}
# Otherwise, we just return the "default" user.
$user = Bugzilla->user;
}
# 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)
case [AUTH_LOGINFAILED, AUTH_NO_SUCH_USER] {
ThrowUserError("invalid_username_or_password");
}
# The account may be disabled
case AUTH_DISABLED {
$self->{_persister}->logout();
# XXX This is NOT a good way to do this, architecturally.
$self->{_persister}->clear_browser_cookies();
# and throw a user error
ThrowUserError("account_disabled",
{'disabled_reason' => $result->{user}->disabledtext});
}
# If we get here, then we've run out of options, which
# shouldn't happen.
else {
ThrowCodeError("authres_unhandled",
{ value => $fail_code });
}
}
}
return $user;
}
# Returns the network address for a given IP
......@@ -72,254 +213,300 @@ sub get_netaddr {
return "0.0.0.0" if ($maskbits == 0);
$addr >>= (32-$maskbits);
$addr <<= (32-$maskbits);
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 = ();
my $current_verify_method;
for my $method (split /,\s*/, Param("user_verify_class")) {
$current_verify_method = $method;
$method = "Bugzilla::Auth::Verify::" . $method;
@result = $method->authenticate(@args);
@firstresult = @result unless @firstresult;
if (($result[0] != AUTH_NODATA)&&($result[0] != AUTH_LOGINFAILED)) {
unshift @result, ($current_verify_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
my $chosen_verify_method;
for my $method (split /,\s*/, Param("user_verify_class")) {
$current_verify_method = $method;
$method = "Bugzilla::Auth::Verify::" . $method;
if ($method->can_edit('new')) {
$chosen_verify_method = $method;
}
}
unshift @result, $chosen_verify_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__
=head1 NAME
Bugzilla::Auth - Authentication handling for Bugzilla users
Bugzilla::Auth - An object that authenticates the login credentials for
a user.
=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).
used to obtain the username/password (from CGI, email, etc), and the
other set uses this data to authenticate against the datasource
(the Bugzilla DB, LDAP, PAM, etc.).
Modules for obtaining the username/password are subclasses of
L<Bugzilla::Auth::Login>, and modules for authenticating are subclasses
of L<Bugzilla::Auth::Verify>.
=head1 AUTHENTICATION ERROR CODES
Whenever a method in the C<Bugzilla::Auth> family fails in some way,
it will return a hashref containing at least a single key called C<failure>.
C<failure> will point to an integer error code, and depending on the error
code the hashref may contain more data.
The error codes are explained here below.
=head2 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.
=head2 C<AUTH_ERROR>
An error occurred when trying to use the login mechanism.
The hashref will also contain an C<error> element, which is the name
of an error from C<template/en/default/global/code-error.html> --
the same type of error that would be thrown by
L<Bugzilla::Error::ThrowCodeError>.
The hashref *may* contain an element called C<details>, which is a hashref
that should be passed to L<Bugzilla::Error::ThrowCodeError> as the
various fields to be used in the error message.
=head2 C<AUTH_LOGINFAILED>
An incorrect username or password was given.
=head2 C<AUTH_NO_SUCH_USER>
This is an optional more-specific version of C<AUTH_LOGINFAILED>.
Modules should throw this error when they discover that the
requested user account actually does not exist, according to them.
That is, for example, L<Bugzilla::Auth::Verify::LDAP> would throw
this if the user didn't exist in LDAP.
Modules for obtaining the data are located under L<Bugzilla::Auth::Login>, and
modules for authenticating are located in L<Bugzilla::Auth::Verify>.
The difference between C<AUTH_NO_SUCH_USER> and C<AUTH_LOGINFAILED>
should never be communicated to the user, for security reasons.
=head2 C<AUTH_DISABLED>
The user successfully logged in, but their account has been disabled.
Usually this is throw only by C<Bugzilla::Auth::login>.
=head1 LOGIN TYPES
The C<login> function (below) can do different types of login, depending
on what constant you pass into it:
=head2 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.
=head2 C<LOGIN_NORMAL>
A login may or may not be required, depending on the setting of the
I<requirelogin> parameter. This is the default if you don't specify a
type.
=head2 C<LOGIN_REQUIRED>
A login is always required to access this data.
=head1 METHODS
C<Bugzilla::Auth> contains several helper methods to be used by
authentication or login modules.
These are methods that can be called on a C<Bugzilla::Auth> object
itself.
=head2 Login
=over 4
=item C<Bugzilla::Auth::get_netaddr($ipaddr)>
=item C<login($type)>
Given an ip address, this returns the associated network address, using
C<Param('loginnetmask')> as the netmask. This can be used to obtain data
in order to restrict weak authentication methods (such as cookies) to
only some addresses.
Description: Logs a user in. For more details on how this works
internally, see the section entitled "STRUCTURE."
Params: $type - One of the Login Types from above.
Returns: An authenticated C<Bugzilla::User>. Or, if the type was
not C<LOGIN_REQUIRED>, then we return an
empty C<Bugzilla::User> if no login data was passed in.
=back
=head1 AUTHENTICATION
=head2 Info Methods
Authentication modules check a user's credentials (username, password,
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
These are methods that give information about the Bugzilla::Auth object.
=over 4
=item C<authenticate($username, $pass)>
=item C<can_change_password>
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.
Description: Tells you whether or not the current login system allows
changing passwords.
Params: None
Returns: C<true> if users and administrators should be allowed to
change passwords, C<false> otherwise.
The first return value is the name of the class that generated the results
constined in the remaining return values. The second 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.
=item C<can_login>
=item C<AUTH_OK>
Description: Tells you whether or not the current login system allows
users to log in through the web interface.
Params: None
Returns: C<true> if users can log in through the web interface,
C<false> otherwise.
Authentication succeeded. The third variable is the userid of the new
user.
=item C<can_logout>
=item C<AUTH_NODATA>
Description: Tells you whether or not the current login system allows
users to log themselves out.
Params: None
Returns: C<true> if users can log themselves out, C<false> otherwise.
If a user isn't logged in, we always return C<false>.
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<user_can_create_account>
Description: Tells you whether or not users are allowed to manually create
their own accounts, based on the current login system in use.
Note that this doesn't check the C<createemailregexp>
parameter--you have to do that by yourself in your code.
Params: None
Returns: C<true> if users are allowed to create new Bugzilla accounts,
C<false> otherwise.
=item C<can_change_email>
=item C<AUTH_ERROR>
Description: Whether or not the current login system allows users to
change their own email address.
Params: None
Returns: C<true> if users can change their own email address,
C<false> otherwise.
An error occurred when trying to use the login mechanism. The third return
value may contain the Bugzilla userid, but will probably be C<undef>,
signifiying that the userid is unknown. The fourth value is a tag describing
the error used by the authentication error templates to print a description
to the user. The optional fifth argument is a hashref of values used as part
of the tag's error descriptions.
=back
This error template must have a name/location of
I<account/auth/C<lc(authentication-type)>-error.html.tmpl>.
=head1 CLASS FUNCTIONS
=item C<AUTH_LOGINFAILED>
C<Bugzilla::Auth> contains several helper methods to be used by
authentication or login modules.
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 third 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.
=over 4
The fourth 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 fifth argument, to be used by the tag to give more detailed
information.
=item C<Bugzilla::Auth::get_netaddr($ipaddr)>
=item C<AUTH_DISABLED>
Given an ip address, this returns the associated network address, using
C<Param('loginnetmask')> as the netmask. This can be used to obtain data
in order to restrict weak authentication methods (such as cookies) to
only some addresses.
The user successfully logged in, but their account has been disabled.
The third argument in the returned array is the userid, and the fourth
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<current_verify_class>
=head1 STRUCTURE
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.
This section is mostly interesting to developers who want to implement
a new authentication type. It describes the general structure of the
Bugzilla::Auth family, and how the C<login> function works.
=item C<can_edit>
A C<Bugzilla::Auth> object is essentially a collection of a few other
objects: the "Info Getter," the "Verifier," and the "Persistence
Mechanism."
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.
They are used inside the C<login> function in the following order:
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.
=head2 The Info Getter
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.
This is a C<Bugzilla::Auth::Login> object. Basically, it gets the
username and password from the user, somehow. Or, it just gets enough
information to uniquely identify a user, and passes that on down the line.
(For example, a C<user_id> is enough to uniquely identify a user,
even without a username and password.)
=back
Some Info Getters don't require any verification. For example, if we got
the C<user_id> from a Cookie, we don't need to check the username and
password.
=head1 LOGINS
If an Info Getter returns only a C<user_id> and no username/password,
then it MUST NOT require verification. If an Info Getter requires
verfication, then it MUST return at least a C<username>.
A login module can be used to try to log in a Bugzilla user in a
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.
=head2 The Verifier
The login interface consists of the following methods:
This verifies that the username and password are valid.
=over 4
It's possible that some methods of verification don't require a password.
=item C<login>, which takes a C<$type> argument, using constants found in
C<Bugzilla::Constants>.
=head2 The Persistence Mechanism
The login method may use various authentication modules (described
above) to try to authenticate a user, and should return the userid on
success, or C<undef> on failure.
This makes it so that the user doesn't have to log in on every page.
Normally this object just sends a cookie to the user's web browser,
as that's the most common method of "login persistence."
When a login is required, but data is not present, it is the job of the
login method to prompt the user for this data.
=head2 Other Things We Do
The constants accepted by C<login> include the following:
After we verify the username and password, sometimes we automatically
create an account in the Bugzilla database, for certain authentication
types. We use the "Account Source" to get data about the user, and
create them in the database. (Or, if their data has changed since the
last time they logged in, their data gets updated.)
=item C<LOGIN_OPTIONAL>
=head2 The C<$login_data> Hash
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.
All of the C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify>
methods take an argument called C<$login_data>. This is basically
a hash that becomes more and more populated as we go through the
C<login> function.
=item C<LOGIN_NORMAL>
All C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods
also *return* the C<$login_data> structure, when they succeed. They
may have added new data to it.
A login may or may not be required, depending on the setting of the
I<requirelogin> parameter.
For all C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods,
the rule is "you must return the same hashref you were passed in." You can
modify the hashref all you want, but you can't create a new one. The only
time you can return a new one is if you're returning some error code
instead of the C<$login_data> structure.
=item C<LOGIN_REQUIRED>
Each C<Bugzilla::Auth::Login> or C<Bugzilla::Auth::Verify> method
explains in its documentation which C<$login_data> elements are
required by it, and which are set by it.
A login is always required to access this data.
Here are all of the elements that *may* be in C<$login_data>:
=item C<logout>, which takes a C<Bugzilla::User> argument for the user
being logged out, and an C<$option> argument. Possible values for
C<$option> include:
=over 4
=item C<LOGOUT_CURRENT>
=item C<user_id>
Log out the user and invalidate his currently registered session.
A Bugzilla C<user_id> that uniquely identifies a user.
=item C<LOGOUT_ALL>
=item C<username>
Log out the user, and invalidate all sessions the user has registered in
Bugzilla.
The username that was provided by the user.
=item C<LOGOUT_KEEP_CURRENT>
=item C<bz_username>
Invalidate all sessions the user has registered excluding his current
session; this option should leave the user logged in. This is useful for
user-performed password changes.
The username of this user inside of Bugzilla. Sometimes this differs from
C<username>.
=back
=item C<password>
The password provided by the user.
=item C<realname>
=head1 SEE ALSO
The real name of the user.
=item C<extern_id>
Some string that uniquely identifies the user in an external account
source. If this C<extern_id> already exists in the database with
a different username, the username will be *changed* to be the
username specified in this C<$login_data>.
That is, let's my extern_id is C<mkanat>. I already have an account
in Bugzilla with the username of C<mkanat@foo.com>. But this time,
when I log in, I have an extern_id of C<mkanat> and a C<username>
of C<mkanat@bar.org>. So now, Bugzilla will automatically change my
username to C<mkanat@bar.org> instead of C<mkanat@foo.com>.
=item C<user>
A L<Bugzilla::User> object representing the authenticated user.
Note that C<Bugzilla::Auth::login> may modify this object at various points.
=back
L<Bugzilla::Auth::Login::WWW::CGI>, L<Bugzilla::Auth::Login::WWW::CGI::Cookie>, L<Bugzilla::Auth::Verify::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.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login;
use strict;
use fields qw();
# Determines whether or not a user can logout. It's really a subroutine,
# but we implement it here as a constant. Override it in subclasses if
# that particular type of login method cannot log out.
use constant can_logout => 1;
use constant can_login => 1;
use constant requires_persistence => 1;
use constant requires_verification => 1;
use constant user_can_create_account => 0;
sub new {
my ($class) = @_;
my $self = fields::new($class);
return $self;
}
1;
__END__
=head1 NAME
Bugzilla::Auth::Login - Gets username/password data from the user.
=head1 DESCRIPTION
Bugzilla::Auth::Login is used to get information that uniquely identifies
a user and allows us to authorize their Bugzilla access.
It is mostly an abstract class, requiring subclasses to implement
most methods.
Note that callers outside of the C<Bugzilla::Auth> package should never
create this object directly. Just create a C<Bugzilla::Auth> object
and call C<login> on it.
=head1 LOGIN METHODS
These are methods that have to do with getting the actual login data
from the user or handling a login somehow.
These methods are abstract -- they MUST be implemented by a subclass.
=over 4
=item C<get_login_info()>
Description: Gets a username/password from the user, or some other
information that uniquely identifies them.
Params: None
Returns: A C<$login_data> hashref. (See L<Bugzilla::Auth> for details.)
The hashref MUST contain: C<user_id> *or* C<username>
If this is a login method that requires verification,
the hashref MUST contain C<password>.
The hashref MAY contain C<realname> and C<extern_id>.
=item C<fail_nodata()>
Description: This function is called when Bugzilla doesn't get
a username/password and the login type is C<LOGIN_REQUIRED>
(See L<Bugzilla::Auth> for a description of C<LOGIN_REQUIRED>).
That is, this handles C<AUTH_NODATA> in that situation.
This function MUST stop CGI execution when it is complete.
That is, it must call C<exit> or C<ThrowUserError> or some
such thing.
Params: None
Returns: Never Returns.
=back
=head1 INFO METHODS
These are methods that describe the capabilities of this
C<Bugzilla::Auth::Login> object. These are all no-parameter
methods that return either C<true> or C<false>.
=over 4
=item C<can_logout>
Whether or not users can log out if they logged in using this
object. Defaults to C<true>.
=item C<can_login>
Whether or not users can log in through the web interface using
this object. Defaults to C<true>.
=item C<requires_persistence>
Whether or not we should send the user a cookie if they logged in with
this method. Defaults to C<true>.
=item C<requires_verification>
Whether or not we should check the username/password that we
got from this login method. Defaults to C<true>.
=item C<user_can_create_account>
Whether or not users can create accounts, if this login method is
currently being used by the system. Defaults to C<false>.
=back
# -*- 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>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::CGI;
use strict;
use base qw(Bugzilla::Auth::Login);
use constant user_can_create_account => 1;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::User;
sub get_login_info {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $username = trim($cgi->param("Bugzilla_login"));
my $password = $cgi->param("Bugzilla_password");
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
if (!defined $username || !defined $password) {
return { failure => AUTH_NODATA };
}
return { username => $username, password => $password };
}
sub fail_nodata {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
# Redirect to SSL if required
if (Param('sslbase') ne '' and Param('ssl') ne 'never') {
$cgi->require_https(Param('sslbase'));
}
print $cgi->header();
$template->process("account/auth/login.html.tmpl",
{ 'target' => $cgi->url(-relative=>1) })
|| ThrowTemplateError($template->error());
exit;
}
1;
# -*- 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.
#
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::Cookie;
use strict;
use base qw(Bugzilla::Auth::Login);
use Bugzilla::Auth;
use Bugzilla::Constants;
use Bugzilla::User;
use Bugzilla::Util;
use constant requires_persistence => 0;
use constant requires_verification => 0;
use constant can_login => 0;
# Note that Cookie never consults the Verifier, it always assumes
# it has a valid DB account or it fails.
sub get_login_info {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
my $ip_addr = $cgi->remote_addr();
my $net_addr = Bugzilla::Auth::get_netaddr($ip_addr);
my $login_cookie = $cgi->cookie("Bugzilla_logincookie");
my $user_id = $cgi->cookie("Bugzilla_login");
if ($login_cookie && $user_id) {
# Anything goes for these params - they're just strings which
# we're going to verify against the db
trick_taint($ip_addr);
trick_taint($login_cookie);
detaint_natural($user_id);
my $query = "SELECT userid
FROM logincookies
WHERE logincookies.cookie = ?
AND logincookies.userid = ?
AND (logincookies.ipaddr = ?";
# If we have a network block that's allowed to use this cookie,
# as opposed to just a single IP.
my @params = ($login_cookie, $user_id, $ip_addr);
if (defined $net_addr) {
trick_taint($net_addr);
$query .= " OR logincookies.ipaddr = ?";
push(@params, $net_addr);
}
$query .= ")";
# If the cookie is valid, return a valid username.
if ($dbh->selectrow_array($query, undef, @params)) {
# If we logged in successfully, then update the lastused
# time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW()
WHERE cookie = ?", undef, $login_cookie);
return { user_id => $user_id };
}
}
# Either the he cookie is invalid, or we got no cookie. We don't want
# to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
# actually throw an error when it gets a bad cookie. It should just
# look like there was no cokie to begin with.
return { failure => AUTH_NODATA };
}
1;
# -*- 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>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::Env;
use strict;
use base qw(Bugzilla::Auth::Login);
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::User;
use constant can_logout => 0;
use constant can_login => 0;
use constant requires_verification => 0;
sub get_login_info {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
my $env_id = $ENV{Param("auth_env_id")} || '';
my $env_email = $ENV{Param("auth_env_email")} || '';
my $env_realname = $ENV{Param("auth_env_realname")} || '';
return { failure => AUTH_NODATA } if !$env_email;
return { username => $env_email, extern_id => $env_id,
realname => $env_realname };
}
sub fail_nodata {
ThrowCodeError('env_no_email');
}
1;
# -*- 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): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::Stack;
use strict;
use base qw(Bugzilla::Auth::Login);
use fields qw(
_stack
successful
);
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
my $list = shift;
$self->{_stack} = [];
foreach my $login_method (split(',', $list)) {
require "Bugzilla/Auth/Login/${login_method}.pm";
push(@{$self->{_stack}},
"Bugzilla::Auth::Login::$login_method"->new(@_));
}
return $self;
}
sub get_login_info {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->get_login_info(@_);
$self->{successful} = $object;
last if !$result->{failure};
# So that if none of them succeed, it's undef.
$self->{successful} = undef;
}
return $result;
}
sub fail_nodata {
my $self = shift;
# We fail from the bottom of the stack.
my @reverse_stack = reverse @{$self->{_stack}};
foreach my $object (@reverse_stack) {
# We pick the first object that actually has the method
# implemented.
if ($object->can('fail_nodata')) {
$object->fail_nodata(@_);
}
}
}
sub can_login {
my ($self) = @_;
# We return true if any method can log in.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_login;
}
return 0;
}
sub user_can_create_account {
my ($self) = @_;
# We return true if any method allows users to create accounts.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
}
1;
# -*- 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;
}
# can_logout determines if a user may log out
sub can_logout {
return 1 if (login_class && login_class->can_logout);
return 0;
}
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)
return $user if $user->id;
$type = LOGIN_REQUIRED if Bugzilla->cgi->param('GoAheadAndLogIn');
$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);
# Redirect to SSL if required
if (Param('sslbase') ne '' and Param('ssl') ne 'never') {
Bugzilla->cgi->require_https(Param('sslbase'));
}
$user->set_flags('can_logout' => $class->can_logout);
} else {
Bugzilla->logout_request();
}
return $user;
}
sub logout {
my ($class, $user, $option) = @_;
if (can_logout) {
$class->login_class->logout($user, $option);
}
}
1;
__END__
=head1 NAME
Bugzilla::Auth::Login::WWW - WWW login information gathering module
=head1 METHODS
=over
=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.
=back
# -*- 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;
use Bugzilla::Token;
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;
my $dbh = Bugzilla->dbh;
# First, try the actual login method against form variables
my $username = trim($cgi->param("Bugzilla_login"));
my $passwd = $cgi->param("Bugzilla_password");
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
# Perform the actual authentication, get the method name from the class name
my ($authmethod, $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 $logincookie = Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
VALUES (?, ?, ?, NOW())",
undef,
$logincookie, $userid, $ipaddr);
# 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') &&
($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) {
# Redirect to SSL if required
if (Param('sslbase') ne '' and Param('ssl') ne 'never') {
$cgi->require_https(Param('sslbase'));
}
# 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),
'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.
$dbh->do("DELETE FROM logincookies WHERE " .
$dbh->sql_to_days('NOW()') . " - " .
$dbh->sql_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_browser_cookies();
# 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 });
}
# This auth style allows the user to log out.
sub can_logout { return 1; }
# 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;
my $cgi = Bugzilla->cgi;
$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 the current login cookie.
# If a new cookie has been issued during this run, that's the current one.
# If not, it's the one we've received.
my $cookie;
foreach (@{$cgi->{'Bugzilla_cookie_list'}}) {
if ($_->name() eq 'Bugzilla_logincookie') {
$cookie = $_->value();
last;
}
}
$cookie ||= $cgi->cookie("Bugzilla_logincookie");
trick_taint($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->remove_cookie('Bugzilla_login');
$cgi->remove_cookie('Bugzilla_logincookie');
}
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>
# -*- 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=?";
my @params = ($login_cookie, $login, $ipaddr);
if (defined $netaddr) {
trick_taint($netaddr);
$query .= " OR logincookies.ipaddr=?";
push(@params, $netaddr);
}
$query .= ")";
my $dbh = Bugzilla->dbh;
my ($userid, $disabledtext) = $dbh->selectrow_array($query, undef, @params);
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=NOW() 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>
# -*- 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::Env;
use strict;
use Bugzilla::Config;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::User;
sub login {
my ($class, $type) = @_;
my $dbh = Bugzilla->dbh;
# XXX This does not currently work correctly with Param('requirelogin').
# Bug 253636 will hopefully see that param's needs taken care of in a
# parent module, but for the time being, this module does not honor
# the param in the way that CGI.pm does.
my $matched_userid;
my $matched_extern_id;
my $disabledtext;
# Gather the environment variables
my $env_id = $ENV{Param("auth_env_id")} || '';
my $env_email = $ENV{Param("auth_env_email")} || '';
my $env_realname = $ENV{Param("auth_env_realname")} || '';
# make sure the email field contains only a valid email address
my $emailregexp = Param("emailregexp");
if ($env_email =~ /($emailregexp)/) {
$env_email = $1;
}
else {
$env_email = '';
}
return undef unless $env_email;
# untaint the remaining values
trick_taint($env_id);
trick_taint($env_realname);
# Look in the DB for the extern_id
if ($env_id) {
($matched_userid, $disabledtext) =
$dbh->selectrow_array('SELECT userid, disabledtext
FROM profiles WHERE extern_id = ?',
undef, $env_id);
}
unless ($matched_userid) {
# There was either no match for the external ID given, or one was
# not present.
#
# Check to see if the email address is in there and has no
# external id assigned. We test for both the login name (which we
# also sent), and the id, so that we have a way of telling that we
# got something instead of a bunch of NULLs
($matched_extern_id, $matched_userid, $disabledtext) =
$dbh->selectrow_array('SELECT extern_id, userid, disabledtext
FROM profiles WHERE ' .
$dbh->sql_istrcmp('login_name', '?'),
undef, $env_email);
if ($matched_userid) {
if ($matched_extern_id) {
# someone with a different external ID has that address!
ThrowUserError("extern_id_conflict");
}
else {
# someone with no external ID used that address, time to
# add the ID!
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
undef,($env_id, $matched_userid));
}
}
else {
# Need to create a new user with that email address. Note
# that cryptpassword has been filled in with '*', since the
# user has no DB password.
insert_new_user($env_email, $env_realname, '*');
my $new_user = Bugzilla::User->new_from_login($env_email);
$matched_userid = $new_user->id;
}
}
# now that we hopefully have a username, we need to see if the data
# has to be updated. If we just created this account, then the data
# is already up to date.
my ($username, $this_realname) =
$dbh->selectrow_array('SELECT login_name, realname
FROM profiles WHERE userid = ?',
undef, $matched_userid);
if (($username ne $env_email) || ($this_realname ne $env_realname)) {
$dbh->do('UPDATE profiles SET login_name = ?, realname = ?
WHERE userid = ?', undef,
($env_email, ($env_realname || $this_realname), $matched_userid));
# If the login name may be new, make sure the regexp groups are current
my $userprofile = new Bugzilla::User($matched_userid);
$userprofile->derive_regexp_groups;
}
# Now we throw an error if the user has been disabled
if ($disabledtext) {
ThrowUserError("account_disabled",
{'disabled_reason' => $disabledtext});
}
return $matched_userid;
}
# This auth style does not allow the user to log out.
sub can_logout { return 0; }
1;
__END__
=head1 NAME
Bugzilla::Auth::Env - Environment Variable Authentication
=head1 DESCRIPTION
Many external user authentication systems supply login information to CGI
programs via environment variables. This module checks to see if those
variables are populated and, if so, assumes authentication was successful and
returns the user's ID, having automatically created a new profile if
necessary.
=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>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Persist::Cookie;
use strict;
use fields qw();
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::User;
use List::Util qw(first);
sub new {
my ($class) = @_;
my $self = fields::new($class);
return $self;
}
sub persist_login {
my ($self, $user) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
my $ip_addr = $cgi->remote_addr;
unless ($cgi->param('Bugzilla_restrictlogin') ||
Param('loginnetmask') == 32)
{
# XXX I don't like this subclass being dependent upon its parent.
$ip_addr = Bugzilla::Auth::get_netaddr($ip_addr);
}
# The IP address is valid, at least for comparing with itself in a
# subsequent login
trick_taint($ip_addr);
my $login_cookie =
Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
VALUES (?, ?, ?, NOW())",
undef, $login_cookie, $user->id, $ip_addr);
# 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') &&
$cgi->param('Bugzilla_remember') eq 'on') )
{
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $user->id,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
}
else {
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $user->id);
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie);
}
}
sub logout {
my ($self, $param) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
$param = {} unless $param;
my $user = $param->{user} || Bugzilla->user;
my $type = $param->{type} || LOGOUT_ALL;
if ($type == LOGOUT_ALL) {
$dbh->do("DELETE FROM logincookies WHERE userid = ?",
undef, $user->id);
return;
}
# The LOGOUT_*_CURRENT options require the current login cookie.
# If a new cookie has been issued during this run, that's the current one.
# If not, it's the one we've received.
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
@{$cgi->{'Bugzilla_cookie_list'}};
my $login_cookie;
if ($cookie) {
$login_cookie = $cookie->value;
}
else {
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
}
trick_taint($login_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 ($type == LOGOUT_KEEP_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
undef, $login_cookie, $user->id);
} elsif ($type == LOGOUT_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
undef, $login_cookie, $user->id);
} else {
die("Invalid type $type supplied to logout()");
}
if ($type != LOGOUT_KEEP_CURRENT) {
clear_browser_cookies();
}
}
sub clear_browser_cookies {
my $cgi = Bugzilla->cgi;
$cgi->remove_cookie('Bugzilla_login');
$cgi->remove_cookie('Bugzilla_logincookie');
}
1;
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.
# -*- 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.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Verify;
use strict;
use fields qw();
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Util;
use constant user_can_create_account => 1;
sub new {
my ($class, $login_type) = @_;
my $self = fields::new($class);
return $self;
}
sub can_change_password {
return $_[0]->can('change_password');
}
sub create_or_update_user {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $extern_id = $params->{extern_id};
my $username = $params->{bz_username} || $params->{username};
my $password = $params->{password} || '*';
my $real_name = $params->{realname} || '';
my $user_id = $params->{user_id};
# A passed-in user_id always overrides anything else, for determining
# what account we should return.
if (!$user_id) {
my $username_user_id = login_to_id($username || '');
my $extern_user_id;
if ($extern_id) {
trick_taint($extern_id);
$extern_user_id = $dbh->selectrow_array('SELECT userid
FROM profiles WHERE extern_id = ?', undef, $extern_id);
}
# If we have both a valid extern_id and a valid username, and they are
# not the same id, then we have a conflict.
if ($username_user_id && $extern_user_id
&& $username_user_id ne $extern_user_id)
{
my $extern_name = Bugzilla::User->new($extern_user_id)->login;
return { failure => AUTH_ERROR, error => "extern_id_conflict",
details => {extern_id => $extern_id,
extern_user => $extern_name,
username => $username} };
}
# If we have a valid username, but no valid id,
# then we have to create the user. This happens when we're
# passed only a username, and that username doesn't exist already.
if ($username && !$username_user_id && !$extern_user_id) {
validate_email_syntax($username)
|| return { failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $username} };
insert_new_user($username, $real_name, $password);
$username_user_id = login_to_id($username);
}
# If we have a valid username id and an extern_id, but no valid
# extern_user_id, then we have to set the user's extern_id.
if ($extern_id && $username_user_id && !$extern_user_id) {
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
undef, $extern_id, $username_user_id);
}
# Finally, at this point, one of these will give us a valid user id.
$user_id = $extern_user_id || $username_user_id;
}
# If we still don't have a valid user_id, then we weren't passed
# enough information in $params, and we should die right here.
ThrowCodeError('bad_arg', {argument => 'params', function =>
'Bugzilla::Auth::Verify::create_or_update_user'})
unless $user_id;
my $user = new Bugzilla::User($user_id);
# Now that we have a valid User, we need to see if any data has to be
# updated.
if ($username && $user->login ne $username) {
validate_email_syntax($username)
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email',
details => {addr => $username} };
$dbh->do('UPDATE profiles SET login_name = ? WHERE userid = ?',
$username, $user->id);
}
if ($real_name && $user->realname ne $real_name) {
$dbh->do('UPDATE profiles SET realname = ? WHERE userid = ?',
undef, $real_name, $user->id);
}
return { user => $user };
}
1;
__END__
=head1 NAME
Bugzilla::Auth::Verify - An object that verifies usernames and passwords.
=head1 DESCRIPTION
Bugzilla::Auth::Verify provides the "Verifier" part of the Bugzilla
login process. (For details, see the "STRUCTURE" section of
L<Bugzilla::Auth>.)
It is mostly an abstract class, requiring subclasses to implement
most methods.
Note that callers outside of the C<Bugzilla::Auth> package should never
create this object directly. Just create a C<Bugzilla::Auth> object
and call C<login> on it.
=head1 VERIFICATION METHODS
These are the methods that have to do with the actual verification.
Subclasses MUST implement these methods.
=over 4
=item C<check_credentials($login_data)>
Description: Checks whether or not a username is valid.
Params: $login_data - A C<$login_data> hashref, as described in
L<Bugzilla::Auth>.
This C<$login_data> hashref MUST contain
C<username>, and SHOULD also contain
C<password>.
Returns: A C<$login_data> hashref with C<bz_username> set. This
method may also set C<realname>. It must avoid changing
anything that is already set.
=back
=head1 MODIFICATION METHODS
These are methods that change data in the actual authentication backend.
These methods are optional, they do not have to be implemented by
subclasses.
=over 4
=item C<create_or_update_user($login_data)>
Description: Automatically creates a user account in the database
if it doesn't already exist, or updates the account
data if C<$login_data> contains newer information.
Params: $login_data - A C<$login_data> hashref, as described in
L<Bugzilla::Auth>.
This C<$login_data> hashref MUST contain
either C<user_id>, C<bz_username>, or
C<username>. If both C<username> and C<bz_username>
are specified, C<bz_username> is used as the
login name of the user to create in the database.
It MAY also contain C<extern_id>, in which
case it still MUST contain C<bz_username> or
C<username>.
It MAY contain C<password> and C<realname>.
Returns: A hashref with one element, C<user>, which is a
L<Bugzilla::User> object. May also return a login error
as described in L<Bugzilla::Auth>.
Note: This method is not abstract, it is actually implemented
and creates accounts in the Bugzilla database. Subclasses
should probably all call the C<Bugzilla::Auth::Verify>
version of this function at the end of their own
C<create_or_update_user>.
=item C<change_password($user, $password)>
Description: Modifies the user's password in the authentication backend.
Params: $user - A L<Bugzilla::User> object representing the user
whose password we want to change.
$password - The user's new password.
Returns: Nothing.
=back
=head1 INFO METHODS
These are methods that describe the capabilities of this object.
These are all no-parameter methods that return either C<true> or
C<false>.
=over 4
=item C<user_can_create_account>
Whether or not users can manually create accounts in this type of
account source. Defaults to C<true>.
=back
......@@ -28,97 +28,51 @@
# Erik Stambaugh <erik@dasbistro.com>
package Bugzilla::Auth::Verify::DB;
use strict;
use base qw(Bugzilla::Auth::Verify);
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Token;
use Bugzilla::Util;
use Bugzilla::User;
my $edit_options = {
'new' => 1,
'userid' => 0,
'login_name' => 1,
'realname' => 1,
};
sub check_credentials {
my ($self, $login_data) = @_;
my $dbh = Bugzilla->dbh;
sub can_edit {
my ($class, $type) = @_;
return $edit_options->{$type};
}
my $username = $login_data->{username};
my $user_id = login_to_id($username);
sub authenticate {
my ($class, $username, $passwd) = @_;
return { failure => AUTH_NO_SUCH_USER } unless $user_id;
return (AUTH_NODATA) unless defined $username && defined $passwd;
$login_data->{bz_username} = $username;
my $password = $login_data->{password};
my $userid = Bugzilla::User::login_to_id($username);
return (AUTH_LOGINFAILED) unless $userid;
trick_taint($username);
my ($real_password_crypted) = $dbh->selectrow_array(
"SELECT cryptpassword FROM profiles WHERE userid = ?",
undef, $user_id);
return (AUTH_LOGINFAILED, $userid)
unless $class->check_password($userid, $passwd);
# Using the internal crypted password as the salt,
# crypt the password the user entered.
my $entered_password_crypted = crypt($password, $real_password_crypted);
return { failure => AUTH_LOGINFAILED }
if $entered_password_crypted ne $real_password_crypted;
# 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_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);
Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in");
return $enteredCryptedPassword eq $realcryptpwd;
return $login_data;
}
sub change_password {
my ($class, $userid, $password) = @_;
my ($self, $user, $password) = @_;
my $dbh = Bugzilla->dbh;
my $cryptpassword = bz_crypt($password);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $userid);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $user->id);
}
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>
......@@ -26,39 +26,30 @@
# Christian Reis <kiko@async.com.br>
# Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Verify::LDAP;
use strict;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
ldap
);
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::User;
use Bugzilla::Error;
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};
}
use constant DEFAULT_PORT => 389;
use constant DEFAULT_SSL_PORT => 636;
sub authenticate {
my ($class, $username, $passwd) = @_;
use constant admin_can_create_account => 0;
use constant user_can_create_account => 0;
# 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;
sub check_credentials {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
# We need to bind anonymously to the LDAP server. This is
# because we need to get the Distinguished Name of the user trying
......@@ -67,151 +58,108 @@ sub authenticate {
# 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");
$self->_bind_ldap_anonymously();
# Now, we verify that the user exists, and get a LDAP Distinguished
# Name for the user.
my $username = $params->{username};
my $dn_result = $self->ldap->search(_bz_search_params($username),
attrs => ['dn']);
return { failure => AUTH_ERROR, error => "ldap_search_error",
details => {errstr => $dn_result->error, username => $username}
} if $dn_result->code;
return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count;
my $dn = $dn_result->shift_entry->dn;
# Check the password.
my $pw_result = $self->ldap->bind($dn, password => $params->{password});
return { failure => AUTH_LOGINFAILED } if $pw_result->code;
# And now we fill in the user's details.
my $detail_result = $self->ldap->search(_bz_search_params($username));
return { failure => AUTH_ERROR, error => "ldap_search_error",
details => {errstr => $detail_result->error, username => $username}
} if $detail_result->code;
my $user_entry = $detail_result->shift_entry;
my $mail_attr = Param("LDAPmailattribute");
if (!$user_entry->exists($mail_attr)) {
return { failure => AUTH_ERROR,
error => "ldap_cannot_retreive_attr",
details => {attr => $mail_attr} };
}
my $LDAPport = "389"; # default LDAP port
my $LDAPprotocol = "ldap";
if ($LDAPserver =~ /(ldap|ldaps):\/\/(.*)/) {
# ldap(s)://server(:port)
$LDAPprotocol = $1;
my $serverpart = $2;
if ($serverpart =~ /:/) {
# ldap(s)://server:port
($LDAPserver, $LDAPport) = split(":", $serverpart);
} else {
# ldap(s)://server
$LDAPserver = $serverpart;
if ($LDAPprotocol eq "ldaps") {
$LDAPport = "636";
}
}
} elsif ($LDAPserver =~ /:/) {
# server:port
($LDAPserver, $LDAPport) = split(":", $LDAPserver);
}
$params->{bz_username} = $user_entry->get_value($mail_attr);
$params->{realname} ||= $user_entry->get_value("displayName");
$params->{realname} ||= $user_entry->get_value("cn");
return $params;
}
my $LDAPconn = Net::LDAP->new("$LDAPprotocol://$LDAPserver:$LDAPport", version => 3);
if(!$LDAPconn) {
return (AUTH_ERROR, undef, "connect_failed");
}
sub _bz_search_params {
my ($username) = @_;
return (base => Param("LDAPBaseDN"),
scope => "sub",
filter => '(&(' . Param("LDAPuidattribute") . "=$username)"
. Param("LDAPfilter") . ')');
}
my $mesg;
sub _bind_ldap_anonymously {
my ($self) = @_;
my $bind_result;
if (Param("LDAPbinddn")) {
my ($LDAPbinddn,$LDAPbindpass) = split(":",Param("LDAPbinddn"));
$mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass);
$bind_result =
$self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
}
else {
$mesg = $LDAPconn->bind();
}
if($mesg->code) {
return (AUTH_ERROR, undef,
"connect_failed",
{ errstr => $mesg->error });
$bind_result = $self->ldap->bind();
}
ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
if $bind_result->code;
}
# 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") });
}
# We can't just do this in new(), because we're not allowed to throw any
# error from anywhere under Bugzilla::Auth::new -- otherwise we
# could create a situation where the admin couldn't get to editparams
# to fix his mistake. (Because Bugzilla->login always calls
# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
sub ldap {
my ($self) = @_;
return $self->{ldap} if $self->{ldap};
# 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
my $server = Param("LDAPserver");
ThrowCodeError("ldap_server_not_defined") unless $server;
# 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 $port = DEFAULT_PORT;
my $protocol = "ldap";
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare_cached("SELECT userid, disabledtext " .
"FROM profiles " .
"WHERE " .
$dbh->sql_istrcmp('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");
if ($server =~ /(ldap|ldaps):\/\/(.*)/) {
# ldap(s)://server(:port)
$protocol = $1;
my $server_part = $2;
if ($server_part =~ /:/) {
# ldap(s)://server:port
($server, $port) = split(":", $server_part);
} else {
# ldap(s)://server
$server = $server_part;
if ($protocol eq "ldaps") {
$port = DEFAULT_SSL_PORT;
}
}
insert_new_user($username, $userRealName);
($userid, $disabledtext) = $dbh->selectrow_array($sth,
undef,
$username);
return (AUTH_ERROR, $userid, "no_userid")
unless $userid;
} elsif ($server =~ /:/) {
# server:port
($server, $port) = split(":", $server);
}
# 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);
my $conn_string = "$protocol://$server:$port";
$self->{ldap} = new Net::LDAP($conn_string)
|| ThrowCodeError("ldap_connect_failed", { server => $conn_string });
return $self->{ldap};
}
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:E<sol>E<sol>bugzilla.mozilla.orgE<sol>> 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>
# -*- 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.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Verify::Stack;
use strict;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
_stack
successful
);
sub new {
my $class = shift;
my $list = shift;
my $self = $class->SUPER::new(@_);
$self->{_stack} = [];
foreach my $verify_method (split(',', $list)) {
require "Bugzilla/Auth/Verify/${verify_method}.pm";
push(@{$self->{_stack}},
"Bugzilla::Auth::Verify::$verify_method"->new(@_));
}
return $self;
}
sub can_change_password {
my ($self) = @_;
# We return true if any method can change passwords.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_change_password;
}
return 0;
}
sub check_credentials {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->check_credentials(@_);
$self->{successful} = $object;
last if !$result->{failure};
# So that if none of them succeed, it's undef.
$self->{successful} = undef;
}
# Returns the result at the bottom of the stack if they all fail.
return $result;
}
sub create_or_update_user {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->create_or_update_user(@_);
last if !$result->{failure};
}
# Returns the result at the bottom of the stack if they all fail.
return $result;
}
sub user_can_create_account {
my ($self) = @_;
# We return true if any method allows the user to create an account.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
}
1;
......@@ -43,6 +43,7 @@ use base qw(Exporter);
AUTH_ERROR
AUTH_LOGINFAILED
AUTH_DISABLED
AUTH_NO_SUCH_USER
USER_PASSWORD_MIN_LENGTH
USER_PASSWORD_MAX_LENGTH
......@@ -144,6 +145,7 @@ use constant AUTH_NODATA => 1;
use constant AUTH_ERROR => 2;
use constant AUTH_LOGINFAILED => 3;
use constant AUTH_DISABLED => 4;
use constant AUTH_NO_SUCH_USER => 5;
# The minimum and maximum lengths a password must have.
use constant USER_PASSWORD_MIN_LENGTH => 3;
......
......@@ -155,17 +155,17 @@ sub disabledtext { $_[0]->{'disabledtext'}; }
sub is_disabled { $_[0]->disabledtext ? 1 : 0; }
sub showmybugslink { $_[0]->{showmybugslink}; }
sub set_flags {
my $self = shift;
while (my $key = shift) {
$self->{'flags'}->{$key} = shift;
}
sub set_authorizer {
my ($self, $authorizer) = @_;
$self->{authorizer} = $authorizer;
}
sub get_flag {
my $self = shift;
my $key = shift;
return $self->{'flags'}->{$key};
sub authorizer {
my ($self) = @_;
if (!$self->{authorizer}) {
require Bugzilla::Auth;
$self->{authorizer} = new Bugzilla::Auth();
}
return $self->{authorizer};
}
# Generate a string to identify the user by name + login if the user
......@@ -1505,6 +1505,17 @@ which to identify the user. Currently the part of the user's email address
before the at sign (@), but that could change, especially if we implement
usernames not dependent on email address.
=item C<authorizer>
This is the L<Bugzilla::Auth> object that the User logged in with.
If the user hasn't logged in yet, a new, empty Bugzilla::Auth() object is
returned.
=item C<set_authorizer($authorizer)>
Sets the L<Bugzilla::Auth> object to be returned by C<authorizer()>.
Should only be called by C<Bugzilla::Auth::login>, for the most part.
=item C<queries>
Returns an array of the user's named queries, sorted in a case-insensitive
......@@ -1718,21 +1729,6 @@ When called with one argument:
Returns C<1> if the user can bless the group with that name, returns
C<0> otherwise.
=item C<set_flags>
=item C<get_flag>
User flags are template-accessible user status information, stored in the form
of a hash. For an example of use, when the current user is authenticated in
such a way that they are allowed to log out, the 'can_logout' flag is set to
true (1). The template then checks this flag before displaying the "Log Out"
link.
C<set_flags> is called with any number of key,value pairs. Flags for each key
will be set to the specified value.
C<get_flag> is called with a single key name, which returns the associated
value.
=item C<wants_bug_mail>
Returns true if the user wants mail for a given bug change.
......
......@@ -49,7 +49,7 @@ my $vars = {};
print $cgi->header();
# If we're using LDAP for login, then we can't create a new account here.
unless (Bugzilla::Auth->can_edit('new')) {
unless (Bugzilla->user->authorizer->user_can_create_account) {
ThrowUserError("auth_cant_create_account");
}
......
......@@ -53,15 +53,7 @@ my $template = Bugzilla->template;
my $vars = {};
my $buffer = $cgi->query_string();
if ($cgi->param("GoAheadAndLogIn")) {
# We got here from a login page, probably from relogin.cgi. We better
# make sure the password is legit.
Bugzilla->login(LOGIN_REQUIRED);
} else {
Bugzilla->login();
}
my $user = Bugzilla->user;
my $user = Bugzilla->login();
my $userid = $user->id;
# Backwards compatibility hack -- if there are any of the old QUERY_*
......
......@@ -75,8 +75,7 @@ elsif ($action eq 'begin-sudo') {
# and password.
# We only need to do this for authentication methods that involve Bugzilla
# directly obtaining a login (i.e. normal CGI login), as opposed to other
# methods (like Environment vars login). We assume that if a user can log
# out, they can also log in:
# methods (like Environment vars login).
# First, record if Bugzilla_login and Bugzilla_password were provided
my $credentials_provided;
......@@ -92,7 +91,7 @@ elsif ($action eq 'begin-sudo') {
# At this point, the user is logged in. However, if they used a method
# where they could have provided a username/password (i.e. CGI), but they
# did not provide a username/password, then throw an error.
if ($user->get_flag('can_logout') && !$credentials_provided) {
if ($user->authorizer->can_login && !$credentials_provided) {
ThrowUserError('sudo_password_required',
{ target_login => $cgi->param('target_login'),
reason => $cgi->param('reason')});
......
......@@ -37,11 +37,7 @@ my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
my $vars = {};
if ($cgi->param('GoAheadAndLogIn')) {
Bugzilla->login(LOGIN_REQUIRED);
} else {
Bugzilla->login();
}
Bugzilla->login();
# Editable, 'single' HTML bugs are treated slightly specially in a few places
my $single = !$cgi->param('format')
......
[%# 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.
#%]
[% PROCESS global/variables.none.tmpl %]
[% admindocslinks = {'extraconfig.html#bzldap' => 'Setting up LDAP authentication'} %]
[% 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" %]
[% terms.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: <tt>[% auth_err_tag FILTER html %]</tt>
[% END %]
......@@ -79,7 +79,7 @@
[%# For now, password change requests only apply to the DB
# verification method #%]
[% IF has_db != 0 %]
[% IF user.authorizer.can_change_password %]
<td>[ <a href="index.cgi?GoAheadAndLogIn=1#forgot">Forgot my Password</a> ]</td>
[% END %]
</tr>
......
......@@ -22,7 +22,6 @@
[%# INTERFACE:
# target: string. URL to go to after login.
# has_db: true if DB is one of the available authentication mechanisms
#%]
[% PROCESS global/variables.none.tmpl %]
......@@ -100,9 +99,7 @@
# their password, assuming that our auth method allows that.
#%]
[% IF caneditaccount %]
[% IF Param("createemailregexp") %]
[% IF Param("createemailregexp") && user.authorizer.user_can_create_account %]
<hr>
<p>
......@@ -111,10 +108,7 @@
</p>
[% END %]
[%# For now, password change requests only apply to the DB
# verification method #%]
[% IF has_db != 0 %]
[% IF user.authorizer.can_change_password %]
<hr>
<a name="forgot"></a>
......@@ -126,10 +120,6 @@
<input size="35" name="loginname">
<input type="submit" value="Submit Request">
</form>
[% END %]
<hr>
[% END %]
[% PROCESS global/footer.html.tmpl %]
......@@ -42,20 +42,21 @@
<tr>
<td colspan="2"><hr></td>
</tr>
[% IF user.authorizer.can_change_password %]
<tr>
<th align="right">New password:</th>
<td>
<input type="password" name="new_password1">
</td>
</tr>
<tr>
<th align="right">New password:</th>
<td>
<input type="password" name="new_password1">
</td>
</tr>
<tr>
<th align="right">Re-enter new password:</th>
<td>
<input type="password" name="new_password2">
</td>
</tr>
<tr>
<th align="right">Re-enter new password:</th>
<td>
<input type="password" name="new_password2">
</td>
</tr>
[% END %]
<tr>
<th align="right">Your real name (optional, but encouraged):</th>
......@@ -64,7 +65,7 @@
</td>
</tr>
[% IF Param('allowemailchange') %]
[% IF user.authorizer.can_change_email && Param('allowemailchange') %]
[% IF login_change_date %]
[% IF new_login_name %]
<tr>
......
......@@ -76,7 +76,7 @@
are impersonating them.
</p>
[% IF user.get_flag("can_logout") %]
[% IF user.authorizer.can_login %]
<p>
Finally, enter your [% terms.Bugzilla %] password:
<input type="hidden" name="Bugzilla_login" value="
......
......@@ -48,15 +48,23 @@
Attachment #[% attach_id 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 == "auth_invalid_email" %]
[% title = "Invalid Email Address" %]
We received an email address (<b>[% addr FILTER html %]</b>)
that didn't pass our syntax checking for a legal email address,
when trying to create or update your account.
[% IF default %]
A legal address must contain exactly one '@',
and at least one '.' after the @.
[% ELSE %]
[%+ Param('emailregexpdesc') %]
[% END %]
It must also not contain any of these special characters:
<tt>\ ( ) &amp; &lt; &gt; , ; : &quot; [ ]</tt>, or any whitespace.</tt>
[% ELSIF error == "authres_unhandled" %]
An authorization handler return value was not handled by the login code.
The result value of [% value FILTER html %] was not handled by
the login code.
[% ELSIF error == "bad_page_cgi_id" %]
[% title = "Invalid Page ID" %]
......@@ -103,10 +111,27 @@
[% ELSIF error == "cookies_need_value" %]
Every cookie must have a value.
[% ELSIF error == "env_no_email" %]
[% terms.Bugzilla %] did not receive an email address from the
environment.
[% IF Param("auth_env_email") %]
This means that the '[% Param("auth_env_email") FILTER html %]'
environment variable was empty or did not exist.
[% ELSE %]
You need to set the "auth_env_email" environment variable to
the name of the environment variable that will contain the
user's email address.
[% END %]
[% ELSIF error == "extension_invalid" %]
An error occured processing hook [% name FILTER html %] in
extension [% extension FILTER html %].
[% ELSIF error == "extern_id_conflict" %]
The external ID '[% extern_id FILTER html %]' already exists
in the database for '[% username FILTER html %]', but your
account source says that '[% extern_user FILTER html %]' has that ID.
[% ELSIF error == "field_type_mismatch" %]
Cannot seem to handle <code>[% field FILTER html %]</code>
and <code>[% type FILTER html %]</code> together.
......@@ -247,6 +272,19 @@
given.
[% END %]
[% ELSIF error == "ldap_bind_failed" %]
Failed to bind to the LDAP server. The error message was:
<code>[% errstr FILTER html %]</code>
[% ELSIF error == "ldap_cannot_retreive_attr" %]
The specified LDAP attribute [% attr FILTER html %] was not found.
[% ELSIF error == "ldap_connect_failed" %]
Could not connect to the LDAP server <code>[% server FILTER html %]</code>.
[% ELSIF error == "ldap_server_not_defined" %]
The LDAP server for authentication has not been defined.
[% ELSIF error == "missing_bug_id" %]
No [% terms.bug %] ID was given.
......
......@@ -57,7 +57,7 @@
[% IF user.login %]
[% ' | <a href="sanitycheck.cgi">Sanity&nbsp;check</a>'
IF user.groups.tweakparams %]
[% IF user.get_flag('can_logout') %]
[% IF user.authorizer.can_logout %]
| <a href="relogin.cgi">Log&nbsp;out</a>&nbsp;
[% ELSE %]
| Logged&nbsp;in&nbsp;as&nbsp;
......@@ -70,10 +70,13 @@
[% user.login FILTER html %]
[% END %]
[% ELSE %]
[% IF Param('createemailregexp') %]
[% IF Param('createemailregexp')
&& user.authorizer.user_can_create_account %]
| <a href="createaccount.cgi">New&nbsp;Account</a>
[% END %]
| <a href="index.cgi?GoAheadAndLogIn=1">Log&nbsp;In</a>
[% IF user.authorizer.can_login %]
| <a href="index.cgi?GoAheadAndLogIn=1">Log&nbsp;In</a>
[% END %]
[% END %]
</div>
</div>
......
......@@ -1339,10 +1339,6 @@
[% title = "Wrong Token" %]
That token cannot be used to change your password.
[% ELSIF error == "extern_id_conflict" %]
[% title = "Extern ID Conflict" %]
Someone with a different external ID has that address.
[% ELSIF error == "wrong_token_for_confirming_email_change" %]
[% title = "Wrong Token" %]
That token cannot be used to change your email address.
......
......@@ -69,12 +69,12 @@ function addSidebar() {
<li id="report"><a href="report.cgi">Summary reports and charts</a></li>
[% IF user.id %]
<li id="userprefs"><a href="userprefs.cgi">Change password or user preferences</a></li>
[% IF user.get_flag('can_logout') %]
[% IF user.authorizer.can_logout %]
<li id="logout"><a href="relogin.cgi">Log out [% user.login FILTER html %]</a></li>
[% END %]
[% ELSE %]
[% ELSIF user.authorizer.can_login %]
[% PROCESS "account/auth/login-small.html.tmpl" %]
[% IF Param('createemailregexp') %]
[% IF Param('createemailregexp') && user.authorizer.user_can_create_account %]
<li id="account"><a href="createaccount.cgi">Open a new [% terms.Bugzilla %] account</a></li>
[% END %]
[% END %]
......
......@@ -97,7 +97,7 @@ function normal_keypress_handler( aEvent ) {
[%- IF user.groups.tweakparams %]
<text class="text-link" onclick="load_relative_url('sanitycheck.cgi')" value="sanity check"/>
[%- END %]
[%- IF user.get_flag('can_logout') %]
[%- IF user.authorizer.can_logout %]
<text class="text-link" onclick="load_relative_url('relogin.cgi')" value="log out [% user.login FILTER html %]"/>
[%- END %]
<separator class="thin"/>
......
......@@ -104,7 +104,7 @@ if ( $::action eq 'reqpw' ) {
|| ThrowUserError("login_needed_for_password_change");
# check verification methods
unless (Bugzilla::Auth->has_db) {
unless (Bugzilla->user->authorizer->can_change_password) {
ThrowUserError("password_change_requests_not_allowed");
}
......
......@@ -50,7 +50,8 @@ sub DoAccount {
($vars->{'realname'}) = $dbh->selectrow_array(
"SELECT realname FROM profiles WHERE userid = ?", undef, $user->id);
if(Param('allowemailchange')) {
if(Param('allowemailchange')
&& Bugzilla->user->authorizer->can_change_email) {
my @token = $dbh->selectrow_array(
"SELECT tokentype, issuedate + " .
$dbh->sql_interval(3, 'DAY') . ", eventdata
......
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