ACL v databázi a DI přes konsrtuktor
- stepos
- Člen | 36
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
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?…
- David Matějka
- Moderator | 6445
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
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
to Nette\Security\Permission si tam nepredavej jako zavislost, ale vytvor si tam instanci. a AclModel si vyzadej v konstruktoru
- David Matějka
- Moderator | 6445
protoze AclFactory ma za ukol Permission vytvorit (jak uz nazev napovida) – nette vola tu metodu create(), aby ziskalo instanci autorizatoru.
- stepos
- Člen | 36
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
- CZechBoY
- Člen | 3608
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
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
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)
- stepos
- Člen | 36
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.