Laravel自定义权限规则 – 门、策略或自定义代码

huangapple go评论70阅读模式
英文:

Laravel custom permission rules - gates, policies or custom code

问题

I have an API that is being gradually built up to include all of the important business logic and behaviour for my platform. I need to allow or disallow certain actions, but not based on the identity of the user trying to complete the action. I'm trying to identify the best pattern for implementing this logic.

All of my actions are performed by various service classes, that live in the app/Service directory. For example - EventService class is responsible for all actions that relate to an event record. A use case here might be that as part of an API call to cancelEvent(Event $event) there must first be a check to make sure no one has registered onto the event - if they have, this is disallowed.

What would be the best solution here? It seems to me like Gates and Policies are more about authorizing particular users to see if they may perform the requested action. Should I build the 'checks and balances' right into the service class, and prevent actions there? Or is it okay to create policies (that I can then reuse elsewhere) that don't care about the user record, and perhaps check these at the controller level instead? Or is there another pattern that I haven't thought of? I have seen people write this into eloquent models too, but that doesn't really feel like the right place to me.

Important here is also the ability to return informative responses in the event of denial. For example, "This event cannot be canceled because there are 2 users registered". This would be returned to the API caller to be fed back to the end user.

Is there a 'Laravel way' of doing this?

英文:

I have an API that is being gradually built up to include all of the important business logic and behaviour for my platform. I need to allow or disallow certain actions, but not based on the identity of the user trying to complete the action. I'm trying to identify the best pattern for implementing this logic.

All of my actions are performed by various service classes, that live in the app/Service directory. For example - EventService class is responsible for all actions that relate to an event record. A use case here might be that as part of an API call to cancelEvent(Event $event) there must first be a check to make sure no-oine has registered onto the event - if they have, this is disallowed.

What would be the best solution here? It seems to me like Gates and Policies are more about authorising particular users to see if they may perform the requested action. Should I build the 'checks and balances' right into the service class, and prevent actions there? Or is it ok to create policies (that I can then reuse elsewehere) that don't care about the user record, and perhaps check these at controller level instead? Or is there another pattern that I haven't thought of? I have seen people write this into eloquent models too, but that doesn't really feel like the right place to me.

Important here is also the ability to return informative responses in the event of denial. For example "This event cannot be cancelled because there are 2 users registered". This would be returned to the api caller to be fed back to the end user.

Is there a 'Laravel way' of doing this?

答案1

得分: 1

Policies could work as the foundation for what you are describing and would be a Laravel Way (given they say as much in the Authorization documentation). Though they are invoked with a user context, that doesn't need to be used inside the policy. Some benefits of policies that I think are valuable to your case:

  • 你可以使用单一的定义来授权特定操作。
  • Laravel 提供了一些便捷的方法来获取与用户和模型相关的策略。
  • 你可以向策略查询提供额外的上下文。

I think this third point may be how you would get your details on the reason for a policy failure. Though I am very reluctant to pass-by-reference, this may be worth an exception in order to use the policy framework and get the reason context.

Here's a sketch:

some.blade.php

@can('doSomething', $someModel)
  You can do the thing.
@else
  Sorry, you cannot do the thing.
@endcan

Http/Controllers/SomeController.php

public function doAction() {
    $reason = null;
    if (auth()->user()->can('doSomething', $someModel, $reason)) {
        return 'Done!';
    } else {
        return 'Not done because ' . $reason;
    }
}
class SomeModelPolicy
{
    use HandlesAuthorization;

    public function doSomething(User $user, SomeModel $someModel, string &$reason = null): bool
    {
        if ($someModel->hasSomeState()) {
            return true;
        } else {
            $reason = 'SomeModel does not have some state';
            return false; 
        }
    }
}

One thing to consider is that you still need definitions of those rules you are validating, like my $someModel->hasSomeState(), and you probably want to encapsulate those on a model in order to have a single, clear business rule being invoked (though an action could require checking multiple rules). This is kind of like the idea of "skinny controllers, fat models". Skinny policies help in the same way.

英文:

Policies could work as the foundation for what you are describing and would be a Laravel Way (given they say as much in the Authorization documentation). Though they are invoked with a user context, that doesn't need to be used inside the policy. Some benefits of policies that I think are valuable to your case:

  • You have a single definition that can be used for authorizing a given action.
  • Laravel gives you some convenient ways to get to those policies given a user and model.
  • You can provide additional context to the policy query.

I think this third point may be how you would get your details on the reason for a policy failure. Though I am very reluctant to pass-by-reference, this may be worth an exception in order to use the policy framework and get the reason context.

Here's a sketch:

some.blade.php

@can('doSomething', $someModel)
  You can do the thing.
@else
  Sorry, you cannot do the thing.
@endcan

Http/Controllers/SomeController.php

public function doAction() {
    $reason = null;
    if (auth()->user()->can('doSomething', $someModel, $reason)) {
        return 'Done!';
    } else {
        return 'Not done because ' . $reason
    }
}
class SomeModelPolicy
{
    use HandlesAuthorization;

    public function doSomething(User $user, SomeModel $someModel, string &$reason = null): bool
    {
        if ($someModel->hasSomeState()) {
            return true;
        } else {
            $reason = 'SomeModel does not have some state';
            return false; 
        }
    }
}

One thing to consider is that you still need definitions of those rules you are validating, like my $someModel->hasSomeState(), and you probably want to encapsulate those on a model in order to have a single, clear business rule being invoked (though an action could require checking multiple rules). This is kind of like the idea of "skinny controllers, fat models". Skinny policies help in the same way.

huangapple
  • 本文由 发表于 2023年6月12日 22:36:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76457714.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定