cakephp / authorization

PSR7 Middleware for authorization
MIT License
76 stars 47 forks source link

Policy for Modelless controller #142

Closed enviniom closed 4 years ago

enviniom commented 4 years ago

Hi, I've created a modelless Controller named Principal, and it works like pages, but I don't know how to make policies for actions in this controller. I baked a PrincipalPolicy, and created the method

public function configuration(IdentityInterface $user)
{
    return $user->rol_id === 1;
}

In action "configuration" in PrincipalController I called $this->Authorization->authorize(); but, throws an error about "Too few arguments to function", and I used $this->Authorization->authorize('configuration'); and throws and error about "Policy for string has not been defined".

I don know how to implementa policy for a modelles controller.

ndm2 commented 4 years ago

The first argument of AuthorizationComponent::authorize() must be a resource for which a policy can be obtained, the second argument is an optional action that should be checked for, it defaults to the current controller action.

See the example in the Cookbook, the resource is an entity and so it will use an entity policy: https://book.cakephp.org/authorization/2/en/component.html#checking-authorization.

So you first need to assess what resource you actually want to protect, the whole controller action, or maybe some other resource that the controller action is accessing/modifying, which could potentially happen in other places too.

If you want to protect the whole controller/action, you might want to look into request policies: https://book.cakephp.org/authorization/2/en/request-authorization-middleware.html

enviniom commented 4 years ago

I really do not understand well how to implement the Authorization middleware, it is very complex for me, I think I will do something similar to what was in the Auth component, to check the access to resources from an action in the controllers.

markstory commented 4 years ago

@enviniom If you don't have resources to check against you likely want the request authorization middleware that NDM linked to. It is also possible that authorization middleware is not a good fit for the problem you're trying to solve. If you can do what you need with a controller method I would do just that. It is often best to start small and grow into more complex solutions as you need them.

viraj-khatavkar commented 3 years ago

@markstory a couple of follow-up questions

markstory commented 3 years ago

is there a way to call the legacy isAuthorized methods on controllers if we are using only the RequestAuthorizationMiddleware for authorization?

No. Middleware has no access to the controller.

Can we have two types of authorization policies stacked on one another? for example, suppose I want to first check a general request policy and then a ORM policy - is that possible?

Sure, you can write a middleware to do what ever you'd like :smile: The middleware that come in this plugin can't do what you're looking for.

viraj-khatavkar commented 3 years ago

@markstory haha.. writing my own "authorization" middleware you say :P - thanks for the answers! Our codebase has all its RBAC authorization logic spread across controllers in the isAuthorized methods. It's gonna be a big pain to refactor to use the new Authorization plugin with orm resolver.

markstory commented 3 years ago

It's gonna be a big pain to refactor to use the new Authorization plugin with orm resolver.

You don't have to use the ORM resolver. You can create a policy that uses your controllers instead. Your policy checks could accept the controller and then call its isAuthorized() method.

viraj-khatavkar commented 3 years ago

You don't have to use the ORM resolver. You can create a policy that uses your controllers instead. Your policy checks could accept the controller and then call its isAuthorized() method.

Umm, stupid question: how can we use the controller in the policy? I was trying to figure that out and couldn't get anywhere. I didn't see anything in the documentation regarding that in the RequestPolicy for RequestAuthorizationMiddleware? The $request->getParam('controller') only gives the name of controller and not the instance of the controller to access it

markstory commented 3 years ago

You can pass the controller into the policy.

// In a controller method
$this->Authorization->authorize($this);

You'll also need a policy class that calls the controller's isAuthorized() method, and either use the MapResolver to connect that policy class to each controller that needs it, or a custom PolicyResolver that returns your policy for any controllers.

Something like:

namespace App\Policy;

class ControllerHookPolicy
{
  public function __call($user, $controller)
  {
    return $controller->isAuthorized($user);
  }
}

class ControllerResolver implements ResolverInterface
{
  public function getPolicy($resource)
  {
    if ($resource instanceof Controller) {
      return new ControllerHookPolicy();
    }
    throw new MissingPolicyException([$resource::class]);
  }
}

If you have a mix of model and controller policies you can use the ResolverCollection to join the various policy resolvers together.

viraj-khatavkar commented 3 years ago

This helps a lot, thanks! Will get it to work. Yeah I read about resolver collection but it didn't work with map resolver for RequestPolicy and OrmResolver as a backup as RequestPolicy for RequestAuthorizationMiddleware needs to return either true or false.

mfrascati commented 2 years ago

@markstory Your example should be mentioned in the docs, it was very useful to understand how to create a custom resolver! Thank you!

Note for future readers: $resource::class should be replaced with get_class($resource) or php will throw an error "Cannot use ::class with dynamic class name"

viraj-khatavkar commented 2 years ago

@mfrascati do you use the above implementation for keeping the legacy isAuthorized methods? If yes, where do you keep the $this->Authorization->authorize($this); for all controllers? beforeFilter sounds good/

mfrascati commented 2 years ago

@mfrascati do you use the above implementation for keeping the legacy isAuthorized methods? If yes, where do you keep the $this->Authorization->authorize($this); for all controllers? beforeFilter sounds good/

Actually I used it to replace the controller logic and centralized the controller/action check in a single RequestPolicy, so when the controller function is called I'm sure the role is allowed to call it. I just have 2 roles (admin access everything, user with limited actions) and I chose to keep all user accessible actions under control in a single file.

markstory commented 2 years ago

Your example should be mentioned in the docs, it was very useful to understand how to create a custom resolver! Thank you!

You're welcome :smile: Where would you hope to find this kind of information in the docs?

mfrascati commented 2 years ago

You're welcome 😄 Where would you hope to find this kind of information in the docs?

Maybe it could be mentioned after the "Creating a resolver" section? It seems a useful example to help people keep using their isAuthorized methods on controllers without rewriting the whole logic!