Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package RT::Authen::ExternalAuth;
- our $VERSION = '0.09';
- =head1 NAME
- RT::Authen::ExternalAuth - RT Authentication using External Sources
- =head1 DESCRIPTION
- A complete package for adding external authentication mechanisms
- to RT. It currently supports LDAP via Net::LDAP and External Database
- authentication for any database with an installed DBI driver.
- It also allows for authenticating cookie information against an
- external database through the use of the RT-Authen-CookieAuth extension.
- MODIFIED IN 2011/10/01 By Luciano Silva - lucianoes at ufrgs dot br
- This version is only numeric UID LDAP. Use at your own risk.
- =begin testing
- ok(require RT::Authen::ExternalAuth);
- =end testing
- =cut
- use RT::Authen::ExternalAuth::LDAP;
- use RT::Authen::ExternalAuth::DBI;
- use strict;
- sub DoAuth {
- my ($session,$given_user,$given_pass) = @_;
- unless(defined($RT::ExternalAuthPriority)) {
- return (0, "ExternalAuthPriority not defined, please check your configuration file.");
- }
- 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;
- }
- # 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,
- # 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) {
- $pass_bypass = 0;
- # Get the full configuration for that service as a hashref
- my $config = $RT::ExternalSettings->{$service};
- $RT::Logger->debug( "Attempting to use external auth service:",
- $service);
- # $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') {
- # 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");
- $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;
- }
- # 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);
- # Don't continue unless the $username exists in the external service
- $RT::Logger->debug("Calling UserExists with \$username ($username) and \$service ($service)");
- next unless RT::Authen::ExternalAuth::UserExists($username, $service);
- }
- ####################################################################
- ########## Load / Auto-Create ######################################
- ####################################################################
- # We are now sure that we're talking about a valid RT user.
- # If the user already exists, load up their info. If they don't
- # then we need to create the user in RT.
- #MODIF
- my $MagicUid = "a";
- #END_MODIF
- # Does user already exist internally to RT?
- $RT::Logger->debug("Auto-Create process, this is where the magic really happens!! Put the A letter befone the UID");
- $session->{'CurrentUser'} = RT::CurrentUser->new();
- $session->{'CurrentUser'}->Load($MagicUid . $username);
- #$session->{'CurrentUser'}->Load($username);
- # 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 => ("a" . $username),
- Gecos => $username,
- );
- unless ($val) {
- $RT::Logger->error( "Couldn't create user $username: $msg" );
- next;
- }
- $RT::Logger->info( "Autocreated external user",
- $UserObj->Name,
- "(",
- $UserObj->Id,
- ")");
- $RT::Logger->debug("Loading new user (",
- $username,
- ") into current session");
- $session->{'CurrentUser'}->Load("a" . $username);
- $RT::Logger->debug("Did the system load the the userseme with a ? Otherwise the next step will fail.");
- }
- ####################################################################
- ########## Authentication ##########################################
- ####################################################################
- # If we successfully used an SSO service, then authentication
- # succeeded. If we didn't then, success is determined by a password
- # test.
- $success = 0;
- 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) {
- delete $session->{'CurrentUser'};
- return (0, "No User");
- }
- unless($success) {
- delete $session->{'CurrentUser'};
- 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
- # 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";
- 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) {
- delete $session->{'CurrentUser'};
- 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",
- $ENV{'REMOTE_ADDR'});
- # Do not delete the session. User stays logged in and
- # autohandler will not check the password again
- } else {
- # Make SURE the session is deleted.
- delete $session->{'CurrentUser'};
- return (0, "Failed to authenticate externally");
- # This will cause autohandler to request IsPassword
- # which will in turn call IsExternalPassword
- }
- return (1, "Successful login");
- }
- sub UpdateUserInfo {
- my $username = shift;
- # Prepare for the worst...
- my $found = 0;
- my $updated = 0;
- my $msg = "User NOT updated";
- my $user_disabled = RT::Authen::ExternalAuth::UserDisabled($username);
- my $UserObj = RT::User->new($RT::SystemUser);
- $UserObj->Load($username);
- # If user is disabled, set the RT::Principle 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
- # denied access after all, don't take any chances.
- # If someone gives me a good enough reason to do it,
- # then I'll update all the info for disabled users
- if ($user_disabled) {
- # Make sure principle is disabled in RT
- my ($val, $message) = $UserObj->SetDisabled(1);
- # Log what has happened
- $RT::Logger->info("User marked as DISABLED (",
- $username,
- ") per External Service",
- "($val, $message)\n");
- $msg = "User Disabled";
- return ($updated, $msg);
- }
- # Make sure principle is not disabled in RT
- my ($val, $message) = $UserObj->SetDisabled(0);
- # Log what has happened
- $RT::Logger->info("User marked as ENABLED (",
- $username,
- ") per External Service",
- "($val, $message)\n");
- # Update their info from external service using the username as the lookup key
- # CanonicalizeUserInfo will work out for itself which service to use
- # Passing it a service instead could break other RT code
- #MODIFICACAO - Incluido o substring no $username para remover o caracter a
- my %args = (Name => substr($username,1));
- $UserObj->CanonicalizeUserInfo(\%args);
- # For each piece of information returned by CanonicalizeUserInfo,
- # run the Set method for that piece of info to change it for the user
- foreach my $key (sort(keys(%args))) {
- next unless $args{$key};
- my $method = "Set$key";
- # We do this on the UserObj from above, not self so that there
- # are no permission restrictions on setting information
- my ($method_success,$method_msg) = $UserObj->$method($args{$key});
- # If your user information is not getting updated,
- # uncomment the following logging statements
- if ($method_success) {
- # At DEBUG level, log that method succeeded
- $RT::Logger->debug((caller(0))[3],"$method Succeeded. $method_msg");
- } else {
- # At DEBUG level, log that method failed
- $RT::Logger->debug((caller(0))[3],"$method Failed. $method_msg");
- }
- }
- # Confirm update success
- $updated = 1;
- $RT::Logger->debug( "UPDATED user (",
- $username,
- ") from External Service\n");
- $msg = 'User updated';
- return ($updated, $msg);
- }
- 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') {
- $success = RT::Authen::ExternalAuth::DBI::GetAuth($service,$username,$password);
- $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);
- } else {
- $RT::Logger->error("Invalid service type for GetAuth:",$service);
- }
- return $success;
- }
- sub UserExists {
- # Request a username/password check from the specified service
- # This is only valid for non-SSO services.
- # my $removemagic = substr($username, 1);
- # my ($removemagic,$service) = @_;
- my ($username,$service) = @_;
- 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') {
- $success = RT::Authen::ExternalAuth::DBI::UserExists($username,$service);
- } elsif ($config->{'type'} eq 'ldap') {
- my $magicnewname = substr($username, '0', 8);
- my $username = $magicnewname;
- $success = RT::Authen::ExternalAuth::LDAP::UserExists($username,$service);
- } else {
- $RT::Logger->debug("Invalid service type for UserExists:",$service);
- }
- return $success;
- }
- sub UserDisabled {
- $RT::Logger->debug("Now is starting UserDisabled function");
- my $username = shift;
- my $user_disabled = 0;
- my @info_services = $RT::ExternalInfoPriority ? @{$RT::ExternalInfoPriority} : undef;
- # For each named service in the list
- # Check to see if the user is found in the external service
- # If not found, jump to next service
- # 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
- 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;
- }
- # If it's a DBI config:
- if ($config->{'type'} eq 'db') {
- unless(RT::Authen::ExternalAuth::DBI::UserExists($username,$service)) {
- $RT::Logger->debug("User (",
- $username,
- ") doesn't exist in service (",
- $service,
- ") - Cannot update information - Skipping...");
- next;
- }
- $user_disabled = RT::Authen::ExternalAuth::DBI::UserDisabled($username,$service);
- } elsif ($config->{'type'} eq 'ldap') {
- $RT::Logger->debug("Removing special character A from the username.");
- unless(RT::Authen::ExternalAuth::LDAP::UserExists(substr($username,1),$service)) {
- $RT::Logger->debug("User (",
- $username,
- ") doesn't exist in service (",
- $service,
- ") - Cannot update information - Skipping...");
- next;
- }
- $RT::Logger->debug("New check on LDAP the username, but removing the special character A from the username.");
- $user_disabled = RT::Authen::ExternalAuth::LDAP::UserDisabled(substr($username,1),$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;
- }
- }
- return $user_disabled;
- }
- sub CanonicalizeUserInfo {
- $RT::Logger->debug("Now is starting CanonicalizeUserInfo function");
- # 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:",
- join(", ", map {sprintf("%s: %s", $_, $args->{$_})}
- sort(keys(%$args))));
- # Get the list of defined external services
- 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
- $RT::Logger->debug( "Attempting to use this canonicalization key:",$rt_attr);
- unless(defined($args->{$rt_attr})) {
- $RT::Logger->debug("This attribute (",
- $rt_attr,
- ") is null or incorrectly defined in the attr_map for this service (",
- $service,
- ")");
- next;
- }
- # Else, use it as a canonicalization key and lookup the user info
- my $key = $config->{'attr_map'}->{$rt_attr};
- my $value = $args->{$rt_attr};
- # Check to see that the key being asked for is defined in the config's attr_map
- my $valid = 0;
- my ($attr_key, $attr_value);
- my $attr_map = $config->{'attr_map'};
- while (($attr_key, $attr_value) = each %$attr_map) {
- $valid = 1 if ($key eq $attr_value);
- }
- unless ($valid){
- $RT::Logger->debug( "This key (",
- $key,
- "is not a valid attribute key (",
- $service,
- ")");
- next;
- }
- # Use an if/elsif structure to do a lookup with any custom code needed
- # for any given type of external service, or die if no code exists for
- # the service requested.
- if($config->{'type'} eq 'ldap'){
- ($found, %params) = RT::Authen::ExternalAuth::LDAP::CanonicalizeUserInfo($service,$key,$value);
- } 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
- last if $found;
- }
- # Don't Check any more services
- last if $found;
- }
- # If found, Canonicalize Email Address and
- # update the args hash that we were given the hashref for
- if ($found) {
- # It's important that we always have a canonical email address
- if ($params{'EmailAddress'}) {
- $params{'EmailAddress'} = $UserObj->CanonicalizeEmailAddress($params{'EmailAddress'});
- }
- %$args = (%$args, %params);
- }
- $RT::Logger->info( (caller(0))[3],
- "returning",
- join(", ", map {sprintf("%s: %s", $_, $args->{$_})}
- sort(keys(%$args))));
- ### HACK: The config var below is to overcome the (IMO) bug in
- ### RT::User::Create() which expects this function to always
- ### return true or rejects the user for creation. This should be
- ### a different config var (CreateUncanonicalizedUsers) and
- ### should be honored in RT::User::Create()
- return($found || $RT::AutoCreateNonExternalUsers);
- }
- {
- no warnings 'redefine';
- *RT::User::CanonicalizeUserInfo = sub {
- my $self = shift;
- my $args = shift;
- return ( CanonicalizeUserInfo( $self, $args ) );
- };
- }
- 1;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement