[rt-users] Yet another RT LDAP authentication hack

Beachey, Kendric Kendric.Beachey at garmin.com
Thu May 30 09:59:07 EDT 2002


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
 




More information about the rt-users mailing list