简体   繁体   English

如何在不覆盖 checkAccess 和 hasAccess 方法的情况下控制对操作的访问

[英]How to control access to actions without override checkAccess and hasAccess methods

From version 3.102.0 of SonataAdminBundle a lot of methods in AbstractAdmin are marked as final.从 SonataAdminBundle 的 3.102.0 版本开始,AbstractAdmin 中的很多方法都被标记为 final。 The most important (in my opinion) "checkAccess" and "hasAccess" methods are also marked as "final" and cannot be overwritten in Admin classes any more to handle access to actions on my own.最重要的(在我看来)“checkAccess”和“hasAccess”方法也被标记为“final”并且不能再在 Admin 类中被覆盖来处理对我自己的操作的访问。 How to handle cases when I want restrict access to some actions based on state of object?当我想根据对象的状态限制对某些操作的访问时,如何处理?

For example I have "Task" entity:例如我有“任务”实体:

<?php
   class Task
   {
      private ?int $id = null;
      private ?string $name = null;
      private bool $closed = false;

      public function getId(): ?int
      {
         return $this->id;
      }

      public function getName(): ?string
      {
         return $this->name;
      }

      public function setName(string $name): self
      {
         $this->name = $name;
         return $this;
      }

      public function isClosed(): bool
      {
         return $this->closed;
      }

      public function setClosed(bool $closed): self
      {
         $this->closed = $closed;
         return $this;
      }
   }

I want to denied access to edit action if Task object is closed.如果 Task 对象关闭,我想拒绝访问编辑操作。

Before version 3.102, doing this was simple:在 3.102 版本之前,这样做很简单:

<?php
   class TaskAdmin extends AbstractAdmin
   {
      protected function checkAccess($action, $object = null)
      {
         if ('edit' === $action && $object && $object->isClosed()) {
            throw new AccessDenied('Access Denied to action edit because task is closed.');
         }

         parent::checkAccess($action, $object);
      }

      protected function hasAccess($action, $object = null)
      {
         if ('edit' === $action && $object && $object->isClosed()) {
            return false;
         }

         return parent::hasAccess($action, $object);
      }
   }

Of course now I can't override these methods.当然,现在我无法覆盖这些方法。

I thought about Voters but in this case is not perfect, because Sonata checks first if user have "Super admin role/roles".我考虑过选民,但在这种情况下并不完美,因为奏鸣曲首先检查用户是否具有“超级管理员角色/角色”。 If not, then next is checked specific role (for example ROLE_ADMIN_TASK_TASK_EDIT in my case).如果没有,则接下来检查特定角色(例如在我的情况下 ROLE_ADMIN_TASK_TASK_EDIT)。 So, user with super admin role will still be able to edit Task object even though it is closed.因此,即使 Task 对象已关闭,具有超级管理员角色的用户仍然可以编辑它。

Another idea was create Controller for this TaskAdmin and override "preEdit" method and check there if object is closed or not and denied access.另一个想法是为此 TaskAdmin 创建控制器并覆盖“preEdit”方法并检查对象是否已关闭并拒绝访问。 This solution is also not perfect, because in many places in templates is fired "hasAccess" method to checks if some parts of UI should be visible or not (for example edit button), so the user will still see the edit button but will not be able to enter the edit action (prevents on controller level).这个解决方案也不是完美的,因为在模板的很多地方会触发“hasAccess”方法来检查UI的某些部分是否应该可见(例如编辑按钮),所以用户仍然会看到编辑按钮但不会能够进入编辑操作(在控制器级别阻止)。

It would be perfect if there were methods for example "preCheckAccess" and "preHasAccess" that could be overwritten in Admin classes (if "checkAccess" and "hasAccess" methods must remain marked as final).如果有可以在 Admin 类中覆盖的方法,例如“preCheckAccess”和“preHasAccess”(如果“checkAccess”和“hasAccess”方法必须保持标记为 final),那将是完美的。

Any other ideas?还有其他想法吗? Thanks for yours help.谢谢你的帮助。

You can use a voter您可以使用选民

in which you check if the task is closed在其中检查任务是否已关闭

switch ($attribute) {
    case self::EDIT:
        return !$subject->isClosed();
}

if you have multiple voters (yours and/or Sonata ones) take a look at the access decision strategy如果您有多个投票者(您的和/或 Sonata 投票者),请查看访问决策策略

for example you can set the strategy to unanimous which will make the user have access only if sonata AND your voter grant access例如,您可以将策略设置为unanimous ,这将使用户只有在奏鸣曲您的选民授予访问权限时才能访问

another example, you can define the whole logic in your voter (checking also user roles) and setting the strategy to priority and then you can give your voter a high priority so your voter will always be executed instead of Sonata ones另一个例子,你可以在你的选民中定义整个逻辑(也检查用户角色)并将策略设置为priority ,然后你可以给你的选民一个高优先级,这样你的选民将始终被执行而不是奏鸣曲

finally read all the options and do the most suitable solution for you because we don't know all your code context最后阅读所有选项并为您做最合适的解决方案,因为我们不知道您所有的代码上下文

The solution is to create and use custom SecurityHandler service for specific Admin class.解决方案是为特定的 Admin 类创建和使用自定义 SecurityHandler 服务。

To solve my case, follow these steps:要解决我的情况,请按照下列步骤操作:

  1. Create custom SecurityHandler class:创建自定义 SecurityHandler 类:
// src/Security/Handler/TaskSecurityHandler.php
<?php
   
namespace App\Security\Handler;

use App\Entity\Task;
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;

class TaskSecurityHandler extends SecurityHandlerInterface
{
    private SecurityHandlerInterface $defaultSecurityHandler;

    public function __construct(SecurityHandlerInterface $defaultSecurityHandler)
    {
        $this->defaultSecurityHandler = $defaultSecurityHandler;
    }

    public function isGranted(AdminInterface $admin, $attributes, ?object $object = null): bool
    {
        // Handle custom access logic
        if (is_string($attributes) && 'EDIT' === $attributes && $object instanceof Task && $object->isClosed()) {
            return false;
        }

        // Leave default access logic
        return $this->defaultSecurityHandler->isGranted($admin, $attributes, $object);
    }

    public function getBaseRole(AdminInterface $admin): string
    {
        return '';
    }

    public function buildSecurityInformation(AdminInterface $admin): array
    {
        return [];
    }

    public function createObjectSecurity(AdminInterface $admin, object $object): void
    {
    }

    public function deleteObjectSecurity(AdminInterface $admin, object $object): void
    {
    }
}
  1. Register custom SecurityHandler class in services.yaml and inject default SecurityHandler service:在 services.yaml 中注册自定义 SecurityHandler 类并注入默认的 SecurityHandler 服务:
# config/services.yaml

services:
    App\Security\Handler\TaskSecurityHandler:
        arguments:
            - '@sonata.admin.security.handler' #default SecurityHandler service configured in global configuration of SonataAdminBundle
  1. Use security_handler tag to point to your custom SecurityHandler service for specific Admin class:使用security_handler标记指向特定 Admin 类的自定义 SecurityHandler 服务:
# config/services.yaml

services:
    # ...
    app.admin.task:
        class: App\Admin\TaskAdmin
        arguments: [~, App\Entity\Task, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: Task, security_handler: App\Security\Handler\TaskSecurityHandler }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM