Yet another RT LDAP authentication hack

Hi all,

In setting up our RT to authenticate against LDAP, I learned that having a
lot of examples to choose from and imitate is helpful. So, for the benefit
of anyone trying to do this now, here are my LDAP authentication hacks.

Our LDAP is provided by Novell Directory Services, version 5.something I
believe. Not all of our sites are running the latest and greatest NDS yet,
though, so the old faithful code to authenticate against RT’s user database
is still in there.

This is actually two hacks:

  1. use NDS to authenticate people logging into WebRT
  2. use NDS data to populate RT user database when receiving e-mail from an
    unknown party

These hacks were made against RT 2.0.11pre (I really do mean to upgrade
soon!) so if you are on a later version of RT, you may need to get out your
merging hat. In any case, you will need to install the Net::LDAP family of
Perl modules.

On with the hacks…

Hack 1: NDS Authentication on web login
File: /path/to/rt2/lib/RT/User.pm
Function: IsPassword()
Comments: pseudo-diff made by hand and edited slightly

-------8<-----snip here----------8<------------------
sub IsPassword {
my $self = shift;
my $value = shift;

  • !!! DO NOT ACTUALLY USE THIS AFTER DEBUGGING IS

DONE!!!

  • #$RT::Logger->error ($self->Name . " is trying to login with password
    $value\n");
#TODO there isn't any apparent way to legitimately ACL this

# RT does not allow null passwords
if ((!defined ($value)) or ($value eq '')) {
    return(undef);
}
if ($self->Disabled) {
    $RT::Logger->info("Disabled user ".$self->Name." tried to log in");
    return(undef);
}

# Try to authenticate against RT's built-in user database
if ($self->__Value('Password') eq crypt($value,

$self->__Value(‘Password’))) {
#$RT::Logger->error (“RT password checks out, you may proceed\n”);
return (1);
}

  • Try to authenticate to NDS LDAP as the given user with the given

password.

  • We will do this a couple of ways for different sites.

  • 1) Olathe

  • #$RT::Logger->error (“Trying to LDAP our way in\n”);
  • use Net::LDAP;
  • use Net::LDAP::Constant qw(LDAP_SUCCESS);
  • my $ldap = Net::LDAP->new(‘my.ldap.server’, port => ‘389’);
  • #$RT::Logger->error (“connected to LDAP\n”);
  • step 1: search for the user’s dn given their cn (username)

  • my $filter = “cn=” . $self->Name;
  • #$RT::Logger->error (“filter = $filter\n”);
  • $mesg = $ldap->search ( base => ‘o=olathe’,
  •                        filter  => $filter);
    
  • #$RT::Logger->error ("did the search, got " . $mesg->count . “
    results\n”);
  • if ($mesg->code == LDAP_SUCCESS)
  • {
  •    if ($mesg->count == 1)
    
  •    {
    
  •            #$RT::Logger->error ("the search didn't bomb out\n");
    
  •            # as_struct returns a reference to a hash of results
    
  •            # keys are dn's of the results; values are themselves
    

hashes

  •            # where keys are attribute names and values are attribute
    

values.

  •            #
    
  •            # All I really want is the DN so I can bind against it.
    
  •            #
    
  •            # I have to do it this way because the ou in the dn may not
    

be

  •            # the same as the actual ou attribute.
    
  •            my $bind_dn = "";
    
  •            my $results = $mesg->as_struct();
    
  •            my @dn_list = keys %$results;
    
  •            my $bind_dn = $dn_list[0];
    
  •            #$RT::Logger->error ("bind_dn = $bind_dn\n");
    
  •            #$RT::Logger->error ("password = $value\n"); # !!!!! DO NOT
    

REALLY USE THIS !!!

  •            # step 2: try to bind with the dn we found and the password
    

the user gave us

  •            my $mesg = $ldap->bind($bind_dn, password => $value);
    
  •            if ($mesg->code == LDAP_SUCCESS)
    
  •            {
    
  •                    #$RT::Logger->error ("binding worked!  you may
    

proceed\n");

  •                    $ldap->unbind;
    
  •                    return (1);
    
  •            }
    
  •            #$RT::Logger->error ("couldn't bind\n");
    
  •    } # end if search turned up ==1 result
    
  • } # end if search succeeded
  • 2) Romsey

  • #$RT::Logger->error (“Trying to LDAP our way in\n”);
  • use Net::LDAP;
  • use Net::LDAP::Constant qw(LDAP_SUCCESS);
  • my $ldap = Net::LDAP->new(‘my.ldap.server’, port => ‘389’);
  • #$RT::Logger->error (“connected to LDAP\n”);
  • step 1: search for the user’s dn given their cn (username)

  • my $filter = “cn=” . $self->Name;
  • #$RT::Logger->error (“filter = $filter\n”);
  • $mesg = $ldap->search ( base => ‘o=romsey’,
  •                        filter  => $filter);
    
  • #$RT::Logger->error ("did the search, got " . $mesg->count . “
    results\n”);
  • if ($mesg->code == LDAP_SUCCESS)
  • {
  •    if ($mesg->count == 1)
    
  •    {
    
  •            #$RT::Logger->error ("the search didn't bomb out\n");
    
  •            # as_struct returns a reference to a hash of results
    
  •            # keys are dn's of the results; values are themselves
    

hashes

  •            # where keys are attribute names and values are attribute
    

values.

  •            # All I really want is the DN so I can bind against it.
    
  •            # I have to do it this way because the ou in the dn may not
    

be

  •            # the same as the actual ou attribute.
    
  •            my $bind_dn = "";
    
  •            my $results = $mesg->as_struct();
    
  •            my @dn_list = keys %$results;
    
  •            my $bind_dn = $dn_list[0];
    
  •            #$RT::Logger->error ("bind_dn = $bind_dn\n");
    
  •            #$RT::Logger->error ("password = $value\n"); # !!!!! DO NOT
    

REALLY USE THIS !!!

  •            # step 2: try to bind with the dn we found and the password
    

the user gave us

  •            my $mesg = $ldap->bind($bind_dn, password => $value);
    
  •            if ($mesg->code == LDAP_SUCCESS)
    
  •            {
    
  •                    #$RT::Logger->error ("binding worked!  you may
    

proceed\n");

  •                    $ldap->unbind;
    
  •                    return (1);
    
  •            }
    
  •            #$RT::Logger->error ("couldn't bind\n");
    
  •    } # end if search turned up ==1 result
    
  • } # end if search succeeded

  • If we get this far, it means we couldn’t authenticate you.

  • #$RT::Logger->error (“RT password is wrong. No RT for you!\n”);

  • else {
    return (undef);
  • }
    }
    -------8<-----snip here----------8<------------------

Hack 2: NDS data used in user creation when
receiving e-mail from unknown party
File: /path/to/rt2/etc/config.pm
Function: LookupExternalUserInfo()
Comments: Chances are good nobody else will need
the exact authorization gyrations we
need here, but this should be a good
example of how to accommodate your
own needs.
Slightly above this section, I have set
$LookupSenderInExternalDatabase = 1;
but I kept
$SenderMustExistInExternalDatabase = undef;
because, as mentioned before, some of our
users can’t be found in LDAP due to the fact
that they’re not running NDS 5.x yet.

-------8<-----snip here----------8<------------------

{{{ Kendric is adding a few extra snippets of code for convenience

$ldap_ok = 1;

sub CrapOut
{
my $message = shift;
$RT::Logger->critical ("LookupExternalUserInfo: " . $message);
$ldap_ok = 0;
} # end sub CrapOut

sub LogIt
{
my $message = shift;
$RT::Logger->debug ("LookupExternalUserInfo: " . $message);
} # end sub LogIt

}}}

sub LookupExternalUserInfo
{
my %UserInfo = {};
$UserInfo{‘EmailAddress’} = shift;
$UserInfo{‘RealName’} = shift;
$UserInfo{‘RealName’} =~ s/"//g;
$UserInfo{‘RealName’} =~ s/(//g;
$UserInfo{‘RealName’} =~ s/)//g;
my ($FoundUser);

    # {{{ load up ldap modules

    use Net::LDAP;
    use Net::LDAP::Constant qw(LDAP_SUCCESS);

    # }}}

    # {{{ defined constants we're going to need

    use constant LDAP      => q(my.ldap.server);
    use constant LDAP_PORT => q(389);
    use constant LDAP_BASE => q(o=olathe);

    # If you're using a server that doesn't require you to
    # bind with a password, set LDAP_BIND and LDAP_BINDPASS to q();

    #use constant LDAP_BIND => q(uid=root, cn=Recipients,

ou=ENGINEERING, etc.);
#use constant LDAP_BINDPASS => q(your_password_here);

    use constant LDAP_BIND => q();
    use constant LDAP_BINDPASS => q();

    # }}}

    # {{{ connect to the ldap server
    my $ldap = Net::LDAP->new(LDAP, port => LDAP_PORT ) || CrapOut

(“Cannot connect to LDAP”, %UserInfo);

    # CrapOut() would set $ldap_ok to 0...
    if ($ldap_ok == 0)
    {
            return (0, %UserInfo);
    }

    # If we're running against a server that makes you bind, do it

now…
if (LDAP_BIND)
{
my $mesg = $ldap->bind(LDAP_BIND, password => LDAP_BINDPASS
);
if ($mesg->code != LDAP_SUCCESS)
{
CrapOut ("Cannot bind to LDAP: " . $mesg->code);
return (0, %UserInfo);
}
} # end if we gotta bind

    # }}}

    # {{{ search for this user by email address

    my $filter = "mail=".$UserInfo{'EmailAddress'};
    LogIt ("First Olathe search filter '$filter'\n");
    $mesg = $ldap->search(  base   => LDAP_BASE,
                            filter => $filter,
                            attrs  => ['mail', 'cn', 'ou', 'uid',

‘fullName’, ‘telephoneNumber’]);
if ($mesg->code != LDAP_SUCCESS)
{
CrapOut ("Could not search for $filter: " . $mesg->code);
return (0, %UserInfo);
}

    LogIt ("First Olathe search produced " . $mesg->count . "

results\n");

    # }}}

    # {{{ if the E-mail search failed, try searching by person's name
    unless ($mesg->count == 1)
    {
            $filter = "fullName=".$UserInfo{'RealName'};
            LogIt ("Second Olathe search filter '$filter'\n");
            $mesg = $ldap->search(  base   => LDAP_BASE,
                                    filter => $filter,
                                    attrs  => ['mail', 'cn', 'ou',

‘uid’, ‘fullName’, ‘telephoneNumber’]);

            if ($mesg->code != LDAP_SUCCESS)
            {
                    CrapOut ("Could not search for $filter: ".

$mesg->code);
return (0, %UserInfo);
}
} # end unless

    LogIt ("Second Olathe search produced " . $mesg->count . " results

with filter $filter\n");

    # }}}

    # {{{ if still no hits, maybe this is a Romsey user
    unless ($mesg->count == 1)
    {
            $filter = "mail=".$UserInfo{'EmailAddress'};
            LogIt ("First Romsey search filter '$filter'\n");
            $mesg = $ldap->search(  base   => 'o=romsey',
                                    filter => $filter,
                                    attrs  => ['mail', 'cn', 'ou',

‘uid’, ‘fullName’, ‘telephoneNumber’]);
if ($mesg->code != LDAP_SUCCESS)
{
CrapOut ("Could not search for $filter: " .
$mesg->code);
return (0, %UserInfo);
}

            LogIt ("First Romsey search produced " . $mesg->count . "

results\n");
} # end unless

    # }}}

    # {{{ if the E-mail search failed, try searching by person's name
    unless ($mesg->count == 1)
    {
            $filter = "fullName=".$UserInfo{'RealName'};
            LogIt ("Second Romsey search filter '$filter'\n");
            $mesg = $ldap->search(  base   => 'o=romsey',
                                    filter => $filter,
                                    attrs  => ['mail', 'cn', 'ou',

‘uid’, ‘fullName’, ‘telephoneNumber’]);

            if ($mesg->code != LDAP_SUCCESS)
            {
                    CrapOut ("Could not search for $filter: ".

$mesg->code);
return (0, %UserInfo);
}
} # end unless

    LogIt ("Second Romsey search produced " . $mesg->count . " results

with filter $filter\n");

    # }}}


    # One of the four searches will hopefully succeed with just one

match
if ($mesg->count == 1)
{
$UserInfo{‘EmailAddress’} =
($mesg->first_entry->get_value(‘mail’))[0];
$UserInfo{‘RealName’} =
($mesg->first_entry->get_value(‘fullName’))[0];
$UserInfo{‘Name’} =
($mesg->first_entry->get_value(‘uid’))[0];
$UserInfo{‘Organization’} =
($mesg->first_entry->get_value(‘OU’))[0];
$UserInfo{‘WorkPhone’} =
($mesg->first_entry->get_value(‘telephoneNumber’))[0];
$FoundUser = 1;
}

    # {{{ close down the ldap connection
    $mesg = $ldap->unbind();
    if ($mesg->code != LDAP_SUCCESS)
    {
            CrapOut ("Could not unbind from LDAP: " . $mesg->code);
    }
    # }}}

    return ($FoundUser, %UserInfo);

} # end sub LookupExternalUserInfo
-------8<-----snip here----------8<------------------

Kendric Beachey