Autentizace a autorizace pomocí anotací

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

Myslím, že mi to funguje, tak jak bych potřeboval, ale raději se chci poradit, zda-li je to takto vůbec správně.

namespace Medved\Security;

use Nette\Security\Permission;

class Authorizator extends Permission
{
    public function __construct()
    {
        $this->addRoles();
        $this->addResources();

        // registered
        $this->allow('registered', 'Homepage', 'default');
    }

    private function addRoles()
    {
        $this->addRole("guest");
        $this->addRole("registered", "guest");
        $this->addRole("admin", "registered");
    }

    private function addResources()
    {
        $this->addResource('Homepage');
    }
}
namespace Medved\Application\UI;

use Nette\Application\ForbiddenRequestException;
use Nette\Security\User;

class Presenter extends \Nette\Application\UI\Presenter
{
    /**
     * {@inheritdoc}
     */
    public function checkRequirements($element)
    {
        $user = $this->user;

        if ($element->hasAnnotation("Secured")) {
            if (!$user->isLoggedIn()) {
                if ($user->getLogoutReason() == User::INACTIVITY) {
                    $this->flashMessage("Byl jsi odhlášen, protože jsi nebyl dlouho aktivní.");
                }

                $this->flashMessage("Pro vstup do této části webu se musíš přihlásit.");
                $this->redirect("Sign:in", array("backlink" => $this->storeRequest()));
            }

            if ($element->hasAnnotation("Resource")) {
                if (!$user->isAllowed($element->getAnnotation("Resource"), $this->action)) {
                    throw new ForbiddenRequestException();
                }
            }
        }
    }
}
/**
 * @Secured
 * @Resource("Homepage")
 */
class HomepagePresenter extends BasePresenter
{
    public function renderDefault()
    {

    }

    public function renderEdit()
    {

    }
}
Ot@s
Backer | 476
+
0
-

Principiálně to provozuji velice podobně (anotuji až na úroveň metod). Za přečtení stojí starý, ale stále platný příspěvek od Davida šikovnější permission (možná jsi ho už četl).

Edit: možná by stálo za to to dotáhnout a dát to do kuchařky

Editoval Ot@s (7. 6. 2012 9:56)

miler
Člen | 75
+
0
-

Pokud bych to chtěl posunout o úroveň níže na anotace metod (asi včetně privilegia v anotaci), stačí přepsat na:

class HomepagePresenter extends BasePresenter
{
    /**
    * @Secured
    * @Resource("Homepage")
    * @Privilege("Show")
    */
    public function renderDefault()
    {

    }

a

(!$user->isAllowed($element->getAnnotation("Resource"), $element->getAnnotation("Privilege"))

a anotovat tedy místo celého presenteru „jen“ metody?

Doplňkový dotaz: V celém principu ACL mi totiž není moc jasné jak správně definovat ta privilegia. Většina ukázek a textů hovoří o privilegiích jako o akcích presenteru – tzn. pro každou akci přidat do ACL nové pravidlo?

Děkuji.

Šaman
Člen | 2664
+
0
-

Nechci zakládat nové téma, tak to píšu sem:

Existuje nějaké ‚oficiální‘ rozšíření na popis práv pomocí anotací? Nebo jsou všechny anotace, které jsem dohledal pouze vlastní přetížení metody checkRequirements?
Nette 2.0.7 zná jen @User loggedIn, na fóru a v dokumentaci k anotacím jsem ale narazil i na @secured, @User (role=admin) apod.

enumag
Člen | 2118
+
0
-

Ty příklady z dokumentace reflexí jsou pouze příklady nějakých anotací, netnamená to, že mají v Nette nějaký význam.

Nette samotné nic jiného než @User loggedIn opravdu nezná. V budoucnu možná něco více znát bude, ale to asi jen tak nebude. Předpokládám, že to bude tehdy až někdo ukáže dobrou komplexní implementaci zabezpečení na bázi anotací, což zatím nikdo neukázal. Já osobně takovou implementaci mám (a asi zdaleka nebudu sám), není to ale dostatečně připravené, abych to zveřejnil.

EDIT: O žádném oficiálním rozšíření rovněž nevím.

Editoval enumag (4. 12. 2012 13:51)

Šaman
Člen | 2664
+
0
-

Díky, po včerejším pátrání jsem si myslel, že to takhl bude, ale teď to vím a můžu si v klidu psát vlastní :D

enumag
Člen | 2118
+
0
-

Co to zkusit dotáhnout ve dvou? ;-)

Šaman
Člen | 2664
+
0
-

Já se v tom nejdřív trochu rozkoukám, přepíšu kontroly ze startup do checkRequirements a pak se uvidí. Co zplodím sem klidně hodím, ale bude to minimalistický..

enumag
Člen | 2118
+
0
-

OK. :-) Já mám docela dobře udělaný nějaký věci kolem, ale implementace těch samotných anotací mi chybí, respektive je jen částečná a ne úplně taková jakou bych chtěl. Bohužel teď asi měsíc budu mít velmi málo času takže se rád inspiruji jinde a udělám z toho nějaký větší celek.

akadlec
Člen | 1326
+
0
-

@enumag: tak co pohnul si s tím nějak?

enumag
Člen | 2118
+
0
-

@akadlec: Jojo, mám to ve stádiu plně funkční beta verze. Chci ale nejdříve mít jasno zda ve 2.1 final bude nebo nebude spravený autorizátor. Když ne budu to muset ještě řešit.

pata.kusik111
Člen | 78
+
+1
-

Zatím neřeší odesílání formulářů, ale podchytí to jak action, tak handle. Navíc je možné udělat celý presenter Secured nebo mu přidat Resource a metody to od něj pak budou „dědit“. Funguje v Nette 2.0.14. Snad to někomu pomůže:

<?php
public function checkRequirements($element)
	{
		$user = $this->user;

		$class = ($element instanceof \Nette\Reflection\Method) ? $element->getDeclaringClass() : $element;
		$element = array_merge($class->getAnnotations(), $element->getAnnotations());

		if (array_key_exists("Secured", $element)
		) {
			if (!$user->isLoggedIn()) {
				if ($user->getLogoutReason() == User::INACTIVITY) {
					$this->flashMessage("Byl jsi odhlášen, protože jsi nebyl dlouho aktivní.");
				}

				$this->flashMessage("Pro vstup do této části webu se musíš přihlásit.");
				$this->redirect("Sign:in", array("backlink" => $this->storeRequest()));
			}

			if (array_key_exists("Resource", $element) &&  array_key_exists("Privilege", $element)) {
				if (!$user->isAllowed($element["Resource"][0], $element["Privilege"][0])) {
					throw new ForbiddenRequestException();
				}
			}
		}
	}
?>
fucikm
Člen | 4
+
0
-

@enumag Zajímalo by mě, zda jsi svoje řešení už zveřejnil nebo ne. Jestli mám znovu vynalézat kolo nebo jsi ochotný půjčit to svoje. :-)

enumag
Člen | 2118
+
0
-

@fucikm Nezveřejnil a zatím to ani neplánuji.

CZechBoY
Člen | 3608
+
0
-

Určitě by mě to taky zajímalo. Já zatím používám tohle:

	/**
	 * @var var $element
	 * @forward Homepage:|Error:blank
	 */
	public function checkRequirements($element) {
		$this->startup(); // tu se nastaví storage, autorizátor

		$class = $element->name;
		$method = 'action' . $this->action;

		$signal = $this->getSignal();
		if ($signal) {
			if ($signal[0]) {
				$class = $this->getComponent($signal[0]);
			}
			$method = 'handle' . $signal[1];
		}

		try {
			$c = new Nette\Reflection\Method($class, $method);

			$fail = false;

			if ($c->hasAnnotation('logged')) {
				if (!$this->user->loggedIn) {
					$this->flashMessage('Pro tuto akci musíte být přihlášen(a).', 'info');
					$fail = true;
				}
			} elseif ($c->hasAnnotation('only_guest')) {
				if ($this->user->loggedIn) {
					$this->flashMessage('Pro tuto akci nesmíte být přihlášen(a).', 'warning');
					$fail = true;
				}
			}

			if ($fail === true) {
				if ($this->isAjax()) {
					$this->forward('Error:blank');
				} else {
					$this->forward('Homepage:');
				}
			}
		} catch(\ReflectionException $e) {

		}
	}

Našel jste někdo chybu nebo něco co by se hodilo ještě kontrolovat?
Mělo by to umět jak kontrolu presenterů tak i komponent. Ale jsem tak trochu vedle v tomhle odvětví tak bych si radši nechal poradit :-)

enumag
Člen | 2118
+
0
-

@CZechBoY Jsi mimo v tom že $element co dostáváš jako parametr už je reflexe (buď třídy nebo metody) čili je zbytečné abys sám nějaké reflexe vytvářel. Volání startup je taky nežádoucí ačkoli chápu proč to děláš (startup se volá až po checkRequirements pro třídu presenteru). Ale jde to napsat i bez toho. Osobně v té metodě checkRequirements jen všechno deleguju na službu která provádí samotné kontroly.

Editoval enumag (5. 9. 2014 22:48)

CZechBoY
Člen | 3608
+
0
-

@enumag Takže z toho $element se jde dostat přímo k volaný metodě? To se mi právě nepodařilo :-/ :-(

enumag
Člen | 2118
+
0
-

@CZechBoY Ta metoda checkRequirements je volaná vícekrát. Poprvé pro třídu, poté postupně pro metody action*, handle* a render* – těsně předtím než je ta metoda reálně zavolána.

https://api.nette.org/…ent.php.html#…

CZechBoY
Člen | 3608
+
0
-

@enumag
Aha… takže jak mám docílit toho, abych vykonával kontrolu až když vím jaká metoda se volá? Prostě vrátit return true, když nevim metodu?

Nepořešíme to někde na IM, případně aby z toho vyšlo nějaký extension…

enumag
Člen | 2118
+
0
-

@CZechBoY Jednoduše, vůbec nezjišťuješ jakou reflexi jsi dostal, prostě se jí podíváš na anotace a zkontroluješ je. Jo a ta metoda checkRequirements nemá vracet true nebo false, pokud kontrola neprojde máš vyhodit výjimku, případně ukončit presenter pomocí forward/redirect/sendResponse/terminate (osobně doporučuji pouze vyhodit výjimku a tu pak zpracovat v ErrorPresenteru).

Pokud se chceš na něco zeptat přes IM, klidně můžeš. V nette konferenci na jabberu jsem pořád.

Editoval enumag (5. 9. 2014 23:40)

ChocoTUx
Člen | 31
+
0
-

Ahoj,

moc pěkná diskuze a rád si implementuji nějakou verzi anotací na premission.
Každopádně ještě trochu bojuji s tím prvkem $element. Všude ho používáte jako nějakou danou věc.

Dalo by se získávat takhle nějak (s tím že nechám volnou ruku jestli je secured nad třídou nebo metodou)?

$this->getReflection()->getAnnotations());
$this->getReflection()->getMethod('render'.$this->getView())->getAnnotations());
enumag
Člen | 2118
+
0
-

@ChocoTUx $element je instance \Nette\Reflection\ClassType nebo \Nette\Reflection\Method (je jedno co z toho).