views:

1707

answers:

3

I'm working on a CakePHP project and am currently building the user authentication part of it. The problem is that my authentication information (ie: the passwords) are not stored in my database -- the authentication source is LDAP but my question applies equally to any non-database source.

It appears as though Cake only handles passwords when they exist in the local database. The Cake Cookbook suggests that you can tell it a different controller/model/object to provide an authorization procedure by using the $this->Auth->authorize variable, however looking at the code (specifically the Auth::startup() function) it looks like Cake will always try to query the database first, checking for a matching username/password, before then looking at the alternative object you specified with Auth->authorize. That is, changing authorize only adds a second-level filter, it doesn't replace the database lookup.

// The process
1. User provides details
2. Cake checks the database
3. If OK, then check the custom object method
4. If OK, return true

// What I'd like:
1. User provides details.
2. Check the custom object method
3. If OK, return true
4. Profit.

Any ideas on how to do this, hopefully without hacking the core files?

A: 

Auth::authorize is indeed not a replacement for the model data, it just adds to it.

5.2.6.10 authorize

Normally, the AuthComponent will attempt to verify that the login credentials you've entered are accurate by comparing them to what's been stored in your user model. However, there are times where you might want to do some additional work in determining proper credentials.

That shouldn't be a problem though, since the LDAP details should be abstracted away in the model. Cake will still check the model for username and password, but it is getting its answers transparently from an LDAP directory. You just need to implement an LDAP datasource for the model. Maybe these two articles can get you started.

deceze
A: 

I was able to hack around the way Cake does it in a relatively dodgy, but probably acceptable way.

I added a "password" field onto my users table and set everyone's password to "a" (though you could use anything).

Then I added a custom hashing function into my model:

function hashPasswords($data) {
    $data['User']['password'] = 'a';
    return $data;
}

And told my controller to use that model for hashing:

$this->Auth->authenticate = ClassRegistry::init('User');

This means now that cake's first step will always pass (assuming the username exists in the table). The authorize function can now do its thing and do your proper check using whatever method you'd like.

It's basically changed the process to this:

// The process
1. User provides details
2. Cake checks the database **and always returns OK**
3. If OK, then check the custom object method
4. If OK, return true
nickf
+3  A: 

Assuming you are simply binding against LDAP and are storing/retrieving User data from MySQL, this approach will work as a "bridge" which will automatically create accounts for successful logins:

// app/controllers/components/ldap_auth.php
<?php
App::import('Component', 'Auth');
class LdapAuthComponent extends AuthComponent {
/**
 * Don't hash passwords
 */
    function hashPasswords($data){
        return $data;
    }
/**
 * We will initially identify the user
 */
    function identify($user=null, $conditions=null) {
        // bind credentials against ldap
        $ldapUser = $this->_ldapAuth($user); // do your stuff
        if (!$ldapUser) {
            return null; // if bind fails, then return null (as stated in api)
        }
        // get the cake model you would normally be authenticating against
        $model =& $this->getModel(); // default is User
        // check for existing User in mysql
        $user = $model->find('first', array('conditions' => array(
            'username' => $ldapUser['cn']
        ));
        // if no existing User, create a new User
        if (!$user) {
            $user = $model->save(array('User' => array(
                'username' => $ldapUser['cn'],
                // .. map needed ldap fields to mysql fields ..
            )));
            if (!$user) {
                $this->cakeError('ldapCreateUser');
            }
            // pass the id of the newly created User to Auth's identify
            return parent::identify($model->id, $conditions);
        }
        // pass the id of the existing User to Auth's identify
        return parent::identify($user[$this->userModel][$model->primaryKey], $conditions);
    }
/**
 * Lets check LDAP
 *
 * @return mixed Array of user data from ldap, or false if bind fails
 */
    function _ldapAuth($user) {
        $username = $user[$this->userModel][$this->fields['username']];
        $password = $user[$this->userModel][$this->fields['password']];
        // use the php ldap functions here
        return $ldapUser;
    }
}
?>

To use, replace all references to Auth with LdapAuth in your application or follow the instructions here.

Note that although the protected _ldapAuth() method could be abstracted out to an LdapUser model, and that model should read from an LdapSource, and the LDAP server connection settings should be in the database.php config, and the LdapAuthComponent should be adapted to use configurable field mappings, these aren't requirements to "just get it done". :)

deizel