PL
games/xencenter-xenadmin-xcp-jumphp / src / Form / MasterPasswordForm.php · commit fb1b7c9
<?phpnamespace App\Form;use App\Gui\IconBox;use App\Model\Session;use JumPHP\Gui\Button;use JumPHP\Gui\Container;use JumPHP\Gui\Form;use JumPHP\Gui\Label;use JumPHP\Gui\TextBox;use JumPHP\Kernel\Engine;/** * The master password of the saved server passwords (forms/MasterPasswordForm.form.yaml; the keys: Model/Keyring): * - mode unlock: asked at start when saved passwords are locked by one; a wrong one says so and asks again; "Forget Saved Passwords" *   drops them (the servers stay); Cancel leaves them locked (the servers are not reconnected); * - mode manage (Tools > Master Password…): set one, change it (the current one first), remove it. * Closes with ['keyring' => 'unlocked' | 'changed' | 'removed' | 'forgotten']: MainForm reconnects the saved servers. * The fields are password fields; what is typed is never shown, logged or kept. * * @property IconBox $icon * @property Label $heading * @property Label $message * @property Container $currentPanel * @property Label $currentLabel * @property TextBox $current * @property Container $newPanel * @property TextBox $newPassword * @property TextBox $confirm * @property Label $note * @property Label $error * @property Button $forget * @property Button $remove * @property Button $ok * @property Button $cancel */final class MasterPasswordForm extends Form{    public function __construct(private Session $session)    {        parent::__construct();    }    /** XAPI's answers, events and the console go on while this dialog is open. */    protected function tick(float $dt): void    {        $this->session->tick($dt);    }    private string $mode = 'manage';    /** At start (MainForm::loaded): asks the master password when saved passwords wait for it. True when it was asked. */    public static function askAtStart(MainForm $form): bool    {        if (!$form->session->keyring()->locked($form->session)) {            return false;        }        Engine::get()->scenes->push(self::class, ['mode' => 'unlock']);        return true;    }    protected function loaded(array $params = []): void    {        $this->mode = (string)($params['mode'] ?? 'manage');        $keyring = $this->session->keyring();        $master = $keyring->hasMaster();        $unlock = $this->mode === 'unlock';        $this->current->text = $this->newPassword->text = $this->confirm->text = '';        $this->error->text = '';        $this->currentPanel->visible = $master;        $this->newPanel->visible = !$unlock;        $this->forget->visible = $master;        $this->remove->visible = !$unlock && $master;        $this->icon->kind = $unlock ? 'warning' : 'info';        $this->heading->text = $unlock ? 'Enter your master password' : 'Master Password';        $this->currentLabel->text = $unlock ? 'Master password:' : 'Current master password:';        $this->message->text = match (true) {            $unlock => 'Your saved server passwords are protected by a master password. Enter it to reconnect your servers.',            $master => 'A master password protects your saved server passwords. Change it or remove it here.',            default => 'Set a master password to protect the passwords XenCenter remembers: it is asked once each time XenCenter starts.',        };        $this->note->text = $master            ? 'Encrypted with ' . $keyring->describe() . '. A forgotten master password cannot be recovered: the saved passwords are then lost (not the servers).'            : 'Without a master password the saved passwords are still encrypted (' . $keyring->describe() . '), but this only protects servers.json against casual reading: anyone who can read your files can read the key too.';        $this->ok->text = $unlock ? 'Unlock' : ($master ? 'Change' : 'Set');        $this->cancel->text = $unlock ? 'Not Now' : 'Cancel';        $this->setFocus($master ? $this->current : $this->newPassword);    }    public function onOkClick(mixed $sender = null): void    {        $keyring = $this->session->keyring();        if ($this->mode === 'unlock') {            if ($keyring->unlockSession($this->session, $this->current->text)) {                $this->close(['keyring' => 'unlocked']);                return;            }            $this->wrong();            return;        }        if (!$this->checkCurrent()) {            return;        }        if ($this->newPassword->text === '') {            $this->error->text = 'Enter the new master password.';            $this->setFocus($this->newPassword);            return;        }        if ($this->newPassword->text !== $this->confirm->text) {            $this->error->text = 'The two new passwords are not the same.';            $this->confirm->text = '';            $this->setFocus($this->confirm);            return;        }        $keyring->changeMaster($this->session, $this->newPassword->text);        $this->close(['keyring' => 'changed']);    }    public function onRemoveClick(Button $sender): void    {        if ($this->checkCurrent()) {            $this->session->keyring()->changeMaster($this->session, null);            $this->close(['keyring' => 'removed']);        }    }    public function onForgetClick(Button $sender): void    {        $this->session->keyring()->forgetAll($this->session);        $this->close(['keyring' => 'forgotten']);    }    public function onCancelClick(Button $sender): void    {        $this->close();    }    /** With a master password, the current one opens the keyring first. */    private function checkCurrent(): bool    {        $keyring = $this->session->keyring();        if (!$keyring->hasMaster() || $keyring->unlockSession($this->session, $this->current->text)) {            return true;        }        $this->wrong();        return false;    }    private function wrong(): void    {        $this->error->text = 'The master password is wrong. Try again' . ($this->mode === 'unlock' ? ', or forget the saved passwords.' : '.');        $this->current->text = '';        $this->setFocus($this->current);    }}