Fixing up the External Authentication (LDAP) plugin

Discussion corner for Developers of Serendipity.
Post Reply
jonathanve
Regular
Posts: 10
Joined: Sat Oct 01, 2005 3:58 am

Fixing up the External Authentication (LDAP) plugin

Post by jonathanve »

Hi all,

(I'm using s9y's svn trunk, currently at revision 1193.)

Up to now, I've managed a client's restricted-access blog using plain vanilla s9y authentication, but there are starting to be too many user accounts to manage efficiently using this method. So I've been fixing up the external authentication LDAP plugin to fit our needs. First off, I've added a number of options like those on the regular add user page...like "send comments", etc. Everything seems to be working quite well so far, but I have a question:

The plugin has the ability to disable a user account (by changing the password) if the userlevel attribute in the LDAP database is -1. However, I don't believe this actually works, because serendipity_login() is first called with external authentication disabled (in serendipity_config.inc.php, line 229), before s9y tries again with it enabled (in serendipity_admin.php, line 30). As a result, once a user account has been cached in s9y, the external authentication plugin will never get called again, so it can't disable existing accounts. I imagine the motivation is speed, but it seems reasonable to assume that if the external authentication plugin is enabled, then the admin already understands the potential performance hit. In that case, it doesn't hurt to always call the authentication plugin.

Thanks for any feedback!

Jonathan
jonathanve
Regular
Posts: 10
Joined: Sat Oct 01, 2005 3:58 am

Post by jonathanve »

I just had another idea: why not have s9y always call the external authentication plugin (if it's enabled), but then provide an option as part of the plugin that specifies whether to check LDAP for existing accounts. That way the behavior depends on how the admin is using this plugin, without a mandatory performance hit.

Jonathan
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

Hi!

That's a good idea and suggestion, however there's a slight problem with it. :-/

The reason is, that the LDAP plugin can only be called once the Plugin API framework is called. However, the plugin API framework depends on several files being included and login already performed, because the login indicates which language is loaded for the blog.

So, if we fire up the plugin API for catching the FIRST login (note that external auth only is enabled as a second step login after the failed first one) it would mean the plugin API getting initialized with the english language no matter what language the user has configured. Which is bad. :-)

So right now I see no way to involve the LDAP plugin at this phase of the framework instanciation.

But, to not leave you tangling with your feet in the air, there might be a way to achieve what you want. The idea would be to make an external process invalidate every persons user account daily (or hourly, or whatever). This would then force the first login to fail, then it would require the author to login to the system, and then if he logs in with valid LDAP data, his user account will be updated with the real password again. You could do this easily with a daily/hourly SQL query:

Code: Select all

UPDATE serendipity_authors SET password = 'Nonegiven';
Of course, the other way would be for you to setup a simple script that queries the LDAP database every day and invalidates the users on its own?

Best regards,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
jonathanve
Regular
Posts: 10
Joined: Sat Oct 01, 2005 3:58 am

Post by jonathanve »

Wow, you're right...this is a pain. Maybe I'm just trying too hard for something that's not really worth it, but here's another idea:

How about changing the semantics of the "backend_auth" event, so that it occurs AFTER we attempt to login using the s9y database. If we can successfully login using the database, then we load the user's configuration settings. But in either case, we fire the event, and plugins can do things:
1) Ask s9y to attempt a login again (only once) if we haven't logged in already. This gives the plugin a chance to add a new user to the database.
2) Veto the login attempt. Does it hurt that we already loaded the user's configuration?

I agree that external authentication really should be used in conjunction with the regular s9y accounts, because so much is associated with user accounts. That is, it's pretty useless to create an authentication plugin that immediately authenticates users without creating a new account. But the idea above would give plugins a bit more power and could be useful for other authentication methods, because of the ability to do everything without relying on anything outside s9y and without loss of instant updating.

What do you think? Is it worth it?

Thanks again!

Jonathan
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

Hi!

Actually, backend_auth is currently also fired when the first internal login failed.

BUT, there's another idea: You could use the backend_configure (or even frontend_Configure) hook to query up your LDAP server even IF the user is logged in. Then you can query the LDAP service with the credentials the user is currently logged in as, and if it does not validate against LDAP anymore, you can set the users account empty and exit the script. If it validates in LDAP, you could set a session variable so that the next time this lookup is not performed on the next successive page calls, but only on the next session...

What's your thought on this?

regards,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
jonathanve
Regular
Posts: 10
Joined: Sat Oct 01, 2005 3:58 am

Post by jonathanve »

Sorry for the long delay...trying to catch up with life, ya know.

Anyway, I like your idea. I think it will work, except I have one question. After the login has completed (in backend_configure), I can access the username using $serendipity['serendipityUser'], but the password in $serendipity['serendipityPassword'] is MD5 encoded, right? Does that mean I would have to change the LDAP server authentication process? Normally, ldap_bind() is called with a plain-text password. Is it definitely a bad idea to put the password somewhere for the plugin?

Thanks!

Jonathan
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

Hi!

I'd recommend to save the cleartext password in the Session container somewhere. Your LDAP plugin can catch the login by listening on frontend_configure and checking when the password is set, and then you can store it in $_SESSION['clearpass'] or somewhere else for future retrieval?

HTH,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
jonathanve
Regular
Posts: 10
Joined: Sat Oct 01, 2005 3:58 am

Post by jonathanve »

Alright, that sounds like a good idea. I'll give it a shot and let you know...

Jonathan
jonathanve
Regular
Posts: 10
Joined: Sat Oct 01, 2005 3:58 am

Post by jonathanve »

Hi,

Well, I think I've got this LDAP thing finally worked out. It's in production use for the last few weeks and so far as good. Thanks so much for such a flexible framework!

I'm more than happy to post my changes, but I'm not sure how useful it would be for others. The more I use LDAP, the more I feel that it's very much an "enterprise" kind-of thing that virtually necessitates a lot of in-house work. So unless someone really wants it, I don't think I want to confuse matters.

Anyway, thanks again for all the help!

Jonathan
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

Hi!

I personally would love to see the changes very much! :-)

Best regards and glad that it seems to work out fine!
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
judebert
Regular
Posts: 2478
Joined: Sat Oct 15, 2005 6:57 am
Location: Orlando, FL
Contact:

Post by judebert »

Others have suggested using LDAP to link authentication for Serendipity and external apps.

We'd love to see what you've got!
Judebert
---
Website | Wishlist | PayPal
jonathanve
Regular
Posts: 10
Joined: Sat Oct 01, 2005 3:58 am

Post by jonathanve »

Hi all,

Thanks for the interest! Here's a link to download it:

http://jdve.yi.org/downloads/serendipity_event_ldap.zip

I changed the name from the original to avoid compatibility issues. Also, I created another visit log plugin (complete with a graph), so I didn't need that functionality in this plugin.

Major differences:
1) Supports setting default values for new users that are created on first login.
2) Even if a user is successfully authenticated using the standard S9Y database, the plugin will still verify against the LDAP server in case the user has been disabled (either the account was deleted or the user level is -1).
3) Changes to a user's password using S9Y's personal settings section will be propogated back to the LDAP server. At this point, only the password is updated, but other values could also be updated (such as the name and e-mail).

Note that #2 and #3 are tied together into one feature called "synchronization". If only #2 was used, then the user could potentially change their password in S9Y, successfully authenticate to S9Y, but fail to authenticate to the LDAP server (because it still has the original password), and so be locked out forever.

My implementation requires a small change to include/functions_config.inc.php (the diff output is below). This serves these purposes:
1) After successfully authenticating using the S9Y database, the user's configuration is immediately loaded.
2) A new event "backend_auth_verify" is now called. The language info is now loaded, so all plugins should show up just fine.
3) If possible the plain-text password is given to "backend_auth_verify", thus avoiding the need to have a session variable with the plain text password. If we don't have the plain-text password, the plugin will simply do nothing.

I think it would be possible to get rid of the call to serendipity_login() from serendipity_admin.php and simply raise the "backend_auth" event immediately inside serendipity_authenticate_author() if we fail to authenticate using the database. Also the call to serendipity_load_configuration() later in serendipity_config.inc.php is probably redundant. I haven't had time to try these things, though.

I've also written an LDAP plugin for the Vanilla forum (http://www.getvanilla.com). So far that's working quite well. I haven't written the synchronization part for that plugin, cause S9Y works great for changing the password. I'm also using LDAP with the apache2 auth_ldap module to authenticate folder listings generated by the autoindex php script (http://autoindex.sourceforge.net).

Well, that's about it. Hopefully I haven't forgotten anything.

Thanks again for all the help!

Jonathan

Code: Select all

Index: include/functions_config.inc.php
===================================================================
--- include/functions_config.inc.php    (revision 1299)
+++ include/functions_config.inc.php    (working copy)
@@ -462,6 +462,7 @@
         }

         if ($is_md5 === false && !empty($password)) {
+            $password_plain = $password;
             $password = md5($password);
         }

@@ -484,7 +485,11 @@
             $_SESSION['serendipityAuthedUser']  = $serendipity['serendipityAuthedUser']   = true;
             $_SESSION['serendipityRightPublish']= $serendipity['serendipityRightPublish'] = $row['right_publish'];
             serendipity_load_configuration($serendipity['authorid']);
-            return true;
+
+            serendipity_plugin_api::hook_event('backend_auth_verify', $is_md5, array('username' => $username, 'password' => $password_plain)
);
+
+            // Return whether we are still authenticated (a plugin might have logged us out).
+            return $_SESSION['serendipityAuthedUser'];
         } else {
             $_SESSION['serendipityAuthedUser'] = false;
             @session_destroy();
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

Hi!

Thanks a lot for sharing this! I'll have a more thorough look the next days.

The problem of putting the backend_auth plugin too high is because then it would be called as the first plugin aPI event.

However that may not happen because else it would load the plugin API too soon, resulting in missing language constants being loaded and irrevertable fixing plugins at their english language. English developers might not realize this, but I have painfully learned that the API is very vulnerable to moving any plugin hook before frontned_configure :-) :-)

Best regards,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
Post Reply