Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TASK] Clean up database:updateschema command #897

Merged
merged 6 commits into from Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 13 additions & 5 deletions Classes/Console/Command/AbstractConvertedCommand.php
Expand Up @@ -14,6 +14,7 @@
*
*/

use Helhum\Typo3Console\Exception\ArgumentValidationFailedException;
use Helhum\Typo3Console\Mvc\Cli\Symfony\Input\ArgvInput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
Expand Down Expand Up @@ -108,14 +109,21 @@ protected function interact(InputInterface $input, OutputInterface $output)
return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});

$argumentValue = null;
$io = new SymfonyStyle($input, $output);
foreach ($missingArguments as $missingArgument) {
while ($argumentValue === null) {
$argumentValue = $io->ask(sprintf('Please specify the required argument "%s"', $missingArgument));
}
$definition = $this->getDefinition()->getArgument($missingArgument);
$argumentValue = $io->ask(
$definition->getDescription(),
null,
function ($value) use ($missingArgument) {
if ($value === null) {
throw new ArgumentValidationFailedException(sprintf('%s must not be empty', $missingArgument));
}

return $value;
}
);
$input->setArgument($missingArgument, $argumentValue);
$argumentValue = null;
}
}

Expand Down
113 changes: 88 additions & 25 deletions Classes/Console/Command/Backend/CreateBackendAdminUserCommand.php
Expand Up @@ -14,16 +14,23 @@
*
*/

use Helhum\Typo3Console\Exception\ArgumentValidationFailedException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class CreateBackendAdminUserCommand extends Command
{
private $passwordAsArgument = true;

protected function configure()
{
$this->setDescription('Create admin backend user');
Expand All @@ -44,52 +51,108 @@ protected function configure()
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
protected function interact(InputInterface $input, OutputInterface $output)
{
$passwordHasher = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
$username = $input->getArgument('username');
$password = $input->getArgument('password');
$givenUsername = $username;
$username = strtolower(preg_replace('/\\s/i', '', $username));
$io = new SymfonyStyle($input, $output);
if (empty($input->getArgument('username'))) {
$username = $io->ask(
'Username',
null,
function ($username) {
if ($error = $this->validateUsername($username)) {
throw new ArgumentValidationFailedException($error);
}

if ($givenUsername !== $username) {
$output->writeln(sprintf('<warning>Given username "%s" contains invalid characters. Using "%s" instead.</warning>', $givenUsername, $username));
return $username;
}
);
$input->setArgument('username', $username);
}
if (empty($input->getArgument('password'))) {
$password = $io->askHidden(
'Password',
function ($password) {
if ($error = $this->validatePassword($password)) {
throw new ArgumentValidationFailedException($error);
}

if ($username === '') {
$output->writeln('<error>Username must have at least 1 character.</error>');

return 1;
return $password;
}
);
$this->passwordAsArgument = false;
$input->setArgument('password', $password);
}
if (strlen($password) < 8) {
$output->writeln('<error>Password must have at least 8 characters.</error>');
}

return 1;
protected function execute(InputInterface $input, OutputInterface $output): int
{
$username = $input->getArgument('username');
$password = $input->getArgument('password');
if ($this->passwordAsArgument) {
$output->writeln('<warning>Using a password on the command line interface can be insecure.</warning>');
}
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$userExists = $connectionPool->getConnectionForTable('be_users')
->count(
'uid',
'be_users',
['username' => $username]
);
if ($userExists) {
$output->writeln(sprintf('<error>A user with username "%s" already exists.</error>', $username));

if ($userError = $this->validateUsername($username)) {
$output->writeln(sprintf('<error>%s</error>', $userError));
}
if ($passwordError = $this->validatePassword($password)) {
$output->writeln(sprintf('<error>%s</error>', $passwordError));
}
if (isset($userError) || isset($passwordError)) {
return 1;
}
$passwordHasher = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
$adminUserFields = [
'username' => $username,
'password' => $passwordHasher->getHashedPassword($password),
'admin' => 1,
'tstamp' => $GLOBALS['EXEC_TIME'],
'crdate' => $GLOBALS['EXEC_TIME'],
];
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$connectionPool->getConnectionForTable('be_users')
->insert('be_users', $adminUserFields);

$output->writeln(sprintf('<info>Created admin user with username "%s".</info>', $username));

return 0;
}

private function validateUsername(?string $username): ?string
{
if (empty($username)) {
return 'Username must not be empty.';
}
$cleanedUsername = strtolower(preg_replace('/\\s/i', '', $username));
if ($username !== $cleanedUsername) {
return sprintf('No special characters are allowed in username. Use "%s" as username instead.', $cleanedUsername);
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
$queryBuilder->getRestrictions()
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class)
->removeByType(HiddenRestriction::class);
$userExists = $queryBuilder->count('uid')
->from('be_users')
->where(
$queryBuilder->expr()->eq('username', $queryBuilder->createNamedParameter($username))
)->execute()->fetchColumn() > 0;

if ($userExists) {
return sprintf('A user with username "%s" already exists.', $username);
}

return null;
}

private function validatePassword(?string $password): ?string
{
if (empty($password)) {
return 'Password must not be empty.';
}
if (strlen($password) < 8) {
return 'Password must have at least 8 characters.';
}

return null;
}
}
51 changes: 18 additions & 33 deletions Classes/Console/Command/Database/DatabaseUpdateSchemaCommand.php
Expand Up @@ -14,12 +14,12 @@
*
*/

use Helhum\Typo3Console\Command\AbstractConvertedCommand;
use Helhum\Typo3Console\Database\Schema\SchemaUpdate;
use Helhum\Typo3Console\Database\Schema\SchemaUpdateResultRenderer;
use Helhum\Typo3Console\Database\Schema\SchemaUpdateType;
use Helhum\Typo3Console\Mvc\Cli\ConsoleOutput;
use Helhum\Typo3Console\Service\Database\SchemaService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand All @@ -28,7 +28,7 @@
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;

class DatabaseUpdateSchemaCommand extends AbstractConvertedCommand
class DatabaseUpdateSchemaCommand extends Command
{
protected function configure()
{
Expand Down Expand Up @@ -64,37 +64,22 @@ protected function configure()
<code>%command.full_name% "*.add,*.change"</code>
EOH
);
/** @deprecated Will be removed with 6.0 */
$this->setDefinition($this->createCompleteInputDefinition());
}

/**
* @deprecated Will be removed with 6.0
*/
protected function createNativeDefinition(): array
{
return [
new InputArgument(
'schemaUpdateTypes',
InputArgument::OPTIONAL,
'List of schema update types (default: "safe")',
'safe'
),
new InputOption(
'dry-run',
'',
InputOption::VALUE_NONE,
'If set the updates are only collected and shown, but not executed'
),
];
}

/**
* @deprecated will be removed with 6.0
*/
protected function handleDeprecatedArgumentsAndOptions(InputInterface $input, OutputInterface $output)
{
// nothing to do here
$this->setDefinition(
[
new InputArgument(
'schemaUpdateTypes',
InputArgument::OPTIONAL,
'List of schema update types',
'safe'
),
new InputOption(
'dry-run',
'',
InputOption::VALUE_NONE,
'If set the updates are only collected and shown, but not executed'
),
]
);
}

protected function execute(InputInterface $input, OutputInterface $output)
Expand Down
21 changes: 21 additions & 0 deletions Classes/Console/Exception/ArgumentValidationFailedException.php
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Exception;

/*
* This file is part of the TYPO3 Console 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
* LICENSE file that was distributed with this source code.
*
*/

use Helhum\Typo3Console\Exception;

class ArgumentValidationFailedException extends Exception
{
}
12 changes: 12 additions & 0 deletions Classes/Console/Mvc/Cli/Symfony/Application.php
Expand Up @@ -29,6 +29,7 @@
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

Expand Down Expand Up @@ -261,5 +262,16 @@ protected function configureIO(InputInterface $input, OutputInterface $output)
$output->getFormatter()->setStyle('ins', new OutputFormatterStyle('green'));
$output->getFormatter()->setStyle('del', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('code', new OutputFormatterStyle(null, null, ['bold']));

// Reverting https://github.com/symfony/symfony/pull/33897 until this is resolved: https://github.com/symfony/symfony/issues/36565
if (function_exists('posix_isatty') && getenv('SHELL_INTERACTIVE') === false && $input->isInteractive()) {
$inputStream = null;
if ($input instanceof StreamableInputInterface) {
$inputStream = $input->getStream();
}
if (!@posix_isatty($inputStream)) {
$input->setInteractive(false);
}
}
}
}
2 changes: 1 addition & 1 deletion Documentation/CommandReference/DatabaseUpdateschema.rst
Expand Up @@ -47,7 +47,7 @@ Arguments
~~~~~~~~~

`schemaUpdateTypes`
List of schema update types (default: "safe")
List of schema update types



Expand Down