Změna hesla v administraci (založeno na ACL)

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

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
+
0
-

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
+
0
-

@Š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
+
0
-
  1. updatePassword nema co delat v obecnem Repository – presun to do UserRepository
  2. na setPassword se vyprdni, udelej si metodu validatePassword, ktera overi spravnost hesla a ve zpracovani formu zavolas validatePassword a kdyz bude ok, tak updatePassword
  3. co tam sakra dela sha1 bez soli? :) – koukni do quickstartu https://doc.nette.org/cs/quickstart a pouzij radeji blowfish
Šaman
Člen | 2659
+
0
-

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)