Completed
Pull Request — master (#32345)
by Sujith
10:02
created

ResetPassword   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 19

Importance

Changes 0
Metric Value
dl 0
loc 176
rs 10
c 0
b 0
f 0
wmc 16
lcom 2
cbo 19

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 20 1
A configure() 0 29 1
C execute() 0 103 14
1
<?php
2
/**
3
 * @author Andreas Fischer <[email protected]>
4
 * @author Christopher Schäpers <[email protected]>
5
 * @author Clark Tomlinson <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Laurens Post <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Thomas Müller <[email protected]>
10
 * @author Sujith Haridasan <[email protected]>
11
 *
12
 * @copyright Copyright (c) 2018, ownCloud GmbH
13
 * @license AGPL-3.0
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OC\Core\Command\User;
30
31
use OC\Helper\EnvironmentHelper;
32
use OCP\AppFramework\Http\TemplateResponse;
33
use OCP\AppFramework\Utility\ITimeFactory;
34
use OCP\IConfig;
35
use OCP\IL10N;
36
use OCP\IURLGenerator;
37
use OCP\IUserManager;
38
use OCP\Mail\IMailer;
39
use OCP\Security\ISecureRandom;
40
use OCP\Util;
41
use Symfony\Component\Console\Command\Command;
42
use Symfony\Component\Console\Input\InputInterface;
43
use Symfony\Component\Console\Input\InputArgument;
44
use Symfony\Component\Console\Input\InputOption;
45
use Symfony\Component\Console\Output\OutputInterface;
46
use Symfony\Component\Console\Question\Question;
47
48
class ResetPassword extends Command {
49
50
	/** @var IUserManager */
51
	protected $userManager;
52
	/** @var \OC_Defaults  */
53
	private $defaults;
54
	/** @var IURLGenerator  */
55
	private $urlgenerator;
56
	/** @var IConfig  */
57
	private $config;
58
	/** @var IMailer  */
59
	private $mailer;
60
	/** @var ISecureRandom  */
61
	private $secureRandom;
62
	/** @var ITimeFactory  */
63
	private $timeFactory;
64
	/** @var IL10N  */
65
	private $l10n;
66
	/** @var EnvironmentHelper  */
67
	private $environmentHelper;
68
69
	public function __construct(IUserManager $userManager,
70
								\OC_Defaults $defaults,
71
								IURLGenerator $urlgenerator,
72
								IConfig $config,
73
								IMailer $mailer,
74
								ISecureRandom $secureRandom,
75
								ITimeFactory $timeFactory,
76
								IL10N $l10n,
77
								EnvironmentHelper $environmentHelper) {
78
		$this->userManager = $userManager;
79
		$this->defaults = $defaults;
80
		$this->urlgenerator = $urlgenerator;
81
		$this->config = $config;
82
		$this->mailer = $mailer;
83
		$this->secureRandom = $secureRandom;
84
		$this->timeFactory = $timeFactory;
85
		$this->l10n = $l10n;
86
		$this->environmentHelper = $environmentHelper;
87
		parent::__construct();
88
	}
89
90
	protected function configure() {
91
		$this
92
			->setName('user:resetpassword')
93
			->setDescription('Resets the password of the named user.')
94
			->addArgument(
95
				'user',
96
				InputArgument::REQUIRED,
97
				'The user\'s name.'
98
			)
99
			->addOption(
100
				'password-from-env',
101
				null,
102
				InputOption::VALUE_NONE,
103
				'Read the password from the OC_PASS environment variable.'
104
			)
105
			->addOption(
106
				'via-email',
107
				null,
108
				InputOption::VALUE_NONE,
109
				'The email-id set while creating the user, will be used to send link for password reset. This option will also display the link sent to user.'
110
			)
111
			->addOption(
112
				'via-link',
113
				null,
114
				InputOption::VALUE_NONE,
115
				'The link to reset the password will be displayed.'
116
			)
117
		;
118
	}
119
120
	protected function execute(InputInterface $input, OutputInterface $output) {
121
		$username = $input->getArgument('user');
122
		$emailLink = $input->getOption('via-email');
123
		$displayLink = $input->getOption('via-link');
124
125
		/** @var $user \OCP\IUser */
126
		$user = $this->userManager->get($username);
127
		if ($user === null) {
128
			$output->writeln('<error>User does not exist</error>');
129
			return 1;
130
		}
131
132
		if ($input->getOption('password-from-env')) {
133
			$password = $this->environmentHelper->getEnvVar('OC_PASS');
134
			if (!$password) {
135
				$output->writeln('<error>--password-from-env given, but OC_PASS is empty!</error>');
136
				return 1;
137
			}
138
		} elseif ($emailLink) {
139
			if ($user->getEMailAddress() !== null) {
140
				$userId = $user->getUID();
141
				$token = $this->secureRandom->generate(21,
142
					ISecureRandom::CHAR_DIGITS,
143
					ISecureRandom::CHAR_LOWER, ISecureRandom::CHAR_UPPER);
0 ignored issues
show
Unused Code introduced by
The call to ISecureRandom::generate() has too many arguments starting with \OCP\Security\ISecureRandom::CHAR_LOWER.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
144
				$this->config->setUserValue($user->getUID(), 'owncloud',
145
					'lostpassword', $this->timeFactory->getTime() . ':' . $token);
146
147
				$mailData = [
148
					'link' => $this->urlgenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $userId, 'token' => $token])
149
				];
150
151
				$mail = new TemplateResponse('core', 'lostpassword/email', $mailData, 'blank');
152
				$mailContent = $mail->render();
153
154
				$subject = $this->l10n->t('Your password is reset');
155
				try {
156
					$message = $this->mailer->createMessage();
157
					$message->setTo([$user->getEMailAddress() => $userId]);
158
					$message->setSubject($subject);
159
					$message->setHtmlBody($mailContent);
0 ignored issues
show
Bug introduced by
It seems like $mailContent defined by $mail->render() on line 152 can also be of type boolean; however, OC\Mail\Message::setHtmlBody() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
160
					$message->setFrom([Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]);
161
					$this->mailer->send($message);
162
					$output->writeln('The password reset link is: ' . $mailData['link']);
163
					return 0;
164
				} catch (\Exception $e) {
165
					$output->writeln('<error>Can\'t send new user mail to ' . $user->getEMailAddress() . ': ' . $e->getMessage());
166
					return 1;
167
				}
168
			} else {
169
				$output->writeln('<error>Email address is not set for the user ' . $user->getUID() . '</error>');
170
				return 1;
171
			}
172
		} elseif ($displayLink) {
173
			$token = $this->secureRandom->generate(21,
174
				ISecureRandom::CHAR_DIGITS,
175
				ISecureRandom::CHAR_LOWER, ISecureRandom::CHAR_UPPER);
0 ignored issues
show
Unused Code introduced by
The call to ISecureRandom::generate() has too many arguments starting with \OCP\Security\ISecureRandom::CHAR_LOWER.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
176
			$this->config->setUserValue($user->getUID(), 'owncloud',
177
				'lostpassword', $this->timeFactory->getTime() . ':' . $token);
178
			$link = $this->urlgenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
179
			$output->writeln('The password reset link is: ' . $link);
180
			return 0;
181
		} elseif ($input->isInteractive()) {
182
			/** @var $dialog \Symfony\Component\Console\Helper\QuestionHelper */
183
			$dialog = $this->getHelperSet()->get('question');
184
185
			if (\OCP\App::isEnabled('encryption')) {
186
				$output->writeln(
187
					'<error>Warning: Resetting the password when using encryption will result in data loss!</error>'
188
				);
189
				if (!$dialog->ask($input, $output, new Question('<question>Do you want to continue?</question>', true))) {
190
					return 0;
191
				}
192
			}
193
194
			$q = new Question('<question>Enter a new password: </question>', false);
195
			$q->setHidden(true);
196
			$password = $dialog->ask($input, $output, $q);
197
			if ($password === false) {
198
				// When user presses RETURN key or no password characters are entered,
199
				// $password gets a boolean value false.
200
				$output->writeln("<error>Password cannot be empty!</error>");
201
				return 1;
202
			}
203
			$q = new Question('<question>Confirm the new password: </question>', false);
204
			$q->setHidden(true);
205
			$confirm = $dialog->ask($input, $output, $q);
206
			if ($password !== $confirm) {
207
				$output->writeln("<error>Passwords did not match!</error>");
208
				return 1;
209
			}
210
		} else {
211
			$output->writeln("<error>Interactive input or --password-from-env is needed for entering a new password!</error>");
212
			return 1;
213
		}
214
215
		$success = $user->setPassword($password);
216
		if ($success) {
217
			$output->writeln("<info>Successfully reset password for " . $username . "</info>");
218
		} else {
219
			$output->writeln("<error>Error while resetting password!</error>");
220
			return 1;
221
		}
222
	}
223
}
224