views:

1405

answers:

3

Imagine I have 4 database tables, and an interface that presents forms for the management of the data in each of these tables on a single webpage (using the accordion design pattern to show only one form at a time). Each form is displayed with a list of rows in the table, allowing the user to insert a new row or select a row to edit or delete. AJAX is then used to send the request to the server.

A different set of forms must be displayed to different users, based on the application ACL.

My question is: In terms of controllers, actions, views, and layouts, what is the best architecture for this interface?

For example, so far I have a controller with add, edit and delete actions for each table. There is an indexAction for each, but it's an empty function. I've also extended Zend_Form for each table. To display the forms, I then in the IndexController pass the Forms to it's view, and echo each form. Javascript then takes care of populating the form and sending requests to the appropraite add/edit/delete action of the appropriate controller. This however doesn't allow for ACL to control the display or not of Forms to different users.

Would it be better to have the indexAction instantiate the form, and then use something like $this->render(); to render each view within the view of the indexAction of the IndexController? Would ACL then prevent certain views from being rendered?

Cheers.

+1  A: 

There are a couple of places you could run your checks against your ACL:

  1. Where you have your loop (or hardcoded block) to load each form.
  2. In the constructor of each of the Form Objects, perhaps throwing a custom exception, which can be caught and appropriately handled.
  3. From the constructor of an extension of Zend_Form from which all your custom Form objects are extended (probably the best method, as it helps reduce code duplication).

Keep in mind, that if you are using ZF to perform an AJAXy solution for your updating, your controller needs to run the ACL check in it's init() method as well, preventing unauthorized changes to your DB.

Hope that helps.

J. Kenzal Hunter Sr.
A: 

I use a modified version of what's in the "Zend Framework in Action" book from Manning Press (available as PDF download if you need it now). I think you can just download the accompanying code from the book's site. You want to look at the Chapter 7 code.

Overview:

The controller is the resource, and the action is the privilege. Put your allows & denys in the controller's init method. I'm also using a customized version of their Controller_Action_Helper_Acl.

Every controller has a public static getAcls method:

public static function getAcls($actionName)
{
    $acls = array();
    $acls['roles']      = array('guest');
    $acls['privileges'] = array('index','list','view');

    return $acls;
}

This lets other controllers ask about this controller's permissions. Every controller init method calls $this->_initAcls(), which is defined in my own base controller:

public function init()
{
    parent::init(); // sets up ACLs
}

The parent looks like this:

public function init()
{
    $this->_initAcls(); // init access control lists.
}

protected function _initAcls()
{
    $to_call = array(get_class($this), 'getAcls');
    $acls = call_user_func($to_call, $this->getRequest()->getActionName());
    // i.e. PageController::getAcls($this->getRequest()->getActionName());

    if(isset($acls['roles']) && is_array($acls['roles']))
    {
        if(count($acls['roles'])==0)     { $acls['roles'] = null; }
        if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
        $this->_helper->acl->allow($acls['roles'], $acls['privileges']);
    }
}

Then I just have a function called:

aclink($link_text, $link_url, $module, $resource, $privilege);

It calls {$resource}Controller::getAcls() and does permission checks against them. If they have permission, it returns the link, otherwise it returns ''.

function aclink($link_text, $link_url, $module, $resource, $privilege)
{
    $auth = Zend_Auth::getInstance();

    $acl = new Acl(); //wrapper for Zend_Acl
    if(!$acl->has($resource))
    {
       $acl->add(new Zend_Acl_Resource($resource));
    }

    require_once ROOT.'/application/'.$module.'/controllers/'.ucwords($resource).'Controller.php';

    $to_call = array(ucwords($resource).'Controller', 'getAcls');
    $acls = call_user_func($to_call, $privilege);

    if(isset($acls['roles']) && is_array($acls['roles']))
    {
        if(count($acls['roles'])==0)     { $acls['roles'] = null; }
        if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
        $acl->allow($acls['roles'], $resource, $acls['privileges']);
    }

    $result = $acl->isAllowed($auth, $resource, $privilege);

    if($result)
    {
       return '<a href="'.$link_url.'" class="aclink">'.$link_text.'</a>';
    }
    else
    {
       return '';
    }
}
lo_fye
A: 

Hi. Have you solved this one yet?

I'm building a big database app with lots of nested sub-controllers as panels on a dashboard shown on the parent controller. Simplified source code is below: comes from my parentController->indexAction()

    $dashboardControllers = $this->_helper->model( 'User' )->getVisibleControllers();
    foreach (array_reverse($dashboardControllers) as $controllerName) // lifo stack so put them on last first
    {
        if ($controllerName == 'header') continue; // always added last
        // if you are wondering why a panel doesn't appear here even though the indexAction is called: it is probably because the panel is redirecting (eg if access denied). The view doesn't render on a redirect / forward
        $this->_helper->actionStack( 'index', $this->parentControllerName . '_' . $controllerName ); 
    }
    $this->_helper->actionStack( 'index', $this->parentControllerName . '_header' ); 

If you have a better solution I'd be keen to hear it. For my next trick I need to figure out how to display these in one, two or three columns depending on a user preference setting

Steve