uhm-coe / authorizer

Authorizer is a WordPress plugin that uses Google, CAS, LDAP, or an OAuth2 provider for logins, and can prevent public access to a WordPress site. It also blocks repeated failed login attempts.
GNU General Public License v3.0
64 stars 36 forks source link

Change user e-mail on login - LDAP #158

Open aaloise opened 1 month ago

aaloise commented 1 month ago

It's probably not a good practice to open an issue to ask a question. I was unable to post on the support forum (https://wordpress.org/support/plugin/authorizer/).

The question is actually quite simple, but I haven't found a direction yet. Users in the LDAP domain we have are @abc.com emails and I need this email to be "transformed" to @xyz.com after a successfull authentication.

The closest I found was using functions.php but without success.

If possible, I would like some guidance, and I apologize in advance for using github to ask a simple question.

pkarjala commented 1 month ago

Hi @aaloise it's fine that you've opened an issue here! In order to keep discussion in one place, I've closed your recently opened WordPress support thread and will keep my responses here.

You may want to consider that if a user is created in WordPress with an abc.com email address and that user expects to receive emails at that domain name, they will not receive them if the email is changed from abc.com to xyz.com, or the emails could be sent to someone completely different who has the same xyz.com email address. This is important because password reset emails and other notifications that are sent via WordPress go to the email address on the user's account. Just something to be aware of!

The main hook in Authorizer that you can use to manipulate user data on account creation would be authorizer_user_register which can be found at https://github.com/uhm-coe/authorizer/blob/master/src/authorizer/class-authorization.php#L291-L310. More on how to create actions when this hook is called can be found at https://developer.wordpress.org/reference/functions/add_action/

So you would want to make your function that will check if the domain name on the email address is abc.com and update it to xyz.com, then create your action with something like:

my_function( $user, $user_data ) {
    // ...some code here to check and manipulate email addresses
}

add_action( 'authorizer_user_register', 'my_function', 10, 2 );

I hope that gets you started! Please let us know if you have additional questions.

aaloise commented 1 month ago

I was able to get it done! Thanks @pkarjala !

aaloise commented 3 weeks ago

Well, it seems I'm missing something here. I was able to change the users e-mail, but when they try to authenticate again, WP creates a new user with the "old" e-mail, instead of authenticate again with the same user before.

I assume that some work must be done with the hooks, but before that I need to understand the whole process of authentication using LDAP/AD.

Any tips why a new user gets created and what workaround should I use? Thanks again!

pkarjala commented 3 weeks ago

Hi @aaloise without seeing your code, it is difficult to determine what might be happening in this case.

It sounds like when the user initially logs in with an abc.com email and is authenticated against LDAP, their email is altered to an xyz.com address using your code and then the user is added to WordPress with the xyz.com email address.

The next time the user attempts to log in, are they using their username or their email address? If they are using their email address, are they logging in with the abc.com or the xyz.com email?

aaloise commented 3 weeks ago

The users authenticate using a username (sAmAccountName) at all moments. The first time, when it doesn't exist in WP, the e-mail is changed to xyz.com and saved in the DB. The second time, instead of authenticate against the recently created user, WP creates a new one using abc.com e-mail in both username and e-mail fields.

The code I am using is this:

function custom_email_domain_change($user, $user_data) {
    error_log('Hook authorizer_user_register called'); 
    error_log('User Data: ' . print_r($user_data, true)); 

    if (isset($user_data['email']) && strpos($user_data['email'], '@abc.com') !== false) {

        $user_data['email'] = str_replace('@abc.com', '@xyz.com', $user_data['email']);

        $user->user_email = $user_data['email'];

        wp_update_user(array('ID' => $user->ID, 'user_email' => $user->user_email));
    }

    error_log('Updated User Email: ' . $user->user_email); 

    return $user;
}

add_action('authorizer_user_register', 'custom_email_domain_change', 10, 2);
pkarjala commented 3 weeks ago

Got it, thank you for sharing your code!

Can you please let us know what is set in the Authorizer configuration under the External Service tab for the field LDAP attribute containing email address?

Additionally, are the LDAP Directory User and LDAP Directory User Password fields set?

aaloise commented 3 weeks ago

The LDAP attribute containing the email address under External Service is "mail".

Yes for both LDAP user and password. They are set.

Just a quick note. The LDAP server is an Active Directory.

pkarjala commented 3 weeks ago

Thanks; one more question. Under the Login Access tab, what is set for Who can log into the site??

pkarjala commented 3 weeks ago

OK, so what we suspect is happening is the following:

  1. The user logs into WordPress using the LDAP authentication for the first time.
  2. The LDAP credentials for the user are checked and verified.
  3. The verified LDAP user is authenticated and then a WordPress account is created through Authorizer. Your hook alters their email address to xyz.com after the WordPress account is created.
  4. The same user logs out and back in.
  5. The LDAP credentials for the user are checked and verified.
  6. The verified LDAP user is authenticated, then Authorizer attempts to match the email address of the user against an existing WordPress user. Because the LDAP user's email address is abc.com and not xyz.com, there is no match. So a new user is created in WordPress using the abc.com email and your hook does not alter it.

The issue is that last one as we do not currently have a way to intercept this process. Instead, however, we suggest trying the following:

  1. Remove the code for your hook from your theme.
  2. In Authorizer under the External Service tab in the field LDAP attribute containing email address, instead of putting mail, put @xyz.com instead.

Please note that this will only work if your LDAP usernames are an exact match to the first part of the email address before the domain name. So if an LDAP user is pkarjala, the email address must be pkarjala@abc.com.

Please give this a try and let us know how it goes!

aaloise commented 3 weeks ago

Steps 1 to 6 are exactly what's happening.

I tried your suggestion, by setting @xyz.com instead of setting "mail" attribute. Unfortunately sAmAccountName is not an exact match to the first part of the e-mail address. The username is an unique ID, so it is a lot of numbers, while e-mail has firstname and lastname.

There is the userPrincipalName attribute, which is firstname.lastname@domain.com. Is there a way we can set it as the username in Authorizer, but remove the @domain.com part, remaining firstname.lastname only during authentication? That way we could set @xyz.com as the LDAP containing e-mail address, which would give us firstname.lastname@xyz.com at the end.

Is that doable?

pkarjala commented 3 weeks ago

That might be doable, but another better option may be to create a new field in your AD/LDAP setup that holds something like wordpressEmail which is a composite of the user's login name and the desired domain name. You would then set this field to be used for the LDAP attribute containing email address in Authorizer.

This way no custom code is required, but you will need to have that field populated in AD/LDAP on your end.

aaloise commented 3 weeks ago

Sadly I have no management over the domain, other then to retrieve data on it. Because of that I can't create a new attribute.

For now it seems that userPrincipalName is the closest candidate here. But for that I need to use a hook, I supose.

figureone commented 3 weeks ago

You should be able to use the authorizer_ldap_search_filter hook to allow users to type in the username portion of userPrincipalName, and then you append the @domain portion manually in the hook so the LDAP search finds the user. https://github.com/uhm-coe/authorizer/blob/master/src/authorizer/class-authentication.php#L1303-L1313

Example:

// If your users log in with just a username, but your LDAP search filter 
// matches on an attribute that contains an email address, use this filter to
// append the domain portion of the email address to the username.
// Example search filter (if `userPrincipalName` is the lookup attribute): 
//   (userPrincipalName=username) => (userPrincipalName=username@domain.com)
add_filter( 'authorizer_ldap_search_filter', function ( $search_filter, $ldap_uid, $username ) {
    $search_filter = str_replace( $username, $username . '@domain.com', $search_filter );
    return $search_filter;
}, 10, 3 );