LDAP.pm 5.95 KB
Newer Older
1 2 3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
#
5 6
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
7

8
package Bugzilla::Auth::Verify::LDAP;
9 10

use 5.10.1;
11
use strict;
12
use warnings;
13

14 15 16 17
use base qw(Bugzilla::Auth::Verify);
use fields qw(
    ldap
);
18 19

use Bugzilla::Constants;
20
use Bugzilla::Error;
21
use Bugzilla::User;
22
use Bugzilla::Util;
23 24

use Net::LDAP;
25
use Net::LDAP::Util qw(escape_filter_value);
26

27 28
use constant admin_can_create_account => 0;
use constant user_can_create_account  => 0;
29

30 31 32
sub check_credentials {
    my ($self, $params) = @_;
    my $dbh = Bugzilla->dbh;
33 34 35 36 37 38 39 40

    # 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.
41
    $self->_bind_ldap_for_search();
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

    # 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.
61 62 63 64

    # First try the search as the (already bound) user in question.
    my $user_entry;
    my $error_string;
65
    my $detail_result = $self->ldap->search(_bz_search_params($username));
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    if ($detail_result->code) {
        # Stash away the original error, just in case
        $error_string = $detail_result->error;
    } else {
        $user_entry = $detail_result->shift_entry;
    }

    # If that failed (either because the search failed, or returned no
    # results) then try re-binding as the initial search user, but only
    # if the LDAPbinddn parameter is set.
    if (!$user_entry && Bugzilla->params->{"LDAPbinddn"}) {
        $self->_bind_ldap_for_search();

        $detail_result = $self->ldap->search(_bz_search_params($username));
        if (!$detail_result->code) {
            $user_entry = $detail_result->shift_entry;
        }
    }

    # If we *still* don't have anything in $user_entry then give up.
86
    return { failure => AUTH_ERROR, error => "ldap_search_error",
87 88
             details => {errstr => $error_string, username => $username}
    } if !$user_entry;
89 90


91
    my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
92 93 94 95 96 97 98
    if ($mail_attr) {
        if (!$user_entry->exists($mail_attr)) {
            return { failure => AUTH_ERROR,
                     error   => "ldap_cannot_retreive_attr",
                     details => {attr => $mail_attr} };
        }

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
        my @emails = $user_entry->get_value($mail_attr);

        # Default to the first email address returned.
        $params->{bz_username} = $emails[0];

        if (@emails > 1) {
            # Cycle through the adresses and check if they're Bugzilla logins.
            # Use the first one that returns a valid id. 
            foreach my $email (@emails) {
                if ( login_to_id($email) ) {
                    $params->{bz_username} = $email;
                    last;
                }
            }
        }

115 116
    } else {
        $params->{bz_username} = $username;
117 118
    }

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

122 123
    $params->{extern_id} = $username;

124 125
    return $params;
}
126

127 128
sub _bz_search_params {
    my ($username) = @_;
129
    $username = escape_filter_value($username);
130
    return (base   => Bugzilla->params->{"LDAPBaseDN"},
131
            scope  => "sub",
132 133 134
            filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"} 
                      . "=$username)"
                      . Bugzilla->params->{"LDAPfilter"} . ')');
135
}
136

137
sub _bind_ldap_for_search {
138 139
    my ($self) = @_;
    my $bind_result;
140 141 142
    if (Bugzilla->params->{"LDAPbinddn"}) {
        my ($LDAPbinddn,$LDAPbindpass) = 
            split(":",Bugzilla->params->{"LDAPbinddn"});
143 144
        $bind_result = 
            $self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
145 146
    }
    else {
147
        $bind_result = $self->ldap->bind();
148
    }
149 150 151
    ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
        if $bind_result->code;
}
152

153 154 155
# 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
156
# to fix their mistake. (Because Bugzilla->login always calls
157 158 159 160
# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
sub ldap {
    my ($self) = @_;
    return $self->{ldap} if $self->{ldap};
161

162
    my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
163
    ThrowCodeError("ldap_server_not_defined") unless @servers;
164

165 166 167 168 169 170
    foreach (@servers) {
        $self->{ldap} = new Net::LDAP(trim($_));
        last if $self->{ldap};
    }
    ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) }) 
        unless $self->{ldap};
171 172

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

179
    return $self->{ldap};
180 181 182
}

1;