Dodání Security\User do Authorizatoru
- tsusanka
- Člen | 23
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 | 2659
Tohle jsem řešil dvěma způsoby. Viz tahle diskuze
- 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;
?>
- čistěji – nepoužívám
$user->isAllowed()
, protože ten nepředá authorizátoru objekt typuIRole
, 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
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 | 2659
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)