[Bps-public-commit] rt-authen-externalauth branch, multiple-emails, created. 0.10_01-64-gb3d3ca4

Jim Brandt jbrandt at bestpractical.com
Tue Jun 26 15:51:31 EDT 2012


The branch, multiple-emails has been created
        at  b3d3ca4285d32a8e29771d81980bb2ac5806472e (commit)

- Log -----------------------------------------------------------------
commit 512d7f2f0dc3197bb03d449a3716f1b32555c6dc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 02:05:39 2011 +0400

    PerformSearch function, cut duplicated code

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index b228e77..9e27e23 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -9,7 +9,6 @@ use strict;
 require Net::SSLeay if $RT::ExternalServiceUsesSSLorTLS;
 
 sub GetAuth {
-    
     my ($service, $username, $password) = @_;
     
     my $config = $RT::ExternalSettings->{$service};
@@ -36,36 +35,21 @@ sub GetAuth {
     my $ldap = _GetBoundLdapObj($config);
     return 0 unless ($ldap);
 
-    $filter = Net::LDAP::Filter->new(   '(&(' . 
+    $filter = '(&(' . 
                                         $attr_map->{'Name'} . 
                                         '=' . 
                                         escape_filter_value($username) . 
                                         ')' . 
                                         $filter . 
                                         ')'
-                                    );
-
-    $RT::Logger->debug( "LDAP Search === ",
-                        "Base:",
-                        $base,
-                        "== Filter:", 
-                        $filter->as_string,
-                        "== Attrs:", 
-                        join(',', at attrs));
-
-    my $ldap_msg = $ldap->search(   base   => $base,
-                                    filter => $filter,
-                                    attrs  => \@attrs);
-
-    unless ($ldap_msg->code == LDAP_SUCCESS || $ldap_msg->code == LDAP_PARTIAL_RESULTS) {
-        $RT::Logger->debug( "search for", 
-                            $filter->as_string, 
-                            "failed:", 
-                            ldap_error_name($ldap_msg->code), 
-                            $ldap_msg->code);
-        # Didn't even get a partial result - jump straight to the next external auth service
-        return 0;
-    }
+    ;
+
+    my $ldap_msg = PerformSearch(
+        $ldap,
+        base   => $base,
+        filter => $filter,
+        attrs  => \@attrs
+    ) or return 0;;
 
     unless ($ldap_msg->count == 1) {
         $RT::Logger->info(  $service,
@@ -113,33 +97,13 @@ sub GetAuth {
 
         # We only need the dn for the actual group since all we care about is existence
         @attrs  = qw(dn);
-        $filter = Net::LDAP::Filter->new("(${group_attr}=" . escape_filter_value($group_val) . ")");
-        
-        $RT::Logger->debug( "LDAP Search === ",
-                            "Base:",
-                            $group,
-                            "== Filter:", 
-                            $filter->as_string,
-                            "== Attrs:", 
-                            join(',', at attrs));
-        
-        $ldap_msg = $ldap->search(  base   => $group,
-                                    filter => $filter,
-                                    attrs  => \@attrs,
-                                    scope  => 'base');
-
-        # And the user isn't a member:
-        unless ($ldap_msg->code == LDAP_SUCCESS || 
-                $ldap_msg->code == LDAP_PARTIAL_RESULTS) {
-            $RT::Logger->critical(  "Search for", 
-                                    $filter->as_string, 
-                                    "failed:",
-                                    ldap_error_name($ldap_msg->code), 
-                                    $ldap_msg->code);
-
-            # Fail auth - jump to next external auth service
-            return 0;
-        }
+        $ldap_msg = PerformSearch(
+            $ldap,
+            base   => $group,
+            filter => "(${group_attr}=" . escape_filter_value($group_val) . ")",
+            attrs  => \@attrs,
+            scope  => 'base'
+        ) or return 0;
 
         unless ($ldap_msg->count == 1) {
             $RT::Logger->debug(
@@ -213,31 +177,15 @@ sub CanonicalizeUserInfo {
     # errors should be logged by _GetBoundLdapObj so we don't have to.
     return ($found, %params) unless ($ldap);
 
-    # Do a search for them in LDAP
-    $RT::Logger->debug( "LDAP Search === ",
-                        "Base:",
-                        $base,
-                        "== Filter:", 
-                        $filter->as_string,
-                        "== Attrs:", 
-                        join(',', at attrs));
-
-    my $ldap_msg = $ldap->search(base   => $base,
-                                 filter => $filter,
-                                 attrs  => \@attrs);
-
-    # If we didn't get at LEAST a partial result, just die now.
-    if ($ldap_msg->code != LDAP_SUCCESS and 
-        $ldap_msg->code != LDAP_PARTIAL_RESULTS) {
-        $RT::Logger->critical(  (caller(0))[3],
-                                ": Search for ",
-                                $filter->as_string,
-                                " failed: ",
-                                ldap_error_name($ldap_msg->code), 
-                                $ldap_msg->code);
-        # $found remains as 0
-        
-        # Drop out to the next external information service
+    my $ldap_msg = PerformSearch(
+        $ldap,
+        base   => $base,
+        filter => $filter,
+        attrs  => \@attrs
+    );
+    
+    unless ( $ldap_msg ) {
+        # If we didn't get at LEAST a partial result, just die now.
         $ldap_msg = $ldap->unbind();
         if ($ldap_msg->code != LDAP_SUCCESS) {
             $RT::Logger->critical(  (caller(0))[3],
@@ -248,7 +196,6 @@ sub CanonicalizeUserInfo {
         undef $ldap;
         undef $ldap_msg;
         return ($found, %params);
-      
     } else {
         # If there's only one match, we're good; more than one and
         # we don't know which is the right one so we skip it.
@@ -299,7 +246,6 @@ sub UserExists {
    $RT::Logger->debug("UserExists params:\nusername: $username , service: $service"); 
     my $config              = $RT::ExternalSettings->{$service};
     
-    my $base                = $config->{'base'};
     my $filter              = $config->{'filter'};
 
     # While LDAP filters must be surrounded by parentheses, an empty set
@@ -323,20 +269,13 @@ sub UserExists {
     my $ldap = _GetBoundLdapObj($config);
     return unless $ldap;
 
-    my @attrs = values(%{$config->{'attr_map'}});
-
     # Check that the user exists in the LDAP service
-    $RT::Logger->debug( "LDAP Search === ",
-                        "Base:",
-                        $base,
-                        "== Filter:", 
-                        $filter->as_string,
-                        "== Attrs:", 
-                        join(',', at attrs));
-    
-    my $user_found = $ldap->search( base    => $base,
-                                    filter  => $filter,
-                                    attrs   => \@attrs);
+    my $user_found = PerformSearch(
+        $ldap,
+        base    => $config->{'base'},
+        filter  => $filter,
+        attrs   => ['uid'],
+    ) or return 0;
 
     if($user_found->count < 1) {
         # If 0 or negative integer, no user found or major failure
@@ -416,21 +355,13 @@ sub UserDisabled {
     my $ldap = _GetBoundLdapObj($config);
     next unless $ldap;
 
-    # We only need the UID for confirmation now, 
-    # the other information would waste time and bandwidth
-    my @attrs = ('uid'); 
-    
-    $RT::Logger->debug( "LDAP Search === ",
-                        "Base:",
-                        $base,
-                        "== Filter:", 
-                        $search_filter->as_string,
-                        "== Attrs:", 
-                        join(',', at attrs));
-          
-    my $disabled_users = $ldap->search(base   => $base, 
-                                       filter => $search_filter, 
-                                       attrs  => \@attrs);
+    my $disabled_users = PerformSearch(
+        $ldap,
+        base   => $base, 
+        filter => $search_filter, 
+        attrs  => ['uid'], # We only need the UID for confirmation now
+    ) or return 0;
+
     # If ANY results are returned, 
     # we are going to assume the user should be disabled
     if ($disabled_users->count) {
@@ -499,6 +430,38 @@ sub _GetBoundLdapObj {
     }
 }
 
+sub PerformSearch {
+    my $ldap = shift;
+    my %args = @_;
+
+    $args{'filter'} = Net::LDAP::Filter->new($args{'filter'})
+        if $args{'filter'} && !ref $args{'filter'};
+
+    $RT::Logger->debug(
+        "LDAP Search === ",
+        $args{'base'}? ("Base:", $args{'base'}) : (),
+        $args{'filter'}? ("== Filter:", $args{'filter'}->as_string) : (),
+        $args{'attrs'}? ("== Attrs:", join ',', @{ $args{'attrs'} }) : (),
+    );
+    
+    my $res = $ldap->search( %args );
+    return undef unless $res;
+
+    # And the user isn't a member:
+    unless (
+        $res->code == LDAP_SUCCESS
+        || $res->code == LDAP_PARTIAL_RESULTS
+    ) {
+        $RT::Logger->error(
+            "Search for", $args{'filter'}->as_string, "failed:",
+            ldap_error_name($res->code), $res->code
+        );
+
+        return undef;
+    }
+    return $res;
+}
+
 # }}}
 
 1;

commit 5a3d89078e981881ed35e54768926000efac07f0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 02:25:54 2011 +0400

    Unbind function to get rid of repeated code

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 9e27e23..0636432 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -35,14 +35,10 @@ sub GetAuth {
     my $ldap = _GetBoundLdapObj($config);
     return 0 unless ($ldap);
 
-    $filter = '(&(' . 
-                                        $attr_map->{'Name'} . 
-                                        '=' . 
-                                        escape_filter_value($username) . 
-                                        ')' . 
-                                        $filter . 
-                                        ')'
-    ;
+    $filter = '(&'
+        .'('.  $attr_map->{'Name'} .  '=' .  escape_filter_value( $username ) .  ')' . 
+        $filter
+    .')';
 
     my $ldap_msg = PerformSearch(
         $ldap,
@@ -186,15 +182,7 @@ sub CanonicalizeUserInfo {
     
     unless ( $ldap_msg ) {
         # If we didn't get at LEAST a partial result, just die now.
-        $ldap_msg = $ldap->unbind();
-        if ($ldap_msg->code != LDAP_SUCCESS) {
-            $RT::Logger->critical(  (caller(0))[3],
-                                    ": Could not unbind: ", 
-                                    ldap_error_name($ldap_msg->code), 
-                                    $ldap_msg->code);
-        }
-        undef $ldap;
-        undef $ldap_msg;
+        Unbind( $ldap );
         return ($found, %params);
     } else {
         # If there's only one match, we're good; more than one and
@@ -215,28 +203,11 @@ sub CanonicalizeUserInfo {
             $found = 1;
         } else {
             # Drop out to the next external information service
-            $ldap_msg = $ldap->unbind();
-            if ($ldap_msg->code != LDAP_SUCCESS) {
-                $RT::Logger->critical(  (caller(0))[3],
-                                        ": Could not unbind: ", 
-                                        ldap_error_name($ldap_msg->code), 
-                                        $ldap_msg->code);
-            }
-            undef $ldap;
-            undef $ldap_msg;
+            Unbind( $ldap );
             return ($found, %params);
         }
     }
-    $ldap_msg = $ldap->unbind();
-    if ($ldap_msg->code != LDAP_SUCCESS) {
-        $RT::Logger->critical(  (caller(0))[3],
-                                ": Could not unbind: ", 
-                                ldap_error_name($ldap_msg->code), 
-                                $ldap_msg->code);
-    }
-
-    undef $ldap;
-    undef $ldap_msg;
+    Unbind( $ldap );
 
     return ($found, %params);
 }
@@ -430,6 +401,19 @@ sub _GetBoundLdapObj {
     }
 }
 
+sub Unbind {
+    my $ldap = shift;
+    my $res = $ldap->unbind;
+    return $res if !$res || $res->code == LDAP_SUCCESS;
+
+    $RT::Logger->error(
+        (caller(1))[3], ": Could not unbind: ", 
+        ldap_error_name($res->code), 
+        $res->code
+    );
+    return $res;
+}
+
 sub PerformSearch {
     my $ldap = shift;
     my %args = @_;

commit 778f0d92d0d1910bb199a423894c51d482f47b45
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 11:28:10 2011 +0400

    several level 'if/else' -> one level 'if'

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 0636432..f21d6b8 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -180,32 +180,22 @@ sub CanonicalizeUserInfo {
         attrs  => \@attrs
     );
     
-    unless ( $ldap_msg ) {
-        # If we didn't get at LEAST a partial result, just die now.
-        Unbind( $ldap );
-        return ($found, %params);
-    } else {
-        # If there's only one match, we're good; more than one and
-        # we don't know which is the right one so we skip it.
-        if ($ldap_msg->count == 1) {
-            my $entry = $ldap_msg->first_entry();
-            foreach my $key (keys(%{$config->{'attr_map'}})) {
+    # If there's only one match, we're good; more than one and
+    # we don't know which is the right one so we skip it.
+    if ($ldap_msg && $ldap_msg->count == 1) {
+        my $entry = $ldap_msg->first_entry();
+        foreach my $key (keys(%{$config->{'attr_map'}})) {
                 # XXX TODO: This legacy code wants to be removed since modern
                 # configs will always fall through to the else and the logic is
                 # weird even if you do have the old config.
                 if ($RT::LdapAttrMap and $RT::LdapAttrMap->{$key} eq 'dn') {
-                    $params{$key} = $entry->dn();
-                } else {
-                    $params{$key} = 
-                      ($entry->get_value($config->{'attr_map'}->{$key}))[0];
-                }
+                $params{$key} = $entry->dn();
+            } else {
+                $params{$key} = 
+                  ($entry->get_value($config->{'attr_map'}->{$key}))[0];
             }
-            $found = 1;
-        } else {
-            # Drop out to the next external information service
-            Unbind( $ldap );
-            return ($found, %params);
         }
+        $found = 1;
     }
     Unbind( $ldap );
 

commit 3a50575e954227a5067c5025a220ae6713201f58
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 13:54:56 2011 +0400

    avoid array with one undef when empty array expected

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 948939a..c12f3c3 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -580,7 +580,7 @@ sub CanonicalizeUserInfo {
                             sort(keys(%$args))));
 
     # Get the list of defined external services
-    my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : undef;
+    my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
     # For each external service...
     foreach my $service (@info_services) {
         

commit c84859d9fa42bb05264a3241067671c3c1c0a15c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 14:28:39 2011 +0400

    valid == 0 is only possible when key is undef

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index c12f3c3..8548b95 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -611,23 +611,13 @@ sub CanonicalizeUserInfo {
             # Else, use it as a canonicalization key and lookup the user info    
             my $key = $config->{'attr_map'}->{$rt_attr};
             my $value = $args->{$rt_attr};
-            
-            # Check to see that the key being asked for is defined in the config's attr_map
-            my $valid = 0;
-            my ($attr_key, $attr_value);
-            my $attr_map = $config->{'attr_map'};
-            while (($attr_key, $attr_value) = each %$attr_map) {
-                $valid = 1 if ($key eq $attr_value);
-            }
-            unless ($valid){
-                $RT::Logger->debug( "This key (",
-                                    $key,
-                                    "is not a valid attribute key (",
-                                    $service,
-                                    ")");
+            unless ( $key ) {
+                $RT::Logger->warning(
+                    "No mapping for $rt_attr in attr_map for this service ($service)"
+                );
                 next;
             }
-            
+
             # Use an if/elsif structure to do a lookup with any custom code needed 
             # for any given type of external service, or die if no code exists for
             # the service requested.

commit 49949593dae7782b32382f811c50bb8184ab6cb8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 14:31:12 2011 +0400

    correct log message

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 8548b95..4c35575 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -589,7 +589,7 @@ sub CanonicalizeUserInfo {
         
         # Get the config for the service so that we know what attrs we can canonicalize
         my $config = $RT::ExternalSettings->{$service};
-        
+
         if($config->{'type'} eq 'cookie'){
             $RT::Logger->debug("You cannot use SSO cookies as an information service!");
             next;
@@ -602,7 +602,7 @@ sub CanonicalizeUserInfo {
             unless(defined($args->{$rt_attr})) {
                 $RT::Logger->debug("This attribute (",
                                     $rt_attr,
-                                    ") is null or incorrectly defined in the attr_map for this service (",
+                                    ") is null or incorrectly defined in the attr_match_list for this service (",
                                     $service,
                                     ")");
                 next;

commit 24ca77d567fa36cf74f1e76838e240f2354272f0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 14:39:57 2011 +0400

    move $value declaration earlier, so we can re-use

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 4c35575..bc41770 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -599,7 +599,8 @@ sub CanonicalizeUserInfo {
         foreach my $rt_attr (@{$config->{'attr_match_list'}}) {
             # Jump to the next attr in $args if this one isn't in the attr_match_list
             $RT::Logger->debug( "Attempting to use this canonicalization key:",$rt_attr);
-            unless(defined($args->{$rt_attr})) {
+            my $value = $args->{$rt_attr};
+            unless( defined $value && length $value ) {
                 $RT::Logger->debug("This attribute (",
                                     $rt_attr,
                                     ") is null or incorrectly defined in the attr_match_list for this service (",
@@ -610,7 +611,6 @@ sub CanonicalizeUserInfo {
                                
             # Else, use it as a canonicalization key and lookup the user info    
             my $key = $config->{'attr_map'}->{$rt_attr};
-            my $value = $args->{$rt_attr};
             unless ( $key ) {
                 $RT::Logger->warning(
                     "No mapping for $rt_attr in attr_map for this service ($service)"

commit 53d142facd926e56c82c9f41af5f0be42e8b0765
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 9 14:40:37 2011 +0400

    pass all available info into XXXX::CanonicalizeUserInfo

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index bc41770..191cf66 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -623,9 +623,9 @@ sub CanonicalizeUserInfo {
             # the service requested.
             
             if($config->{'type'} eq 'ldap'){    
-                ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value);
+                ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value, $args);
             } elsif ($config->{'type'} eq 'db') {
-                ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo($service,$key,$value);
+                ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo($service,$key,$value, $args);
             } else {
                 $RT::Logger->debug( (caller(0))[3],
                                     "does not consider",

commit 90ebd777119007382532c26f0747c7fb3a6313d2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 01:48:58 2011 +0400

    get rid of $found and params

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index f21d6b8..529eeac 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -132,11 +132,6 @@ sub CanonicalizeUserInfo {
     
     my ($service, $key, $value) = @_;
 
-    my $found = 0;
-    my %params = (Name         => undef,
-                  EmailAddress => undef,
-                  RealName     => undef);
-
     # Load the config
     my $config = $RT::ExternalSettings->{$service};
    
@@ -163,7 +158,7 @@ sub CanonicalizeUserInfo {
         $RT::Logger->critical(  (caller(0))[3],
                                 "LDAP baseDN not defined");
         # Drop out to the next external information service
-        return ($found, %params);
+        return (0);
     }
 
     # Get a Net::LDAP object based on the config we provide
@@ -171,7 +166,7 @@ sub CanonicalizeUserInfo {
 
     # Jump to the next external information service if we can't get one, 
     # errors should be logged by _GetBoundLdapObj so we don't have to.
-    return ($found, %params) unless ($ldap);
+    return (0) unless ($ldap);
 
     my $ldap_msg = PerformSearch(
         $ldap,
@@ -182,24 +177,27 @@ sub CanonicalizeUserInfo {
     
     # If there's only one match, we're good; more than one and
     # we don't know which is the right one so we skip it.
-    if ($ldap_msg && $ldap_msg->count == 1) {
-        my $entry = $ldap_msg->first_entry();
-        foreach my $key (keys(%{$config->{'attr_map'}})) {
+    unless ($ldap_msg && $ldap_msg->count == 1) {
+        Unbind( $ldap );
+        return (0);
+    }
+
+    my %res;
+    my $entry = $ldap_msg->first_entry();
+    foreach my $key (keys(%{$config->{'attr_map'}})) {
                 # XXX TODO: This legacy code wants to be removed since modern
                 # configs will always fall through to the else and the logic is
                 # weird even if you do have the old config.
                 if ($RT::LdapAttrMap and $RT::LdapAttrMap->{$key} eq 'dn') {
-                $params{$key} = $entry->dn();
-            } else {
-                $params{$key} = 
-                  ($entry->get_value($config->{'attr_map'}->{$key}))[0];
-            }
+            $res{$key} = $entry->dn();
+        } else {
+            $res{$key} = 
+              ($entry->get_value($config->{'attr_map'}->{$key}))[0];
         }
-        $found = 1;
     }
-    Unbind( $ldap );
 
-    return ($found, %params);
+    Unbind( $ldap );
+    return (1, %res);
 }
 
 sub UserExists {

commit 55eb0a73598a0b403bb8db232b02c2efb190d615
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 02:42:50 2011 +0400

    support not only Name matching on update

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 191cf66..b7a3ad3 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -408,8 +408,8 @@ sub UpdateUserInfo {
     # Update their info from external service using the username as the lookup key
     # CanonicalizeUserInfo will work out for itself which service to use
     # Passing it a service instead could break other RT code
-    my %args = (Name => $username);
-    $UserObj->CanonicalizeUserInfo(\%args);
+    my %args;
+    $UserObj->CanonicalizeUserInfo( \%args );
 
     # For each piece of information returned by CanonicalizeUserInfo,
     # run the Set method for that piece of info to change it for the user
@@ -572,6 +572,14 @@ sub CanonicalizeUserInfo {
                   EmailAddress => undef,
                   RealName     => undef);
     
+    my $current_value = sub {
+        my $field = shift;
+        return $args->{ $field } if keys %$args;
+
+        return undef unless $UserObj->can( $field );
+        return $UserObj->$field();
+    };
+
     $RT::Logger->debug( (caller(0))[3], 
                         "called by", 
                         caller, 
@@ -599,7 +607,7 @@ sub CanonicalizeUserInfo {
         foreach my $rt_attr (@{$config->{'attr_match_list'}}) {
             # Jump to the next attr in $args if this one isn't in the attr_match_list
             $RT::Logger->debug( "Attempting to use this canonicalization key:",$rt_attr);
-            my $value = $args->{$rt_attr};
+            my $value = $current_value->( $rt_attr );
             unless( defined $value && length $value ) {
                 $RT::Logger->debug("This attribute (",
                                     $rt_attr,

commit 3d13ea850c73ba91f7c614dceac7f5ba1c296940
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 02:50:19 2011 +0400

    in CanonicalizeUserInfo deal with attr_map outside implementations

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index b7a3ad3..e3351a7 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -566,12 +566,7 @@ sub CanonicalizeUserInfo {
     
     my $UserObj = shift;
     my $args    = shift;
-    
-    my $found   = 0;
-    my %params  = (Name         => undef,
-                  EmailAddress => undef,
-                  RealName     => undef);
-    
+
     my $current_value = sub {
         my $field = shift;
         return $args->{ $field } if keys %$args;
@@ -580,6 +575,8 @@ sub CanonicalizeUserInfo {
         return $UserObj->$field();
     };
 
+    my ($found, $config, %params) = (0);
+
     $RT::Logger->debug( (caller(0))[3], 
                         "called by", 
                         caller, 
@@ -596,13 +593,19 @@ sub CanonicalizeUserInfo {
                             $service);
         
         # Get the config for the service so that we know what attrs we can canonicalize
-        my $config = $RT::ExternalSettings->{$service};
+        $config = $RT::ExternalSettings->{$service};
 
         if($config->{'type'} eq 'cookie'){
             $RT::Logger->debug("You cannot use SSO cookies as an information service!");
             next;
         }  
         
+        # Get the list of unique attrs we need
+        my @service_attrs = do {
+            my %seen;
+            grep !$seen{$_}++, map ref($_)? @$_ : ($_), values %{ $config->{'attr_map'} }
+        };
+
         # For each attr we've been told to canonicalize in the match list
         foreach my $rt_attr (@{$config->{'attr_match_list'}}) {
             # Jump to the next attr in $args if this one isn't in the attr_match_list
@@ -631,9 +634,9 @@ sub CanonicalizeUserInfo {
             # the service requested.
             
             if($config->{'type'} eq 'ldap'){    
-                ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value, $args);
+                ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value, \@service_attrs);
             } elsif ($config->{'type'} eq 'db') {
-                ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo($service,$key,$value, $args);
+                ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo($service,$key,$value, \@service_attrs);
             } else {
                 $RT::Logger->debug( (caller(0))[3],
                                     "does not consider",
@@ -647,29 +650,40 @@ sub CanonicalizeUserInfo {
         # Don't Check any more services
         last if $found;
     }
-    
-    # If found, Canonicalize Email Address and 
-    # update the args hash that we were given the hashref for
-    if ($found) {
-        # It's important that we always have a canonical email address
-        if ($params{'EmailAddress'}) {
-            $params{'EmailAddress'} = $UserObj->CanonicalizeEmailAddress($params{'EmailAddress'});
-        } 
-        %$args = (%$args, %params);
+
+    unless ( $found ) {
+        ### HACK: The config var below is to overcome the (IMO) bug in
+        ### RT::User::Create() which expects this function to always
+        ### return true or rejects the user for creation. This should be
+        ### a different config var (CreateUncanonicalizedUsers) and 
+        ### should be honored in RT::User::Create()
+        return($RT::AutoCreateNonExternalUsers);
+    }
+
+    # If found let's build back RT's fields
+    my %res;
+    while ( my ($k, $v) = each %{ $config->{'attr_map'} } ) {
+        if ( keys %$args ) {
+            $res{ $k } = $params{ $v };
+        } else {
+            $res{ $k } = $params{ $v };
+        }
     }
 
+    # It's important that we always have a canonical email address
+    if ($res{'EmailAddress'}) {
+        $res{'EmailAddress'} = $UserObj->CanonicalizeEmailAddress($res{'EmailAddress'});
+    } 
+
+    # update the args hash that we were given the hashref for
+    %$args = (%$args, %res);
+
     $RT::Logger->info(  (caller(0))[3], 
                         "returning", 
                         join(", ", map {sprintf("%s: %s", $_, ($args->{$_} ? $args->{$_} : ''))}
                             sort(keys(%$args))));
 
-    ### HACK: The config var below is to overcome the (IMO) bug in
-    ### RT::User::Create() which expects this function to always
-    ### return true or rejects the user for creation. This should be
-    ### a different config var (CreateUncanonicalizedUsers) and 
-    ### should be honored in RT::User::Create()
-    return($found || $RT::AutoCreateNonExternalUsers);
-   
+    return $found;
 }
 
 {
diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 529eeac..d2844f4 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -130,7 +130,7 @@ sub GetAuth {
 
 sub CanonicalizeUserInfo {
     
-    my ($service, $key, $value) = @_;
+    my ($service, $key, $value, $attrs) = @_;
 
     # Load the config
     my $config = $RT::ExternalSettings->{$service};
@@ -139,9 +139,6 @@ sub CanonicalizeUserInfo {
     my $base            = $config->{'base'};
     my $filter          = $config->{'filter'};
 
-    # Get the list of unique attrs we need
-    my @attrs = values(%{$config->{'attr_map'}});
-
     # This is a bit confusing and probably broken. Something to revisit..
     my $filter_addition = ($key && $value) ? "(". $key . "=". escape_filter_value($value) .")" : "";
     if(defined($filter) && ($filter ne "()")) {
@@ -172,7 +169,7 @@ sub CanonicalizeUserInfo {
         $ldap,
         base   => $base,
         filter => $filter,
-        attrs  => \@attrs
+        attrs  => $attrs
     );
     
     # If there's only one match, we're good; more than one and
@@ -183,19 +180,17 @@ sub CanonicalizeUserInfo {
     }
 
     my %res;
-    my $entry = $ldap_msg->first_entry();
-    foreach my $key (keys(%{$config->{'attr_map'}})) {
-                # XXX TODO: This legacy code wants to be removed since modern
-                # configs will always fall through to the else and the logic is
-                # weird even if you do have the old config.
-                if ($RT::LdapAttrMap and $RT::LdapAttrMap->{$key} eq 'dn') {
-            $res{$key} = $entry->dn();
+    my $entry = $ldap_msg->first_entry;
+    foreach my $attr ( @$attrs ) {
+        # XXX TODO: This if branch was inactive for a while
+        # now it works, but is it right thing to do? entry->dn
+        # is it at all diiferent from get_value('dn')?
+        if ( $attr eq 'dn' ) {
+            $res{ $attr } = $entry->dn;
         } else {
-            $res{$key} = 
-              ($entry->get_value($config->{'attr_map'}->{$key}))[0];
+            $res{ $attr } = ($entry->get_value( $attr ))[0];
         }
     }
-
     Unbind( $ldap );
     return (1, %res);
 }

commit 2238a54866bc1e490e8028d6750bf2f5b8571abf
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 17:04:14 2011 +0400

    ::Test->add_ldap_user_simple method

diff --git a/lib/RT/Authen/ExternalAuth/Test.pm b/lib/RT/Authen/ExternalAuth/Test.pm
index b7c1c59..7a19e77 100644
--- a/lib/RT/Authen/ExternalAuth/Test.pm
+++ b/lib/RT/Authen/ExternalAuth/Test.pm
@@ -117,4 +117,21 @@ sub add_ldap_user {
     return $ldap{'client'}->add( $dn, attr => [%args] );
 }
 
+{ my $i = 0;
+sub add_ldap_user_simple {
+    my $self = shift;
+    my %args = @_;
+
+    my $name = "testuser". ++$i;
+
+    s/\%name\b/$name/g foreach grep defined, values %args;
+
+    $self->add_ldap_user(
+        cn    => $name,
+        mail  => "$name\@invalid.tld",
+        %args,
+    );
+    return $name;
+} }
+
 1;

commit 9f2edc9da1a4e135d7dc7a538214bd4f309b6ff6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 17:03:28 2011 +0400

    test that AutoCreate works on submission by email

diff --git a/xt/ldap/privileged.t b/xt/ldap/privileged.t
index 4dd277b..d54997a 100644
--- a/xt/ldap/privileged.t
+++ b/xt/ldap/privileged.t
@@ -1,51 +1,65 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test ldap => 1, tests => 15;
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 20;
 my $class = 'RT::Authen::ExternalAuth::Test';
 
-
 my ($server, $client) = $class->bootstrap_ldap_basics;
 ok( $server, "spawned test LDAP server" );
 
-my $username = "testuser";
-$class->add_ldap_user(
-    uid  => $username,
-    mail => "$username\@invalid.tld",
-);
-
 RT->Config->Set( AutoCreate                  => { Privileged => 1 } );
 
+RT::Test->set_rights(
+    { Principal => 'Everyone', Right => [qw(SeeQueue ShowTicket CreateTicket)] },
+);
+
 my ( $baseurl, $m ) = RT::Test->started_ok();
 
-diag "test uri login";
+
+diag "login, make sure user privileged";
 {
-    ok( !$m->login( 'fakeuser', 'password' ), 'not logged in with fake user' );
-    ok( $m->login( 'testuser', 'password' ), 'logged in' );
+    my $username = $class->add_ldap_user_simple;
+    ok( $m->login( $username, 'password' ), 'logged in' );
+
+    {
+        my $user = RT::User->new($RT::SystemUser);
+        my ($ok,$msg) = $user->Load( $username );
+        ok($user->id);
+        is($user->EmailAddress, "$username\@invalid.tld");
+        ok($user->Privileged, 'privileged user');
+    }
+
+    unlike( $m->uri, qr!SelfService!, 'privileged home page' );
 }
 
-diag "test user creation";
+diag "send mail, make sure user privileged";
 {
-    my $testuser = RT::User->new($RT::SystemUser);
-    my ($ok,$msg) = $testuser->Load( 'testuser' );
-    ok($ok,$msg);
-    is($testuser->EmailAddress,'testuser at invalid.tld');
-}
+    my $username = $class->add_ldap_user_simple;
+    {
+        my $mail = << "MAIL";
+Subject: Test
+From: $username\@invalid.tld
 
+test
+MAIL
 
-diag "test form login";
-{
-    $m->logout;
-    $m->get_ok( $baseurl, 'base url' );
-    $m->submit_form(
-        form_number => 1,
-        fields      => { user => 'testuser', pass => 'password', },
-    );
-    $m->text_contains( 'Logout', 'logged in via form' );
-}
+        my ($status, $id) = RT::Test->send_via_mailgate($mail);
+        is ($status >> 8, 0, "The mail gateway exited normally");
+        ok ($id, "got id of a newly created ticket - $id");
 
-like( $m->uri, qr!$baseurl/(index\.html)?!, 'privileged home page' );
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
 
-$client->unbind();
+        my $user = $ticket->CreatorObj;
+        is($user->EmailAddress, "$username\@invalid.tld");
+        ok($user->Privileged, 'privileged user');
+    }
+    {
+        ok( $m->login( $username, 'password' ), 'logged in' );
+        unlike( $m->uri, qr!SelfService!, 'privileged home page' );
+    }
+}
 
+$client->unbind();
 $m->get_warnings;

commit 29cc01fd4996af61833e703003c27fa3ab96dfbb
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 17:11:39 2011 +0400

    tests for setups with multiple emails stored in LDAP

diff --git a/xt/ldap/multiple-emails.t b/xt/ldap/multiple-emails.t
new file mode 100644
index 0000000..5ebe609
--- /dev/null
+++ b/xt/ldap/multiple-emails.t
@@ -0,0 +1,164 @@
+use strict;
+use warnings;
+
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 43;
+my $class = 'RT::Authen::ExternalAuth::Test';
+
+my ($server, $client) = $class->bootstrap_ldap_basics;
+ok( $server, "spawned test LDAP server" );
+
+my $queue = RT::Test->load_or_create_queue(Name => 'General');
+ok($queue->id, "loaded the General queue");
+
+RT->Config->Set( AutoCreate                  => { Privileged => 1 } );
+
+RT->Config->Get('ExternalSettings')->{'My_LDAP'}{'attr_map'}{'EmailAddress'}
+    = ['mail', 'alias'];
+
+RT::Test->set_rights(
+    { Principal => 'Everyone', Right => [qw(SeeQueue ShowTicket CreateTicket)] },
+);
+
+my ( $baseurl, $m ) = RT::Test->started_ok();
+
+diag "login then send emails from different addresses";
+{
+    my $username = new_user();
+    my $first_user;
+    {
+        ok( $m->login( $username, 'password' ), 'logged in' );
+
+        ok( $m->goto_create_ticket( $queue ), "go to create ticket" );
+        $m->form_name('TicketCreate');
+        $m->submit;
+
+        my ($id) = ($m->content =~ /.*Ticket (\d+) created.*/g);
+        ok $id, "created a ticket";
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $first_user = $ticket->CreatorObj;
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@invalid.tld" );
+    }
+
+    {
+        my $mail = << "MAIL";
+Subject: Test
+From: $username\@invalid.tld
+
+test
+MAIL
+
+        my ($status, $id) = RT::Test->send_via_mailgate($mail);
+        is ($status >> 8, 0, "The mail gateway exited normally");
+        ok ($id, "got id of a newly created ticket - $id");
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $ticket->CreatorObj;
+        is( $user->id, $first_user->id );
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@invalid.tld" );
+    }
+
+    {
+        my $mail = << "MAIL";
+Subject: Test
+From: $username\@alternative.tld
+
+test
+MAIL
+
+        my ($status, $id) = RT::Test->send_via_mailgate($mail);
+        is ($status >> 8, 0, "The mail gateway exited normally");
+        ok ($id, "got id of a newly created ticket - $id");
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $ticket->CreatorObj;
+        is( $user->id, $first_user->id );
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@invalid.tld" );
+    }
+}
+
+diag "send a mail from alternative address, then try other credentials";
+{
+    my $username = new_user();
+    my $first_user;
+    {
+        my $mail = << "MAIL";
+Subject: Test
+From: $username\@alternative.tld
+
+test
+MAIL
+
+        my ($status, $id) = RT::Test->send_via_mailgate($mail);
+        is ($status >> 8, 0, "The mail gateway exited normally");
+        ok ($id, "got id of a newly created ticket - $id");
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $first_user = $ticket->CreatorObj;
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@alternative.tld" );
+    }
+
+    {
+        ok( $m->login( $username, 'password' ), 'logged in' );
+
+        ok( $m->goto_create_ticket( $queue ), "go to create ticket" );
+        $m->form_name('TicketCreate');
+        $m->submit;
+
+        my ($id) = ($m->content =~ /.*Ticket (\d+) created.*/g);
+        ok $id, "created a ticket";
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $ticket->CreatorObj;
+        is( $user->id, $first_user->id );
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@alternative.tld" );
+    }
+
+    {
+        my $mail = << "MAIL";
+Subject: Test
+From: $username\@invalid.tld
+
+test
+MAIL
+
+        my ($status, $id) = RT::Test->send_via_mailgate($mail);
+        is ($status >> 8, 0, "The mail gateway exited normally");
+        ok ($id, "got id of a newly created ticket - $id");
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $ticket->CreatorObj;
+        is( $user->id, $user->id );
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@alternative.tld" );
+    }
+}
+
+$client->unbind();
+$m->get_warnings;
+
+sub new_user { return $class->add_ldap_user_simple( alias => '%name at alternative.tld' ) }
+

commit 8e43ba70b77b5a4893aaf85ded715b04695c9efd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 17:15:00 2011 +0400

    method to join zero or more LDAP filters with '&' or '|' op

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index d2844f4..8482d4c 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -429,6 +429,25 @@ sub PerformSearch {
     return $res;
 }
 
+sub JoinFilters {
+    my $op = shift;
+    my @list =
+        grep defined && length && $_ ne '()',
+        map ref $_? $_->as_string : $_,
+        @_;
+    return undef unless @list;
+
+    my $str = @list > 1
+        ? "($op". join( '', @list ) .')'
+        : $list[0]
+    ;
+    my $obj = Net::LDAP::Filter->new( $str );
+    $RT::Logger->error("'$str' is not valid LDAP filter")
+        unless $obj;
+
+    return $obj;
+}
+
 # }}}
 
 1;

commit 7fdd4df538d0cb9a3620e4be46620fbbdda3f2a7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 18:07:18 2011 +0400

    change how we build main filter in LDAP::CanonicalizeUserInfo
    
    * support key to be an array to mean any of these LDAP attributes
    * key and value are always defined by the caller
    * use JoinFilters
    ** support situation when filter in the config is empty

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 8482d4c..15e0774 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -134,23 +134,14 @@ sub CanonicalizeUserInfo {
 
     # Load the config
     my $config = $RT::ExternalSettings->{$service};
-   
-    # Figure out what's what
-    my $base            = $config->{'base'};
-    my $filter          = $config->{'filter'};
 
-    # This is a bit confusing and probably broken. Something to revisit..
-    my $filter_addition = ($key && $value) ? "(". $key . "=". escape_filter_value($value) .")" : "";
-    if(defined($filter) && ($filter ne "()")) {
-        $filter = Net::LDAP::Filter->new(   "(&" . 
-                                            $filter . 
-                                            $filter_addition . 
-                                            ")"
-                                        ); 
-    } else {
-        $RT::Logger->debug( "LDAP Filter invalid or not present.");
-    }
+    my $filter = JoinFilters(
+        '&',
+        JoinFilters('|', map "($_=". escape_filter_value( $value ) .")", ref $key? @$key: ($key) ),
+        $config->{'filter'},
+    ) or return (0);
 
+    my $base = $config->{'base'};
     unless (defined($base)) {
         $RT::Logger->critical(  (caller(0))[3],
                                 "LDAP baseDN not defined");

commit d46ad6aab2285ec1b5b4a4b1d7de53bf0ed6fe5a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 18:11:06 2011 +0400

    change how we process hash from XXX::CanonicalizeUserInfo
    
    support the following config when we map hash returned by
    CanonicalizeUserInfo to RT user's fields.
    
    attr_map => {
        EmailAddress => ['mail', 'alias'],
        ...
    },

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index e3351a7..22523f5 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -663,10 +663,18 @@ sub CanonicalizeUserInfo {
     # If found let's build back RT's fields
     my %res;
     while ( my ($k, $v) = each %{ $config->{'attr_map'} } ) {
-        if ( keys %$args ) {
+        unless ( ref $v ) {
             $res{ $k } = $params{ $v };
+            next;
+        }
+
+        my $current = $current_value->( $k );
+        unless ( defined $current ) {
+            $res{ $k } = (grep defined && length, map $params{ $_ }, @$v)[0];
         } else {
-            $res{ $k } = $params{ $v };
+            unless ( grep defined && length && $_ eq $current, map $params{ $_ }, @$v ) {
+                $res{ $k } = (grep defined && length, map $params{ $_ }, @$v)[0];
+            }
         }
     }
 

commit 201a7883f1d1d62de5b83f68ca9f243c063efa5d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 18:18:49 2011 +0400

    extend ::User->LoadByCols to support alternative values
    
    RT's default ::User->LoadByCols failed, but user record may be
    still there if we have info providing service with alternative
    values for fields from 'attr_match_list' list

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 22523f5..239ed24 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -703,4 +703,91 @@ sub CanonicalizeUserInfo {
     };
 }
 
+{
+    no warnings 'redefine';
+    my $orig = RT::User->can('LoadByCols');
+    *RT::User::LoadByCols = sub {
+        my $self = shift;
+        my %args = @_;
+
+        my $rv = $orig->( $self, %args );
+        return $rv if $self->id;
+
+# we couldn't load a user. ok, but user may exist anyway. It may happen when
+# RT fields in attr_match_list are mapped to multiple attributes in an external
+# source, for example: attr_map => { EmailAddress => [qw(mail alias1 alias2 alias3)], }
+
+        # find services that may have alternative values for a field we search by
+        my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
+        foreach my $service ( splice @info_services ) {
+            my $config = $RT::ExternalSettings->{ $service };
+            next if $config->{'type'} eq 'cookie';
+            next unless
+                grep ref $_,
+                map $config->{'attr_map'}{ $_ },
+                @{ $config->{'attr_match_list'} };
+
+            push @info_services, $service;
+        }
+        return $rv unless @info_services;
+
+        # find user in external service and fetch alternative values
+        # for a field
+        my ($found, $search_by, @alternatives);
+        foreach my $service (@info_services) {
+            my $config = $RT::ExternalSettings->{$service};
+
+            # Get the list of unique attrs we need
+            my @service_attrs = do {
+                my %seen;
+                grep !$seen{$_}++, map ref($_)? @$_ : ($_), values %{ $config->{'attr_map'} }
+            };
+
+            foreach my $rt_attr ( @{ $config->{'attr_match_list'} } ) {
+                next unless exists $args{ $rt_attr }
+                    && defined $args{ $rt_attr }
+                    && length $args{ $rt_attr };
+                next unless ref $config->{'attr_map'}{ $rt_attr };
+
+                $search_by = $rt_attr;
+                last;
+            }
+            next unless $search_by;
+
+            my @search_args = (
+                $service,
+                $config->{'attr_map'}{ $search_by },
+                $args{ $search_by },
+                $config->{'attr_map'}{ $search_by },
+            );
+
+            my %params;
+            if($config->{'type'} eq 'ldap') {
+                ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo( @search_args );
+            } elsif ($config->{'type'} eq 'db') {
+                ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo( @search_args );
+            } else {
+                $RT::Logger->debug( (caller(0))[3],
+                                    "does not consider",
+                                    $service,
+                                    "a valid information service");
+            }
+            next unless $found;
+
+            @alternatives = grep defined && length && $_ ne $args{ $search_by }, values %params;
+
+            # Don't Check any more services
+            last;
+        }
+        return $rv unless $found;
+
+        # lookup by alternatives
+        foreach my $value ( @alternatives ) {
+            my $rv = $orig->( $self, %args, $search_by => $value );
+            return $rv if $self->id;
+        }
+        return $rv;
+    };
+}
+
 1;

commit c51375c60d64931cb8f61a61df6519f5042c6385
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 21:55:41 2011 +0400

    drop unused code

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 239ed24..b0a5723 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -737,12 +737,6 @@ sub CanonicalizeUserInfo {
         foreach my $service (@info_services) {
             my $config = $RT::ExternalSettings->{$service};
 
-            # Get the list of unique attrs we need
-            my @service_attrs = do {
-                my %seen;
-                grep !$seen{$_}++, map ref($_)? @$_ : ($_), values %{ $config->{'attr_map'} }
-            };
-
             foreach my $rt_attr ( @{ $config->{'attr_match_list'} } ) {
                 next unless exists $args{ $rt_attr }
                     && defined $args{ $rt_attr }

commit aca46e2a3abda9a95a45bfe9248eb7eba66c4fa3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 11 22:55:24 2011 +0400

    clean search_by to avoid leftovers from previouse service

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index b0a5723..5118264 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -737,6 +737,7 @@ sub CanonicalizeUserInfo {
         foreach my $service (@info_services) {
             my $config = $RT::ExternalSettings->{$service};
 
+            $search_by = undef;
             foreach my $rt_attr ( @{ $config->{'attr_match_list'} } ) {
                 next unless exists $args{ $rt_attr }
                     && defined $args{ $rt_attr }

commit 9ddaab4ee6650e6468741c911dc05718f43485a2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 12 00:41:27 2011 +0400

    bring AutoCreate option into ::Interface::Email::CreateUser()
    
    Workaround for a bug in RT. When a user that is not in the DB
    submits a ticket via Email then a new account created without
    AutoCreate config option.

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 5118264..24573e0 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -567,6 +567,8 @@ sub CanonicalizeUserInfo {
     my $UserObj = shift;
     my $args    = shift;
 
+    WorkaroundAutoCreate( $UserObj, $args );
+
     my $current_value = sub {
         my $field = shift;
         return $args->{ $field } if keys %$args;
@@ -785,4 +787,20 @@ sub CanonicalizeUserInfo {
     };
 }
 
+sub WorkaroundAutoCreate {
+    my $user = shift;
+    my $args = shift;
+
+    # CreateUser in RT::Interface::Email doesn't account $RT::AutoCreate
+    # config option. Let's workaround it.
+
+    return unless $RT::AutoCreate && keys %$RT::AutoCreate;
+    return unless keys %$args; # no args - update
+    return unless (caller(4))[3] eq 'RT::Interface::Email::CreateUser';
+
+    my %tmp = %$RT::AutoCreate;
+    delete @tmp{qw(Name EmailAddress RealName Comments)};
+    %$args = (%$args, %$RT::AutoCreate);
+}
+
 1;

commit e6301e99d665987f84b72665bc9cf19b0948765d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 12 01:49:51 2011 +0400

    port xt/sqlite.t over ::Test

diff --git a/lib/RT/Authen/ExternalAuth/Test.pm b/lib/RT/Authen/ExternalAuth/Test.pm
index 7a19e77..20c376a 100644
--- a/lib/RT/Authen/ExternalAuth/Test.pm
+++ b/lib/RT/Authen/ExternalAuth/Test.pm
@@ -42,6 +42,20 @@ sub import {
             exit;
         }
     }
+    if ( my $driver = delete $args{'dbi'} ) {
+        local $@;
+        eval {
+            require DBI;
+            require File::Temp;
+            require Digest::MD5;
+            require File::Spec;
+            eval "require DBD::$driver; 1";
+        } or do {
+            require Test::More;
+            Test::More::plan( skip_all => 'Unable to test without DB modules: '. $@ );
+            exit;
+        }
+    }
 
     $class->SUPER::import( %args );
     $class->export_to_level(1);
diff --git a/xt/sqlite.t b/xt/sqlite.t
index 3c52649..0e04b22 100644
--- a/xt/sqlite.t
+++ b/xt/sqlite.t
@@ -1,15 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test;
-use DBI;
-use File::Temp;
-use Digest::MD5;
-use File::Spec;
-
-eval { require DBD::SQLite; } or do {
-    plan skip_all => 'Unable to test without DBD::SQLite';
-};
+use RT::Authen::ExternalAuth::Test dbi => 'SQLite', tests => 19;
+my $class = 'RT::Authen::ExternalAuth::Test';
 
 my $dir    = File::Temp::tempdir( CLEANUP => 1 );
 my $dbname = File::Spec->catfile( $dir, 'rtauthtest' );

commit 585a3b6a9cf9ef1f5012985c5236c04f11512ffa
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 12 01:52:02 2011 +0400

    port ::DBI::CanonicalizeUserInfo over new API

diff --git a/lib/RT/Authen/ExternalAuth/DBI.pm b/lib/RT/Authen/ExternalAuth/DBI.pm
index 7099632..adc5a38 100644
--- a/lib/RT/Authen/ExternalAuth/DBI.pm
+++ b/lib/RT/Authen/ExternalAuth/DBI.pm
@@ -120,12 +120,10 @@ sub GetAuth {
 
 sub CanonicalizeUserInfo {
     
-    my ($service, $key, $value) = @_;
+    my ($service, $key, $value, $attrs) = @_;
 
     my $found = 0;
-    my %params = (Name         => undef,
-                  EmailAddress => undef,
-                  RealName     => undef);
+    my %params = ();
     
     # Load the config
     my $config = $RT::ExternalSettings->{$service};
@@ -147,27 +145,21 @@ sub CanonicalizeUserInfo {
         return ($found, %params);
     }
     
-    # "where" refers to WHERE section of SQL query
-    my ($where_key,$where_value) = ("@{[ $key ]}",$value);
-
     # Get the list of unique attrs we need
-    my %db_attrs = map {$_ => 1} values(%{$config->{'attr_map'}});
-    my @attrs = keys(%db_attrs);
-    my $fields = join(',', at attrs);
-    my $query = "SELECT $fields FROM $table WHERE $where_key=?";
-    my @bind_params = ($where_value);
+    my $query = "SELECT ". join(', ', @$attrs) ." FROM $table WHERE $key=?";
+    my @bind_params = ($value);
 
     # Uncomment this to trace basic DBI throughput in a log
     # DBI->trace(1,'/tmp/dbi.log');
     my $dbh = _GetBoundDBIObj($config);
-    my $results_hashref = $dbh->selectall_hashref($query,$key,{}, at bind_params);
+    my $results = $dbh->selectall_arrayref($query, undef, @bind_params);
     $dbh->disconnect();
 
-    if ((scalar keys %$results_hashref) != 1) {
+    if ( @$results != 1 ) {
         # If returned users <> 1, we have no single unique user, so prepare to die
         my $death_msg;
         
-	    if ((scalar keys %$results_hashref) == 0) {
+	    if ( @$results == 0) {
             # If no user...
 	        $death_msg = "No User Found in External Database!";
         } else {
@@ -190,14 +182,8 @@ sub CanonicalizeUserInfo {
 
     # We haven't dropped out, so DB search must have succeeded with 
     # exactly 1 result. Get the result and set $found to 1
-    my $result = $results_hashref->{$value};
- 
-    # Use the result to populate %params for every key we're given in the config
-    foreach my $key (keys(%{$config->{'attr_map'}})) {
-        $params{$key} = ($result->{$config->{'attr_map'}->{$key}})[0];
-    }
-    
     $found = 1;
+    @params{ @$attrs } = @{ $results->[0] };
   
     return ($found, %params);
 }

commit d9d06f6a895270bdea4df985b8d66ade982c7062
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri May 13 01:28:37 2011 +0400

    use M::I::Substitute for 'use lib ...' in ::Test

diff --git a/Makefile.PL b/Makefile.PL
index bf93ff5..a62a8ef 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -32,6 +32,14 @@ recursive_author_tests('xt');
 
 requires_rt('3.8.2');
 
+my ($lib_path) = $INC{'RT.pm'} =~ /^(.*)[\\\/]/;
+my $local_lib_path = "$RT::LocalPath/lib";
+substitute( {
+        RT_LIB_PATH => join( ' ', $local_lib_path, $lib_path ),
+    },
+    'lib/RT/Authen/ExternalAuth/Test.pm',
+);
+
 &auto_install();
 
 my ($lib_path) = $INC{'RT.pm'} =~ /^(.*)[\\\/]/;

commit 462b2bd8fab377ffd0663328d181080f43470255
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Thu May 12 18:12:17 2011 -0400

    Remove comment copied from first refactored chunk

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 15e0774..0bb5917 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -405,7 +405,6 @@ sub PerformSearch {
     my $res = $ldap->search( %args );
     return undef unless $res;
 
-    # And the user isn't a member:
     unless (
         $res->code == LDAP_SUCCESS
         || $res->code == LDAP_PARTIAL_RESULTS

commit 6cd868e6f2264aea6cce2b51596846128c00f489
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 14 11:20:00 2011 +0400

    test late sync, e.g. ExtAuth enabled on live system

diff --git a/xt/ldap/late-sync.t b/xt/ldap/late-sync.t
new file mode 100644
index 0000000..0673910
--- /dev/null
+++ b/xt/ldap/late-sync.t
@@ -0,0 +1,72 @@
+use strict;
+use warnings;
+
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 43;
+my $class = 'RT::Authen::ExternalAuth::Test';
+
+my ($server, $client) = $class->bootstrap_ldap_basics;
+ok( $server, "spawned test LDAP server" );
+
+my $queue = RT::Test->load_or_create_queue(Name => 'General');
+ok($queue->id, "loaded the General queue");
+
+RT->Config->Set( AutoCreate                  => { Privileged => 1 } );
+RT->Config->Set( AutoCreateNonExternalUsers  => 1 );
+
+RT::Test->set_rights(
+    { Principal => 'Everyone', Right => [qw(SeeQueue ShowTicket CreateTicket)] },
+);
+
+my ( $baseurl, $m ) = RT::Test->started_ok();
+
+diag "send email w/o LDAP, login with LDAP";
+{
+    my $username = 'test1';
+    my $first_user;
+    {
+        my $mail = << "MAIL";
+Subject: Test
+From: $username\@invalid.tld
+
+test
+MAIL
+
+        my ($status, $id) = RT::Test->send_via_mailgate($mail);
+        is ($status >> 8, 0, "The mail gateway exited normally");
+        ok ($id, "got id of a newly created ticket - $id");
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        # no LDAP account
+        my $user = $first_user = $ticket->CreatorObj;
+        is( $user->Name, "$username\@invalid.tld" );
+        is( $user->EmailAddress, "$username\@invalid.tld" );
+    }
+
+    $class->add_ldap_user_simple( cn => $username );
+
+    {
+        ok( $m->login( $username, 'password' ), 'logged in' );
+
+        ok( $m->goto_create_ticket( $queue ), "go to create ticket" );
+        $m->form_name('TicketCreate');
+        $m->submit;
+
+        my ($id) = ($m->content =~ /.*Ticket (\d+) created.*/g);
+        ok $id, "created a ticket";
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $ticket->CreatorObj;
+        is( $user->id, $first_user->id );
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@invalid.tld" );
+    }
+}
+
+$client->unbind();
+$m->get_warnings;

commit d66ddaf6af9774ace89d1eae33eb40f603cb6be6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 14 11:20:52 2011 +0400

    make it possible to pass cn (name) into add_ldap_user_simple

diff --git a/lib/RT/Authen/ExternalAuth/Test.pm b/lib/RT/Authen/ExternalAuth/Test.pm
index 20c376a..9226a9d 100644
--- a/lib/RT/Authen/ExternalAuth/Test.pm
+++ b/lib/RT/Authen/ExternalAuth/Test.pm
@@ -136,7 +136,7 @@ sub add_ldap_user_simple {
     my $self = shift;
     my %args = @_;
 
-    my $name = "testuser". ++$i;
+    my $name = delete $args{'cn'} || "testuser". ++$i;
 
     s/\%name\b/$name/g foreach grep defined, values %args;
 

commit b45afe3016ce51a58d1a7bc87ca0dfb7e534f349
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 14 11:22:46 2011 +0400

    split LoadByCols addition into two attempts
    
    new attempt checks for not yet synced records, for example
    user (foo at xxx, foo at xxx) logs in as foo which exist in external
    source as (foo, foo at xxx).

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 24573e0..4796b7a 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -715,50 +715,132 @@ sub CanonicalizeUserInfo {
         my $rv = $orig->( $self, %args );
         return $rv if $self->id;
 
-# we couldn't load a user. ok, but user may exist anyway. It may happen when
-# RT fields in attr_match_list are mapped to multiple attributes in an external
+# we couldn't load a user. ok, but user may exist anyway. It may happen in the following
+# cases:
+# 1) Service has multiple fields in attr_match_list, it's important when we have Name
+# and EmailAddress in there. 
+
+        my (%other) = FindRecordsByOtherFields( $self, %args );
+        while ( my ($search_by, $values) = each %other ) {
+            foreach my $value ( @$values ) {
+                my $rv = $orig->( $self, $search_by => $value );
+                return $rv if $self->id;
+            }
+        }
+
+# 2) RT fields in attr_match_list are mapped to multiple attributes in an external
 # source, for example: attr_map => { EmailAddress => [qw(mail alias1 alias2 alias3)], }
+        my ($search_by, @alternatives) = FindRecordsWithAlternatives( $self, %args );
+        foreach my $value ( @alternatives ) {
+            my $rv = $orig->( $self, %args, $search_by => $value );
+            return $rv if $self->id;
+        }
 
-        # find services that may have alternative values for a field we search by
-        my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
-        foreach my $service ( splice @info_services ) {
-            my $config = $RT::ExternalSettings->{ $service };
-            next if $config->{'type'} eq 'cookie';
-            next unless
-                grep ref $_,
-                map $config->{'attr_map'}{ $_ },
-                @{ $config->{'attr_match_list'} };
+        return $rv;
+    };
+}
+
+sub FindRecordsWithAlternatives {
+    my $user = shift;
+    my %args = @_;
 
-            push @info_services, $service;
+    # find services that may have alternative values for a field we search by
+    my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
+    foreach my $service ( splice @info_services ) {
+        my $config = $RT::ExternalSettings->{ $service };
+        next if $config->{'type'} eq 'cookie';
+        next unless
+            grep ref $_,
+            map $config->{'attr_map'}{ $_ },
+            @{ $config->{'attr_match_list'} };
+
+        push @info_services, $service;
+    }
+    return unless @info_services;
+
+    # find user in external service and fetch alternative values
+    # for a field
+    foreach my $service (@info_services) {
+        my $config = $RT::ExternalSettings->{$service};
+
+        my $search_by = undef;
+        foreach my $rt_attr ( @{ $config->{'attr_match_list'} } ) {
+            next unless exists $args{ $rt_attr }
+                && defined $args{ $rt_attr }
+                && length $args{ $rt_attr };
+            next unless ref $config->{'attr_map'}{ $rt_attr };
+
+            $search_by = $rt_attr;
+            last;
         }
-        return $rv unless @info_services;
-
-        # find user in external service and fetch alternative values
-        # for a field
-        my ($found, $search_by, @alternatives);
-        foreach my $service (@info_services) {
-            my $config = $RT::ExternalSettings->{$service};
-
-            $search_by = undef;
-            foreach my $rt_attr ( @{ $config->{'attr_match_list'} } ) {
-                next unless exists $args{ $rt_attr }
-                    && defined $args{ $rt_attr }
-                    && length $args{ $rt_attr };
-                next unless ref $config->{'attr_map'}{ $rt_attr };
-
-                $search_by = $rt_attr;
-                last;
-            }
-            next unless $search_by;
+        next unless $search_by;
+
+        my @search_args = (
+            $service,
+            $config->{'attr_map'}{ $search_by },
+            $args{ $search_by },
+            $config->{'attr_map'}{ $search_by },
+        );
+
+        my ($found, %params);
+        if($config->{'type'} eq 'ldap') {
+            ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo( @search_args );
+        } elsif ($config->{'type'} eq 'db') {
+            ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo( @search_args );
+        } else {
+            $RT::Logger->debug( (caller(0))[3],
+                                "does not consider",
+                                $service,
+                                "a valid information service");
+        }
+        next unless $found;
+
+        my @alternatives = grep defined && length && $_ ne $args{ $search_by }, values %params;
 
+        # Don't Check any more services
+        return @alternatives;
+    }
+    return;
+}
+
+sub FindRecordsByOtherFields {
+    my $user = shift;
+    my %args = @_;
+
+    my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
+    foreach my $service ( splice @info_services ) {
+        my $config = $RT::ExternalSettings->{ $service };
+        next if $config->{'type'} eq 'cookie';
+        next unless @{ $config->{'attr_match_list'} } > 1;
+
+        push @info_services, $service;
+    }
+    return unless @info_services;
+
+    # find user in external service and fetch alternative values
+    # for a field
+    foreach my $service (@info_services) {
+        my $config = $RT::ExternalSettings->{$service};
+
+        foreach my $search_by ( @{ $config->{'attr_match_list'} } ) {
+            next unless exists $args{ $search_by }
+                && defined $args{ $search_by }
+                && length $args{ $search_by };
+
+            my @fetch =
+                map ref $_? @$_ : $_,
+                grep defined,
+                map $config->{'attr_map'}{ $_ },
+                grep $_ ne $search_by,
+                @{ $config->{'attr_match_list'} };
             my @search_args = (
                 $service,
                 $config->{'attr_map'}{ $search_by },
                 $args{ $search_by },
-                $config->{'attr_map'}{ $search_by },
+                \@fetch,
             );
 
-            my %params;
+            my ($found, %params);
             if($config->{'type'} eq 'ldap') {
                 ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo( @search_args );
             } elsif ($config->{'type'} eq 'db') {
@@ -771,20 +853,19 @@ sub CanonicalizeUserInfo {
             }
             next unless $found;
 
-            @alternatives = grep defined && length && $_ ne $args{ $search_by }, values %params;
-
-            # Don't Check any more services
-            last;
-        }
-        return $rv unless $found;
-
-        # lookup by alternatives
-        foreach my $value ( @alternatives ) {
-            my $rv = $orig->( $self, %args, $search_by => $value );
-            return $rv if $self->id;
+            my %res =
+                map { $_ => $config->{'attr_map'}{ $_ } }
+                grep defined $config->{'attr_map'}{ $_ },
+                grep $_ ne $search_by,
+                @{ $config->{'attr_match_list'} }
+            ;
+            foreach my $value ( values %res ) {
+                $value = ref $value? [ map $params{$_}, @$value ] : [ $params{ $value } ];
+            }
+            return %res;
         }
-        return $rv;
-    };
+    }
+    return;
 }
 
 sub WorkaroundAutoCreate {

commit 20d1b2e392ba81bf152da2dee622d4c7a2730e23
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 14 12:32:54 2011 +0400

    workaround some cache issues in a test

diff --git a/xt/ldap/late-sync.t b/xt/ldap/late-sync.t
index 0673910..b7028d8 100644
--- a/xt/ldap/late-sync.t
+++ b/xt/ldap/late-sync.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test ldap => 1, tests => 43;
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 19;
 my $class = 'RT::Authen::ExternalAuth::Test';
 
 my ($server, $client) = $class->bootstrap_ldap_basics;
@@ -43,12 +43,16 @@ MAIL
         my $user = $first_user = $ticket->CreatorObj;
         is( $user->Name, "$username\@invalid.tld" );
         is( $user->EmailAddress, "$username\@invalid.tld" );
+
+        # make user privileged to stop $m->login failing
+        $user->SetPrivileged(1) unless $user->Privileged;
     }
+    DBIx::SearchBuilder::Record::Cachable->FlushCache;
 
     $class->add_ldap_user_simple( cn => $username );
 
     {
-        ok( $m->login( $username, 'password' ), 'logged in' );
+        ok( custom_login( $username, 'password' ), 'logged in' );
 
         ok( $m->goto_create_ticket( $queue ), "go to create ticket" );
         $m->form_name('TicketCreate');
@@ -68,5 +72,18 @@ MAIL
     }
 }
 
+sub custom_login {
+    my $user = shift || 'root';
+    my $pass = shift || 'password';
+   
+    $m->logout;
+
+    my $url = $m->rt_base_url;
+    $m->get($url . "?user=$user;pass=$pass");
+    return $m->status == 200
+        && $m->content =~ qr/Logout/i
+        && $m->content =~ m{<span>\Q$user\E\@invalid\.tld</span>}i;
+}
+
 $client->unbind();
 $m->get_warnings;

commit 5a11a1c2801a251f95ca6f489a77eacd330ef6ad
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 14 14:00:13 2011 +0400

    drop inline tests

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 4796b7a..10542d1 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -128,7 +128,6 @@ This software is released under version 2 of the GNU
 General Public License. The license is distributed with
 this package in the LICENSE file found in the directory 
 root.
-
 =cut    
 
 use RT::Authen::ExternalAuth::LDAP;

commit 096996ce3b708f84774b7b1a4939c37d631c4f06
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 14 14:01:37 2011 +0400

    delete indent from POD

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 10542d1..d1cce38 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -4,16 +4,16 @@ our $VERSION = '0.10_01';
 
 =head1 NAME
 
-  RT::Authen::ExternalAuth - RT Authentication using External Sources
+RT::Authen::ExternalAuth - RT Authentication using External Sources
 
 =head1 DESCRIPTION
 
-  A complete package for adding external authentication mechanisms
-  to RT. It currently supports LDAP via Net::LDAP and External Database
-  authentication for any database with an installed DBI driver.
+A complete package for adding external authentication mechanisms
+to RT. It currently supports LDAP via Net::LDAP and External Database
+authentication for any database with an installed DBI driver.
 
-  It also allows for authenticating cookie information against an
-  external database through the use of the RT-Authen-CookieAuth extension.
+It also allows for authenticating cookie information against an
+external database through the use of the RT-Authen-CookieAuth extension.
 
 =head1 UPGRADING
 

commit 7ba0ba3049a7e7deffdfd73b9209066b89a5307b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 14 14:25:13 2011 +0400

    update changelog

diff --git a/ChangeLog b/ChangeLog
index 352ccfe..cdb0e0d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+
+    * avoid some user create/update conflicts when module enabled
+      on old system
+    * enable $AutoCreate config option for accounts created through
+      email submission
+    * support mapping user's field in RT to multiple attributes
+      in external source (LDAP/DBI)
+
 0.10_01 2012-02-23  Thomas Sibley
 	* Escape usernames in filter values so special characters don't die
 

commit 1625971e01c9abd67426c7c834540051f1ac5bdd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun May 15 00:14:51 2011 +0400

    add documentation

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index d1cce38..e92ca83 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -113,6 +113,57 @@ Then use the examples provided to prepare your own custom
 configuration which should be added to your site configuration in
 $RTHOME/etc/RT_SiteConfig.pm
 
+=head1 CONFIGURATION
+
+=head2 Generic
+
+=head3 attr_match_list
+
+The list of RT attributes that uniquely identify a user. It's
+recommended to use 'Name' and 'EmailAddress' to save
+encountering problems later. Example:
+
+    'attr_match_list' => [
+        'Name',
+        'EmailAddress', 
+        'RealName',
+        'WorkPhone', 
+    ],
+
+=head3 attr_map
+
+Mapping of RT attributes on to attributes in the external source.
+Example:
+
+    'attr_map' => {
+        'Name'         => 'sAMAccountName',
+        'EmailAddress' => 'mail',
+        'Organization' => 'physicalDeliveryOfficeName',
+        'RealName'     => 'cn',
+        ...
+    },
+
+Since version 0.10 it's possible to map one RT field to multiple
+external attributes, for example:
+
+    attr_map => {
+        EmailAddress => ['mail', 'alias'],
+        ...
+    },
+
+Note that only one value storred in RT. However, search goes by
+all external attributes if such RT field list in L</attr_match_list>.
+On create or update entered value is used as long as it's valid.
+If user didn't enter value then value stored in the first external
+attribute is used. Config example:
+
+    attr_match_list => ['Name', 'EmailAddress'],
+    attr_map => {
+        Name         => 'account',
+        EmailAddress => ['mail', 'alias'],
+        ...
+    },
+
 =head1 AUTHOR
         Mike Peachey
         Jennic Ltd.
@@ -867,6 +918,14 @@ sub FindRecordsByOtherFields {
     return;
 }
 
+=head2 WorkaroundAutoCreate
+
+RT has C<$AutoCreate> option in the config. However, up to RT 4.0.0 this
+option is no used when account created by incomming email. This module
+workarounds this problem.
+
+=cut
+
 sub WorkaroundAutoCreate {
     my $user = shift;
     my $args = shift;

commit bd33ec167be6832224b89033494780c1e4f0e365
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 16 12:24:40 2011 +0400

    update manifest

diff --git a/MANIFEST b/MANIFEST
index 5fe7c31..9935788 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -16,19 +16,24 @@ inc/Module/Install/Makefile.pm
 inc/Module/Install/Metadata.pm
 inc/Module/Install/ReadmeFromPod.pm
 inc/Module/Install/RTx.pm
+inc/Module/Install/Substitute.pm
 inc/Module/Install/Win32.pm
 inc/Module/Install/WriteAll.pm
 lib/RT/Authen/ExternalAuth.pm
 lib/RT/Authen/ExternalAuth/DBI.pm
 lib/RT/Authen/ExternalAuth/DBI/Cookie.pm
 lib/RT/Authen/ExternalAuth/LDAP.pm
+lib/RT/Authen/ExternalAuth/Test.pm
 LICENSE
 Makefile.PL
 MANIFEST			This list of files
 META.yml
 README
-xt/ldap.t
+xt/ldap/basics.t
 xt/ldap_escaping.t
 xt/ldap_group.t
-xt/ldap_privileged.t
+xt/ldap/late-sync.t
+xt/ldap/multiple-emails.t
+xt/ldap/privileged.t
+xt/ldap/user-create.t
 xt/sqlite.t

commit b7dd9ad8186f78a6c3dfd9b6d21dff67b8584a5b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 16 13:05:03 2011 +0400

    update cangelog

diff --git a/ChangeLog b/ChangeLog
index cdb0e0d..bb274e1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,7 +3,9 @@
       on old system
     * enable $AutoCreate config option for accounts created through
       email submission
-    * support mapping user's field in RT to multiple attributes
+    * external info update was only possible by Name, now it
+      works for any field from attr_match_list
+    * support mapping fields in RT to multiple attributes
       in external source (LDAP/DBI)
 
 0.10_01 2012-02-23  Thomas Sibley

commit 6a21a97eba86250337c7f64046ec4a8164db3b44
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 18 01:41:15 2011 +0400

    refactor long grep/map chains

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index e92ca83..e0d9e47 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -800,8 +800,7 @@ sub FindRecordsWithAlternatives {
         my $config = $RT::ExternalSettings->{ $service };
         next if $config->{'type'} eq 'cookie';
         next unless
-            grep ref $_,
-            map $config->{'attr_map'}{ $_ },
+            grep ref $config->{'attr_map'}{ $_ },
             @{ $config->{'attr_match_list'} };
 
         push @info_services, $service;
@@ -877,12 +876,13 @@ sub FindRecordsByOtherFields {
                 && defined $args{ $search_by }
                 && length $args{ $search_by };
 
-            my @fetch =
-                map ref $_? @$_ : $_,
-                grep defined,
-                map $config->{'attr_map'}{ $_ },
-                grep $_ ne $search_by,
-                @{ $config->{'attr_match_list'} };
+            my @fetch;
+            foreach my $field ( @{ $config->{'attr_match_list'} } ) {
+                next if $field eq $search_by;
+
+                my $external = $config->{'attr_map'}{ $field };
+                push @fetch, ref $external? (@$external) : ($external);
+            }
             my @search_args = (
                 $service,
                 $config->{'attr_map'}{ $search_by },

commit 959c776e6f519bef2df5bb106a4a66151304cad3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jun 5 22:09:04 2012 +0400

    update M:I

diff --git a/META.yml b/META.yml
index f11a001..57463eb 100644
--- a/META.yml
+++ b/META.yml
@@ -1,5 +1,5 @@
 ---
-abstract: 'RT Authen-ExternalAuth Extension'
+abstract: 'RT Authentication using External Sources'
 author:
   - 'Mike Peachey'
   - 'Mike Peachey <zordrak at cpan.org>'

commit 411b4e3553c7042728d76b50a5a3b8e3607f8082
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jun 6 14:37:04 2012 +0400

    on cold server record may not have method

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index e0d9e47..319afb3 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -623,7 +623,7 @@ sub CanonicalizeUserInfo {
         my $field = shift;
         return $args->{ $field } if keys %$args;
 
-        return undef unless $UserObj->can( $field );
+        return undef if !$UserObj->can( $field ) && !exists $UserObj->_ClassAccessible->{$field};
         return $UserObj->$field();
     };
 
diff --git a/xt/ldap/late-sync.t b/xt/ldap/late-sync.t
index b7028d8..645eab5 100644
--- a/xt/ldap/late-sync.t
+++ b/xt/ldap/late-sync.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test ldap => 1, tests => 19;
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 20;
 my $class = 'RT::Authen::ExternalAuth::Test';
 
 my ($server, $client) = $class->bootstrap_ldap_basics;
@@ -75,15 +75,14 @@ MAIL
 sub custom_login {
     my $user = shift || 'root';
     my $pass = shift || 'password';
-   
+
     $m->logout;
 
     my $url = $m->rt_base_url;
     $m->get($url . "?user=$user;pass=$pass");
     return $m->status == 200
         && $m->content =~ qr/Logout/i
-        && $m->content =~ m{<span>\Q$user\E\@invalid\.tld</span>}i;
+        && $m->content =~ m{<span class="current-user">\Q$user\E\@invalid\.tld</span>}i;
 }
 
 $client->unbind();
-$m->get_warnings;

commit e1a555b59a873bafb93487d8240a7209cc014b18
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jun 6 14:37:49 2012 +0400

    minor test tweaks to pass on 4.0-trunk

diff --git a/xt/ldap/multiple-emails.t b/xt/ldap/multiple-emails.t
index 5ebe609..290923b 100644
--- a/xt/ldap/multiple-emails.t
+++ b/xt/ldap/multiple-emails.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test ldap => 1, tests => 43;
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 44;
 my $class = 'RT::Authen::ExternalAuth::Test';
 
 my ($server, $client) = $class->bootstrap_ldap_basics;
@@ -115,7 +115,7 @@ MAIL
     }
 
     {
-        ok( $m->login( $username, 'password' ), 'logged in' );
+        ok( $m->login( $username, 'password', logout => 1 ), 'logged in' );
 
         ok( $m->goto_create_ticket( $queue ), "go to create ticket" );
         $m->form_name('TicketCreate');
@@ -158,7 +158,6 @@ MAIL
 }
 
 $client->unbind();
-$m->get_warnings;
 
 sub new_user { return $class->add_ldap_user_simple( alias => '%name at alternative.tld' ) }
 
diff --git a/xt/ldap/privileged.t b/xt/ldap/privileged.t
index d54997a..fc8210d 100644
--- a/xt/ldap/privileged.t
+++ b/xt/ldap/privileged.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test ldap => 1, tests => 20;
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 19;
 my $class = 'RT::Authen::ExternalAuth::Test';
 
 my ($server, $client) = $class->bootstrap_ldap_basics;
@@ -56,10 +56,9 @@ MAIL
         ok($user->Privileged, 'privileged user');
     }
     {
-        ok( $m->login( $username, 'password' ), 'logged in' );
+        ok( $m->login( $username, 'password', logout => 1 ), 'logged in' );
         unlike( $m->uri, qr!SelfService!, 'privileged home page' );
     }
 }
 
 $client->unbind();
-$m->get_warnings;
diff --git a/xt/sqlite.t b/xt/sqlite.t
index 0e04b22..fa4313e 100644
--- a/xt/sqlite.t
+++ b/xt/sqlite.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test dbi => 'SQLite', tests => 19;
+use RT::Authen::ExternalAuth::Test dbi => 'SQLite', tests => 21;
 my $class = 'RT::Authen::ExternalAuth::Test';
 
 my $dir    = File::Temp::tempdir( CLEANUP => 1 );

commit 3d57a3dac5e24c70e544af2237eb9a12d6fc4b6f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jun 6 14:39:00 2012 +0400

    update README

diff --git a/README b/README
index 5ee8a9e..6b2c65a 100644
--- a/README
+++ b/README
@@ -1,13 +1,13 @@
 NAME
-      RT::Authen::ExternalAuth - RT Authentication using External Sources
+    RT::Authen::ExternalAuth - RT Authentication using External Sources
 
 DESCRIPTION
-      A complete package for adding external authentication mechanisms
-      to RT. It currently supports LDAP via Net::LDAP and External Database
-      authentication for any database with an installed DBI driver.
+    A complete package for adding external authentication mechanisms to RT.
+    It currently supports LDAP via Net::LDAP and External Database
+    authentication for any database with an installed DBI driver.
 
-      It also allows for authenticating cookie information against an
-      external database through the use of the RT-Authen-CookieAuth extension.
+    It also allows for authenticating cookie information against an external
+    database through the use of the RT-Authen-CookieAuth extension.
 
 UPGRADING
     If you are upgrading from an earlier version of this extension, you must
@@ -95,6 +95,53 @@ INSTALLATION
     which should be added to your site configuration in
     $RTHOME/etc/RT_SiteConfig.pm
 
+CONFIGURATION
+  Generic
+   attr_match_list
+    The list of RT attributes that uniquely identify a user. It's
+    recommended to use 'Name' and 'EmailAddress' to save encountering
+    problems later. Example:
+
+        'attr_match_list' => [
+            'Name',
+            'EmailAddress', 
+            'RealName',
+            'WorkPhone', 
+        ],
+
+   attr_map
+    Mapping of RT attributes on to attributes in the external source.
+    Example:
+
+        'attr_map' => {
+            'Name'         => 'sAMAccountName',
+            'EmailAddress' => 'mail',
+            'Organization' => 'physicalDeliveryOfficeName',
+            'RealName'     => 'cn',
+            ...
+        },
+
+    Since version 0.10 it's possible to map one RT field to multiple
+    external attributes, for example:
+
+        attr_map => {
+            EmailAddress => ['mail', 'alias'],
+            ...
+        },
+
+    Note that only one value storred in RT. However, search goes by all
+    external attributes if such RT field list in "attr_match_list". On
+    create or update entered value is used as long as it's valid. If user
+    didn't enter value then value stored in the first external attribute is
+    used. Config example:
+
+        attr_match_list => ['Name', 'EmailAddress'],
+        attr_map => {
+            Name         => 'account',
+            EmailAddress => ['mail', 'alias'],
+            ...
+        },
+
 AUTHOR
         Mike Peachey
         Jennic Ltd.
@@ -108,3 +155,8 @@ COPYRIGHT AND LICENCE
     License. The license is distributed with this package in the LICENSE
     file found in the directory root.
 
+  WorkaroundAutoCreate
+    RT has $AutoCreate option in the config. However, up to RT 4.0.0 this
+    option is no used when account created by incomming email. This module
+    workarounds this problem.
+

commit 4270c0fe09410f56a115d71d8ff101ae58b312eb
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue Jun 26 13:01:01 2012 -0400

    Skip LDAP entries in config that have no value

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 319afb3..6c00647 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -179,6 +179,7 @@ This software is released under version 2 of the GNU
 General Public License. The license is distributed with
 this package in the LICENSE file found in the directory 
 root.
+
 =cut    
 
 use RT::Authen::ExternalAuth::LDAP;
@@ -773,6 +774,7 @@ sub CanonicalizeUserInfo {
         my (%other) = FindRecordsByOtherFields( $self, %args );
         while ( my ($search_by, $values) = each %other ) {
             foreach my $value ( @$values ) {
+                next if not defined $value; # Entry in config, but no value in LDAP
                 my $rv = $orig->( $self, $search_by => $value );
                 return $rv if $self->id;
             }
diff --git a/xt/ldap/multiple-emails.t b/xt/ldap/multiple-emails.t
index 290923b..5d762c4 100644
--- a/xt/ldap/multiple-emails.t
+++ b/xt/ldap/multiple-emails.t
@@ -13,7 +13,7 @@ ok($queue->id, "loaded the General queue");
 RT->Config->Set( AutoCreate                  => { Privileged => 1 } );
 
 RT->Config->Get('ExternalSettings')->{'My_LDAP'}{'attr_map'}{'EmailAddress'}
-    = ['mail', 'alias'];
+    = ['mail', 'alias', 'proxyAddresses', 'foo'];
 
 RT::Test->set_rights(
     { Principal => 'Everyone', Right => [qw(SeeQueue ShowTicket CreateTicket)] },

commit a49670bfa14b559070987561d6d1dd2e8682bc7d
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue Jun 26 13:05:57 2012 -0400

    Return search_by as first value from FindRecordsWithAlternatives

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 6c00647..80b0ca1 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -849,7 +849,7 @@ sub FindRecordsWithAlternatives {
         my @alternatives = grep defined && length && $_ ne $args{ $search_by }, values %params;
 
         # Don't Check any more services
-        return @alternatives;
+        return ($search_by, @alternatives);
     }
     return;
 }

commit b3d3ca4285d32a8e29771d81980bb2ac5806472e
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue Jun 26 15:30:41 2012 -0400

    Add option to prepend values on LDAP search filter
    
    Customer AD configurations contain entries in the form:
    
        proxyAddresses: smtp:email at test.com
    
    where AD puts the 'smtp:' prefix on the email address. This commit
    adds a configuration option to pass in a value to be prepended
    to the incoming email address in the LDAP filter. This allows
    alternate email addresses listed in attributes like proxyAddresses
    to match.
    
    It sets smtp: as a default for proxyAddresses since it seems to be
    the common case for AD.

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 80b0ca1..f9c725d 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -151,10 +151,10 @@ external attributes, for example:
         ...
     },
 
-Note that only one value storred in RT. However, search goes by
-all external attributes if such RT field list in L</attr_match_list>.
-On create or update entered value is used as long as it's valid.
-If user didn't enter value then value stored in the first external
+Note that only one value is stored in RT. However, the search includes
+all external attributes if the RT field is listed in L</attr_match_list>.
+On create or update, the entered value is used as long as it's valid.
+If the user didn't enter a value then the value stored in the first external
 attribute is used. Config example:
 
     attr_match_list => ['Name', 'EmailAddress'],
@@ -164,6 +164,25 @@ attribute is used. Config example:
         ...
     },
 
+=head3 attr_prefix
+
+In some cases, multiple-value LDAP attributes may have a prefix on the values
+in the LDAP entry. The C<attr_prefix> allows you to set values to prepend to
+filter terms before searching. For example, if you want an email address to
+match 'alias' in an LDAP entry that prepends 'other:' to the email
+address, you can do the following:
+
+    attr_prefix => {
+        alias => ['other:'],
+        ...
+    },
+
+The key must be included in the C<attr_map> or it won't be checked.
+To search on an attribute with no value prepended in addition to
+some prepended values, include an empty string C<''> in the list.
+The most common case for this is the proxyAddresses needing the
+value 'smtp:' prepended, so that happens automatically.
+
 =head1 AUTHOR
         Mike Peachey
         Jennic Ltd.
diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 0bb5917..c0b6651 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -135,9 +135,28 @@ sub CanonicalizeUserInfo {
     # Load the config
     my $config = $RT::ExternalSettings->{$service};
 
+    # Default smtp: as the most common case
+    my %filter_prefix = (
+                        proxyAddresses => [ 'smtp:'],
+                        %{$config->{'attr_prefix'}}
+                        );
+
+    # Build the LDAP filters
+    my @filter_list;
+    foreach my $filter_key ( ref $key ? @$key : ($key) ){
+        push @filter_list, "($filter_key=" . escape_filter_value( $value ) . ")";
+
+        # Prepend prefixes for AD
+        if( exists $filter_prefix{$filter_key} ){
+            foreach my $prefix ( @{ $filter_prefix{$filter_key} } ){
+                push @filter_list, "($filter_key=" . escape_filter_value( $prefix . $value ) . ")";
+           }
+        }
+    }
+
     my $filter = JoinFilters(
         '&',
-        JoinFilters('|', map "($_=". escape_filter_value( $value ) .")", ref $key? @$key: ($key) ),
+        JoinFilters('|', @filter_list ),
         $config->{'filter'},
     ) or return (0);
 
diff --git a/xt/ldap/multiple-emails.t b/xt/ldap/multiple-emails.t
index 5d762c4..495f6e8 100644
--- a/xt/ldap/multiple-emails.t
+++ b/xt/ldap/multiple-emails.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Authen::ExternalAuth::Test ldap => 1, tests => 44;
+use RT::Authen::ExternalAuth::Test ldap => 1, tests => 50;
 my $class = 'RT::Authen::ExternalAuth::Test';
 
 my ($server, $client) = $class->bootstrap_ldap_basics;
@@ -15,6 +15,9 @@ RT->Config->Set( AutoCreate                  => { Privileged => 1 } );
 RT->Config->Get('ExternalSettings')->{'My_LDAP'}{'attr_map'}{'EmailAddress'}
     = ['mail', 'alias', 'proxyAddresses', 'foo'];
 
+RT->Config->Get('ExternalSettings')->{'My_LDAP'}{'attr_prefix'}{'proxyAddresses'}
+    = [ 'smtp:', 'SMTP:', 'X400:', '' ];
+
 RT::Test->set_rights(
     { Principal => 'Everyone', Right => [qw(SeeQueue ShowTicket CreateTicket)] },
 );
@@ -44,6 +47,7 @@ diag "login then send emails from different addresses";
         is( $user->EmailAddress, "$username\@invalid.tld" );
     }
 
+    diag "Send first email from initial address.";
     {
         my $mail = << "MAIL";
 Subject: Test
@@ -66,6 +70,7 @@ MAIL
         is( $user->EmailAddress, "$username\@invalid.tld" );
     }
 
+    diag "Send email from alias address.";
     {
         my $mail = << "MAIL";
 Subject: Test
@@ -87,6 +92,31 @@ MAIL
         is( $user->Name, $username );
         is( $user->EmailAddress, "$username\@invalid.tld" );
     }
+
+    diag "Send email from smtp: address.";
+    my $smtp = substr($username, 1);
+    {
+        my $mail = << "MAIL";
+Subject: Test
+From: $smtp\@alternative.tld
+
+test
+MAIL
+
+        my ($status, $id) = RT::Test->send_via_mailgate($mail);
+        is ($status >> 8, 0, "The mail gateway exited normally");
+        ok ($id, "got id of a newly created ticket - $id");
+
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok ($ticket->id, 'loaded ticket');
+
+        my $user = $ticket->CreatorObj;
+        is( $user->id, $first_user->id );
+        is( $user->Name, $username );
+        is( $user->EmailAddress, "$username\@invalid.tld", "Got original email address with smtp email." );
+    }
+
 }
 
 diag "send a mail from alternative address, then try other credentials";
@@ -159,5 +189,6 @@ MAIL
 
 $client->unbind();
 
-sub new_user { return $class->add_ldap_user_simple( alias => '%name at alternative.tld' ) }
+sub new_user { return $class->add_ldap_user_simple( alias => '%name at alternative.tld',
+						    add_proxy_addresses => 'alternative.tld') }
 

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list