I've developed a small yet effective MVC-style framework for use in an application, and I'm implementing an ACL per-request check.
Quick details: PHP 5.3+; MySQL 5.1+; Custom framework, "MVC-like"
As of now, the ACL check is simple "deny-if-not white-listing"; Each group
can be assigned permission to certain request handlers. For example:
privilege permission
+----------+---------------+ +---------------+---------------+
| group_id | permission_id | | permission_id | handler_key |
+----------+---------------+ +---------------+---------------+
| 1 | 1 | | 1 | lorem_ipsum |
| 1 | 2 | | 2 | hello_world |
| 2 | 3 | | 3 | foobar |
+----------+---------------+ +---------------+---------------+
(user
and group
excluded for brevity, but their models are nothing unusual)
Anyways, my framework routes URIs to the appropriate handler_key
via a handler/path table (to decouple the filesystem architecture) The request is then dispatched to the handler, given the group_id
associated with the request is white-listed for that handler_key
.
I'm curious, what is the best approach to implement storing/checking arbitrary (user-defined) constraints? Case examples would be:
user
. This check would involve perhaps, a check of the user_id
field associated with the content to be modified by the handler, and the user_id
associated with the request
I had a flags
column, however that is not future-proof with the introduction of more feature, group, and constraint requirements. I was thinking in the following direction, but what to use?
permission
+---------------+----------------------------+
| permission_id | handler_key | constraint |
+---------------+---------------+------------+
| 1 | lorem_ipsum | ? |
| 2 | hello_world | ? |
| 3 | foobar | ? |
+---------------+---------------+------------+
Unnecessary clarification:
(Note: code was typed here, not copypasta from project)
To clarify some of the jargon here; handlers (specifically web handlers) are essentially controllers, for those familiar with the MVC archetype.
Their specific implementation is a single PHP file, which returns a function to be called by the dispatcher, or sub-handler caller. For example:
<?php
$meta = array('subhandlers' => array());
return function($request, $response) use($meta){
$response['foo'] = 'bar';
};
My framework uses web handlers and API handlers; web handlers feed data into a response object (basically a collection of hierarchical views) which generates HTML. The data is obtained by invoking the API handlers, which simply return raw data (API handlers could be regarded as representations of the model, going back to typical MVC)
Compound handlers are essentially an abstraction layer, as they are handlers themselves that invoke handlers to aggregate data. My current implementation of the ACL check, does a cursory check of all nested handlers (via $meta
, an array variable declared to act as the metadata header for the handler) For example:
<?php
$meta = array('subhandlers' => array('my_subhandler'));
return function($request, $response) use($meta){
$someData = Caller::call('my_subhandler', array($request, $response));
$response->bind($someData);
};
The video I posted in my comment has some similar ideas to the ones you shared, no code though.
Regarding your question, I've never implemented this and I have only a vague idea how such system could (or couldn't) be built, so take my input with a (or several) grain(s) of salt.
The first thing you must do is identify the constrains the user can restrict, forget about arbitrary data, defining what the user can and cannot define is the most important point here.
The next step is to define some sort of notation or data structure you can easily parse and validate, for instance, in time based restrictions you have the following possibilities:
If you have a propper notation you can easily parse and validate this by tokenizing the rule and maybe using something DatePeriod or DateInterval or even the modulus operator like crontab does.
Your second example:
Only permit the given group to invoke a handler to modify "owned" data; ie: data created by the associated user.
Sounds like plain ACL to me:
Then of course, you have the more complicated rules like:
The user who approves the record must have an equal or higher level (in the same security namespace) as the user who created the record, but they can't be the same user.
I think this kind of rules must be specific to the application logic and a true universal solution would be extremely difficult to implement, Zed Shaw mentioned in his talk that he came up with a complete solution that only uses 400 lines of code, I would be very interested in learning how he did that.
Flags (stored in bits) are a great (although cryptic) way of specifying a combination of one or several constrains but in my experience it's better if you define all the constrains apriori.
Since you don't seem to know exactly the constrains you want to check I suggest you create notations for the several type of restrictions you want to check:
And then implement methods that parse (maybe with a notation that implies the version, so you can migrate in the future) and validate all constrains.
I'm sorry if my answer doesn't help you much but I find the question very interesting and I hope someone else comes up with a better one, AFAIK there is no magic wand to do this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With