Externalauth changed to accommodate local fix.
[usit-rt.git] / local / plugins / RT-Authen-ExternalAuth / lib / RT / Authen / ExternalAuth.pm
index 5877af7..fa7609f 100644 (file)
@@ -4,67 +4,22 @@ our $VERSION = '0.09';
 
 =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 CONFIGURATION
+=begin testing
 
-=head2 Generic
+ok(require RT::Authen::ExternalAuth);
 
-=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'],
-        ...
-    },
+=end testing
 
 =cut    
 
@@ -324,8 +279,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;
-    $UserObj->CanonicalizeUserInfo( \%args );
+    my %args = (Name => $username);
+    $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
@@ -482,19 +437,12 @@ sub CanonicalizeUserInfo {
     
     my $UserObj = shift;
     my $args    = shift;
-
-    WorkaroundAutoCreate( $UserObj, $args );
-
-    my $current_value = sub {
-        my $field = shift;
-        return $args->{ $field } if keys %$args;
-
-        return undef unless $UserObj->can( $field );
-        return $UserObj->$field();
-    };
-
-    my ($found, $config, %params) = (0);
-
+    
+    my $found   = 0;
+    my %params  = (Name         => undef,
+                  EmailAddress => undef,
+                  RealName     => undef);
+    
     $RT::Logger->debug( (caller(0))[3], 
                         "called by", 
                         caller, 
@@ -503,7 +451,7 @@ sub CanonicalizeUserInfo {
                             sort(keys(%$args))));
 
     # Get the list of defined external services
-    my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
+    my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : undef;
     # For each external service...
     foreach my $service (@info_services) {
         
@@ -511,50 +459,54 @@ sub CanonicalizeUserInfo {
                             $service);
         
         # Get the config for the service so that we know what attrs we can canonicalize
-        $config = $RT::ExternalSettings->{$service};
-
+        my $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
+        my $attr_map = (defined $config->{'lookup_attr_map'}) ? $config->{'lookup_attr_map'} : $config->{'attr_map'};
         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 = $current_value->( $rt_attr );
-            unless( defined $value && length $value ) {
+            unless(defined($args->{$rt_attr})) {
                 $RT::Logger->debug("This attribute (",
                                     $rt_attr,
-                                    ") is null or incorrectly defined in the attr_match_list for this service (",
+                                    ") is null or incorrectly defined in the attr_map for this service (",
                                     $service,
                                     ")");
                 next;
             }
                                
             # Else, use it as a canonicalization key and lookup the user info    
-            my $key = $config->{'attr_map'}->{$rt_attr};
-            unless ( $key ) {
-                $RT::Logger->warning(
-                    "No mapping for $rt_attr in attr_map for this service ($service)"
-                );
+            my $key = $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);
+            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,
+                                    ")");
                 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.
             
             if($config->{'type'} eq 'ldap'){    
-                ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value, \@service_attrs);
+                ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value);
             } elsif ($config->{'type'} eq 'db') {
-                ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo($service,$key,$value, \@service_attrs);
+                ($found, %params) = RT::Authen::ExternalAuth::DBI::CanonicalizeUserInfo($service,$key,$value);
             } else {
                 $RT::Logger->debug( (caller(0))[3],
                                     "does not consider",
@@ -568,48 +520,29 @@ sub CanonicalizeUserInfo {
         # Don't Check any more services
         last if $found;
     }
-
-    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'} } ) {
-        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 {
-            unless ( grep defined && length && $_ eq $current, map $params{ $_ }, @$v ) {
-                $res{ $k } = (grep defined && length, map $params{ $_ }, @$v)[0];
-            }
-        }
-    }
-
-    # It's important that we always have a canonical email address
-    if ($res{'EmailAddress'}) {
-        $res{'EmailAddress'} = $UserObj->CanonicalizeEmailAddress($res{'EmailAddress'});
-    } 
-
+    
+    # If found, Canonicalize Email Address and 
     # update the args hash that we were given the hashref for
-    %$args = (%$args, %res);
+    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);
+    }
 
     $RT::Logger->info(  (caller(0))[3], 
                         "returning", 
                         join(", ", map {sprintf("%s: %s", $_, $args->{$_})} 
                             sort(keys(%$args))));
 
-    return $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($found || $RT::AutoCreateNonExternalUsers);
+   
 }
 
 {
@@ -621,191 +554,4 @@ 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 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;
-        }
-
-        return $rv;
-    };
-}
-
-sub FindRecordsWithAlternatives {
-    my $user = shift;
-    my %args = @_;
-
-    # 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 $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;
-        }
-        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;
-            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 },
-                $args{ $search_by },
-                \@fetch,
-            );
-
-            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 %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;
-}
-
-=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;
-
-    # 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;