- Úvodní stránka
- » Autentizace a autorizace
- » Best practices: ACL, Autentizaci, autorizaci…
#1 19. 4. 2009 22:54
- Roman Ožana
- Člen

- Místo: Ostrava
- Registrovaný: 18. 4. 2009
- Příspěvky: 39
- Web
Best practices: ACL, Autentizaci, autorizaci…
Nemáte někdo komplexní příklad (best practices) pro ACL, Autentizaci, autorizaci a řízení kontroly přístupu do jednotlivých Preseneteru atd.
Nějaké fajn střípky a náznaky tady už jsou (Jak resite kontrolu opravneni), ale nic komplexnějšího. Zkoušel jsem se tím dneska prokopat podle návodu, ale asi mi prostě stále něco uniká :-(
Akrobata jsem si prošel. Nezdá se mi moc DRY kontrolovat v každém Presenteru/Startup oprávnění přístupu.
Takže myslím, že by to mohlo fungovat nějak takhle:
- V tabulce Users přidám atribut role – (tam bude např. quest, admin atd.)
- Models/Users funkce authenticate na konci navíc vrátí return new Identity($row->email, $row->role, $row);
<?php
// Models/Users.php
class Users extends Object implements IAuthenticator
{
public function authenticate(array $credentials)
{
// input
$username = strtolower($credentials[self::USERNAME]);
$password = strtolower($credentials[self::PASSWORD]);
// search
if (StrValidate::email($username)) {
// autenticate by e-mail
$row = dibi::select('*')->from('user')->where('email=%s', $username)->fetch();
} else {
// autenticate by username
$row = dibi::select('*')->from('user')->where('username=%s', $username)->fetch();
}
// user validate
if (!$row) {
throw new AuthenticationException("Uživatel '$username' nenalezen.", self::IDENTITY_NOT_FOUND);
}
// password validate
if ($row->password !== md5($credentials[self::PASSWORD])) {
throw new AuthenticationException("Neplatné heslo.", self::INVALID_CREDENTIAL);
}
// unset password
unset($row->password);
// return identity
echo $row->role;
return new Identity($row->email, $row->role, $row);
}
}
- V bootstrap.php bude potřeba konstruovat ACL a patřičně jej nastavit.
// cast bootstrap.php
$acl = Environment::getService('Nette\Security\IAuthorizator');
$acl->addRole('admin');
$acl->addRole('quest'); // quest nemůže vše
$acl->addResource('dashboard'); // presenter
$acl->addResource('config'); // presenter
$acl->allow('admin'); // admin muze vse
$acl->allow('quest', array('dashboard')); // quest muze jen vse na dashboard
//echo $acl->isAllowed('admin', 'config') ? "allowed" : "denied";
- BasePresenter by asi měl kontrolovat přístupy/oprávnění (asi ve funkcích beforeRender nebo startup)
V config.ini mám toto
service.Nette-Security-IAuthenticator = Users
service.Nette-Security-IAuthorizator = Permission
První řádek chápu. Ten druhů dělá přesně co? Kde bude hledat třídu Permission?
Rád bych to všechno napsal dobře :-) a podělil se s ostatními.
Offline
#2 19. 4. 2009 23:41
- Honza Marek
- Moderator

- Místo: Kladno
- Registrovaný: 31. 3. 2007
- Příspěvky: 1281
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
service.Nette-Security-IAuthorizator = Permission
nastaví službu Nette\Security\IAuthorizator tak, aby ji obstarávala třída Permission. Hledá ji autoloader.
Místo nastavování Permission v bootstrapu bych vytvořil poděděnou třídu k Permission, třeba MyPermission a ty pravidla nastavil v konstruktoru. Pak stačí v configu nastavit
service.Nette-Security-IAuthorizator = MyPermission
a nezasírá se bootstrap. Přijde mi to tak lepší :-)
Offline
#3 20. 4. 2009 6:42
- Jan Tvrdík
- Nette guru

- Místo: Prostějov
- Registrovaný: 13. 4. 2008
- Příspěvky: 604
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
Offline
#5 20. 4. 2009 19:01
- Roman Ožana
- Člen

- Místo: Ostrava
- Registrovaný: 18. 4. 2009
- Příspěvky: 39
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
Myslím že guest i když to je Quest :)
Offline
#6 20. 4. 2009 19:06
- Roman Ožana
- Člen

- Místo: Ostrava
- Registrovaný: 18. 4. 2009
- Příspěvky: 39
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
To: Honza M.
- a nezasírá se bootstrap. Přijde mi to tak lepší :-) – souhlasím a díky za tip
To: Jan Tvrdík
- procházel jsem, ale chtěl bych to nastavovat natvrdo (alespoň zatím)
Offline
#7 3. 5. 2009 21:00
- Roman Ožana
- Člen

- Místo: Ostrava
- Registrovaný: 18. 4. 2009
- Příspěvky: 39
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
Takže slíbené (moje) best practices:
První věc ACL – pro správu oprávnění a rolí
<?php
class ACL extends Permission
{
function __construct() {
$this->addRoles(); // add roles
$this->addResources(); // add all resources
$this->allow('admin'); // admin muze vse
$this->allow('guest', array('Dashboard')); // quest muze jen vse na dashboard
}
/**
* Add all Resources
*/
function addResources() {
$this->addResource('Dashboard'); // takhle mám pojmenované presentery
$this->addResource('Config'); // ConfigPresenter.php
$this->addResource('Client');
}
/**
* Add all roles
*/
function addRoles()
{
$this->addRole('guest'); // moje role
$this->addRole('admin');
}
}
?>
ACL.php – umístil jsme do složky presenters (možná by se mu dalo najít i lepší místo) a v config.ini jsem přidal /service.Nette-Security-IAuthorizator = ACL/
Druhá věc boj s uživatelem:
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(255) COLLATE utf8_czech_ci NOT NULL,
`password` varchar(45) COLLATE utf8_czech_ci NOT NULL,
`username` varchar(255) COLLATE utf8_czech_ci NOT NULL,
`real_name` varchar(128) COLLATE utf8_czech_ci NOT NULL,
`role` varchar(128) COLLATE utf8_czech_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;
to máme tabulku users a model
<?php
class Users extends Object implements IAuthenticator
{
public function authenticate(array $credentials)
{
// input
$username = strtolower($credentials[self::USERNAME]);
$password = strtolower($credentials[self::PASSWORD]);
// search
if (StrValidate::email($username)) {
// autenticate by e-mail
$row = dibi::select('*')->from('user')->where('email=%s', $username)->fetch();
} else {
// autenticate by username
$row = dibi::select('*')->from('user')->where('username=%s', $username)->fetch();
}
// user validate
if (!$row) {
throw new AuthenticationException("Uživatel '$username' nenalezen.", self::IDENTITY_NOT_FOUND);
}
// password validate
if ($row->password !== md5($credentials[self::PASSWORD])) {
throw new AuthenticationException("Neplatné heslo.", self::INVALID_CREDENTIAL);
}
// unset password
unset($row->password);
// tady nacitam taky roli
return new Identity($row->email, $row->role, $row);
}
}
?>
Presenter, který se stará o přihlášení
<?php
class AuthPresenter extends Presenter
{
/** @persistent */
public $backlink = '';
public function actionLogin($backlink)
{
$form = new AppForm($this, 'form');
$form->addText('username', 'Uživatelské jméno:')
->addRule(Form::FILLED, 'Zadejte uživatelské jméno, nebo e-mail.');
$form->addPassword('password', 'Heslo:')
->addRule(Form::FILLED, 'Please provide a password.');
$form->addSubmit('login', 'Přihlásit');
$form->onSubmit[] = array($this, 'loginFormSubmitted');
$form->addProtection('Prosím přihlašte se znovu.');
$this->template->form = $form;
$this->template->title = "Přihlásit se";
}
public function renderLogout()
{
Environment::getUser()->signOut();
$this->flashMessage('Byl jste úspěšně odhlášen.');
$this->redirect('Auth:login');
}
public function loginFormSubmitted($form)
{
try {
$user = Environment::getUser();
$user->authenticate($form['username']->getValue(), $form['password']->getValue());
$this->getApplication()->restoreRequest($this->backlink);
$this->redirect('Dashboard:');
} catch (AuthenticationException $e) {
$form->addError($e->getMessage());
}
}
}
?>
jo a v config.ini řádek /service.Nette-Security-IAuthenticator = Users/
Nakonec jsem do BasePresenter (po kterém dědí všechny ostatní presentery, kromě AuthPresenter) tohle:
<?php
abstract class BasePresenter extends Presenter
{
protected function startup()
{
// tady kontroluju jestli mam opravneni zobrazit
if (!Environment::getUser()->isAllowed($this->name, $this->view))
{
//throw new InvalidStateException();
$this->redirect('Auth:logout'); // fuj přihlaš se
} else {
echo 'ANO OK mas na to pravo';
}
}
}
?>
Editoval Roman Ožana (3. 5. 2009 21:16)
Offline
#8 25. 5. 2009 16:55
- Jerry123456789
- Člen

- Registrovaný: 29. 12. 2008
- Příspěvky: 37
Re: Best practices: ACL, Autentizaci, autorizaci…
Pěkný Best Practices, jen jedna drobnost: nikde nemáš storeRequest a v logoutu (souvisí to s tim storem) do actionLogin nepředáváš $key ze storu. //edit: a ještě mi nepřijde dobré upozorňovat uživatele, který se nikdy nepřihlásil (ani nezaregistroval) „Byl jste úspěšně odhlášen“
Editoval Jerry123456789 (25. 5. 2009 16:58)
42
Offline
#9 29. 6. 2009 14:20
- wdolek
- Nette guru
- Místo: Praha
- Registrovaný: 18. 10. 2008
- Příspěvky: 210
Re: Best practices: ACL, Autentizaci, autorizaci…
jak je to s „guest“ uctem? tj aby kazdy kdo jen vleze na stranku mel guesti prava? – resit nekde v aplikaci jestli je user NULL nebo ma prava mi prijde „komplikovane“ (proc to nemit v jednom)… ?
Offline
#10 29. 6. 2009 14:43
- Ondřej Mirtes
- Moderator

- Místo: Praha
- Registrovaný: 8. 1. 2009
- Příspěvky: 1357
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
Stačí volat Environment::getUser()->isAllowed($resource,
$privilege) a je to pořešené podle jeho role…
Offline
#11 13. 7. 2009 14:18
- Lábus
- Nový člen
- Registrovaný: 8. 10. 2008
- Příspěvky: 6
Re: Best practices: ACL, Autentizaci, autorizaci…
v příkladu dáváte ověření identity do funkce startup() v BasePresenteru… jak resite situaci, kdy potrebujete pouzit startup() (napriklad pro nacteni nejake konfigurace) v jinem presentu, ktery od BasePresenteru dedi?
Offline
#12 13. 7. 2009 14:22
- jasir
- Nette guru

- Místo: Praha
- Registrovaný: 4. 12. 2008
- Příspěvky: 626
Re: Best practices: ACL, Autentizaci, autorizaci…
Lábus napsal(a):
v příkladu dáváte ověření identity do funkce startup() v BasePresenteru… jak resite situaci, kdy potrebujete pouzit startup() (napriklad pro nacteni nejake konfigurace) v jinem presentu, ktery od BasePresenteru dedi?
<?php
class MyPresenter extends BasePresenter {
public function startup() {
parent::startup();
...tvůj kód...
}
}
?>
:-)
Offline
#13 13. 7. 2009 14:41
- Ondřej Brejla
- Nette guru

- Místo: Praha
- Registrovaný: 20. 4. 2008
- Příspěvky: 439
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
To jsou základy dědičnosti, zkuste si pročíst některý ze článků zabývající se objekty v PHP. Ujasníte si určitě spoustu věcí. Z hlavy mě nenapadá žádný určitý, ale po troše googlení se jistě něco najde.
Offline
#14 13. 7. 2009 18:42
- kravčo
- Moderator

- Místo: Bratislava
- Registrovaný: 15. 6. 2008
- Příspěvky: 564
Re: Best practices: ACL, Autentizaci, autorizaci…
Práve na tento problém som prednedávnom narazil aj ja. Píšem
„problém“, pretože keď niekde jedno volanie
parent::startup() zabudnem, mám problém a s trochou šťastia
i slušnú bezpečnostnú dieru…
Jedným z riešení je deklarovať metódu final startup() na
mieste, kde sa kontrolujú práva, problém je, že ďalej ju už nemôžem
rozširovať, čo môže byť niekedy obmedzujúce.
Vyriešiť sa to dá napríklad trojicou metód:
final protected function startup()
{
$this->verify();
$this->initialize();
}
final protected function verify()
{
// ...
}
protected function initialize()
{
}
Toto mi ale príde vcelku komplikované a nepohodlné, pričom nemôžem
využívať zažitú metódu startup() a namiesto nej mám metódu
initialize()…
To, čo mi ešte napadlo je rozšíriť životný cyklus prezenteru o metódu
určenú na overovanie práv, ktorú by bolo možné na vhodnom mieste
deklarovať final a tým znemožniť neúmyselné obídenie tejto
kontroly.
public function run()
{
...
$this->verify();
$this->startup();
...
}
Čo si o tom myslíte?
Offline
#15 13. 7. 2009 23:47
- Ondřej Brejla
- Nette guru

- Místo: Praha
- Registrovaný: 20. 4. 2008
- Příspěvky: 439
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
Já myslím, že zařazení metody do životního cyklu pro kontolu práv je
dobrý nápad…ale nemyslím si, že je nutnost tuto metodu deklarovat jako
final, jasně, je to lepší…„nevědomky si jí nepřekryju“,
ale není to nutnost…rozhodně bych její finalitu nevynucoval…„co kdyby
náhodou někde…“;-) Navíc pokud se dobře zvolí název, tak je
minimální šance, že jí překryju neúmyslně. Ale pro zavedení rozhodně
jsem. Já startup() používám k inicializaci modelů a pokaždé
si vzpomenout, že musím volat parent…nic moc. Takže metodu pro práva
určitě ano.
Offline
#16 14. 7. 2009 0:23
- kravčo
- Moderator

- Místo: Bratislava
- Registrovaný: 15. 6. 2008
- Příspěvky: 564
Re: Best practices: ACL, Autentizaci, autorizaci…
Warden napsal(a):
…ale nemyslím si, že je nutnost tuto metodu deklarovat jako
final, jasně, je to lepší…„nevědomky si jí nepřekryju“, ale není to nutnost…rozhodně bych její finalitu nevynucoval…„co kdyby náhodou někde…“;-)
Framework samozrejme finalitu metódy vynucovať nemôže – v takom prípade by nebola veľmi použiteľná… Príkladom som sa snažil ukázať, že finálna by mala byť až vlastná implementácia a s predpokladom takejto metódy v životnom cykle to bude podstatne jednoduchšie.
class BasePresenter extends Presenter
{
final protected function verify()
{
// verification
}
protected function startup()
{
// initialization
}
}
Offline
#17 14. 7. 2009 0:34
#18 14. 7. 2009 9:53
Re: Best practices: ACL, Autentizaci, autorizaci…
Myslím že jen malá část lidí to ocení, zvláště kdyby to bylo svázané jen na oveření práva. Napadá mě ale přidat před startup událost onStartup stejně jako to je u shutdown:
// Presenter::run();
$this->onStartup($this);
$this->startup();
...
$this->onShutdown($this, $e);
$this->shutdown($e);
Byla by už pak na tobě co si budeš volat jestli inicialize, verify, whatever
Offline
#19 14. 7. 2009 10:45
- Ondřej Brejla
- Nette guru

- Místo: Praha
- Registrovaný: 20. 4. 2008
- Příspěvky: 439
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
kravco napsal(a):
Jj já vim jak si to myslel, jen sem nechtěl, aby se ona finalita této metody nedostala do jakých si Best practices (viz. nadpis vlákna) a stalo se z toho nějaké dogma. Jinak s tebou souhlasím.
K tomu jestli zbytečné nebo ne…teď tu máme jiné opravdu zbytečné metody prepare atd…ty se nevyužívají, tohle by se imho využívalo, jakmile by si na to lidé zvykli ;-)
Offline
#20 14. 7. 2009 21:08
- romansklenar
- Moderator

- Místo: Ostrava
- Registrovaný: 20. 7. 2008
- Příspěvky: 769
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
Souhlasím s Petrem, událost onStartup je vhodnější než
zesložiťovat životní cyklus presenteru. Chtělo by to ale, aby byla přímo
ve frameworku.
EDIT: akorát mě nenapadá, kde tu událost nadefinovat… Hned poté, co je aplikaci presenter známý tak ho spouští.
Offline
#21 15. 7. 2009 14:25
Re: Best practices: ACL, Autentizaci, autorizaci…
romansklenar napsal(a):
Souhlasím s Petrem, událost
onStartupje vhodnější než zesložiťovat životní cyklus presenteru. Chtělo by to ale, aby byla přímo ve frameworku.EDIT: akorát mě nenapadá, kde tu událost nadefinovat… Hned poté, co je aplikaci presenter známý tak ho spouští.
asi jedině v konstructoru (to není nejlepší), nebo takto:
class BasePresenter extends Presenter
{
public $onStartup = arary(array(__CLASS__,'initialize'));
static public function initialize($_this)
{
$_this->doSomething();
}
}
To má zase nevýhodu že můze přistupovat jen k public věcem presenteru.
Offline
#22 15. 7. 2009 17:39
- David Grudl
- Administrator

- Registrovaný: 8. 2. 2005
- Příspěvky: 4050
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
A co zajistit/vynutit, že bylo voláno parent::startup()? Na
to by se nějaký obecný mechanismus udělat dal.
Offline
#23 15. 7. 2009 23:10
Re: Best practices: ACL, Autentizaci, autorizaci…
Jako něco takového?
//Presenter
public function startup()
{
$this->bylVolanParentStartup = true;
}
//Presenter::run()
$this->startup();
if ($this->bylVolanParentStartup !== true)
throw new WtfException('Co děláš déžo, nezavolal si `parent::startup()`, to se mi ale vůbec nepáčí!!!!');
Offline
#24 18. 7. 2009 1:18
- David Grudl
- Administrator

- Registrovaný: 8. 2. 2005
- Příspěvky: 4050
- Web
Re: Best practices: ACL, Autentizaci, autorizaci…
Hele mě se to líbí ;)
Mám začít vyhazovat warning, když se nezavolá parent::startup()? Co myslíte? Jsem pro!
Offline
#25 18. 7. 2009 1:27
- Úvodní stránka
- » Autentizace a autorizace
- » Best practices: ACL, Autentizaci, autorizaci…


