{"id":13,"date":"2009-06-12T15:14:35","date_gmt":"2009-06-12T22:14:35","guid":{"rendered":"http:\/\/www.analogrithems.com\/rant\/?p=13"},"modified":"2011-08-25T19:49:37","modified_gmt":"2011-08-26T02:49:37","slug":"cakephp-with-full-crud-a-living-example","status":"publish","type":"post","link":"https:\/\/www.analogrithems.com\/rant\/cakephp-with-full-crud-a-living-example\/","title":{"rendered":"CakePHP: LDAP with full CRUD, a living example!"},"content":{"rendered":"<p><span>I&#8217;ve been using <span>CakePHP<\/span> for a while now and I&#8217;ve been thinking for a while it was time to see if I could give something back.  As an IT leader I&#8217;m in love with LDAP.  It makes life so simple for me and my team.  The big downside to LDAP is it&#8217;s not very easy to learn how all the <span>objectClasses<\/span> and attributes work with various applications.  Microsoft has eliminated this with the <\/span><em>Microsoft Management Console<\/em> (<em>MMC<\/em><span>).   It amazes me that no open source project has developed a tools such as this before.  I&#8217;ve worked on a open source tool in the past that was a web interface wrapper around <span>ldap<\/span> to do account management, so I&#8217;m familiar with the requirements such an application should have. Being that <span>CakePHP<\/span> is so powerful, I wanted to see if I could do this with that.<\/span><\/p>\n<p><span>When I first started, I realized that <span>CakePHP<\/span> didn&#8217;t have an LDAP data source officially supported yet.  I did find two articles about some good attempts.  One by <\/span><a href=\"http:\/\/bakery.cakephp.org\/articles\/view\/ldap-datasource-for-cakephp\" target=\"_blank\"><span><span><span>euphrate<\/span><\/span><\/span><\/a><span>, unfortunately this one was only for reading from <span>ldap<\/span>.  The second one was by <\/span><a href=\"http:\/\/memdump.wordpress.com\/2008\/04\/26\/ldap-data-source-now-with-full-crud\/\"><span><span>Gservat<\/span><\/span><\/a><span>, 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 <span>CakePHP<\/span> 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.<\/span><\/p>\n<p><span>Before we get started I want to state the environment I was using to do my work was <span>Redhat<\/span> Enterprise 5.2 &amp; Fedora 10 (Work requirement) with <span>redhat<\/span> 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 <span>ldap<\/span> data source I&#8217;m working on.  My hope is to get <span>ldap<\/span> as an <span>offical<\/span> <span>CakePHP<\/span> data source.  With that said the reason i call this a living example is because I&#8217;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 y<span>ou<\/span> look up an user account it also shows y<span>ou<\/span> all the groups that user is in.  This will take some time but I&#8217;ll get there.  This work is all being done in the hopes that I can use this data source and <span>CakePHP<\/span> to build a really user friendly web interface for managing enterprise LDAP infrastructures without a whole lot of LDAP knowledge.<\/span><\/p>\n<p><strong><span>6\/1\/2011 &#8211; This <span>datasource<\/span> has gotten picked up by the <span>CakePHP<\/span> core guys and they may be <\/span><span>officially<\/span> adding it in the future. \u00c2\u00a0I&#8217;ve also added some changes to support Active Directory better.<\/strong><\/p>\n<p><strong><span>4\/15\/2010 &#8211; Made several changes to the code to hopefully play nicely with active directory.\u00c2\u00a0 Tested it with <span>CakePHP<\/span>-1.3 everything looks good.\u00c2\u00a0 Please test and leave feedback.\u00c2\u00a0 The new <span>datasource<\/span> requires y<span>ou<\/span> to add a new field to the database.<span>cfg<\/span> <span>ldap<\/span> entry.\u00c2\u00a0 See the <span>config<\/span> below<\/span><br \/>\n<\/strong><\/p>\n<p><strong><span>8\/20\/2009 &#8211; New Home for the source.  I&#8217;ve got this <span>datasource<\/span> in my  <span>github<\/span> tree now <\/span><a href=\"http:\/\/github.com\/analogrithems\/idbroker\/tree\/master\/models\/datasources\"><span>http:\/\/<span>github<\/span>.com\/<span>analogrithems<\/span>\/<span>idbroker<\/span>\/tree\/master\/models\/<span>datasources<\/span><\/span><\/a> enjoy, and feel free to submit bugs or request there.<\/strong><\/p>\n<p><strong><span>7\/13\/2009 &#8211; updated <span>ldap<\/span>_source.<span>php<\/span> 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 <span>aci<\/span> rules when logging in as non-admin users and trying to do things like update your <span>userpassword<\/span> or email.<\/span><\/strong><\/p>\n<p><strong><span>6\/20\/2009 &#8211; Updated <span>ldap<\/span>_source.<span>php<\/span> to work with <span>OpenLDAP<\/span> 2.3 schema system.  First it will try &#8216;<span>cn<\/span>=schema&#8217;, if that doesn&#8217;t return any results then it looks for <span>schemas<\/span> in &#8216;<span>cn<\/span>=subschema&#8217;  this make sure the code will work with <span>OpenLDAP<\/span> as well as the Netscape based versions like <span>iPlanet<\/span>, <span>Redhat<\/span> Directory Server, Fedora Directory Server etc.<\/span><\/strong><\/p>\n<p><span>First things first, here is my <span>ldap<\/span> data source for <span>CakePHP<\/span>.  Y<span>ou<\/span> will need to download this <\/span><a href=\"http:\/\/www.analogrithems.com\/rant\/wp-content\/uploads\/2009\/07\/ldap_source.phps\" target=\"_blank\"><span><span>ldap<\/span>_source.<span>php<\/span><\/span><\/a><span> to your &#8216;app\/models\/<span>datasources<\/span>\/&#8217; directory.<\/span><\/p>\n<p><span>So lets dive right in below is the database <span>config<\/span> we will use.<\/span><\/p>\n<p>[php]<br \/>\n&lt;?php<br \/>\nclass DATABASE_CONFIG {<br \/>\nvar $ldap = array (<br \/>\n \t\t&#8216;datasource&#8217; =&#8211;&gt; &#8216;ldap&#8217;,<br \/>\n\t\t&#8216;host&#8217; =&gt; &#8216;localhost&#8217;,<br \/>\n\t\t&#8216;port&#8217; =&gt; 389,<br \/>\n\t\t&#8216;basedn&#8217; =&gt; &#8216;dc=examnple,dc=com&#8217;,<br \/>\n\t\t&#8216;login&#8217; =&gt; &#8221;,<br \/>\n\t\t&#8216;password&#8217; =&gt; &#8221;,<br \/>\n\t\t&#8216;database&#8217; =&gt; &#8221;,<br \/>\n                &#8216;tls&#8217;         =&gt; false,<br \/>\n                &#8216;type&#8217; =&gt; &#8216;Netscape&#8217;, \/\/Available types are &#8216;OpenLDAP&#8217;, &#8216;ActiveDirectory&#8217;, &#8216;Netscape&#8217;<br \/>\n\t\t&#8216;version&#8217; =&gt; 3<br \/>\n\t);<br \/>\n}<br \/>\n?&gt;<br \/>\n[\/php]<\/p>\n<p><span>Y<span>ou<\/span> notice that the variables database, <span>login<\/span> and password are blank.  Keep at least database this way.  Y<span>ou<\/span> can populate <span>login<\/span> and password if don&#8217;t want your <span>ldap<\/span> connections to be anonymous.  I keep mine blank because I have written my own <span>auth<\/span> component that uses <span>ldap<\/span>,   So once I&#8217;m <span>authed<\/span> that gets passed to the <span>datasource<\/span> instead.  This is a ugly hack that I&#8217;ve written another <\/span><a href=\"http:\/\/www.analogrithems.com\/rant\/2009\/06\/13\/ldapauth-component-for-cakephp\/\">post about<\/a>.<\/p>\n<p><span>Please note that if y<span>ou<\/span> are using <span>Redhat<\/span> directory server, <span>iPlanet<\/span> or Fedora Project 389 then the <\/span><strong>type<\/strong> is Netscape as these all evolved off the Netscape LDAP server code base.\u00c2\u00a0 The other available types are &#8216;OpenLDAP&#8217; or &#8216;ActiveDirectory&#8217;\u00c2\u00a0 (case matters).<\/p>\n<p>Here is our people model for accessing the users in your LDAP tree.<br \/>\n[php]<br \/>\n&lt;?php<br \/>\nclass Person extends AppModel {<br \/>\n        var $name = &#8216;Person&#8217;;<br \/>\n        var $useDbConfig = &#8216;ldap&#8217;;<\/p>\n<p>        \/\/ This would be the ldap equivalent to a primary key if your dn is<br \/>\n        \/\/ in the format of uid=username, ou=people, dc=example, dc=com<br \/>\n        var $primaryKey = &#8216;uid&#8217;;<\/p>\n<p>        \/\/ The table would be the branch of your basedn that you defined in<br \/>\n        \/\/ the database config<br \/>\n        var $useTable = &#8221;;<\/p>\n<p>        var $validate = array(<br \/>\n                &#8216;cn&#8217; =&gt; array(<br \/>\n                        &#8216;alphaNumeric&#8217; =&gt; array(<br \/>\n                                &#8216;rule&#8217; =&gt; array(&#8216;custom&#8217;, &#8216;\/^[a-zA-Z ]+$\/&#8217;),<br \/>\n                                &#8216;required&#8217; =&gt; true,<br \/>\n                                &#8216;on&#8217; =&gt; &#8216;create&#8217;,<br \/>\n                                &#8216;message&#8217; =&gt; &#8216;Only Letters, Numbers and spaces\t can be used for Display Name.&#8217;<br \/>\n                        ),<br \/>\n                        &#8216;between&#8217; =&gt; array(<br \/>\n                                &#8216;rule&#8217; =&gt; array(&#8216;between&#8217;, 5, 40),<br \/>\n                                &#8216;on&#8217; =&gt; &#8216;create&#8217;,<br \/>\n                                &#8216;message&#8217; =&gt; &#8216;Between 5 to 40 characters&#8217;<br \/>\n                        )<br \/>\n                ),<br \/>\n                &#8216;sn&#8217; =&gt; array(<br \/>\n                                &#8216;rule&#8217; =&gt; array(&#8216;custom&#8217;, &#8216;\/^[a-zA-Z]*$\/&#8217;),<br \/>\n                                &#8216;required&#8217; =&gt; true,<br \/>\n                                &#8216;on&#8217; =&gt; &#8216;create&#8217;,<br \/>\n                                &#8216;message&#8217; =&gt; &#8216;Only Letters and Numbers can be used for Last Name.&#8217;<br \/>\n                ),<br \/>\n                &#8216;userpassword&#8217; =&gt; array(<br \/>\n                                &#8216;rule&#8217; =&gt; array(&#8216;minLength&#8217;, &#8216;8&#8217;),<br \/>\n                                &#8216;message&#8217; =&gt; &#8216;Mimimum 8 characters long.&#8217;<br \/>\n                ),<br \/>\n                &#8216;uid&#8217; =&gt; array(<br \/>\n                                &#8216;rule&#8217; =&gt; array(&#8216;custom&#8217;, &#8216;\/^[a-zA-Z0-9]*$\/&#8217;),<br \/>\n                                &#8216;required&#8217; =&gt; true,<br \/>\n                                &#8216;on&#8217; =&gt; &#8216;create&#8217;,<br \/>\n                                &#8216;message&#8217; =&gt; &#8216;Only Letters and Numbers can be used for Username.&#8217;<br \/>\n                )<br \/>\n        );<br \/>\n}<br \/>\n?&gt;<br \/>\n[\/php]<br \/>\nHere 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.<\/p>\n<p>[php]<br \/>\n&lt;?php<br \/>\nclass PeopleController extends AppController {<\/p>\n<p>\tvar $name = &#8216;People&#8217;;<br \/>\n\tvar $components = array(&#8216;RequestHandler&#8217;, &#8216;Ldap&#8217;);<br \/>\n\tvar $helpers = array(&#8216;Form&#8217;,&#8217;Html&#8217;,&#8217;Javascript&#8217;, &#8216;Ajax&#8217;);<\/p>\n<p>\tfunction add(){<br \/>\n\t\tif(!empty($this-&gt;data)){<br \/>\n\t\t\t$this-&gt;data[&#8216;Person&#8217;][&#8216;objectclass&#8217;] = array(&#8216;top&#8217;, &#8216;organizationalperson&#8217;, &#8216;inetorgperson&#8217;,&#8217;person&#8217;,&#8217;posixaccount&#8217;,&#8217;shadowaccount&#8217;);<\/p>\n<p>\t\t\tif($this-&gt;data[&#8216;Person&#8217;][&#8216;password&#8217;] == $this-&gt;data[&#8216;Person&#8217;][&#8216;password_confirm&#8217;]){<br \/>\n\t\t\t\t$this-&gt;data[&#8216;Person&#8217;][&#8216;userpassword&#8217;] = $this-&gt;data[&#8216;Person&#8217;][&#8216;password&#8217;];<br \/>\n\t\t\t\tunset($this-&gt;data[&#8216;Person&#8217;][&#8216;password&#8217;]);<br \/>\n\t\t\t\tunset($this-&gt;data[&#8216;Person&#8217;][&#8216;password_confirm&#8217;]);<\/p>\n<p>\t\t\t\tif(!isset($this-&gt;data[&#8216;Person&#8217;][&#8216;homedirectory&#8217;])&amp;&amp; isset($this-&gt;data[&#8216;Person&#8217;][&#8216;uid&#8217;])){<br \/>\n\t\t\t\t\t$this-&gt;data[&#8216;Person&#8217;][&#8216;homedirectory&#8217;] = &#8216;\/home\/&#8217;.$this-&gt;data[&#8216;Person&#8217;][&#8216;uid&#8217;];<br \/>\n\t\t\t\t}<\/p>\n<p>\t\t\t\t$cn = $this-&gt;data[&#8216;Person&#8217;][&#8216;cn&#8217;];<br \/>\n\t\t\t\tif ($this-&gt;Person-&gt;save($this-&gt;data)) {<br \/>\n\t\t\t\t\t$this-&gt;Session-&gt;setFlash($cn.&#8217; was added Successfully.&#8217;);<br \/>\n\t\t\t\t\t$id = $this-&gt;Person-&gt;id;<br \/>\n\t\t\t\t\t$this-&gt;redirect(array(&#8216;action&#8217; =&gt; &#8216;view&#8217;, &#8216;id&#8217;=&gt; $id));<br \/>\n\t\t\t\t}else{<br \/>\n\t\t\t\t\t$this-&gt;Session-&gt;setFlash(&quot;$cn couldn&#8217;t be created.&quot;);<br \/>\n\t\t\t\t}<br \/>\n\t\t\t}else{<br \/>\n\t\t\t\t$this-&gt;Session-&gt;setFlash(&quot;Passwords don&#8217;t match.&quot;);<br \/>\n\t\t\t}<br \/>\n\t\t}<br \/>\n\t\t$attributes = array(&#8216;uidnumber&#8217;, &#8216;uid&#8217;, &#8216;homedirectory&#8217;);<br \/>\n\t\t$preset = $this-&gt;autoSet($attributes);<br \/>\n\t\tforeach($this-&gt;data[&#8216;Person&#8217;] as $key =&gt; $value){<br \/>\n\t\t\t$preset[$key] = $value;<br \/>\n\t\t}<br \/>\n\t\t$this-&gt;data[&#8216;Person&#8217;] = $preset;<\/p>\n<p>\t\t$groups = $this-&gt;Ldap-&gt;getGroups(array(&#8216;cn&#8217;,&#8217;gidnumber&#8217;),null,&#8217;posixgroup&#8217;);<br \/>\n\t\tforeach($groups as $group){<br \/>\n\t\t\t$groupList[$group[&#8216;gidnumber&#8217;]] = $group[&#8216;cn&#8217;];<br \/>\n\t\t}<br \/>\n\t\tnatcasesort($groupList);<br \/>\n\t\t$this-&gt;set(&#8216;groups&#8217;,$groupList);<br \/>\n\t\t$this-&gt;layout = &#8216;people&#8217;;<br \/>\n\t}<\/p>\n<p>\tfunction view( $id ){<br \/>\n\t\tif(!empty($id)){<br \/>\n\t\t\t$filter = $this-&gt;Person-&gt;primaryKey.&quot;=&quot;.$id;<br \/>\n\t\t\t$people = $this-&gt;Person-&gt;find(&#8216;first&#8217;, array( &#8216;conditions&#8217;=&gt;$filter));<br \/>\n\t\t\t$this-&gt;set(compact(&#8216;people&#8217;));<br \/>\n\t\t}<br \/>\n\t\t$this-&gt;layout = &#8216;people&#8217;;<br \/>\n\t}<\/p>\n<p>\tfunction delete($id = null) {<br \/>\n\t\t$this-&gt;Person-&gt;id = $id;<br \/>\n\t\treturn $this-&gt;Person-&gt;del($id);<br \/>\n\t}<\/p>\n<p>\t\/**<br \/>\n\t*  The AuthComponent provides the needed functionality<br \/>\n\t*  for login, so you can leave this function blank.<br \/>\n\t*\/<br \/>\n\tfunction login() {<br \/>\n\t}<\/p>\n<p>\tfunction logout() {<br \/>\n\t\t$this-&gt;redirect($this-&gt;LdapAuth-&gt;logout());<br \/>\n\t}<\/p>\n<p>\t\/\/Very Ugly, fix this.,<br \/>\n\tfunction isAuthorized() {<br \/>\n\t\treturn true;<br \/>\n\t}<\/p>\n<p>}<br \/>\n[\/php]<\/p>\n<p>So lets talk about somethings here,  in our model we define <strong><span>$<span>primaryKey<\/span><\/span><\/strong> &amp; <strong><span>$<span>useTable<\/span><\/span><\/strong> variables.  The <strong><span>$<span>useTable<\/span><\/span><\/strong><span> is the branch of the <span>ldap<\/span> server.  For this models purpose we define our table as <\/span><strong>&#8216;ou=people&#8217;<\/strong>.  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 &#8216;jdoe&#8217; to the delete action it will search that branch for the user object to delete.  The <strong><span>$<span>primaryKey<\/span><\/span><\/strong><span> also helps in the creation and deleting of users.  It makes sure that the <span>dn<\/span> is created as <span>uid<\/span>, this is helpful to make sure that a user doesn&#8217;t already have that user name.  Also since <span>ldap<\/span> is case insensitive y<span>ou<\/span> don&#8217;t have to worry about the possible variations of the object names when checking the existence.<\/span><\/p>\n<p>Now your model isn&#8217;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.<\/p>\n<p>You&#8217;ll notice here we set our <strong><span>$<span>useTable<\/span><\/span><\/strong><span> to nothing (important, y<span>ou<\/span> get errors about no db defined from <span>CakePHP<\/span> if this missing).  The really interesting part here is that we set <\/span><strong>$primaryKey<\/strong><span> to <span>dn<\/span>.  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 <span>dn<\/span>.<\/span><\/p>\n<p>Our new data source also adds some new options to the find function.<br \/>\n<strong>$options[&#8216;targetDN&#8217;] :<\/strong><span> This is more like the point in the tree we want to start our search.  If y<span>ou<\/span> don&#8217;t define it it defaults to the $<span>useTable<\/span>.$<span>config<\/span>[$<span>useDbConfig<\/span>][&#8216;basedn&#8217;]  if your $<span>useTable<\/span> variable is empty it defaults to the <span>basedn<\/span> configured in your database <span>config<\/span>.<\/span><\/p>\n<p><strong>$options[&#8216;scope&#8217;] :<\/strong><span> If you&#8217;ve worked with <span>ldap<\/span> before then y<span>ou<\/span> are familiar with the concept of search scopes.  Y<span>ou<\/span> have three search scopes, &#8216;sub&#8217;, &#8216;one, &amp; &#8216;base&#8217;.  Basically <\/span><strong>sub<\/strong> means search from this point down the tree.  <strong>one<\/strong> means search one level below this point and <strong>base<\/strong><span> means search just this point.  For example if y<span>ou<\/span> wanted to see if a user already existed y<span>ou<\/span> could set the targetDn to <span>uid<\/span>=<span>jdoe<\/span>,<span>ou<\/span>=people,dc=example,dc=com and it will check if this object already exists.   The default scope is <\/span><strong>sub<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve been using CakePHP for a while now and I&#8217;ve been thinking for a while it was time to see if I could give something back. As an IT leader I&#8217;m in love with LDAP. It makes life so simple for me and my team. The big downside to LDAP is it&#8217;s not very easy [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,44,3],"tags":[170,168],"class_list":["post-13","post","type-post","status-publish","format-standard","hentry","category-cakephp","category-featured","category-ldap","tag-cakephp","tag-ldap"],"_links":{"self":[{"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/posts\/13","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/comments?post=13"}],"version-history":[{"count":51,"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/posts\/13\/revisions"}],"predecessor-version":[{"id":481,"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/posts\/13\/revisions\/481"}],"wp:attachment":[{"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/media?parent=13"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/categories?post=13"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.analogrithems.com\/rant\/wp-json\/wp\/v2\/tags?post=13"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}