Symfony2 acl groups

Typically, I have the following business model:

There are users and groups. Each user belongs to only one group, and the number of groups is not determined in advance (as well as the number of users for most sites). There are also several different employment objects that may belong to the user.

Groups are not separate objects that must be controlled by the ACL itself, but they must influence how other objects are managed in the same way as unix groups.

There are 3 main roles: SUPERADMIN, ADMIN and USER.

  • SUPERADMIN can do anything with any object.
  • The USER can usually read / write his own entities (including his / her) and read entitites from his / her group.
  • ADMIN should have full control over within its group, but not from other groups. I do not understand how to apply ACL inheritance here (and whether it can be applied at all).

I am also interested in how to deny access to the ACL. Just as the user has read and write access to all of his fields except the username. The user should only read his username. That is, it is logical to provide read / write access to his own profile, but to prohibit write access to the system, and not directly determine read and write access to all its fields (except the login name).

+3
source share
2 answers

Ok, here it is. The code is not perfect at all, but it's better than nothing.

Voting service.

<?php
namespace Acme\AcmeBundle\Services\Security;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;

class GroupedConcernVoter implements VoterInterface {

    public function __construct(ContainerInterface $container)
    {   
        $this->container = $container;
        $rc = $this->container->getParameter('grouped_concern_voter.config');
        // some config normalization performed
        $this->rightsConfig = $rc;
    }   

    // even though supportsAttribute and supportsClass methods are required by interface,
    // services that I saw, leaves them empty and do not use them

    public function supportsAttribute($attribute)
    {   
        return in_array($attribute, array('OWNER', 'MASTER', 'OPERATOR', 'VIEW', 'EDIT', 'CREATE', 'DELETE', 'UNDELETE', 'DEPLOY'))
            // hacky way to support per-attribute edit and even view rights.
            or preg_match("/^(EDIT|VIEW)(_[A-Z]+)+$/", $attribute);
    }           

    public function supportsClass($object)
    {   
        $object = $object instanceof ObjectIdentity ? $object->getType() : $object;
        // all our business object, which should be manageable by that code have common basic class.
        // Actually it is a decorator over Propel objects with some php magic... nevermind.
        // If one wants similar solution, interface like IOwnableByUserAndGroup with
        // getUserId and getGroupId methods may be defined and used
        return is_subclass_of($object, "Acme\\AcmeBundle\\CommonBusinessObject");
    }       

    function vote(TokenInterface $token, $object, array $attributes)
    {   

        if (!$this->supportsClass($object)) {
            return self::ACCESS_ABSTAIN;
        }
        if ($object instanceof ObjectIdentity) $object = $object->getType();

        if (is_string($object)) {
            $scope = 'own';
            $entity = $object;
        } else {
            if ($object->getUserId() == $this->getUser()->getId()) {
                $scope = 'own';
            } else if ($object->getGroupId() == $this->getUser()->getGroupId()) {
                $scope = 'group';
            } else {
                $scope = 'others';
            }
            $entity = get_class($object);
        }

        $user = $token->getUser();
        $roles = $user->getRoles();
        $role = empty($roles) ? 'ROLE_USER' : $roles[0];

        $rights = $this->getRightsFor($role, $scope, $entity);
        if ($rights === null) return self::ACCESS_ABSTAIN;

        // some complicated logic for checking rights...
        foreach ($attributes as $attr) {
            $a = $attr;
            $field = '';
            if (preg_match("/^(EDIT|VIEW)((?:_[A-Z]+)+)$/", $attr, $m)) list(, $a, $field) = $m;
            if (!array_key_exists($a, $rights)) return self::ACCESS_DENIED;
            if ($rights[$a]) {
                if ($rights[$a] === true
                or  $field === '')
                    return self::ACCESS_GRANTED;
            }
            if (is_array($rights[$a])) {
                if ($field == '') return self::ACCESS_GRANTED;
                $rfield = ltrim(strtolower($field), '_');
                if (in_array($rfield, $rights[$a])) return self::ACCESS_GRANTED;
            }

            return self::ACCESS_DENIED;
        }
    }

    private function getRightsFor($role, $scope, $entity)
    {
        if (array_key_exists($entity, $this->rightsConfig)) {
            $rc = $this->rightsConfig[$entity];
        } else {
            $rc = $this->rightsConfig['global'];
        }
        $rc = $rc[$role][$scope];
        $ret = array();
        foreach($rc as $k => $v) {
            if (is_numeric($k)) $ret[$v] = true;
            else $ret[$k] = $v;
        }
        // hacky way to emulate cumulative rights like in ACL
        if (isset($ret['OWNER'])) $ret['MASTER'] = true;
        if (isset($ret['MASTER'])) $ret['OPERATOR'] = true;
        if (isset($ret['OPERATOR']))
            foreach(array('VIEW', 'EDIT', 'CREATE', 'DELETE', 'UNDELETE') as $r) $ret[$r] = true;
        return $ret;
    }

    private function getUser() {
        if (empty($this->user)) {
            // Not sure, how this shortcut works. This is a service (?) returning current authorized user.
            $this->user = $this->container->get('acme.user.shortcut');
        }
        return $this->user;
    }

}

config... , .

grouped_concern_voter.config:
    global:
        ROLE_SUPERADMIN:
            own: [MASTER]
            group: [MASTER]
            others: [MASTER]
        ROLE_ADMIN:
            own: [MASTER]
            group: [MASTER]
            others: []
        ROLE_USER:
            own: [VIEW, EDIT, CREATE]
            group: [VIEW]
            others: []
    "Acme\\AcmeBundle\\User":
        # rights for ROLE_SUPERADMIN are derived from 'global'
        ROLE_ADMIN:
            own:
                VIEW: [login, email, real_name, properties, group_id]
                EDIT: [login, password, email, real_name, properties]
                CREATE: true
            group:
                VIEW: [login, email, real_name, properties]
                EDIT: [login, password, email, real_name, properties]
            # rights for ROLE_ADMIN/others are derived from 'global'
        ROLE_USER:
            own:
                VIEW: [login, password, email, real_name, properties]
                EDIT: [password, email, real_name, properties]
            group: []
            # rights for ROLE_USER/others are derived from 'global'
    "Acme\\AcmeBundle\\Cake":
        # most rights are derived from global here.
        ROLE_ADMIN:
            others: [VIEW]
        ROLE_USER:
            own: [VIEW]
            others: [VIEW]

, , . - :

$cake = Acme\AcmeBundle\CakeFactory->produce('strawberry', '1.3kg');
$securityContext = $this->get('security.context');
if ($securityContext->isGranted('EAT', $cake)) {
    die ("The cake is a lie");
}
+2

, ROLE_GROUP_ ( ), roleecurityidentity

0

All Articles