Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
453 views
in Technique[技术] by (71.8m points)

symfony - Symfony2 ACL combined with another criteria

I'm wondering if anyone knows of an elegant way to achieve this using the Symfony2 ACL system.

I have a Comment entity (my domain object) which needs to be editable by ROLE_USER but this is only allowed within 5 minutes of the comment being posted - otherwise the comment can only be edited by ROLE_ADMIN.

Making it so that it can only be edited by ROLE_USER and ROLE_ADMIN is simple, just make a RoleSecurityIdentity for each.

Now my problem occurs when I want to incorporate the time factor for ROLE_USER. My first problem is that it needs information from the domain object, not just the ACL table but I think this is solvable by making a custom ObjectIdentity class which can also hold the time that the Comment was posted.

Now for the hard part

I think I need to create a custom PermissionGrantingStrategy that knows to also look at the creation time. This has to be loaded when a Comment type is being checked, but I don't know how to get it to load. Does anyone know if there's some kind of factory through which this sort of thing can be configured? So that if an entity has a specific PermissionGrantingStrategy associated with it then it gets used otherwise the default is used?

I know this is a bit of a long one, many thanks if anyone knows how to achieve this as the ACL documentation seems a tad sparse at the moment. My fallback solution is to simply make some kind of service to check if a Comment can be edited and not bother with ACL at all.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I'm posting this solution so that others can see my final code but here are the pitfalls I found when implementing a Voter as Problematic suggested.

supportsAttribute: It appears that when you call the isGranted method on the SecurityContext that it doesn't actually check this method before delegating a vote call to a VoterInterface so inside your vote method you actually have to check the attributes yourself.

supportsClass: In problematic's answer above it seemed like this method could be a key for a Factory based selection of which VoterInterfaces can vote but actually the symfony2 documentation reads:

The supportsClass() method is used to check if the voter supports the current user token class.

Therefore it actually seems to pertain to whether or not the Voter supports the token type. To make matters worse the PHP Doc seems vague:

Checks if the voter supports the given class.

At any rate the main problem is that this method is never checked by the SecurityContext before delegating the call to the vote method of any voter - even if this method is hardcoded to return false vote will still be called!

So basically the moral of the story appeared to be: check the $attributes and $object coming in on the vote method manually.

My Code:

services.yml

parameters:
    comment_voter.class: AcmeBundleCommentBundleSecurityAuthorizationVoterCommentVoter

services:
    comment_voter:
        class: %comment_voter.class%
        arguments:  [@service_container]
        public: false
        tags:
          - { name: security.voter }

and the voter class:

<?php

namespace AcmeBundleCommentBundleSecurityAuthorizationVoter;

use SymfonyComponentSecurityCoreAuthorizationVoterVoterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;

use AcmeBundleCommentBundleEntityComment;
use SymfonyComponentSecurityCoreUserUserInterface;

/**
 * A class to check editing privileges for Comments.
 */
class CommentVoter implements VoterInterface {

    const AUTHOR_EDIT_TIME_LIMIT    = 300;

    private $container;

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

    public function supportsAttribute($attribute) {
        return $attribute === 'EDIT';
    }

    public function supportsClass($class) {
        return true;
    }

    /**
     * Checks whether or not the current user can edit a comment.
     * 
     * Users with the role ROLE_COMMENT_MODERATOR may always edit.
     * A comment's author can only edit within 5 minutes of it being posted.
     * 
     * {@inheritdoc}
     */
    public function vote(TokenInterface $token, $object, array $attributes) {
        if ( !($object instanceof Comment) ) {
            return VoterInterface::ACCESS_ABSTAIN;
        }

        // Only supports 'EDIT' for now.
        if ( !$this->supportsAttribute($attributes[0]) ) {
            return VoterInterface::ACCESS_ABSTAIN;
        }

        $user = $token->getUser();
        if ( !($user instanceof UserInterface) ) {
            return VoterInterface::ACCESS_DENIED;
        }

        // Is the token a comment moderator?
        if ( $this->container->get('security.context')->isGranted('ROLE_COMMENT_MODERATOR') ) {
            return VoterInterface::ACCESS_GRANTED;
        }

        // Is the token the author of the post and within the edit window.
        $originalRevision = $object->getOriginalRevision();
        if ( $originalRevision->getAuthor()->equals($user) ) {
            if ( 
                (time() - $originalRevision->getCreationDate()->getTimestamp())
                <= self::AUTHOR_EDIT_TIME_LIMIT
            ) {
                return VoterInterface::ACCESS_GRANTED;
            }
        }

        return VoterInterface::ACCESS_DENIED;
    }

}

and finally template:

{% if is_granted('EDIT', comment) %}<a href="#">Edit</a>{% endif %}

I hope this helps someone else in the future and a big thanks to Problematic for pointing me in the direction of Voters.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...