From Request Tracker Wiki
Jump to navigation Jump to search

This page 'AutoCreateAndCanonicalizeUserInfo' is tagged as OUTDATED
This page contains out of date and possibly misleading information or instructions such as installation methods or configuration examples that no longer apply. Please consider this warning when reading the page below.
If you have checked or updated this page and found the content to be suitable, please remove this notice by editing the page and remove the Outdated template tag.

Auto Create User and Lookup Alternate Addresses via LDAP

To make the AutoCreateFromExternalUserInfo overlay recognise that a person may have multiple users I added amended scripts. This allows RT to work the LDAP directory in MS Exchange 5.5 (I don't know about later versions). e.g.

cn = joeb
email =
otherMailbox = smtp$
otherMailbox = smtp$

Will all refer back to RT user joeb with RT email =

It also allows the helpdesk staff to enter requestors as joeb,, or and still refer to user joeb.

Place and in $RT_HOME/lib/RT folders

  # overlay
  # author: Phillip Cole (phillip d cole @ uk d coltgroup d com)
  # based on and much help from the mailing lists
  no warnings qw(redefine);
  # {{{ sub CanonicalizeEmailAddress
  =head2 CanonicalizeEmailAddress ADDRESS
  # Lookup this email address in Exchange LDAP to make sure it is the
  # users primary email address
  # i.e. replace with
  sub CanonicalizeEmailAddress {
      my $self = shift;
      my $email = shift;
      $RT::Logger->debug("CanonicalizeEmailAddress: Inside version email=$email\n");
      if ($email) {
          my ($found, %params) = RT::User::LookupExternalUserInfo("otherMailbox", "smtp\$$email");
          if ($found) {
              if ($params{'EmailAddress'} and ($email ne $params{'EmailAddress'})) {
                  $RT::Logger->info("CanonicalizeEmailAddress: Changing email to " . $params{'EmailAddress'} . "\n");
  # {{{ sub CanonicalizeUserInfo
  =head2 CanonicalizeUserInfo
  Colt Custom function to lookup a new users details from within an LDAP directory
  sub CanonicalizeUserInfo {
      my $self = shift;
      my $args = shift;
      my $success = 1;
      $success = 0;
      $RT::Logger->debug("CanonicalizeUserInfo: Inside version\n" .
                         '   $args->{\'RealName\'}    =' . $args->{'RealName'} . "\n" .
                         '   $args->{\'Name\'}        =' . $args->{'Name'} . "\n" .
                         '   $args->{\'EmailAddress\'}=' . $args->{'EmailAddress'} . "\n");
      # $args is a hash reference.  To get at it's values need to call $args->{'key'}
      # $args has keys:
      #    RealName     - User human name (e.g. Cole, Phillip)
      #    Name         - Username (e.g. ukpgc)
      #    EmailAddress - Email address (e.g.
      #    Comments     - Comments created during creation
      # Try to match by User ID first
      my ($found, %params) = RT::User::LookupExternalUserInfo("uid", $args->{'Name'});
      if (!$found) {
          # If not found try to match email address
          my $email = $self->CanonicalizeEmailAddress($args->{'EmailAddress'});
          ($found, %params) = RT::User::LookupExternalUserInfo("mail", $email);
      if ($found) {
          $RT::Logger->info("CanonicalizeUserInfo: Setting args as returned via LDAP,\n");
          $args->{'Name'} = $params{'Name'};
          $args->{'RealName'} = $params{'RealName'};
          $args->{'EmailAddress'} = $params{'EmailAddress'};
          $args->{'Comments'} = 'Autocreated via LDAP when added as a watcher';
          $success = 1;
  # }}}
  # {{{ sub LookupExternalUserInfo
  =head2 LookupExternalUserInfo
  LookupExternalUserInfo takes a key/value pair, looks it up in LDAP,
  and returns a (sparse) params hash suitable for creating a User object
  sub LookupExternalUserInfo {
     my $key = shift;
     my $value = shift;
     # I have dreams of this function using an $LdapAttrMap hash fpr
     # Ldap -> RT account creation, in the form of:
     #   %RT::LdapAttrMap = ( Name=> 'uid',
     #                        RealName=> 'cn',
     #                        EmailAdress=> 'mail',
     #                        [anyUserAttr]=> [anyLdapAttr]);
     # But until then, you better have the following variables defined
     # in your
     #   Set($LdapServer, "ldap.domain.tld");
     #   Set($LdapBase, "cn=Users,dc=domain,dc=tld");
     #   Set($LdapUidAttr, "uid");
     #   Set($LdapNameAttr, "cn");
     #   Set($LdapMailAttr, "mail");
     #   Set($LdapFilter, "(objectclass=person)");
     #   Set($AutoCreateFromExternalUserInfo, 1);
     # enjoy!
     my $FoundInExternalDatabase = 0;
     my %params = ( Name=> undef,
                    EmailAddress=> undef,
                    RealName=> undef);
     $RT::Logger->debug("LookupExternalUserInfo: Entered with:\n",
                        "\tkey   = $key\n",
                        "\tvalue = $value\n");
     use Net::LDAP;
     my $ldap = new Net::LDAP($RT::LdapServer)
       or $RT::Logger->critical("LookupExternalUserInfo: Cannot connect to ",
         return ($FoundInExternalDatabase, %params);
     my $mesg = $ldap->bind();
     if ($mesg->code != LDAP_SUCCESS) {
       $RT::Logger->critical("LookupExternalUserInfo: Cannot bind anonymously ",
                             "to LDAP:", $mesg->code, "\n");
       #$params{'RealName'} = "\"$params{'RealName'}\"";
       return ($FoundInExternalDatabase, %params);
     my $filter = "@{[ $key ]}=$value";
     $RT::Logger->debug("LookupExternalUserInfo: First search filter ",
     $mesg = $ldap->search(base   => $RT::LdapBase,
                           filter => $filter,
                           attrs  => [ $RT::LdapUidAttr, $RT::LdapMailAttr, $RT::LdapNameAttr ]);
     if ($mesg->code != LDAP_SUCCESS and $mesg->code != LDAP_PARTIAL_RESULTS)  {
       $RT::Logger->critical("LookupExternalUserInfo: Could not search for ",
                             "$filter: ", $mesg->code, "\n");
       $params{'RealName'} = "\"$params{'RealName'}\"";
       return ($FoundInExternalDatabase, %params);
     $RT::Logger->debug("LookupExternalUserInfo:  Search produced ",
                        $mesg->count, " results\n");
     # The search succeeded with just one match
     if ($mesg->count == 1) {
       $params{'Name'} = ($mesg->first_entry->get_value($RT::LdapUidAttr))[0];
       $params{'EmailAddress'} = ($mesg->first_entry->get_value($RT::LdapMailAttr))[0];
       $params{'RealName'} = ($mesg->first_entry->get_value($RT::LdapNameAttr))[0];
       #$params{'RealName'} = "\"$params{'RealName'}\"";
       $FoundInExternalDatabase = 1;
     $mesg = $ldap->unbind();
     if ($mesg->code != LDAP_SUCCESS) {
       $RT::Logger->critical("LookupExternalUserInfo: Could not unbind from ",
                             "LDAP: ", $mesg->code, "\n");
     undef $ldap;
     undef $mesg;
     $RT::Logger->debug("LookupExternalUserInfo: Leaving LDAP examination ",
                        "\tName         = $params{'Name'}\n",
                        "\tEmailAddress = $params{'EmailAddress'}\n",
                        "\tRealName     = $params{'RealName'}\n",
                        "\tFound        = $FoundInExternalDatabase\n");
     return ($FoundInExternalDatabase, %params);
  # }}}

  # AutoCreateFromExternalUserInfo overlay
  # author: Robin Battey <zanfur at zanfur dot com>
  # modified by Phillip Cole to remove $self-> from LookupExternalUserInfo
  # and move LookupExternalUserInfo into
  # based in part by the LookupExternalUserInfo function
  # written by Jeff Hoover <jeff.hoover at infotechfl dot com>
  # This Overlay is useful if you want the following behavior:
  #  * Whenever RT tries -- and fails -- to load a user from the
  #    RT user database, it queries an Ldap server for the account
  #    information and creates it on the fly (for both email lookups
  #    and web logins)
  #  * RT behaves as though the account has always been there -- i.e.
  #    there is no reason to give "Everyone" the "Create Ticket" right,
  #    because the account has been there all along ...
  #  * NO synchronization with the Ldap server once the account has
  #    been created, so any changes in RT stay in RT and any changes
  #    in Ldap stay in Ldap (unless you run some other script, of course)
  # The situation I had at my place of employment, which caused me
  # to write this, is we have an Active Directory server with a fairly
  # large number of users on it, with a fair bit of adding/deleting of
  # users going on.  I wanted a way for users to send a request to the
  # help queue, and then immediately be able to see the ticket, the
  # ticket's status, etc using their Active Directory (i.e. windows
  # domain) username and password.  I wrote this overlay for the account
  # generation, and used the LdapOverlay available in the "Contributions"
  # section of the RT3 Wiki ( for the
  # authentication.  This overlay was therefore designed to work well
  # with the LdapOverlay, and actually uses (some of) the same Ldap
  # variables.
  # I spent a bit of effort making sure this was modular, particularly
  # in the case of a different external user info source.  It currently
  # works with LDAP (and, of course, Active Directory), but there's no
  # good reason why you can't just replace the LookupExternalUserInfo
  # function to get your data from somewhere else.
  no warnings qw(redefine);
  # {{{ sub CreateFromExternalUserInfo
  =head2 CreateFromExternalUserInfo
  Calls LookupExternalUserInfo and creates a user from the results
  sub CreateFromExternalUserInfo {
      my $self = shift;
      my @lookupargs = @_;
      $RT::Logger->debug( "CreateFromExternalUserInfo: entered with args: ", @lookupargs, "\n");
      my ($found, %params) = RT::User::LookupExternalUserInfo (@lookupargs);
      $RT::Logger->debug( "CreateFromExternalUserInfo: params hash: ", %params, "\n");
      unless ($found) {
          $RT::Logger->info ("CreateFromExternalUserInfo: failed to find user with args: ", @lookupargs, "\n");
          return undef;
      my $UserObj = RT::User->new($RT::SystemUser);
      my ($val, $msg) = $UserObj->Create(
          %{ref($RT::AutoCreate) ? $RT::AutoCreate : {}},
      unless ($val) {
          $RT::Logger->info ("CreateFromExternalUserInfo: failed to create user with args: ", @lookupargs, "\n");
          return undef;
      $RT::Logger->info ("CreateFromExternalUserInfo: created user $val (\"$msg\")\n");
  # }}}
  # {{{ sub LoadByEmail
  =head2 LoadByEmail
  Loads a User into this CurrentUser object.
  Takes the email address of the user to load.
  sub LoadByEmail  {
      my $self = shift;
      my $identifier = shift;
      $identifier = RT::User::CanonicalizeEmailAddress(undef, $identifier);
      if (!$self->Id and $RT::AutoCreateFromExternalUserInfo) {
          $self->CreateFromExternalUserInfo ("mail", $identifier);
  # }}}
  # {{{ sub LoadByName
  =head2 LoadByName
  Loads a User into this CurrentUser object.
  Takes a Name.
  sub LoadByName {
      my $self = shift;
      my $identifier = shift;
      if (!$self->Id and $RT::AutoCreateFromExternalUserInfo) {
          $self->CreateFromExternalUserInfo ("uid", $identifier);
  # }}}
  # {{{ sub Load
  =head2 Load
  Loads a User into this CurrentUser object.
  Takes either an integer (users id column reference) or a Name
  The latter is deprecated. Instead, you should use LoadByName.
  Formerly, this routine also took email addresses.
  sub Load  {
    my $self = shift;
    my $identifier = shift;
    #if it's an int, load by id. otherwise, load by name.
    if ($identifier !~ /\D/) {
    elsif (UNIVERSAL::isa($identifier,"RT::User")) {
           # DWIM if they pass a user in
    else {
        # This is a bit dangerous, we might get false authen if somebody
        # uses ambigous userids or real names:
  # }}}

--Phillip Cole 30/09/05