Skip to content

Commit

Permalink
[TASK] Add informational upgrade wizard for argon2i
Browse files Browse the repository at this point in the history
This adds a dummy wizard to remind admins during upgrade
to check the live system for argon2i support if the local
instance uses it, or to select a different hash algorithm.
Having this wizard gives this information to admins early
in the upgrade phase, so they have time to check the live
system or to select a different mechanism before too many
passwords have been upgraded.

Resolves: #86402
Releases: master
Change-Id: I2b1f75ecf079dc2e29d2675dda558c79b67f77e0
Reviewed-on: https://review.typo3.org/58411
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
  • Loading branch information
lolli42 authored and NeoBlack committed Sep 29, 2018
1 parent 9bb994f commit 551ed0c
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 35 deletions.
10 changes: 8 additions & 2 deletions typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php
Expand Up @@ -216,18 +216,24 @@ protected function runSingleWizard(
$this->output->title('Running Wizard ' . $instance->getTitle());
if ($instance instanceof ConfirmableInterface) {
$confirmation = $instance->getConfirmation();
$defaultString = $confirmation->getDefaultValue() ? '(Y/n)' : '(y/N)';
$defaultString = $confirmation->getDefaultValue() ? 'Y/n' : 'y/N';
$question = new ConfirmationQuestion(
sprintf(
'<info>%s</info>' . LF . '%s %s',
'<info>%s</info>' . LF . '%s' . LF . '%s %s (%s)',
$confirmation->getTitle(),
$confirmation->getMessage(),
$confirmation->getConfirm(),
$confirmation->getDeny(),
$defaultString
),
$confirmation->getDefaultValue()
);
$helper = $this->getHelper('question');
if (!$helper->ask($this->input, $this->output, $question)) {
if ($confirmation->isRequired()) {
$this->output->error('You have to acknowledge this wizard to continue');
return 1;
}
if ($instance instanceof RepeatableInterface) {
$this->output->note('No changes applied.');
} else {
Expand Down
72 changes: 41 additions & 31 deletions typo3/sysext/install/Classes/Service/UpgradeWizardsService.php
Expand Up @@ -303,28 +303,28 @@ public function getWizardUserInput(string $identifier): array
E_USER_DEPRECATED
);
} elseif ($updateObject instanceof UpgradeWizardInterface && $updateObject instanceof ConfirmableInterface) {
$wizardHtml = '
<div class="panel panel-danger">
<div class="panel-heading">
' . htmlspecialchars($updateObject->getConfirmation()->getTitle()) . '
</div>
<div class="panel-body">
<p>' . nl2br(htmlspecialchars($updateObject->getConfirmation()->getMessage())) . '</p>
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-default active">
<input type="radio" name="install[values][' .
htmlspecialchars($updateObject->getIdentifier()) .
'][install]" value="0" checked="checked" /> No, skip wizard
</label>
<label class="btn btn-default">
<input type="radio" name="install[values][' .
htmlspecialchars($updateObject->getIdentifier()) .
'][install]" value="1" /> Yes, execute wizard
</label>
</div>
</div>
</div>
';
$markup = [];
$radioAttributes = [
'type' => 'radio',
'name' => 'install[values][' . $updateObject->getIdentifier() . '][install]',
'value' => 0
];
$markup[] = '<div class="panel panel-danger">';
$markup[] = ' <div class="panel-heading">';
$markup[] = htmlspecialchars($updateObject->getConfirmation()->getTitle());
$markup[] = ' </div>';
$markup[] = ' <div class="panel-body">';
$markup[] = ' <p>' . nl2br(htmlspecialchars($updateObject->getConfirmation()->getMessage())) . '</p>';
$markup[] = ' <div class="btn-group" data-toggle="buttons">';
if (!$updateObject->getConfirmation()->isRequired()) {
$markup[] = ' <label class="btn btn-default active"><input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . ' checked="checked" />' . $updateObject->getConfirmation()->getDeny() . '</label>';
}
$radioAttributes['value'] = 1;
$markup[] = ' <label class="btn btn-default"><input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . ' />' . $updateObject->getConfirmation()->getConfirm() . '</label>';
$markup[] = ' </div>';
$markup[] = ' </div>';
$markup[] = '</div>';
$wizardHtml = implode('', $markup);
}

$result = [
Expand Down Expand Up @@ -372,15 +372,25 @@ public function executeWizard(string $identifier): FlashMessageQueue
} else {
if ($updateObject instanceof UpgradeWizardInterface) {
$requestParams = GeneralUtility::_GP('install');
if ($updateObject instanceof ConfirmableInterface
&& (
isset($requestParams['values'][$updateObject->getIdentifier()]['install'])
&& empty($requestParams['values'][$updateObject->getIdentifier()]['install'])
)
) {
$this->output->writeln('No changes applied, marking wizard as done.');
// confirmation was set to "no"
$performResult = true;
if ($updateObject instanceof ConfirmableInterface) {
// value is set in request but is empty
$isSetButEmpty = isset($requestParams['values'][$updateObject->getIdentifier()]['install'])
&& empty($requestParams['values'][$updateObject->getIdentifier()]['install']);

$checkValue = (int)$requestParams['values'][$updateObject->getIdentifier()]['install'];

if ($checkValue === 1) {
// confirmation = yes, we do the update
$performResult = $updateObject->executeUpdate();
} elseif ($updateObject->getConfirmation()->isRequired()) {
// confirmation = no, but is required, we do *not* the update and fail
$performResult = false;
} elseif ($isSetButEmpty) {
// confirmation = no, but it is *not* required, we do *not* the update, but mark the wizard as done
$this->output->writeln('No changes applied, marking wizard as done.');
// confirmation was set to "no"
$performResult = true;
}
} else {
// confirmation yes or non-confirmable
$performResult = $updateObject->executeUpdate();
Expand Down
115 changes: 115 additions & 0 deletions typo3/sysext/install/Classes/Updates/Argon2iPasswordHashes.php
@@ -0,0 +1,115 @@
<?php
declare(strict_types = 1);

namespace TYPO3\CMS\Install\Updates;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
* Informational upgrade wizard to remind upgrading instances
* may have to verify argon2i is available on the live servers
*/
class Argon2iPasswordHashes implements UpgradeWizardInterface, ConfirmableInterface
{
protected $confirmation;

public function __construct()
{
$this->confirmation = new Confirmation(
'Please make sure to read the following carefully:',
$this->getDescription(),
false,
'Yes, I understand!',
'',
true
);
}

/**
* @return string Unique identifier of this updater
*/
public function getIdentifier(): string
{
return 'argon2iPasswordHashes';
}

/**
* @return string Title of this updater
*/
public function getTitle(): string
{
return 'Reminder to verify live system supports argon2i';
}

/**
* @return string Longer description of this updater
*/
public function getDescription(): string
{
return 'TYPO3 uses the modern hash mechanism "argon2i" on this system. Existing passwords'
. ' will be automatically upgraded to this mechanism upon user login. If this instance'
. ' is later deployed to a different system, make sure the system does support argon2i'
. ' too, otherwise logins will fail. If that is not possible, select a different hash'
. ' algorithm in Setting > Presets > Password hashing settings and make sure no user'
. ' has been upgraded yet. This upgrade wizard exists only to inform you, it does not'
. ' change the system';
}

/**
* Checks whether updates are required.
*
* @return bool Whether an update is required (TRUE) or not (FALSE)
*/
public function updateNecessary(): bool
{
$passwordHashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
$feHash = $passwordHashFactory->getDefaultHashInstance('BE');
$beHash = $passwordHashFactory->getDefaultHashInstance('FE');
return $feHash instanceof Argon2iPasswordHash || $beHash instanceof Argon2iPasswordHash;
}

/**
* @return string[] All new fields and tables must exist
*/
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class,
];
}

/**
* This upgrade wizard has informational character only, it does not perform actions.
*
* @return bool Whether everything went smoothly or not
*/
public function executeUpdate(): bool
{
return true;
}

/**
* Return a confirmation message instance
*
* @return \TYPO3\CMS\Install\Updates\Confirmation
*/
public function getConfirmation(): Confirmation
{
return $this->confirmation;
}
}
56 changes: 54 additions & 2 deletions typo3/sysext/install/Classes/Updates/Confirmation.php
@@ -1,5 +1,6 @@
<?php
declare(strict_types = 1);

namespace TYPO3\CMS\Install\Updates;

/*
Expand Down Expand Up @@ -32,16 +33,67 @@ class Confirmation
*/
protected $message = '';

/**
* @var string
*/
protected $confirm;

/**
* @var string
*/
protected $deny;

/**
* @var bool
*/
protected $required;

/**
* @param string $title
* @param string $message
* @param bool $defaultValue
* @param string $confirm
* @param string $deny
* @param bool $required
*/
public function __construct(string $title, string $message, bool $defaultValue = false)
{
public function __construct(
string $title,
string $message,
bool $defaultValue = false,
string $confirm = 'Yes, execute',
string $deny = 'No, do not execute',
bool $required = false
) {
$this->title = $title;
$this->message = $message;
$this->defaultValue = $defaultValue;
$this->confirm = $confirm;
$this->deny = $deny;
$this->required = $required;
}

/**
* @return string
*/
public function getConfirm(): string
{
return $this->confirm;
}

/**
* @return string
*/
public function getDeny(): string
{
return $this->deny;
}

/**
* @return bool
*/
public function isRequired(): bool
{
return $this->required;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions typo3/sysext/install/ext_localconf.php
Expand Up @@ -66,6 +66,8 @@
= \TYPO3\CMS\Install\Updates\AdminPanelInstall::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['pagesSlugs']
= \TYPO3\CMS\Install\Updates\PopulatePageSlugs::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['argon2iPasswordHashes']
= \TYPO3\CMS\Install\Updates\Argon2iPasswordHashes::class;

$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
$icons = [
Expand Down

0 comments on commit 551ed0c

Please sign in to comment.