A while ago I needed to hook up symfony’s excellent sfGuard plugin to some LDAP functionality. There are a couple of issues with the plugin and the readme which I think need fixing. In particular, there is no support for checking both LDAP and standard sfGuard passwords. This is absolutely essential (eg an admin user or guest users who aren’t in LDAP), and I’m somewhat amazed that there’s no provision for this. Moreover, the plugin structure generally makes it seemingly impossible (or if it is possible it’s just too horrible to contemplate) to write your own checkPassword() which does do both sorts of checking.
Anyway this post is partly an attempt for me to get my head round something which I’ve either totally misunderstood, or just isn’t well documented.
Confusing README
The README file that comes with the plugin is great, but it’s a little confusing for explaining how you might override its own way of checking passwords with something that’s LDAP-based. It’s really very simple to do when written out in plain English:
1. Make a file called myLDAP.class.php in your application’s lib folder. Put this in it:
class myLDAP extends sfGuardSecurityUser
{
}
2. Now put a method inside the class you just created:
public static function checkLDAPPassword($username, $password)
{
// put your preferred LDAP-checking code in here
// should return true or false
}
3. Add this to your application’s app.yml file:
all:
sf_guard_plugin:
check_password_callable: [myLDAP, checkLDAPPassword]
where myLDAP is the file called myLDAP.class.php that you created in step 1 above. checkLDAPPassword is the method you made inside myLDAP.class.php that you made in step 2.
And there you have it. Basic LDAP password checking.
How does checkPassword() actually work?
The above code is all very well, but what’s really going on?
Well, there are a couple of steps which I managed to work out:
1. sfGuardUserValidator contains a call to $user->checkPassword($password) where $user is a result of sfGuardUserPeer::retrieveByUsername($username)
2. PluginsfGuardUser (extends BasesfGuardUser) has a method called checkPassword() too. This calls the method you listed in your app.yml file (checkLDAPPassword in this case), or does a standard sfGuard password check (checkPasswordByGuard) if you didn’t specify anything:
public function checkPassword($password)
{
if ($callable = sfConfig::get('app_sf_guard_plugin_check_password_callable'))
{
return call_user_func_array($callable, array($this->getUsername(), $password));
}
else
{
return $this->checkPasswordByGuard($password);
}
}
Which it does depends on how you set up your app.yml file (see Step 3 above).
At this point, if you set up your app.yml correctly you’ll be in your custom checkLDAPPassword() method, which will do whatever you want it to do to check LDAP passwords. So long as it returns true or false depending on whether the check was successful or not, you’ll be ok.
Note that sfGuardSecurityUser class also contains a checkPassword() method. This basically does the same thing as the in sfGuardUserValidator, but in a slightly different way, and allows access to checkPassword() in templates and controllers through the $sf_user object. The important thing to realise is that both pieces of code end up calling the same checkPassword() method in PluginsfGuardUser.
So how about LDAP and sfGuard checking?
Now the fun begins. The problem is that it’s seemingly impossible to call checkPasswordByGuard() yourself from inside your custom-built checkLDAPPassword() method. Why would you want to do this? Because that way you can do some standard password checking first to see if your user is in fact a sfGuard user and not an LDAP user. Only if that failed would you do LDAP checking (I suppose you could do it the other way round if you wanted.) Even better, you wouldn’t need to alter checkPasswordByGuard().
So why is it impossible to call checkPasswordByGuard() ‘by hand’? As far as I can see, it assumes that you’re not in a static context, and that you have some kind of user object available. But of course, my checkLDAPPassword() is in a static context. It had to be that way because it was called by call_user_func_array() which uses the output of sfConfig::get() as an argument.
Sigh…
If you find a way of doing this, please let me know. Losing the will to live is merely one of the symptoms of battling with this sort of thing. I have a life to live.
All I can suggest to you in the meantime is to modify your copy of PluginsfGuardUser.php to allow both sorts of password checking. That’s what I did and it worked for me. It’s just a pity that you can’t override the checkPasswordByGuard() method in some way without changing the core of the plugin.
Pingback: rpsblog.com » A week of symfony #78 (23->29 june 2008)
Hello,
I’m very interested in this article, trying to create a authentication using sfGuard and LDAP.
There is not a lot of documentation on this, so i appreciate a lot your article.
Though your 3 steps does not work for me, when i do what you say, i still have a :
Warning: call_user_func_array() [function.call-user-func-array]: First argument is expected to be a valid callback, ‘MyLDAP::checkLDAPPassword’ was given in /mysymphonyproject/plugins/sfGuardPlugin/lib/model/plugin/PluginsfGuardUser.php on line 62
Apparently it never goes to my myLDAPP.class.php located in the folder : mysymphonyproject/apps/frontend/lib
Do you know where it could come from ?
I can’t find any solution and i can’t see the end of it :s
Julien,
I think your problem is probably just a typo. You need to make sure the class and method names are exactly the same (including upper and lower cases) in your app.yml as in the class file. At least, when I tried changing the class name in my app.yml to MyLDAP rather than myLDAP I got exactly the same error message as you.
Anyway hope you sort things out because I know how frustrating it can be when you’re so close to getting it all to work!
Hello,
You were right.
Thank you again !
Hello again,
Sorry to be a little a pain in the ass, but i do have another question on the same area.
It appears that my program authentificates via LDAP only when i type “admin” as a username. if i don’t then it will just reject usnme & psswd, and won’t use my class anyway.
Do you have any idea ?
I’m not sure what your problem is exactly. If you’ve set up the plugin according to the sfGuard instructions, including the right app.yml setup, then it should work. If it’s verifying the admin user and not LDAP users then I can only guess there’s a problem with the app.yml file and so it’s just never using your LDAP class. Sorry I can’t help more without seeing the app.yml file.
Did you try the following?
$user = sfGuardUserPeer::retrieveByUsername($username);
return $user->checkPasswordByGuard($password);
the new version (for Symfony 1.1) contains the $user object as a third parameter in the callable I believe, so you don’t have to call the retrieveByUsername thus saving one SQL statement.
Ah great thanks! I haven’t had time to look in detail at 1.1 yet but I’ll give that a try.
Pingback: The Codebelay Blog » Installing sfGuardPlugin in symfony 1.1 — A Guide for the Perplexed
That worked Ben, thanks.
Now I only have to write the LDAP autentication code… since I’m not the ldap admin I have to figure it out.
Well I follow all the steps and it doesn’t work for me. First I create a file called LDAP.class.php and put this code inside:
class LDAPclass extends sfGuardSecurityUser {
public static function checkLDAPPassword($username, $password)
{
$options = array(‘account_suffix’=>’@uci.cu’,'base_dn’=>’DC=uci,DC=cu’,'domain_controllers’=>array(’10.0.0.3′,’10.0.0.4′));
$ldap = new adLDAP($options);
$authenticated = $ldap->authenticate($username, $password);
if ($authenticated) {
return true;
} else {
return false;
}
}
}
and my app.yml file look like this:
all:
sf_guard_plugin:
check_password_callable: [LDAP, checkLDAPPassword]
When I put my domain user I get always this error: The username and/or password is invalid.
Can any help me?
It’s seems too easy to me, so I think I’m missing something… can’t you simply override checkPassword method in sfGuardUser class? sfGuardUser is an extension of PluginsfGuardUser and it’s the class used by sfGuard (indeed, the use of PluginsfGuardUser class is just meant to be something in the middle between sfGuardUser and BasesfGuardUser to separate plugin’s customizations and developer’s ones).
I’ve run into a similar problem as ReynierPM and Julien: I can’t validate the LDAP password unless I use a username which is registered in the sfGuard database. The user “admin” exists only if you created it during inital setup of the sfGuard plugin. This is due to the way the validator class of the plugin checks the password: By using an instance of sfGuardUser ( $user = sfGuardUserPeer::retrieveByUsername($username) ). If the user doesn’t exist in the database, he/she cannot be checked for the (ldap) password afterwards.
What I need is a way to have a sfGuard user from the ldap directory. Maybe I can write a custom ::retrieveByUsername() method which initally pulls the requested user out of the ldap directory and inserts it into the sfGuard database? But I get the feeling this is not the way this powerful framework should be used. Does anybody else have an idea about this?
Well this is what I’m think too. I have a method in wich I use a LDAP class and check the credentials of a certain user. Then after check if the user is who really he/she said then I want to insert this data in sf_guard table but I don’t know how. Can any help me?
Any new findings on this one? I’m running into the same problem implementing a Facebook Connect for a Symfony app.
I have detailed a solution to this problem, very easily altered to use both or either authentication mechanisms. The big deal is to alter the validator for that signin form because that is where it blows up where there is no user.
http://blog.honnecke.us/2010/01/using-sfdoctrineguardusers-external-authentication/