diff options
Diffstat (limited to 'Bugzilla/Auth/Verify')
-rw-r--r-- | Bugzilla/Auth/Verify/DB.pm | 96 | ||||
-rw-r--r-- | Bugzilla/Auth/Verify/LDAP.pm | 248 | ||||
-rw-r--r-- | Bugzilla/Auth/Verify/Stack.pm | 81 |
3 files changed, 204 insertions, 221 deletions
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; |