Změna hesla v administraci (založeno na ACL)
- _enigma
- Člen | 17
Zdravím,
podle návodu jsem si prošel ACL a po nějakém čase se mi jej podařilo zprovoznit. Nyní bych chtěl v administraci udělat formulář pro změnu hesla. Chtěl jsem se v AdminPresenteru použít metodu authenticate, ale to mi končí výpisem laděnky:
Cannot read an undeclared property AdminModule\AdminPresenter::$authenticator.
AdminPresenter dědí z BasePresenteru.
BasePresenter.php
<?php
namespace AdminModule;
use Nette\Security\User;
abstract class BasePresenter extends \BasePresenter
{
public function startup()
{
parent::startup();
if ($this->name != 'Admin:Auth') {
if (!$this->user->isLoggedIn()) {
if ($this->user->getLogoutReason() === User::INACTIVITY) {
$this->flashMessage('Session timeout, you have been logged out');
}
$this->redirect('Auth:login', array(
'backlink' => $this->storeRequest()
));
} else {
if (!$this->user->isAllowed($this->name, $this->action)) {
$this->flashMessage('Access denied');
switch ($this->user->getIdentity()->role) {
case 'client':
$this->redirect('Client:');
break;
case 'admin':
$this->redirect('Admin:');
break;
}
}
}
}
}
/**
* Logout user
*/
public function handleLogout()
{
$this->user->logOut();
$this->flashMessage('You were logged off.');
$this->redirect('this');
}
}
AdminPresenter.php
<?php
namespace AdminModule;
use Nette\Application\UI\Form;
use Nette\Security\User;
use Nette\Security\Identity;
use Nette\Security\AuthenticationException;
use BootstrapFormRenderer\BootstrapRenderer;
class AdminPresenter extends BasePresenter {
protected function createComponentChangePassForm() {
$form = new Form;
$form->setRenderer(new BootstrapRenderer);
$form->addPassword('oldPassword', '')
->addRule(Form::FILLED, 'Enter your password')
->setAttribute('class', 'input-block-level')
->setAttribute('placeholder', 'actual password');
$form->addPassword('newPassword', '')
->addRule(Form::FILLED, 'Enter new password')
->setAttribute('class', 'input-block-level')
->setAttribute('placeholder', 'new password');
$form->addPassword('newPassword2', '')
->addRule(Form::FILLED, 'Enter new password')
->addRule(Form::EQUAL, "Please fill new password two times correct.", $form["newPassword"])
->setAttribute('class', 'input-block-level')
->setAttribute('placeholder', 'new password again');
$form->addSubmit('send', 'Change')->setAttribute('class', 'btn-large btn-block btn-primary');
$form->onSuccess[] = $this->processChangePassForm;
return $form;
}
public function processChangePassForm($form) {
$values = $form->getValues();
$user = $this->getUser();
try {
$this->authenticator->authenticate(array(
$user->getIdentity()->username,
$values->oldPassword
));
$this->authenticator->setPassword($user->getId(), $values->newPassword);
$this->flashMessage('Heslo bylo změněno.', 'success');
$this->redirect('Homepage:');
} catch (Nette\Security\AuthenticationException $e) {
$form->addError('Zadané heslo není správné.');
}
}
}
Prosím o nakopnutí nebo ukázku, jak postupovat pro úspěšnou změnu hesla uživatele.
Děkuji.
- Šaman
- Člen | 2659
Však to ta chybová hláška říká jasně, ne? V AdminPresenteru (ani
v BasePresenteru) nedefinuješ proměnnou $authenticator. Použij metodu injectAuthenticator()
.
A propo, ACL je authorizátor a s autentikátorem nijak nesouvisí. Autentikátor ověřuje, že uživatel za počítačem je skutečně ten, za koho se přihlašuje, zatímco autorizátor říká, jaké má onen (už ověřený) uživatel práva.
A ještě jedna poznámka: autentikátor by měl skutečně jen ověřovat uživatele. Nastavování hesla (stejně jako nastavování jiných údajů v uživatelském profilu) by měla řešit třída pro správu uživatelů (často pojmenovaná třeba UserRepository), která si jen pomocí autentikátoru nechá vytvořit hash hesla.
Eh, sorry, ještě na jednu věc bych rád upozornil. To, že uživatele
ověřuješ pomocí starého hesla tím, že zavoláš metodu
authenticate
v try-catch bloku není moc hezké. Spoléháš na
to, že při špatném hesle (nebo neexistujícím uživateli) ti tato metoda
vyhodí výjimku. Sice to vyhazování výjimky je popsáno v API dokumentaci,
ale bylo by čistější porovnávat vrácenou identitu. Tedy pracovat
s výsledkem funkce, kterou voláš, místo toho abys výsledek zahodil a jen
počkal, jestli nevyhodí výjimku.
Ještě lepší by ale bylo jen porovnat hash zadaného hesla s tím
v databázi. Osobně bych to udělal tak, že bych část metody
authenticate
vyhodil do samostatné funkce (třeba
checkPassword($user, $password)
, která by vracela bool) a v
authenticate
bych ji jen volal.
Editoval Šaman (23. 9. 2013 9:16)
- _enigma
- Člen | 17
@Šaman: Díky za tvůj čas a popis.
Zatím jsem to zbastil takto:
AdminPresenter.php
<?php
namespace AdminModule;
use Nette\Application\UI\Form;
use BootstrapFormRenderer\BootstrapRenderer;
class AdminPresenter extends BasePresenter {
/** @var AdminModule\UserRepository */
private $userRepository;
public function inject(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
protected function createComponentChangePassForm() {
$form = new Form;
$form->setRenderer(new BootstrapRenderer);
$form->addPassword('oldPassword', '')
->addRule(Form::FILLED, 'Enter your password')
->setAttribute('class', 'input-block-level')
->setAttribute('placeholder', 'actual password');
$form->addPassword('newPassword', '')
->addRule(Form::FILLED, 'Enter new password')
->setAttribute('class', 'input-block-level')
->setAttribute('placeholder', 'new password');
$form->addPassword('newPassword2', '')
->addRule(Form::FILLED, 'Enter new password')
->addRule(Form::EQUAL, "Please fill new password two times correct.", $form["newPassword"])
->setAttribute('class', 'input-block-level')
->setAttribute('placeholder', 'new password again');
$form->addSubmit('send', 'Change')->setAttribute('class', 'btn-large btn-block btn-primary');
$form->onSuccess[] = $this->processChangePassForm;
return $form;
}
public function processChangePassForm($form) {
$values = $form->getValues();
$userId = $this->getUser()->id;
$oldPassword = $values["oldPassword"];
$newPassword = $values["newPassword"];
try {
if ($this->userRepository->setPassword($userId,$oldPassword,$newPassword)) $this->flashMessage('Great! Password was changed.', 'success');
else $this->flashMessage('Please fill your correct actual password.', 'warning');
} catch (Nette\Security\AuthenticationException $e) {
$form->addError('Zadané heslo není správné.');
}
}
}
UserREpository.php
<?php
namespace AdminModule;
use Nette;
class UserRepository extends Repository
{
/**
* @return Nette\Database\Table\ActiveRow
*/
public function findByName($username)
{
return $this->findBy(array('username' => $username))->fetch();
}
/**
* @return Boolean
*/
public function setPassword($userId,$oldPassword,$newPassword)
{
$actualPassword = $this->findBy(array('id' => $userId))->fetch()->password;
$password = sha1($oldPassword);
if ($actualPassword == $password){
$this->updatePassword($userId, $newPassword);
return TRUE;
}
else return FALSE;
}
}
Repository.php
<?php
namespace AdminModule;
use Nette;
/**
* Provádí operace nad databázovou tabulkou.
*/
abstract class Repository extends Nette\Object
{
/** @var Nette\Database\Connection */
protected $connection;
public function __construct(Nette\Database\Connection $db)
{
$this->connection = $db;
}
/**
* Vrací objekt reprezentující databázovou tabulku.
* @return Nette\Database\Table\Selection
*/
protected function getTable()
{
return $this->connection->table("user");
}
/**
* Vrací všechny řádky z tabulky.
* @return Nette\Database\Table\Selection
*/
public function findAll()
{
return $this->getTable();
}
/**
* Vrací řádky podle filtru, např. array('name' => 'John').
* @return Nette\Database\Table\Selection
*/
public function findBy(array $by)
{
return $this->getTable()->where($by);
}
public function updatePassword($userId, $newPassword)
{
$this->getTable()->where(array('id' => $userId))->update(array(
'password' => sha1($newPassword),
));
}
}
Asi je to trochu prasárna, ale jede to. Teď se to budu snažit postupně vylepšovat. Pokud k tomuto máte kdokoliv jakékoliv komenty, budu za ně rád.
- David Matějka
- Moderator | 6445
- updatePassword nema co delat v obecnem Repository – presun to do UserRepository
- na setPassword se vyprdni, udelej si metodu validatePassword, ktera overi spravnost hesla a ve zpracovani formu zavolas validatePassword a kdyz bude ok, tak updatePassword
- co tam sakra dela sha1 bez soli? :) – koukni do quickstartu https://doc.nette.org/cs/quickstart a pouzij radeji blowfish
- Šaman
- Člen | 2659
K tomu, co psal Matěj bych přidal ještě
4. Všechny repository jsou součástí modelu a tudíž by neměly být v namespace AdminModulu. Model zkrátka stojí mimo strukturu modulů a presenterů.
5. Ten try-catch blok je teď úplně zbytečný, protože při špatném
heslu se žádná výjimka nevyhodí. Ono s těma výjimkama je to trochu
sporné téma – ideálně bys je měl používat jen kde hrozí, že se
algoritmus zhroutí a aplikace by se dostala do nedefinovaného stavu (např.
dělení nulou, čtení z neexistující tabulky). V případě toho
autentikátoru reálně hrozí, že se někdo pokusí přihlásit se špatným
heslem. To není nic výjimečného a osobně bych raději tento případ
ošetřil jinak, než výjimkou. Výjimka (výjimečný, neočekávaný
případ) by nastala třeba pokud by se nedalo připojit k db, nebo pokud by
neexistoval sloupec username
a password
.
Editoval Šaman (23. 9. 2013 21:05)