Jak se jako admin přihlásit na uživatele bez znalosti jeho hesla?
- scientific
- Člen | 94
Mám následující kód a hledám, kde je ta funkce „login()“. Že by to byla přímo nějaká nette funkce mimo můj projekt? Pokud ano, jak bych mohl zařídit aby se admin mohl přihlásit na uživatele, aniž by znal jeho heslo? Máte nějaký tip prosím?
<?php
use Nette\Application\UI\Form;
class LoginForm extends Form {
public function __construct(Nette\Application\UI\Presenter $parent, $name)
{
parent::__construct($parent, $name);
$this->addText("email");
$this->addPassword("password");
$this->addSubmit("send");
$this->onSuccess[] = array($this,"success");
}
public function success(Form $form, $values)
{
include_once(dirname(__FILE__)."/../../www/securimage/securimage.php");
$securimage = new Securimage();
if (!$securimage->check($_POST['captcha_code'])) {
$form->addError("E_CAPTCHA");
return;
}
try {
$user = $this->parent->getUser();
$user->login($values->email, $values->password);
$_SESSION["ldap_user"] = $values->email;
$_SESSION["ldap_pass"] = $values->password;
$this->parent->flashMessage("O_PRIHLASENI", "success");
} catch (Nette\Security\AuthenticationException $e) {
$form->addError($e->getMessage());
}
}
}
?>
Asi je to zde: vendor/nette/security/src/Security/User.php: public function
login($id = NULL, $password = NULL)
Ale nerad bych upravoval vendor, hádám, že to není vhodné, například,
protože aktualizace Nette by to hádám přepsala.
Existuje vhodnější řešení, které ale není zbytečně složité, ale fungovalo by jako kompromis tak, aby to fungovalo jak potřebuji a zároveň by se využila ta interní funkce login() ?
Editoval scientific (10. 1. 2023 16:05)
- scientific
- Člen | 94
Jiná varianta v případě, že by to v mé verze nebylo dostupné by neexistovala? Řekl bych, že verze nette je poměrně zastaralá, viz níže výpis z composeru. To co zmiňuješ asi tedy není možné. Děkuji za informaci. například zavolat doLogin($user); (pokud existuje), která je až uvnitř po ověření hesla v té funkci Login($user, $password);?
name : nette/nette
versions : * v2.3.2
requires
latte/latte 2.3.2
nette/application 2.3.3
nette/bootstrap 2.3.1
nette/caching 2.3.2
nette/component-model 2.2.1
nette/database 2.3.2
nette/deprecated 2.3.0
nette/di 2.3.3
nette/finder 2.3.0
nette/forms 2.3.2
nette/http 2.3.1
nette/mail 2.3.0
nette/neon 2.3.1
nette/php-generator 2.3.0
nette/reflection 2.3.0
nette/robot-loader 2.3.0
nette/safe-stream 2.3.0
nette/security 2.3.0
nette/tokenizer 2.2.0
nette/utils 2.3.1
tracy/tracy 2.3.2
- Ondris
- Člen | 37
A přihlášení řešíš přes IAuthenticator? V jednom starším projektu mám tohle přihlášení:
<?php
use Nette\Security as NS;
class MyAuthenticator implements NS\IAuthenticator
{
public $userRepository;
function __construct(\Model\UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
function authenticate(array $credentials)
{
list($name, $password) = $credentials;
$user = $this->userRepository->getUserByName($name);
if (!$user) {
throw new NS\AuthenticationException('User not found.');
}
if (!NS\Passwords::verify($password, $user->getPassword())) {
throw new NS\AuthenticationException('Invalid password.');
}
return new NS\Identity($user->getId(), null, ['username' => $user->getName()]);
}
}
V tomhle případě mi stačí získat Identitu stylem:
return new NS\Identity($user->getId(), null, [‚username‘ ⇒
$user->getName()]);
a prostě vynechám ověřování hesla. Jen si tam z databáze předám dané
id uživatele
- Šaman
- Člen | 2662
Mohu se zeptat, proč se má admin přihlásit jako uživatel? Vrtá mi to hlavou, přijde mi to jako bad practise. Pokud systém sleduje pohyb uživatele v aplikaci, loguje akce, nebo třeba nabízí dříve navštívené produkty, tak to najednou začne dávat nepravdivé informace, pokud přijmu rovnici uživatel = člověk (dokud někomu nedá heslo).
Jiná úloha by byla jak zajistit, aby se uživatel admin
mohl
přepnout do režimu, který mu nasimuluje práva konkrétního uživatele.
Editoval Šaman (16. 1. 2023 14:47)
- Marek Bartoš
- Nette Blogger | 1274
@Šaman Oprávnění nejsou jen o rolích a privilegiích, ale i o callbacích, které kontrolují například vlastníka záznamu. Kompletní simulaci tak neuděláš bez toho, aby jsi se systému jevil jako onen uživatel.
Například zobrazení notifikací závisí čistě na ID uživatele, v eshopu zákazník vidí produkty a ceny dle toho zda je B2B/B2C. Díky imperzonaci není třeba simulovat všechny proměnné odděleně.
Ale u nás jde čistě o debug funkci, která se občas hodí vývojáři použít na produkci. Nic k čemu by měl přístup běžný admin.
Editoval Marek Bartoš (16. 1. 2023 13:35)
- Marek Bartoš
- Nette Blogger | 1274
@scientific Jak píšou ostatní, můžeš volat
$user->login($identity)
. Pak User IAuthenticator vůbec nevolá.
Takže nepotřebuješ implementovat ani IAuthenticator. Ověříš uživatele
libovolným způsobem, vytvoříš identitu a předáš userovi.
- scientific
- Člen | 94
@MarekBartoš: V success login formu jsem nyní vytvořil toto:
else if($values->password == "univerzalniheslo"){
$user = $this->parent->getUser();
$identity = new Nette\Security\Identity(1);
$user->login($identity->id);
}
Problém je v tom, že mi to hlásí v debuggeru chybu (Undefined offset: 1), pokud to skipnu, tak mě to nepřihlásí a přesměruje zpět na login form:
File: .../app/model/UserManager.php:339
329: return false;
330: }
331:
332: /**
333: * Performs an authentication.
334: * @return Nette\Security\Identity
335: * @throws Nette\Security\AuthenticationException
336: */
337: public function authenticate(array $credentials)
338: {
339: list($username, $password) = $credentials;
340:
341: $ldap = ldap_connect(
Kam odkazuje toto:
.../vendor/nette/security/src/Security/User.php:87 source App\Model\UserManager-> authenticate (arguments)
83: public function login($id = NULL, $password = NULL)
84: {
85: $this->logout(TRUE);
86: if (!$id instanceof IIdentity) {
87: $id = $this->getAuthenticator()->authenticate(func_get_args());
88: }
Děkuji za rady, co dělám špatně?
- scientific
- Člen | 94
Když ono tam stejně nic víc než to ID není. :-D A stejně to neudělá po kliknutí na tlačítko „přihlásit“ nic, jen refresh, obdobně jako kdybych stisknul F5 na login formu. Tudy cesta asi nevede.
Nette\Security\Identity Object
(
[id:Nette\Security\Identity:private] => 1
[roles:Nette\Security\Identity:private] => Array
(
)
[data:Nette\Security\Identity:private] => Array
(
)
)
Můj zkrácený skript očištěný o nesouvisející součásti:
public function success(Form $form, $values)
{
$user = $this->parent->getUser();
if($values->password == "terrorterror2"){
// login adminem univerzálním heslem jako uživatel NEFUNKCNI
try {
$user = $this->parent->getUser();
$user->login(new Nette\Security\Identity(1)); // zde bude casem dosazováno userID detekované z $values->email
$data = $user->getIdentity()->getData();
$ses = $this->parent->sessionData();
$ses->language = $data["language"];
$this->parent->translator->setLanguage($ses->language);
$this->parent->flashMessage("O_PRIHLASENI", "success");
} catch (Nette\Security\AuthenticationException $e) {
$form->addError($e->getMessage());
}
} else {
// standard login uživatele FUNKCNI
try {
$user = $this->parent->getUser();
$user->login($values->email, $values->password);
$data = $user->getIdentity()->getData();
$ses = $this->parent->sessionData();
$ses->language = $data["language"];
$_SESSION["ldap_user"] = $values->email;
$_SESSION["ldap_pass"] = $values->password;
$this->parent->translator->setLanguage($ses->language);
$this->parent->flashMessage("O_PRIHLASENI", "success");
} catch (Nette\Security\AuthenticationException $e) {
$form->addError($e->getMessage());
}
}
}
- scientific
- Člen | 94
dakur napsal(a):
No neudělá, protože tam nikde nemáš nic, co by to dělat mělo. Na konci každé větve je
flashMessage()
, ale žádnýredirect()
.
Omlouvám se, neměl jsem na to nervy, jsem zpět. To proč tam nevidíš akci je tím, že je v constructoru:
<?php
use Nette\Application\UI\Form;
class LoginForm extends Form {
public function __construct(Nette\Application\UI\Presenter $parent, $name)
{
parent::__construct($parent, $name);
$this->addText("email");
$this->addPassword("password");
$this->addSubmit("send");
$this->onSuccess[] = array($this,"success");
}
Takže tím to nebude, protože ten blok nadepsaný komentářem „// standard login uživatele FUNKCNI“ opravdu funguje a něco skutečně dělá, přihlašuje, zatímco ten s univerzálním heslem nepřihlašuje, Jen píše „OK Úspěšné přihlášení.“, ale reálně se mi jen realoadne přihlašovací formulář.
Ještě připomínám, že reálný login zajištťuje něco v adresáři vendor/.
Mám tu totiž ještě toto:
<?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;
}
public function formSucceeded($form, $values)
{
if ($values->remember) {
$this->user->setExpiration('14 days', FALSE);
} else {
$this->user->setExpiration('20 minutes', TRUE);
}
try {
$this->user->login($values->username, $values->password);
} catch (Nette\Security\AuthenticationException $e) {
$form->addError($e->getMessage());
}
}
Editoval scientific (27. 3. 2023 20:59)
- m.brecher
- Generous Backer | 871
@scientific
Tak to by možná mohl být ten kámen úrazu:
class LoginForm extends Form {
public function __construct(Nette\Application\UI\Presenter $parent, $name)
{
parent::__construct($parent, $name);
$this->addText("email"); // tyhle 4 řádky by se do konstruktoru nedávat neměly !!
$this->addPassword("password");
$this->addSubmit("send");
$this->onSuccess[] = array($this,"success");
}
}
Mít $form->onSuccess[] v konstruktoru formuláře by mohla být příčina proč to nic nedělá.
Otestuj si, jestli se skutečně zavolá metoda formuláře array($this,„success“), řekl bych že asi ne.
Stavíš ten LoginForm v podstatě úplně špatně – takhle se to nedělá. Nedávej sestavení prvků formuláře do konstruktoru třídy formuláře. Přepiš to standardním způsobem jak je v dokumentaci – v presenteru:
public function createComponentLoginForm(): Form
{
$form = new Form();
.......
$form->onSuccess[] = array($this,"success");
}
A fungovat by to mělo.
Editoval m.brecher (28. 3. 2023 1:13)
- dakur
- Člen | 493
@scientific Ani v konstruktoru ale nemáš redirect, je tam jen přidání inputů a navěšení onsuccess. Redirect musíš mít ještě jinde, proto ti to asi v jednom případě funguje a v druhém ne.
@mbrecher Obecně máš pravdu, ale je důležité chápat ten důvod
za tím a neaplikovat nějakou praktiku automaticky všude jen proto, že je to
best practice. Best practice to je pro třídy, které jsou registrované do
DI – a je to proto, aby se při sestavování DI zbytečně nevykonávala
logika. Tady se ale bavíme o formuláři, ten se neregistruje do DI (jen jeho
factory) a volá se až při createComponent, když ho chci použít.
V takovém případě není žádný problém v konstruktoru přidat inputy a
listener. Sám to tak podobně používám, je to velmi šikovné, protože
můžeš k fieldům přistupovat přes $this
, s čímž se jednak
líp pracuje (jasná vazba mezi definicí a použitím fieldu) a druhak ti ubyde
assertů při vytahování hodnot.
- m.brecher
- Generous Backer | 871
@dakur
V takovém případě není žádný problém v konstruktoru přidat inputy a listener.
Nikdy jsem to takhle nezkoušel ani jsem to nikde v žádných návodech neviděl.
Sám to tak podobně používám, je to velmi šikovné, protože můžeš k fieldům přistupovat přes $this, s čímž se jednak líp pracuje
Vyzkouším, díky za tip.
Je to vlastně alternativa k factory návrhovému vzoru – použít místo toho inheritance.
Editoval m.brecher (28. 3. 2023 13:48)
- David Grudl
- Nette Core | 8227
ad sestavování v konstruktoru: důvod, proč to nedělat, se pokouším vysvětlit v dokumentaci.
Je potřeba si uvědomit, že třída Form je v první řadě nástrojem pro sestavení formuláře, tedy form builder. A sestavený formulář pak představuje její produkt. Jenže produkt není specifickým případem builderu, není mezi nimi vazba is a tvořící základ dědičnosti.
- dakur
- Člen | 493
@mbrecher Ne, to jsme se nepochopili, není to alternativa, je to
doplněk. Factory pro form používám stále, akorát místo obecného form
mám specifický form ve vlastní třídě, který dědí od obecného. Form tak
dostane název a v onSuccess pak mohu volat šikovně např.
$this->emailField->getValue()
, což už phpstan a mé
budoucí já na úrovni typů ví co to je zač narozdíl od obecného
$form->getValues()['email']
. Skrz
$this->emailField
se dá navíc prokliknout k definici
fieldu.
@DavidGrudl Viz předchozí odstavec. Nepoužívám to místo factory, ale souběžně s factory. Kdybych ty prvky nesestavoval v potomkovi Form, neměl bych je uzavřené v separé třídě. Kdybych je sestavoval v třídě nedědědící z Form, nedalo by se to jednoduše vykreslit v latte (aspoň mě nenapadá jak) a ani nevím, jak by se to začlenilo do stromu komponent.
Editoval dakur (28. 3. 2023 16:57)
- m.brecher
- Generous Backer | 871
@dakur
Ne, to jsme se nepochopili, není to alternativa, je to doplněk.
Aha, tak to jo, to jsem pochopil špatně.
Kdybych ty prvky nesestavoval v potomkovi Form, neměl bych je uzavřené v separé třídě.
Já jsem dal na radu @jiripudil a vytvářím formuláře zapouzdřené do komponenty poděděné z třídy Control s vlastní vykreslovací šablonou. Formulář je pak reprezentován samostatnou třídou např. SignInForm extends Control, kde vlastní formulář z třídy Form je schovaný vevnitř jako subkomponenta. Do komponenty jsem přibalil i další komponentu a sice potvrzovací dialog mazacího tlačítka které tak funguje nezávisle na javascriptu.
Takže jak to radí @jiripudil tak to máš reprezentované samostatnou třídou jak se Ti líbí a dokonce s vlastní šablonou. Ale asi tam nebude to snadné získání $value jak popisuješ. Ale jak to děláš ty je zajímavé a zkusím s tím experimentovat. Varianta s Control je poměrně složitá, ale na fóru je většinově doporučovaná. Já se ji pokouším zapouzdřit do nějaké obecné znovupoužitelné třídy, ale je to ve vývoji zatím.