Passed
Push — master ( 4ec39a...24c59a )
by
unknown
15:04
created

ResetPasswordCommand::execute()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 19
nc 4
nop 2
dl 0
loc 24
rs 9.6333
c 1
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
namespace TYPO3\CMS\Backend\Command;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
use Psr\Http\Message\ServerRequestInterface;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Style\SymfonyStyle;
24
use TYPO3\CMS\Backend\Authentication\PasswordReset;
25
use TYPO3\CMS\Core\Context\Context;
26
use TYPO3\CMS\Core\Core\Environment;
27
use TYPO3\CMS\Core\Http\NormalizedParams;
28
use TYPO3\CMS\Core\Http\ServerRequest;
29
use TYPO3\CMS\Core\Http\Uri;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32
/**
33
 * Triggers the workflow to request a new password for a user.
34
 */
35
class ResetPasswordCommand extends Command
36
{
37
    /**
38
     * Configure the command by defining the name, options and arguments
39
     */
40
    protected function configure()
41
    {
42
        $this
43
            ->setDescription('Trigger a password reset for a backend user.')
44
            ->addArgument(
45
                'backendurl',
46
                InputArgument::REQUIRED,
47
                'The URL of the TYPO3 Backend, e.g. https://www.example.com/typo3/'
48
            )->addArgument(
49
                'email',
50
                InputArgument::REQUIRED,
51
                'The email address of a valid backend user'
52
            );
53
    }
54
    /**
55
     * Executes the command for sending out an email to reset the password.
56
     *
57
     * @inheritDoc
58
     */
59
    protected function execute(InputInterface $input, OutputInterface $output): int
60
    {
61
        $io = new SymfonyStyle($input, $output);
62
        $email = $input->getArgument('email');
63
        if (!GeneralUtility::validEmail($email)) {
0 ignored issues
show
Bug introduced by
It seems like $email can also be of type string[]; however, parameter $email of TYPO3\CMS\Core\Utility\G...alUtility::validEmail() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

63
        if (!GeneralUtility::validEmail(/** @scrutinizer ignore-type */ $email)) {
Loading history...
64
            $io->error('The given email "' . $email . '" is not a valid email address.');
0 ignored issues
show
Bug introduced by
Are you sure $email of type null|string|string[] can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
            $io->error('The given email "' . /** @scrutinizer ignore-type */ $email . '" is not a valid email address.');
Loading history...
65
            return 1;
66
        }
67
        $backendUrl = $input->getArgument('backendurl');
68
        if (!GeneralUtility::isValidUrl($backendUrl)) {
0 ignored issues
show
Bug introduced by
It seems like $backendUrl can also be of type string[]; however, parameter $url of TYPO3\CMS\Core\Utility\G...alUtility::isValidUrl() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
        if (!GeneralUtility::isValidUrl(/** @scrutinizer ignore-type */ $backendUrl)) {
Loading history...
69
            $io->error('The given backend URL "' . $backendUrl . '" is not a valid URL.');
0 ignored issues
show
Bug introduced by
Are you sure $backendUrl of type null|string|string[] can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

69
            $io->error('The given backend URL "' . /** @scrutinizer ignore-type */ $backendUrl . '" is not a valid URL.');
Loading history...
70
            return 1;
71
        }
72
        $reset = GeneralUtility::makeInstance(PasswordReset::class);
73
        if (!$reset->isEnabled()) {
74
            $io->error('Password reset functionality is disabled');
75
            return 1;
76
        }
77
        $context = GeneralUtility::makeInstance(Context::class);
78
        $request = $this->createFakeWebRequest($backendUrl);
0 ignored issues
show
Bug introduced by
It seems like $backendUrl can also be of type null and string[]; however, parameter $backendUrl of TYPO3\CMS\Backend\Comman...:createFakeWebRequest() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
        $request = $this->createFakeWebRequest(/** @scrutinizer ignore-type */ $backendUrl);
Loading history...
79
        $GLOBALS['TYPO3_REQUEST'] = $request;
80
        $reset->initiateReset($request, $context, $email);
81
        $io->success('Sent out an email to "' . $email . '" requesting to set a new password.');
82
        return 0;
83
    }
84
85
    /**
86
     * This is needed to create a link to the backend properly.
87
     *
88
     * @param string $backendUrl
89
     * @return ServerRequestInterface
90
     */
91
    protected function createFakeWebRequest(string $backendUrl): ServerRequestInterface
92
    {
93
        $uri = new Uri($backendUrl);
94
        $request = new ServerRequest(
95
            $uri,
96
            'GET',
97
            'php://input',
98
            [],
99
            [
100
                'HTTP_HOST' => $uri->getHost(),
101
                'SERVER_NAME' => $uri->getHost(),
102
                'HTTPS' => $uri->getScheme() === 'https',
103
                'SCRIPT_FILENAME' => __FILE__,
104
                'SCRIPT_NAME' => rtrim($uri->getPath(), '/') . '/'
105
            ]
106
        );
107
        $backedUpEnvironment = $this->simulateEnvironmentForBackendEntryPoint();
108
        $normalizedParams = NormalizedParams::createFromRequest($request);
109
110
        // Restore the environment
111
        Environment::initialize(
112
            Environment::getContext(),
113
            Environment::isCli(),
114
            Environment::isComposerMode(),
115
            Environment::getProjectPath(),
116
            Environment::getPublicPath(),
117
            Environment::getVarPath(),
118
            Environment::getConfigPath(),
119
            $backedUpEnvironment['currentScript'],
120
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
121
        );
122
123
        return $request->withAttribute('normalizedParams', $normalizedParams);
124
    }
125
126
    /**
127
     * This is a workaround to use "PublicPath . /typo3/index.php" instead of "publicPath . /typo3/sysext/core/bin/typo3"
128
     * so the the web root is detected properly in normalizedParams.
129
     */
130
    protected function simulateEnvironmentForBackendEntryPoint(): array
131
    {
132
        $currentEnvironment = Environment::toArray();
133
        Environment::initialize(
134
            Environment::getContext(),
135
            Environment::isCli(),
136
            Environment::isComposerMode(),
137
            Environment::getProjectPath(),
138
            Environment::getPublicPath(),
139
            Environment::getVarPath(),
140
            Environment::getConfigPath(),
141
            // This is ugly, as this change fakes the directory
142
            dirname(Environment::getCurrentScript(), 4) . DIRECTORY_SEPARATOR . 'index.php',
143
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
144
        );
145
        return $currentEnvironment;
146
    }
147
}
148