保护实体的某些属性不被修改
Protecting some properties of an entity from modification
我有一个使用 Doctrine ORM 进行实体管理的 Symfony 3 应用程序。目前,我正在致力于启用 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;
}
据我所知,没有专门针对个别属性的访问功能。当然,只要我 post 这个,其他人就会带着那个过来。
您可能会考虑定义两个编辑角色,EDIT_BY_ADMIN 和 EDIT_BY_OWNER。然后您可以测试条件和 select 要使用的表单类型。
$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, [
这应该可以解决问题。当然有很多变化。您可以坚持使用一种项目类型并在类型 class 内进行访问测试,尽管这需要表单侦听器。
如果您需要更细化,那么您可以添加一些 EDIT_PROP1、EDIT_PROP2 类型的角色。
当然,如果您真的喜欢它,那么您可以将一些访问代码移到某种数据库中。或者看看那里的一些访问控制列表组件。
最后我想到了这个解决方案:
我没有使用多个 FormType
,而是只使用了一个,最终根据选民的结果启用或禁用 [=38=] 的表单字段。为此,我按照 Cerad 的建议定义了一个新的开关案例(出于演示目的,在此答案中命名为 ProjectVoter::MODIFY_PROTECTED_PROPERTY
)并根据我的喜好添加了业务逻辑。
我刚刚启用或禁用了表单字段,因为我实际上希望用户看到 he/she 无法编辑 属性。但是很可能很容易也没有 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),
])
;
}
我还在其 configureOptions
函数中为名为 token
的表单类型添加了一个选项,默认为 null
,以便可以为任意用户构建表单而不是当前登录的那个,如果需要的话。
我有一个使用 Doctrine ORM 进行实体管理的 Symfony 3 应用程序。目前,我正在致力于启用 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;
}
据我所知,没有专门针对个别属性的访问功能。当然,只要我 post 这个,其他人就会带着那个过来。
您可能会考虑定义两个编辑角色,EDIT_BY_ADMIN 和 EDIT_BY_OWNER。然后您可以测试条件和 select 要使用的表单类型。
$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, [
这应该可以解决问题。当然有很多变化。您可以坚持使用一种项目类型并在类型 class 内进行访问测试,尽管这需要表单侦听器。
如果您需要更细化,那么您可以添加一些 EDIT_PROP1、EDIT_PROP2 类型的角色。
当然,如果您真的喜欢它,那么您可以将一些访问代码移到某种数据库中。或者看看那里的一些访问控制列表组件。
最后我想到了这个解决方案:
我没有使用多个 FormType
,而是只使用了一个,最终根据选民的结果启用或禁用 [=38=] 的表单字段。为此,我按照 Cerad 的建议定义了一个新的开关案例(出于演示目的,在此答案中命名为 ProjectVoter::MODIFY_PROTECTED_PROPERTY
)并根据我的喜好添加了业务逻辑。
我刚刚启用或禁用了表单字段,因为我实际上希望用户看到 he/she 无法编辑 属性。但是很可能很容易也没有 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),
])
;
}
我还在其 configureOptions
函数中为名为 token
的表单类型添加了一个选项,默认为 null
,以便可以为任意用户构建表单而不是当前登录的那个,如果需要的话。