Componette

Componette

download-cloud-line composer require damejidlo/permissions

Downloads this Month Latest Stable Version

Motivation

Purpose of this library is to add User specific data to isAllowed evaluation. Assertion callback got IUser directly as first argument.

This solves biggest "problem" of native ACL in Nette such is:

	$callback = function (IUser $user, $queriedRole, $queriedResource) {
		return $user->getEntity()->getId() === $queriedResource->getEntity()->getCreatorId();
	};

	// god can destroy world, but only the one he created
	$authorizator->allow('god', 'world', 'destroy', $callback);

Another aspect of this library is separating Authorizator from Nette\Security\User as it's definitely not users responsibility to provide this functionality.

Disclaimer

This library is written to be as much as possible similar to Permission class in Nette. However evaluation of rules is written from scratch.

And therefore:

  • does not implement Nette\Security\IAuthorizator (it can't due to different isAllowed method API),
  • can be significantly slower (but is written nicely),
  • there is no guarantee that behaves 100% same way.

Installation

composer require damejidlo/permission

Configuration

AccessList and Neon

Example implementation of your own AccessList service.

class AccessList extends Authorizator
{
	/**
	 * @param string[][] $roles
	 */
	public function addRoles(array $roles)
	{
		foreach ($roles as $role => $parentRoles) {
			$this->addRole($role, $parentRoles);
		}
	}

	/**
	 * @param @param string[] $resources
	 */
	public function addResources(array $resources)
	{
		foreach ($resources as $resource) {
			$this->addResource($resource);
		}
	}

	/**
	 * @param string[][][] $directives
	 */
	public function addDirectives(array $directives)
	{
		foreach ($directives as $resource => $resourceDirectives) {
			foreach ($resourceDirectives as $privilege => $privilegeDirectives) {
				foreach ($privilegeDirectives as $roleIdentifier => $directiveType) {
					$this->createDirective($directiveType, $roleIdentifier, $resource, $privilege);
				}
			}
		}
	}

	public function someStuff()
	{
		$callback = function (IUser $user, $queriedRole, $queriedResource) {
			return $user->getEntity()->getId() === $queriedResource->getEntity()->getCreatorId();
		};

		// god can destroy world, but only the one he created
		$authorizator->allow('god', 'world', 'destroy', $callback);
	}
}

Then just add to your config.neon

parameters:
	acl:
		roles:
			writer: []
			reviewer: [writer]

		resources:
			- article

		directives:
			article:
				create:
					writer: allow
				publish:
					reviewer: allow

services:
	acl:
		class: YourProject\Security\AccessList
		setup:
			- addRoles(%acl.roles%)
			- addResources(%acl.resources%)
			- addDirectives(%acl.directives%)
			- someStuff() # here we can do some "cool stuff"

Create your AclUser

class AclUser extends Object implements IUser
{
	// Implement `getRoles` method
}

Creating your own Nette\Security\User

You need to create your own User service

class MyLoggedUser extends \Nette\Security\User
{
	/**
	 * @param IUserStorage $storage
	 * @param IAuthenticator|NULL $authenticator
	 */
	public function __construct(IUserStorage $storage, IAuthenticator $authenticator = NULL)
	{
		parent::__construct($storage, $authenticator); // No IAuthorizator here !!!
	}

	/**
	 * @inheritdoc
	 */
	public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL)
	{
		throw new LogicException('Use Damejidlo\ACL\Authorizator directly. User shouldn\'t have such a responsibility');
	}

	/**
	 * @inheritdoc
	 */
	public function isInRole($role)
	{
		throw new LogicException('Use Damejidlo\ACL\Authorizator directly. User shouldn\'t have such a responsibility');
	}

	/**
	 * @return AclUser
	 */
	public function getAclUser()
	{
		$entity = $this->getEntity(); // depens on your implementation
		return new AclUser($entity, $this->getRoles());
	}
}
services:
    user: Some\Namespace\MyLoggedUser

Load your Authorizator into template

Best way is to create your own TemplateFactory. And in createTemplate method just call:

	/**
	 * @param Control|NULL $control
	 * @return Template
	 */
	public function createTemplate(Control $control = NULL)
	{
		$template = parent::createTemplate($control);

		// Some stuff (helper registration, etc...)

		$template->setParameters([
			'authorizator' => $this->authorizator,
		]);

		return $template;
	}

Usage

And now, profit!

	// In some Presenter

	public function handleDestroy($worldId)
	{
		$world = $this->worldFinder->findWorld($worldId);
		$resource = new WorldResource($world);
		$permission = 'destroy';

		if (!$this->authorizator->isAllowed($this->user->getAclUser(), $resource, $permission) {
			throw new NotAllowedException($resource, $permission);
		}
	}
  • 2.0.1 2.0.1

    Move coding-standard to dev requirements

  • 2.0 2.0

    • Allow php >= 7.1
    • Added CS
    • Require directly nette/utils:^2.4
  • 1.1 Allow to require Nette 2.4

  • 1.0.0 First release

Componette Componette felix@nette.org