Symfony 2:表单权限授权策略

时间:2015-12-01 18:56:19

标签: symfony access-control

我有一个包含五个不同实体的Symfony应用程序(它们并不重要)。

对于每个实体,注册用户必须具有NONE,READ,EDIT,DELETE权限。我要抓住的棘手部分是每个用户可以为每个实体拥有不同的权限;用户A可以编辑实体A,但只能查看实体B等。

现在,在每个用户的选项页面上,管理员应该能够看到每个表单的权限。单选按钮应显示每个表单的四个选项。类似的东西:

Entity A:  O NONE    O READ    X EDIT    O DELETE
Entity B:  O NONE    X READ    O EDIT    O DELETE
... 

我知道我的选择基本上是在创建某种类型的选民系统或访问控制列表之间。

首先,我刚开始列出我的UserType中系统中当前的所有角色:

    $builder
        ...
        ->add('roles', 'choice', array(
            'choices' => $this->roles,
            'choices_as_values' => true,
            'label' => 'Roles',
            'expanded' => true,
            'multiple' => true,
            'mapped' => true,
        ))
    ;

但我觉得从长远来看这并不会非常有效。无论哪种方式,这还会显示与特定实体(例如ROLE_USER,ROLE_ADMIN等)的访问控制无关的其他系统角色。

我没有寻找完整的解决方案或类似的东西,我只是很难开始并看到如何实现这一目标的全局。 (是的,我知道Symfony文档......有时候这些东西起初并没有什么意义。)

进度更新

我决定访问控制列表

首先,当创建新实体时,我使用Symfony文档中提到的标准ACL创建策略:

public function postAvrequestAction(Request $request){
        $entity = new AvRequest();

        $form = $this->get('form.factory')->createNamed('', new AvRequestType(), $entity);
        $form->handleRequest($request);

        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($entity);
            $em->flush();

            $serializer = $this->get('serializer');
            $serialized = $serializer->serialize($entity, 'json');


            // creating the ACL
            $aclProvider = $this->get('security.acl.provider');
            $objectIdentity = ObjectIdentity::fromDomainObject($entity);
            $acl = $aclProvider->createAcl($objectIdentity);

            // retrieving the security identity of the currently logged-in user
            $tokenStorage = $this->get('security.token_storage');
            $users = $em->getRepository('AppBundle:User')->findAll();

            //$tokenStorage->getToken()->getUser();
            foreach($users as $user){
              $securityIdentity = UserSecurityIdentity::fromAccount($user);

              // grant owner access based on owner's overall permissions for this type of entity
              $acl->insertObjectAce($securityIdentity, 0);
              $aclProvider->updateAcl($acl);
            }

            return new Response($serialized, 201);
        }

        return new JsonResponse(array(
            'errors' => $this->getFormErrors($form)
        ));
    } 

接下来,我创建了一个包含所有必要依赖项的服务,以更新每个实体的用户权限:

#services.yml
services:
    user_service:
      class: AppBundle\Resources\Services\UserService
      arguments: [ @doctrine.orm.entity_manager, @service_container, @security.authorization_checker, @security.acl.provider ]

该服务具有以下功能:

/**
 * ACLs grant user permission on every instance of each entity.
 * In order to edit permissions across all of these entites for each user,
 * first iterate over all entities. 
 * For each entity, update the permission for the specified user.
 *
 * @param  \AppBundle\Entity\User $user  The user object whose permissions should be updated
 * @param String $entity  The entity whose permissions should be updated (e.g. 'AppBundle:AvRequest')
 * @param int $permission  The bitmask value of the permission level (e.g. MaskBuilder::MASK_VIEW (=4))
 * 
 * @return null
 */
  public function editPermission(User $user, $entity, $permission){
    $allEntities = $this->em->getRepository($entity)->findAll();

    foreach($allEntities as $oneEntity){
      // locate the ACL
      $objectIdentity = ObjectIdentity::fromDomainObject($oneEntity);
      $acl = $this->aclProvider->findAcl($objectIdentity);

      // update user access
      $objectAces = $acl->getObjectAces();
      foreach($objectAces as $i => $ace) {
          $acl->updateObjectAce($i, $permission); 
      }
    }
  }  

此功能遍历实体的每个实例,并为指定用户提供相同的权限级别。

我还没想到的下一步是为实体上的用户设置主权限级别,如我的单选按钮所述。我需要能够转到用户的个人资料页面,查看用户对每个实体类型的权限的单选列表,提交单选按钮值,然后在保存时运行editPermission()函数。

1 个答案:

答案 0 :(得分:0)

您正在寻找Access Control Lists。用户或用户组很容易设置权限。

按用户添加访问级别:

$builder = new MaskBuilder();
$builder
    ->add('view')
    ->add('edit')
    ->add('delete')
    ->add('undelete')
;
$mask = $builder->get(); // int(29)

$identity = new UserSecurityIdentity('johannes', 'AppBundle\Entity\User');
$acl->insertObjectAce($identity, $mask);

按实体指定最低访问级别:

public function addCommentAction(Post $post)
{
    $comment = new Comment();

    // ... setup $form, and submit data

    if ($form->isValid()) {
        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($comment);
        $entityManager->flush();

        // creating the ACL
        $aclProvider = $this->get('security.acl.provider');
        $objectIdentity = ObjectIdentity::fromDomainObject($comment);
        $acl = $aclProvider->createAcl($objectIdentity);

        // retrieving the security identity of the currently logged-in user
        $tokenStorage = $this->get('security.token_storage');
        $user = $tokenStorage->getToken()->getUser();
        $securityIdentity = UserSecurityIdentity::fromAccount($user);

        // grant owner access
        $acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
        $aclProvider->updateAcl($acl);
    }
}
public function editCommentAction(Comment $comment)
{
    $authorizationChecker = $this->get('security.authorization_checker');

    // check for edit access
    if (false === $authorizationChecker->isGranted('EDIT', $comment)) {
        throw new AccessDeniedException();
    }

    // ... retrieve actual comment object, and do your editing here
}