[Bps-public-commit] rt-authen-externalauth branch, master, updated. 0.23-17-gbad4dac

Alex Vandiver alexmv at bestpractical.com
Mon Sep 15 15:15:10 EDT 2014


The branch, master has been updated
       via  bad4dac8c9c68886414106747cde46d38e287307 (commit)
       via  5eb5a98041da31d9bd9fe9e1fd024b4650b70d24 (commit)
       via  0b7072db03a8c8de7f78542f3612e54b2c1397ae (commit)
       via  320e49cefb084af28f8c136106ef9b3cec851526 (commit)
       via  34c8860c3814ab951d4bbe09d9a34cc32c1f9ba2 (commit)
       via  eeb05b8f2c216e2362a012db89f518b739c27b1b (commit)
       via  694ba2eae5710e2bd6d9abfb557bb5bae8bc32b0 (commit)
       via  9882340d31aea347ecb981e165f1418530be435a (commit)
       via  45ef4945f9154c4de6ef6cd0a62aa83ac46fc093 (commit)
       via  cc10eab24225c0faf8d6bf92564fa841717deda1 (commit)
       via  5c269d9df9335f89eaea5ba2730cd1e83b6aa8c5 (commit)
       via  8ac8af83b5ac5dd771fe8925017606d60bce3475 (commit)
       via  ccb964b56cd0f3cdf7a2dd9d089d84f4f45d2a68 (commit)
       via  d65d526935a616fb31eaf057d413d3812a1c35b0 (commit)
       via  2960d9099ed6d20bd1954dc12bb10c44e67cb464 (commit)
       via  af180f0db2035db7d1353ca83d4a6055a2d35822 (commit)
      from  e1e72e500a43eb6381668494a3f95681fcf25d72 (commit)

Summary of changes:
 README                             |    2 +-
 html/Elements/DoAuth               |   37 +-
 lib/RT/Authen/ExternalAuth.pm      |  403 +++++------
 lib/RT/Authen/ExternalAuth/DBI.pm  | 1166 ++++++++++++++++----------------
 lib/RT/Authen/ExternalAuth/LDAP.pm | 1286 ++++++++++++++++++------------------
 5 files changed, 1453 insertions(+), 1441 deletions(-)

- Log -----------------------------------------------------------------
commit af180f0db2035db7d1353ca83d4a6055a2d35822
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 17:03:04 2014 -0400

    Convert to unix line-endings

diff --git a/lib/RT/Authen/ExternalAuth/DBI.pm b/lib/RT/Authen/ExternalAuth/DBI.pm
index 235c3a4..f6f5c4d 100644
--- a/lib/RT/Authen/ExternalAuth/DBI.pm
+++ b/lib/RT/Authen/ExternalAuth/DBI.pm
@@ -1,110 +1,110 @@
-package RT::Authen::ExternalAuth::DBI;
-
-use DBI;
-use RT::Authen::ExternalAuth::DBI::Cookie;
-
-use strict;
-
-=head1 NAME
-
-RT::Authen::ExternalAuth::DBI - External database source for RT authentication
-
-=head1 DESCRIPTION
-
-Provides the database implementation for L<RT::Authen::ExternalAuth>.
-
-=head1 SYNOPSIS
-
-    Set($ExternalSettings, {
-        'My_MySQL'   =>  {
-            'type'                      =>  'db',
-
-            'dbi_driver'                =>  'DBI_DRIVER',
-
-            'server'                    =>  'server.domain.tld',
-            'port'                      =>  'DB_PORT',
-            'user'                      =>  'DB_USER',
-            'pass'                      =>  'DB_PASS',
-
-            'database'                  =>  'DB_NAME',
-            'table'                     =>  'USERS_TABLE',
-            'u_field'                   =>  'username',
-            'p_field'                   =>  'password',
-
+package RT::Authen::ExternalAuth::DBI;
+
+use DBI;
+use RT::Authen::ExternalAuth::DBI::Cookie;
+
+use strict;
+
+=head1 NAME
+
+RT::Authen::ExternalAuth::DBI - External database source for RT authentication
+
+=head1 DESCRIPTION
+
+Provides the database implementation for L<RT::Authen::ExternalAuth>.
+
+=head1 SYNOPSIS
+
+    Set($ExternalSettings, {
+        'My_MySQL'   =>  {
+            'type'                      =>  'db',
+
+            'dbi_driver'                =>  'DBI_DRIVER',
+
+            'server'                    =>  'server.domain.tld',
+            'port'                      =>  'DB_PORT',
+            'user'                      =>  'DB_USER',
+            'pass'                      =>  'DB_PASS',
+
+            'database'                  =>  'DB_NAME',
+            'table'                     =>  'USERS_TABLE',
+            'u_field'                   =>  'username',
+            'p_field'                   =>  'password',
+
             # Example of custom hashed password check
             #'p_check'                   =>  sub {
             #    my ($hash_from_db, $password) = @_;
             #    return $hash_from_db eq function($password);
             #},
 
-            'p_enc_pkg'                 =>  'Crypt::MySQL',
-            'p_enc_sub'                 =>  'password',
-            'p_salt'                    =>  'SALT',
-
-            'd_field'                   =>  'disabled',
-            'd_values'                  =>  ['0'],
-
-            'attr_match_list' =>  [
-                'Gecos',
-                'Name',
-            ],
-            'attr_map' => {
-                'Name'           => 'username',
-                'EmailAddress'   => 'email',
-                'ExternalAuthId' => 'username',
-                'Gecos'          => 'userID',
-            },
-        },
-    } );
-
-=head1 CONFIGURATION
-
-DBI-specific options are described here. Shared options
-are described in the F<etc/RT_SiteConfig.pm> file included
-in this distribution.
-
-The example in the L</SYNOPSIS> lists all available options
-and they are described below. See the L<DBI> module for details
-on debugging connection issues.
-
-=over 4
-
-=item dbi_driver
-
-The name of the Perl DBI driver to use (e.g. mysql, Pg, SQLite).
-
-=item server
-
-The server hosting the database.
-
-=item port
-
-The port to use to connect on (e.g. 3306).
-
-=item user
-
-The database user for the connection.
-
-=item pass
-
-The password for the database user.
-
-=item database
-
-The database name.
-
-=item table
-
-The database table containing the user information to check against.
-
-=item u_field
-
-The field in the table that holds usernames
-
-=item p_field
-
-The field in the table that holds passwords
-
+            'p_enc_pkg'                 =>  'Crypt::MySQL',
+            'p_enc_sub'                 =>  'password',
+            'p_salt'                    =>  'SALT',
+
+            'd_field'                   =>  'disabled',
+            'd_values'                  =>  ['0'],
+
+            'attr_match_list' =>  [
+                'Gecos',
+                'Name',
+            ],
+            'attr_map' => {
+                'Name'           => 'username',
+                'EmailAddress'   => 'email',
+                'ExternalAuthId' => 'username',
+                'Gecos'          => 'userID',
+            },
+        },
+    } );
+
+=head1 CONFIGURATION
+
+DBI-specific options are described here. Shared options
+are described in the F<etc/RT_SiteConfig.pm> file included
+in this distribution.
+
+The example in the L</SYNOPSIS> lists all available options
+and they are described below. See the L<DBI> module for details
+on debugging connection issues.
+
+=over 4
+
+=item dbi_driver
+
+The name of the Perl DBI driver to use (e.g. mysql, Pg, SQLite).
+
+=item server
+
+The server hosting the database.
+
+=item port
+
+The port to use to connect on (e.g. 3306).
+
+=item user
+
+The database user for the connection.
+
+=item pass
+
+The password for the database user.
+
+=item database
+
+The database name.
+
+=item table
+
+The database table containing the user information to check against.
+
+=item u_field
+
+The field in the table that holds usernames
+
+=item p_field
+
+The field in the table that holds passwords
+
 =item p_check
 
 Optional.  An anonymous subroutine definition used to check the (presumably
@@ -123,85 +123,85 @@ An example, where C<FooBar()> is some external hashing function:
 Importantly, the C<p_check> subroutine allows for arbitrarily complex password
 checking unlike C<p_enc_pkg> and C<p_enc_sub>.
 
-=item p_enc_pkg, p_enc_sub
-
-The Perl package and subroutine used to encrypt passwords from the
-database. For example, if the passwords are stored using the MySQL
-v3.23 "PASSWORD" function, then you will need the L<Crypt::MySQL>
-C<password> function, but for the MySQL4+ password you will need
-L<Crypt::MySQL>'s C<password41>. Alternatively, you could use
-L<Digest::MD5> C<md5_hex> or any other encryption subroutine you can
-load in your Perl installation.
-
-=item p_salt
-
-If p_enc_sub takes a salt as a second parameter then set it here.
-
-=item d_field, d_values
-
-The field and values in the table that determines if a user should
-be disabled. For example, if the field is 'user_status' and the values
-are ['0','1','2','disabled'] then the user will be disabled if their
-user_status is set to '0','1','2' or the string 'disabled'.
-Otherwise, they will be considered enabled.
-
-=back
-
-=cut
-
-sub GetAuth {
-
-    my ($service, $username, $password) = @_;
-    
-    my $config = $RT::ExternalSettings->{$service};
-    $RT::Logger->debug( "Trying external auth service:",$service);
-
-    my $db_table        = $config->{'table'};
-    my $db_u_field      = $config->{'u_field'};
-    my $db_p_field 	    = $config->{'p_field'};
-    my $db_p_check      = $config->{'p_check'};
-    my $db_p_enc_pkg    = $config->{'p_enc_pkg'};
-    my $db_p_enc_sub    = $config->{'p_enc_sub'};
-    my $db_p_salt       = $config->{'p_salt'};
-
-    # Set SQL query and bind parameters
-    my $query = "SELECT $db_u_field,$db_p_field FROM $db_table WHERE $db_u_field=?";
-    my @params = ($username);
-    
-    # Uncomment this to trace basic DBI information and drop it in a log for debugging
-    # DBI->trace(1,'/tmp/dbi.log');
-
-    # Get DBI handle object (DBH), do SQL query, kill DBH
-    my $dbh = _GetBoundDBIObj($config);
-    return 0 unless $dbh;
-    
-    my $results_hashref = $dbh->selectall_hashref($query,$db_u_field,{}, at params);
-    $dbh->disconnect();
-
-    my $num_users_returned = scalar keys %$results_hashref;
-    if($num_users_returned != 1) { # FAIL
-        # FAIL because more than one user returned. Users MUST be unique! 
-        if ((scalar keys %$results_hashref) > 1) {
-            $RT::Logger->info(  $service,
-                                "AUTH FAILED",
-                                $username,
-                                "More than one user with that username!");
-        }
-
-        # FAIL because no users returned. Users MUST exist! 
-        if ((scalar keys %$results_hashref) < 1) {
-            $RT::Logger->info(  $service,
-                                "AUTH FAILED",
-                                $username,
-                                "User not found in database!");
-        }
-
-	    # Drop out to next external authentication service
-	    return 0;
-    }
-    
-    # Get the user's password from the database query result
-    my $pass_from_db = $results_hashref->{$username}->{$db_p_field};        
+=item p_enc_pkg, p_enc_sub
+
+The Perl package and subroutine used to encrypt passwords from the
+database. For example, if the passwords are stored using the MySQL
+v3.23 "PASSWORD" function, then you will need the L<Crypt::MySQL>
+C<password> function, but for the MySQL4+ password you will need
+L<Crypt::MySQL>'s C<password41>. Alternatively, you could use
+L<Digest::MD5> C<md5_hex> or any other encryption subroutine you can
+load in your Perl installation.
+
+=item p_salt
+
+If p_enc_sub takes a salt as a second parameter then set it here.
+
+=item d_field, d_values
+
+The field and values in the table that determines if a user should
+be disabled. For example, if the field is 'user_status' and the values
+are ['0','1','2','disabled'] then the user will be disabled if their
+user_status is set to '0','1','2' or the string 'disabled'.
+Otherwise, they will be considered enabled.
+
+=back
+
+=cut
+
+sub GetAuth {
+
+    my ($service, $username, $password) = @_;
+    
+    my $config = $RT::ExternalSettings->{$service};
+    $RT::Logger->debug( "Trying external auth service:",$service);
+
+    my $db_table        = $config->{'table'};
+    my $db_u_field      = $config->{'u_field'};
+    my $db_p_field 	    = $config->{'p_field'};
+    my $db_p_check      = $config->{'p_check'};
+    my $db_p_enc_pkg    = $config->{'p_enc_pkg'};
+    my $db_p_enc_sub    = $config->{'p_enc_sub'};
+    my $db_p_salt       = $config->{'p_salt'};
+
+    # Set SQL query and bind parameters
+    my $query = "SELECT $db_u_field,$db_p_field FROM $db_table WHERE $db_u_field=?";
+    my @params = ($username);
+    
+    # Uncomment this to trace basic DBI information and drop it in a log for debugging
+    # DBI->trace(1,'/tmp/dbi.log');
+
+    # Get DBI handle object (DBH), do SQL query, kill DBH
+    my $dbh = _GetBoundDBIObj($config);
+    return 0 unless $dbh;
+    
+    my $results_hashref = $dbh->selectall_hashref($query,$db_u_field,{}, at params);
+    $dbh->disconnect();
+
+    my $num_users_returned = scalar keys %$results_hashref;
+    if($num_users_returned != 1) { # FAIL
+        # FAIL because more than one user returned. Users MUST be unique! 
+        if ((scalar keys %$results_hashref) > 1) {
+            $RT::Logger->info(  $service,
+                                "AUTH FAILED",
+                                $username,
+                                "More than one user with that username!");
+        }
+
+        # FAIL because no users returned. Users MUST exist! 
+        if ((scalar keys %$results_hashref) < 1) {
+            $RT::Logger->info(  $service,
+                                "AUTH FAILED",
+                                $username,
+                                "User not found in database!");
+        }
+
+	    # Drop out to next external authentication service
+	    return 0;
+    }
+    
+    # Get the user's password from the database query result
+    my $pass_from_db = $results_hashref->{$username}->{$db_p_field};        
 
     if ( $db_p_check ) {
         unless ( ref $db_p_check eq 'CODE' ) {
@@ -222,409 +222,409 @@ sub GetAuth {
                 "$service AUTH FAILED for $username: Password Incorrect (via p_check)"
             );
         } else {
-            $RT::Logger->info(  (caller(0))[3], 
-                                "External Auth OK (",
-                                $service,
-                                "):", 
-                                $username);
+            $RT::Logger->info(  (caller(0))[3], 
+                                "External Auth OK (",
+                                $service,
+                                "):", 
+                                $username);
         }
         return $check;
     }
-
-    # This is the encryption package & subroutine passed in by the config file
-    $RT::Logger->debug( "Encryption Package:",
-                        $db_p_enc_pkg);
-    $RT::Logger->debug( "Encryption Subroutine:",
-                        $db_p_enc_sub);
-
-    # Use config info to auto-load the perl package needed for password encryption
-    # I know it uses a string eval - but I don't think there's a better way to do this
-    # Jump to next external authentication service on failure
-    eval "require $db_p_enc_pkg" or 
-        $RT::Logger->error("AUTH FAILED, Couldn't Load Password Encryption Package. Error: $@") && return 0;
-    
-    my $encrypt = $db_p_enc_pkg->can($db_p_enc_sub);
-    if (defined($encrypt)) {
-        # If the package given can perform the subroutine given, then use it to compare the
-        # password given with the password pulled from the database.
-        # Jump to the next external authentication service if they don't match
-        if(defined($db_p_salt)) {
-            $RT::Logger->debug("Using salt:",$db_p_salt);
-            if(${encrypt}->($password,$db_p_salt) ne $pass_from_db){
-                $RT::Logger->info(  $service,
-                                    "AUTH FAILED", 
-                                    $username, 
-                                    "Password Incorrect");
-                return 0;
-            }
-        } else {
-            if(${encrypt}->($password) ne $pass_from_db){
-                $RT::Logger->info(  $service,
-                                    "AUTH FAILED", 
-                                    $username, 
-                                    "Password Incorrect");
-                return 0;
-            }
-        }
-    } else {
-        # If the encryption package can't perform the request subroutine,
-        # dump an error and jump to the next external authentication service.
-        $RT::Logger->error($service,
-                            "AUTH FAILED",
-                            "The encryption package you gave me (",
-                            $db_p_enc_pkg,
-                            ") does not support the encryption method you specified (",
-                            $db_p_enc_sub,
-                            ")");
-            return 0;
-    }
-    
-    # Any other checks you want to add? Add them here.
-
-    # If we've survived to this point, we're good.
-    $RT::Logger->info(  (caller(0))[3], 
-                        "External Auth OK (",
-                        $service,
-                        "):", 
-                        $username);
-    
-    return 1;   
-}
-
-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};
-    
-    # Figure out what's what
-    my $table      = $config->{'table'};
-
-    unless ($table) {
-        $RT::Logger->critical(  (caller(0))[3],
-                                "No table given");
-        # Drop out to the next external information service
-        return ($found, %params);
-    }
-
-    unless ($key && $value){
-        $RT::Logger->critical(  (caller(0))[3],
-                                " Nothing to look-up given");
-        # Drop out to the next external information service
-        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);
-
-    # 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);
-    $dbh->disconnect();
-
-    if ((scalar keys %$results_hashref) != 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 no user...
-	        $death_msg = "No User Found in External Database!";
-        } else {
-            # If more than one user...
-            $death_msg = "More than one user found in External Database with that unique identifier!";
-        }
-
-        # Log the death
-        $RT::Logger->info(  (caller(0))[3],
-                            "INFO CHECK FAILED",
-                            "Key: $key",
-                            "Value: $value",
-                            $death_msg);
-        
-        # $found remains as 0
-        
-        # Drop out to next external information service
-        return ($found, %params);
-    }
-
-    # 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;
-  
-    return ($found, %params);
-}
-
-sub UserExists {
-    
-    my ($username,$service) = @_;
-    my $config              = $RT::ExternalSettings->{$service};
-    my $table    	        = $config->{'table'};
-    my $u_field	            = $config->{'u_field'};
-    my $query               = "SELECT $u_field FROM $table WHERE $u_field=?";
-    my @bind_params         = ($username);
-
-    # Uncomment this to do a basic trace on DBI information and log it
-    # DBI->trace(1,'/tmp/dbi.log');
-    
-    # Get DBI Object, do the query, disconnect
-    my $dbh = _GetBoundDBIObj($config);
-    my $results_hashref = $dbh->selectall_hashref($query,$u_field,{}, at bind_params);
-    $dbh->disconnect();
-
-    my $num_of_results = scalar keys %$results_hashref;
-        
-    if ($num_of_results > 1) { 
-        # If more than one result returned, die because we the username field should be unique!
-        $RT::Logger->debug( "Disable Check Failed :: (",
-                            $service,
-                            ")",
-                            $username,
-                            "More than one user with that username!");
-        return 0;
-    } elsif ($num_of_results < 1) { 
-        # If 0 or negative integer, no user found or major failure
-        $RT::Logger->debug( "Disable Check Failed :: (",
-                            $service,
-                            ")",
-                            $username,
-                            "User not found");   
-        return 0; 
-    }
-    
-    # Number of results is exactly one, so we found the user we were looking for
-    return 1;            
-}
-
-sub UserDisabled {
-
-    my ($username,$service) = @_;
-    
-    # FIRST, check that the user exists in the DBI service
-    unless(UserExists($username,$service)) {
-        $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking");
-        return 0;
-    }
-    
-    # Get the necessary config info
-    my $config              = $RT::ExternalSettings->{$service};
-    my $table    	        = $config->{'table'};
-    my $u_field	            = $config->{'u_field'};
-    my $disable_field       = $config->{'d_field'};
-    my $disable_values_list = $config->{'d_values'};
-
-    unless ($disable_field) {
-        # If we don't know how to check for disabled users, consider them all enabled.
-        $RT::Logger->debug("No d_field specified for this DBI service (",
-                            $service,
-                            "), so considering all users enabled");
-        return 0;
-    } 
-    
-    my $query = "SELECT $u_field,$disable_field FROM $table WHERE $u_field=?";
-    my @bind_params = ($username);
-
-    # Uncomment this to do a basic trace on DBI information and log it
-    # DBI->trace(1,'/tmp/dbi.log');
-    
-    # Get DBI Object, do the query, disconnect
-    my $dbh = _GetBoundDBIObj($config);
-    my $results_hashref = $dbh->selectall_hashref($query,$u_field,{}, at bind_params);
-    $dbh->disconnect();
-
-    my $num_of_results = scalar keys %$results_hashref;
-        
-    if ($num_of_results > 1) { 
-        # If more than one result returned, die because we the username field should be unique!
-        $RT::Logger->debug( "Disable Check Failed :: (",
-                            $service,
-                            ")",
-                            $username,
-                            "More than one user with that username! - Assuming not disabled");
-        # Drop out to next service for an info check
-        return 0;
-    } elsif ($num_of_results < 1) { 
-        # If 0 or negative integer, no user found or major failure
-        $RT::Logger->debug( "Disable Check Failed :: (",
-                            $service,
-                            ")",
-                            $username,
-                            "User not found - Assuming not disabled");   
-        # Drop out to next service for an info check
-        return 0;             
-    } else { 
-        # otherwise all should be well
-        
-        # $user_db_disable_value = The value for "disabled" returned from the DB
-        my $user_db_disable_value = $results_hashref->{$username}->{$disable_field};
-        
-        # For each of the values in the (list of values that we consider to mean the user is disabled)..
-        foreach my $disable_value (@{$disable_values_list}){
-            $RT::Logger->debug( "DB Disable Check:", 
-                                "User's Val is $user_db_disable_value,",
-                                "Checking against: $disable_value");
-            
-            # If the value from the DB matches a value from the list, the user is disabled.
-            if ($user_db_disable_value eq $disable_value) {
-                return 1;
-            }
-        }
-        
-        # If we've not returned yet, the user can't be disabled
-        return 0;
-    }
-    $RT::Logger->crit("It is seriously not possible to run this code.. what the hell did you do?!");
-    return 0;
-}
-
-sub GetCookieAuth {
-
-    $RT::Logger->debug( (caller(0))[3],
-	                "Checking Browser Cookies for an Authenticated User");
-			     
-    # Get our cookie and database info...
-    my $config = shift;
-
-    my $username = undef;
-    my $cookie_name = $config->{'name'};
-
-    my $cookie_value = RT::Authen::ExternalAuth::DBI::Cookie::GetCookieVal($cookie_name);
-
-    unless($cookie_value){
-        return $username;
-    }
-
-    # The table mapping usernames to the Username Match Key
-    my $u_table     = $config->{'u_table'};
-    # The username field in that table
-    my $u_field     = $config->{'u_field'};
-    # The field that contains the Username Match Key
-    my $u_match_key = $config->{'u_match_key'};
-
-    # The table mapping cookie values to the Cookie Match Key
-    my $c_table     = $config->{'c_table'};
-    # The cookie field in that table - The same as the cookie name if unspecified
-    my $c_field     = $config->{'c_field'};
-    # The field that connects the Cookie Match Key
-    my $c_match_key = $config->{'c_match_key'};
-
-    # These are random characters to assign as table aliases in SQL
-    # It saves a lot of garbled code later on
-    my $u_table_alias = "u";
-    my $c_table_alias = "c";
-
-    # $tables will be passed straight into the SQL query
-    # I don't see this as a security issue as only the admin may modify the config file anyway
-    my $tables;
-
-    # If the tables are the same, then the aliases should be the same
-    # and the match key becomes irrelevant. Ensure this all works out
-    # fine by setting both sides the same. In either case, set an
-    # appropriate value for $tables.
-    if ($u_table eq $c_table) {
-            $u_table_alias  = $c_table_alias;
-            $u_match_key    = $c_match_key;
-            $tables         = "$c_table $c_table_alias";
-    } else {
-            $tables = "$c_table $c_table_alias, $u_table $u_table_alias";
-    }
-
-    my $select_fields = "$u_table_alias.$u_field";
-    my $where_statement = "$c_table_alias.$c_field = ? AND $c_table_alias.$c_match_key = $u_table_alias.$u_match_key";
-
-    my $query = "SELECT $select_fields FROM $tables WHERE $where_statement";
-    my @params = ($cookie_value);
-
-    # Use this if you need to debug the DBI SQL process
-    # DBI->trace(1,'/tmp/dbi.log');
-
-    my $dbh = _GetBoundDBIObj($RT::ExternalSettings->{$config->{'db_service_name'}});
-    my $query_result_arrayref = $dbh->selectall_arrayref($query,{}, at params);
-    $dbh->disconnect();
-
-    # The log messages say it all here...
-    my $num_rows = scalar @$query_result_arrayref;
-    if ($num_rows < 1) {
-        $RT::Logger->info(  "AUTH FAILED",
-                            $cookie_name,
-                            "Cookie value not found in database.",
-                            "User passed an authentication token they were not given by us!",
-                            "Is this nefarious activity?");
-    } elsif ($num_rows > 1) {
-        $RT::Logger->error( "AUTH FAILED",
-                            $cookie_name,
-                            "Cookie's value is duplicated in the database! This should not happen!!");
-    } else {
-        $username = $query_result_arrayref->[0][0];
-    }
-
-    if ($username) {
-        $RT::Logger->debug( "User (",
-                            $username,
-                            ") was authenticated by a browser cookie");
-    } else {
-        $RT::Logger->debug( "No user was authenticated by browser cookie");
-    }
-
-    return $username;
-
-}
-
-
-# {{{ sub _GetBoundDBIObj
-
-sub _GetBoundDBIObj {
-    
-    # Config as hashref. 
-    my $config = shift;
-
-    # Extract the relevant information from the config.
-    my $db_server     = $config->{'server'};
-    my $db_user       = $config->{'user'};
-    my $db_pass       = $config->{'pass'};
-    my $db_database   = $config->{'database'};
-    my $db_port       = $config->{'port'};
-    my $dbi_driver    = $config->{'dbi_driver'};
-
-    # Use config to create a DSN line for the DBI connection
-    my $dsn;
-    if ( $dbi_driver eq 'SQLite' ) {
-        $dsn = "dbi:$dbi_driver:$db_database";
-    }
-    else {
-        $dsn = "dbi:$dbi_driver:database=$db_database;host=$db_server;port=$db_port";
-    }
-
-    # Now let's get connected
-    my $dbh = DBI->connect($dsn, $db_user, $db_pass,{RaiseError => 1, AutoCommit => 0 })
-            or die $DBI::errstr;
-
-    # If we didn't die, return the DBI object handle 
-    # and hope it's treated sensibly and correctly 
-    # destroyed by the calling code
-    return $dbh;
-}
-
-# }}}
-
-1;
+
+    # This is the encryption package & subroutine passed in by the config file
+    $RT::Logger->debug( "Encryption Package:",
+                        $db_p_enc_pkg);
+    $RT::Logger->debug( "Encryption Subroutine:",
+                        $db_p_enc_sub);
+
+    # Use config info to auto-load the perl package needed for password encryption
+    # I know it uses a string eval - but I don't think there's a better way to do this
+    # Jump to next external authentication service on failure
+    eval "require $db_p_enc_pkg" or 
+        $RT::Logger->error("AUTH FAILED, Couldn't Load Password Encryption Package. Error: $@") && return 0;
+    
+    my $encrypt = $db_p_enc_pkg->can($db_p_enc_sub);
+    if (defined($encrypt)) {
+        # If the package given can perform the subroutine given, then use it to compare the
+        # password given with the password pulled from the database.
+        # Jump to the next external authentication service if they don't match
+        if(defined($db_p_salt)) {
+            $RT::Logger->debug("Using salt:",$db_p_salt);
+            if(${encrypt}->($password,$db_p_salt) ne $pass_from_db){
+                $RT::Logger->info(  $service,
+                                    "AUTH FAILED", 
+                                    $username, 
+                                    "Password Incorrect");
+                return 0;
+            }
+        } else {
+            if(${encrypt}->($password) ne $pass_from_db){
+                $RT::Logger->info(  $service,
+                                    "AUTH FAILED", 
+                                    $username, 
+                                    "Password Incorrect");
+                return 0;
+            }
+        }
+    } else {
+        # If the encryption package can't perform the request subroutine,
+        # dump an error and jump to the next external authentication service.
+        $RT::Logger->error($service,
+                            "AUTH FAILED",
+                            "The encryption package you gave me (",
+                            $db_p_enc_pkg,
+                            ") does not support the encryption method you specified (",
+                            $db_p_enc_sub,
+                            ")");
+            return 0;
+    }
+    
+    # Any other checks you want to add? Add them here.
+
+    # If we've survived to this point, we're good.
+    $RT::Logger->info(  (caller(0))[3], 
+                        "External Auth OK (",
+                        $service,
+                        "):", 
+                        $username);
+    
+    return 1;   
+}
+
+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};
+    
+    # Figure out what's what
+    my $table      = $config->{'table'};
+
+    unless ($table) {
+        $RT::Logger->critical(  (caller(0))[3],
+                                "No table given");
+        # Drop out to the next external information service
+        return ($found, %params);
+    }
+
+    unless ($key && $value){
+        $RT::Logger->critical(  (caller(0))[3],
+                                " Nothing to look-up given");
+        # Drop out to the next external information service
+        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);
+
+    # 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);
+    $dbh->disconnect();
+
+    if ((scalar keys %$results_hashref) != 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 no user...
+	        $death_msg = "No User Found in External Database!";
+        } else {
+            # If more than one user...
+            $death_msg = "More than one user found in External Database with that unique identifier!";
+        }
+
+        # Log the death
+        $RT::Logger->info(  (caller(0))[3],
+                            "INFO CHECK FAILED",
+                            "Key: $key",
+                            "Value: $value",
+                            $death_msg);
+        
+        # $found remains as 0
+        
+        # Drop out to next external information service
+        return ($found, %params);
+    }
+
+    # 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;
+  
+    return ($found, %params);
+}
+
+sub UserExists {
+    
+    my ($username,$service) = @_;
+    my $config              = $RT::ExternalSettings->{$service};
+    my $table    	        = $config->{'table'};
+    my $u_field	            = $config->{'u_field'};
+    my $query               = "SELECT $u_field FROM $table WHERE $u_field=?";
+    my @bind_params         = ($username);
+
+    # Uncomment this to do a basic trace on DBI information and log it
+    # DBI->trace(1,'/tmp/dbi.log');
+    
+    # Get DBI Object, do the query, disconnect
+    my $dbh = _GetBoundDBIObj($config);
+    my $results_hashref = $dbh->selectall_hashref($query,$u_field,{}, at bind_params);
+    $dbh->disconnect();
+
+    my $num_of_results = scalar keys %$results_hashref;
+        
+    if ($num_of_results > 1) { 
+        # If more than one result returned, die because we the username field should be unique!
+        $RT::Logger->debug( "Disable Check Failed :: (",
+                            $service,
+                            ")",
+                            $username,
+                            "More than one user with that username!");
+        return 0;
+    } elsif ($num_of_results < 1) { 
+        # If 0 or negative integer, no user found or major failure
+        $RT::Logger->debug( "Disable Check Failed :: (",
+                            $service,
+                            ")",
+                            $username,
+                            "User not found");   
+        return 0; 
+    }
+    
+    # Number of results is exactly one, so we found the user we were looking for
+    return 1;            
+}
+
+sub UserDisabled {
+
+    my ($username,$service) = @_;
+    
+    # FIRST, check that the user exists in the DBI service
+    unless(UserExists($username,$service)) {
+        $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking");
+        return 0;
+    }
+    
+    # Get the necessary config info
+    my $config              = $RT::ExternalSettings->{$service};
+    my $table    	        = $config->{'table'};
+    my $u_field	            = $config->{'u_field'};
+    my $disable_field       = $config->{'d_field'};
+    my $disable_values_list = $config->{'d_values'};
+
+    unless ($disable_field) {
+        # If we don't know how to check for disabled users, consider them all enabled.
+        $RT::Logger->debug("No d_field specified for this DBI service (",
+                            $service,
+                            "), so considering all users enabled");
+        return 0;
+    } 
+    
+    my $query = "SELECT $u_field,$disable_field FROM $table WHERE $u_field=?";
+    my @bind_params = ($username);
+
+    # Uncomment this to do a basic trace on DBI information and log it
+    # DBI->trace(1,'/tmp/dbi.log');
+    
+    # Get DBI Object, do the query, disconnect
+    my $dbh = _GetBoundDBIObj($config);
+    my $results_hashref = $dbh->selectall_hashref($query,$u_field,{}, at bind_params);
+    $dbh->disconnect();
+
+    my $num_of_results = scalar keys %$results_hashref;
+        
+    if ($num_of_results > 1) { 
+        # If more than one result returned, die because we the username field should be unique!
+        $RT::Logger->debug( "Disable Check Failed :: (",
+                            $service,
+                            ")",
+                            $username,
+                            "More than one user with that username! - Assuming not disabled");
+        # Drop out to next service for an info check
+        return 0;
+    } elsif ($num_of_results < 1) { 
+        # If 0 or negative integer, no user found or major failure
+        $RT::Logger->debug( "Disable Check Failed :: (",
+                            $service,
+                            ")",
+                            $username,
+                            "User not found - Assuming not disabled");   
+        # Drop out to next service for an info check
+        return 0;             
+    } else { 
+        # otherwise all should be well
+        
+        # $user_db_disable_value = The value for "disabled" returned from the DB
+        my $user_db_disable_value = $results_hashref->{$username}->{$disable_field};
+        
+        # For each of the values in the (list of values that we consider to mean the user is disabled)..
+        foreach my $disable_value (@{$disable_values_list}){
+            $RT::Logger->debug( "DB Disable Check:", 
+                                "User's Val is $user_db_disable_value,",
+                                "Checking against: $disable_value");
+            
+            # If the value from the DB matches a value from the list, the user is disabled.
+            if ($user_db_disable_value eq $disable_value) {
+                return 1;
+            }
+        }
+        
+        # If we've not returned yet, the user can't be disabled
+        return 0;
+    }
+    $RT::Logger->crit("It is seriously not possible to run this code.. what the hell did you do?!");
+    return 0;
+}
+
+sub GetCookieAuth {
+
+    $RT::Logger->debug( (caller(0))[3],
+	                "Checking Browser Cookies for an Authenticated User");
+			     
+    # Get our cookie and database info...
+    my $config = shift;
+
+    my $username = undef;
+    my $cookie_name = $config->{'name'};
+
+    my $cookie_value = RT::Authen::ExternalAuth::DBI::Cookie::GetCookieVal($cookie_name);
+
+    unless($cookie_value){
+        return $username;
+    }
+
+    # The table mapping usernames to the Username Match Key
+    my $u_table     = $config->{'u_table'};
+    # The username field in that table
+    my $u_field     = $config->{'u_field'};
+    # The field that contains the Username Match Key
+    my $u_match_key = $config->{'u_match_key'};
+
+    # The table mapping cookie values to the Cookie Match Key
+    my $c_table     = $config->{'c_table'};
+    # The cookie field in that table - The same as the cookie name if unspecified
+    my $c_field     = $config->{'c_field'};
+    # The field that connects the Cookie Match Key
+    my $c_match_key = $config->{'c_match_key'};
+
+    # These are random characters to assign as table aliases in SQL
+    # It saves a lot of garbled code later on
+    my $u_table_alias = "u";
+    my $c_table_alias = "c";
+
+    # $tables will be passed straight into the SQL query
+    # I don't see this as a security issue as only the admin may modify the config file anyway
+    my $tables;
+
+    # If the tables are the same, then the aliases should be the same
+    # and the match key becomes irrelevant. Ensure this all works out
+    # fine by setting both sides the same. In either case, set an
+    # appropriate value for $tables.
+    if ($u_table eq $c_table) {
+            $u_table_alias  = $c_table_alias;
+            $u_match_key    = $c_match_key;
+            $tables         = "$c_table $c_table_alias";
+    } else {
+            $tables = "$c_table $c_table_alias, $u_table $u_table_alias";
+    }
+
+    my $select_fields = "$u_table_alias.$u_field";
+    my $where_statement = "$c_table_alias.$c_field = ? AND $c_table_alias.$c_match_key = $u_table_alias.$u_match_key";
+
+    my $query = "SELECT $select_fields FROM $tables WHERE $where_statement";
+    my @params = ($cookie_value);
+
+    # Use this if you need to debug the DBI SQL process
+    # DBI->trace(1,'/tmp/dbi.log');
+
+    my $dbh = _GetBoundDBIObj($RT::ExternalSettings->{$config->{'db_service_name'}});
+    my $query_result_arrayref = $dbh->selectall_arrayref($query,{}, at params);
+    $dbh->disconnect();
+
+    # The log messages say it all here...
+    my $num_rows = scalar @$query_result_arrayref;
+    if ($num_rows < 1) {
+        $RT::Logger->info(  "AUTH FAILED",
+                            $cookie_name,
+                            "Cookie value not found in database.",
+                            "User passed an authentication token they were not given by us!",
+                            "Is this nefarious activity?");
+    } elsif ($num_rows > 1) {
+        $RT::Logger->error( "AUTH FAILED",
+                            $cookie_name,
+                            "Cookie's value is duplicated in the database! This should not happen!!");
+    } else {
+        $username = $query_result_arrayref->[0][0];
+    }
+
+    if ($username) {
+        $RT::Logger->debug( "User (",
+                            $username,
+                            ") was authenticated by a browser cookie");
+    } else {
+        $RT::Logger->debug( "No user was authenticated by browser cookie");
+    }
+
+    return $username;
+
+}
+
+
+# {{{ sub _GetBoundDBIObj
+
+sub _GetBoundDBIObj {
+    
+    # Config as hashref. 
+    my $config = shift;
+
+    # Extract the relevant information from the config.
+    my $db_server     = $config->{'server'};
+    my $db_user       = $config->{'user'};
+    my $db_pass       = $config->{'pass'};
+    my $db_database   = $config->{'database'};
+    my $db_port       = $config->{'port'};
+    my $dbi_driver    = $config->{'dbi_driver'};
+
+    # Use config to create a DSN line for the DBI connection
+    my $dsn;
+    if ( $dbi_driver eq 'SQLite' ) {
+        $dsn = "dbi:$dbi_driver:$db_database";
+    }
+    else {
+        $dsn = "dbi:$dbi_driver:database=$db_database;host=$db_server;port=$db_port";
+    }
+
+    # Now let's get connected
+    my $dbh = DBI->connect($dsn, $db_user, $db_pass,{RaiseError => 1, AutoCommit => 0 })
+            or die $DBI::errstr;
+
+    # If we didn't die, return the DBI object handle 
+    # and hope it's treated sensibly and correctly 
+    # destroyed by the calling code
+    return $dbh;
+}
+
+# }}}
+
+1;
diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 96a63a0..cc92410 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -1,643 +1,643 @@
-package RT::Authen::ExternalAuth::LDAP;
-
-use Net::LDAP qw(LDAP_SUCCESS LDAP_PARTIAL_RESULTS);
-use Net::LDAP::Util qw(ldap_error_name escape_filter_value);
-use Net::LDAP::Filter;
-
-use strict;
-
-=head1 NAME
-
-RT::Authen::ExternalAuth::LDAP - LDAP source for RT authentication
-
-=head1 DESCRIPTION
-
-Provides the LDAP implementation for L<RT::Authen::ExternalAuth>.
-
-=head1 SYNOPSIS
-
-    Set($ExternalSettings, {
-        # AN EXAMPLE LDAP SERVICE
-        'My_LDAP'       =>  {
-            'type'                      =>  'ldap',
-
-            'server'                    =>  'server.domain.tld',
-            'user'                      =>  'rt_ldap_username',
-            'pass'                      =>  'rt_ldap_password',
-
-            'base'                      =>  'ou=Organisational Unit,dc=domain,dc=TLD',
-            'filter'                    =>  '(FILTER_STRING)',
-            'd_filter'                  =>  '(FILTER_STRING)',
-
-            'group'                     =>  'GROUP_NAME',
-            'group_attr'                =>  'GROUP_ATTR',
-
-            'tls'                       =>  0,
-            'ssl_version'               =>  3,
-
-            'net_ldap_args'             => [    version =>  3   ],
-
-            'attr_match_list' => [
-                'Name',
-                'EmailAddress',
-            ],
-            'attr_map' => {
-                'Name' => 'sAMAccountName',
-                'EmailAddress' => 'mail',
-                'Organization' => 'physicalDeliveryOfficeName',
-                'RealName' => 'cn',
-                'ExternalAuthId' => 'sAMAccountName',
-                'Gecos' => 'sAMAccountName',
-                'WorkPhone' => 'telephoneNumber',
-                'Address1' => 'streetAddress',
-                'City' => 'l',
-                'State' => 'st',
-                'Zip' => 'postalCode',
-                'Country' => 'co'
-            },
-        },
-    } );
-
-=head1 CONFIGURATION
-
-LDAP-specific options are described here. Shared options
-are described in the F<etc/RT_SiteConfig.pm> file included
-in this distribution.
-
-The example in the L</SYNOPSIS> lists all available options
-and they are described below. Note that many of these values
-are specific to LDAP, so you should consult your LDAP
-documentation for details.
-
-=over 4
-
-=item server
-
-The server hosting the LDAP or AD service.
-
-=item user, pass
-
-The username and password RT should use to connect to the LDAP
-server.
-
-If you can bind to your LDAP server anonymously you may be able to omit these
-options.  Many servers do not allow anonymous binds, or restrict what information
-they can see or how much information they can retrieve.  If your server does not
-allow anonymous binds then you must have a service account created for this
-extension to function.
-
-=item base
-
-The LDAP search base.
-
-=item filter
-
-The filter to use to match RT users. You B<must> specify it
-and it B<must> be a valid LDAP filter encased in parentheses.
-
-For example:
-
-    filter => '(objectClass=*)',
-
-=item d_filter
-
-The filter that will only match disabled users. Optional.
-B<Must> be a valid LDAP filter encased in parentheses.
-
-For example with Active Directory the following can be used:
-
-    d_filter => '(userAccountControl:1.2.840.113556.1.4.803:=2)'
-
-=item group
-
-Does authentication depend on group membership? What group name?
-
-=item group_attr
-
-What is the attribute for the group object that determines membership?
-
-=item group_scope
-
-What is the scope of the group search? C<base>, C<one> or C<sub>.
-Optional; defaults to C<base>, which is good enough for most cases.
-C<sub> is appropriate when you have nested groups.
-
-=item group_attr_value
-
-What is the attribute of the user entry that should be matched against
-group_attr above? Optional; defaults to C<dn>.
-
-=item tls
-
-Should we try to use TLS to encrypt connections?
-
-=item ssl_version
-
-SSL Version to provide to Net::SSLeay *if* using SSL.
-
-=item net_ldap_args
-
-What other args should be passed to Net::LDAP->new($host, at args)?
-
-=back
-
-=cut
-
-sub GetAuth {
-    
-    my ($service, $username, $password) = @_;
-    
-    my $config = $RT::ExternalSettings->{$service};
-    $RT::Logger->debug( "Trying external auth service:",$service);
-
-    my $base            = $config->{'base'};
-    my $filter          = $config->{'filter'};
-    my $group           = $config->{'group'};
-    my $group_attr      = $config->{'group_attr'};
-    my $group_attr_val  = $config->{'group_attr_value'} || 'dn';
-    my $group_scope     = $config->{'group_scope'} || 'base';
-    my $attr_map        = $config->{'attr_map'};
-    my @attrs           = ('dn');
-
-    # Make sure we fetch the user attribute we'll need for the group check
-    push @attrs, $group_attr_val
-        unless lc $group_attr_val eq 'dn';
-
-    # Empty parentheses as filters cause Net::LDAP to barf.
-    # We take care of this by using Net::LDAP::Filter, but
-    # there's no harm in fixing this right now.
-    if ($filter eq "()") { undef($filter) };
-
-    # Now let's get connected
-    my $ldap = _GetBoundLdapObj($config);
-    return 0 unless ($ldap);
-
-    $filter = Net::LDAP::Filter->new(   '(&(' . 
-                                        $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;
-    }
-
-    unless ($ldap_msg->count == 1) {
-        $RT::Logger->info(  $service,
-                            "AUTH FAILED:", 
-                            $username,
-                            "User not found or more than one user found");
-        # We got no user, or too many users.. jump straight to the next external auth service
-        return 0;
-    }
-
-    my $ldap_entry = $ldap_msg->first_entry;
-    my $ldap_dn    = $ldap_entry->dn;
-
-    $RT::Logger->debug( "Found LDAP DN:", 
-                        $ldap_dn);
-
-    # THIS bind determines success or failure on the password.
-    $ldap_msg = $ldap->bind($ldap_dn, password => $password);
-
-    unless ($ldap_msg->code == LDAP_SUCCESS) {
-        $RT::Logger->info(  $service,
-                            "AUTH FAILED", 
-                            $username, 
-                            "(can't bind:", 
-                            ldap_error_name($ldap_msg->code), 
-                            $ldap_msg->code, 
-                            ")");
-        # Could not bind to the LDAP server as the user we found with the password
-        # we were given, therefore the password must be wrong so we fail and
-        # jump straight to the next external auth service
-        return 0;
-    }
-
-    # The user is authenticated ok, but is there an LDAP Group to check?
-    if ($group) {
-        my $group_val = lc $group_attr_val eq 'dn'
-                            ? $ldap_dn
-                            : $ldap_entry->get_value($group_attr_val);
-
-        # Fallback to the DN if the user record doesn't have a value
-        unless (defined $group_val) {
-            $group_val = $ldap_dn;
-            $RT::Logger->debug("Attribute '$group_attr_val' has no value; falling back to '$group_val'");
-        }
-
-        # 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,
-                            "== Scope:",
-                            $group_scope,
-                            "== Filter:", 
-                            $filter->as_string,
-                            "== Attrs:", 
-                            join(',', at attrs));
-        
-        $ldap_msg = $ldap->search(  base   => $group,
-                                    filter => $filter,
-                                    attrs  => \@attrs,
-                                    scope  => $group_scope);
-
-        # 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;
-        }
-
-        unless ($ldap_msg->count == 1) {
-            $RT::Logger->debug(
-                "LDAP group membership check returned",
-                $ldap_msg->count, "results"
-            );
-            $RT::Logger->info(  $service,
-                                "AUTH FAILED:", 
-                                $username);
-                                
-            # Fail auth - jump to next external auth service
-            return 0;
-        }
-    }
-    
-    # Any other checks you want to add? Add them here.
-
-    # If we've survived to this point, we're good.
-    $RT::Logger->info(  (caller(0))[3], 
-                        "External Auth OK (",
-                        $service,
-                        "):", 
-                        $username);
-    return 1;
-
-}
-
-
-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};
-   
-    # Figure out what's what
-    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 "()")) {
-        $filter = Net::LDAP::Filter->new(   "(&" . 
-                                            $filter . 
-                                            $filter_addition . 
-                                            ")"
-                                        ); 
-    } else {
-        $RT::Logger->debug( "LDAP Filter invalid or not present.");
-    }
-
-    unless (defined($base)) {
-        $RT::Logger->critical(  (caller(0))[3],
-                                "LDAP baseDN not defined");
-        # Drop out to the next external information service
-        return ($found, %params);
-    }
-
-    # Get a Net::LDAP object based on the config we provide
-    my $ldap = _GetBoundLdapObj($config);
-
-    # 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);
-
-    # 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
-        $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;
-        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'}})) {
-                # 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];
-                }
-            }
-            $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;
-            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;
-
-    return ($found, %params);
-}
-
-sub UserExists {
-    my ($username,$service) = @_;
-   $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
-    # of parentheses is an invalid filter and will cause failure
-    # This shouldn't matter since we are now using Net::LDAP::Filter below,
-    # but there's no harm in doing this to be sure
-    if ($filter eq "()") { undef($filter) };
-
-    if (defined($config->{'attr_map'}->{'Name'})) {
-        # Construct the complex filter
-        $filter = Net::LDAP::Filter->new(           '(&' . 
-                                                    $filter . 
-                                                    '(' . 
-                                                    $config->{'attr_map'}->{'Name'} . 
-                                                    '=' . 
-                                                    escape_filter_value($username) . 
-                                                    '))'
-                                        );
-    }
-
-    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);
-
-    if($user_found->count < 1) {
-        # If 0 or negative integer, no user found or major failure
-        $RT::Logger->debug( "User Check Failed :: (",
-                            $service,
-                            ")",
-                            $username,
-                            "User not found");   
-        return 0;  
-    } elsif ($user_found->count > 1) {
-        # If more than one result returned, die because we the username field should be unique!
-        $RT::Logger->debug( "User Check Failed :: (",
-                            $service,
-                            ")",
-                            $username,
-                            "More than one user with that username!");
-        return 0;
-    }
-    undef $user_found;
-    
-    # If we havent returned now, there must be a valid user.
-    return 1;
-}
-
-sub UserDisabled {
-
-    my ($username,$service) = @_;
-
-    # FIRST, check that the user exists in the LDAP service
-    unless(UserExists($username,$service)) {
-        $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking");
-        return 0;
-    }
-    
-    my $config          = $RT::ExternalSettings->{$service};
-    my $base            = $config->{'base'};
-    my $filter          = $config->{'filter'};
-    my $d_filter        = $config->{'d_filter'};
-    my $search_filter;
-
-    # While LDAP filters must be surrounded by parentheses, an empty set
-    # of parentheses is an invalid filter and will cause failure
-    # This shouldn't matter since we are now using Net::LDAP::Filter below,
-    # but there's no harm in doing this to be sure
-    if ($filter eq "()") { undef($filter) };
-    if ($d_filter eq "()") { undef($d_filter) };
-
-    unless ($d_filter) {
-        # If we don't know how to check for disabled users, consider them all enabled.
-        $RT::Logger->debug("No d_filter specified for this LDAP service (",
-                            $service,
-                            "), so considering all users enabled");
-        return 0;
-    }
-
-    if (defined($config->{'attr_map'}->{'Name'})) {
-        # Construct the complex filter
-        $search_filter = Net::LDAP::Filter->new(   '(&' . 
-                                                    $filter . 
-                                                    $d_filter . 
-                                                    '(' . 
-                                                    $config->{'attr_map'}->{'Name'} . 
-                                                    '=' . 
-                                                    escape_filter_value($username) . 
-                                                    '))'
-                                                );
-    } else {
-        $RT::Logger->debug("You haven't specified an LDAP attribute to match the RT \"Name\" attribute for this service (",
-                            $service,
-                            "), so it's impossible look up the disabled status of this user (",
-                            $username,
-                            ") so I'm just going to assume the user is not disabled");
-        return 0;
-        
-    }
-
-    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);
-    # If ANY results are returned, 
-    # we are going to assume the user should be disabled
-    if ($disabled_users->count) {
-        undef $disabled_users;
-        return 1;
-    } else {
-        undef $disabled_users;
-        return 0;
-    }
-}
-# {{{ sub _GetBoundLdapObj
-
-sub _GetBoundLdapObj {
-
-    # Config as hashref
-    my $config = shift;
-
-    # Figure out what's what
-    my $ldap_server     = $config->{'server'};
-    my $ldap_user       = $config->{'user'};
-    my $ldap_pass       = $config->{'pass'};
-    my $ldap_tls        = $config->{'tls'};
-    my $ldap_ssl_ver    = $config->{'ssl_version'};
-    my $ldap_args       = $config->{'net_ldap_args'};
-    
-    my $ldap = new Net::LDAP($ldap_server, @$ldap_args);
-    
-    unless ($ldap) {
-        $RT::Logger->critical(  (caller(0))[3],
-                                ": Cannot connect to",
-                                $ldap_server);
-        return undef;
-    }
-
-    if ($ldap_tls) {
-        require Net::SSLeay;
-        $Net::SSLeay::ssl_version = $ldap_ssl_ver;
-        # Thanks to David Narayan for the fault tolerance bits
-        eval { $ldap->start_tls; };
-        if ($@) {
-            $RT::Logger->critical(  (caller(0))[3], 
-                                    "Can't start TLS: ",
-                                    $@);
-            return;
-        }
-
-    }
-
-    my $msg = undef;
-
-    if (($ldap_user) and ($ldap_pass)) {
-        $msg = $ldap->bind($ldap_user, password => $ldap_pass);
-    } elsif (($ldap_user) and ( ! $ldap_pass)) {
-        $msg = $ldap->bind($ldap_user);
-    } else {
-        $msg = $ldap->bind;
-    }
-
-    unless ($msg->code == LDAP_SUCCESS) {
-        $RT::Logger->critical(  (caller(0))[3], 
-                                "Can't bind:", 
-                                ldap_error_name($msg->code), 
-                                $msg->code);
-        return undef;
-    } else {
-        return $ldap;
-    }
-}
-
-# }}}
-
-1;
+package RT::Authen::ExternalAuth::LDAP;
+
+use Net::LDAP qw(LDAP_SUCCESS LDAP_PARTIAL_RESULTS);
+use Net::LDAP::Util qw(ldap_error_name escape_filter_value);
+use Net::LDAP::Filter;
+
+use strict;
+
+=head1 NAME
+
+RT::Authen::ExternalAuth::LDAP - LDAP source for RT authentication
+
+=head1 DESCRIPTION
+
+Provides the LDAP implementation for L<RT::Authen::ExternalAuth>.
+
+=head1 SYNOPSIS
+
+    Set($ExternalSettings, {
+        # AN EXAMPLE LDAP SERVICE
+        'My_LDAP'       =>  {
+            'type'                      =>  'ldap',
+
+            'server'                    =>  'server.domain.tld',
+            'user'                      =>  'rt_ldap_username',
+            'pass'                      =>  'rt_ldap_password',
+
+            'base'                      =>  'ou=Organisational Unit,dc=domain,dc=TLD',
+            'filter'                    =>  '(FILTER_STRING)',
+            'd_filter'                  =>  '(FILTER_STRING)',
+
+            'group'                     =>  'GROUP_NAME',
+            'group_attr'                =>  'GROUP_ATTR',
+
+            'tls'                       =>  0,
+            'ssl_version'               =>  3,
+
+            'net_ldap_args'             => [    version =>  3   ],
+
+            'attr_match_list' => [
+                'Name',
+                'EmailAddress',
+            ],
+            'attr_map' => {
+                'Name' => 'sAMAccountName',
+                'EmailAddress' => 'mail',
+                'Organization' => 'physicalDeliveryOfficeName',
+                'RealName' => 'cn',
+                'ExternalAuthId' => 'sAMAccountName',
+                'Gecos' => 'sAMAccountName',
+                'WorkPhone' => 'telephoneNumber',
+                'Address1' => 'streetAddress',
+                'City' => 'l',
+                'State' => 'st',
+                'Zip' => 'postalCode',
+                'Country' => 'co'
+            },
+        },
+    } );
+
+=head1 CONFIGURATION
+
+LDAP-specific options are described here. Shared options
+are described in the F<etc/RT_SiteConfig.pm> file included
+in this distribution.
+
+The example in the L</SYNOPSIS> lists all available options
+and they are described below. Note that many of these values
+are specific to LDAP, so you should consult your LDAP
+documentation for details.
+
+=over 4
+
+=item server
+
+The server hosting the LDAP or AD service.
+
+=item user, pass
+
+The username and password RT should use to connect to the LDAP
+server.
+
+If you can bind to your LDAP server anonymously you may be able to omit these
+options.  Many servers do not allow anonymous binds, or restrict what information
+they can see or how much information they can retrieve.  If your server does not
+allow anonymous binds then you must have a service account created for this
+extension to function.
+
+=item base
+
+The LDAP search base.
+
+=item filter
+
+The filter to use to match RT users. You B<must> specify it
+and it B<must> be a valid LDAP filter encased in parentheses.
+
+For example:
+
+    filter => '(objectClass=*)',
+
+=item d_filter
+
+The filter that will only match disabled users. Optional.
+B<Must> be a valid LDAP filter encased in parentheses.
+
+For example with Active Directory the following can be used:
+
+    d_filter => '(userAccountControl:1.2.840.113556.1.4.803:=2)'
+
+=item group
+
+Does authentication depend on group membership? What group name?
+
+=item group_attr
+
+What is the attribute for the group object that determines membership?
+
+=item group_scope
+
+What is the scope of the group search? C<base>, C<one> or C<sub>.
+Optional; defaults to C<base>, which is good enough for most cases.
+C<sub> is appropriate when you have nested groups.
+
+=item group_attr_value
+
+What is the attribute of the user entry that should be matched against
+group_attr above? Optional; defaults to C<dn>.
+
+=item tls
+
+Should we try to use TLS to encrypt connections?
+
+=item ssl_version
+
+SSL Version to provide to Net::SSLeay *if* using SSL.
+
+=item net_ldap_args
+
+What other args should be passed to Net::LDAP->new($host, at args)?
+
+=back
+
+=cut
+
+sub GetAuth {
+    
+    my ($service, $username, $password) = @_;
+    
+    my $config = $RT::ExternalSettings->{$service};
+    $RT::Logger->debug( "Trying external auth service:",$service);
+
+    my $base            = $config->{'base'};
+    my $filter          = $config->{'filter'};
+    my $group           = $config->{'group'};
+    my $group_attr      = $config->{'group_attr'};
+    my $group_attr_val  = $config->{'group_attr_value'} || 'dn';
+    my $group_scope     = $config->{'group_scope'} || 'base';
+    my $attr_map        = $config->{'attr_map'};
+    my @attrs           = ('dn');
+
+    # Make sure we fetch the user attribute we'll need for the group check
+    push @attrs, $group_attr_val
+        unless lc $group_attr_val eq 'dn';
+
+    # Empty parentheses as filters cause Net::LDAP to barf.
+    # We take care of this by using Net::LDAP::Filter, but
+    # there's no harm in fixing this right now.
+    if ($filter eq "()") { undef($filter) };
+
+    # Now let's get connected
+    my $ldap = _GetBoundLdapObj($config);
+    return 0 unless ($ldap);
+
+    $filter = Net::LDAP::Filter->new(   '(&(' . 
+                                        $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;
+    }
+
+    unless ($ldap_msg->count == 1) {
+        $RT::Logger->info(  $service,
+                            "AUTH FAILED:", 
+                            $username,
+                            "User not found or more than one user found");
+        # We got no user, or too many users.. jump straight to the next external auth service
+        return 0;
+    }
+
+    my $ldap_entry = $ldap_msg->first_entry;
+    my $ldap_dn    = $ldap_entry->dn;
+
+    $RT::Logger->debug( "Found LDAP DN:", 
+                        $ldap_dn);
+
+    # THIS bind determines success or failure on the password.
+    $ldap_msg = $ldap->bind($ldap_dn, password => $password);
+
+    unless ($ldap_msg->code == LDAP_SUCCESS) {
+        $RT::Logger->info(  $service,
+                            "AUTH FAILED", 
+                            $username, 
+                            "(can't bind:", 
+                            ldap_error_name($ldap_msg->code), 
+                            $ldap_msg->code, 
+                            ")");
+        # Could not bind to the LDAP server as the user we found with the password
+        # we were given, therefore the password must be wrong so we fail and
+        # jump straight to the next external auth service
+        return 0;
+    }
+
+    # The user is authenticated ok, but is there an LDAP Group to check?
+    if ($group) {
+        my $group_val = lc $group_attr_val eq 'dn'
+                            ? $ldap_dn
+                            : $ldap_entry->get_value($group_attr_val);
+
+        # Fallback to the DN if the user record doesn't have a value
+        unless (defined $group_val) {
+            $group_val = $ldap_dn;
+            $RT::Logger->debug("Attribute '$group_attr_val' has no value; falling back to '$group_val'");
+        }
+
+        # 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,
+                            "== Scope:",
+                            $group_scope,
+                            "== Filter:", 
+                            $filter->as_string,
+                            "== Attrs:", 
+                            join(',', at attrs));
+        
+        $ldap_msg = $ldap->search(  base   => $group,
+                                    filter => $filter,
+                                    attrs  => \@attrs,
+                                    scope  => $group_scope);
+
+        # 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;
+        }
+
+        unless ($ldap_msg->count == 1) {
+            $RT::Logger->debug(
+                "LDAP group membership check returned",
+                $ldap_msg->count, "results"
+            );
+            $RT::Logger->info(  $service,
+                                "AUTH FAILED:", 
+                                $username);
+                                
+            # Fail auth - jump to next external auth service
+            return 0;
+        }
+    }
+    
+    # Any other checks you want to add? Add them here.
+
+    # If we've survived to this point, we're good.
+    $RT::Logger->info(  (caller(0))[3], 
+                        "External Auth OK (",
+                        $service,
+                        "):", 
+                        $username);
+    return 1;
+
+}
+
+
+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};
+   
+    # Figure out what's what
+    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 "()")) {
+        $filter = Net::LDAP::Filter->new(   "(&" . 
+                                            $filter . 
+                                            $filter_addition . 
+                                            ")"
+                                        ); 
+    } else {
+        $RT::Logger->debug( "LDAP Filter invalid or not present.");
+    }
+
+    unless (defined($base)) {
+        $RT::Logger->critical(  (caller(0))[3],
+                                "LDAP baseDN not defined");
+        # Drop out to the next external information service
+        return ($found, %params);
+    }
+
+    # Get a Net::LDAP object based on the config we provide
+    my $ldap = _GetBoundLdapObj($config);
+
+    # 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);
+
+    # 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
+        $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;
+        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'}})) {
+                # 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];
+                }
+            }
+            $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;
+            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;
+
+    return ($found, %params);
+}
+
+sub UserExists {
+    my ($username,$service) = @_;
+   $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
+    # of parentheses is an invalid filter and will cause failure
+    # This shouldn't matter since we are now using Net::LDAP::Filter below,
+    # but there's no harm in doing this to be sure
+    if ($filter eq "()") { undef($filter) };
+
+    if (defined($config->{'attr_map'}->{'Name'})) {
+        # Construct the complex filter
+        $filter = Net::LDAP::Filter->new(           '(&' . 
+                                                    $filter . 
+                                                    '(' . 
+                                                    $config->{'attr_map'}->{'Name'} . 
+                                                    '=' . 
+                                                    escape_filter_value($username) . 
+                                                    '))'
+                                        );
+    }
+
+    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);
+
+    if($user_found->count < 1) {
+        # If 0 or negative integer, no user found or major failure
+        $RT::Logger->debug( "User Check Failed :: (",
+                            $service,
+                            ")",
+                            $username,
+                            "User not found");   
+        return 0;  
+    } elsif ($user_found->count > 1) {
+        # If more than one result returned, die because we the username field should be unique!
+        $RT::Logger->debug( "User Check Failed :: (",
+                            $service,
+                            ")",
+                            $username,
+                            "More than one user with that username!");
+        return 0;
+    }
+    undef $user_found;
+    
+    # If we havent returned now, there must be a valid user.
+    return 1;
+}
+
+sub UserDisabled {
+
+    my ($username,$service) = @_;
+
+    # FIRST, check that the user exists in the LDAP service
+    unless(UserExists($username,$service)) {
+        $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking");
+        return 0;
+    }
+    
+    my $config          = $RT::ExternalSettings->{$service};
+    my $base            = $config->{'base'};
+    my $filter          = $config->{'filter'};
+    my $d_filter        = $config->{'d_filter'};
+    my $search_filter;
+
+    # While LDAP filters must be surrounded by parentheses, an empty set
+    # of parentheses is an invalid filter and will cause failure
+    # This shouldn't matter since we are now using Net::LDAP::Filter below,
+    # but there's no harm in doing this to be sure
+    if ($filter eq "()") { undef($filter) };
+    if ($d_filter eq "()") { undef($d_filter) };
+
+    unless ($d_filter) {
+        # If we don't know how to check for disabled users, consider them all enabled.
+        $RT::Logger->debug("No d_filter specified for this LDAP service (",
+                            $service,
+                            "), so considering all users enabled");
+        return 0;
+    }
+
+    if (defined($config->{'attr_map'}->{'Name'})) {
+        # Construct the complex filter
+        $search_filter = Net::LDAP::Filter->new(   '(&' . 
+                                                    $filter . 
+                                                    $d_filter . 
+                                                    '(' . 
+                                                    $config->{'attr_map'}->{'Name'} . 
+                                                    '=' . 
+                                                    escape_filter_value($username) . 
+                                                    '))'
+                                                );
+    } else {
+        $RT::Logger->debug("You haven't specified an LDAP attribute to match the RT \"Name\" attribute for this service (",
+                            $service,
+                            "), so it's impossible look up the disabled status of this user (",
+                            $username,
+                            ") so I'm just going to assume the user is not disabled");
+        return 0;
+        
+    }
+
+    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);
+    # If ANY results are returned, 
+    # we are going to assume the user should be disabled
+    if ($disabled_users->count) {
+        undef $disabled_users;
+        return 1;
+    } else {
+        undef $disabled_users;
+        return 0;
+    }
+}
+# {{{ sub _GetBoundLdapObj
+
+sub _GetBoundLdapObj {
+
+    # Config as hashref
+    my $config = shift;
+
+    # Figure out what's what
+    my $ldap_server     = $config->{'server'};
+    my $ldap_user       = $config->{'user'};
+    my $ldap_pass       = $config->{'pass'};
+    my $ldap_tls        = $config->{'tls'};
+    my $ldap_ssl_ver    = $config->{'ssl_version'};
+    my $ldap_args       = $config->{'net_ldap_args'};
+    
+    my $ldap = new Net::LDAP($ldap_server, @$ldap_args);
+    
+    unless ($ldap) {
+        $RT::Logger->critical(  (caller(0))[3],
+                                ": Cannot connect to",
+                                $ldap_server);
+        return undef;
+    }
+
+    if ($ldap_tls) {
+        require Net::SSLeay;
+        $Net::SSLeay::ssl_version = $ldap_ssl_ver;
+        # Thanks to David Narayan for the fault tolerance bits
+        eval { $ldap->start_tls; };
+        if ($@) {
+            $RT::Logger->critical(  (caller(0))[3], 
+                                    "Can't start TLS: ",
+                                    $@);
+            return;
+        }
+
+    }
+
+    my $msg = undef;
+
+    if (($ldap_user) and ($ldap_pass)) {
+        $msg = $ldap->bind($ldap_user, password => $ldap_pass);
+    } elsif (($ldap_user) and ( ! $ldap_pass)) {
+        $msg = $ldap->bind($ldap_user);
+    } else {
+        $msg = $ldap->bind;
+    }
+
+    unless ($msg->code == LDAP_SUCCESS) {
+        $RT::Logger->critical(  (caller(0))[3], 
+                                "Can't bind:", 
+                                ldap_error_name($msg->code), 
+                                $msg->code);
+        return undef;
+    } else {
+        return $ldap;
+    }
+}
+
+# }}}
+
+1;

commit 2960d9099ed6d20bd1954dc12bb10c44e67cb464
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 17:04:54 2014 -0400

    Whitespace cleanup

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 3b0d2c6..daf9583 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -357,14 +357,14 @@ sub DoAuth {
     return (0, "User already logged in!") if ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id);
     # We don't have a logged in user. Let's try all our available methods in order.
     # last if success, next if not.
-    
+
     # Get the prioritised list of external authentication services
     my @auth_services = @$RT::ExternalAuthPriority;
-    
+
     # For each of those services..
     foreach my $service (@auth_services) {
 
-	$pass_bypass = 0;
+        $pass_bypass = 0;
 
         # Get the full configuration for that service as a hashref
         my $config = $RT::ExternalSettings->{$service};
@@ -374,42 +374,42 @@ sub DoAuth {
         # $username will be the final username we decide to check
         # This will not necessarily be $given_user
         my $username = undef;
-        
+
         #############################################################
         ####################### SSO Check ###########################
         #############################################################
-        if ($config->{'type'} eq 'cookie') {    
+        if ($config->{'type'} eq 'cookie') {
             # Currently, Cookie authentication is our only SSO method
             $username = RT::Authen::ExternalAuth::DBI::GetCookieAuth($config);
         }
         #############################################################
-        
+
         # If $username is defined, we have a good SSO $username and can
         # safely bypass the password checking later on; primarily because
         # it's VERY unlikely we even have a password to check if an SSO succeeded.
         $pass_bypass = 0;
-	if(defined($username)) {
-	    $RT::Logger->debug("Pass not going to be checked, attempting SSO");
+        if(defined($username)) {
+            $RT::Logger->debug("Pass not going to be checked, attempting SSO");
             $pass_bypass = 1;
         } else {
 
-	    # SSO failed and no $user was passed for a login attempt
-	    # We only don't return here because the next iteration could be an SSO attempt
-	    unless(defined($given_user)) {
-	    	$RT::Logger->debug("SSO Failed and no user to test with. Nexting");
-		next;
-	    }
+            # SSO failed and no $user was passed for a login attempt
+            # We only don't return here because the next iteration could be an SSO attempt
+            unless(defined($given_user)) {
+                $RT::Logger->debug("SSO Failed and no user to test with. Nexting");
+                next;
+            }
 
             # We don't have an SSO login, so we will be using the credentials given
             # on RT's login page to do our authentication.
             $username = $given_user;
-    
+
             # Don't continue unless the service works.
-	    # next unless RT::Authen::ExternalAuth::TestConnection($config);
+            # next unless RT::Authen::ExternalAuth::TestConnection($config);
 
             # Don't continue unless the $username exists in the external service
 
-	    $RT::Logger->debug("Calling UserExists with \$username ($username) and \$service ($service)");
+            $RT::Logger->debug("Calling UserExists with \$username ($username) and \$service ($service)");
             next unless RT::Authen::ExternalAuth::UserExists($username, $service);
         }
 
@@ -426,8 +426,8 @@ sub DoAuth {
 
         # Unless we have loaded a valid user with a UserID create one.
         unless ($session->{'CurrentUser'}->Id) {
-			my $UserObj = RT::User->new($RT::SystemUser);
-        	my ($val, $msg) = 
+                        my $UserObj = RT::User->new($RT::SystemUser);
+                my ($val, $msg) =
               $UserObj->Create(%{ref($RT::AutoCreate) ? $RT::AutoCreate : {}},
                                Name   => $username,
                                Gecos  => $username,
@@ -441,13 +441,13 @@ sub DoAuth {
                                 "(",
                                 $UserObj->Id,
                                 ")");
-            
+
             $RT::Logger->debug("Loading new user (",
-            					$username,
-            					") into current session");
+                                                $username,
+                                                ") into current session");
             $session->{'CurrentUser'}->Load($username);
-        } 
-        
+        }
+
         ####################################################################
         ########## Authentication ##########################################
         ####################################################################
@@ -455,21 +455,21 @@ sub DoAuth {
         # succeeded. If we didn't then, success is determined by a password
         # test.
         $success = 0;
-	if($pass_bypass) {
+        if($pass_bypass) {
             $RT::Logger->debug("Password check bypassed due to SSO method being in use");
             $success = 1;
         } else {
             $RT::Logger->debug("Password validation required for service - Executing...");
             $success = RT::Authen::ExternalAuth::GetAuth($service,$username,$given_pass);
         }
-       
+
         $RT::Logger->debug("Password Validation Check Result: ",$success);
 
         # If the password check succeeded then this is our authoritative service
         # and we proceed to user information update and login.
         last if $success;
     }
-    
+
     # If we got here and don't have a user loaded we must have failed to
     # get a full, valid user from an authoritative external source.
     unless ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id) {
@@ -479,37 +479,37 @@ sub DoAuth {
 
     unless($success) {
         $session->{'CurrentUser'} = RT::CurrentUser->new;
-	return (0, "Password Invalid");
+        return (0, "Password Invalid");
     }
-    
+
     # Otherwise we succeeded.
     $RT::Logger->debug("Authentication successful. Now updating user information and attempting login.");
-        
+
     ####################################################################################################
     ############################### The following is auth-method agnostic ##############################
     ####################################################################################################
-    
+
     # If we STILL have a completely valid RT user to play with...
     # and therefore password has been validated...
     if ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id) {
-        
+
         # Even if we have JUST created the user in RT, we are going to
         # reload their information from an external source. This allows us
         # to be sure that the user the cookie gave us really does exist in
-        # the database, but more importantly, UpdateFromExternal will check 
-        # whether the user is disabled or not which we have not been able to 
+        # the database, but more importantly, UpdateFromExternal will check
+        # whether the user is disabled or not which we have not been able to
         # do during auto-create
 
-	# These are not currently used, but may be used in the future.
-	my $info_updated = 0;
-	my $info_updated_msg = "User info not updated";
+        # These are not currently used, but may be used in the future.
+        my $info_updated = 0;
+        my $info_updated_msg = "User info not updated";
 
         unless($no_info_check) {
             # Note that UpdateUserInfo does not care how we authenticated the user
             # It will look up user info from whatever is specified in $RT::ExternalInfoPriority
             ($info_updated,$info_updated_msg) = RT::Authen::ExternalAuth::UpdateUserInfo($session->{'CurrentUser'}->Name);
         }
-                
+
         # Now that we definitely have up-to-date user information,
         # if the user is disabled, kick them out. Now!
         if ($session->{'CurrentUser'}->UserObj->Disabled) {
@@ -517,12 +517,12 @@ sub DoAuth {
             return (0, "User account disabled, login denied");
         }
     }
-    
+
     # If we **STILL** have a full user and the session hasn't already been deleted
     # This If/Else is logically unnecessary, but it doesn't hurt to leave it here
     # just in case. Especially to be a double-check to future modifications.
     if ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id) {
-            
+
             $RT::Logger->info(  "Successful login for",
                                 $session->{'CurrentUser'}->Name,
                                 "from",
@@ -537,10 +537,10 @@ sub DoAuth {
             # Make SURE the session is purged to an empty user.
             $session->{'CurrentUser'} = RT::CurrentUser->new;
             return (0, "Failed to authenticate externally");
-            # This will cause autohandler to request IsPassword 
+            # This will cause autohandler to request IsPassword
             # which will in turn call IsExternalPassword
     }
-    
+
     return (1, "Successful login");
 }
 
@@ -552,18 +552,18 @@ sub UpdateUserInfo {
     my $updated         = 0;
     my $msg             = "User NOT updated";
 
-    my $user_disabled 	= RT::Authen::ExternalAuth::UserDisabled($username);
+    my $user_disabled   = RT::Authen::ExternalAuth::UserDisabled($username);
 
     my $UserObj = RT::User->new($RT::SystemUser);
-    $UserObj->Load($username);        
+    $UserObj->Load($username);
 
     # If user is disabled, set the RT::Principal to disabled and return out of the function.
     # I think it's a waste of time and energy to update a user's information if they are disabled
-    # and it could be a security risk if they've updated their external information with some 
-    # carefully concocted code to try to break RT - worst case scenario, but they have been 
+    # and it could be a security risk if they've updated their external information with some
+    # carefully concocted code to try to break RT - worst case scenario, but they have been
     # denied access after all, don't take any chances.
-     
-    # If someone gives me a good enough reason to do it, 
+
+    # If someone gives me a good enough reason to do it,
     # then I'll update all the info for disabled users
 
     if ($user_disabled) {
@@ -573,14 +573,14 @@ sub UpdateUserInfo {
             # Log what has happened
             $RT::Logger->info("User marked as DISABLED (",
                                 $username,
-                                ") per External Service", 
+                                ") per External Service",
                                 "($val, $message)\n");
             $msg = "User Disabled";
         }
 
         return ($updated, $msg);
-    }    
-        
+    }
+
     # Make sure principal is not disabled in RT
     if ( $UserObj->Disabled ) {
         my ($val, $message) = $UserObj->SetDisabled(0);
@@ -623,27 +623,27 @@ sub GetAuth {
 
     # Request a username/password check from the specified service
     # This is only valid for non-SSO services.
-    
+
     my ($service,$username,$password) = @_;
-    
+
     my $success = 0;
-    
+
     # Get the full configuration for that service as a hashref
     my $config = $RT::ExternalSettings->{$service};
-    
+
     # And then act accordingly depending on what type of service it is.
     # Right now, there is only code for DBI and LDAP non-SSO services
-    if ($config->{'type'} eq 'db') {    
+    if ($config->{'type'} eq 'db') {
         $success = RT::Authen::ExternalAuth::DBI::GetAuth($service,$username,$password);
-	$RT::Logger->debug("DBI password validation result:",$success);
+        $RT::Logger->debug("DBI password validation result:",$success);
     } elsif ($config->{'type'} eq 'ldap') {
         $success = RT::Authen::ExternalAuth::LDAP::GetAuth($service,$username,$password);
-	$RT::Logger->debug("LDAP password validation result:",$success);
+        $RT::Logger->debug("LDAP password validation result:",$success);
     } else {
         $RT::Logger->error("Invalid service type for GetAuth:",$service);
     }
-    
-    return $success; 
+
+    return $success;
 }
 
 sub UserExists {
@@ -672,10 +672,10 @@ sub UserExists {
 }
 
 sub UserDisabled {
-    
+
     my $username = shift;
     my $user_disabled = 0;
-    
+
     my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
 
     # For each named service in the list
@@ -684,10 +684,10 @@ sub UserDisabled {
     # If found, check to see if user is considered disabled by the service
     # Then update the user's info in RT and return
     foreach my $service (@info_services) {
-        
-        # Get the external config for this service as a hashref        
+
+        # Get the external config for this service as a hashref
         my $config = $RT::ExternalSettings->{$service};
-        
+
         # If the config doesn't exist, don't bother doing anything, skip to next in list.
         unless(defined($config)) {
             $RT::Logger->debug("You haven't defined a configuration for the service named \"",
@@ -695,10 +695,10 @@ sub UserDisabled {
                                 "\" so I'm not going to try to get user information from it. Skipping...");
             next;
         }
-        
+
         # If it's a DBI config:
         if ($config->{'type'} eq 'db') {
-            
+
             unless(RT::Authen::ExternalAuth::DBI::UserExists($username,$service)) {
                 $RT::Logger->debug("User (",
                                     $username,
@@ -708,9 +708,9 @@ sub UserDisabled {
                 next;
             }
             $user_disabled = RT::Authen::ExternalAuth::DBI::UserDisabled($username,$service);
-            
+
         } elsif ($config->{'type'} eq 'ldap') {
-            
+
             unless(RT::Authen::ExternalAuth::LDAP::UserExists($username,$service)) {
                 $RT::Logger->debug("User (",
                                     $username,
@@ -720,7 +720,7 @@ sub UserDisabled {
                 next;
             }
             $user_disabled = RT::Authen::ExternalAuth::LDAP::UserDisabled($username,$service);
-                    
+
         } elsif ($config->{'type'} eq 'cookie') {
             RT::Logger->error("You cannot use SSO Cookies as an information service.");
             next;
@@ -730,30 +730,30 @@ sub UserDisabled {
             # Drop out to next service in list
             next;
         }
-    
+
     }
     return $user_disabled;
 }
 
 sub CanonicalizeUserInfo {
-    
+
     # Careful, this $args hashref was given to RT::User::CanonicalizeUserInfo and
     # then transparently passed on to this function. The whole purpose is to update
     # the original hash as whatever passed it to RT::User is expecting to continue its
     # code with an update args hash.
-    
+
     my $UserObj = shift;
     my $args    = shift;
-    
+
     my $found   = 0;
     my %params  = (Name         => undef,
                   EmailAddress => undef,
                   RealName     => undef);
-    
-    $RT::Logger->debug( (caller(0))[3], 
-                        "called by", 
-                        caller, 
-                        "with:", 
+
+    $RT::Logger->debug( (caller(0))[3],
+                        "called by",
+                        caller,
+                        "with:",
                         join(", ", map {sprintf("%s: %s", $_, ($args->{$_} ? $args->{$_} : ''))}
                             sort(keys(%$args))));
 
@@ -761,18 +761,18 @@ sub CanonicalizeUserInfo {
     my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : undef;
     # For each external service...
     foreach my $service (@info_services) {
-        
+
         $RT::Logger->debug( "Attempting to get user info using this external service:",
                             $service);
-        
+
         # 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;
-        }  
-        
+        }
+
         # 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
@@ -785,11 +785,11 @@ sub CanonicalizeUserInfo {
                                     ")");
                 next;
             }
-                               
-            # Else, use it as a canonicalization key and lookup the user info    
+
+            # 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);
@@ -805,12 +805,12 @@ sub CanonicalizeUserInfo {
                                     ")");
                 next;
             }
-            
-            # Use an if/elsif structure to do a lookup with any custom code needed 
+
+            # 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'){    
+
+            if($config->{'type'} eq 'ldap'){
                 ($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);
@@ -820,36 +820,36 @@ sub CanonicalizeUserInfo {
                                     $service,
                                     "a valid information service");
             }
-       
+
             # Don't Check any more attributes
             last if $found;
         }
         # Don't Check any more services
         last if $found;
     }
-    
-    # If found, Canonicalize Email Address and 
+
+    # 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);
     }
 
-    $RT::Logger->info(  (caller(0))[3], 
-                        "returning", 
+    $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 
+    ### a different config var (CreateUncanonicalizedUsers) and
     ### should be honored in RT::User::Create()
     return($found || $RT::AutoCreateNonExternalUsers);
-   
+
 }
 
 {
diff --git a/lib/RT/Authen/ExternalAuth/DBI.pm b/lib/RT/Authen/ExternalAuth/DBI.pm
index f6f5c4d..7caa4da 100644
--- a/lib/RT/Authen/ExternalAuth/DBI.pm
+++ b/lib/RT/Authen/ExternalAuth/DBI.pm
@@ -152,13 +152,13 @@ Otherwise, they will be considered enabled.
 sub GetAuth {
 
     my ($service, $username, $password) = @_;
-    
+
     my $config = $RT::ExternalSettings->{$service};
     $RT::Logger->debug( "Trying external auth service:",$service);
 
     my $db_table        = $config->{'table'};
     my $db_u_field      = $config->{'u_field'};
-    my $db_p_field 	    = $config->{'p_field'};
+    my $db_p_field          = $config->{'p_field'};
     my $db_p_check      = $config->{'p_check'};
     my $db_p_enc_pkg    = $config->{'p_enc_pkg'};
     my $db_p_enc_sub    = $config->{'p_enc_sub'};
@@ -167,20 +167,20 @@ sub GetAuth {
     # Set SQL query and bind parameters
     my $query = "SELECT $db_u_field,$db_p_field FROM $db_table WHERE $db_u_field=?";
     my @params = ($username);
-    
+
     # Uncomment this to trace basic DBI information and drop it in a log for debugging
     # DBI->trace(1,'/tmp/dbi.log');
 
     # Get DBI handle object (DBH), do SQL query, kill DBH
     my $dbh = _GetBoundDBIObj($config);
     return 0 unless $dbh;
-    
+
     my $results_hashref = $dbh->selectall_hashref($query,$db_u_field,{}, at params);
     $dbh->disconnect();
 
     my $num_users_returned = scalar keys %$results_hashref;
     if($num_users_returned != 1) { # FAIL
-        # FAIL because more than one user returned. Users MUST be unique! 
+        # FAIL because more than one user returned. Users MUST be unique!
         if ((scalar keys %$results_hashref) > 1) {
             $RT::Logger->info(  $service,
                                 "AUTH FAILED",
@@ -188,7 +188,7 @@ sub GetAuth {
                                 "More than one user with that username!");
         }
 
-        # FAIL because no users returned. Users MUST exist! 
+        # FAIL because no users returned. Users MUST exist!
         if ((scalar keys %$results_hashref) < 1) {
             $RT::Logger->info(  $service,
                                 "AUTH FAILED",
@@ -196,12 +196,12 @@ sub GetAuth {
                                 "User not found in database!");
         }
 
-	    # Drop out to next external authentication service
-	    return 0;
+            # Drop out to next external authentication service
+            return 0;
     }
-    
+
     # Get the user's password from the database query result
-    my $pass_from_db = $results_hashref->{$username}->{$db_p_field};        
+    my $pass_from_db = $results_hashref->{$username}->{$db_p_field};
 
     if ( $db_p_check ) {
         unless ( ref $db_p_check eq 'CODE' ) {
@@ -222,10 +222,10 @@ sub GetAuth {
                 "$service AUTH FAILED for $username: Password Incorrect (via p_check)"
             );
         } else {
-            $RT::Logger->info(  (caller(0))[3], 
+            $RT::Logger->info(  (caller(0))[3],
                                 "External Auth OK (",
                                 $service,
-                                "):", 
+                                "):",
                                 $username);
         }
         return $check;
@@ -240,9 +240,9 @@ sub GetAuth {
     # Use config info to auto-load the perl package needed for password encryption
     # I know it uses a string eval - but I don't think there's a better way to do this
     # Jump to next external authentication service on failure
-    eval "require $db_p_enc_pkg" or 
+    eval "require $db_p_enc_pkg" or
         $RT::Logger->error("AUTH FAILED, Couldn't Load Password Encryption Package. Error: $@") && return 0;
-    
+
     my $encrypt = $db_p_enc_pkg->can($db_p_enc_sub);
     if (defined($encrypt)) {
         # If the package given can perform the subroutine given, then use it to compare the
@@ -252,16 +252,16 @@ sub GetAuth {
             $RT::Logger->debug("Using salt:",$db_p_salt);
             if(${encrypt}->($password,$db_p_salt) ne $pass_from_db){
                 $RT::Logger->info(  $service,
-                                    "AUTH FAILED", 
-                                    $username, 
+                                    "AUTH FAILED",
+                                    $username,
                                     "Password Incorrect");
                 return 0;
             }
         } else {
             if(${encrypt}->($password) ne $pass_from_db){
                 $RT::Logger->info(  $service,
-                                    "AUTH FAILED", 
-                                    $username, 
+                                    "AUTH FAILED",
+                                    $username,
                                     "Password Incorrect");
                 return 0;
             }
@@ -278,31 +278,31 @@ sub GetAuth {
                             ")");
             return 0;
     }
-    
+
     # Any other checks you want to add? Add them here.
 
     # If we've survived to this point, we're good.
-    $RT::Logger->info(  (caller(0))[3], 
+    $RT::Logger->info(  (caller(0))[3],
                         "External Auth OK (",
                         $service,
-                        "):", 
+                        "):",
                         $username);
-    
-    return 1;   
+
+    return 1;
 }
 
 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};
-    
+
     # Figure out what's what
     my $table      = $config->{'table'};
 
@@ -319,7 +319,7 @@ sub CanonicalizeUserInfo {
         # Drop out to the next external information service
         return ($found, %params);
     }
-    
+
     # "where" refers to WHERE section of SQL query
     my ($where_key,$where_value) = ("@{[ $key ]}",$value);
 
@@ -339,10 +339,10 @@ sub CanonicalizeUserInfo {
     if ((scalar keys %$results_hashref) != 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 ((scalar keys %$results_hashref) == 0) {
             # If no user...
-	        $death_msg = "No User Found in External Database!";
+                $death_msg = "No User Found in External Database!";
         } else {
             # If more than one user...
             $death_msg = "More than one user found in External Database with that unique identifier!";
@@ -354,47 +354,47 @@ sub CanonicalizeUserInfo {
                             "Key: $key",
                             "Value: $value",
                             $death_msg);
-        
+
         # $found remains as 0
-        
+
         # Drop out to next external information service
         return ($found, %params);
     }
 
-    # We haven't dropped out, so DB search must have succeeded with 
+    # 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;
-  
+
     return ($found, %params);
 }
 
 sub UserExists {
-    
+
     my ($username,$service) = @_;
     my $config              = $RT::ExternalSettings->{$service};
-    my $table    	        = $config->{'table'};
-    my $u_field	            = $config->{'u_field'};
+    my $table                   = $config->{'table'};
+    my $u_field             = $config->{'u_field'};
     my $query               = "SELECT $u_field FROM $table WHERE $u_field=?";
     my @bind_params         = ($username);
 
     # Uncomment this to do a basic trace on DBI information and log it
     # DBI->trace(1,'/tmp/dbi.log');
-    
+
     # Get DBI Object, do the query, disconnect
     my $dbh = _GetBoundDBIObj($config);
     my $results_hashref = $dbh->selectall_hashref($query,$u_field,{}, at bind_params);
     $dbh->disconnect();
 
     my $num_of_results = scalar keys %$results_hashref;
-        
-    if ($num_of_results > 1) { 
+
+    if ($num_of_results > 1) {
         # If more than one result returned, die because we the username field should be unique!
         $RT::Logger->debug( "Disable Check Failed :: (",
                             $service,
@@ -402,34 +402,34 @@ sub UserExists {
                             $username,
                             "More than one user with that username!");
         return 0;
-    } elsif ($num_of_results < 1) { 
+    } elsif ($num_of_results < 1) {
         # If 0 or negative integer, no user found or major failure
         $RT::Logger->debug( "Disable Check Failed :: (",
                             $service,
                             ")",
                             $username,
-                            "User not found");   
-        return 0; 
+                            "User not found");
+        return 0;
     }
-    
+
     # Number of results is exactly one, so we found the user we were looking for
-    return 1;            
+    return 1;
 }
 
 sub UserDisabled {
 
     my ($username,$service) = @_;
-    
+
     # FIRST, check that the user exists in the DBI service
     unless(UserExists($username,$service)) {
         $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking");
         return 0;
     }
-    
+
     # Get the necessary config info
     my $config              = $RT::ExternalSettings->{$service};
-    my $table    	        = $config->{'table'};
-    my $u_field	            = $config->{'u_field'};
+    my $table                   = $config->{'table'};
+    my $u_field             = $config->{'u_field'};
     my $disable_field       = $config->{'d_field'};
     my $disable_values_list = $config->{'d_values'};
 
@@ -439,22 +439,22 @@ sub UserDisabled {
                             $service,
                             "), so considering all users enabled");
         return 0;
-    } 
-    
+    }
+
     my $query = "SELECT $u_field,$disable_field FROM $table WHERE $u_field=?";
     my @bind_params = ($username);
 
     # Uncomment this to do a basic trace on DBI information and log it
     # DBI->trace(1,'/tmp/dbi.log');
-    
+
     # Get DBI Object, do the query, disconnect
     my $dbh = _GetBoundDBIObj($config);
     my $results_hashref = $dbh->selectall_hashref($query,$u_field,{}, at bind_params);
     $dbh->disconnect();
 
     my $num_of_results = scalar keys %$results_hashref;
-        
-    if ($num_of_results > 1) { 
+
+    if ($num_of_results > 1) {
         # If more than one result returned, die because we the username field should be unique!
         $RT::Logger->debug( "Disable Check Failed :: (",
                             $service,
@@ -463,33 +463,33 @@ sub UserDisabled {
                             "More than one user with that username! - Assuming not disabled");
         # Drop out to next service for an info check
         return 0;
-    } elsif ($num_of_results < 1) { 
+    } elsif ($num_of_results < 1) {
         # If 0 or negative integer, no user found or major failure
         $RT::Logger->debug( "Disable Check Failed :: (",
                             $service,
                             ")",
                             $username,
-                            "User not found - Assuming not disabled");   
+                            "User not found - Assuming not disabled");
         # Drop out to next service for an info check
-        return 0;             
-    } else { 
+        return 0;
+    } else {
         # otherwise all should be well
-        
+
         # $user_db_disable_value = The value for "disabled" returned from the DB
         my $user_db_disable_value = $results_hashref->{$username}->{$disable_field};
-        
+
         # For each of the values in the (list of values that we consider to mean the user is disabled)..
         foreach my $disable_value (@{$disable_values_list}){
-            $RT::Logger->debug( "DB Disable Check:", 
+            $RT::Logger->debug( "DB Disable Check:",
                                 "User's Val is $user_db_disable_value,",
                                 "Checking against: $disable_value");
-            
+
             # If the value from the DB matches a value from the list, the user is disabled.
             if ($user_db_disable_value eq $disable_value) {
                 return 1;
             }
         }
-        
+
         # If we've not returned yet, the user can't be disabled
         return 0;
     }
@@ -500,8 +500,8 @@ sub UserDisabled {
 sub GetCookieAuth {
 
     $RT::Logger->debug( (caller(0))[3],
-	                "Checking Browser Cookies for an Authenticated User");
-			     
+                        "Checking Browser Cookies for an Authenticated User");
+
     # Get our cookie and database info...
     my $config = shift;
 
@@ -594,8 +594,8 @@ sub GetCookieAuth {
 # {{{ sub _GetBoundDBIObj
 
 sub _GetBoundDBIObj {
-    
-    # Config as hashref. 
+
+    # Config as hashref.
     my $config = shift;
 
     # Extract the relevant information from the config.
@@ -619,8 +619,8 @@ sub _GetBoundDBIObj {
     my $dbh = DBI->connect($dsn, $db_user, $db_pass,{RaiseError => 1, AutoCommit => 0 })
             or die $DBI::errstr;
 
-    # If we didn't die, return the DBI object handle 
-    # and hope it's treated sensibly and correctly 
+    # If we didn't die, return the DBI object handle
+    # and hope it's treated sensibly and correctly
     # destroyed by the calling code
     return $dbh;
 }
diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index cc92410..1e1b228 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -144,9 +144,9 @@ What other args should be passed to Net::LDAP->new($host, at args)?
 =cut
 
 sub GetAuth {
-    
+
     my ($service, $username, $password) = @_;
-    
+
     my $config = $RT::ExternalSettings->{$service};
     $RT::Logger->debug( "Trying external auth service:",$service);
 
@@ -172,21 +172,21 @@ sub GetAuth {
     my $ldap = _GetBoundLdapObj($config);
     return 0 unless ($ldap);
 
-    $filter = Net::LDAP::Filter->new(   '(&(' . 
-                                        $attr_map->{'Name'} . 
-                                        '=' . 
-                                        escape_filter_value($username) . 
-                                        ')' . 
-                                        $filter . 
+    $filter = Net::LDAP::Filter->new(   '(&(' .
+                                        $attr_map->{'Name'} .
+                                        '=' .
+                                        escape_filter_value($username) .
+                                        ')' .
+                                        $filter .
                                         ')'
                                     );
 
     $RT::Logger->debug( "LDAP Search === ",
                         "Base:",
                         $base,
-                        "== Filter:", 
+                        "== Filter:",
                         $filter->as_string,
-                        "== Attrs:", 
+                        "== Attrs:",
                         join(',', at attrs));
 
     my $ldap_msg = $ldap->search(   base   => $base,
@@ -194,10 +194,10 @@ sub GetAuth {
                                     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), 
+        $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;
@@ -205,7 +205,7 @@ sub GetAuth {
 
     unless ($ldap_msg->count == 1) {
         $RT::Logger->info(  $service,
-                            "AUTH FAILED:", 
+                            "AUTH FAILED:",
                             $username,
                             "User not found or more than one user found");
         # We got no user, or too many users.. jump straight to the next external auth service
@@ -215,7 +215,7 @@ sub GetAuth {
     my $ldap_entry = $ldap_msg->first_entry;
     my $ldap_dn    = $ldap_entry->dn;
 
-    $RT::Logger->debug( "Found LDAP DN:", 
+    $RT::Logger->debug( "Found LDAP DN:",
                         $ldap_dn);
 
     # THIS bind determines success or failure on the password.
@@ -223,11 +223,11 @@ sub GetAuth {
 
     unless ($ldap_msg->code == LDAP_SUCCESS) {
         $RT::Logger->info(  $service,
-                            "AUTH FAILED", 
-                            $username, 
-                            "(can't bind:", 
-                            ldap_error_name($ldap_msg->code), 
-                            $ldap_msg->code, 
+                            "AUTH FAILED",
+                            $username,
+                            "(can't bind:",
+                            ldap_error_name($ldap_msg->code),
+                            $ldap_msg->code,
                             ")");
         # Could not bind to the LDAP server as the user we found with the password
         # we were given, therefore the password must be wrong so we fail and
@@ -250,29 +250,29 @@ 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,
                             "== Scope:",
                             $group_scope,
-                            "== Filter:", 
+                            "== Filter:",
                             $filter->as_string,
-                            "== Attrs:", 
+                            "== Attrs:",
                             join(',', at attrs));
-        
+
         $ldap_msg = $ldap->search(  base   => $group,
                                     filter => $filter,
                                     attrs  => \@attrs,
                                     scope  => $group_scope);
 
         # And the user isn't a member:
-        unless ($ldap_msg->code == LDAP_SUCCESS || 
+        unless ($ldap_msg->code == LDAP_SUCCESS ||
                 $ldap_msg->code == LDAP_PARTIAL_RESULTS) {
-            $RT::Logger->critical(  "Search for", 
-                                    $filter->as_string, 
+            $RT::Logger->critical(  "Search for",
+                                    $filter->as_string,
                                     "failed:",
-                                    ldap_error_name($ldap_msg->code), 
+                                    ldap_error_name($ldap_msg->code),
                                     $ldap_msg->code);
 
             # Fail auth - jump to next external auth service
@@ -285,21 +285,21 @@ sub GetAuth {
                 $ldap_msg->count, "results"
             );
             $RT::Logger->info(  $service,
-                                "AUTH FAILED:", 
+                                "AUTH FAILED:",
                                 $username);
-                                
+
             # Fail auth - jump to next external auth service
             return 0;
         }
     }
-    
+
     # Any other checks you want to add? Add them here.
 
     # If we've survived to this point, we're good.
-    $RT::Logger->info(  (caller(0))[3], 
+    $RT::Logger->info(  (caller(0))[3],
                         "External Auth OK (",
                         $service,
-                        "):", 
+                        "):",
                         $username);
     return 1;
 
@@ -307,7 +307,7 @@ sub GetAuth {
 
 
 sub CanonicalizeUserInfo {
-    
+
     my ($service, $key, $value) = @_;
 
     my $found = 0;
@@ -317,7 +317,7 @@ sub CanonicalizeUserInfo {
 
     # Load the config
     my $config = $RT::ExternalSettings->{$service};
-   
+
     # Figure out what's what
     my $base            = $config->{'base'};
     my $filter          = $config->{'filter'};
@@ -328,11 +328,11 @@ sub CanonicalizeUserInfo {
     # 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 . 
+        $filter = Net::LDAP::Filter->new(   "(&" .
+                                            $filter .
+                                            $filter_addition .
                                             ")"
-                                        ); 
+                                        );
     } else {
         $RT::Logger->debug( "LDAP Filter invalid or not present.");
     }
@@ -347,7 +347,7 @@ sub CanonicalizeUserInfo {
     # Get a Net::LDAP object based on the config we provide
     my $ldap = _GetBoundLdapObj($config);
 
-    # Jump to the next external information service if we can't get one, 
+    # 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);
 
@@ -355,9 +355,9 @@ sub CanonicalizeUserInfo {
     $RT::Logger->debug( "LDAP Search === ",
                         "Base:",
                         $base,
-                        "== Filter:", 
+                        "== Filter:",
                         $filter->as_string,
-                        "== Attrs:", 
+                        "== Attrs:",
                         join(',', at attrs));
 
     my $ldap_msg = $ldap->search(base   => $base,
@@ -365,28 +365,28 @@ sub CanonicalizeUserInfo {
                                  attrs  => \@attrs);
 
     # If we didn't get at LEAST a partial result, just die now.
-    if ($ldap_msg->code != LDAP_SUCCESS and 
+    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_error_name($ldap_msg->code),
                                 $ldap_msg->code);
         # $found remains as 0
-        
+
         # 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), 
+                                    ": Could not unbind: ",
+                                    ldap_error_name($ldap_msg->code),
                                     $ldap_msg->code);
         }
         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.
@@ -399,7 +399,7 @@ sub CanonicalizeUserInfo {
                 if ($RT::LdapAttrMap and $RT::LdapAttrMap->{$key} eq 'dn') {
                     $params{$key} = $entry->dn();
                 } else {
-                    $params{$key} = 
+                    $params{$key} =
                       ($entry->get_value($config->{'attr_map'}->{$key}))[0];
                 }
             }
@@ -409,8 +409,8 @@ sub CanonicalizeUserInfo {
             $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), 
+                                        ": Could not unbind: ",
+                                        ldap_error_name($ldap_msg->code),
                                         $ldap_msg->code);
             }
             undef $ldap;
@@ -421,8 +421,8 @@ sub CanonicalizeUserInfo {
     $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), 
+                                ": Could not unbind: ",
+                                ldap_error_name($ldap_msg->code),
                                 $ldap_msg->code);
     }
 
@@ -434,9 +434,9 @@ sub CanonicalizeUserInfo {
 
 sub UserExists {
     my ($username,$service) = @_;
-   $RT::Logger->debug("UserExists params:\nusername: $username , service: $service"); 
+   $RT::Logger->debug("UserExists params:\nusername: $username , service: $service");
     my $config              = $RT::ExternalSettings->{$service};
-    
+
     my $base                = $config->{'base'};
     my $filter              = $config->{'filter'};
 
@@ -448,12 +448,12 @@ sub UserExists {
 
     if (defined($config->{'attr_map'}->{'Name'})) {
         # Construct the complex filter
-        $filter = Net::LDAP::Filter->new(           '(&' . 
-                                                    $filter . 
-                                                    '(' . 
-                                                    $config->{'attr_map'}->{'Name'} . 
-                                                    '=' . 
-                                                    escape_filter_value($username) . 
+        $filter = Net::LDAP::Filter->new(           '(&' .
+                                                    $filter .
+                                                    '(' .
+                                                    $config->{'attr_map'}->{'Name'} .
+                                                    '=' .
+                                                    escape_filter_value($username) .
                                                     '))'
                                         );
     }
@@ -467,11 +467,11 @@ sub UserExists {
     $RT::Logger->debug( "LDAP Search === ",
                         "Base:",
                         $base,
-                        "== Filter:", 
+                        "== Filter:",
                         $filter->as_string,
-                        "== Attrs:", 
+                        "== Attrs:",
                         join(',', at attrs));
-    
+
     my $user_found = $ldap->search( base    => $base,
                                     filter  => $filter,
                                     attrs   => \@attrs);
@@ -482,8 +482,8 @@ sub UserExists {
                             $service,
                             ")",
                             $username,
-                            "User not found");   
-        return 0;  
+                            "User not found");
+        return 0;
     } elsif ($user_found->count > 1) {
         # If more than one result returned, die because we the username field should be unique!
         $RT::Logger->debug( "User Check Failed :: (",
@@ -494,7 +494,7 @@ sub UserExists {
         return 0;
     }
     undef $user_found;
-    
+
     # If we havent returned now, there must be a valid user.
     return 1;
 }
@@ -508,7 +508,7 @@ sub UserDisabled {
         $RT::Logger->debug("User (",$username,") doesn't exist! - Assuming not disabled for the purposes of disable checking");
         return 0;
     }
-    
+
     my $config          = $RT::ExternalSettings->{$service};
     my $base            = $config->{'base'};
     my $filter          = $config->{'filter'};
@@ -532,13 +532,13 @@ sub UserDisabled {
 
     if (defined($config->{'attr_map'}->{'Name'})) {
         # Construct the complex filter
-        $search_filter = Net::LDAP::Filter->new(   '(&' . 
-                                                    $filter . 
-                                                    $d_filter . 
-                                                    '(' . 
-                                                    $config->{'attr_map'}->{'Name'} . 
-                                                    '=' . 
-                                                    escape_filter_value($username) . 
+        $search_filter = Net::LDAP::Filter->new(   '(&' .
+                                                    $filter .
+                                                    $d_filter .
+                                                    '(' .
+                                                    $config->{'attr_map'}->{'Name'} .
+                                                    '=' .
+                                                    escape_filter_value($username) .
                                                     '))'
                                                 );
     } else {
@@ -548,28 +548,28 @@ sub UserDisabled {
                             $username,
                             ") so I'm just going to assume the user is not disabled");
         return 0;
-        
+
     }
 
     my $ldap = _GetBoundLdapObj($config);
     next unless $ldap;
 
-    # We only need the UID for confirmation now, 
+    # We only need the UID for confirmation now,
     # the other information would waste time and bandwidth
-    my @attrs = ('uid'); 
-    
+    my @attrs = ('uid');
+
     $RT::Logger->debug( "LDAP Search === ",
                         "Base:",
                         $base,
-                        "== Filter:", 
+                        "== Filter:",
                         $search_filter->as_string,
-                        "== Attrs:", 
+                        "== Attrs:",
                         join(',', at attrs));
-          
-    my $disabled_users = $ldap->search(base   => $base, 
-                                       filter => $search_filter, 
+
+    my $disabled_users = $ldap->search(base   => $base,
+                                       filter => $search_filter,
                                        attrs  => \@attrs);
-    # If ANY results are returned, 
+    # If ANY results are returned,
     # we are going to assume the user should be disabled
     if ($disabled_users->count) {
         undef $disabled_users;
@@ -593,9 +593,9 @@ sub _GetBoundLdapObj {
     my $ldap_tls        = $config->{'tls'};
     my $ldap_ssl_ver    = $config->{'ssl_version'};
     my $ldap_args       = $config->{'net_ldap_args'};
-    
+
     my $ldap = new Net::LDAP($ldap_server, @$ldap_args);
-    
+
     unless ($ldap) {
         $RT::Logger->critical(  (caller(0))[3],
                                 ": Cannot connect to",
@@ -609,7 +609,7 @@ sub _GetBoundLdapObj {
         # Thanks to David Narayan for the fault tolerance bits
         eval { $ldap->start_tls; };
         if ($@) {
-            $RT::Logger->critical(  (caller(0))[3], 
+            $RT::Logger->critical(  (caller(0))[3],
                                     "Can't start TLS: ",
                                     $@);
             return;
@@ -628,9 +628,9 @@ sub _GetBoundLdapObj {
     }
 
     unless ($msg->code == LDAP_SUCCESS) {
-        $RT::Logger->critical(  (caller(0))[3], 
-                                "Can't bind:", 
-                                ldap_error_name($msg->code), 
+        $RT::Logger->critical(  (caller(0))[3],
+                                "Can't bind:",
+                                ldap_error_name($msg->code),
                                 $msg->code);
         return undef;
     } else {

commit d65d526935a616fb31eaf057d413d3812a1c35b0
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 17:07:28 2014 -0400

    Note that getting passed CurrentUser is fixed on a branch

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index daf9583..f0bd2db 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -298,7 +298,7 @@ use strict;
 $RT::Config::META{ExternalSettings}->{Obfuscate} = sub {
     my ($config, $sources, $user) = @_;
 
-    # XXX $user is never passed from RT as of 4.0.5 :(
+    # $user is only passed in versions of RT with 3c7db050
     my $msg = 'Password not printed';
        $msg = $user->loc($msg) if $user and $user->Id;
 

commit ccb964b56cd0f3cdf7a2dd9d089d84f4f45d2a68
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 17:07:37 2014 -0400

    Add a missing close paren

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index f0bd2db..b689352 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -317,7 +317,7 @@ sub DoAuth {
 
     my $no_info_check = 0;
     unless(defined($RT::ExternalInfoPriority)) {
-        $RT::Logger->debug("ExternalInfoPriority not defined. User information (including user enabled/disabled cannot be externally-sourced");
+        $RT::Logger->debug("ExternalInfoPriority not defined. User information (including user enabled/disabled) cannot be externally-sourced");
         $no_info_check = 1;
     }
 

commit 8ac8af83b5ac5dd771fe8925017606d60bce3475
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 18:48:50 2014 -0400

    Fix a variable name in the POD

diff --git a/README b/README
index 1883dbe..27944eb 100644
--- a/README
+++ b/README
@@ -177,7 +177,7 @@ CONFIGURATION
         # Use the below LDAP source for both authentication, as well as user
         # information
         Set( $ExternalAuthPriority, ["My_LDAP"] );
-        Set( $ExternalAuthInfo,     ["My_LDAP"] );
+        Set( $ExternalInfoPriority, ["My_LDAP"] );
 
         # Users created from LDAP should be Privileged; this is a core RT
         # option.  Additionally, this is the 4.2 name for the option; for RT
diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index b689352..14dc1d3 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -216,7 +216,7 @@ For example, an LDAP mapping might look like:
     # Use the below LDAP source for both authentication, as well as user
     # information
     Set( $ExternalAuthPriority, ["My_LDAP"] );
-    Set( $ExternalAuthInfo,     ["My_LDAP"] );
+    Set( $ExternalInfoPriority, ["My_LDAP"] );
 
     # Users created from LDAP should be Privileged; this is a core RT
     # option.  Additionally, this is the 4.2 name for the option; for RT

commit 5c269d9df9335f89eaea5ba2730cd1e83b6aa8c5
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 18:49:24 2014 -0400

    Use RT->Config->Get/Set, and check configs at PostLoadCheck time

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 14dc1d3..4b1fac9 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -294,59 +294,120 @@ use RT::Authen::ExternalAuth::DBI;
 
 use strict;
 
-# Ensure passwords are obfuscated on the System Configuration page
-$RT::Config::META{ExternalSettings}->{Obfuscate} = sub {
-    my ($config, $sources, $user) = @_;
+$RT::Config::META{ExternalSettings} = {
+    Obfuscate => sub {
+        # Ensure passwords are obfuscated on the System Configuration page
+        my ($config, $sources, $user) = @_;
 
-    # $user is only passed in versions of RT with 3c7db050
-    my $msg = 'Password not printed';
-       $msg = $user->loc($msg) if $user and $user->Id;
+        # $user is only passed in versions of RT with 3c7db050
+        my $msg = 'Password not printed';
+           $msg = $user->loc($msg) if $user and $user->Id;
 
-    for my $source (values %$sources) {
-        $source->{pass} = $msg;
-    }
-    return $sources;
+        for my $source (values %$sources) {
+            $source->{pass} = $msg;
+        }
+        return $sources;
+    },
+    PostLoadCheck => sub {
+        my $self = shift;
+        my $settings = shift || {};
+
+        my $remove = sub {
+            my ($service) = @_;
+            delete $settings->{$service};
+
+            $self->Set( 'ExternalAuthPriority',
+                        [ grep { $_ ne $service } @{ $self->Get('ExternalAuthPriority') || [] } ] );
+
+            $self->Set( 'ExternalInfoPriority',
+                        [ grep { $_ ne $service } @{ $self->Get('ExternalInfoPriority') || [] } ] );
+        };
+
+        for my $service (keys %$settings) {
+            my %conf = %{ $settings->{$service} };
+
+            if ($conf{type} !~ /^(ldap|db|cookie)$/) {
+                $RT::Logger->error(
+                    "Service '$service' in ExternalInfoPriority is not ldap, db, or cookie; removing."
+                );
+                $remove->($service);
+                next;
+            }
+
+            next unless $conf{type} eq 'db';
+
+            # Ensure people don't misconfigure DBI auth to point to RT's
+            # Users table; only check server/hostname/table, as
+            # user/pass might be different (root, for instance)
+            no warnings 'uninitialized';
+            next unless lc $conf{server} eq lc RT->Config->Get('DatabaseHost') and
+                        lc $conf{database} eq lc RT->Config->Get('DatabaseName') and
+                        lc $conf{table} eq 'users';
+
+            $RT::Logger->error(
+                "RT::Authen::ExternalAuth should _not_ be configured with a database auth service ".
+                "that points back to RT's internal Users table.  Removing the service '$service'! ".
+                "Please remove it from your config file."
+            );
+
+            $remove->($service);
+        }
+        $self->Set( 'ExternalSettings', $settings );
+    },
 };
 
-sub DoAuth {
-    my ($session,$given_user,$given_pass) = @_;
+$RT::Config::META{ExternalAuthPriority} = {
+    PostLoadCheck => sub {
+        my $self = shift;
+        my @values = @{ shift || [] };
+        if (not @values) {
+            $self->Set( 'ExternalAuthPriority', \@values );
+            return;
+        }
 
-    unless(defined($RT::ExternalAuthPriority)) {
-        return (0, "ExternalAuthPriority not defined, please check your configuration file.");
-    }
+        my %settings = %{ $self->Get('ExternalSettings') };
+        for my $key (grep {not $settings{$_}} @values) {
+            $RT::Logger->error("Removing '$key' from ExternalAuthPriority, as it is not defined in ExternalSettings");
+        }
+        @values = grep {$settings{$_}} @values;
+        $self->Set( 'ExternalAuthPriority', \@values );
+    },
+};
 
-    my $no_info_check = 0;
-    unless(defined($RT::ExternalInfoPriority)) {
-        $RT::Logger->debug("ExternalInfoPriority not defined. User information (including user enabled/disabled) cannot be externally-sourced");
-        $no_info_check = 1;
-    }
+$RT::Config::META{ExternalInfoPriority} = {
+    PostLoadCheck => sub {
+        my $self = shift;
+        my @values = @{ shift || [] };
+        if (not @values) {
+            $RT::Logger->debug("ExternalInfoPriority not defined. User information (including user enabled/disabled) cannot be externally-sourced");
+            $self->Set( 'ExternalInfoPriority', \@values );
+            return;
+        }
 
-    # Ensure people don't misconfigure DBI auth to point to RT's Users table
-    for my $service (keys %$RT::ExternalSettings) {
-        my %conf = %{ $RT::ExternalSettings->{$service} };
-        next unless $conf{type} eq 'db';
+        my %settings = %{ $self->Get('ExternalSettings') };
+        for my $key (grep {not $settings{$_}} @values) {
+            $RT::Logger->error("Removing '$key' from ExternalInfoPriority, as it is not defined in ExternalSettings");
+        }
+        @values = grep {$settings{$_}} @values;
 
-        # user/pass might be different (root, for instance)
-        no warnings 'uninitialized';
-        next unless lc $conf{server} eq lc $RT::DatabaseHost and
-                    lc $conf{database} eq lc $RT::DatabaseName and
-                    lc $conf{table} eq 'users';
+        for my $key (grep {$settings{$_}{type} eq "cookie"} @values) {
+            $RT::Logger->error("Removing '$key' from ExternalInfoPriority, as cookie authentication cannot be used as an information source");
+        }
+        @values = grep {$settings{$_}{type} ne "cookie"} @values;
 
-        $RT::Logger->error(
-            "RT::Authen::ExternalAuth should _not_ be configured with a database auth service ".
-            "that points back to RT's internal Users table.  Removing the service '$service'! ".
-            "Please remove it from your config file."
-        );
+        $self->Set( 'ExternalInfoPriority', \@values );
+    },
+};
 
-        # Remove it!
-        delete $RT::ExternalSettings->{$service};
+sub DoAuth {
+    my ($session,$given_user,$given_pass) = @_;
 
-        @$RT::ExternalAuthPriority = grep { $_ ne $service } @$RT::ExternalAuthPriority
-            if $RT::ExternalAuthPriority;
+    # Get the prioritised list of external authentication services
+    my @auth_services = @{ RT->Config->Get('ExternalAuthPriority') };
+    my $settings = RT->Config->Get('ExternalSettings');
 
-        @$RT::ExternalInfoPriority = grep { $_ ne $service } @$RT::ExternalInfoPriority
-            if $RT::ExternalInfoPriority;
-    }
+    return (0, "ExternalAuthPriority not defined, please check your configuration file.")
+        unless @auth_services;
 
     # This may be used by single sign-on (SSO) authentication mechanisms for bypassing a password check.
     my $pass_bypass = 0;
@@ -355,11 +416,6 @@ sub DoAuth {
     # Should have checked if user is already logged in before calling this function,
     # but just in case, we'll check too.
     return (0, "User already logged in!") if ($session->{'CurrentUser'} && $session->{'CurrentUser'}->Id);
-    # We don't have a logged in user. Let's try all our available methods in order.
-    # last if success, next if not.
-
-    # Get the prioritised list of external authentication services
-    my @auth_services = @$RT::ExternalAuthPriority;
 
     # For each of those services..
     foreach my $service (@auth_services) {
@@ -367,7 +423,7 @@ sub DoAuth {
         $pass_bypass = 0;
 
         # Get the full configuration for that service as a hashref
-        my $config = $RT::ExternalSettings->{$service};
+        my $config = $settings->{$service};
         $RT::Logger->debug( "Attempting to use external auth service:",
                             $service);
 
@@ -426,12 +482,13 @@ sub DoAuth {
 
         # Unless we have loaded a valid user with a UserID create one.
         unless ($session->{'CurrentUser'}->Id) {
-                        my $UserObj = RT::User->new($RT::SystemUser);
-                my ($val, $msg) =
-              $UserObj->Create(%{ref($RT::AutoCreate) ? $RT::AutoCreate : {}},
-                               Name   => $username,
-                               Gecos  => $username,
-                              );
+            my $UserObj = RT::User->new($RT::SystemUser);
+            my $create = RT->Config->Get('AutoCreate');
+            my ($val, $msg) =
+                $UserObj->Create(%{ref($create) ? $create : {}},
+                                 Name   => $username,
+                                 Gecos  => $username,
+                             );
             unless ($val) {
                 $RT::Logger->error( "Couldn't create user $username: $msg" );
                 next;
@@ -504,7 +561,7 @@ sub DoAuth {
         my $info_updated = 0;
         my $info_updated_msg = "User info not updated";
 
-        unless($no_info_check) {
+        if ( @{ RT->Config->Get('ExternalInfoPriority') } ) {
             # Note that UpdateUserInfo does not care how we authenticated the user
             # It will look up user info from whatever is specified in $RT::ExternalInfoPriority
             ($info_updated,$info_updated_msg) = RT::Authen::ExternalAuth::UpdateUserInfo($session->{'CurrentUser'}->Name);
@@ -554,7 +611,7 @@ sub UpdateUserInfo {
 
     my $user_disabled   = RT::Authen::ExternalAuth::UserDisabled($username);
 
-    my $UserObj = RT::User->new($RT::SystemUser);
+    my $UserObj = RT::User->new(RT->SystemUser);
     $UserObj->Load($username);
 
     # If user is disabled, set the RT::Principal to disabled and return out of the function.
@@ -629,7 +686,7 @@ sub GetAuth {
     my $success = 0;
 
     # Get the full configuration for that service as a hashref
-    my $config = $RT::ExternalSettings->{$service};
+    my $config = RT->Config->Get('ExternalSettings')->{$service};
 
     # And then act accordingly depending on what type of service it is.
     # Right now, there is only code for DBI and LDAP non-SSO services
@@ -639,8 +696,6 @@ sub GetAuth {
     } elsif ($config->{'type'} eq 'ldap') {
         $success = RT::Authen::ExternalAuth::LDAP::GetAuth($service,$username,$password);
         $RT::Logger->debug("LDAP password validation result:",$success);
-    } else {
-        $RT::Logger->error("Invalid service type for GetAuth:",$service);
     }
 
     return $success;
@@ -656,7 +711,7 @@ sub UserExists {
     my $success = 0;
 
     # Get the full configuration for that service as a hashref
-    my $config = $RT::ExternalSettings->{$service};
+    my $config = RT->Config->Get('ExternalSettings')->{$service};
 
     # And then act accordingly depending on what type of service it is.
     # Right now, there is only code for DBI and LDAP non-SSO services
@@ -664,8 +719,6 @@ sub UserExists {
         $success = RT::Authen::ExternalAuth::DBI::UserExists($username,$service);
     } elsif ($config->{'type'} eq 'ldap') {
         $success = RT::Authen::ExternalAuth::LDAP::UserExists($username,$service);
-    } else {
-        $RT::Logger->debug("Invalid service type for UserExists:",$service);
     }
 
     return $success;
@@ -676,7 +729,7 @@ sub UserDisabled {
     my $username = shift;
     my $user_disabled = 0;
 
-    my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : ();
+    my @info_services = @{ RT->Config->Get('ExternalInfoPriority') };
 
     # For each named service in the list
     # Check to see if the user is found in the external service
@@ -686,15 +739,7 @@ sub UserDisabled {
     foreach my $service (@info_services) {
 
         # Get the external config for this service as a hashref
-        my $config = $RT::ExternalSettings->{$service};
-
-        # If the config doesn't exist, don't bother doing anything, skip to next in list.
-        unless(defined($config)) {
-            $RT::Logger->debug("You haven't defined a configuration for the service named \"",
-                                $service,
-                                "\" so I'm not going to try to get user information from it. Skipping...");
-            next;
-        }
+        my $config = RT->Config->Get('ExternalSettings')->{$service};
 
         # If it's a DBI config:
         if ($config->{'type'} eq 'db') {
@@ -721,14 +766,6 @@ sub UserDisabled {
             }
             $user_disabled = RT::Authen::ExternalAuth::LDAP::UserDisabled($username,$service);
 
-        } elsif ($config->{'type'} eq 'cookie') {
-            RT::Logger->error("You cannot use SSO Cookies as an information service.");
-            next;
-        } else {
-            # The type of external service doesn't currently have any methods associated with it. Or it's a typo.
-            RT::Logger->error("Invalid type specification for config %config->{'name'}");
-            # Drop out to next service in list
-            next;
         }
 
     }
@@ -758,7 +795,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->Config->Get('ExternalInfoPriority') };
     # For each external service...
     foreach my $service (@info_services) {
 
@@ -766,12 +803,7 @@ sub CanonicalizeUserInfo {
                             $service);
 
         # 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;
-        }
+        my $config = RT->Config->Get('ExternalSettings')->{$service};
 
         # For each attr we've been told to canonicalize in the match list
         foreach my $rt_attr (@{$config->{'attr_match_list'}}) {
@@ -814,11 +846,6 @@ sub CanonicalizeUserInfo {
                 ($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);
-            } else {
-                $RT::Logger->debug( (caller(0))[3],
-                                    "does not consider",
-                                    $service,
-                                    "a valid information service");
             }
 
             # Don't Check any more attributes
@@ -848,7 +875,7 @@ sub CanonicalizeUserInfo {
     ### 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 || RT->Config->Get('AutoCreateNonExternalUsers'));
 
 }
 
diff --git a/lib/RT/Authen/ExternalAuth/DBI.pm b/lib/RT/Authen/ExternalAuth/DBI.pm
index 7caa4da..3fe6cda 100644
--- a/lib/RT/Authen/ExternalAuth/DBI.pm
+++ b/lib/RT/Authen/ExternalAuth/DBI.pm
@@ -153,7 +153,7 @@ sub GetAuth {
 
     my ($service, $username, $password) = @_;
 
-    my $config = $RT::ExternalSettings->{$service};
+    my $config = RT->Config->Get('ExternalSettings')->{$service};
     $RT::Logger->debug( "Trying external auth service:",$service);
 
     my $db_table        = $config->{'table'};
@@ -301,7 +301,7 @@ sub CanonicalizeUserInfo {
                   RealName     => undef);
 
     # Load the config
-    my $config = $RT::ExternalSettings->{$service};
+    my $config = RT->Config->Get('ExternalSettings')->{$service};
 
     # Figure out what's what
     my $table      = $config->{'table'};
@@ -378,7 +378,7 @@ sub CanonicalizeUserInfo {
 sub UserExists {
 
     my ($username,$service) = @_;
-    my $config              = $RT::ExternalSettings->{$service};
+    my $config              = RT->Config->Get('ExternalSettings')->{$service};
     my $table                   = $config->{'table'};
     my $u_field             = $config->{'u_field'};
     my $query               = "SELECT $u_field FROM $table WHERE $u_field=?";
@@ -427,7 +427,7 @@ sub UserDisabled {
     }
 
     # Get the necessary config info
-    my $config              = $RT::ExternalSettings->{$service};
+    my $config              = RT->Config->Get('ExternalSettings')->{$service};
     my $table                   = $config->{'table'};
     my $u_field             = $config->{'u_field'};
     my $disable_field       = $config->{'d_field'};
@@ -558,7 +558,7 @@ sub GetCookieAuth {
     # Use this if you need to debug the DBI SQL process
     # DBI->trace(1,'/tmp/dbi.log');
 
-    my $dbh = _GetBoundDBIObj($RT::ExternalSettings->{$config->{'db_service_name'}});
+    my $dbh = _GetBoundDBIObj(RT->Config->Get('ExternalSettings')->{$config->{'db_service_name'}});
     my $query_result_arrayref = $dbh->selectall_arrayref($query,{}, at params);
     $dbh->disconnect();
 
diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 1e1b228..42df518 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -147,7 +147,7 @@ sub GetAuth {
 
     my ($service, $username, $password) = @_;
 
-    my $config = $RT::ExternalSettings->{$service};
+    my $config = RT->Config->Get('ExternalSettings')->{$service};
     $RT::Logger->debug( "Trying external auth service:",$service);
 
     my $base            = $config->{'base'};
@@ -316,7 +316,7 @@ sub CanonicalizeUserInfo {
                   RealName     => undef);
 
     # Load the config
-    my $config = $RT::ExternalSettings->{$service};
+    my $config = RT->Config->Get('ExternalSettings')->{$service};
 
     # Figure out what's what
     my $base            = $config->{'base'};
@@ -435,7 +435,7 @@ sub CanonicalizeUserInfo {
 sub UserExists {
     my ($username,$service) = @_;
    $RT::Logger->debug("UserExists params:\nusername: $username , service: $service");
-    my $config              = $RT::ExternalSettings->{$service};
+    my $config              = RT->Config->Get('ExternalSettings')->{$service};
 
     my $base                = $config->{'base'};
     my $filter              = $config->{'filter'};
@@ -509,7 +509,7 @@ sub UserDisabled {
         return 0;
     }
 
-    my $config          = $RT::ExternalSettings->{$service};
+    my $config          = RT->Config->Get('ExternalSettings')->{$service};
     my $base            = $config->{'base'};
     my $filter          = $config->{'filter'};
     my $d_filter        = $config->{'d_filter'};

commit cc10eab24225c0faf8d6bf92564fa841717deda1
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 18:51:02 2014 -0400

    Scope $pass_bypass more tightly, as it is set/used once per loop

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index 4b1fac9..aa36fe2 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -410,7 +410,6 @@ sub DoAuth {
         unless @auth_services;
 
     # This may be used by single sign-on (SSO) authentication mechanisms for bypassing a password check.
-    my $pass_bypass = 0;
     my $success = 0;
 
     # Should have checked if user is already logged in before calling this function,
@@ -420,8 +419,6 @@ sub DoAuth {
     # For each of those services..
     foreach my $service (@auth_services) {
 
-        $pass_bypass = 0;
-
         # Get the full configuration for that service as a hashref
         my $config = $settings->{$service};
         $RT::Logger->debug( "Attempting to use external auth service:",
@@ -443,7 +440,7 @@ sub DoAuth {
         # If $username is defined, we have a good SSO $username and can
         # safely bypass the password checking later on; primarily because
         # it's VERY unlikely we even have a password to check if an SSO succeeded.
-        $pass_bypass = 0;
+        my $pass_bypass = 0;
         if(defined($username)) {
             $RT::Logger->debug("Pass not going to be checked, attempting SSO");
             $pass_bypass = 1;

commit 45ef4945f9154c4de6ef6cd0a62aa83ac46fc093
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 18:59:17 2014 -0400

    Remove unused %once block

diff --git a/html/Elements/DoAuth b/html/Elements/DoAuth
index 49aaab5..17a6a87 100644
--- a/html/Elements/DoAuth
+++ b/html/Elements/DoAuth
@@ -1,6 +1,3 @@
-<%once>
-my $loaded_user = 0;
-</%once>
 <%init>
 
 use RT::Authen::ExternalAuth;

commit 9882340d31aea347ecb981e165f1418530be435a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 19:00:42 2014 -0400

    Remove unused %ARGS variable

diff --git a/html/Elements/DoAuth b/html/Elements/DoAuth
index 17a6a87..c66ccc4 100644
--- a/html/Elements/DoAuth
+++ b/html/Elements/DoAuth
@@ -27,6 +27,5 @@ return;
 <%ARGS>
 $user => undef
 $pass => undef
-$menu => undef
 </%ARGS>
 

commit 694ba2eae5710e2bd6d9abfb557bb5bae8bc32b0
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 19:02:12 2014 -0400

    RT::Authen::ExternalAuth will always have been loaded, via plugin code
    
    This is a holdover from before @Plugins existed

diff --git a/html/Elements/DoAuth b/html/Elements/DoAuth
index c66ccc4..0f535d8 100644
--- a/html/Elements/DoAuth
+++ b/html/Elements/DoAuth
@@ -1,7 +1,4 @@
 <%init>
-
-use RT::Authen::ExternalAuth;
-
 my ($val,$msg);
 unless($session{'CurrentUser'} && $session{'CurrentUser'}->Id) {
     # It's important to nab the next page from the session before we

commit eeb05b8f2c216e2362a012db89f518b739c27b1b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 19:08:57 2014 -0400

    DoAuth already deals with the already-logged-in case

diff --git a/html/Elements/DoAuth b/html/Elements/DoAuth
index 0f535d8..e3a707b 100644
--- a/html/Elements/DoAuth
+++ b/html/Elements/DoAuth
@@ -1,26 +1,20 @@
 <%init>
-my ($val,$msg);
-unless($session{'CurrentUser'} && $session{'CurrentUser'}->Id) {
-    # It's important to nab the next page from the session before we
-    # potentially blow the session away below.
-    my $next = $session{'NextPage'}->{ $ARGS{'next'} || "" };
-       $next = $next->{'url'} if ref $next;
+# It's important to nab the next page from the session before we
+# potentially blow the session away below.
+my $next = $session{'NextPage'}->{ $ARGS{'next'} || "" };
+   $next = $next->{'url'} if ref $next;
 
-    ($val,$msg) = RT::Authen::ExternalAuth::DoAuth(\%session,$user,$pass);
-    $RT::Logger->debug("Autohandler called ExternalAuth. Response: ($val, $msg)");
-    if ( $val ) {
-        $m->callback( %ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler', RedirectTo => \$next );
-    }
-
-    # 3.8.9 doesn't redirect to the specified page if request has one.
-    RT::Interface::Web::Redirect( $next )
-        if $val and $next
-       and $m->request_comp->path eq '/NoAuth/Login.html';
+my ($val,$msg) = RT::Authen::ExternalAuth::DoAuth(\%session,$user,$pass);
+$RT::Logger->debug("Autohandler called ExternalAuth. Response: ($val, $msg)");
+if ( $val ) {
+    $m->callback( %ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler', RedirectTo => \$next );
 }
 
-return;
+# Redirect to the relevant page if the above succeeded
+RT::Interface::Web::Redirect( $next )
+    if $val and $next
+   and $m->request_comp->path eq '/NoAuth/Login.html';
 </%init>
-
 <%ARGS>
 $user => undef
 $pass => undef

commit 34c8860c3814ab951d4bbe09d9a34cc32c1f9ba2
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 19:14:05 2014 -0400

    Remove unnecessary ssl_version argument

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 42df518..245ee27 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -33,7 +33,6 @@ Provides the LDAP implementation for L<RT::Authen::ExternalAuth>.
             'group_attr'                =>  'GROUP_ATTR',
 
             'tls'                       =>  0,
-            'ssl_version'               =>  3,
 
             'net_ldap_args'             => [    version =>  3   ],
 
@@ -131,10 +130,6 @@ group_attr above? Optional; defaults to C<dn>.
 
 Should we try to use TLS to encrypt connections?
 
-=item ssl_version
-
-SSL Version to provide to Net::SSLeay *if* using SSL.
-
 =item net_ldap_args
 
 What other args should be passed to Net::LDAP->new($host, at args)?
@@ -591,7 +586,6 @@ sub _GetBoundLdapObj {
     my $ldap_user       = $config->{'user'};
     my $ldap_pass       = $config->{'pass'};
     my $ldap_tls        = $config->{'tls'};
-    my $ldap_ssl_ver    = $config->{'ssl_version'};
     my $ldap_args       = $config->{'net_ldap_args'};
 
     my $ldap = new Net::LDAP($ldap_server, @$ldap_args);
@@ -604,8 +598,6 @@ sub _GetBoundLdapObj {
     }
 
     if ($ldap_tls) {
-        require Net::SSLeay;
-        $Net::SSLeay::ssl_version = $ldap_ssl_ver;
         # Thanks to David Narayan for the fault tolerance bits
         eval { $ldap->start_tls; };
         if ($@) {

commit 320e49cefb084af28f8c136106ef9b3cec851526
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 19:26:06 2014 -0400

    Don't attempt to call ->as_string on an undef value if we do not have a filter

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 245ee27..2f81a8d 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -463,7 +463,7 @@ sub UserExists {
                         "Base:",
                         $base,
                         "== Filter:",
-                        $filter->as_string,
+                        ($filter ? $filter->as_string : ''),
                         "== Attrs:",
                         join(',', at attrs));
 
@@ -557,7 +557,7 @@ sub UserDisabled {
                         "Base:",
                         $base,
                         "== Filter:",
-                        $search_filter->as_string,
+                        ($search_filter ? $search_filter->as_string : ''),
                         "== Attrs:",
                         join(',', at attrs));
 

commit 0b7072db03a8c8de7f78542f3612e54b2c1397ae
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 19:26:29 2014 -0400

    Warnings avoidance for undef filter / d_filter

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 2f81a8d..8849f66 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -161,7 +161,7 @@ sub GetAuth {
     # Empty parentheses as filters cause Net::LDAP to barf.
     # We take care of this by using Net::LDAP::Filter, but
     # there's no harm in fixing this right now.
-    if ($filter eq "()") { undef($filter) };
+    undef $filter if defined $filter and $filter eq "()";
 
     # Now let's get connected
     my $ldap = _GetBoundLdapObj($config);
@@ -439,7 +439,7 @@ sub UserExists {
     # of parentheses is an invalid filter and will cause failure
     # This shouldn't matter since we are now using Net::LDAP::Filter below,
     # but there's no harm in doing this to be sure
-    if ($filter eq "()") { undef($filter) };
+    undef $filter if defined $filter and $filter eq "()";
 
     if (defined($config->{'attr_map'}->{'Name'})) {
         # Construct the complex filter
@@ -514,8 +514,8 @@ sub UserDisabled {
     # of parentheses is an invalid filter and will cause failure
     # This shouldn't matter since we are now using Net::LDAP::Filter below,
     # but there's no harm in doing this to be sure
-    if ($filter eq "()") { undef($filter) };
-    if ($d_filter eq "()") { undef($d_filter) };
+    undef $filter   if defined $filter   and $filter eq "()";
+    undef $d_filter if defined $d_filter and $d_filter eq "()";
 
     unless ($d_filter) {
         # If we don't know how to check for disabled users, consider them all enabled.

commit 5eb5a98041da31d9bd9fe9e1fd024b4650b70d24
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Aug 14 19:26:48 2014 -0400

    Support verifying the LDAPS connection

diff --git a/lib/RT/Authen/ExternalAuth/LDAP.pm b/lib/RT/Authen/ExternalAuth/LDAP.pm
index 8849f66..912e8e6 100644
--- a/lib/RT/Authen/ExternalAuth/LDAP.pm
+++ b/lib/RT/Authen/ExternalAuth/LDAP.pm
@@ -32,7 +32,7 @@ Provides the LDAP implementation for L<RT::Authen::ExternalAuth>.
             'group'                     =>  'GROUP_NAME',
             'group_attr'                =>  'GROUP_ATTR',
 
-            'tls'                       =>  0,
+            'tls'                       =>  { verify => "require", capath => "/path/to/ca.pem" },
 
             'net_ldap_args'             => [    version =>  3   ],
 
@@ -128,7 +128,14 @@ group_attr above? Optional; defaults to C<dn>.
 
 =item tls
 
-Should we try to use TLS to encrypt connections?
+Should we try to use TLS to encrypt connections?  Either a scalar, for
+simple enabling, or a hash of values to pass to L<Net::LDAP/start_tls>.
+By default, L<Net::LDAP> does B<no> certificate validation!  To validate
+certificates, pass:
+
+    tls => { verify => 'require',
+             cafile => "/etc/ssl/certs/ca.pem",  # Path CA file
+           },
 
 =item net_ldap_args
 
@@ -586,6 +593,7 @@ sub _GetBoundLdapObj {
     my $ldap_user       = $config->{'user'};
     my $ldap_pass       = $config->{'pass'};
     my $ldap_tls        = $config->{'tls'};
+    $ldap_tls = $ldap_tls ? {} : undef unless ref $ldap_tls;
     my $ldap_args       = $config->{'net_ldap_args'};
 
     my $ldap = new Net::LDAP($ldap_server, @$ldap_args);
@@ -599,7 +607,7 @@ sub _GetBoundLdapObj {
 
     if ($ldap_tls) {
         # Thanks to David Narayan for the fault tolerance bits
-        eval { $ldap->start_tls; };
+        eval { $ldap->start_tls( %{$ldap_tls} ); };
         if ($@) {
             $RT::Logger->critical(  (caller(0))[3],
                                     "Can't start TLS: ",

commit bad4dac8c9c68886414106747cde46d38e287307
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Sep 15 15:07:57 2014 -0400

    Respect both 4.2 and 4.0 names for AutoCreate
    
    aed73129 changed the documentation to reference the 4.2 configuration
    name; change the code to do so, as well.

diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm
index aa36fe2..9c206d9 100644
--- a/lib/RT/Authen/ExternalAuth.pm
+++ b/lib/RT/Authen/ExternalAuth.pm
@@ -480,7 +480,8 @@ sub DoAuth {
         # Unless we have loaded a valid user with a UserID create one.
         unless ($session->{'CurrentUser'}->Id) {
             my $UserObj = RT::User->new($RT::SystemUser);
-            my $create = RT->Config->Get('AutoCreate');
+            my $create = RT->Config->Get('UserAutocreateDefaultsOnLogin')
+                || RT->Config->Get('AutoCreate');
             my ($val, $msg) =
                 $UserObj->Create(%{ref($create) ? $create : {}},
                                  Name   => $username,

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


More information about the Bps-public-commit mailing list