Page 1 of 1

Per-field access control and controller design

Posted: Fri Jul 17, 2009 11:35 am
by inghamn
When doing role-based access control for my web applications, I invariably end up needing to control access on a per-field basis. It doesn't seem to line up on a per-action basis.

For example, in our CMS, Webmasters, Administrators, Content Creators, and Publishers can all edit documents. However, their roles determine which fields they're allowed to change. On a document, all users can change the type, title, description, and content. However, only certain roles can change things like the alias, department, locking, or PHP support.

I've been implementing this by setting the fields that all roles are allowed to change; then, working through the rest of the fields, and only setting those fields if they have the correct role.

ACL can specify this, but in the controllers, I still end up needing to run though and check the persmissions on each field of the document as I'm handling a form post.

Is there a better design that I'm missing?
Here's some (stripped down) controller code as an example:

Code: Select all

 
verifyUser(array('Webmaster','Administrator','Content Creator','Publisher'));
 
$document = new Document($_REQUEST['document_id']);
 
# Make sure they're allowed to be editing this document
if (!$document->permitsEditingBy($_SESSION['USER'])) {
    $_SESSION['errorMessages'][] = "noAccessAllowed";
    Header("Location: $return_url");
    exit();
}
 
 
# Handle any document fields that are allowed to changed by
# any user-role
$editableByAll = array('type','title','description','content');
if (isset($_POST['document'])) {
    foreach($_POST['document'] as $field=>$value) {
        if (in_array($field,$editableByAll)) {
            $set = "set".ucfirst($field);
            $document->$set($value);
        }
    }
}
 
# Only Administrators and webmasters can change the Department and Alias
if (userHasRole(array('Administrator','Webmaster'))) {
    if (isset($_POST['department_id'])) {
        $document->setDepartment_id($_POST['department_id']);
    }
    if (isset($_POST['alias'])) {
        $document->setAlias($_POST['alias']);
    }
}
 
 
# Only Administrators can set the locked flag
if (isset($_POST['locked'])) {
    # Make sure they're allowed to change the lock status
    if (!$document->isLocked()
        || userHasRole('Administrator')
        || $_SESSION['USER']->getId()==$document->getLockedBy()) {
        if ($_POST['locked']==='Locked') {
            if (!$document->isLocked()) { $document->setLockedByUser($_SESSION['USER']); }
        }
        else {
            $document->setLockedBy(null);
        }
    }
}
 
# PHP code inside the content can only be allowed by an Administrator, or a Webmaster
if (isset($_POST['enablePHP'])) {
    if (userHasRole(array('Administrator','Webmaster'))) {
        $document->setEnablePHP($_POST['enablePHP']);
    }
}
 
# Save the document only when they ask for it
if (isset($_POST['action'])
    && ($_POST['action'] == 'save'
        || $_POST['action']=='saveAndContinue') ) {
    try {
        $document->save();
        if ($_POST['continue'] != 'true') {
            unset($document);
            Header("Location: $return_url");
            exit();
        }
    }
    catch (Exception $e) { $_SESSION['errorMessages'][] = $e; }
}
 
 
# Display the form
$template = new Template('popup');
$template->title = $document->getTitle();
 
$form = new Block("documents/update.inc");
$form->document = $document;
$form->return_url = $return_url;
 
$template->blocks[] = $form;
echo $template->render();
 

Re: Per-field access control and controller design

Posted: Fri Jul 17, 2009 12:32 pm
by alex.barylski
It's an interesting problem I have toiled over for years. I try and design the interface to reflect that of appropriate roles, however if roles are dynamically configured, then that one to one mapping doesn't exist.

I truly believe that roles are best determined by the architect/designer but some software I suppose is just best left to a user to figure out so as to fit their organization hierarchy/workflow, etc.

Doing things dynamically like you are doing just seems like so much work, I mean when you add a field, don't you have to wire it into the ACL and everything? Whereas if roles mapped to actions, it's much easier for a front controller plugin or similar to check whether you have the right access to perform an action.

Per-field ACL is very fine grained which seems great but in my experience results in more issues than it solves. It's almost akin to giving a user to many options, like going to Subway and trying o decide what possible combo of condomints you want on it. :P

Interesting problem though, short of having a hasPermission() method on an array of permissions assigned to individual users, I don't think there is a easy/better way.

Re: Per-field access control and controller design

Posted: Sat Jul 18, 2009 1:39 am
by Benjamin
Populate an array of fields that the user IS allowed to modify. Loop ONLY through those checking for posted data, ignoring the rest.

Re: Per-field access control and controller design

Posted: Fri Jul 24, 2009 1:09 pm
by koen.h
I think that you can do this with with zend framework acl.

I don't like the hardcoding of your roles and privileges though.

I think I would prefer this approach:

Code: Select all

class AclWrapper {
    public function __construct($objectToChange, $acl, $roles) {}
    public function __call($methodName, $arguments) {
       if ($this->acl->is_allowed('user_roles', $methodName, $objectToChange->aclName) {
           //call_user_func_array
        }
        else { // }
    }
}
 
$acl->addResource('document');
$acl->allow('admin', 'setAlias', 'document');
 
// in your controller
$document = new AclWrapper(new Document, $acl, array('roles user has'));
$document->setAlias($arguments);
 
Maybe this gives you a new idea.