ACL v databázi a DI přes konsrtuktor

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

Hezký den, jsem začátečník a několik dní řeším DI připojení k databázi. Zamotal jsem se do toho, pomůžete mi prosím? Hází mi to hlášku

Argument 1 passed to Model\BaseRepository::__construct() must be an instance of Nette\Database\Context, none given, called in /home/www/ikona/app/model/Acl.php on line 5

Pochopil jsem správně, že připojení řeším v BaseRepository a všechny potomci ho automaticky mají?

config.neon

nette:
	database:
		dsn: 'mysql:host=127.0.0.1;dbname=dbname'
		user: username
		password: pass
		debugger: true
		options:
			lazy: yes



php:
	date.timezone: Europe/Prague


application:
	errorPresenter: Error
	mapping:
		*: App\*Module\Presenters\*Presenter


session:
	expiration: 14 days


services:
	cacheStorage:
		class: Nette\Caching\Storages\DevNullStorage
	database: @nette.database.default.context
	- App\Forms\SignFormFactory
	- Model\AclModel



	authenticator: Model\Acl

Model\BaseRepository.php

namespace Model;
use Nette;
abstract class BaseRepository extends Nette\Object {

/** @var Nette\Database\Context */
 protected $database;

public function __construct(Nette\Database\Context $database) {
    $this->database= $database;
 }
}

AclModel:

<?php
namespace Model;
use Nette\Database\Context;
use Tracy\Debugger;
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of AclModel
 *
 * @author stepanp
 */
class AclModel extends BaseRepository  {

    const ACL_TABLE = 'i_users_users_role';
    const PRIVILEGES_TABLE = 'i_users_privil';
    const RESOURCES_TABLE = 'i_users_resources';
    const ROLES_TABLE = 'i_users_roles';

	public function getRoles() {

        return $this->database->query('SELECT r1.name, r2.name as parent_name
                               FROM '. self::ROLES_TABLE . ' r1
                               LEFT JOIN '. self::ROLES_TABLE . ' r2 ON (r1.parent_id = r2.id)
                              ');
    }

    public function getResources() {
        return $this->database->query('SELECT name FROM ['. self::RESOURCES_TABLE . '] ');
    }

    public function getRules() {
        return $this->database->query('
            SELECT
                a.allowed as allowed,
                ro.name as role,
                re.name as resource,
                p.name as privilege
                FROM [' . self::ACL_TABLE . '] a
                JOIN [' . self::ROLES_TABLE . '] ro ON (a.role_id = ro.id)
                LEFT JOIN [' . self::RESOURCES_TABLE . '] re ON (a.resource_id = re.id)
                LEFT JOIN [' . self::PRIVILEGES_TABLE . '] p ON (a.privilege_id = p.id)
                ORDER BY a.id ASC
        ');
    }
}

Acl.php

<?php
namespace Model;
class Acl extends \Nette\Security\Permission{
    public function __construct() {
        $model = new AclModel();

        foreach($model->getRoles() as $role)
            $this->addRole($role->name, $role->parent_name);

        foreach($model->getResources() as $resource)
            $this->addResource($resource->name);

        foreach($model->getRules() as $rule)
            $this->{$rule->allowed == 'Y' ? 'allow' : 'deny'}($rule->role, $rule->resource, $rule->privilege);
    }
}

SignFormFactory.php

namespace App\Forms;

use Nette,
	Nette\Application\UI\Form,
	Nette\Security\User;


class SignFormFactory extends Nette\Object
{
	/** @var User */
	private $user;


	public function __construct(User $user)
	{
		$this->user = $user;
	}


	/**
	 * @return Form
	 */
	public function create()
	{
		$form = new Form;
		$form->addText('username', 'Jméno:')
			->setRequired('Prosím zadej své přihlašovací jméno.');

		$form->addPassword('password', 'Heslo:')
			->setRequired('Prosím, zadej své heslo.');

//		$form->addCheckbox('remember', 'Keep me signed in');

		$form->addSubmit('send', 'Přihlásit');

		$form->onSuccess[] = array($this, 'formSucceeded');
		return $form;
	}


	public function formSucceeded($form, $values)
	{
		$this->user->setExpiration('20 minutes', TRUE);

		try {
			$this->user->login($values->username, $values->password);
		} catch (Nette\Security\AuthenticationException $e) {
			$form->addError($e->getMessage());
		}
	}

}

Můžete-li pomoci, prosím posuňte mě z bludného kruhu.
Díky

David Matějka
Moderator | 6445
+
+1
-

to AclModel nevytvarej rucne, ale vyzadej si to v konstruktoru jako zavislost – stejne jako to delas s tim Database\Context v konstruktoru BaseRepository

jo a Permission je lepsi vytvaret pres nejakou tovarnu, nez to dedit, viz https://cse.google.com/cse?…

stepos
Člen | 36
+
0
-

Díky moc, ale promiň, nerozumím, jak to myslíš aclModel nevytvářet ručně?

David Matějka
Moderator | 6445
+
+5
-

mas tam $model = new AclModel();, coz je rucni inicializace.

namisto toho si to vyzadej pres konstruktor

public function __construct(AclModel $model) {

Tim se to preda z DI kontejneru vcetne vsech zavislosti. Samozrejme musis mit to AclModel registrovany jako sluzbu

stepos
Člen | 36
+
0
-

díky, už to běží. S tím děděním permission a lepším řešením s továrnou to ještě musím pochopit a pročíst… Na začátek je toho nějak moc na pochopení a zboření všech svých zvyklostí…:) Díky a možná se ještě ozvu

stepos
Člen | 36
+
0
-

díky moc, budu si pamatovat – změna funkčnosti dané třídy->dědit, nastavit určité parametry->factory

Zkusil jsem to, ale asi jsem to překombinoval hlásí mi to chybu:
Circular reference detected for services: application.3, security.user, authorizator

udělal jsem to:

namespace Model;

class AclFactory extends \Nette\Object
{

    public function create(AclModel $model, \Nette\Security\Permission $permission) {

        foreach($model->getRoles() as $role)
            $permission->addRole($role->name, $role->parent_name);

        foreach($model->getResources() as $resource)
            $permission->addResource($resource->name);

        foreach($model->getRules() as $rule)
            $permission->{$rule->allowed == '1' ? 'allow' : 'deny'}($rule->role, $rule->resource, $rule->privilege);

		return $permission;
    }
}

a v configu:

...
	- Model\AclModel
	aclFactory: Model\AclFactory
	authorizator:
		class: Nette\Security\Permission
		create: @aclFactory::create()

	authenticator: Model\Authenticator

Nebo tam dělá paseku ještě ten authenticator?

Díky za pomoc

David Matějka
Moderator | 6445
+
+1
-

to Nette\Security\Permission si tam nepredavej jako zavislost, ale vytvor si tam instanci. a AclModel si vyzadej v konstruktoru

stepos
Člen | 36
+
0
-

Jo, už to jde. Ale mám zamotanou hlavu. Kdy tedy dělat nové instance a kdy závislosti? Proč AclModel jako závislost a permission jako nová instance?

David Matějka
Moderator | 6445
+
+2
-

protoze AclFactory ma za ukol Permission vytvorit (jak uz nazev napovida) – nette vola tu metodu create(), aby ziskalo instanci autorizatoru.

stepos
Člen | 36
+
0
-

Diky moc,
jestě jeden dotaz, tentokrát k databázi, existuje nějaký způsob, jak obsah sloupce z tabulky vrátit v poli (ne asociativním)?
Myslel jsem na toArray(), ale to asi vrací jen řádky. U $row to funguje, u $roles ne.

	$roles = $this->database->table(self::ROLES_TABLE)->select(self::COLUMN_ROLES_ROLE)->where(self::COLUMN_ROLES_USER, $row[self::COLUMN_ID]);
$roles_arr = $roles->toArray();		 //chyba Call to undefined method Nette\Database\Table\Selection::toArray().

$row = $this->database->table(self::TABLE_NAME)->where(self::COLUMN_NAME, $username)->fetch();
$arr = $row->toArray();	//OK
Pavel Kravčík
Člen | 1196
+
0
-

Nechybí Ti tam tohle?

	->fetchAll();
David Matějka
Moderator | 6445
+
0
-

mohlo by jit

->fetchPairs(NULL, self::COLUMN_ROLES_ROLE)
stepos
Člen | 36
+
0
-

Pavel Kravčík napsal(a):

Nechybí Ti tam tohle?

	->fetchAll();

S tímto + toArray(), te píšs stejnou chybu.

stepos
Člen | 36
+
0
-

David Matějka napsal(a):

mohlo by jit

->fetchPairs(NULL, self::COLUMN_ROLES_ROLE)

Super, to je ono. díky

stepos
Člen | 36
+
0
-

Diky za vsechny rady. Ještě mam jeden dotaz, jak nejjednoduseji logovat vsechny dotazy insert update?
Rad bych si je ukladal do specialni tabulky kvuli kontrole, co system dela. Je na to neco uz hotoveho?
Diky S

CZechBoY
Člen | 3608
+
0
-

Můžeš se přihlásit k odběru události na \Nette\Database\Connection::onQuery a tam si parsovat QueryString z druhýho parametru, co ti přijde do toho handleru onQuery.

Editoval CZechBoY (28. 1. 2016 18:05)

stepos
Člen | 36
+
0
-

Moc se omlouvám, myslel jsem, že už začínám DI v nette chápat, ale asi to bude delší proces.
Potřebuju si po úspěšné autentizaci uložit do sessions údaj o uživatelském jménu → aby k tomu údaji mohl starý systém vnořený v nette adresářové sturktuře.

Když to udělám ručně

public function __construct($identity_name)
	{
		$this->session=new  Nette\Http\Session;

        $this->sessionSection = $this->session->getSection(self::OLD_SYSTEM_SESSION_SECTION);
		$this->session->{self::OLD_SYSTEM_SESSION_VARIABLE}=$identity_name;
	}

hází mi to chybu

*Argument 1 passed to Nette\Http\Session::__construct() must implement interface Nette\Http\IRequest, none given, called in /home/www/ikona/app/model/OldSystemIdentity.php on line 18 and defined
*
Když to udělám přes závislost:

public function __construct($identity_name, Nette\Http\Session $session)
	{
		$this->session=$session;
		$this->sessionSection = $this->session->getSection(self::OLD_SYSTEM_SESSION_SECTION);
		$this->session->{self::OLD_SYSTEM_SESSION_VARIABLE}=$identity_name;
	}

Hází chybu
Argument 2 passed to Model\OldSystemIdentity::__construct() must be an instance of Nette\Http\Session, none given, called in /home/www/ikona/app/presenters/SignPresenter.php on line 30 and defined.

Spouštím to v SignPresenter:

namespace App\Presenters;

use Nette,
	App\Forms\SignFormFactory;



/**
 * Sign in/out presenters.
 */
class SignPresenter extends BasePresenter
{
	/** @var SignFormFactory @inject */
	public $factory;

	/**
	 * Sign-in form factory.
	 * @return Nette\Application\UI\Form
	 */
	protected function createComponentSignInForm()
	{
		$form = $this->factory->create();
		$form->onSuccess[] = function ($form) {

			//nastavíme starému systému identitu
			$osi=new \Model\OldSystemIdentity($this->user->getIdentity());
			$form->getPresenter()->redirect('Old:default');
		};
		return $form;
	}


	public function actionOut()
	{
		$this->getUser()->logout();
		$this->flashMessage('You have been signed out.');
		$this->redirect('in');
	}

}

v configu mám jen

session:
	expiration: 14 days

když jsem přidal do services:

  • Nette\Http\Session

Service ‚application.1‘: Multiple services of type Nette\Http\Session found: session.session, 31_Nette_Http_Session

Tak nevím, pochopitelně by bylo nejjednodušší rovnou to tam uložit pres $_SESSIONS a neřešit to, ale, když už to nette má, tak bych to chtěl přes nette:)

Děkuju za pomoc, vím, že to je asi několikátý dotaz na toto téma, ale nějak mě mate, kde to volat, jestli ještě v modelu při autentizaci, nebo v SignFormFactory. Volám to co nejpozdějí, protože nevím přesně, kdy sessions startuje. Moc si vaší pomoci vážím.

stepos
Člen | 36
+
0
-

Omlouvám se zase za hloupý dotaz, ale plácám se a zatím jsem přesně nepochopil user->isAllowed.
V presenteru mi totiž $this->user->isAllowed(‚Homepage‘) vrací false, přestože v acl to povoleno má view. Myslel jsem, že pokud chybí druhý parametr u metody isAllowed, bere se, že se jedná o jakékoliv privilegium.
$this->user->isAllowed(‚Homepage‘,‚view‘) vrací true, tak jsem z toho jelen.

v configu:

services:
	router: App\RouterFactory::createRouter
	database: @nette.database.default.context
	- App\Forms\SignFormFactory
	- Model\AclModel
	aclFactory: Model\AclFactory
	authorizator:
		class: Nette\Security\Permission
		create: @aclFactory::create()

	authenticator: Model\Authenticator

AlcFactory:

namespace Model;

class AclFactory extends \Nette\Object
{
    public function create(AclModel $model) {
		$permission = new \Nette\Security\Permission ;
        foreach($model->getRoles() as $role)
            $permission->addRole($role->name, $role->parent_name);

        foreach($model->getResources() as $resource)
            $permission->addResource($resource->name);

        foreach($model->getRules() as $rule){
		\Tracy\Debugger::bardump($rule,"Role");
            $permission->{$rule->allowed == '1' ? 'allow' : 'deny'}($rule->role, $rule->resource, $rule->privilege);
     }

		return $permission;
    }
}

tady mi tu roli bardump vypíše:

Nette\Database\Row #a10b
allowed => 1
role => "Root" (4)
resource => "Homepage" (8)
privilege => "view" (4)

Autentikátor:

class Authenticator extends \Model\BaseRepository implements NS\IAuthenticator
{
	const
		TABLE_NAME = 'i_users',
		COLUMN_ID = 'id',
		COLUMN_NAME = 'login',
		COLUMN_PASSWORD_HASH = 'password',
		USER_ROLES_TABLE = 'i_users_users_roles',
		COLUMN_ROLES_USER = 'user_id',
		COLUMN_ROLES_ROLE = 'role_id',
		ROLES_NAMES_TABLE =  'i_users_roles',
		COLUMN_ROLE_NAME = 'name',
		COLUMN_ROLE_ID = 'id' 	;

	/**
	 * Performs an authentication.
	 * @return Nette\Security\Identity
	 * @throws Nette\Security\AuthenticationException
	 */
	public function authenticate(array $credentials)
	{
		list($username, $password) = $credentials;

		$row = $this->database->table(self::TABLE_NAME)->where(self::COLUMN_NAME, $username)->fetch();
		if (!$row) {
			throw new NS\AuthenticationException('Jméno nebo heslo není zadáno správně.', self::IDENTITY_NOT_FOUND);

		} elseif (!NS\Passwords::verify($password, $row[self::COLUMN_PASSWORD_HASH])) {
//			\Tracy\Debugger::dump(NS\Passwords::hash($password));
			throw new NS\AuthenticationException('Jméno nebo heslo není zadáno správně.', self::INVALID_CREDENTIAL);

		} elseif (NS\Passwords::needsRehash($row[self::COLUMN_PASSWORD_HASH])) {
			$row->update(array(
				self::COLUMN_PASSWORD_HASH => Passwords::hash($password),
			));
		}

		$roles_arr = $this->database->table(self::ROLES_NAMES_TABLE)->select(self::COLUMN_ROLE_NAME)->where(self::COLUMN_ROLE_ID,$this->database->table(self::USER_ROLES_TABLE)->select(self::COLUMN_ROLES_ROLE)->where(self::COLUMN_ROLES_USER, $row[self::COLUMN_ID]))->fetchPairs(NULL, self::COLUMN_ROLE_NAME);

		$arr = $row->toArray();
		unset($arr[self::COLUMN_PASSWORD_HASH]);

		return new NS\Identity($row[self::COLUMN_ID], $roles_arr, $arr);
	}

A když v presenteru vypíšu:

namespace App\Presenters;

use Nette,
	App\Forms\SignFormFactory;



/**
 * Sign in/out presenters.
 */
class SignPresenter extends BasePresenter
{
	/** @var SignFormFactory @inject */
	public $factory;
	private $work_space='';
	/**
	 * Sign-in form factory.
	 * @return Nette\Application\UI\Form
	 */
	protected function createComponentSignInForm()
	{

		$form = $this->factory->create();
		$form->onSuccess[] = function ($form) {

			//nastavíme starému systému identitu
			\Tracy\Debugger::barDump($this->user->isAllowed('Homepage'),'Alowed');

			\Tracy\Debugger::bardump($this->user->getIdentity()->roles,"Role id:");
			if(!$this->user->isAllowed('Homepage')
				throw new \Nette\InvalidArgumentException("Na homepage není právo přistoupit");

		};
		return $form;
	}


	public function actionOut()
	{
		$this->getUser()->logout();
		$this->flashMessage('You have been signed out.');
		$this->redirect('in');
	}

}

Hází mi tu chybu, že není možné přistoupit na home page.
Přitom výpis bardumpu je:

Alowed

FALSE

Role id:

array (1)
0 => "Root" (4)

Vidíte někdo prosím někde chybu? Děkuji za pomoc.

Editoval stepos (8. 2. 2016 22:30)

CZechBoY
Člen | 3608
+
0
-

Proč nemůžeš dát do druhýho parametru (volání $user->isAllowed()) to view?

Aurielle
Člen | 1281
+
+1
-

Nebere se jakékoliv, berou se všechna privilegia. Viz API.

stepos
Člen | 36
+
0
-

Díky, po odeslání jsem pochopil, ze jsem to nedomyslel, když jsem se domníval, že pokud je acl aspoň jedno privilegium povolene, vrátí true. A kdy tedy je možné použít isAllowed jen s jedním parametrem? K čemu by se to dalo prakticky použít?
Díky moc, vážím si času, který mi dáváte.

CZechBoY
Člen | 3608
+
0
-

Asi nikdy, ale ty Permission::ALL konstanty se používaj spíš pro nastavení oprávnění.
Tzn nějaký roli nastavim, že může dělat cokoliv s nějakým resourcem.