solution I came up with. For one, you can't refer to AuthComponent via
$this->Auth inside your controller anymore, it has to be $this-
>LdapAuth. Not sure how much that will affect things.
First an explanation of what this does. Basically, the
LdapAuthComponent sits on top of AuthComponent and intercepts login
form submissions. It then uses the LdapUser model to check to see if
the user exists, and if you can bind successfully with that username
and password combination. If so, it inserts (or updates) a user in the
users table with the username and password combination that was
submitted on login, and from there AuthComponent can use that data in
the table to log them in. This is ideal for me because I don't have
control over the data the LDAP server provides me, and I'd like to be
able to attach more data to a user than the LDAP server provides me.
Also note: this code is unsupported. Use it at your own risk. I may or
may not answer questions about it.
Anyway, here's the extension I came up with (saved in app/controllers/
components/ldap_auth.php):
<?php
App::import('Component', 'Auth');
class LdapAuthComponent extends AuthComponent {
var $ldapModel = 'LdapUser';
function startup(&$controller) {
if (isset($controller->data[$this->userModel])) {
$username = $controller->data[$this->userModel][$this-
>fields['username']];
$password = $controller->data[$this->userModel][$this-
>fields['password']];
$res = $this->preauthUser($username, $password);
if (!$res) {
//set password to blank to ensure the auth fails
$controller->data[$this->userModel][$this->fields['password']] =
'';
}
}
//Continue with standard auth process
return parent::startup($controller);
}
function preauthUser($username, $password) {
//TODO: un-hard-code the other database model fields.
$ldap =& $this->getLdapModel();
$model =& $this->getModel();
$res = $ldap->auth($username, $password);
if ($res !== false) {
//Successfull LDAP bind - update user database
$data = $model->findByUsername($username);
if (!$data) {
$data = array();
$data[$this->userModel][$this->fields['username']] = $username;
$data[$this->userModel]['created'] = date('Y-m-d H:i:s');
}
//TODO: if data hasn't changed, avoid updating the database
$data[$this->userModel][$this->fields['password']] = $this-
>password($password);
$data[$this->userModel]['email'] = $res[0][$this->ldapModel]
['mail'];
$model->save($data);
return true;
}
return false;
}
function &getLdapModel($name = null) {
$model = null;
if (!$name) {
$name = $this->ldapModel;
}
if (PHP5) {
$model = ClassRegistry::init($name);
} else {
$model =& ClassRegistry::init($name);
}
if (empty($model)) {
trigger_error(__('LdapAuth::getLdapModel() - Model is not set or
could not be found', true), E_USER_WARNING);
return null;
}
return $model;
}
}
?>
I use the following model for Users - this is stored in the database
(app/models/user.php):
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array(
'email' => array('email'),
'password' => array('alphaNumeric'),
'active' => array('numeric')
);
}
/* database creation script:
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL auto_increment,
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`email` varchar(128) default NULL,
`active` tinyint(4) NOT NULL default '0',
`created` datetime default NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
*/
?>
And the following model for LdapUsers (app/models/ldap_user.php)
I'm not sure where I got this code--I thought it was from the bakery,
but I can't seem to find it again. :( Regardless, it has been slightly
modified for my purposes.
<?php
class LdapUser extends AppModel
{
var $name = 'LdapUser';
var $useTable = false;
var $primaryKey = 'uid';
var $host = 'ldap.example.com';
var $port = 389;
var $baseDn = 'ou=people,o=example.com';
var $user = '';
var $pass = '';
var $ds;
function __construct()
{
parent::__construct();
$this->ds = ldap_connect($this->host, $this->port);
ldap_set_option($this->ds, LDAP_OPT_PROTOCOL_VERSION, 3);
if ($this->user) {
ldap_bind($this->ds, $this->user, $this->pass);
} else {
//Do an anonymous bind.
ldap_bind($this->ds);
}
}
function __destruct()
{
ldap_close($this->ds);
}
function findAll($attribute = 'uid', $value = '*', $baseDn = '')
{
if (!$baseDn) {
$baseDn = $this->baseDn;
}
$r = ldap_search($this->ds, $baseDn, $attribute . '=' . $value);
if ($r)
{
//if the result contains entries with surnames,
//sort by surname:
ldap_sort($this->ds, $r, "sn");
$result = ldap_get_entries($this->ds, $r);
return $this->convert_from_ldap($result);
}
return null;
}
// would be nice to read fields. left the parameter in as placeholder
and to be compatible with other read()'s
function read($fields=null, $uid)
{
$r = ldap_search($this->ds, $this->baseDn, 'uid='. $uid);
if ($r)
{
$l = ldap_get_entries($this->ds, $r);
$convert = $this->convert_from_ldap($l);
return $convert[0];
}
}
function save($data)
{
$dn = "uid=".$data['LdapUser']['uid'].",".$this->baseDn;
foreach ($data['LdapUser'] as $field => $value):
$data_ldap[$field][0] = $value;
endforeach;
// The following line sets the object classes. The ones shown are
the default for users. Other environments may be different.
// For example, in my world, I use
array('inetOrgPerson','posixAccount','top','shadowAccount').
// However, this depends on how your ldap schema is setup.
$data_ldap['objectClass'] =
array('account','posixAccount','top','shadowAccount');
return ldap_add($this->ds, $dn, $data_ldap);
}
function del($uid)
{
$dn = "uid=$uid,".$this->baseDn;
return ldap_delete($this->ds, $dn);
}
function auth($uid, $password)
{
if (trim($password) == '') {
return false;
}
$result = $this->findAll('uid', $uid);
if(isset($result[0]))
{
if (@ldap_bind($this->ds, "uid=$uid,$this->baseDn", $password))
{
return $result;
}
else
{
return false;
}
}
else
{
return false;
}
}
function findLargestUidNumber()
{
$r = ldap_search($this->ds, $this->baseDn, 'uidnumber=*');
if ($r)
{
// there must be a better way to get the largest uidnumber, but I
can't find a way to reverse sort.
ldap_sort($this->ds, $r, "uidnumber");
$result = ldap_get_entries($this->ds, $r);
$count = $result['count'];
$biguid = $result[$count-1]['uidnumber'][0];
return $biguid;
}
return null;
}
private function convert_from_ldap($data)
{
$final = array();
foreach ($data as $key => $row):
if($key === 'count') continue;
foreach($row as $key1 => $param):
if(!is_numeric($key1)) continue;
if($row[$param]['count'] === 1)
$final[$key]['LdapUser'][$param] = $row[$param][0];
else
{
foreach($row[$param] as $key2 => $item):
if($key2 === 'count') continue;
$final[$key]['LdapUser'][$param][] = $item;
endforeach;
}
endforeach;
endforeach;
return $final;
}
}
?>
From there, I just do as suggested in http://book.cakephp.org/view/172/Authentication,
replacing $this->Auth with $this->LdapAuth in the controller as
needed.
On Nov 3, 1:40 am, "bitter...@gmail.com" <bitter...@gmail.com> wrote:
> Hi Nathaniel,
>
> Did you get your AuthComponent extension working?
> If so, would you like to share it? :)
>
> I've been looking for LDAP authentication in cake for some time, but
> I'm not sure how to get to it.
>
> --
> alx
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "CakePHP" group.
To post to this group, send email to cake-php@googlegroups.com
To unsubscribe from this group, send email to cake-php+unsubscribe@googlegroups.com
For more options, visit this group at http://groups.google.com/group/cake-php?hl=en
-~----------~----~----~----~------~----~------~--~---
No comments:
Post a Comment