summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Auth
diff options
context:
space:
mode:
authormkanat%bugzilla.org <>2006-05-12 11:40:56 +0200
committermkanat%bugzilla.org <>2006-05-12 11:40:56 +0200
commitd9cbb0f0a62bba345ed26ac68364bb441f41d35d (patch)
tree415d30523fb728a3192970a6d2b168b095f260dc /Bugzilla/Auth
parentd7447bf95827d7e9da681d496a192fffbc2810a4 (diff)
downloadbugzilla-d9cbb0f0a62bba345ed26ac68364bb441f41d35d.tar.gz
bugzilla-d9cbb0f0a62bba345ed26ac68364bb441f41d35d.tar.xz
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
Diffstat (limited to 'Bugzilla/Auth')
-rw-r--r--Bugzilla/Auth/Login.pm125
-rw-r--r--Bugzilla/Auth/Login/CGI.pm73
-rw-r--r--Bugzilla/Auth/Login/Cookie.pm83
-rw-r--r--Bugzilla/Auth/Login/Env.pm54
-rw-r--r--Bugzilla/Auth/Login/Stack.pm87
-rw-r--r--Bugzilla/Auth/Login/WWW.pm111
-rw-r--r--Bugzilla/Auth/Login/WWW/CGI.pm275
-rw-r--r--Bugzilla/Auth/Login/WWW/CGI/Cookie.pm113
-rw-r--r--Bugzilla/Auth/Login/WWW/Env.pm156
-rw-r--r--Bugzilla/Auth/Persist/Cookie.pm153
-rw-r--r--Bugzilla/Auth/README132
-rw-r--r--Bugzilla/Auth/Verify.pm223
-rw-r--r--Bugzilla/Auth/Verify/DB.pm96
-rw-r--r--Bugzilla/Auth/Verify/LDAP.pm248
-rw-r--r--Bugzilla/Auth/Verify/Stack.pm81
15 files changed, 1002 insertions, 1008 deletions
diff --git a/Bugzilla/Auth/Login.pm b/Bugzilla/Auth/Login.pm
new file mode 100644
index 000000000..4a4c5f26d
--- /dev/null
+++ b/Bugzilla/Auth/Login.pm
@@ -0,0 +1,125 @@
+# -*- 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
diff --git a/Bugzilla/Auth/Login/CGI.pm b/Bugzilla/Auth/Login/CGI.pm
new file mode 100644
index 000000000..14b64ee79
--- /dev/null
+++ b/Bugzilla/Auth/Login/CGI.pm
@@ -0,0 +1,73 @@
+# -*- 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;
diff --git a/Bugzilla/Auth/Login/Cookie.pm b/Bugzilla/Auth/Login/Cookie.pm
new file mode 100644
index 000000000..e4cc0daac
--- /dev/null
+++ b/Bugzilla/Auth/Login/Cookie.pm
@@ -0,0 +1,83 @@
+# -*- 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;
diff --git a/Bugzilla/Auth/Login/Env.pm b/Bugzilla/Auth/Login/Env.pm
new file mode 100644
index 000000000..fda71bf35
--- /dev/null
+++ b/Bugzilla/Auth/Login/Env.pm
@@ -0,0 +1,54 @@
+# -*- 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;
diff --git a/Bugzilla/Auth/Login/Stack.pm b/Bugzilla/Auth/Login/Stack.pm
new file mode 100644
index 000000000..d51003861
--- /dev/null
+++ b/Bugzilla/Auth/Login/Stack.pm
@@ -0,0 +1,87 @@
+# -*- 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;
diff --git a/Bugzilla/Auth/Login/WWW.pm b/Bugzilla/Auth/Login/WWW.pm
deleted file mode 100644
index 29cc7fced..000000000
--- a/Bugzilla/Auth/Login/WWW.pm
+++ /dev/null
@@ -1,111 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): 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
diff --git a/Bugzilla/Auth/Login/WWW/CGI.pm b/Bugzilla/Auth/Login/WWW/CGI.pm
deleted file mode 100644
index 5030691e3..000000000
--- a/Bugzilla/Auth/Login/WWW/CGI.pm
+++ /dev/null
@@ -1,275 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): Terry Weissman <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>
diff --git a/Bugzilla/Auth/Login/WWW/CGI/Cookie.pm b/Bugzilla/Auth/Login/WWW/CGI/Cookie.pm
deleted file mode 100644
index c2244d15d..000000000
--- a/Bugzilla/Auth/Login/WWW/CGI/Cookie.pm
+++ /dev/null
@@ -1,113 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): Terry Weissman <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>
diff --git a/Bugzilla/Auth/Login/WWW/Env.pm b/Bugzilla/Auth/Login/WWW/Env.pm
deleted file mode 100644
index f437bf06f..000000000
--- a/Bugzilla/Auth/Login/WWW/Env.pm
+++ /dev/null
@@ -1,156 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): 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>
diff --git a/Bugzilla/Auth/Persist/Cookie.pm b/Bugzilla/Auth/Persist/Cookie.pm
new file mode 100644
index 000000000..ce59ef4bd
--- /dev/null
+++ b/Bugzilla/Auth/Persist/Cookie.pm
@@ -0,0 +1,153 @@
+# -*- 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;
diff --git a/Bugzilla/Auth/README b/Bugzilla/Auth/README
deleted file mode 100644
index e573e2c0b..000000000
--- a/Bugzilla/Auth/README
+++ /dev/null
@@ -1,132 +0,0 @@
-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.
-
diff --git a/Bugzilla/Auth/Verify.pm b/Bugzilla/Auth/Verify.pm
new file mode 100644
index 000000000..cbff2c73f
--- /dev/null
+++ b/Bugzilla/Auth/Verify.pm
@@ -0,0 +1,223 @@
+# -*- 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
diff --git a/Bugzilla/Auth/Verify/DB.pm b/Bugzilla/Auth/Verify/DB.pm
index 405a737b8..88ad78d54 100644
--- a/Bugzilla/Auth/Verify/DB.pm
+++ b/Bugzilla/Auth/Verify/DB.pm
@@ -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>
diff --git a/Bugzilla/Auth/Verify/LDAP.pm b/Bugzilla/Auth/Verify/LDAP.pm
index 376fac71d..848018549 100644
--- a/Bugzilla/Auth/Verify/LDAP.pm
+++ b/Bugzilla/Auth/Verify/LDAP.pm
@@ -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>
diff --git a/Bugzilla/Auth/Verify/Stack.pm b/Bugzilla/Auth/Verify/Stack.pm
new file mode 100644
index 000000000..577b5a22f
--- /dev/null
+++ b/Bugzilla/Auth/Verify/Stack.pm
@@ -0,0 +1,81 @@
+# -*- 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;