We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

ACL with namespaces

I'm writing an ACL that needs to take namespaces into account. Some of my controllers are named the same but are from different namespaces. Example: App\Controllers\Account\Tickets App\Controllers\Admin\Tickets

How can I add this functionality to my ACL? My first thought was to add the namespace in the resourcename and let the beforeDispatch code do a namepace+controller search in all the available resources. This resulted in an Dispatcher has detected a cyclic routing causing stability problems error.

Thanks in advance for any help!

Original code:


<?php

use Phalcon\Acl;
use Phalcon\Acl\Role;
use Phalcon\Acl\Adapter\Memory as AclList;
use Phalcon\Acl\Resource;
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Dispatcher;

class SecurityPlugin extends Plugin
{

    /**
     * Returns an existing or new access control list
     *
     * @returns AclList
     */
    public function getAcl()
    {
        if (!isset($this->persistent->acl)) {
            $acl = new AclList();
            $acl->setDefaultAction(Acl::DENY);

            // Register roles
            $roles = [
        'admins' => new Role(
          'admins',
          'Website administrators'
        ),
                'users'  => new Role(
                    'users',
                    'Member privileges, granted after sign in.'
                ),
                'guests' => new Role(
                    'guests',
                    'Anyone browsing the site who is not signed in is considered to be a "Guest".'
                )
            ];
            foreach ($roles as $role) {
                $acl->addRole($role);
            }

            //Private area resources
            $privateResources = array(
                'accountdashboard'    => array('*'),
                'licensemanager'    => array('*'),
                'profile'    => array('*'),
                'tickets'    => array('*')
            );

      $privateResourcesAdmin = array(
                'admindashboard'     =>  array('*'),
                'tickets'     => array('*'),
                'licensemanager' => array('*')
            );

      //Public area resources
            $publicResources = array(
                //Public section
                'index'       => array('*'),
                'register'    => array('*'),
                'errors'      => array('show401', 'show404', 'show500'),
                'register'    => array('*'),
                'login'       => array('*'),
                'logout'            => array('*'),
                'buy'                   => array('*'),
                'license'           => array('*'),
                //API
                'auth'                          => array('*'),
                'user'                          => array('*'),
                'courses'                       => array('*'),
                'announcements'         => array('*'),
            );

            foreach ($privateResources as $resource => $actions) {
                $acl->addResource(new Resource($resource), $actions);
            }

      foreach ($privateResourcesAdmin as $resource => $actions) {
        $acl->addResource(new Resource($resource), $actions);
      }

            foreach ($publicResources as $resource => $actions) {
                $acl->addResource(new Resource($resource), $actions);
            }

            //Grant access to public areas to users, admins and guests
            foreach ($roles as $role) {
                foreach ($publicResources as $resource => $actions) {
                    foreach ($actions as $action){
                        $acl->allow($role->getName(), $resource, $action);
                    }
                }
            }

            //Grant access to private area to role Users
            foreach ($privateResources as $resource => $actions) {
                foreach ($actions as $action){
                    $acl->allow('users', $resource, $action);
                }
            }

      foreach ($privateResourcesAdmin as $resource => $actions) {
                foreach ($actions as $action){
                    $acl->allow('admins', $resource, $action);
                }
            }

            //The acl is stored in session, APC would be useful here too
            $this->persistent->acl = $acl;
        }
        return $this->persistent->acl;
    }

    /**
     * This action is executed before execute any action in the application
     *
     * @param Event $event
     * @param Dispatcher $dispatcher
     * @return bool
     */
    public function beforeDispatch(Event $event, Dispatcher $dispatcher){
        $auth = $this->session->has('auth');

        if (!$auth){
            $role = 'guests';
        } else {
      $authSession = $this->session->get("auth");
      if($authSession['account_type'] == 'admin'){
        $role = 'admins';
      } else {
        $role = 'users';
      }
        }

        $namespace = $dispatcher->getNamespaceName();
        $controller = $dispatcher->getControllerName();
        $action = $dispatcher->getActionName();
        $acl = $this->getAcl();

        if (!$acl->isResource($controller)) {
            $dispatcher->forward([
                'namespace' => 'App\\Controllers',
                'controller' => 'errors',
                'action'     => 'show404'
            ]);
            return false;
        }

        $allowed = $acl->isAllowed($role, $controller, $action);

        if (!$allowed) {
            if($namespace == 'App\\Controllers\\Admin'){
                $dispatcher->forward(array(
                    'namespace' => 'App\\Controllers',
                    'controller' => 'errors',
                    'action'     => 'show404'
                ));
            } else {
                $dispatcher->forward(array(
                    'namespace' => 'App\\Controllers',
                    'controller' => 'errors',
                    'action'     => 'show401'
                ));
            }
            return false;
        }
    }
}


145.0k
Accepted
answer
edited May '17

Well tbh i would just add resource as:

Admin:Tickes and Account:Tickets names, and just proper code in beforeDispatch

This error you have is most likely due to some forward loop. Not related to those namespaces thing. Test it with xdebug to find what exactly happens i gueess it's like - beforeDispatch -> forward -> beforeDispatch -> forward etc

Also i think it's better to put this code to beforeExecuteAction so you know that those actions exist.

Well at least i use it like this in multimodule app.



5.9k
edited May '17

Your : suggestion did the trick. I tried using a slash but that just wouldn't work.

I'm using beforeDispatch in conjunction with an not found plugin so it handles all the exceptions being thrown by beforeDispatch :)

Thank you very much for your help!

For future reference by other people, this is the code that solved it for me:


<?php

use Phalcon\Acl;
use Phalcon\Acl\Role;
use Phalcon\Acl\Adapter\Memory as AclList;
use Phalcon\Acl\Resource;
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Dispatcher;

class SecurityPlugin extends Plugin
{

    /**
     * Returns an existing or new access control list
     *
     * @returns AclList
     */
    public function getAcl()
    {
        if (!isset($this->persistent->acl)) {
            $acl = new AclList();
            $acl->setDefaultAction(Acl::DENY);

            // Register roles
            $roles = [
        'admins' => new Role(
          'admins',
          'Website administrators'
        ),
                'users'  => new Role(
                    'users',
                    'Member privileges, granted after sign in.'
                ),
                'guests' => new Role(
                    'guests',
                    'Anyone browsing the site who is not signed in is considered to be a "Guest".'
                )
            ];
            foreach ($roles as $role) {
                $acl->addRole($role);
            }

            //Private area resources
            $privateResources = array(
                'App\Controllers\Account:accountdashboard'    => array('*'),
                'App\Controllers\Account:licensemanager'    => array('*'),
                'App\Controllers\Account:profile'    => array('*'),
                'App\Controllers\Account:tickets'    => array('*')
            );

      $privateResourcesAdmin = array(
                'App\Controllers\Admin:admindashboard'     =>  array('*'),
                'App\Controllers\Admin:tickets'     => array('*'),
                'App\Controllers\Admin:licensemanager' => array('*')
            );

      //Public area resources
            $publicResources = array(
                //Public section
                'App\Controllers:index'       => array('*'),
                'App\Controllers:register'    => array('*'),
                'App\Controllers:errors'      => array('show401', 'show404', 'show500'),
                'App\Controllers:register'    => array('*'),
                'App\Controllers:login'       => array('*'),
                'App\Controllers:logout'            => array('*'),
                'App\Controllers:buy'                   => array('*'),
                'App\Controllers:license'           => array('*'),
                //API
                'App\Controllers\Api:auth'                          => array('*'),
                'App\Controllers\Api:user'                          => array('*')
            );

            foreach ($privateResources as $resource => $actions) {
                $acl->addResource(new Resource($resource), $actions);
            }

      foreach ($privateResourcesAdmin as $resource => $actions) {
        $acl->addResource(new Resource($resource), $actions);
      }

            foreach ($publicResources as $resource => $actions) {
                $acl->addResource(new Resource($resource), $actions);
            }

            //Grant access to public areas to users, admins and guests
            foreach ($roles as $role) {
                foreach ($publicResources as $resource => $actions) {
                    foreach ($actions as $action){
                        $acl->allow($role->getName(), $resource, $action);
                    }
                }
            }

            //Grant access to private area to role Users
            foreach ($privateResources as $resource => $actions) {
                foreach ($actions as $action){
                    $acl->allow('users', $resource, $action);
                }
            }

      foreach ($privateResourcesAdmin as $resource => $actions) {
                foreach ($actions as $action){
                    $acl->allow('admins', $resource, $action);
                }
            }

            //The acl is stored in session, APC would be useful here too
            $this->persistent->acl = $acl;
        }
        return $this->persistent->acl;
    }

    /**
     * This action is executed before execute any action in the application
     *
     * @param Event $event
     * @param Dispatcher $dispatcher
     * @return bool
     */
    public function beforeDispatch(Event $event, Dispatcher $dispatcher){
        $auth = $this->session->has('auth');

        if (!$auth){
            $role = 'guests';
        } else {
      $authSession = $this->session->get("auth");
      if($authSession['account_type'] == 'admin'){
        $role = 'admins';
      } else {
        $role = 'users';
      }
        }

        $namespace = $dispatcher->getNamespaceName();
        $controller = $dispatcher->getControllerName();
        $action = $dispatcher->getActionName();
        $acl = $this->getAcl();

        //Mind the ':' inbetween $namespace and $controller!
        if (!$acl->isResource($namespace . ":" . $controller)) {
            $dispatcher->forward([
                'namespace' => 'App\\Controllers',
                'controller' => 'errors',
                'action'     => 'show404'
            ]);
            return false;
        }

        //Mind the ':' inbetween $namespace and $controller!
        $allowed = $acl->isAllowed($role, $namespace . ":" . $controller, $action);

        if (!$allowed) {
            if($namespace == 'App\\Controllers\\Admin'){
                $dispatcher->forward(array(
                    'namespace' => 'App\\Controllers',
                    'controller' => 'errors',
                    'action'     => 'show404'
                ));
            } else {
                $dispatcher->forward(array(
                    'namespace' => 'App\\Controllers',
                    'controller' => 'errors',
                    'action'     => 'show401'
                ));
            }
            return false;
        }
    }
}

Well tbh i would just add resource as:

Admin:Tickes and Account:Tickets names, and just proper code in beforeDispatch

This error you have is most likely due to some forward loop. Not related to those namespaces thing. Test it with xdebug to find what exactly happens i gueess it's like - beforeDispatch -> forward -> beforeDispatch -> forward etc

Also i think it's better to put this code to beforeExecuteAction so you know that those actions exist.

Well at least i use it like this in multimodule app.