#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.


Tvořím internetové stránky s chutí – www.omdesign.cz

Blog: nabito.net | Twitter: @OzzyCzech.

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

Offline

 

#4 20. 4. 2009 12:38

zakjan
Člen
Místo: Praha
Registrovaný: 17. 2. 2009
Příspěvky: 10
Web

Re: Best practices: ACL, Autentizaci, autorizaci…

Jedna poznámka – quest nebo guest? ;)

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 :)


Tvořím internetové stránky s chutí – www.omdesign.cz

Blog: nabito.net | Twitter: @OzzyCzech.

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)

Tvořím internetové stránky s chutí – www.omdesign.cz

Blog: nabito.net | Twitter: @OzzyCzech.

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)


Tvořím internetové stránky s chutí – www.omdesign.cz

Blog: nabito.net | Twitter: @OzzyCzech.

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

Jod
Nette guru
Registrovaný: 24. 9. 2008
Příspěvky: 790
Web

Re: Best practices: ACL, Autentizaci, autorizaci…

Trošku zbytočné


Všetko je v Akrabat.forms v examples distribúcie. Zomg, puff..

Offline

 

#18 14. 7. 2009 9:53

PetrP
Moderator
Místo: Praha
Registrovaný: 15. 7. 2008
Příspěvky: 610
Web

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

PetrP
Moderator
Místo: Praha
Registrovaný: 15. 7. 2008
Příspěvky: 610
Web

Re: Best practices: ACL, Autentizaci, autorizaci…

romansklenar napsal(a):

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í.

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

PetrP
Moderator
Místo: Praha
Registrovaný: 15. 7. 2008
Příspěvky: 610
Web

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

Jod
Nette guru
Registrovaný: 24. 9. 2008
Příspěvky: 790
Web

Re: Best practices: ACL, Autentizaci, autorizaci…

To by asi viac vecí ulahčilo než zťažilo.


Všetko je v Akrabat.forms v examples distribúcie. Zomg, puff..

Offline

 

Zápatí