3
\$\begingroup\$

I've been working on putting a new app up against Zend. In my admin section, I want the nav links to only show if the user has rights to see the page. So I set up some Acls. But this doesn't seem like the right way to do things. What's the "right" way to do this stuff?

Bootstrap.php

<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
    protected $_aclAdapter;


    protected function _initDoctype() {
        $this->bootstrap('view');
        $view = $this->getResource('view');
        $view->doctype('XHTML1_STRICT');
    }

    protected function _initPlugins() {
        // First, set up the Cache
        $frontendOptions = array(
                'automatic_serialization' => true
                );

        if (! is_dir("/tmp/cache")) mkdir("/tmp/cache");
        $backendOptions  = array(
                'cache_dir'                => '/tmp/cache'
                );

        $cache = Zend_Cache::factory('Core',
                'File',
                $frontendOptions,
                $backendOptions);

        Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);


        $this->_aclAdapter = new My_Plugin_Acl();
    }

    protected function _initRoute() {

        /*
        $this->bootstrap('FrontController');
        $front = $this->getResource('FrontController');
        $router = $front->getRouter();

        $restRoute = new Zend_Rest_Route($front, array(), array('rest'));
        $router->addRoute('rest', $restRoute);

        return $router;
        */
    }

    protected function _initAutoloadModuleAdmin() {
        $autoloader = new Zend_Application_Module_Autoloader(
            array(
                'namespace' => 'Admin',
                'basePath' => APPLICATION_PATH . '/modules',
            )
        );
        return $autoloader;
    }

    protected function _initDatabase() {
        $database = Zend_Db_Table::getDefaultAdapter();
        return $database;
    }

    protected function _initAuthChecker() {
        $this->bootstrap('view');
        $view = $this->getResource('view');

        /**
         *  @todo Determine how to pass Acl information to the view
         *  so that links can be determined properly
         */


        $auth = Zend_Auth::getInstance();
        if ($auth->hasIdentity()) {
            $view->isLoggedIn = true;

            $aclAdapter = $this->_aclAdapter;

            $container = new Zend_Navigation();

            $pages = array(
                new Zend_Navigation_Page_Mvc(array('module' => 'admin', 'action' => 'index', 'controller' => 'make', 'label' => 'Makes')),
                new Zend_Navigation_Page_Mvc(array('module' => 'admin', 'action' => 'index', 'controller' => 'model', 'label' => 'Models')),
                new Zend_Navigation_Page_Mvc(array('module' => 'admin', 'action' => 'index', 'controller' => 'year', 'label' => 'Years')),
                new Zend_Navigation_Page_Mvc(array('module' => 'admin', 'action' => 'index', 'controller' => 'user', 'label' => 'Users')),
            );

            foreach ($pages as $page) {
                $resources = $aclAdapter->getCurrentResources($page);


                $allowedToViewResource = true;
                foreach ($resources as $resource) {
                    // access to the module
                    if(!$aclAdapter->isAllowed($aclAdapter->getRolesForIdentity($auth->getIdentity()->nickname), $resource, 'view')) {
                        $allowedToViewResource = false;
                    }
                }

                if ($allowedToViewResource) {
                    $container->addPage($page);
                }
                $view->navigation = $container;
            }

        } else {
            $view->isLoggedIn = false;
        }
    }
}

My/Plugin/Acl.php

<?php

/**
 * Acl functionality
 *
 * @category My
 * @package  My_Plugin
 * @author   Glen Solsberry <glens@dp.cx>
 */


class My_Plugin_Acl extends Zend_Controller_Plugin_Abstract {
    /**
     * @var Zend_Acl
     */
    protected $_acl = null;

    public function __construct() {

        $args = func_get_args();
        if (count($args) > 0) {
            $this->_acl = $args[0];
        } else {
            $this->_acl = new Zend_Acl();
        }

        $this->_acl = $this->setupRoles($this->_acl);
        $this->_acl = $this->setupResources($this->_acl);
        $this->_acl = $this->setupAllowances($this->_acl);
    }

    public function isAllowed($role, $resource, $function) {
        return $this->_acl->isAllowed($role, $resource, $function);
    }


    /**
     * preDispatch
     *
     * @param Zend_Controller_Request_Abstract $request
     * @return none
     */
    public function preDispatch(Zend_Controller_Request_Abstract $request) {

        if (Zend_Auth::getInstance()->hasIdentity()) {
                $identity = Zend_Auth::getInstance()->getIdentity();
            $role = $this->getRolesForIdentity($identity->nickname);
        } else {
            $role = 'guest';
        }

        $resources = $this->getCurrentResources($request);

        $allowedToViewResource = true;
        foreach ($resources as $resource) {
            // access to the module
            if(!$this->_acl->isAllowed($role, $resource, 'view')) {
                $allowedToViewResource = false;
            }
        }
        if ($allowedToViewResource === false) {
            //If the user has no access we send him elsewhere by changing the request
            // print sprintf("I'm sorry, but your role '%s' doesn't have access to the resource named '%s'<br>", $role, $resource);
            $request->setModuleName('')->setControllerName('auth')->setActionName('login');
            return false;
        }
        return true;
    }

    public function getCurrentResources($request) {

        if ($request instanceOf Zend_Controller_Request_Http) {
            $resources = array(
                $request->getModuleName(),
                implode("-", array($request->getModuleName(), $request->getControllerName())),
                implode("-", array($request->getModuleName(), $request->getControllerName(), $request->getActionName())),
            );
        } else if ($request instanceOf Zend_Navigation_Page_Mvc) {
            $resources = array(
                $request->getModule(),
                implode("-", array($request->getModule(), $request->getController())),
                implode("-", array($request->getModule(), $request->getController(), $request->getAction())),
            );
        } else {
            throw new Zend_Exception('Unsupported request type');
        }

        return $resources;
    }

    /**
     * Gets available roles for an identity, by nickname
     * @param string $nickname
     */
    public function getRolesForIdentity($nickname)  {
        if ($nickname === "") {
            return 'guest';
        }
        return 'admin';
    }

    private function setupRoles(Zend_Acl $acl) {
        $acl->addRole(new Zend_Acl_Role('guest'));

        // make-admin has all rights that guest has
        $acl->addRole(new Zend_Acl_Role('make-admin'), 'guest');
        $acl->addRole(new Zend_Acl_Role('model-admin'), 'guest');
        $acl->addRole(new Zend_Acl_Role('year-admin'), 'guest');
        $acl->addRole(new Zend_Acl_Role('user-admin'), 'guest');

        // admin has all rights that year-admin, make-admin, user-admin and model-admin have
        $acl->addRole(new Zend_Acl_Role('admin'), array('year-admin', 'make-admin', 'model-admin', 'user-admin'));

        return $acl;
    }

    private function setupResources(Zend_Acl $acl) {

        // setup admin resources
        $this->setupResourcesFromArrays(
            $acl,
            array('admin'),
            array('index','make','year','model','user'),
            array('index','update','delete')
        );

        $this->setupResourcesFromArrays(
            $acl,
            array('default'),
            array('auth','index','error'),
            array('index')

        );

        $this->setupResourcesFromArrays(
            $acl,
            array('default'),
            array('auth'),
            array('logout')
        );

        $this->setupResourcesFromArrays(
            $acl,
            array('default'),
            array('favicon.ico'),
            null
        );

        return $acl;
    }

    private function setupResourcesFromArrays($acl, $modules, $controllers, $actions) {
        foreach ($modules as $module) {
            $resource_name = $module;
            $this->addResource($acl, $resource_name);

            foreach ($controllers as $controller) {
                $resource_name = sprintf('%s-%s', $module, $controller);
                $this->addResource($acl, $resource_name);

                if (is_array($actions)) {
                    foreach ($actions as $action) {
                        $resource_name = sprintf('%s-%s-%s', $module, $controller, $action);
                        $this->addResource($acl, $resource_name);
                    }
                }
            }
        }
    }

    private function addResource($acl, $resource_name) {
        if (! $acl->has($resource_name)) {
            $acl->addResource(new Zend_Acl_Resource($resource_name));
        }
    }

    private function setupAllowances(Zend_Acl $acl) {
        // role, resource, privilege
        $acl->allow('admin', 'admin', 'view');
        $acl->allow('admin', 'admin-index', 'view');
        $acl->allow('admin', 'admin-index-index', 'view');
        /**
          SNIP
        */

        $acl->allow('guest', 'default', 'view');
        $acl->allow('guest', 'default-index', 'view');
        $acl->allow('guest', 'default-index-index', 'view');
        $acl->allow('guest', 'default-auth', 'view');
        $acl->allow('guest', 'default-auth-index', 'view');

        return $acl;
    }
}
\$\endgroup\$
1
\$\begingroup\$

You should use the available bootstrap resources where possible:

protected function _initPlugins() {
    // First, set up the Cache
    $frontendOptions = array(
            'automatic_serialization' => true
            );

    if (! is_dir("/tmp/cache")) mkdir("/tmp/cache");
    $backendOptions  = array(
            'cache_dir'                => '/tmp/cache'
            );

    $cache = Zend_Cache::factory('Core',
            'File',
            $frontendOptions,
            $backendOptions);

could be replaced by without any functions in your bootstrap:

resources.cachemanager.yourcachename.frontend.name = Core
resources.cachemanager.yourcachename.options.automatic_serialization = true
resources.cachemanager.yourcachename.backend.name = File
resources.cachemanager.yourcachename.backend.options.cache_dir = "/tmp/cache"

Finally, you activate the metadatacache for your database:

resources.db.defaultMetadataCache = "yourcachename"

Same can be done with your view:

protected function _initDoctype() {
    $this->bootstrap('view');
    $view = $this->getResource('view');
    $view->doctype('XHTML1_STRICT');
}

// can be replaced with:
resources.view.doctype = "XHTML1_STRICT"

Assuming you're setting up the database in your config, you probably don't need this line:

protected function _initDatabase() {
    $database = Zend_Db_Table::getDefaultAdapter();
    return $database;
}

You can do the same for your autoloader and routes as well.
This probably should be a plugin on it's own:

protected function _initAuthChecker() 
{
    /* .... */
}

So far on your bootstrap. I can see you're take your modules/controllers as resources. Usually I don't aggree with this: a controller handles a set of models, which are the resources. I know this doesn't make the navigation easier ;) You should have a look at the module-bootstraps too. They take module-specific bootstraping away from your main-bootstrap.

\$\endgroup\$
4
  • \$\begingroup\$ As I've not read up on bootstrap resources, does this just create those objects and variables within the bootstrap automagically? \$\endgroup\$ Jun 5 '11 at 15:45
  • \$\begingroup\$ @gms8994: Yes :) (In a more unified way) \$\endgroup\$
    – Fge
    Jun 5 '11 at 16:18
  • \$\begingroup\$ "So far on your bootstrap. I can see you're take your modules/controllers as resources. Usually I don't aggree with this: a controller handles a set of models, which are the resources. I know this doesn't make the navigation easier ;) You should have a look at the module-bootstraps too. They take module-specific bootstraping away from your main-bootstrap." can you detail what you mean about this a little more? Thanks! \$\endgroup\$ Jun 5 '11 at 16:42
  • \$\begingroup\$ See Zend_Application_Resources_Modules and weierophinney.net/matthew/archives/… for module-bootstraping. For the controller/model stuff... this is way too much to explain it in a comment ;) survivethedeepend.com/zendframeworkbook/en/1.0 has a great explanation of both topics (you probably should read even more about it then :) \$\endgroup\$
    – Fge
    Jun 5 '11 at 17:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.