Dodání Security\User do Authorizatoru

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
tsusanka
Člen | 23
+
0
-

Ve svém Authorizatoru bych potřeboval mít k dispozici ID přihlášeného uživatele.

class Authorizator extends Nette\Security\Permission
{
	private $user;

	public function __construct(Nette\Security\User $user)
	{
		$this->user = $user;

		$this->addRole('editor');
		$this->addRole('admin');

		$this->addResource('article');

		$this->allow('editor', 'article', 'edit', callback($this, 'ownsArticle'));
		$this->allow('admin', 'article', self::ALL);
	}

	public function ownsArticle(Nette\Security\Permission $permission)
	{
		return $permission->queriedResource->createdBy === $this->user->id;
	}

V configu mám authorizator: Matfyz\Authorizator(@user), přestože @user by se doplnilo samo. Tento kód mi však generuje Nette\InvalidStateException: Circular reference detected for services: user, authorizator..

Předpokládám, že to je tím, že User potřebuje k vytvoření Authenticator a v tomto případě také naopak (Authenticator potřebuje User), takže to vede k zacyklení. Jak jinak ale vyřešit, že role editor může editovat pouze své články?

Díky moc za radu.

Šaman
Člen | 2636
+
0
-

Tohle jsem řešil dvěma způsoby. Viz tahle diskuze

  1. Nečistě – v bootstrapu jsem po vytvoření contextu ručně předal usera pomocí setteru:
<?php
# Z připraveného konfigurátoru vytvoříme a vrátíme DI kontejner
$container = $configurator->createContainer();

# Do autorizátoru předáme aktuálního uživatele, dokud nebude uspokojivě vyřešená metoda getQueriedRole() v Nette
$authorizator = $container->getService('authorizator');
$user = $container->getService('user');
$authorizator->setIdentity($user->identity);

return $container;
?>
  1. čistěji – nepoužívám $user->isAllowed(), protože ten nepředá authorizátoru objekt typu IRole, ale pouze id role (např. 'user'). Pokud použiješ
<?php
$authorizator->isAllowed($role, $resource, $privilege);
?>

kde $role je objekt splňující rozhraní IRole a $resource objekt IResource, tak máš možnost napsat si vlastní podmínku takto:

<?php
# class AccessList extends Permission

# v konstruktoru
$this->allow('user', 'Event', 'delete!', callback($this, 'isOwned'));


/**
 * Zjistí, jestli je aktuální uživatel vlatníkem zadaného zdroje
 *
 * @param Permission $acl
 * @param mixed      $role
 * @param mixed      $resource
 * @param mixed      $privilege
 * @return bool rozhodnutí, zda je aktuální uživatel vlastníkem zadaného zdroje
 */
public function isOwned(Permission $acl, $role, $resource, $privilege)
{
	if ($acl->queriedResource instanceof IResource)
	{
		return $acl->queriedRole->id === $acl->queriedResource->ownerId;
	}
	else
	{
		return FALSE; // statický přístup ke zdroji
	}
}
?>

Já to mám ještě spojené se svým modelem a jeho $userem, takže mám zaručeno, že $role bude objekt. Ty bys potřeboval podmínku rozšířit ještě o kontrolu, že $this->queriedRole instanceof IRole.
(To $acl->queriedResource->ownerId už je moje vlastní konvence, ale pro tebe je důležité, že $acl->queriedResource je objekt rozhraní IResource a s ním můžeš pracovat.)


//edit: opraveno $this->queriedRole->id$acl->queriedRole->id

Editoval Šaman (30. 7. 2013 4:41)

tsusanka
Člen | 23
+
0
-

Díky moc za odpověď. Verze 2 se mi také líbí víc a funguje! Ještě to shrnu pro budoucno:

  • moje entita v modelu User (nikoliv Security\User) implementuje IRole (a má funkci getRoleId())
  • ostatní entity implementují IResource (a mají funkci getResourceId())
  • v Authorizatoru je funkce isOwned definována o příspěvek vyše
  • pro kontrolu přístupu používám vlastní funkci isAllowed() v BasePresenteru, kterou volám když potřebuji. Tato funkce volá $this->context->authorizator->isAllowed($this->userEntity, $entity, $action) (a userEntity je onen model User)

Editoval tsusanka (27. 7. 2013 22:03)

Šaman
Člen | 2636
+
0
-

Ještě je na zvážení, jak pracovat se statickým přístupem. Já osobně, pokud nedostanu objekt Resource, tak vracím TRUE.
Tedy může uživatel prohlížet uživatele? Ano. (Např, když rozhoduji o záložkách v menu.)
Může prohlížet tohoto uživatele? Rozhodne podmínka. (Až když zpracovávám konkrétní objekty.)

Takže u mě to vypadá takto:

<?php

namespace MyApp\Model\Security;

use Nette\Security\IRole as INetteRole;


/**
 * Rozhraní popisující držitele role
 */
interface IRole extends INetteRole
{

	/**
	 * Vrátí ID držitele role.
	 *
	 * @return int
	 */
	function getId();

}
?>
<?php

namespace MyApp\Model\Security;

use Nette\Security\IResource as INetteResource;


/**
 * Rozhraní popisující datový zdroj
 */
interface IResource extends INetteResource
{

	/**
	 * Vrátí ID vlastníka zdroje.
	 *
	 * @return int
	 */
	function getOwnerId();

}
?>
<?php
# class AccessList extends Permission

# v konstruktoru
$this->allow('user', 'Event', array('list', 'detail'), callback($this, 'isOwnedOrStatic'));


/**
 * Zjistí, jestli je aktuální uživatel vlatníkem zadaného zdroje
 *
 * @param Permission $acl
 * @param mixed      $role
 * @param mixed      $resource
 * @param mixed      $privilege
 * @return bool rozhodnutí, zda je aktuální uživatel vlastníkem zadaného zdroje
 */
public function isOwnedOrStatic(Permission $acl, $role, $resource, $privilege)
{
	if ($acl->queriedResource instanceof IResource)
	{
		return $acl->queriedRole->id === $acl->queriedResource->ownerId;
	}
	else
	{
		return TRUE; // statický přístup
	}
}
?>
<?php
# presenter
# $acl a repozitáře jsou injectovány

$user = $this->userRepository->get($this->user->id); # náš user už bude splňovat IRole (ale může mít pouze jednu!)

$this->acl->isAllowed($user, 'Event', 'list'); # TRUE, žádná id nemáme
$this->acl->isAllowed($user, 'Event', 'detail'); # TRUE, existuje šance, že na to budu mít právo

$event = $this->eventRepository->get(10); # vrátí nám objekt IResource
$this->acl->isAllowed($user, $event, 'detail'); # jen pokud jsme vlastníkem této konkrétní události
?>

P.S. Jen pro úplnost dodávám, že používám ORM (např. YetORM, nebo LeanMapper) a repozitáře mi vrací skutečné instance konkrétních entit. Na repozitářích z QuickStaru to fungovat nebude, tam byste si museli uživatele a událost vytvořit jinak.

Mimochodem uživatel musí splňovat obě rozhraní, protože je zároveň držitelem práv a zároveň zdrojem.


//edit: opraveno $this->queriedRole->id$acl->queriedRole->id

Editoval Šaman (30. 7. 2013 4:42)