Home » CakePHP » CakePHP: LDAP with full CRUD, a living example!

I’ve been using CakePHP for a while now and I’ve been thinking for a while it was time to see if I could give something back. As an IT leader I’m in love with LDAP. It makes life so simple for me and my team. The big downside to LDAP is it’s not very easy to learn how all the objectClasses and attributes work with various applications. Microsoft has eliminated this with the Microsoft Management Console (MMC). It amazes me that no open source project has developed a tools such as this before. I’ve worked on a open source tool in the past that was a web interface wrapper around ldap to do account management, so I’m familiar with the requirements such an application should have. Being that CakePHP is so powerful, I wanted to see if I could do this with that.

When I first started, I realized that CakePHP didn’t have an LDAP data source officially supported yet. I did find two articles about some good attempts. One by euphrate, unfortunately this one was only for reading from ldap. The second one was by Gservat, this one was a bit more complete, but was not really working for me and as i read from his comments many others. I think we wrote his for CakePHP 1.1. Since I wanted to use Current cake 1.2.8xxx I set out to use this as my start and fix/extend it.

Before we get started I want to state the environment I was using to do my work was Redhat Enterprise 5.2 & Fedora 10 (Work requirement) with redhat directory server 8.1 and Fedora directory server 1.2. Now while LDAP is a standard protocol, some of the driver may have become centric to those platforms, so if this is the case, please leave me a comment and I will try to correct the ldap data source I’m working on. My hope is to get ldap as an offical CakePHP data source. With that said the reason i call this a living example is because I’ve continued to upgrade and improve this data source as well as this article. Some of the next features I want to implement is data associations. Basically has and belongs to many relations. This way when you look up an user account it also shows you all the groups that user is in. This will take some time but I’ll get there. This work is all being done in the hopes that I can use this data source and CakePHP to build a really user friendly web interface for managing enterprise LDAP infrastructures without a whole lot of LDAP knowledge.

6/1/2011 – This datasource has gotten picked up by the CakePHP core guys and they may be officially adding it in the future.  I’ve also added some changes to support Active Directory better.

4/15/2010 – Made several changes to the code to hopefully play nicely with active directory.  Tested it with CakePHP-1.3 everything looks good.  Please test and leave feedback.  The new datasource requires you to add a new field to the database.cfg ldap entry.  See the config below

8/20/2009 – New Home for the source. I’ve got this datasource in my github tree now http://github.com/analogrithems/idbroker/tree/master/models/datasources enjoy, and feel free to submit bugs or request there.

7/13/2009 – updated ldap_source.php to make better use of the debug describe code. Also fixed the way things update. Only update what has changed instead of whole record. This will help with LDAP aci rules when logging in as non-admin users and trying to do things like update your userpassword or email.

6/20/2009 – Updated ldap_source.php to work with OpenLDAP 2.3 schema system. First it will try ‘cn=schema’, if that doesn’t return any results then it looks for schemas in ‘cn=subschema’ this make sure the code will work with OpenLDAP as well as the Netscape based versions like iPlanet, Redhat Directory Server, Fedora Directory Server etc.

First things first, here is my ldap data source for CakePHP. You will need to download this ldap_source.php to your ‘app/models/datasources/’ directory.

So lets dive right in below is the database config we will use.

<?php 
class DATABASE_CONFIG { 	
var $ldap = array (
 		'datasource' =--> 'ldap',
		'host' => 'localhost',
		'port' => 389,
		'basedn' => 'dc=examnple,dc=com',
		'login' => '',
		'password' => '',
		'database' => '',
                'tls'         => false,
                'type' => 'Netscape', //Available types are 'OpenLDAP', 'ActiveDirectory', 'Netscape'
		'version' => 3
	);
}
?>

You notice that the variables database, login and password are blank. Keep at least database this way. You can populate login and password if don’t want your ldap connections to be anonymous. I keep mine blank because I have written my own auth component that uses ldap, So once I’m authed that gets passed to the datasource instead. This is a ugly hack that I’ve written another post about.

Please note that if you are using Redhat directory server, iPlanet or Fedora Project 389 then the type is Netscape as these all evolved off the Netscape LDAP server code base.  The other available types are ‘OpenLDAP’ or ‘ActiveDirectory’  (case matters).

Here is our people model for accessing the users in your LDAP tree.

<?php
class Person extends AppModel {
        var $name = 'Person';
        var $useDbConfig = 'ldap';

        // This would be the ldap equivalent to a primary key if your dn is
        // in the format of uid=username, ou=people, dc=example, dc=com
        var $primaryKey = 'uid';

        // The table would be the branch of your basedn that you defined in
        // the database config
        var $useTable = '';

        var $validate = array(
                'cn' => array(
                        'alphaNumeric' => array(
                                'rule' => array('custom', '/^[a-zA-Z ]+$/'),
                                'required' => true,
                                'on' => 'create',
                                'message' => 'Only Letters, Numbers and spaces	 can be used for Display Name.'
                        ),
                        'between' => array(
                                'rule' => array('between', 5, 40),
                                'on' => 'create',
                                'message' => 'Between 5 to 40 characters'
                        )
                ),
                'sn' => array(
                                'rule' => array('custom', '/^[a-zA-Z]*$/'),
                                'required' => true,
                                'on' => 'create',
                                'message' => 'Only Letters and Numbers can be used for Last Name.'
                ),
                'userpassword' => array(
                                'rule' => array('minLength', '8'),
                                'message' => 'Mimimum 8 characters long.'
                ),
                'uid' => array(
                                'rule' => array('custom', '/^[a-zA-Z0-9]*$/'),
                                'required' => true,
                                'on' => 'create',
                                'message' => 'Only Letters and Numbers can be used for Username.'
                )
        );
}
?>

Here is a very basic controller to accompany our people model. It demonstrates the important core functions and should get you started on using this data source with your own application.

<?php
class PeopleController extends AppController {

	var $name = 'People';    
	var $components = array('RequestHandler', 'Ldap');
	var $helpers = array('Form','Html','Javascript', 'Ajax');
 
	function add(){
		if(!empty($this->data)){
			$this->data['Person']['objectclass'] = array('top', 'organizationalperson', 'inetorgperson','person','posixaccount','shadowaccount');

			if($this->data['Person']['password'] == $this->data['Person']['password_confirm']){
				$this->data['Person']['userpassword'] = $this->data['Person']['password'];
				unset($this->data['Person']['password']);
				unset($this->data['Person']['password_confirm']);
			
				if(!isset($this->data['Person']['homedirectory'])&& isset($this->data['Person']['uid'])){
					$this->data['Person']['homedirectory'] = '/home/'.$this->data['Person']['uid'];
				}

				$cn = $this->data['Person']['cn'];
				if ($this->Person->save($this->data)) {
					$this->Session->setFlash($cn.' was added Successfully.');
					$id = $this->Person->id;
					$this->redirect(array('action' => 'view', 'id'=> $id));
				}else{
					$this->Session->setFlash("$cn couldn't be created.");
				}
			}else{
				$this->Session->setFlash("Passwords don't match.");
			}
		}
		$attributes = array('uidnumber', 'uid', 'homedirectory');
		$preset = $this->autoSet($attributes);
		foreach($this->data['Person'] as $key => $value){
			$preset[$key] = $value; 
		}
		$this->data['Person'] = $preset;
		
		$groups = $this->Ldap->getGroups(array('cn','gidnumber'),null,'posixgroup');
		foreach($groups as $group){
			$groupList[$group['gidnumber']] = $group['cn'];
		}
		natcasesort($groupList);
		$this->set('groups',$groupList);
		$this->layout = 'people';
	}

	function view( $id ){
		if(!empty($id)){
			$filter = $this->Person->primaryKey."=".$id;
			$people = $this->Person->find('first', array( 'conditions'=>$filter));
			$this->set(compact('people'));
		}
		$this->layout = 'people';
	}

	function delete($id = null) {
		$this->Person->id = $id;
		return $this->Person->del($id);
	}

	/**
	*  The AuthComponent provides the needed functionality
	*  for login, so you can leave this function blank.
	*/
	function login() {
	}

	function logout() {
		$this->redirect($this->LdapAuth->logout());
	}

	//Very Ugly, fix this.,
	function isAuthorized() {
		return true;
	}

}

So lets talk about somethings here, in our model we define $primaryKey & $useTable variables. The $useTable is the branch of the ldap server. For this models purpose we define our table as ‘ou=people’. This makes sure that objects we create (I.E. Users/people) will be added under the organization unit people. It also makes sure that when you pass something like ‘jdoe’ to the delete action it will search that branch for the user object to delete. The $primaryKey also helps in the creation and deleting of users. It makes sure that the dn is created as uid, this is helpful to make sure that a user doesn’t already have that user name. Also since ldap is case insensitive you don’t have to worry about the possible variations of the object names when checking the existence.

Now your model isn’t limited to one branch or object type. If you wanted to create a browser for example your could define a model like the following.

You’ll notice here we set our $useTable to nothing (important, you get errors about no db defined from CakePHP if this missing). The really interesting part here is that we set $primaryKey to dn. This is the ultimate primary key for our type or data source. The difference here is that when we create/delete an object we have to pass it the full dn.

Our new data source also adds some new options to the find function.
$options[‘targetDN’] : This is more like the point in the tree we want to start our search. If you don’t define it it defaults to the $useTable.$config[$useDbConfig][‘basedn’] if your $useTable variable is empty it defaults to the basedn configured in your database config.

$options[‘scope’] : If you’ve worked with ldap before then you are familiar with the concept of search scopes. You have three search scopes, ‘sub’, ‘one, & ‘base’. Basically sub means search from this point down the tree. one means search one level below this point and base means search just this point. For example if you wanted to see if a user already existed you could set the targetDn to uid=jdoe,ou=people,dc=example,dc=com and it will check if this object already exists. The default scope is sub

60 thoughts on “CakePHP: LDAP with full CRUD, a living example!

  1. egelados says:

    exceptional article 🙂

  2. jaymitt says:

    Thanks for writing this!
    I originally found euphrate_ylb’s article on the cakephp site, then followed it to gservat’s, but they were a little difficult to understand because I’m a noob to cakePHP and PHP in general.

    This really helps out. I’m in IT as well, and like many companies we are Microsoft-centric, but I would rather focus my learning efforts on open-source technologies.

    That is why I’m learning/using PHP and cakePHP, and I want to write some admin web interfaces that will be able to interact and pull data from our MS Active Directory. I will be using this code to try and accomplish this – I’ll let you know how it goes.

    • analogrithems says:

      Awesome, if you have any troubles let me know. I’d like to make sure this works with MS as well, It fact I wrote the ldap datasource for a web based mmc for unix and hopefully windows. I really like the way the M$ mmc works but there is nothing like it for Unix yet. Also I’d like to have a web thin client version for windows to manage AD domains.

  3. dotbart says:

    Hi,
    thanks for both updating and sharing your source. It’s a really nice thing to do 🙂

    I have a question tho. When using the old version from gservat, I didn’t get any errors connecting to the LDAP. Just when writing to it..
    However in your version I get the following when not executing anything (just opening a form in a controller wich uses the datasource):
    “Warning (2): ldap_count_entries(): supplied argument is not a valid ldap result resource [APP\models\datasources\ldap_source.php, line 577]”

    So I guess this is Cake trying to find out the Model structure like the DESCRIBE in SQL. Great, but why the error?

    In your code, I found you’re looking for cn=Schema. When searching my LDAP, I can’t find an object like that. Is that what causes it? How can I solve this?

    Thanks,
    Bart

    • analogrithems says:

      I’ve updated my code. The cn=schema was for pulling the schema. I originally wrote it around redhat ds. I then updated the code to use cn=schema if that fails then use cn=subschema (Openldap). I’ve also made several fixes to the debug describe format. The ldap_count_entries is ignore able and i will actually place an @ to silence it, notice it’s only a warning. This is caused by always checking for record count for compatibility to cakephp model design

  4. Brendan says:

    The link to ldap_source.php at the top of the article is returning a 404.

    I think this is the correct URL:

    http://www.analogrithems.com/rant/wp-content/uploads/2009/07/ldap_source.phps

  5. Robert says:

    Thanks for the code.
    Works great.

    I had to change line 72 in ldap_source.php:

    if (!ldap_start_tls($this->connection)) {

    $this->log(“Ldap_start_tls failed”, ‘ldap.error’);

    fatal_error(“Ldap_start_tls failed”);

    }

    Changed $this->connection in $this->database on the 1st line:

    if (!ldap_start_tls($this->database)) {

    $this->log(“Ldap_start_tls failed”, ‘ldap.error’);

    fatal_error(“Ldap_start_tls failed”);

    }

  6. […] with this by Nathaniel Price and modifying the code using some other posts that I have found like CakePHP with full CRUD, a living example! by analogrithems.  Both posts are excellent and I would not know where to begin without […]

  7. […] with this by Nathaniel Price and modifying the code using some other posts that I have found like CakePHP with full CRUD, a living example! by analogrithems.  Both posts are excellent and I would not know where to begin without […]

  8. orangepeelbeef says:

    I can’t for the life of me get this to work with microsoft Active directory.
    I am sure this is related to the schema, but can’t get it working.

    2010-01-12 18:35:51 Warning: Warning (2): ldap_count_entries(): supplied argument is not a valid ldap result resource in [APP/models/datasources/ldap_source.php, line 592]
    2010-01-12 18:35:51 Warning: Warning (2): ksort() expects parameter 1 to be array, null given in [APP/models/datasources/ldap_source.php, line 1245]
    2010-01-12 18:35:51 Warning: Warning (2): Invalid argument supplied for foreach() in [CORE/cake/libs/view/helpers/form.php, line 128]
    2010-01-12 18:35:51 Notice: Notice (8): Undefined variable: schema_entries in [APP/models/datasources/ldap_source.php, line 617]
    2010-01-12 18:35:51 Notice: Notice (8): Undefined variable: return in [APP/models/datasources/ldap_source.php, line 721]

    any ideas?

  9. This datasource works almost… Except I use OpenLDAP provided by Ubuntu, which has its schemas stored in the LDAP server (cn=schema,cn=config), and I cannot retrieve them using the getLDAPschema() class. Maybe you can point me in the right direction?

  10. orangepeelbeef says:

    windows server 2003. Those errors may be strictly cosmetic, I think it is actually binding to the ad server. Had some trouble with the ‘proxy user’ it ended up having to be a fully qualified cn=blabla,dc=bla etc. I was hoping username@domain.com would work, but it didn’t. So, in short, i believe it is functioning, even with those errors above.

    • analogrithems says:

      The way to tell is simply in your view do a

      < ?php print_r($this->data); ?>

      i believe and this will tell you all the details of your datasource connection

  11. orangepeelbeef says:

    Some fixes to get rid of the errors when it can’t get the schema:

    592: if(@ldap_count_entries($this->database, $check) > 0){

    617: if( isset($schema_entries) ) {

    //add before 721
    if (!isset($return)) {
    $return=array();
    }
    721: return $return;

    but with those fixes in, still getting these errors:
    Notice (8): Undefined index: attributetypes [APP/models/datasources/ldap_source.php, line 1246]
    Warning (2): ksort() expects parameter 1 to be array, null given [APP/models/datasources/ldap_source.php, line 1247]
    Warning (2): Cannot modify header information – headers already sent by (output started at /var/www/fw_request/app/models/datasources/ldap_source.php:1282) [CORE/cake/libs/controller/controller.php, line 644]

  12. orangepeelbeef says:

    header information was whitespace in my ldap_source.php file after the ?>

    ksort seems to be failing because the object->schema contains nothing.

    otherwise, i’m up and running.

    How do I validate that a user is already logged in?

    How do I retrieve the user’s email for example?

  13. orangepeelbeef says:

    function describe(&$model, $field = null){
    $attrs=null;
    $schemas = $this->__getLDAPschema();
    if (isset($schemas[‘attributetypes’])) {
    $attrs = $schemas[‘attributetypes’];
    ksort($attrs);
    }
    if(!empty($field)){
    return($attrs[strtolower($field)]);
    }else{
    return $attrs;
    }
    }

    fixes the attributetypes and ksort errors

    but again, i don’t know if the schema is necessary for retrieving info or what.

    • analogrithems says:

      Ya, the schema is important. It does the input validation. I’m going to try to setup a windows 2003 server this weekend so i can get the schema working under it. Once I identify the schema path and the make sure its objectclasses and attributes are parsed right it should fix your problems.

  14. orangepeelbeef says:

    I found this, but I’m not sure where to put it or if it’s even the right thing…

    http://wiki.bestpractical.com/view/LdapAttrMap

    Here are mappings which should work with a Windows Active Directory server (Win2000 and Win2003).

    Set($LdapAttrMap, {‘Name’ => ‘sAMAccountName’,
    ‘EmailAddress’ => ‘mail’,
    ‘Organization’ => ‘physicalDeliveryOfficeName’,
    ‘RealName’ => ‘cn’,
    ‘ExternalContactInfoId’ => ‘dn’,
    ‘ExternalAuthId’ => ‘sAMAccountName’,
    ‘Gecos’ => ‘sAMAccountName’,
    ‘WorkPhone’ => ‘telephoneNumber’,
    ‘Address1’ => ‘streetAddress’,
    ‘City’ => ‘l’,
    ‘State’ => ‘st’,
    ‘Zip’ => ‘postalCode’,
    ‘Country’ => ‘co’}
    );

    • analogrithems says:

      No, this is from RT which is witten in Perl. I need to get the schema working for ad, that fixes the issues you are having.

  15. Bas Tichelaar says:

    The version of OpenLDAP is 2.4.18. More information is on this page: http://www.openldap.org/doc/admin24/slapdconf2.html. In short, the current version of the datasource retrieves the main schema (cn=schema,cn=config) but not the subschemas cn={0}core,cn=schema,cn=config for example. Thanks for your quick replies, let’s hope we get this working!

    • analogrithems says:

      I’l try to setup a current version of the openldap to make it work with the datasource. I need to do this with AD also.
      It’s important for it to load the schema array since it will use it later for data validation. The schema’s state which fields are must vs may. In other words it helps input validation know which fields you must have vs which fields you may have.

      in the mean time if you want to try to fix the datasource for this version of openldap look at
      http://github.com/analogrithems/idbroker/blob/master/models/datasources/ldap_source.php
      at line 589 i have the function that trys to parse the schema

  16. orangepeelbeef says:

    I pulled the schema naming contexts from my AD server. Here are the results. (replaced my root dc with ROOTDC to limit information exposure)

    ‘type’ => ‘namingContexts’,
    ‘vals’ => [
    ‘ROOTDC’,
    ‘CN=Configuration,ROOTDC’,
    ‘CN=Schema,CN=Configuration,ROOTDC’,
    ‘DC=DomainDnsZones,ROOTDC’,
    ‘DC=ForestDnsZones,ROOTDC’

  17. orangepeelbeef says:

    Also, Fwiw, phpldapadmin can retrieve my AD schema with no modifications. I only mention this because the __getLDAPschema() code mentions it was borrowed from phpldapadmin. I assume it was borrowed a long time ago, and things have probably changed dramatically.

  18. orangepeelbeef says:

    Aside from the schema it seems to be authenticating properly. I see the user information gets dumped into the session data, but how do I access the ldap attributes? I want to retrieve the email address the ‘mail’ attribute, but i’m not having much luck getting that info. Should I be grabbing it out of the session or is there some trick for getting it via something like $user[‘mail’] ?

    • analogrithems says:

      I do it like this
      function view( $id ){
      if(!empty($id)){
      $filter = $this->Person->primaryKey.”=”.$id;
      $people = $this->Person->find(‘first’, array( ‘conditions’=>$filter));
      $this->set(compact(‘people’));
      }
      }

      And in my model I define my primary Key like this
      // This would be the ldap equivalent to a primary key if your dn is
      // in the format of uid=username, ou=people, dc=example, dc=com
      var $primaryKey = ‘uid’;

      Note though that depending on what your trying to model you can change the primaryKey, sometimes you want to look at your objects holistically so the primaryKey would be ‘dn’. Or if you are dealing with groups your primary key would be ‘cn’. This takes a little getting used to.

      In My view I can then access the cn like this
      < ?php echo $this->data[‘Person’][‘cn’]; ?>

  19. orangepeelbeef says:

    Thanks, that was a big help. I now can view the ldap user data within the users controller, but how do I know what the uid of the user who is currently logged in is? That is the last piece I’m missing to make this functional. I also want to do some differing access levels based on what ldap group the user is in, so I need to be able to access the ldap info for the user who previously logged in. I used beforefilters to force a login for all my pages.

  20. orangepeelbeef says:

    figured it out:

    controller:

    $user = $this->LdapAuth->user();
    $this->set(compact(‘user’));

    then can retrieve as

    view:
    echo $user[‘User’][‘mail’]

  21. Bas Tichelaar says:

    Hi, any progress made already?

  22. ldibanyez says:

    Hi there,

    First of all, congratulations for your effort, is really useful.

    I’ve just set it up and succesfully read (thats all i need for the app i’m doing) the users, but now i need some relations between ldap-sourced models and between ldap-sourced and sql-sourced models. I’ve seen that you implemented ‘generateassociationQuery’ but i can’t figure out what goes in the ‘foreignKey’ field.

    For example, if i got a set of ou (departments), and inside each of them a set of users, how i say to cake that a user belongsTo a department?

    Or maybe i’m just screwing it all up, because foreign key is a relational concept not applicable to ldap, and a i’ll be better making a full dn find to know the users that belong to a department.

    Thanks in advance.

    • analogrithems says:

      The foriegn key is how you want to index your ldap data. If you have the cn and want all your lookups in a certain controller/model to be via the cn then use the cn as your primary group. This works well in ou=people or ou=group context. If you are referring to any object in the tree, or you don’t specify the foriegn key then it will be dn. The foreign key is just the index really.

      In your situation I would make my departments groupofuniquenames and follow the traditional ldap DIT (Directory information tree). Then include each users dn in that group. This make more sense in the long run also because if you are using something like apache to perhaps restrict access to a site (For example only billing department can access billing records page). Then you’ll be happy you followed the standard convention because you can tell apache only people in the group ‘cn=billing, ou=groups,dc=example,dc=org’ are allowed access. Also some people may be in multiple departments. What do you do then? In the group context it’s perfectly fine to have someone in multiple groups. From the IT perspective I’ve found I often place my IT people in each group to verify they work. This makes it easier for them to support the end users.

  23. Hi
    I managed to use your database model for a cakephp project connecting to openldap. Good work, congrats.
    But I made several changes, which I like to share with you:

    Function update:
    if((!$this->in_arrayi(‘userpassword’, $update))&& (isset($entry[‘userpassword’]))){
    => password needs to be set, to unset. Found the problem, when updating entries without password (eg objectclass top).
    same for the following lines:
    if (isset($entry[‘count’])) { unset($entry[‘count’]);}
    if (isset($entry[‘dn’])) { unset($entry[‘dn’]); }

    For the return value of the function, I added a return true, if $entry is empthy (if you call update but nothing changes….)

    To make the whole story to work with openldap, I changed all occurences of ‘attributetypes’ with ‘olcattributetypes’ and all occurences of ‘objectclasses’ with ‘objectclass’.
    Then I made: $schemaDN = ‘cn=schema,cn=config’; (added cn=config).
    Probably it would be a good thing to make this hardcoded things a config option in the database definition.

    I still have problems with adding new entries, but give me some time to find out 🙂
    Another trouble: Paging does not work (not implemented, needs limit of data) and Sort is also not working. Could you please tell me if sort is working for you ?

    If you like my changes as a path, let me know.

    Regards Benedikt

    • analogrithems says:

      I’ve been trying to decide if it should be a config option, or if it should be case statement t try to discover what the ldap server type is.

  24. If you make it a case statement you are making several queries against the ldap server for something, that will never change. This for each pageview. Probably not a performant idea.
    The disadvantage about a config file is, that you cannot use a fail-over configuration using different products (but does this work anyway ?).
    So I vote for a config option !

  25. Kip says:

    Is it possible for you to provide basic instruction? I think theres something wrong with your code section display. 🙁

    • analogrithems says:

      I’ve written an example using the code. There is no error, but I forgot to update the code in mention some new updates to the database.php please see notes above.

  26. I am having an issue with the latest ldap_source.php from git and trying to use it in combination with the LDAP Auth article you wrote. I had auth somewhat working with the original version of the ldap_source you posted, but I was getting errors about the query() function not existing. After updating to the latest one from git, I am getting the following:

    Notice (8): Undefined variable: return [ROOT/post/models/datasources/ldap_source.php, line 727]
    Warning (2): ksort() expects parameter 1 to be array, null given [ROOT/post/models/datasources/ldap_source.php, line 1251]

    And in the query output I get:
    -1: Can’t contact LDAP server

    I have my database config and model setup just as they were when it was semi-working and just as you outline in the article. This is also running against Active Directory if that helps. Any ideas?
    Thanks

    • analogrithems says:

      I added a new attribute that needs to be added to the database config, Here is my database.php This is to make the datasource work with different ldap servers operational attributes and know how to find the schemas that exists in different locations between them.

      vi config/core.php

      < ?php
       class DATABASE_CONFIG {
              //This will be used for extensions/plugins.  if you plugin needs a db, define it here.
              var $default = array(
                      'driver' => 'mysql',
                      'persistent' => false,
                      'host' => 'localhost',
                      'login' => 'user',
                      'password' => 'password',
                      'database' => 'database_name',
                      'prefix' => '',
              );
              var $ldap = array (
                      'datasource' => 'ldap',
                      'host' => 'localhost',
                      'port' => 389,
                      'basedn' => 'dc=analogrithems,dc=com',
                      'login' => '',     //For Proxy Userdn
                      'password' => '',  //For Proxy UserDN password
                      'database' => '',  //Needed to prevent cakePHP from throwing errors about database missing
                      'tls' => '', //Set this to true if using tls
                      'type' => 'Netscape', //Type Can be either 'Netscape', 'OpenLDAP', 'ActiveDirectory'
                      'version' => 3 //LDAP Version 3
               );
       }
      ?>
      
    • analogrithems says:

      Actually I think I am seeing an issue, looks like the upload to github broke part of the file. Working on it now, will have a new version later today.

      • analogrithems says:

        Nevermind, I forgot to mention that I added a new attribute to the database config. See the previous comments. I’ll update the article.

  27. Thanks very much. Everything is working great now. Fantastic article, thanks for all the work you have put in

  28. jwb says:

    Hello, there seems to be something wrong with the model/controller examples. There seems be a lot of coding missing and the ‘>’-signs are changed to > . Can you please email me your full examples?

  29. Keber says:

    Hi! good job, great effort.

    I’m trying to develop a user interface to mantain accounts on an ldap and googleapps. Many of our sites and services authenticate against the ldap, and users (9k+) have email and other services on googleapps. Althought user authentication to googleapps is already being done against the ldap through a SSO page, we still need an app to quickly create or update users, names and passwords on both ldap and GApps.

    I have to do this with cakephp, so i’m trying to use your ldap_source datasource. The first issue is that althought our ldap it’s implemented with OpenLDAP, setting this type results in a few warnings while trying to retrieve data. Instead, ‘ActiveDirectory’ type setting does the job without warnings. The second one, is that when I try to retrieve data with:

    $ldapusers = $this->User->find(‘uid=joe.*’);

    it retrieves only the first match and then returns a “4: Size limit exceeded” error. I’ve checked our sizelimit on /etc/ldap/slapd.conf on the server and it is set to 50000 . So, at this point, i’m not sure if I need to retrieve data with another function (but this doesn’t seems to be the problem), but this happens with OpenLDAP type setting too.

    Here is a portion of the code:

    ——— adms_controller.php ———-
    class AdmsController extends AppController {
    var $name = ‘Adms’;
    var $uses = array(‘User’);

    function index(){
    $ldap2 = $this->User->find(‘uid=joe.*’);
    $this->set(compact(‘ldap2’));
    }
    }

    —- index.ctp —-

    — The warnings when ‘type’ =>”OpenLDAP” on database.php —
    Notice (8): Undefined offset: 0 [APP/models/datasources/ldap_source.php, line 631]
    Notice (8): Undefined index: attributetypes [APP/models/datasources/ldap_source.php, line 1255]
    Warning (2): ksort() expects parameter 1 to be array, null given [APP/models/datasources/ldap_source.php, line 1256]

    Thanks in advance for any help, and again for your work!

  30. Keber says:

    This is how i’m showing the data from a simple database table, after sending to the view from the controller with $this->set(compact(

    echo $html->tableHeaders(array('username','mail','firstname','lastname','Edit','Delete'));
    foreach($ldapusuarios as $usuario) {
    echo $html->tableCells(array(
    array(
    $usuario['Ldapusuario']['uid'],
    $usuario['Ldapusuario']['mail'],
    $usuario['Ldapusuario']['givenname'],
    $usuario['Ldapusuario']['sn'],

    $html->link('Editar', "/adms/edit/".$usuario['Ldapusuario']['uid']),
    $html->link('Eliminar', "/adms/delete/".$usuario['Ldapusuario']['uid'])
    )
    ));

    echo "";

    }//endforeach;
    echo "";

    Something similar with the real ldap (here named ldap2)


    echo $html->tableHeaders(array('username','mail','firstname','lastname'));//,'Edit','Delete'));
    foreach($ldap2 as $usuario) {
    if (isset($usuario['uid'])) { //ignores count array
    echo $html->tableCells(array(
    array(
    $usuario['uid'],
    $usuario['mail'],
    $usuario['givenname'],
    $usuario['sn'],

    )
    ));
    //print_r($usuario);
    echo "";
    }
    }//endforeach;

    I’ve tried replacing line 1025 (ldap_search call) to send 0 on limit param, but still shows the first row, althought the query debug shows


    1 search | cond: uid=ernesto.* | targetDn: cn=Users,dc=foo,dc=org | order: | limit: 1 25 24084
    2 search | cond: username=ernesto.lastnameA | targetDn: cn=Users,dc=foo,dc=org | limit: 1 0 376
    3 search | cond: username=ernesto.lastnameB | targetDn: cn=Users,dc=foo,dc=org | limit: 1 0 226
    4 search | cond: username=ernesto.lastnameC | targetDn: cn=Users,dc=foo,dc=org | limit: 1 0 14
    ...

    Any ideas?

  31. Keber says:

    I’m very sorry, finally was a ‘layer 8’ or rtfm issue.

    $ldap2 = $this->User->find('all', array('conditions' => array('uid' => 'ernesto.*')));

    and after a few paints in the view, i’m now retrieving my list. I’ve restored the change made in my ldap_source file.

  32. Ralph Rößner says:

    The setup code for talking to OpenLDAP servers appears to be missing a value for SchemaFilter. Consequently the datasource constructs a broken (bad filter) LDAP query and is unable to read any schema information from OpenLDAP. Setting SchemaFilter fixes that, like this:

    — ldap_source.php.orig 2010-08-11 18:05:37.000000000 +0200
    +++ ldap_source.php 2010-08-11 18:05:42.000000000 +0200
    @@ -1293,6 +1293,7 @@
    }

    function setOpenLDAPEnv(){
    + $this->SchemaFilter = ‘(objectClass=subschema)’;
    $this->OperationalAttributes = ‘ + ‘;
    }

  33. Ruben says:

    I’m still having these 3 errors in my view.:

    Notice (8): Undefined offset: 0 [APP/models/datasources/ldap_source.php, line 626]
    Notice (8): Undefined index: attributetypes [APP/models/datasources/ldap_source.php, line 1250]
    Warning (2): ksort() expects parameter 1 to be array, null given [APP/models/datasources/ldap_source.php, line 1251]

    Code for user_controller.php

    class UsersController extends AppController {

    var $name = ‘Users’;

    function index() {
    $data = $this->User->find(‘all’, array (
    ‘conditions’ => array (
    ‘uid’ => ‘*’,
    ),
    ‘fields’ => array (
    ‘uid’,
    ‘givenname’,
    ‘sn’
    )
    ));
    $this->set(compact(‘data’));
    }
    }

    code for user.php

    class User extends AppModel {
    var $useDbConfig = ‘ldap_default’;
    var $primaryKey = ‘cn’;
    var $defaultObjectClass = ‘inetOrgPerson’;
    var $name = ‘User’;
    var $useTable = ‘ou=users’;
    }

    code for database.php

    var $ldap_default = array(
    ‘datasource’ => ‘ldap’,
    ‘host’ => array(‘localhost’),
    ‘port’ => 389,
    ‘basedn’ => ‘o=org,dc=test’,
    ‘login’ => ‘cn=root,dc=test’,
    ‘password’ => ‘someText’,
    ‘database’ => ”,
    ‘type’ => ‘OpenLDAP’,
    ‘tls’ => false,
    ‘version’ => 3
    );

    I have to mention that the users are retrived from the LDAP server.

  34. orangepeelbeef says:

    For some reason when i move the application from my development server and onto my prod server I get LDAP ‘Operations Error’ with no additional information in the logs, is there any way to increase debugging?

    • analogrithems says:

      You should use $this->log(print_r($var,true),’debug’); statements all over your coder and this model datasource to track things down.

  35. Invis says:

    Hey! Thanks for putting together this and the LdapAuth component — they work great!

    I’ve run into a problem getting complex conditions to work — it seems that it passes the conditions array to the ldap_search call, which doesn’t work. Here’s an example of the find I’m running:

    find(‘all’, array(‘conditions’ => array(‘and’ => array(‘cn’ => $searchCn, ‘objectclass’ => ‘person’)), ‘fields’ => array(‘displayname’), ‘limit’ => 10));

    Is there another way to do this, syntax-wise? The filter string that needs to be generated for the actual query looks something like:

    ‘(&(cn=foo*)(objectclass=person))’

    Thanks much for the add-ons and any help!

    • analogrithems says:

      Try this condition
      $condition = array(‘and’ => array(‘equals’ => array( ‘cn’=> ‘aaronc’, ‘equals’=>array(‘objectclass’ => ‘person’))));

      filters in ldap are a tricky thing, I should update the docs for this to explain this logic. If you think about this, it makes sense. You have to use the equals operator, because it needs to support fuzzy matches.

  36. Leandro says:

    Hello, thank you very much for the code!

    Im wondering if you could help because im having trouble using Paginator Helper.

    I simply can’t sort any column coming from the Ldap Model (people). For example if i click on $this->Paginator->sort(‘givenname’); nothing happens. Other columns, based on Mysql Models works fine.

    Any fix or tip?

    • analogrithems says:

      I’m not sure it will work with paginator. I’ve never tried that. you should be able to get column sorting by setting options in your model->find

Leave a Reply to egelados Cancel reply

Your email address will not be published. Required fields are marked *

</Random> is Stephen Fry proof thanks to caching by WP Super Cache