保护实体的某些属性不被修改

时间:2018-01-06 14:03:16

标签: php symfony orm crud symfony-forms

我有一个Symfony 3应用程序,它使用Doctrine ORM进行实体管理。目前,我正致力于启用CRUD支持。我已经发现我可以使用security voters来限制对实体或控制器的访问。例如,我将其配置为只有管理员可以创建,更新或删除A类实体的方式。

对于我的实体类型B的实例,我还想让各自所有者更新(不创建或删除),我设法轻松完成。但是,不应允许所有者修改所有实体的属性 - 只是其中一些属性。我如何用Symfony实现这一点?另外,我使用Form Bundle来创建和验证表单。

编辑:我添加了一些相关代码。控制器调用denyAccessUnlessGranted,触发选民。只是为了澄清,该代码已经正常工作。我的问题与我还没有的代码有关。

控制器:

public function editAction(Request $request, int $id) {

    $em = $this->getDoctrine()->getManager();

    $project = $em->getRepository(Project::class)->findOneBy(['id'=>$id]);

    $this->denyAccessUnlessGranted(ProjectVoter::EDIT, $project);

    $users = $em->getRepository(EntityUser::class)->findAll();
    $groups = $em->getRepository(Group::class)->findAll();
    $tags = $em->getRepository(Tag::class)->findAll();

    $form = $this->createForm(ProjectType::class, $project, [
        'possibleAdmins' => $users,
        'possibleRequiredGroups' => $groups,
        'possibleTags' => $tags,
    ]);

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {

        $project = $form->getData();
        $em->flush();

        return $this->redirectToRoute('projects_show', ['id'=>$project->getId()]);
    }

    return $this->render('project/editor.html.twig',
        ['project'=>$project, 'form'=>$form->createView()]);
}

选民:

protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {

    /** @var UserInterface $user */
    $user = $token->getUser();

    if (!$user instanceof UserInterface) {
        // the user must be logged in; if not, deny access
        return false;
    }
    else if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
        return true; // system-wide admins shall always have access
    }

    switch($attribute) {

        case self::SHOW:
            return  ($subject->isVisible() || $subject->getAdmins()->contains($user);

        case self::EDIT:
            return $subject->getAdmins()->contains($user);

        case self::REMOVE:
            return false;
    }

    return false;
}

2 个答案:

答案 0 :(得分:1)

据我所知,没有与个别属性特别相关的访问功能。当然,只要我发布此内容,其他人就会恰到好处。

您可能会考虑做的是定义两个编辑角色,EDIT_BY_ADMIN和EDIT_BY_OWNER。然后,您可以测试条件并选择要使用的表单类型。

$projectTypeClass = null;
if ($this->isGranted(ProjectVoter::EDIT_BY_ADMIN,$project)) {
    $projectTypeClass = ProjectAdminType::class);
}
elseif ($this->isGranted(ProjectVoter::EDIT_BY_OWNER,$project)) {
    $projectTypeClass = ProjectOwnerType::class);
}
if (!$projectTypeClass) {
    // throw access denied exception
}
$form = $this->createForm($projectTypeClass, $project, [

这应该可以解决问题。当然有很多变化。您可以坚持使用一种项目类型并在类型类中进行访问测试,但这需要表单侦听器。

如果您需要更多粒度,那么您可以改为添加一些EDIT_PROP1,EDIT_PROP2类型的角色。

当然,如果你真的进入它,那么你可以将一些访问代码移动到某种类型的数据库中。或者可以看一下那里的一些访问控制列表组件。

答案 1 :(得分:0)

我最终提出了这个解决方案:

我没有使用多个FormType,而是只使用了一个,最终根据选民的结果启用或禁用了属性的表单字段。为此,我定义了一个新的交换机案例,如Cerad建议的那样(在本答案中将其命名为ProjectVoter::MODIFY_PROTECTED_PROPERTY用于演示目的)并根据自己的喜好添加了业务逻辑。

我刚启用或禁用了表单字段,因为我实际上希望用户看到他/她无法编辑该属性。但是很可能很容易也不可能首先add这个领域,所以它不可见。

表单类型:

信息:$this->tokenStorage$this->accessDecisionManager分别是注入服务("security.token_storage""security.access.decision_manager")。

public function buildForm(FormBuilderInterface $builder, array $options) {

    $token = $options['token'] ?? $this->tokenStorage->getToken();

    $project = $builder->getData();

    $builder
        ->add('name')

        // ...

        ->add('protectedProperty', null, [
            'disabled' => !$this->accessDecisionManager->decide($token, [ProjectVoter::MODIFY_PROTECTED_PROPERTY], $project),
        ])
    ;
}

我还在其token函数中添加了一个名为configureOptions的表单类型的选项,默认为null,因此可以为任意用户而不是一个用户构建表单如果需要,目前已登录。