Cake4: Authorisation for namespaces/prefixes and userroles

Is there a way to create Authorization policies for whole namespaces?
I.e.: I have a controller namespace App\Controller\Customer.

At the moment I check at the beginning of the controller if the user (based on its userrole_id) is allowed to access this namespace.

  public function beforeFilter(\Cake\Event\EventInterface $event)
  {
      parent::beforeFilter($event);
      $userrole_id = $this->request->getAttribute('identity')->userrole_id;
      if ($userrole_id <> 1) { //userrole with "id" other than "1" is not allowed to access "namespace App\Controller\Customer"
        return $this->redirect(['prefix'=>false,'controller'=>'pages','action'=>'display','anyerrorpage']);
      }
  }

I have to do this for every controller and every namespace.
Is there a better way to do this?
I read the chapter authorization in cakebook, but as far as I understood the authorization is always based on individual users and not on a group of users that have the same userrole_id

Maybe someone can give me a link for further reading on this?

If you are using the Authorization Plugin you could look at the RequestAuthtorizationMiddleware

Following the basic setup you should end up with an App\Policy\RequestPolicy class that will allow or eject users very early in the request; before the controller is initialized.

Reading your question again I wonder if my answer gets to the heart of your issue.

I use very detailed User Role policies in an application. I have 5 different roles and they apply in very specific ways to each Controller’s actions.

My system is made up of 4 parts:

  • A set of constants that name every role and sets of roles
  • A method on the identity that, when given the value of one of the constants, reports whether the identity matches the role or is in the set
  • A policy that routes to a concrete rule for the requested contoller
  • Concrete rules that decide which roles can access which actions for a controller.

The concrete controller-access rules look like this:

Example Policy Code for a single controller
    /**
     * WarehouseWorkersController action access
     *
     * @param $identity UserIdentity
     * @param $action string
     * @return bool
     */
    public function warehouseWorkers($identity, $action)
    {
        if ($identity->roleIs(RoleCon::SYSTEM_ADMIN)) { return true; }
        switch ($action) {
            case 'index':
            case 'view':
                // Warehouse admin or staff
                $result = $identity->roleIs(RoleCon::WORKERS);
                break;
            case 'edit':
                $result = $identity->roleIs(RoleCon::WAREHOUSE_WORKER);
                break;
            case 'add':
            case 'delete':
                // Warehouse admin
                $result = $identity->roleIs(RoleCon::WAREHOUSE_ADMIN);
                break;
            default:
                $result = false;
                break;
        }
        return $result;
    }

More about the `roleIs()` syntax in the policy code

I use an roleIs() method on my Identity object which means I had to use a more advanced setup for Authentication/Authorization plugins to substitute my own Identity class.

The method looks like this:

    /**
     * Is the role of this person the $role or among the [$role]?
     *
     * @param string|array $role
     * @return bool
     */
    public function roleIs($role)
    {
        if (is_array($role)) {
            return in_array($this->getRole(), $role);
        }
        return $role === $this->getRole();
    }

With that in place I make constants for all my roles and groupings of roles. That’s what RoleCon is, a Class that holds many, many constants:

<?php

namespace App\Constants;

class RoleCon
{
    const SYSTEM_ADMIN = 'admin';
    const WAREHOUSE_ADMIN = 'warehouseAdmin';
    const WAREHOUSE_STAFF = 'warehouseStaff';
    const TENANT_ADMIN = 'tenantAdmin';
    const TENANT_STAFF = 'tenantStaff';
    const WAREHOUSE_ROBOT = 'warehouseRobot';
    const TENANT_ROBOT = 'tenantRobot';

    const WAREHOUSE_WORKER = [
        self::WAREHOUSE_ADMIN => self::WAREHOUSE_ADMIN,
        self::WAREHOUSE_STAFF => self::WAREHOUSE_STAFF
    ];

    const WORKERS = [
        self::TENANT_ADMIN => self::TENANT_ADMIN,
        self::TENANT_STAFF => self::TENANT_STAFF,
        self::WAREHOUSE_ADMIN => self::WAREHOUSE_ADMIN,
        self::WAREHOUSE_STAFF => self::WAREHOUSE_STAFF
   ];

}

One request policy serves my entire app. It is this policy that decided which controller and action have been requested and it makes the final call to my methods (see example policy code above.

The master policy (router)
    /**
     * Method to check if the request can be accessed
     *
     * @param IdentityInterface|null $identity Identity
     * @param ServerRequest $request Server Request
     * @return bool|ResultInterface
     */
    public function canAccess($identity, ServerRequest $request)
    {
        /* Ignore plugin requests */
        if($request->getParam('plugin') == 'DebugKit') { return true; }

        $method = lcfirst($request->getAttribute('params')['controller']);

        /* Not logged-in has very few options (null identity throw errors otherwise) */
        if(is_null($identity) && !in_array($method, ['pages', 'users'])) {
            throw new UnauthenticatedException();
        }

        /* logged in users can check it all */
        return $this->$method($identity, $request->getAttribute('params')['action']);
    }
1 Like

Thanks a lot for this input. I will read it carefully the next days, but from the first look, this is what imwas asking for.
Thanks again

One detail that may or may not be of interest:

My answer implies that the concrete rules (the switch statements) are methods of the RequestPolicy class. I actually have them included as a Trait.

I did this so I could reuse the entire set of rules in Helper classes that I use to generate links on the pages.

My link helper methods get the current Identity and the controller/method for the link and check the rule to see if the link will be allowed. The helper returns the link or null. This simplifies my templates and elements a lot. I’m able to echo every link that will ever be valid on a page and the helpers will figure out which ones to actually show for any given user.

This is, of course, a simplification of my actual code. But might give you some useful ideas for your project.