Last Comment Bug 401050 - LDAP doesn't support `references`
: LDAP doesn't support `references`
Status: NEW
:
Product: Bugzilla
Classification: Server Software
Component: Bugzilla-General (show other bugs)
: 3.1.2
: All All
: -- minor with 1 vote (vote)
: ---
Assigned To: Nobody; OK to take it and work on it
: default-qa
:
Mentors:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2007-10-24 20:10 PDT by Clement Chan
Modified: 2013-08-01 10:38 PDT (History)
1 user (show)
See Also:
QA Whiteboard:
Iteration: ---
Points: ---


Attachments

Description Clement Chan 2007-10-24 20:10:44 PDT
User-Agent:       Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.3) Gecko/20070417 Fedora/2.0.0.3-4.fc7 Firefox/2.0.0.3
Build Identifier: 3.0.2

In an LDAP environment, the LDAP server can return an array of references for the login_in name if the first search() call fails. (See actual result For Net::LDAP returned hash object)  If `references` is provided by the LDAP server, the returned hash value will contain a reference field for retry. The current Bugzilla::Auth::Verify::LDAP::check_credentials() and Bugzilla::Auth::Verify::LDAP::ldap() are being invoked without parameter, as a result the retained value of the LDAP connection object fails to re-bind properly. This is currently how Net::LDAP works, you can't call the bind() by using the same object again. It will fail. 

The Net::LDAP->new($server) must be invoked again every time the ldap() method is called.


Reproducible: Always

Steps to Reproduce:
1.configure LDAP with reference
2.login using LDAP account
3.look at the search() return hash value to see the reference address.
   (please see actual results below)
Actual Results:  
0  Net::LDAP::Search=HASH(0x9e86994)
   'callback' => undef
   'errorMessage' => ''
   'matchedDN' => ''
   'mesgid' => 2
   'parent' => Net::LDAP=HASH(0x9e85bd8)
      'net_ldap_async' => 0
      'net_ldap_debug' => 0
      'net_ldap_host' => 'ldap.sh.mvista.com'
      'net_ldap_mesg' => HASH(0x9e86124)
           empty hash
      'net_ldap_refcnt' => 1
      'net_ldap_resp' => HASH(0x9e85c68)
           empty hash
      'net_ldap_socket' => IO::Socket::INET=GLOB(0x9e85ab8)
         -> *Symbol::GEN0
               FileHandle({*Symbol::GEN0}) => fileno(7)
      'net_ldap_uri' => 'ldap.sh.mvista.com'
      'net_ldap_version' => 3
   'reference' => ARRAY(0xa87eb88)
      0  'ldap://10.40.0.70/ou=People,dc=mvista,dc=com'
      1  'ldap://10.40.0.70/ou=Group,dc=mvista,dc=com'
   'resultCode' => 0

Expected Results:  
The result should be successful after retrying the search() with the referenced addresses, instead of invalid user account/password error.

======== see patch below for the retry attempt =========

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
    # to log in.  Some servers (such as iPlanet) allow you to have unique
    # uids spread out over a subtree of an area (such as "People"), so
    # just appending the Base DN to the uid isn't sufficient to get the
    # user's DN.  For servers which don't work this way, there will still
    # be no harm done.
    my @old_references = ();
    my @references     = ( [ Bugzilla->params()->{"LDAPserver"}, Bugzilla->params()->{"LDAPBaseDN"} ] );
    my $username       = $params->{username};
    my $dn_result;
    my $server;
    my $dn;
    do {
        ($server, $dn) = @{shift @references};

        $self->_bind_ldap_anonymously($server);

        # Now, we verify that the user exists, and get a LDAP Distinguished
        # Name for the user.
        $dn_result = $self->ldap($server)->search( _bz_search_params($username),
                base   => $dn,
                attrs  => ['dn', Bugzilla->params()->{"LDAPuidatribute"}]);

        return { failure => AUTH_ERROR, error => "ldap_search_error",
                 details => {errstr => $dn_result->error, username => $username}
        } if (@references == 0) and $dn_result->code;

        if (!$dn_result->count && int($dn_result->{reference}) > 0) {
            @references = _bz_reference($dn_result);
        }
    } until (($dn_result->count) or (@references == 0));

    return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count;

    $dn = $dn_result->shift_entry->dn;

    # Check the password.   
    my $pw_result = $self->ldap($server)->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($server)->search(_bz_search_params($username),
                                        base => $dn);
    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 = Bugzilla->params->{"LDAPmailattribute"};
    if ($mail_attr) {
        if (!$user_entry->exists($mail_attr)) {
            return { failure => AUTH_ERROR,
                     error   => "ldap_cannot_retreive_attr",
                     details => {attr => $mail_attr} };
        }

        $params->{bz_username} = $user_entry->get_value($mail_attr);
    } else {
        $params->{bz_username} = $username;
    }

    $params->{realname}  ||= $user_entry->get_value("displayName");
    $params->{realname}  ||= $user_entry->get_value("cn");

    return $params;
}
sub _bz_search_params {
    my ($username) = @_;
    return (scope  => "sub",
            filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
                      . "=$username)"
                      . Bugzilla->params->{"LDAPfilter"} . ')');
}

sub _bind_ldap_anonymously {
    my ($self, $server) = @_;
    my $bind_result;
    if (Bugzilla->params->{"LDAPbinddn"}) {
        my ($LDAPbinddn,$LDAPbindpass) =
            split(":",Bugzilla->params->{"LDAPbinddn"});
        $bind_result =
            $self->ldap($server)->bind($LDAPbinddn, password => $LDAPbindpass);
    }
    else {
        $bind_result = $self->ldap($server)->bind();
    }
    ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
        if $bind_result->code;
}

# 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, $server) = @_;

    ThrowCodeError("ldap_server_not_defined") unless $server;

    $self->{ldap} = Net::LDAP->new($server)
        || ThrowCodeError("ldap_connect_failed", { server => $server });

    # try to start TLS if needed
    if (Bugzilla->params->{"LDAPstarttls"}) {
        my $mesg = $self->{ldap}->start_tls();
        ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() })
            if $mesg->code();
    }

    return $self->{ldap};
}


I put the patch that I created in the expected result area for investigation purpose, it works for me in our LDAP settings.
Comment 1 Max Kanat-Alexander 2007-10-25 00:37:51 PDT
If you have a patch, please attach it, as a "cvs diff -u".

Note You need to log in before you can comment on or make changes to this bug.