Statické ACL v modulárnej aplikácii
V tomto tutoriáli sa naučíme vytvoriť modulárnu aplikáciu v Nette Framework, rozdelenú na verejnú a admin sekciu. Vstup a pohyb po admin sekcii bude kontrolovaný pomocou statického ACL (access control list). Ukážeme vám ako nastaviť ACL v súčinnosti s tzv. rolami (roles), zdrojmi (resources) a privilégiami (privileges).
Práve prebieha aktualizácia tutoriálu na Nette 2 beta. Tutoriál je tak nekonzistentý (z polovice nový, z polovice starý). Prosím počkajte kým dokončím zmeny.
Predhovor
Užívatelia, ktorí začnú využívať niektorý webový PHP framework čoskoro zistia, že zložitejšiu aplikáciu je nutné rozdeliť na viac častí (modulov). Najčastejšie sa aplikácia rozdeľuje na dva moduly – Frontend a Backend (niekedy tiež označovaný ako administrácia). Frontend je verejne prístupná časť aplikácie, ktorú vidí bežný návštevník. Backend predstavuje uzavretú časť aplikácie, do ktorej majú mať prístup iba oprávnení užívatelia (admin, redaktor, moderátor). Pre verejnosť je uzavretá. Oba moduly využívajú ten istý Model (DATA) na manipuláciu s biznis logikou aplikácie.

Frontend DATA zvyčajne iba zobrazuje (preto jednosmerná šípka), Backend poskytuje funkcie aj na ich manipuláciu (pridávanie, editovanie, mazanie). Až zložitejšie (WEB 2.0) aplikácie umožňujú manipuláciu s dátami aj na Frontende. Jedná sa vtedy napr. o pridávanie diskusných príspevkov bežnými návštevníkmi.
Čo to je ACL a načo ho potrebujeme
Mnoho programátorov webových aplikácií už výraz ACL možno videlo, ale nevedia tento komponent webových frameworkov správne uchopiť a použiť vo webovej MVC/MVP aplikácií. Môže za to väčšinou forma akou je ACL komponenta popísaná v referenčnej príručke frameworku. Len málokedy sa v nej dočítate ako ACL zakomponovať do komplexnej webovej aplikácie. Pritom sa jedná o jednu z najdôležitejších častí webovej aplikácie, pretože ACL je zodpovedné za riadenie bezpečnostnej politiky (user access managment).
ACL je definitívnym riešením oprávnení užívateľov. Prvé pokročilejšie PHP aplikácie programátori väčšinou napíšu tak, že pripravia login stránku pre administrátorov/redaktorov, vytvoria administračné rozhranie kde môžu pridávať články, moderovať komentáre a pod. Riadenie bezpečnostnej politiky potom väčšinou vyzerá ako tento kód
if ($user->type == ADMIN) {
$html .= '<a href="/delete.php?page=' . $id . '">delete</a>';
}
Síce to funguje ale takýto kód je veľmi ťažké spravovať (pridať nový typ užívateľa do povoleného zoznamu). Okrem toho tento kód porušuje obecné princípy OOP, predovšetkým zapuzdrenie (logika, ktorá rozhodne o povolení by mala byť pred programátorom skrytá do nejakého API).
Ak použijeme na riešenie úlohy ACL, kód by mohol vypadať napr. takto
if ($user->isAllowed('page', 'delete')) {
$html .= '<a href="/delete.php?page=' . $id . '">delete</a>';
}
Kód robí úplne rovnakú vec ale logika je zapuzdrená do objektu. Aj čitateľnosť kódu stúpla – kód tvorí pomerne jasnú anglickú vetu (aj keď so zlým slovosledom): User is allowed page delete?. Síce sa rovnaký kód musí vyskytovať na všetkých miestach, kde chceme mať odkaz na zmazanie stránky, ale už sme nastúpili na správnu cestu – zvonka nie je vidieť ako funguje logika, ktorá vypočíta oprávnenia k odkazu na zmazané stránky.
ACL v praxi
ACL je skratkou pre access control list. Je to systém na spravovanie ľubovoľne komplexnej špecifikácie oprávnení. Systém ACL je postavený na troch základných komponentoch:
- Role (roles) – „admin“, „moderator“, „guest“. Podľa architektúry môže mať užívateľ jednu alebo viac rolí.
- Zdroje (resources) – „produkt č. 123“, „články“ a pod. Je to entita, ktorú pomocou ACL chránime.
- Privilégia (privileges) – „delete“, „create“, „view“. Akcia ktorú môže byť rola oprávnená na zdroji vykonať.
Niektoré implementácie ACL (medzi inými aj tá v Nette Framework) umožňujú vytvoriť strom oprávnení, kde jedná rola môže podediť oprávnenia od inej role (editor má rovnaké oprávnenia ako moderator plus …)
ACL a Nette Framework
Najjednoduchšie využitie ACL v Nette Framework je v súčinnosti s MVP konceptom. Ako zdroje použijeme názvy Presenterov a privilégia budú zastupovať názvy akcii/view metód
class BasePresenter extends Presenter
{
public function foo()
{
$user = $this->getUser();
if ($user->isAllowed($this->name, $this->view) {
// chraneny kod
}
}
}
Čo budeme potrebovať?
- Tento tutoriál je určený pre verziu 2.0 beta pre PHP 5.3. Pred začatím si prosím otestujte svoj webový server pomocou nástroja requirement-checker.
- Zo stránky Download si stiahnite archív Nette Framework 2.0-beta pro PHP 5.3 se jmennými prostory.
- Zo "stiahnutého archívu budeme potrebovať knižnice Nette Framework a obsah zložky examples/Modules-Usage.
- Vytvorte si pracovnú zložku pre projekt, napr. nette-acl a príp. sprevádzkujte nový VirtualHost.
Do zložky nette-acl nakopírujte obsah zložky examples/Modules-Usage z distribučného balíka Nette Framework. V zložke ďalej vytvorte adresár libs a umiestnite do neho knižnice Nette Framework. V zložke libs ešte vytvorte jednoduchú štruktúru podadresárov s názvami AclProj/Security.
Názov AclProj predstavuje názov vášho projektu. V tejto zložke budú umiestnené definície vlastných tried projektu. V reálnej situácii svoj projekt zvyčajne pomenujete nejakým zmysluplnejším menom. Toto meno použite ako názov namiesto AclProj.
Celú štruktúru projektu demonštruje obrázok:

Tweaks
Pôvodný kód Modules-Usage vyžaduje niekoľko úprav, pretože sa jedná o jednoúčelovú ukážku, ktorá nie je príliš vhodná pre nové projekty. Prepíšeme preto niekoľko súborov:
- súbor
web/index.phpprepíšte obsahom rovnakého súboru z projektu sandbox, ktorý nájdete v distribučnom archíve. - v súbore
app/bootstrap.phpopravte inicializáciu frameworku a RobotLoader-a, inšpiráciu opäť nájdete v sandboxe.
Graficky znázornené zmeny:
- kód šablóny
app/AdminModule/templates/Default.default.lattenahraďte týmto kódom - kód layoutu administrácie
app/AdminModule/templates/@layout.lattenahraďte týmto kódom
Pre maximálne zjednodušenie budeme v tutoriáli využívať minifikovaný CSS súbor projektu Twitter Bootstrap. Ak budete vyvíjať offline, stiahnite si predtým prosím súbor k sebe a správne ho v layoute nalinkujte.
Teraz už môžeme otestovať základnú funkčnosť aplikácie. V tomto momente beží len skeleton modulárnej aplikácie v Nette Framework.
Zabezpečený Backend
Ako už bolo spomenuté vyššie, na riadenie prístupu do Backend modulu
(v ďalšom texte bude použité spojenie AdminModule alebo admin
sekcia) bude využívané statické ACL. Statické znamená, že všetky
privilégia, zdroje a role budú zapísané do .php súboru. Oprávnených
užívateľov a ich roly si však uložíme do databázy a následne využijeme
Nette\Database na získanie týchto údajov z databáze.
Databázové údaje a spojenie
Vytvorte novú (MySQL) databázu pre projekt (napr. nette_acl) a novú tabuľku user, ktorá bude udržiavať údaje o oprávnených užívateľoch
CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `password` char(40) NOT NULL, `role` varchar(255) NOT NULL, UNIQUE KEY `email` (`email`) ) DEFAULT CHARACTER SET = utf8;
Všimnite si, že tabuľka obsahuje stĺpec role. V tomto poli je uložená rola ktorú užívateľovi priradíte. Ako prihlasovacie meno použijeme email užívateľa. V stĺpci name je uložené plné meno užívateľa.
Heslo je uložené ako sha1 hash pôvodného hesla + tzv.
soli (salted hash) definovanej v app/config.ini. Heslo je
osolené obyčajným zreťazením so soľou. Spustite tento SQL príkaz
nad vašou databázou, čím naplníte tabuľku users.
INSERT INTO `user` (`id`, `name`, `email`, `password`, `role`) VALUES
(NULL, 'John Doe', 'admin@example.com', SHA1('adminzb1g7IHt1I'), 'admin'),
(NULL, 'Betty Lee', 'betty@example.com', SHA1('pokuszb1g7IHt1I'), 'editor'),
(NULL, 'Peter Brown', 'brown.p@example.com', SHA1('pokuszb1g7IHt1I'), 'member');
Heslá jednotlivých užívateľov sú:
- John Doe: admin
- Betty Lee: pokus
- Peter Brown: pokus
Pripojenie ku databáze je potrebné nakonfigurovať ako službu. Bude tak
kedykoľvek dostupné v systémovom DI kontajneri. Konfiguráciu zapíšte do
app/config.neon (prihlasovacie údaje upravte podľa svojho DB
servera).
common:
...
services:
database:
class: Nette\Database\Connection
arguments: ['%database.driver%:host=%database.host%;dbname=%database.dbname%', %database.user%, %database.password%]
development < common:
parameters:
database:
driver : mysql
host : localhost
dbname : nette_acl
user : php
password : php
security.salt = zb1g7IHt1I
Vytvorenie ACL
Ako sme už spomenuli, ACL je v tomto tutoriáli zapísaný staticky v
.php súbore. Jeho úlohou je definovanie rolí, zdrojov a
oprávnení. Použité role sa musia zhodovať s rolami užívateľov
uložených v databáze v tabuľke user. Vytvorte nový súbor
libs/AclProj/Security/Acl.php:
<?php
namespace AclProj\Security;
use Nette\Security\Permission;
class Acl extends Permission
{
public function __construct()
{
//roles
$this->addRole('guest');
$this->addRole('member', 'guest');
$this->addRole('editor', 'member');
$this->addRole('admin');
// resources
$this->addResource('Admin:Default');
$this->addResource('Admin:Page');
$this->addResource('Admin:User');
// privileges
$this->allow('member', 'Admin:Default', Permission::ALL);
$this->allow('editor', 'Admin:Page', Permission::ALL);
$this->allow('admin', Permission::ALL, Permission::ALL);
}
}
Všimnite si ako je názov (a namespace) triedy odvodený od
cesty, kde je súbor uložený. Ak zmeníte ktorúkoľvek časť cesty
k súboru, je vhodné tieto zmeny reflektovať aj vo vašich .php
súboroch v zložke libs a naopak, ak zmeníte názov triedy (alebo
namespace) súbor adekvátne premiestniť (viď. tip o mene projektu
vyššie).
V konštruktore AclProj\Security\Acl definujeme celý ACL.
V Nette Framework je možné rolu podediť od inej role. V hore
uvedenom kóde napr. rola editor dedí od role member. V praxi to
znamená, že editor získa všetky oprávnenia, ktoré definujeme pre
rolu member. ACL obsahuje aj rolu guest. Nette Framework túto
rolu priradí každému návštevníkovi, ktorý nebol autorizovaný. Rolu
guest tak automaticky nadobudne každý neprihlásený užívateľ.
Ďalej ACL obsahuje definíciu zdrojov. V našom prípade zdroje reprezentujú názvy samotných Presenterov admin sekcie.
Nakoniec sú jednotlivým roliam nastavené privilégia. Zápis Permission::ALL je možné chápať ako zástupný znak *, čiže rola je oprávnená nad zdrojom robiť všetky operácie. Ak chcete povolené operácie explicitne definovať, zapíšte ich názvy do poľa
$this->allow('editor', 'Admin:Page', array('view', 'edit'));
Rola admin má nastavenú neobmedzenú moc, keďže má povolenie pristupovať k všetkým operáciám vo všetkých zdrojoch. Nemusí teda dediť od žiadnej existujúcej role.
ACL je tiež nutné zaregistrovať ako službu, do
app/config.neon preto pridajte:
common:
...
services:
...
authorizator:
class: AclProj\Security\Acl
V tomto prípade sa jedná o najjednoduchšiu definíciu služby – keď
si ktokoľvek od DIc vyžiada authorizator, dostane inštanciu
nášho AclProj\Security\Acl.
Prihlásenie do Admin sekcie
Keďže údaje o našich užívateľoch sú uložené v databázi, musíme
si napísať vlastný authenticator. Je to služba, ktorá obstará
overenie údajov zadaných do prihlasovacieho formulára oproti našej DB
tabuľke. Službu najskôr zaregistrujeme v app/config.neon:
authenticator:
class: AclProj\Security\Authenticator
arguments: ['@database', %security.salt%]
V authtenticatore budeme potrebovať databázové spojenie a password salt. Službe teda tieto objekty predáme cez konštruktor.
Teraz vytvorte súbor libs/AclProj/Security/Authenticator.php a
zapíšte do neho tento kód:
<?php
namespace AclProj\Security;
use Nette\Object,
Nette\Database\Connection,
Nette\Security\Identity,
Nette\Security\IAuthenticator,
Nette\Security\AuthenticationException;
class Authenticator extends Object implements IAuthenticator
{
private $dbConnection;
private $passwordSalt;
public function __construct(Connection $dbConnection, $salt)
{
$this->dbConnection = $dbConnection;
$this->passwordSalt = $salt;
}
public function authenticate(array $credentials)
{
$email = $credentials[self::USERNAME];
$password = sha1($credentials[self::PASSWORD] . $this->passwordSalt);
$user = $this->dbConnection
->table('user')
->where('email=?', array($email))
->fetch();
if (!$user) {
throw new AuthenticationException("User with login email '$email' not found", self::IDENTITY_NOT_FOUND);
}
if ($user->password != $password) {
throw new AuthenticationException('Wrong password', self::INVALID_CREDENTIAL);
}
$identity = new Identity($user->id, $user->role);
$identity->name = $user->name;
$identity->email = $user->email;
return $identity;
}
}
Trieda AclProj\Security\Authenticator implementuje rozhranie
Nette\Security\IAuthenticator, preto musí obsahovať definíciu
verejnej metódy authenticate(), ktorá obstará samotné overenie
prihlasovacích údajov. najskôr sa v tabuľke user pokúsi
nájsť užívateľa v daným emailom. Následne overuje zadané heslo. Oba
tieto údaje obdrží metóda authenticate() automaticky. Ak email
a heslo súhlasia, vytvoríme novú identitu, ktorá sa potom stane súčasťou
Nette\Http\User. Vrátením tejto identity frameworku indikujeme,
že užívateľ bol úspešne autentifikovaný. V prípade neúspechu metóda
vyhadzuje výnimky, ktoré proces autentifikácie zastavia a obsahujú správu
pre prihlasujúceho sa užívateľa.
Login action
Logika aplikácie v AdminModule bude taká, že ak sa užívateľ pokúsi
spustiť akciu z chráneného Presentera, overí sa, či je užívateľ
prihlásený. Ak nie je, bude presmerovaný na
AdminModule\AuthPresenter:login, ktorý mu zobrazí prihlasovací
formulár. Ak je užívateľ už prihlásený, aplikácia v súčinnosti s ACL
overí, či má užívateľ oprávnenie spustiť žiadanú
Presenter:action.
Vytvorte najskôr AdminModule\BasePresenter, od ktorého budú
dediť všetky Presentre v AdminModule:
app/AdminModule/presenters/BasePresenter.php
<?php
namespace AdminModule;
abstract class BasePresenter extends \BasePresenter
{
}
Ďalej otvorte súbor
app/AdminModule/presenters/DefaultPresenter.php, zmažte jeho obsah
a vložte nasledujúci kód
<?php
namespace AdminModule;
use Nette\Http\User;
final class DefaultPresenter extends BasePresenter
{
public function startup()
{
parent::startup();
if (!$this->user->isLoggedIn()) {
if ($this->user->getLogoutReason() === User::INACTIVITY) {
$this->flashMessage('Session timeout, you have been logged out', 'warning');
}
$backlink = $this->getApplication()->storeRequest();
$this->redirect('Auth:login', array('backlink' => $backlink));
} else {
if (!$this->user->isAllowed($this->name, $this->action)) {
$this->flashMessage('Access diened. You don\'t have permissions to view that page.', 'warning');
$this->redirect('Auth:login');
}
}
}
public function actionLogout()
{
$this->user->logOut();
$this->redirect('Auth:login');
}
}
V Presenteri sme prepísali metódu startup(). Ak sa pozorne
pozrieme na životný
cyklus Presentera, zistíme, že metóda startup() sa volá na
úplnom začiatku behu Presentera. Ak teda chcete zabrániť vykonaniu akcie
Presentera, musíme to urobiť práve tu.
Kód presne implementuje logiku, ktorú sme spomínali o pár odstavcov vyššie – overí sa, či je užívateľ prihlásený
- ak nie, overí sa či už neuplynul logout interval, v tomto
prípade nastavíme flashMessage s informáciou. Každý neprihlásený
užívateľ je potom presmerovaný na
AdminModule:AuthPresenter:login, ktorý zabezpečí vykreslenie prihlasovacieho formulára. Predtým je ešte do Session uložený aktuálny request, vďaka čomu bude užívateľ po prihlásení hneď presmerovaný na destination, z ktorej sme ho v tomto kroku vyhodili. - ak áno, je s pomocou ACL rozhodnuté či má užívateľ privilégium k zdroju.
Presenter okrem toho ešte obsahuje akciu na odhlásenie užívateľa
z admin modulu. Nikdy nezabudnite všetkým užívateľom, ktorí sa
prihlasujú do AdminModule dať oprávnenie na túto akciu, inak sa nebudú
môcť odhlásiť! Naše ACL toto splňuje nakoľko rola registered má
oprávnenia na všetky privilégia v
AdminModule\DefaultPresenter.
Ako ste si mohli všimnúť z popisu a kódu, neprihlásení užívatelia
sú presmerovaní na AdminModule:AuthPresenter:login, kde sa im
zobrazí prihlasovací formulár. Vytvorte nový súbor pre Presenter
app/AdminModule/presenters/AuthPresenter.php a zapíšte do neho
tento kód:
<?php
namespace AdminModule;
use AdminModule\Forms\LoginForm;
final class AuthPresenter extends BasePresenter
{
/** @persistent */
public $backlink = '';
protected function createComponentLoginForm($name)
{
$form = new LoginForm($this, $name);
}
}
Presenter obsahuje iba jednu továrničku na vytvorenie login formulára.
Samotný formulár je uložený v samostatnej triede
AdminModule\Forms\LoginForm. Doporučujeme v8m aby ste si v rámci
projektu organizovali Formuláre/Komponenty do samostatných tried do súborov,
ktorých umiestnenie kopíruje ich namespace.
Vytvorte nový súbor app/AdminModule/forms/LoginForm.php a
zapíšte do neho definíciu formulára:
<?php
namespace AdminModule\Forms;
use Nette\Application\UI\Form,
Nette\Forms\Form,
Nette\Security\AuthenticationException;
class LoginForm extends AppForm
{
public function __construct($parent, $name)
{
parent::__construct($parent, $name);
$this->addProtection('Token timeout. Please send form again.');
$this->addText('login', 'Email:')
->addRule(Form::FILLED, 'Enter login email')
->addRule(Form::EMAIL, 'Filled value is not valid email');
$this->addPassword('password', 'Password:')
->addRule(Form::FILLED, 'Enter password');
$this->addSubmit('send', 'Log in!');
$this->onSubmit[] = array($this, 'formSubmited');
}
public function formSubmited($form)
{
try {
$user = $this->presenter->user;
$user->login($form['login']->value, $form['password']->value);
$this->resenter->application->restoreRequest($this->presenter->backlink);
$this->presenter->redirect('Default:default');
} catch (AuthenticationException $e) {
$form->addError($e->getMessage());
}
}
}
Formulár obsahuje polia pre email (s patričným validátorom), pre heslo, odosielacie tlačidlo. Okrem toho sme formulár obohatili o ochranu pred CSRF a nakoniec sme pridali callback na metódu, ktorá spracuje údaje z formulára.
Tá sa vykoná iba ak je odoslaný formulár valídny (prejde validačnými pravidlami definovanými v konštruktore formulára). V tele callback funkcie sa pokúsime užívateľa prihlásiť (na pozadí sa zavolá metóda authenticate() z nášho Authenticatora viď. vyššie). V prípade, že je prihlásenie úspešné, užívateľ je presmerovaný späť.
Všimnite, si že volanie $user->login() je obalené try/catch blokom. Ak teda metóda authenticate() vyhodí nejakú výnimku (a že ich vyhadzuje sa presvedčte vyššie v definícii triedy AclProj\Security\Authenticator), je message výnimky vložená do formulára a ten je nanovo vykreslený užívateľovi (samozrejme užívateľa neprihlásime), kde je o svojej chybe informovaný.
Ostáva doplniť kód šablóny AuthPresentera, kde sa nachádza vykreslenie prihlasovacieho formulára. Vytvorte súbor app/AdminModule/templates/Auth.login.latte
{block #title}Login :: {include #parent}{/block}
{var robots = noindex}
{block #content}
<div id="loginForm">
{control loginForm}
</div>
Keďže na prihlásenie sa zvyčajne používa iné rozloženie stránky ako v samotnej admin sekcii, budeme pri vykresľovaní AdminModule\AuthPresenter:login používať iný layout. Vytvorte súbor app/AdminModule/templates/Auth.@layout.latte a dajte do neho dajte tento kód.
Teraz sa už môžete vyskúšať prihlásiť do admin sekcie
- zadajte do browsra URL http://nette-acl/admin
- ak sa chcete prihlásiť ako admin zadajte email admin@example.com a heslo admin.
Ak aplikácia nechce pracovať a neustále vyskakuje Laděnka, nezabudnite premazať zložku temp/cache.
Aby ste mohli vyskúšať funkciu ACL, je treba do AdminModulu pridať ďalšie dva Presentery. Tie dodatočné dva, ktoré sú definované v ACL. Aby sme ich rovnako ochránili pred neoprávneným prístupom pomocou ACL, je treba preťažiť ich metódy startup(). Takýto prístup ale nie je veľmi efektívny – písať stále sa opakujúci kód do každého Presentera AdminModulu – to si žiada nejaký refactoring.
Prvé čo vás asi napadne je „ochranný“ kód z DefaultPresentera presunúť do BasePresenter. Keď sa však na kód dobre pozriete, zistíte že kód v prvom kroku overí či je užívateľ prihlásený. Ak nie je, bude presmerovaný na AuthPresenter:login. Lenže aj on dedí od Admin_BasePresenter. Po presmerovaní by sa znova overovala „prihlásenosť“ a zase by sa presmerovávalo. Aplikácia sa dostane do slučky a zhavaruje. AuthPresenter musíme teda z overovania vylúčiť. Overovací kód je treba umiestniť inam.
Ideálne je vytvoriť nového predka pre všetky Presentery, ktoré budú chránené ACL. Tu vás však upozorníme, že od tohto predka nebude dediť DefaultPresenter. Je to z toho dôvodu, prihlásený užívateľ ktorý sa snaží spustiť Presenter:action na ktorý nemá oprávnenia by nemal byť presmerovaný na AuthPresenter:login (kde sa vykresľuje prihlasovací formulár). Je už predsa prihlásený! Kód v DefaultPresenter::startup() však robí práve to (viď. vyššie). Prihlásených užívateľov po neúspešnom vstupe do sekcií kde nemajú na prístup oprávenia presmerujeme späť na úvodnú stránku admin sekcie DefaultPresenter:default.
Takýto rozbor nás dovedie k nasledujúcemu stromu presenterov:
Všetky chránené Presentery s výnimkou DefaultPresenter budú teda dediť od SecuredPresenter. Ten prepisuje metódu startup() takže všetci jeho potomkovia budú pomocou kódu v nej overovaní cez ACL. Ostáva teda napísať kód zostávajúcich Presenterov
app/AdminModule/presenters/SecuredPresenter.php
<?php
namespace AdminModule;
use Nette\Web\User;
abstract class SecuredPresenter extends BasePresenter
{
public function startup()
{
parent::startup();
$user = $this->getUser();
if (!$user->isLoggedIn()) {
if ($user->getLogoutReason() === User::INACTIVITY) {
$this->flashMessage('Uplynula doba neaktivity! Systém vás z bezpečnostných dôvodov odhlásil.', 'warning');
}
$backlink = $this->getApplication()->storeRequest();
$this->redirect('Auth:login', array('backlink' => $backlink));
} else {
if (!$user->isAllowed($this->name, $this->action)) {
$this->flashMessage('Na vstup do tejto sekcie nemáte dostatočné oprávnenia!', 'warning');
$this->redirect('Default:');
}
}
}
}
Všimnite si, že metóda presmeruje pri nepovolenom prístupe na Default: a nie na Auth:login ako v DefaultPresenteri.
Pridajte aj dva nové chránené Presentery…
app/AdminModule/presenters/PagePresenter.php
<?php
namespace AdminModule;
final class PagePresenter extends SecuredPresenter
{
}
app/AdminModule/presenters/UserPresenter.php
<?php
namespace AdminModule;
final class UserPresenter extends SecuredPresenter
{
}
…a ich šablóny.
app/AdminModule/templates/Page.default.latte
{block #title}Správa stránok :: {include #parent}{/block}
{block #content}
<h1>Správa stránok</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce luctus tellus at odio fringilla in venenatis dolor aliquam.</p>
app/AdminModule/templates/User.default.latte
{block #title}Správa užívateľov :: {include #parent}{/block}
{block #content}
<h1>Správa užívateľov</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce luctus tellus at odio fringilla in venenatis dolor aliquam.</p>
Premažte zložku temp/cache a otestuje prístup na nové Presentery pomocou všetkých užívateľov. Odhláste administratora a prihláste sa napr. ako Betty
- email betty@example.com, heslo pokus
Bety má podľa ACL povolenie na pohyb po DefaultPresenteri a PagePresenteri. Nemá oprávnenie na vstup do UserPresentera. Skúste klepnúť na jeho odkaz a uvidíte čo sa stane
Zapracoval ACL a Presenter ochránil pred nepovoleným vstupom neoprávenej role. Aplikácia je v tomto momente hotová, môžete si ju celú stiahnuť ako archív .zip (neobsahuje knižnice Nette Framework).
Komentáře 
gawan | 9. 3. 2010, 10:49 | comment
Vynikajúci tutoriál, gratulujem a ďakujem. Ušetril mi niekoľko hodín študovania. Takto kvalitných návodov by mohlo byť viac.
Fergi | 12. 3. 2010, 21:26 | comment
díky moc za tenhle navod, ktery mi konecne pomohl vse pochopit ;)
Endrju | 14. 3. 2010, 6:08 | question
Fakt moc pěkná práce srigi, děkuji :)!
Během čtení toho tutoríálu mě napadla jen jedna věc – nemělo by
být volání parent::startup(); hned na začátku metody místo na
konci? Máš to ve třídě Admin_DefaultPresenter a
Admin_SecuredPresenter v metodách startup().
Upozornil mě na to jasir v jednom z komentářů na foru a jeho zdůvodnění mi přišlo logické.
Pokud se provede přesměrování redirect (po vyhodnocení
podmínky), tak se volání parent::startup() neprovede a pokud
budu mít nějaký kód v rodičovské medotě startup(), tak se
ten kód nevykoná. Ty v rodičovské metodě
Admin_BasePresenter::startup() žádný kód nemáš, tak to
nevadí, ale kdyby někdo kód tohoto tutoriálu upravoval pro svoji aplikaci,
mohl by narazit na problém.
A pak jedna minidrobnůstka.. V templatech
app/AdminModule/templates/@layout.phtml a
app/AdminModule/templates/Auth.@layout.phtml bych
zaměnil odkaz
<a id="homelink" href="/admin">nette-acl admin<span></span></a>
za
<a id="homelink" href="{plink Default:}">nette-acl admin</a>
Né každý si možná nastaví nový VirtualHost a pak by mu odkaz nefungoval.
Tak snad se na mě nenazlobíš a ještě jednou díky :)!!
srigi | 16. 3. 2010, 10:53 | comment
Vdaka za pekne komenty. Az sa najde trocha casu vsetky vase pripomienky zapracujem a tut upravim.
raia | 19. 3. 2010, 19:28 | comment
Príma návod, přelouskal jsem zdrojak do PHP5.3 a přidal opravy z výše uvedených komentářů. Soubory přikládám. (Nedá se to hodit někam na files.nette.org ?
gdx | 2. 4. 2010, 2:23 | bug
Ahoj, musim pochvalit vyborny navod, ktory mi tiez pomohol, avsak chcem upozornit na jednu drobnost :)
v triede Admin_SecuredPresenter() volas setAuthorizationHandler(new Proj_Security_Acl());
pritom Proj_Security_Acl je v tomto navode uz registrovana ako sluzba: service.Nette-Security-IAuthorizator = Proj_Security_Acl
srigi | 14. 5. 2010, 23:15 | comment
Tak, navod som „refaktoroval“. Zapracoval som vsetky vase pripomienky. Enjoy :)
iguana007 | 14. 5. 2010, 23:23 | comment
Paráda. Díky moc ;)
rokerkony | 25. 5. 2010, 11:17 | comment
vsiml jsem si kdyz jsem to narychlo prochazel ze tam pouzivas $user->getSignOutReason() … lepsi bude uz i ve stable pouzit $user->getLogoutReason() aby to nikoho nerozhodilo pri prechodu na alpha verzi :)
to same u Environment::getUser()->signOut(); → Environment::getUser()->logout(); víc jsem asi neviděl :)
(ve verzi stable jsou ty metody funkci a nehazou warning, ale v komentari uz deprecated je)
pseudo | 17. 6. 2010, 20:08 | comment
Nevím čím to je, možná tím že jsem se pokoušel návod aplikovat v php 5.3, ale při pokusu přihlásit se s neexistujícím emailem (v mém případě přihlašovacím jménem) dostanu místo vyhozené výjimky chybu: Class ‚Klara\Security\AuthenticationException‘ not found
<?php
namespace Klara\Security;
use Nette\Object; use Nette\Environment; use Nette\Security\IAuthenticator; use Nette\Security\Identity;
class Authenticator extends Object implements IAuthenticator {
public function authenticate(array $credentials)
{
$login = $credentialsUSERNAME;
$row = \UserModel::getByLogin($login);
if (!$row)
{
throw new AuthenticationException(„Neplatné autorizační jméno“,
self::IDENTITY_NOT_FOUND);
}
$config = Environment::getConfig(‚security‘);
$password = hash_hmac(‚sha256‘, $credentialsPASSWORD , $config->hmacKey);
if ($row->password !== $password)
{
throw new AuthenticationException(„Neplatné autorizační heslo“,
self::INVALID_CREDENTIAL);
}
return new Identity($row->name, $row->role);
} } ?>
Ondřej Mirtes | 18. 6. 2010, 7:40 | comment
- Dotazy patří do fóra.
- Nemáš v use Nette\Security\AuthenticationException, nauč se pracovat s namespaces.
Inferi | 28. 7. 2010, 22:29 | question
ahoj, vím, že to patří na fórum, ale nedostalo se mi odpovědi. Stáhl sem tuto aplikaci kompletní pro php 5.2 a 5.3. Nicméně po pokusu o přihlášení mi vyskočí chybová hláška: „Service ‚Nette\Security\IAuthenticator‘ not found“. Nette a Dibi + databázi sem do aplikací doplnil. Netuším vůbec, čím to může být, děkuji za vysvětlení, co s tím.
Roman Ožana | 16. 8. 2010, 13:22 | bug
neustále vykakuje Laděnka :)
sibep | 8. 10. 2010, 10:28 | question
Ahoj, po zpracování do php5.3 mi vše funguje jak má, pak jsem ale zjistil, že v modulu, konkrétně v AdminModule, mi laděnka hlásí, že nemůže najít dibi „Class ‚AdminModule\dibi‘ not found“. To se musí pro každý modul dibi znovu registrovat?
funTomas | 15. 10. 2010, 19:37 | comment
Rozhodně užitečný tutorial ukazující užití ACL – děkuji za něj. Na základě čerstvého školení Davida Grudla bych rad upozornil, že implementace ACL by měla být ještě lépe o „level“ níže a to na úrovni modelu. Důvod? Pokud by aplikace implementovala i jiné rozhraní než jen náš původní presenter, pak by logika autorizace (úroveň bezpečnosti) měla zůstávat stejná a je zbytečné mít tento kód duplicitní.
Dtx | 7. 12. 2010, 13:43 | comment
Zdravím,
jakožto začátečník jsem si zkusil rozjet acl podle tohoto návodu a narazil na jeden nemalý problém: při přístupu na stránku localhost/admin to vyhazuje 500 Internal server error.
Nevěděl jsem, kde je chyba, tak jsem trošku lehkomyslně pro jistotu dal všem adresářům veškerá práva, obsah /temp vysypávám pravidelně.. ale stále stejná chyba.
Stáhnul jsem si tedy zde přiložený kompletní zdrojový kód v domnění, že jsem udělal chybu při přepisování/kopírování, upravil potřebné soubory (config, bootstrap..), ale nepomohlo to. Stále je to stejné.
Chci jen zmínit, že „frontend“ jede bez problémů, chyba se objevuje pouze při přístupu do /admin, případně /admin/auth/login
„Vystopoval“ jsem, že chyba by mohla vznikat
v app/AdminModule/Presenters/DefaultPresenter.php, kde před řádkem
s podmínkou if (!$user->isAuthenticated()) { se kód ještě
provede, ale kdekoliv za ním už ne. Takže nějaký problem s objektem
$user? Ten se koukám o řádek výše loaduje takto: $user
= Environment::getUser();
takže „vede“ do tříd frameworku jako takového, v kterém si netroufám se svou chabou znalostí nic debugovat. Napadá někoho nějaké řešení? Už u toho trávím hodiny a rád bych se přes to dostal, přece kvůli tomu netípnu možnost naučit se všude tak vychvalovaný framework ;)
Jan Tvrdík | 7. 12. 2010, 17:03 | comment
Máš zapnutou laděnku?
Dtx | 7. 12. 2010, 18:08 | comment
No to je tezko rict, k vypisu nijakych chyb se to kazdopadne nedostane. Ted jsem to rozjel na jinem hostingu, tam to alespon zapisuje do error.log a vytvari ty html errorSoubory (to bude asi delat ta Ladena, ze). Tam mi to ale zase rve furt neco jineho. Nejdriv, ze neexistuje ErrorPresenter, kdyz ho vytvorim, tak neexistoval HomepagePresenter, ted zase neco, ze could not find method $dibi->addColophone(), ci neco v tom smyslu. Ja jen ziram, ze vam to tady vsem funguje, me se to nedari rozchodit. Zitra se tomu zase povenuju a snad to nejak dam, kazdopadne to tady nechci syflit timto chatem, vim ze toto patri do fora.
tatyalien | 6. 1. 2011, 10:49 | comment
Tak jsem se tak dostal do bodu viz laděnka:
MemberAccessException Call to undefined method NUser::isAuthenticated().
Používám verzy kde musím doplňovat „N“ pokud jsem někdě na to zapomněl, tak jsem se asi přehlídl.. .ale laděnka mě končí vždy na Line 10:
Line 3: final class Admin_DefaultPresenter extends Admin_BasePresenter Line 4: { Line 5: public function startup() Line 6: { Line 7: parent::startup(); Line 8: $user = NEnvironment::getUser(); Line 9: Line 10: if (!$user->isAuthenticated()) { Line 11: if ($user->getSignOutReason() === User::INACTIVITY) { Line 12: $this->flashMessage(‚Uplynula doba neaktivity! Syst�m v�s z bezpe�nostn�ch d�vodov odhl�sil.‘, ‚warning‘); Line 13: }
:-( a nevím kde jsem co přehlídl… Nette jsem použil NetteFramework-2.0dev-PHP5.2
tatyalien | 6. 1. 2011, 11:11 | comment
Tak jsem postoupil a teď už mě to končí na:
Line 29: $user->authenticate($form‚login‘->value, $form‚password‘->value);
// musel jsem měnit názvy viz fórum: http://forum.nette.org/…uthenticated
tatyalien | 6. 1. 2011, 14:32 | comment
Tak již vyřešeno.
srigi | 16. 1. 2011, 19:57 | comment
Updatoval som tutorial na Nette 2.0 Alpha 2, PHP 5.3. Kritika a opravy su vitane. Enjoy.
roarbb | 16. 1. 2011, 21:16 | comment
Nemohol by mi niekto poslat verziu pre php 5.2? Bud odkaz sem na do komentara alebo roarbb@gmail.com. Bol by som Vam velmi vdacny.
Patrik Votoček | 17. 1. 2011, 8:41 | comment
@roarbb: veškeré tutoriály jsou psány pro PHP 5.3 (je totiž zbytečné
psát klon pro PHP 5.2 protože stačí většinou pouze umazat
use a namespace). Btw stejně bych ti doporučil
spíše přejít na PHP 5.3 (stejně na to jednou dojde – čím dřív
tím líp)
Pitrsonek | 24. 1. 2011, 0:25 | comment
Ahoj děkuji za pěkný komentář, chci se zeptat jestli je nějaká možnost defaultně nastavit nepřihlášenému uživateli roly QUEST a jakmile se rozhodne přihlásit a jestli se přihlásit tak se mu role nastaví na roli nastavenou u účtu uživatele.
Jde mi o to, abych si mohl nadefinovat přístupy roli QUEST a tak ovlivnovat možnosti jen návštěvníků stránek bez přihlášení a účtu.
romiix.org | 24. 1. 2011, 9:47 | comment
thunderb | 9. 2. 2011, 17:57 | comment
Mne vyskocila Ladenka s hlaskou: PDOException
could not find driver
co s tym? Tej hlaske moc nerozumiem.
dakujem
public static function initialize() 15: { 16: $dbConfig = Environment::getConfig(‚database‘); 17: self::$connection = new Connection( 18: „{$dbConfig->driver}:host={$dbConfig->host};dbname={$dbConfig->database}“, 19: $dbConfig->username, 20: $dbConfig->password 21: ); 22: } 23: }
thunderb | 10. 2. 2011, 10:15 | comment
Vyriesene: ./configure –with-pdo-mysql
Climber007 | 17. 2. 2011, 20:51 | question
Prošel jsem fórum a všechno co se dalo, ale Nette se teprve prokousávám a nevím kde je chyba, vyskakuje toto:
Nette\AmbiguousServiceException
Cannot instantiate service ‚Nette\Security\IAuthenticator‘, class ‚AclProj\Security\Authenticator‘ not found.
forms/LoginForm.php 33: $user->login($form‚login‘->value, $form‚password‘->value);
Díky za pomoc Martin
Patrik Votoček | 18. 2. 2011, 8:06 | comment
Však to tam máš napsané: „Cannot instantiate service ‚Nette\Security\IAuthenticator‘, class ‚AclProj\Security\Authenticator‘ not found.“ volně přeloženo: „Nelze načíst službu ‚Nette\Security\IAuthenticator‘ třída ‚AclProj\Security\Authenticator‘ neexistuje.“
Climber007 | 18. 2. 2011, 15:06 | question
To chápu, ale nevím proč. Všechno mám podle tutoriálu use, zápis v config.ini i adresářovou strukturu.
ala3ama | 3. 3. 2011, 15:13 | comment
tiež mám ten istý problém ako Climber007. dva krát som to prešiel celé, ale chybu som nenašiel. Nejaké rady? thx for help
kralik | 16. 3. 2011, 16:24 | question
Ahoj, také mi vyběhla laděnka s problémem:
PDOException could not find driver
používám WAMP na windows. Povolený extensions „php_pdo_mysql“ mám, načten v pohodě.
Nevíte někdo co s tím?
Ještě by mě zajímalo ohledně databáze: dibi se nepoužívá? je možné či vhodné to na dibi přetvořit?
Mooc díky t0m
Whoopi | 16. 3. 2011, 23:13 | comment
Ahoj
S chybou „Cannot instantiate service ‚Nette\Security\IAuthenticator‘, class ‚AclProj\Security\Authenticator‘ not found.“ jsem se taky potkal a pátral jsem, proč nastala. Když jsem zkopíroval adresář AclProj do adresáře app tak to začalo fungovat a nejspíš je to způsobené konfigurací RobotLoaderu. Přidal jsem tedy do konfigurace řádek directory: %libsDir%¨ Jde o konfiguraci NEON
Nette\Loaders\RobotLoader:
option:
directory: [%appDir%]
directory: [%libsDir%]
run: true
Sice to začalo jedno fungovat, ale vyskytly se další chyby. Jsem taky na začátku používání Nette tak snad se tím prokoušu. Není to úplně prekérka :-)
Co se týče PDOException tak jsem na to taky narazil a laděním jména driveru v konfiguraci jsem to snad správně napravil. driver: mysql žádné driver: pdo_myslq
Tak snad Vám to pomůže ke spokojenosti a radosti programování s Nette :-)
Tomen | 17. 4. 2011, 13:31 | comment
Díky, za tento článek. Ve verzi Nette s .ini jsem neměl problém a do neonu se mi to nepovedlo přepsat :( končil jsem u hlášky: „PDOException – could not find driver“
Royce | 18. 4. 2011, 21:22 | comment
jo to mmi psalo taky.. vyřešil jsem to v neonu takto
development < common:
database:
driver: mysql
host: localhost
database: nette_acl
username: name
password: pass
vojty | 19. 9. 2011, 14:53 | question
Zdá se mi to nebo v aktuální betě Nette není obsažena třída Multirouter? ⇒ Jak tedy mám nastavit routování pro modální aplikaci? I konfigurace je jiná.
Jan Tvrdík | 6. 10. 2011, 23:15 | comment
Třída MultiRouter byla přejmenována ve verzi 2.0 na
Nette\Application\Routers\RouteList.
Kranox | 21. 12. 2011, 22:59 | comment
Tutoriál je už prerobený celý na verziu 2.0, alebo nie? :/
enigma | 4. 1. 2012, 23:56 | comment
Také by mě zajímalo?
talpa | 10. 2. 2012, 11:06 | comment
Docela me sere ze se reklo ze Nette 2 beta je po strance struktury kompletni a ejhle ve verzi 2.0 stable je nova vyprasenina neonu na ktery neni poradny navod a ktery mi odmita nacist lib AclProj a authentizator…
LuKo | 10. 2. 2012, 12:37 | comment
@talpa: http://doc.nette.org/cs/configuring nepomohlo?






LuKo | 3. 3. 2010, 14:18 | bug
Velmi zajímavý a přínosný článek, díky za něj. Zhruba uprostřed jsem narazil na chybu a překlep. Ve větě: V Presenteri sme preťažili metódu stratup(). – Metoda by měla být přepsaná, nikoli přetížená a drobný překlep v názvu metody.