<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>some flot, some jet</title>
	<atom:link href="http://jmcneese.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://jmcneese.wordpress.com</link>
	<description>a periodic excursion into the mind of one of those asshole developers</description>
	<lastBuildDate>Wed, 28 Sep 2011 15:08:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='jmcneese.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://1.gravatar.com/blavatar/95ad1c07ccfddc328849ae8e922e4339?s=96&#038;d=http%3A%2F%2Fs2.wp.com%2Fi%2Fbuttonw-com.png</url>
		<title>some flot, some jet</title>
		<link>http://jmcneese.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://jmcneese.wordpress.com/osd.xml" title="some flot, some jet" />
	<atom:link rel='hub' href='http://jmcneese.wordpress.com/?pushpress=hub'/>
		<item>
		<title>more coming soon, i promise</title>
		<link>http://jmcneese.wordpress.com/2010/09/28/more-coming-soon-i-promise/</link>
		<comments>http://jmcneese.wordpress.com/2010/09/28/more-coming-soon-i-promise/#comments</comments>
		<pubDate>Tue, 28 Sep 2010 22:47:06 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=117</guid>
		<description><![CDATA[i am dreadfully sorry that i haven&#8217;t posted in a very long time, but that will change soon.  there are some new projects i am preparing to release into the wild, so just hang tight.<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=117&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>i am dreadfully sorry that i haven&#8217;t posted in a very long time, but that will change soon.  there are some new projects i am preparing to release into the wild, so just hang tight.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/117/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/117/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/117/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/117/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/117/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/117/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/117/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/117/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/117/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/117/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/117/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/117/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/117/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/117/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=117&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2010/09/28/more-coming-soon-i-promise/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>what&#8217;s this? data about data?</title>
		<link>http://jmcneese.wordpress.com/2010/02/11/whats-this-data-about-data/</link>
		<comments>http://jmcneese.wordpress.com/2010/02/11/whats-this-data-about-data/#comments</comments>
		<pubDate>Fri, 12 Feb 2010 01:04:54 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[metadata]]></category>
		<category><![CDATA[plugins]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=106</guid>
		<description><![CDATA[the classic set up so, i&#8217;m sure you&#8217;ve had that problem. no, not that one, the one where you want to store a bunch of things that are similar, but not identical, in the same database table. for instance, you want to store files uploaded to your site in an &#8220;uploads&#8221; table. since you are [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=106&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<h2>the classic set up</h2>
<p>so, i&#8217;m sure you&#8217;ve had that problem. no, not that one, the one where you want to store a bunch of things that are similar, but not identical, in the same database table.  for instance, you want to store files uploaded to your site in an &#8220;uploads&#8221; table.  since you are a normalization freak like myself, you put the common fields in the &#8220;uploads&#8221; table, then happily start to make helper/lookup tables that will contain the &#8220;weird&#8221; fields.  so, when you look up a particular upload record, you can easily select (or LEFT JOIN) all the &#8220;weird&#8221; fields associated with that particular record.  works great.  but then, you perhaps want to do this for another table.  rinse, repeat.  since you are exactly like me, you probably thought to yourself &#8220;well, time to make a polymorphic table to contain all this mess&#8221;.  so, you create One Table To Rule Them All, with model/foreign_id fields, and all is well.  until you, like me, decide that you want hierarchical data.  &#8220;no problem&#8221; you think, &#8220;that&#8217;s why God (or the Devil, if you ask most programmers) invented tree structures!&#8221;.  so you update your OTTRTA (see above for witty acronym) table to actAs Tree.  fine, works great.  until you get millions of rows and something goes corrupt (it always does, trust me).</p>
<h2>what now?</h2>
<p>well, since it is my mission in life to suffer so that you don&#8217;t, i have experienced all of the above and more so that i could present to you:</p>
<h2><a title="metadata@github" href="http://github.com/jmcneese/metadata" target="_blank">THE METADATA PLUGIN (for CakePHP 1.3+)</a></h2>
<p>wtf does it do, i hear you asking? good thing i made a github project page, because it tells me the metadata plugin is <em>&#8220;A CakePHP 1.3 plugin that provides arbitrary metadata storage for model records&#8221;</em>.</p>
<p>simple enough.  so, how to set it up:</p>
<ol>
<li>check out the code from github:
<pre class="brush: bash;">$ cd /path/to/your/app/plugins &amp;&amp; git clone git://github.com/jmcneese/metadata.git</pre>
<p>if you already have your project under git you can do this (it&#8217;s call sub-tree&#8217;ing), or muddle through setting up and using submodules.  pick your poison.</li>
<li>create the required db table.  you can either do this via the SQL files included in metadata/config/schema (there are two, those who prefer UUID ids, and those who prefer INT), or you can use the schema shell to do this:
<pre class="brush: bash;">$ cake schema create Metadata.metadata</pre>
</li>
<li>set metadata as a behavior on the models you wish to use it with, ala:
<pre class="brush: php;">
var $actsAs = array('Metadata.Metadata' =&gt; array(
    'validate' =&gt; array(
        'fieldName' =&gt; array(
            'rule'      =&gt; 'postal',
            'message'   =&gt; 'Must be valid postal code',
        ),
        'some' =&gt; array(
            'nested' =&gt; array(
                'array' =&gt; array(
                    array(
                        'rule'      =&gt; 'numeric',
                        'message'   =&gt; 'Must be numeric'
                    ),
                    array(
                        'rule'      =&gt; array(
                            'decimal',
                            2
                        ),
                        'message'   =&gt; 'Must be decimal'
                    )
                )
            )
        )
    )
));
</pre>
</li>
<li>Define any validation rules that you need, in a similar fashion to model validation rules (all the built-in Validator rules work). Multiple rules per node are supported, and nodes can be as deep as required.</li>
</ol>
<p>so, #1 and #2 are pretty cut and dry, but #3 and #4 probably merit more explanation:</p>
<p>you can specify whatever nodes you want validated, so long as they are &#8220;leaf&#8221; nodes.  a leaf node is one that does not have children underneath it.  take the following example:</p>
<pre class="brush: php;">
$some_metadata = array(
    'settings' =&gt; array(
        'profile' =&gt; array(
            'show_gravatar' =&gt; true,
            'hide_email' =&gt; false
        ),
        'anonymous' =&gt; false
    )
);
</pre>
<p>the nodes &#8216;settings.anonymous&#8217;, &#8216;settings.profile.hide_email&#8217; and  &#8217;settings.profile.show_gravatar&#8217; would be &#8220;leaf&#8221; nodes, in that they have no children.  &#8217;settings.profile&#8217; would <strong>not</strong> be a leaf, and thus cannot have validation rules associated with it (can&#8217;t really think of any you would need/want anyhow).</p>
<p>all the rules that are built in to the core Validation class are supported.  unlike model validation, you cannot (currently) specify your own custom rules, as this still needs to be built out.</p>
<p>multiple rules per node are supported, so long as you format them as shown above.</p>
<p>the &#8216;last&#8217; parameter is supported for validation rules, but that&#8217;s it (no &#8220;on&#8221; or any other model validation parameter).</p>
<h2>how to shake your meta-thing</h2>
<p>first off, you can use the methods getMeta() and setMeta().  for this to work, the model must be referencing a valid model record, ala:</p>
<pre class="brush: php;">
$this-&gt;Stuff-&gt;id = 1; // or whatever you use for primary key ids
$this-&gt;Stuff-&gt;setMeta('foo','bar');
</pre>
<p>now, the metadata &#8220;foo&#8221; with the value of &#8220;bar&#8221; is set for the record with the id of the record for the model &#8220;Stuff&#8221;.  now, to retrieve the metadata:</p>
<pre class="brush: php;">
$this-&gt;Stuff-&gt;id = 1; // or whatever you use for primary key ids
$meta_foo = $this-&gt;Stuff-&gt;getMeta('foo');
// $meta_foo now equals 'bar'
</pre>
<p>much like the Configure class, you can set n-level deep keys:</p>
<pre class="brush: php;">
$this-&gt;Stuff-&gt;id = 1; // or whatever you use for primary key ids
$this-&gt;Stuff-&gt;setMeta('some.deeply.nested.setting','shoop');
</pre>
<p>doing this creates a nested structure that holds all these keys, and allows you to query as deep as you wish:</p>
<pre class="brush: php;">
$this-&gt;Stuff-&gt;id = 1; // or whatever you use for primary key ids

$meta = $this-&gt;Stuff-&gt;getMeta('some.deeply');
// this results in:
$meta = array(
    'nested' =&gt; array(
        'setting' =&gt; 'shoop'
    )
);
</pre>
<p>if you pass no path to getMeta, it will return all metadata associated with the model record in question.  alternately, you can pass an entire array to setMeta() and the entire structure will be saved.  pre-existing keys will be merged with what you pass.</p>
<p>another way to save metadata (particularly when creating a new record) would be to include it in the data you pass to your primary model when saving.  to illustrate:</p>
<pre class="brush: php;">
$this-&gt;Stuff-&gt;create();
$this-&gt;Stuff-&gt;save(array(
    'Stuff' =&gt; array(
        'name' =&gt; 'blah',
        'Metadatum' =&gt; array(
            'settings' =&gt; array(
                'show' =&gt; false
            )
        )
    )
));
</pre>
<p>this would cause the new Stuff record to be created, and the metadata saved along with it.  after that saved, you&#8217;d be able to:</p>
<pre class="brush: php;">
$show_stuff = $this-&gt;Stuff-&gt;getMeta('settings.show');
</pre>
<h2>don&#8217;t look behind the curtain</h2>
<p>this all works by utilizing the TreeBehavior, specifically with the scoping configurations that limit the tree calculations to just a small section of the overall table.  this means that you may have hundreds of thousands of (or millions even) trees in your metadatum table with no noticeable degradation in performance (other than the natural one that happens when your tables get large).  it&#8217;s conceivable that any of these mini-trees could become corrupt, and for that there are some custom methods for dealing with this, verifyMeta() and recoverMeta().  these work pretty much exactly like the TreeBehavior methods verify() and recover(), except that they set the scope of the tree first.</p>
<p>also, if you are feeling particularly brave, you can use the Metadatum model directly via setKey()/getKey() (both of these work just like get/setMeta(), except they don&#8217;t scope the internal tree).  this can be useful for application-wide settings/metadata, but i would really recommend using Configure for that.</p>
<p>now, go <a title="metadata@github" href="http://github.com/jmcneese/metadata" target="_self">fork it and fork it well</a>!</p>
<p>as always:</p>
<h2>caveat emptor</h2>
<p>this works for my purposes as it stands, but i imagine that there may be those out there who would like it to exactly mimic model validaton (what with the &#8220;on&#8221; parameter, or custom validation).  as with other projects, i accept bug reports, suggestions, donations, patches and/or feature requests.</p>
<h2>future plans</h2>
<p>one of the biggest things that still needs to be implemented is the auto-inclusion of metadata when finding primary model records.  as it stands now, you have to retrieve any and all metadata via the getMeta() method, they are never automatically included, as when a model hasAndBelongsToMany some other model.  this is because the Metadatum model is not bound/associated in any way to the primary model.  i&#8217;ll look into finding some way to configure this sort of behavior.</p>
<br /> Tagged: <a href='http://jmcneese.wordpress.com/tag/cakephp/'>CakePHP</a>, <a href='http://jmcneese.wordpress.com/tag/metadata/'>metadata</a>, <a href='http://jmcneese.wordpress.com/tag/plugins/'>plugins</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/106/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/106/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/106/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/106/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/106/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/106/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/106/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/106/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/106/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/106/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/106/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/106/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/106/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/106/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=106&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2010/02/11/whats-this-data-about-data/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>RMAC is dead, long live RMAC</title>
		<link>http://jmcneese.wordpress.com/2010/01/28/rmac-is-dead-long-live-rmac/</link>
		<comments>http://jmcneese.wordpress.com/2010/01/28/rmac-is-dead-long-live-rmac/#comments</comments>
		<pubDate>Thu, 28 Jan 2010 06:59:33 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PermissionableBehavior]]></category>
		<category><![CDATA[permissions]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=90</guid>
		<description><![CDATA[&#8230;and the Permissionable plugin rose from the ashes&#8230; with apologies to the queen after an incredibly long delay i have updated the PermissionableBehavior to be a more feature complete plugin (1.3 compatible). my apologies for taking so long, life has been hectic.  if you don&#8217;t know what i&#8217;m talking about, go look here and come [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=90&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<blockquote><p><span style="font-style:italic;">&#8230;and the Permissionable plugin rose from the ashes&#8230;</span></p></blockquote>
<h2>with apologies to the queen</h2>
<p>after an incredibly long delay i have updated the <a title="RMAC FTW (part 1)" href="http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/" target="_blank">PermissionableBehavior</a> to be a more feature complete plugin (1.3 compatible).  my apologies for taking so long, life has been hectic.  if you don&#8217;t know what i&#8217;m talking about, go look <a title="RMAC FTW (part 1)" href="http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/" target="_blank">here</a> and come back.</p>
<h2>so, where&#8217;s the beef?</h2>
<p>what does the update contain, you ask?  well, that&#8217;s an excellent question!</p>
<ul>
<li>all code bundled up in a nice tidy plugin</li>
<li>the concept of group bits has been discarded, removing the artificial limitation on number of possible groups</li>
<li>much faster (possibly due to cakephp 1.3 being faster, dunno)</li>
<li>test cases and fixtures (100% coverage, bitches!)</li>
<li>using constants for user_id/group_id(s) has been abandoned in favor of static storage classes (note: not that i think constants are a bad solution, but only that the static class is more flexible)</li>
<li>behavior init now binds the Permissionable.Permission model in a hasOne association to the primary model.  this is better than the previous adding of a join via the $queryData in beforeFind
<ul>
<li>an additional benefit of this is the unbinding of the model when permissions are not needed (when user is root, or when permissions have been disabled)</li>
</ul>
</li>
<li>more concise handling of permissions checking</li>
<li>better handling of &#8220;backout&#8221; scenarios (when a record is attempted to be saved when no permissionable information is available)</li>
<li>better handling of model aliasing, which should help prevent name collisions when querying multiple models that reference the same permissioned model</li>
<li>added cake schema file to easily create the permissions table (along with a couple SQL files, for those afraid of the command-line)</li>
<li>500% cooler. it&#8217;ll bring all the boys to the yard.</li>
<li>Permissionable now lives on github.  it&#8217;s enjoying its stay so far.  <a title="github is forking awesome" href="http://github.com/jmcneese/permissionable" target="_blank">go check it out</a>.</li>
</ul>
<h2>how do i tame this wild beast?</h2>
<p>easier than ever:</p>
<ol>
<li>check out the code from github:
<pre class="brush: bash;">cd /path/to/your/app/plugins &amp;&amp; git clone git://github.com/jmcneese/permissionable.git</pre>
<p>if you already have your project under git you can do this (it&#8217;s call sub-tree&#8217;ing), or muddle through setting up and using submodules.  pick your poison.</li>
<li>create the required db table.  you can either do this via the SQL files included in permissionable/config/schema (there are two, those who prefer UUID ids, and those who prefer INT), or you can use the schema shell to do this:
<pre class="brush: bash;">cake schema create Permissionable.permission</pre>
</li>
<li>take a look at <a title="permissionable.php" href="http://github.com/jmcneese/permissionable/blob/1.1/controllers/components/permissionable.php">permissionable/controllers/components/permissionable.php</a>.  you&#8217;ll need to include this in the components array for whichever controllers use your permissioned models, or in AppController.
<pre class="brush: php;">public $components = array('Permissionable.Permissionable');</pre>
<p>also, you&#8217;ll need to put some code in the initialize method of the component that sets the id of the logged in user (via AuthComponent or whatever method you use), as well as the group id(s).</li>
<li>next up, you&#8217;ll need to include the behavior in whatever models you want to be permission-controlled:
<pre class="brush: php;">public $actsAs = array('Permissionable.Permissionable' =&gt; array('defaultBits'=&gt;480);</pre>
<p>note: you need not include the defaultBits option to the behavior, unless you a) know what you are doing, b) can calculate up the proper bits and c) really need to.  if any of the above apply, feel free.  if you need a refresher course, look <a title="RMAC FTW (part 1)" href="http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/" target="_blank">here</a>.</li>
</ol>
<h2>*batteries not included</h2>
<p>so, now that you&#8217;ve done all that, there&#8217;s only a few things you need to know about how to use it.  as before, permissions are checked on the fly and transparently.  if you have permission to the action you are trying to perform (read/update/delete), then your model call will be successful.  if not, you&#8217;ll get a big fat false.  if you wish to disable this on a per-find basis, you can. e.g.:</p>
<pre class="brush: php;">$result = $this-&gt;Foo-&gt;find('all', array(
    'permissionable' =&gt; false,
    'conditions' =&gt; array(
        'Foo.val' =&gt; 'Bar'
    )
));</pre>
<p>or, this way, in case you need to add it to the scope of something like TreeBehavior or AuthComponent:</p>
<pre class="brush: php;">$result = $this-&gt;Foo-&gt;find('all', array(
    'conditions' =&gt; array(
    'permissionable' =&gt; false,
        'Foo.val' =&gt; 'Bar'
    )
));</pre>
<p>permissions will always be created updated on save though.  i might make this configurable in the future, or accept patches for it via github.   you can, however, override the permissions of the item being saved via:</p>
<pre class="brush: php;">$this-&gt;Thing-&gt;save(array(
    'Thing' =&gt; array(
        'name'	=&gt; 'Baz',
        'desc'	=&gt; 'Baz is a Thing!'
    ),
    'Permissionable' =&gt; array(
        'perms' =&gt; 416
    )
));</pre>
<h2>what the future holds</h2>
<p>i intend on updating the plugin to have more options for configuration, to whit: implementing some level of inheritable permissions, introducing the concept of roles, supporting &#8220;trickle-down&#8221; permission changes to models that actAs Tree, common UNIX-y commands like chmod/chown/chgrp.</p>
<p>as always, i welcome comments, questions, patches and suggestions. if you find a bug, let me know&#8230; or better yet, submit a patch!</p>
<h2>caveat emptor, balatros</h2>
<ul>
<li>i assume that you have users and groups, meaning that your user has a primary group, and secondary groups.  this <em>is</em> UNIX-like, after all.  now, an easy and standard way to do this is define model associations like User hasMany Group, User belongsTo Group.  it&#8217;s really up to you how you want to get that list, Permissionable doesn&#8217;t care, so long as you set it in the component.</li>
</ul>
<br /> Tagged: CakePHP, PermissionableBehavior, permissions <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/90/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=90&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2010/01/28/rmac-is-dead-long-live-rmac/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>RMAC FTW (part 1)</title>
		<link>http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/</link>
		<comments>http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/#comments</comments>
		<pubDate>Mon, 20 Apr 2009 03:23:47 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[ACL]]></category>
		<category><![CDATA[PermissionableBehavior]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[RBAC]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=57</guid>
		<description><![CDATA[once again into the breach no, not Rambunctious Models Artfully Concealing Faded Temptress Wrinkles, it&#8217;s even better. those of you who have been following the PermissionableBehavior know that the goal of the behavior is to help achieve a RBAC-like system to control not only access to specific parts of an application, but also row-level permissions [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=57&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<h2>once again into the breach</h2>
<p>no, not Rambunctious Models Artfully Concealing Faded Temptress Wrinkles, it&#8217;s even better.</p>
<p>those of you who have been following the <a title="Row-level Model Access Control" href="http://jmcneese.wordpress.com/2009/04/05/row-level-model-access-control-for-cakephp/" target="_blank">PermissionableBehavior</a> know that the goal of the behavior is to help achieve a <a title="Role Based Access Control" href="http://en.wikipedia.org/wiki/RBAC" target="_blank">RBAC</a>-like system to control not only access to specific parts of an application, but also row-level permissions on data in the application.  i released an initial prototype of the behavior a couple weeks ago, and it was <a title="Nate Abele" href="http://jmcneese.wordpress.com/2009/04/07/update-row-level-model-access-control-for-cakephp/#comment-36" target="_blank">fairly well-received</a>.</p>
<p>i&#8217;ve refactored a little bit, and per popular demand, i&#8217;ll also be supplying a sample implementation for those of you who prefer learning by example.  first though, i thought i would take a step back and try to outline the overall design and <strong>how</strong> it works.</p>
<h2>a shave and a haircut, 9 bits</h2>
<p>bit(mask)s are a wonderful and compact way to keep track of multiple booleans.  i&#8217;m not even going to attempt to explain why, since <a title="Bitwise Operation" href="http://en.wikipedia.org/wiki/Bitwise_operation" target="_blank">wikipedia</a> can do it so much <a title="Bitmasks" href="http://en.wikipedia.org/wiki/Bitmask" target="_blank">better</a>. until you get the hang of them they can be a little confusing, but they are really just a different (and far more basic) way of looking at what you are already used to.  let&#8217;s look at some examples, in which i&#8217;ll limit to be relevant to the PermissionableBehavior.</p>
<p>let&#8217;s set the parameters.  the bit definitions for the row action we want to limit are:</p>
<ul>
<li>Owner
<ul>
<li>Read &#8211; 256</li>
<li>Write &#8211; 128</li>
<li>Delete &#8211; 64</li>
</ul>
</li>
<li>Group
<ul>
<li>Read &#8211; 32</li>
<li>Write &#8211; 16</li>
<li>Delete &#8211; 8</li>
</ul>
</li>
<li>Other
<ul>
<li>Read &#8211; 4</li>
<li>Write &#8211; 2</li>
<li>Delete &#8211; 1</li>
</ul>
</li>
</ul>
<p>notice that the numbers for the permissions increase in powers of 2.  i&#8217;ll not go into why this works, since it&#8217;s <a title="Bitwise Arithmetic" href="http://www.xaprb.com/blog/2005/09/28/bitwise-arithmetic/" target="_blank">covered</a> <a title="How to build role-based access control in SQL" href="http://www.xaprb.com/blog/2006/08/16/how-to-build-role-based-access-control-in-sql/" target="_blank">elsewhere</a>, but sufficed to say that bitwise, each number is distinct and non-inclusive of the other numbers (256 does not contain 128, 128 does not contain 64, etc).</p>
<p>ok, now imagine the permissions that we want to assign to an item as a binary number.  trust me, it&#8217;s actually easier:</p>
<table border="0">
<thead>
<tr>
<th colspan="3">Owner</th>
<th colspan="3">Group</th>
<th colspan="3">Other</th>
<th>Decimal</th>
</tr>
<tr>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>Permission</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>000</td>
</tr>
</tbody>
</table>
<p>this bitmask indicates that no one has any permission to a record with these bits set (or unset, actually).  now let&#8217;s look at a more real-world example:</p>
<table border="0">
<thead>
<tr>
<th colspan="3">Owner</th>
<th colspan="3">Group</th>
<th colspan="3">Other</th>
<th>Decimal</th>
</tr>
<tr>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>Permission</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>436</td>
</tr>
</tbody>
</table>
<p>what&#8217;s going on here?  the binary number 110110100 == the decimal number 436 (256+128+32+16+4, according to the numbers i assigned above).  storing the number 436 as the permission bit on a record would reflect that the owner of the record can read/write, the group owner(s) of the record can read/write, and all others (non-owners) can read.  with bitwise operators, it&#8217;s trivial to see if a permission bit has required permissions.</p>
<p>for example, let&#8217;s use the &#8220;&amp;&#8221; (AND) bitwise operator to see if the row&#8217;s permission bit contains the bit that says that the owner can delete the row (64).</p>
<pre class="brush: php;">$allowed = ((436 &amp; 64) &lt;&gt; 0);</pre>
<table border="0">
<thead>
<tr>
<th></th>
<th colspan="3">Owner</th>
<th colspan="3">Group</th>
<th colspan="3">Other</th>
<th>Decimal</th>
</tr>
<tr>
<th></th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>Permission</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>436</td>
</tr>
<tr>
<td>Requested</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>64</td>
</tr>
</tbody>
</table>
<p>how does this work?  since (436 &amp; 64) equals 0, then the request for owner to delete would be denied, as the row permission does <strong>not</strong> contain 64. the &#8216;&amp;&#8217; operator tests returns which bits are set in both numbers.  as you can see above, there is no column in which both have 1 set, thus returning &#8217;0&#8242;.</p>
<blockquote><p>NOTE:  you <strong>must</strong> enclose the bitwise operation in parentheses or you will get false positives.  due to the logical order in which the operation is processed, if you tried to do <em>(426 &amp; 64 &lt;&gt; 0)</em>, it would return true, since the &#8220;not 0&#8243; portion is processed first.  additionally, unless you have a firm grip on your variable types, i recommend typecasting all your variables as integers, e.g. ((int) $x &amp; (int) $y) &lt;&gt; 0), since if either variable is string, then you&#8217;ll get bizarre results.</p></blockquote>
<p>now for an example where the request is allowed:</p>
<table border="0">
<thead>
<tr>
<th></th>
<th colspan="3">Owner</th>
<th colspan="3">Group</th>
<th colspan="3">Other</th>
<th>Decimal</th>
</tr>
<tr>
<th></th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>R</th>
<th>W</th>
<th>D</th>
<th>Permission</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>436</td>
</tr>
<tr>
<td>Requested</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>4</td>
</tr>
</tbody>
</table>
<p>now, notice that in the Other Read column, both numbers have the bit set.  and so (436 &amp; 4) would return &#8217;4&#8242;, indicating that both numbers have the &#8217;4&#8242; bit set.</p>
<blockquote><p>NOTE:  there are several ways to test using this operator.  i use <em>(x &amp; y) &lt;&gt; 0</em>, when you can also use <em>(x &amp; y) == y</em>.  the latter will work in most scenarios, but in the PermissionableBehavior i have some places where permissions are inherited, and so the returned number is sometimes a product of shared bits, meaning it returns neither 0 or the mask.  in either case, &lt;&gt; will work.</p></blockquote>
<h2>2 bits walk into a |, and the operator says &#8220;2&#8243;</h2>
<p>so far i&#8217;ve been using the permission bits for example, but all the above also applies to the group bits assigned to a row.  this works the same way permissions do, ala:</p>
<ul>
<li>Root &#8211; 1</li>
<li>Global &#8211; 2
<ul>
<li>Internal &#8211; 4
<ul>
<li>Dept. A &#8211; 8</li>
<li>Dept. B &#8211; 16</li>
</ul>
</li>
<li>External &#8211; 32
<ul>
<li>Client A &#8211; 64</li>
<li>Client B &#8211; 128</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>now, here is one of the drawbacks of this approach.  you are limited to the number of bits in an integer, whatever your architecture supports.  in the case of 32bit, this equates to 32 groups, and 64bit, 64 groups.  this isn&#8217;t a problem for me, and i&#8217;m sure there are ways to get around this (hashing, bribery, etc), but i&#8217;ll not address this until i actually have a polished version of the behavior (or need more than 32 groups!).</p>
<p>anyway.  as i said, what applied for permission bits works the same for group bits.  let&#8217;s assume we have a user who is in Dept. B.  in the PermissionableBehavior, i traverse the groups tree and add up all the bits for the groups that the user is part of.  be careful, since you can&#8217;t just add up using arithmetic since any duplication of bits would produce inaccurate results.  the group bits for a user in Dept. B would be 22 (16 | 4 | 2).  note the use of the &#8220;|&#8221; (OR) operator.  this operator works by returning the bits that are set in both sets of numbers.  since none of the bits in the group path for Dept. B are inclusive (16 does not include 4 or 2, 4 does not contain 2), the result is the the bits added up and we get 22.  but what if the user was in multiple groups?  let&#8217;s say the user is in Dept. B and Client A groups.  if we iterated each group and added up it&#8217;s path, we&#8217;d get 120.  this would be wrong, as it includes the &#8216;Global&#8217; bit (2) twice, thus skewing the resulting bits.  the right way would be to use bitwise OR, like so (64 | 32 | 2 | 16 | 4 | 2), which gives us a group bits of 118.  Now for the real-world example:</p>
<p>if a row in the database has a group bits of 96, we can test whether or not someone has access to the group owner permissions by comparing their cumulative group bits with the rows.</p>
<pre class="brush: php;">$is_group_owner = ((96 &amp; 118) &lt;&gt; 0);</pre>
<p>since <em>(96 &amp; 118)</em> returns &#8217;96&#8242;, that being the bits that are shared by both numbers, we know that the user in question has access to the group permissions, in that he is in at least one group that the row is also in.</p>
<h2>group bits + permission bits == unix delicious</h2>
<p>so, now that we&#8217;ve covered how the group and permission bits work, i&#8217;ll show how these method actually limit the results from a database query, so that a user is only able to perform allowed actions.</p>
<p>first off, let&#8217;s go for the simple case: selecting rows from a table.  let&#8217;s set the parameters first.  User A is in the groups Dept. A and Client B.  her ID in the users table is 100 (for example), and her group bits are 118.  she performs a query via $groupModel-&gt;find(&#8216;all&#8217;).  without the Permissionable behavior, the query would look something like this:</p>
<pre class="brush: sql;">
SELECT `Group`.*
  FROM `groups` AS `Group`
</pre>
<p><em>with</em> the PermissionableBehavior however, the same $groupModel-&gt;find(&#8216;all&#8217;) would result in this query (and i apologize again for not having syntax formatting/highlighting&#8230; does anyone recommend a free webhost?):</p>
<pre class="brush: sql;">
SELECT `Group`.*, `PermissionModel`.*
  FROM `groups` AS `Group`
  INNER JOIN permissions AS `PermissionModel` ON (
    `PermissionModel`.`model` = 'Group'  AND -- since the PermissionModel is polymorphic, we need to limit to rows that have to do with the primary model
    `PermissionModel`.`foreign_key` = `Group`.`id` AND
    (
      (`PermissionModel`.`permission` &amp; 4 &lt;&gt; 0) OR  -- first check for 'other read' permission
        (
          (`PermissionModel`.`permission` &amp; 32 &lt;&gt; 0) AND -- otherwise 'group read'
          (`PermissionModel`.`group_bits` &amp; 118 &lt;&gt; 0) -- assuming the user and the row share a group
        ) OR (
          (`PermissionModel`.`permission` &amp; 256 &lt;&gt; 0)  AND -- otherwise 'owner read'
          (`PermissionModel`.`user_id` = '100') -- assuming the user is the row owner
        )
      )
    )
</pre>
<p>now, i&#8217;m sure several of you are looking at that and thinking &#8220;bleeding jesus h. christ on a pogostick, that&#8217;s a lot of parentheses&#8221;, but they are necessary and not really all that complex.  i should blog about the queries i had to write for a database that had +1 billion rows&#8230; but i digress; the query, while more than twice as &#8220;long&#8221; as the previous, really doesn&#8217;t incur any overhead, especially when your tables are properly indexed.  what&#8217;s nice about this approach is that the original query was modified to filter out rows in the primary table that do not have the appropriate permissions.  in this case we&#8217;ve specified &#8220;read&#8221; permissions (owner &#8211; 256, group &#8211; 32, other &#8211; 4) as our criteria.  we&#8217;ve only applied the owner read permission condition if the user is indeed the owner, and the group permission only if the row and the user share at least one group, or if the row has an &#8216;other read&#8217; bit set.  on my modest macbook pro, this takes less than a millisecond to query, and EXPLAIN tells me that the query is using only simple selects, constant and indexed references with only a WHERE for the inner join.</p>
<h2>stop teaching me <em>how </em>to fish, just give me a fish dammit</h2>
<p>alright, as promised, here&#8217;s the example application that you can just unarchive (over a fresh cake install) and play with: <a title="app.zip" href="http://dl.getdropbox.com/u/8816/app.zip" target="_self">ZOMG Download ME!!!!1</a></p>
<p>a couple things i didn&#8217;t explain before, but bear mentioning:</p>
<ul>
<li>the order in which you add the behavior to your models (most notably  Tree&#8217;d models) is very important.  i&#8217;d recommend putting Permissionable first, in most cases.</li>
<li>if you don&#8217;t use AuthComponent to authenticate your users, you&#8217;ll need to make sure that any query that it does on a permissioned model includes &#8216;permissions&#8217; =&gt; false in it&#8217;s conditions</li>
<li>oh yeah, you can disable the behavior runtime by either:
<ul>
<li>including  &#8216;permissions&#8217; =&gt; false in your query conditions.</li>
<li>calling $model-&gt;disableChecks() to disable just permission checking.</li>
<li>calling $model-&gt;disableCreation() to disable, you guessed it, permission creation.</li>
<li>or by calling $model-&gt;disable() to shut the whole thing down.</li>
<li>to enable again, just call $model-&gt;disable(false), or write a method called &#8216;enable&#8217;.  i dare you.</li>
</ul>
</li>
<li>you can add considerably more permissionable actions, so long as you keep incrementing the bits by the power of two.  please only do so if you understand what you are doing, since you&#8217;ll have to shift the permission bits around (at least i would, so they would remain pretty), and most likely re-test the whole thing since i didn&#8217;t really code with that in mind. although it <em>should</em> work.</li>
<li>i make no claims about this being perfect.  i haven&#8217;t tested in with all other core behaviors, and there still may be weirdness that may crop up.  YMMV, caveat emptor, amen.</li>
</ul>
<h2>&lt;insert witty closing heading here&gt;</h2>
<p>so there you have it, folks.  as always, i welcome comments, questions, critique, praise, beer and naked pictures (ladies only please).  anyone who has suggestions on how to improve this code, please do so.  i am committed to making this the first step in a much larger plugin that can theoretically replace cake default ACLs.</p>
<p>speaking of which, stay tuned for the second part of this article, entitled <strong><em>&#8220;RBAC FTW (part 2)&#8221;</em></strong>, in which we will see how we still need the cake ACL structure to limit access to controller actions, thus rounding out this first effort towards our stated lofty goal.</p>
<br /> Tagged: ACL, CakePHP, PermissionableBehavior, permissions, programming, RBAC <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/57/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/57/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/57/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/57/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/57/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/57/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/57/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/57/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/57/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/57/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/57/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/57/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/57/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/57/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=57&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/feed/</wfw:commentRss>
		<slash:comments>32</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>RMAC update soon</title>
		<link>http://jmcneese.wordpress.com/2009/04/08/rmac-update-soon/</link>
		<comments>http://jmcneese.wordpress.com/2009/04/08/rmac-update-soon/#comments</comments>
		<pubDate>Wed, 08 Apr 2009 23:34:19 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[ACL]]></category>
		<category><![CDATA[PermissionableBehavior]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[RBAC]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=54</guid>
		<description><![CDATA[i&#8217;ll be releasing a more complete version of the Permissionable behavior in the next couple of days, along with an example schema, for those interested.  stay tuned! and here it is. Tagged: ACL, CakePHP, PermissionableBehavior, permissions, programming, RBAC<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=54&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>i&#8217;ll be releasing a more complete version of the Permissionable behavior in the next couple of days, along with an example schema, for those interested.  stay tuned!</p>
<p><em><a title="RMAC FTW" href="http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/">and here it is</a>.</em></p>
<br /> Tagged: ACL, CakePHP, PermissionableBehavior, permissions, programming, RBAC <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/54/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=54&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2009/04/08/rmac-update-soon/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>Update: Row-level Model Access Control for CakePHP</title>
		<link>http://jmcneese.wordpress.com/2009/04/07/update-row-level-model-access-control-for-cakephp/</link>
		<comments>http://jmcneese.wordpress.com/2009/04/07/update-row-level-model-access-control-for-cakephp/#comments</comments>
		<pubDate>Tue, 07 Apr 2009 05:04:31 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[ACL]]></category>
		<category><![CDATA[PermissionableBehavior]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[RBAC]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=49</guid>
		<description><![CDATA[UPDATED! so, there apparently is a bit of scuffle about my last post, so let me clear things up. first off, this is my first real blog, and first real blog post of any substance, so i&#8217;m still learning. secondly, i wasn&#8217;t attacking cake, cake devs, or cake ACLs.  all of the above rock the [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=49&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p><a title="RMAC FTW" href="http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/">UPDATED!</a></p>
<p>so, there apparently is a bit of scuffle about my last post, so let me clear things up.</p>
<p>first off, this is my first real blog, and first real blog post of any substance, so i&#8217;m still learning.</p>
<p>secondly, i wasn&#8217;t attacking cake, cake devs, or cake ACLs.  all of the above rock the llama&#8217;s ass.</p>
<p>thirdly, cake devs <strong>have</strong> said that using cake ACLs for row-level access/deny is probably not the best way to accomplish what you probably want to do.  nate abele&#8217;s opinion as to whether or not any cake dev would say that is irrelevant, as it <a href="http://logs.cakephp.nu/cakephp/chat.log.2009-03-31#line_06_05_markstory"><strong>was</strong></a> <a href="http://logs.cakephp.nu/cakephp/chat.log.2009-03-31#line_06_07_markstory">said</a>, for better or worse.  i happen to agree with the sentiment, not because cake&#8217;s ACL implementation is poor (it&#8217;s not), but because using ACLs for row level access is flawed from the get-go.  sure, it can be done, just as an entire application can be built without a framework, or as a monolithic index.php with a shit-ton of functions. it is just not good <a title="Separation of concerns" href="http://en.wikipedia.org/wiki/Separation_of_concerns">separation of concerns</a>, and i didn&#8217;t see that until after i had tried three different ways to limit access to specific rows in a database using ACLs. i reiterate the quote from mark story that if you use cake ACLs to limit row access you &#8220;have to know the answer to the question before you even ask it&#8221;.</p>
<p>what did mark mean by this?  he meant that if you have even a simple setup with users being in groups (and groups not being hierarchical), you must query the model in question joining in various ACL models to find allowed ids <em>before you can even ask the original model for rows that match those ids</em>.  when you deal with inherited permissions from a group model that is tree-based, you have even more complexity.  i.e. for the group that the user is in, you have to iterate upwards in the tree to see if <em>that</em> particular group has access to the row in question.  and if your requirements call for your users to be in multiple groups (which cake default ACL behavior/component doesn&#8217;t support at all), then it&#8217;s even more complex, since you have to loop through the ancestor tree of every group that the user is in.  and to top it all off, if the model you are trying to read is also a tree, you not only have to do all of the above, but you also solve the problem of denied parent nodes, and figure out how to deal with non-explicitly denied children of denied parent nodes.  it&#8217;s a mess.  i had a completely working implementation of what i just described and when i issued a single Model::read() on a record owned by a user who was in three groups, it performed <em>fourteen</em> queries to determine if you user could see he his own record!  did it work?  sure.  was it optimal? fuck no.  would indexing help me? sure, until my tree got huge, and i would never be able to shake the feeling that i was doing things the wrong way just to stick to purely cake components.</p>
<p>fourthly, i am not advocating or urging the use of either ACLs or UNIX-y permissions.  both are good when used appropriately.  both can be overkill for simple &#8220;can user x do y&#8221; scenarios.  it&#8217;s merely what worked for me, and i want to share.  in fact, what is missing from my previous article is noting that to achieve a &#8220;real&#8221; RBAC permissioning system, both row-level access like this and ACLs are required (at least in cake terms).  ACLs to know who can <strong>access</strong> what part of the system, and a simple method like mine to determine who can <strong>change</strong> what in the data.  if someone can address my concerns noted above as to how this can be done in a generic implementation of ACL like cake&#8217;s, and it does not require a whole handful of queries before the actual model query requested (note: a simple find(&#8216;all&#8217;) with the Permissionable behavior doesn&#8217;t increase the query count <em>at all</em>), then i&#8217;ll eat crow. <img src='http://s1.wp.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<br /> Tagged: ACL, CakePHP, PermissionableBehavior, permissions, programming, RBAC <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/49/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/49/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/49/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/49/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/49/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/49/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/49/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/49/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/49/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/49/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/49/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/49/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/49/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/49/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=49&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2009/04/07/update-row-level-model-access-control-for-cakephp/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>Row-level Model Access Control for CakePHP</title>
		<link>http://jmcneese.wordpress.com/2009/04/05/row-level-model-access-control-for-cakephp/</link>
		<comments>http://jmcneese.wordpress.com/2009/04/05/row-level-model-access-control-for-cakephp/#comments</comments>
		<pubDate>Sun, 05 Apr 2009 23:20:30 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[ACL]]></category>
		<category><![CDATA[PermissionableBehavior]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[RBAC]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=25</guid>
		<description><![CDATA[UPDATED! Core Developer says &#8220;You Don&#8217;t Need It&#8221; it seems that for as long as i have been using cake, i&#8217;ve been hearing people asking &#8220;how do i control access to an individual record with ACL?&#8221;.  the usual response is &#8220;you aren&#8217;t doing it right if you need to restrict access to a specific row&#8221;.  [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=25&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p><a title="RMAC FTW" href="http://jmcneese.wordpress.com/2009/04/19/rmac-ftw-part-1/">UPDATED!</a></p>
<h2>Core Developer says &#8220;You Don&#8217;t Need It&#8221;</h2>
<p>it seems that for as long as i have been using cake, i&#8217;ve been hearing people asking &#8220;how do i control access to an individual record with ACL?&#8221;.  the usual response is &#8220;you aren&#8217;t doing it right if you need to restrict access to a specific row&#8221;.  while this may be correct 90% of the time (due to the php/development greenhorns who use cake, but that&#8217;s a different blog post), i think it&#8217;s a bit narrowminded to think in this way.  who&#8217;s to say whether or not i or my product owner want to restrict access to certain db rows?  and how much of that response is due to the fact that cake&#8217;s generic ACL implemenation makes doing row level permissions a nightmare?  and so, remembering the &#8220;good old days&#8221; when i used phpGACL in projects, i wondered if there was a <em>better</em> way to do this in cake, that doesn&#8217;t require backporting an old clunky library like phpGACL (which i&#8217;ve seen done elsewhere).</p>
<h2>Enter the &#8220;RMAC&#8221; dragon</h2>
<p>awhile back, i ran across <a title="How to build role based access control in sql" href="http://www.xaprb.com/blog/2006/08/16/how-to-build-role-based-access-control-in-sql/" target="_blank">this post</a> on by Baron Schwartz, author of the excellent O&#8217;reilly book &#8220;<a title="High Performance Mysql on Amazon" href="http://www.amazon.com/High-Performance-MySQL-Optimization-Replication/dp/0596101716/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1238970565&amp;sr=1-1" target="_blank">High Performance MySQL</a>&#8221; and various handy <a href="http://code.google.com/u/baron.schwartz/" target="_blank">mysql tools</a>.  his two-part article on how to implement role based access in just sql is very intriguing, to say the least.  while i like his implementation, it&#8217;s has a lot of bells and whistles that are more specific to his requirements that i would need.</p>
<p>around a year ago, i tasked one of my <a title="Joe Beeson" href="http://blog.joebeeson.com/" target="_blank">developers</a> with adapting Baron&#8217;s implementation to fit our requirements.  in the end, while it was a <a title="Unix Behavior Revisited" href="http://blog.joebeeson.com/?p=145" target="_blank">good stab at it</a>, i think we still tried to make it do too much, and there were various problems with our implementation (accessing $_SESSION in a model behavior gives me shivers), but it did what we needed it to do and all was good.</p>
<p>for a new project we are working on i thought i would try to do this from scratch.  i&#8217;ll leave off describing all the exhaustive implemenation/testing that was done to verify whether or not this can be done with cake&#8217;s built in ACL behavior/component.  in a nutshell, the answer is yes, but to put it how <a title="Mark Story" href="http://mark-story.com/" target="_blank">Mark Story</a> said &#8220;you have to know the answer to the question before you even ask it&#8221;, in that you have to query the model in question, along with the aros/acos/aros_acos table to find a list of row ids that you are able to access, and then use that as a filter for your original query.  this becomes even more tedious when dealing with a tree behaviored model, because you will run into the scenario where someone may be allowed to access a node several levels deep in the tree, but disallowed access to it&#8217;s parent.  you then have to decide to bypass the permissions and give the user access to read the parent node, or else change the way you save permission for each user/group to implicitly allow paths.  this sucks, to say the least.  after all this &#8220;research&#8221; (read: unsatisfactory implementations that made my head hurt to try to keep straight), i scrapped all of this.  i have come to the conclusion that cake&#8217;s ACL is a very good <em>generic</em> implementation that never was explicitly intended for use with users/groups, much less roles.  that is not to say you <em>can&#8217;t</em>, but that you probably shouldn&#8217;t.  turns out the cake core developers were right (but i still think that blithely ignoring the fact that it&#8217;s just not suited for this sort of implementation is kind of naughty).</p>
<p>so where does this leave us?  after scrapping the implementations that used cake&#8217;s generic ACL, i returned to Baron&#8217;s method and stripped it down.  while i don&#8217;t have it in a &#8220;plug-n-play&#8221; state just yet, here she is, the Permissionable behavior! &lt;/trumpet flourish&gt; (apologies for not having syntax highlighting!)</p>
<div class="php" style="border:1px dotted #a0a0a0;overflow:scroll;height:200px;white-space:nowrap;font-family:'Courier New',Courier,monospace;background-color:#f0f0f0;color:#000000;margin:0;padding:10px;">
<pre>/**
 * Permission Model
 *
 * Model for manipulating permissions data
 *
 * @author      Joshua McNeese<code>
 */
final class PermissionModel extends AppModel {

    /**
     * Permissions table
     *
     * @access  public
     * @var     string
     */
    public $useTable = 'permissions';

}

/**
 * Permissionable Behavior
 *
 * @author      Joshua McNeese
 * @since       1.0
 * @internal    $Info: $
 * @version     $Rev: $
 */
final class PermissionableBehavior extends ModelBehavior {

    public $settings    = array();
    private $__defaults = array(
        'userModel'     =&gt; 'User',
        'groupModel'    =&gt; 'Group',
        'defaultBits'   =&gt; 416      // owner_read (256) +
                                    // owner_write (128) +
                                    // group_read (32)
    );
    private $__enabled  = true;

    /**
     * Bits for various permissions.  Don't touch!
     *
     * @access  private
     * @var     array
     */
    private $__permissions = array(
        'owner_read'   =&gt; 256,
        'owner_write'  =&gt; 128,
        'owner_delete' =&gt; 64,
        'group_read'   =&gt; 32,
        'group_write'  =&gt; 16,
        'group_delete' =&gt; 8,
        'other_read'   =&gt; 4,
        'other_write'  =&gt; 2,
        'other_delete' =&gt; 1
    );

    public function setup(&amp;$model, $config = array()) {
        $model-&gt;Permission =&amp; ClassRegistry::init('PermissionModel');

        $config = (is_array($config) and !empty($config))
                ? am($this-&gt;__defaults, $config)
                : $this-&gt;__defaults;

        foreach($config as $k=&gt;$v) {
            if(isset($model-&gt;{$k})) {
                $config[$k] = $model-&gt;{$k};
            }
        }

        $this-&gt;settings[$model-&gt;alias] = $config;
	}

	public function afterDelete(&amp;$model) {
	    if(!$this-&gt;isEnabled()) {
	        return true;
	    }

        $model-&gt;Permission-&gt;deleteAll(array(
            'model'         =&gt; $model-&gt;alias,
            'foreign_key'   =&gt; $model-&gt;id
        ));
	}

	public function afterSave(&amp;$model, $created) {
	    if(!$this-&gt;isEnabled()) {
	        return true;
	    }

	    if(
	       !defined('PERMISSION_GROUP_BITS') or
	       !defined('PERMISSION_USER_ID')
        ) {
	        return $this-&gt;backout($model);
	    }

	    $data = array();

	    if($created) {
	        $data = array(
                'model'         =&gt; $model-&gt;alias,
                'foreign_key'   =&gt; $model-&gt;id,
                'user_id'       =&gt; PERMISSION_USER_ID,
                'group_bits'    =&gt; PERMISSION_GROUP_BITS,
                'permission'    =&gt; $this-&gt;getPermissionBits($model)
	        );

	        $groupModelName = $this-&gt;settings[$model-&gt;alias]['groupModel'];
	        $userModelName  = $this-&gt;settings[$model-&gt;alias]['userModel'];

	        if($model-&gt;alias == $userModelName) {
	            $data['user_id'] = $model-&gt;id;

	            if(
	               !isset($model-&gt;data[$groupModelName]) or
	               empty($model-&gt;data[$groupModelName])
                ) {
                    return $this-&gt;backout($model);
                }

                $groupIds = array_unique(am(
                    Set::extract(
                        "/$groupModelName/$groupModelName/.",
                        $model-&gt;data
                    ),
                    Set::extract(
                        "/$groupModelName/{$model-&gt;{$groupModelName}-&gt;primaryKey}",
                        $model-&gt;data
                    )
                ));

                if(empty($groupIds)) {
                    return $this-&gt;backout($model);
                }

                $groupModel= $this-&gt;getModel($model, $groupModelName);

                if(empty($groupModel)) {
                    return $this-&gt;backout($model);
                }

                $groupBits = 0;

                foreach($groupIds as $groupId) {
                    $bitPath    = $groupModel-&gt;getpath($groupId, array('bit'));
                    $bitArray   = array_sum(Set::extract("/$groupModelName/bit", $bitPath));
                    $groupBits  = (int) $groupBits | (int) $pathBits;
                }

                if(empty($groupBits)) {
                    return $this-&gt;backout($model);
                }

                $data['group_bits'] = (int) $groupBits;
	        } elseif($model-&gt;alias == $groupModelName) {
	            $data['group_bits'] = $model-&gt;data[$groupModelName]['bit'];
	        }

	        $model-&gt;Permission-&gt;create();
	    } elseif(
	       isset($model-&gt;data['Permission']) and
	       !empty($model-&gt;data['Permission'])
        ) {
	        $data = $model-&gt;data['Permission'];

	        if(!isset($data['id']) or empty($data['id'])) {
	            $oldPermission = $model-&gt;Permission-&gt;find('first', array(
                    'conditions' =&gt; array(
                        'model'         =&gt; $model-&gt;alias,
                        'foreign_key'   =&gt; $model-&gt;id
    	            )
	            ));

	            if(empty($oldPermission)) {
	                $model-&gt;Permission-&gt;create();
	                $data['permission'] = $this-&gt;getPermissionBits($model);
	            } else {
	                $data = am($oldPermission['Permission'], $data);
	            }
	        }

	        $data = am($data, array(
	           'model'         =&gt; $model-&gt;alias,
               'foreign_key'   =&gt; $model-&gt;id
	        ));
	    }

	    if(!empty($data)) {
	        $model-&gt;Permission-&gt;save($data);
	    }
	}

	public function beforeDelete(&amp;$model) {
	    if(!$this-&gt;isEnabled()) {
	        return true;
	    }

	    return $this-&gt;hasPermission($model, 'delete');
	}

	public function beforeFind(&amp;$model, $queryData) {
	    if((
            isset($queryData['permissions']) and
	        $queryData['permissions'] == false
        ) or (
            isset($queryData['conditions']['permissions']) and
            $queryData['conditions']['permissions'] == false
        )) {
	        unset($queryData['conditions']['permissions']);
	        unset($queryData['permissions']);
	        return $queryData;
	    } elseif(!$this-&gt;isEnabled()) {
	        return true;
	    } elseif(!defined('PERMISSION_GROUP_BITS') or !defined('PERMISSION_USER_ID')) {
	        $queryData['conditions'] = '1=0';
	    } elseif((PERMISSION_GROUP_BITS &amp; 1) &lt;&gt; 0) {
	        return true;
	    } else {
	        $alias = $model-&gt;Permission-&gt;alias;

	        $queryData['joins'][]  = array(
                'table'     =&gt; $model-&gt;Permission-&gt;table,
                'alias'     =&gt; $alias,
                'type'      =&gt; 'INNER',
                'foreignKey'=&gt; false,
                'conditions'=&gt; array(
                    "$alias.model = '{$model-&gt;alias}'",
                    "$alias.foreign_key = {$model-&gt;alias}.{$model-&gt;primaryKey}",
                    'or' =&gt; $this-&gt;getPermissionQuery($model)
                )
            );
	    }

	    return $queryData;
	}

	public function beforeSave(&amp;$model) {
	    if(!$this-&gt;isEnabled()) {
	        return true;
	    }

	    $id = null;

	    if(!empty($model-&gt;id)) {
	        $id = $model-&gt;id;
	    } elseif(
	       !empty($model-&gt;data) and
	       isset($model-&gt;data[$model-&gt;alias]) and
           isset($model-&gt;data[$model-&gt;alias]['id']) and
           !empty($model-&gt;data[$model-&gt;alias]['id'])) {
	        $id = $model-&gt;data[$model-&gt;alias]['id'];
	    }

            if($model-&gt;alias == $this-&gt;settings[$model-&gt;alias]['groupModel'] and empty($id)) {
                $next_bit   = 1;
                $max_bit    = $model-&gt;field('bit', null, "{$model-&gt;alias}.bit DESC");

                if(!empty($max_bit)) {
                    $next_bit = $max_bit * 2;
                }

                $this-&gt;set('bit', $next_bit);
            }

            return (!empty($id))
	           ? $this-&gt;hasPermission($model, 'write', $id)
	           : true;
	}

	private function backout(&amp;$model) {
	    $model-&gt;del();
	    return false;
	}

	private function getModel(&amp;$model, $name = null) {

	    if(empty($name)) {
	        return null;
	    }

	    if(isset($model-&gt;{$name})) {
	        $theModel =&amp; $model-&gt;{$name};
	    } else {
	        $theModel =&amp; ClassRegistry::init($name);
	    }

	    return $theModel;
	}

	private function getPermissionBits(&amp;$model) {
	    return (isset($model-&gt;data['permission']) and
	            !empty($model-&gt;data['permission']))
                    ? $model-&gt;data['permission']
                    : (isset($model-&gt;data[$model-&gt;alias]) and
                       isset($model-&gt;data[$model-&gt;alias]['permission']) and
                       !empty($model-&gt;data[$model-&gt;alias]['permission']))
                        ? $model-&gt;data[$model-&gt;alias]['permission']
                        : $this-&gt;settings[$model-&gt;alias]['defaultBits'];
	}

	private function getPermissionQuery(&amp;$model, $action = 'read') {
	    $alias = $model-&gt;Permission-&gt;alias;
	    $perms = $this-&gt;__permissions;

	    return array(
            "$alias.permission &amp; {$perms['other_'.$action]} &lt;&gt; 0",
            array(
                "$alias.permission &amp; {$perms['group_'.$action]} &lt;&gt; 0",
                "$alias.group_bits &amp; ". PERMISSION_GROUP_BITS ." &lt;&gt; 0"
            ),
            array(
                "$alias.permission &amp; {$perms['owner_'.$action]} &lt;&gt; 0",
                "$alias.user_id = '". PERMISSION_USER_ID ."'"
            )
        );
	}

	public function hasPermission(&amp;$model, $action = 'read', $id = null) {
	    if(!$this-&gt;isEnabled()) {
	        return true;
	    }

	    if((PERMISSION_GROUP_BITS &amp; 1) &lt;&gt; 0) {
	        return true;
	    } elseif(empty($id) and empty($model-&gt;id)) {
	        return false;
	    }

	    $permission = $model-&gt;Permission-&gt;find('count', array(
            'recursive'     =&gt; -1,
            'conditions'    =&gt; array(
                'model'         =&gt; $model-&gt;alias,
                'foreign_key'   =&gt; (!empty($id)
                                ?  $id
                                :  $model-&gt;id),
                'or'            =&gt; $this-&gt;getPermissionQuery($model, $action)
            )
	    ));

	    return (!empty($permission) and $permission &gt; 0);
	}

	private function isEnabled() {
	    return $this-&gt;__enabled;
	}

	private function isTree(&amp;$model) {
	    return $model-&gt;Behaviors-&gt;attached('Tree');
	}

	public function setUserData(&amp;$model, $data = array()) {
	    if(empty($data)) {
	        return;
	    }

	    $modelNames = am(array_values($model-&gt;tableToModel), array($model-&gt;alias));

	    foreach($modelNames as $modelName) {
	        if(!isset($this-&gt;settings[$modelName])) {
	            $this-&gt;settings[$modelName] = $this-&gt;__defaults;
	        }

	        $this-&gt;settings[$modelName]['userData'] = $data;
	    }
	}

}
</code></pre>
</div>
<h2>How To Use</h2>
<p>first off, there are a couple of caveats, as with anything that&#8217;s not 100% ready for release and built primarily for my use.</p>
<ul>
<li>my app uses UUIDs, so you might need to adjust sql/code if you don&#8217;t</li>
<li>groups are hierarchical, and thus tree-behaviored</li>
<li>there <strong>must</strong> be a &#8220;root&#8221; group with a bit of 1, and a &#8220;root&#8221; user in that group.  any other groups must be created as siblings to the root group (not children!)</li>
<li>if you use the Auth component to control your app, you&#8217;ll need to set the scope for Auth, ala: $this-&gt;Auth-&gt;userScope = array(&#8216;permissions&#8217; =&gt; false);</li>
<li>if you use this on a tree-behaviored model besides the group model, you will need to handle inherited permissions (if you want that)</li>
<li>there are other missing knicknacks, specifically updating the user&#8217;s group_bits when you add/remove groups, or if you add/remove the user from groups.  actually, now that i think about it, removing a group won&#8217;t cause a problem, so long as you skip the removed group&#8217;s bit when adding a new group.  i&#8217;ll explain why in a bit.  and there isn&#8217;t a way to override the owner or group_bit for a record as of the moment.  i&#8217;ll add that soon.</li>
</ul>
<p>you will need to create the appropriate tables:</p>
<div class="php" style="border:1px dotted #a0a0a0;overflow:scroll;height:200px;white-space:nowrap;font-family:'Courier New',Courier,monospace;background-color:#f0f0f0;color:#000000;margin:0;padding:10px;">
<pre>CREATE TABLE `groups` (
  `id` char(36) collate utf8_unicode_ci NOT NULL,
  `parent_id` char(36) collate utf8_unicode_ci default NULL,
  `lft` int(3) NOT NULL,
  `rght` int(3) NOT NULL,
  `name` varchar(100) collate utf8_unicode_ci NOT NULL,
  `bit` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `parent_id` (`parent_id`),
  KEY `lft` (`lft`,`rght`),
  KEY `bit` (`bit`)
) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `groups_users` (
  `id` int(11) NOT NULL auto_increment,
  `group_id` char(36) collate utf8_unicode_ci NOT NULL,
  `user_id` char(36) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `group_id` (`group_id`,`user_id`)
)  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `permissions` (
  `id` char(36) collate utf8_unicode_ci NOT NULL,
  `model` varchar(25) collate utf8_unicode_ci NOT NULL,
  `foreign_key` char(36) collate utf8_unicode_ci NOT NULL,
  `user_id` char(36) collate utf8_unicode_ci NOT NULL,
  `group_bits` int(11) NOT NULL default '0',
  `permission` int(3) unsigned zerofill NOT NULL default '000',
  PRIMARY KEY  (`id`),
  KEY `user_id` (`user_id`),
  KEY `group_bit` (`group_bits`),
  KEY `model` (`model`,`foreign_key`)
) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `users` (
  `id` char(36) collate utf8_unicode_ci NOT NULL,
  `password` char(40) collate utf8_unicode_ci NOT NULL,
  `email` varchar(255) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`id`)
) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;</pre>
</div>
<p>and that&#8217;s it!  well, not really.  you will need to add the behavior to each model you want to be permissioned:</p>
<div class="php" style="border:1px dotted #a0a0a0;white-space:nowrap;font-family:'Courier New',Courier,monospace;background-color:#f0f0f0;color:#000000;margin:0;padding:10px;">
<pre>public $actsAs = array('Permissionable');</pre>
</div>
<p>you will need to put this code in AppController::beforeFilter().  this will inform the behavior who the user in question is, as well as what the group_bit is for the user (we&#8217;ll explain how this works later).</p>
<p>if you put this in some other controller&#8217;s beforeFilter you run the risk of permissioned associated models not knowing who the logged-in user is, i don&#8217;t recommend it. i&#8217;ll be moving this to a component in all likelihood, so it doesn&#8217;t have to live directly in any controller(s).</p>
<div class="php" style="border:1px dotted #a0a0a0;white-space:nowrap;font-family:'Courier New',Courier,monospace;background-color:#f0f0f0;color:#000000;margin:0;padding:10px;">
<pre>        $modelName  = Inflector::classify($this-&gt;name);
        $user_id    = $this-&gt;Auth-&gt;user('id');

        if(
            empty($user_id) or !isset($this-&gt;{$modelName}) or
            $this-&gt;{$modelName}-&gt;Behaviors-&gt;attached('Permissionable') == false
        ) {
            return;
        }

        $mainModel  = $this-&gt;{$modelName};
        $group_bits = $this-&gt;Session-&gt;read('Permission.group_bits');

        if(empty($group_bits)) {
            $permissionModel    =&amp; $mainModel-&gt;Permission;
            $permissionBehavior = $mainModel-&gt;Behaviors-&gt;Permissionable;
            $group_bits         = $permissionModel-&gt;field('group_bits', array(
                'model'         =&gt; $permissionBehavior-&gt;settings[$mainModel-&gt;alias]['userModel'],
                'foreign_key'   =&gt; $user_id
            ));

            if(empty($group_bits)) {
                return;
            }

            $this-&gt;Session-&gt;write('Permission.group_bits', $group_bits);
        }

        define('PERMISSION_USER_ID',    $user_id);
        define('PERMISSION_GROUP_BITS', $group_bits);</pre>
</div>
<p>after that, any new record that is created will have a permission set, the creating user being the owner, and the groups that the user is in as the group_bit for the permission.  this means that every group the user is in will have the same group level permissions that the creating user has.  for example:  if the record is created with the permissions of (user read/write/delete, group read/write, others read), then any member of any group that the creator is in will also have read/write permissions on that record, while the author will additionally have delete permission, and all others will have read.</p>
<p>now, if you want to override the default permissions, you can do so in the model by setting the $defaultBits property to the desired permission for that model.  additionally, you can set $model-&gt;data['permission'] or $model-&gt;data[$modelName]['permission'] to the desired permission for the row being saved.</p>
<h2>So, how does it work, Mr. Wizard?</h2>
<p>well, the simple answer is &#8220;bitwise&#8221;-ly.  without going into tedious detail, we make use of bitwise operators in php (and sql) to determine what type of operation we are trying to accomplish (selects are reads, updates are writes and deletes are deletes), we check whether or not the user is either the owner of the row, in a group that the record in question is also in, or if &#8220;other&#8221; permissions allow them to do what they are asking.  some quick examples:</p>
<ul>
<li>group A has bit of 1 (root)</li>
<li>group B has bit of 2</li>
<li>group C has bit of 4</li>
<li>group D has a bit of 8</li>
<li>user 1 has a group_bit of 1 (root)</li>
<li>user 2 has a group_bit of 6 (group B + group C)</li>
<li>user 3 has a group_bit of 8 (group D)</li>
<li>record is owned by user 2, has a group_bit of 6 (group B + group C) and a permission of 416 (owner read (256) + owner write (128) + group read (32) = 416)</li>
</ul>
<p>user 1 (root) tries to select the record.  since he&#8217;s root (bit 1), he&#8217;s allowed.</p>
<p>user 2 tries to select record. he&#8217;s not the record owner, so permissions are checked to see if it contains the &#8220;group read&#8221; bit: (416 &amp; 32 &lt;&gt; 0) == true, and then the record&#8217;s group_bit is checked to see if the user has a matching group bit: (6 &amp; 6 &lt;&gt; 0) == true.  both checks are true, so the user can read the row.</p>
<p>user 3 tries to select record. he&#8217;s not the record owner, so group permissions are checked: (416 &amp; 32 &lt;&gt; 0) == true, and then group_bit is checked: (6 &amp; 8 &lt;&gt; 0) == false, then other_read permissions are checked (416 &amp; 4 &lt;&gt; 0) == false, so the user is denied access to the row.</p>
<p>probably the nicest thing about this method is that instead of having to do separate queries in a beforeFind to determine if the query should continue or how to filter the original query, all the behavior does is add a join to the query on the permissions table.  all of your single queries to find matching records stay single queries.  there still are pre-checks when trying to update or delete a record, but they are quick and relatively painless.  there&#8217;s nothing else to really change.  all your find calls will work like before, except they will only return allowed rows.  save()/delete() calls will return false if the row doesn&#8217;t have write access for the user trying to do so.  inserting isn&#8217;t covered by this method though, that&#8217;s where ACLs come into play&#8230; and that&#8217;s what the follow-up article will cover.</p>
<p>so there you have it.  since this is only mostly finished, i welcome fixes, comment, critique and/or praise.</p>
<br /> Tagged: ACL, CakePHP, PermissionableBehavior, permissions, programming, RBAC <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/25/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=25&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2009/04/05/row-level-model-access-control-for-cakephp/feed/</wfw:commentRss>
		<slash:comments>18</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>ExtJS Core 3.0 vs. jQuery (round 1)</title>
		<link>http://jmcneese.wordpress.com/2009/04/05/extjs-core-30-vs-jquery-round-1/</link>
		<comments>http://jmcneese.wordpress.com/2009/04/05/extjs-core-30-vs-jquery-round-1/#comments</comments>
		<pubDate>Sun, 05 Apr 2009 03:43:39 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[ui]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=21</guid>
		<description><![CDATA[@aconran tweeted about this post on Tim Sporcic&#8217;s blog comparing the new ExtJS Core to jQuery as a low-level javascript library of choice.  Check it out. Tagged: extjs, Javascript, programming, ui<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=21&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p><a title="@aconran" href="http://twitter.com/aconran" target="_blank">@aconran</a> tweeted about this post on Tim Sporcic&#8217;s blog comparing the new ExtJS Core to jQuery as a low-level javascript library of choice.  <a title="New Kid on the Block" href="http://www.sporcic.org/2009/04/ext-core/" target="_blank">Check it out</a>.</p>
<br /> Tagged: extjs, Javascript, programming, ui <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/21/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/21/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/21/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/21/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/21/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/21/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/21/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/21/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/21/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/21/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/21/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/21/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/21/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/21/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=21&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2009/04/05/extjs-core-30-vs-jquery-round-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>ExtJS Core 3.0 Beta released</title>
		<link>http://jmcneese.wordpress.com/2009/04/04/extjs-core-30-beta-released/</link>
		<comments>http://jmcneese.wordpress.com/2009/04/04/extjs-core-30-beta-released/#comments</comments>
		<pubDate>Sat, 04 Apr 2009 17:50:35 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[ui]]></category>

		<guid isPermaLink="false">http://jmcneese.wordpress.com/?p=15</guid>
		<description><![CDATA[oh it is a happy day in JS land. i think that Prototype (Prototype is dead! Long live Prototype!) and jQuery have something to worry about. check it out. Tagged: extjs, Javascript, programming, ui<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=15&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>oh it is a happy day in JS land.</p>
<p>i think that <a title="Prototype JS" href="http://www.prototypejs.org" target="_blank">Prototype</a> (Prototype is dead! Long live Prototype!) and <a title="jQuery" href="http://jquery.com" target="_blank">jQuery</a> have something to worry about.</p>
<p><a title="Ext Core 3.0 Beta released" href="http://extjs.com/blog/2009/04/04/ext-core-30-beta-released/" target="_blank">check it out.</a></p>
<br /> Tagged: extjs, Javascript, programming, ui <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/15/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/15/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/15/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/15/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/15/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/15/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/15/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/15/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=15&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2009/04/04/extjs-core-30-beta-released/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
		<item>
		<title>echo &#8216;Hello World!&#8217;;</title>
		<link>http://jmcneese.wordpress.com/2009/03/29/hello-world/</link>
		<comments>http://jmcneese.wordpress.com/2009/03/29/hello-world/#comments</comments>
		<pubDate>Sun, 29 Mar 2009 15:26:25 +0000</pubDate>
		<dc:creator>Joshua McNeese</dc:creator>
				<category><![CDATA[Other Shit]]></category>
		<category><![CDATA[bullshit]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[trite]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[ah, the inevitable first post, sometimes funny, sometimes informative, always trite. funny:  &#8220;Unix is user friendly. It&#8217;s just very particular about who it&#8217;s friends are.&#8221; informative: &#8220;In computer science, tree-traversal refers to the process of visiting (examining and/or updating) each node in a tree data structure, exactly once, in a systematic way. Such traversals are [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=1&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>ah, the inevitable first post, sometimes funny, sometimes informative, always trite.</p>
<p>funny:  &#8220;Unix is user friendly. It&#8217;s just very particular about who it&#8217;s friends are.&#8221;</p>
<p>informative: &#8220;In <a title="Computer science" href="http://en.wikipedia.org/wiki/Computer_science">computer science</a>, <strong>tree-traversal</strong> refers to the process of visiting (examining and/or updating) each node in a <a class="mw-redirect" title="Tree data structure" href="http://en.wikipedia.org/wiki/Tree_data_structure">tree data structure</a>, exactly once, in a systematic way. Such traversals are classified by the order in which the nodes are visited.&#8221; &#8212; <a title="Tree Traversal" href="http://en.wikipedia.org/wiki/Tree_traversal" target="_blank">wikipedia</a></p>
<p>trite: see post title.</p>
<p>check.</p>
<br /> Tagged: bullshit, programming, trite <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jmcneese.wordpress.com/1/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jmcneese.wordpress.com/1/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jmcneese.wordpress.com/1/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jmcneese.wordpress.com/1/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jmcneese.wordpress.com/1/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jmcneese.wordpress.com/1/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jmcneese.wordpress.com/1/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jmcneese.wordpress.com/1/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jmcneese.wordpress.com/1/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jmcneese.wordpress.com/1/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jmcneese.wordpress.com/1/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jmcneese.wordpress.com/1/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jmcneese.wordpress.com/1/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jmcneese.wordpress.com/1/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jmcneese.wordpress.com&amp;blog=7151869&amp;post=1&amp;subd=jmcneese&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jmcneese.wordpress.com/2009/03/29/hello-world/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/bae72608ed8d47ceb851898f6368e4d2?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=X" medium="image">
			<media:title type="html">jmcneese</media:title>
		</media:content>
	</item>
	</channel>
</rss>
