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
|
|
|
'send-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
|
|
|
'output-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('send-email'); |
123
|
|
|
$displayLink = $input->getOption('output-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 | $displayLink) { |
139
|
|
|
$userId = $user->getUID(); |
140
|
|
|
$token = $this->secureRandom->generate(21, |
141
|
|
|
ISecureRandom::CHAR_DIGITS, |
142
|
|
|
ISecureRandom::CHAR_LOWER, ISecureRandom::CHAR_UPPER); |
|
|
|
|
143
|
|
|
$this->config->setUserValue($user->getUID(), 'owncloud', |
144
|
|
|
'lostpassword', $this->timeFactory->getTime() . ':' . $token); |
145
|
|
|
$mailData = [ |
146
|
|
|
'link' => $this->urlgenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $userId, 'token' => $token]) |
147
|
|
|
]; |
148
|
|
|
|
149
|
|
|
if ($emailLink && $user->getEMailAddress() !== null) { |
150
|
|
|
$mail = new TemplateResponse('core', 'lostpassword/email', $mailData, 'blank'); |
151
|
|
|
$mailContent = $mail->render(); |
152
|
|
|
|
153
|
|
|
$subject = $this->l10n->t('Your password is reset'); |
154
|
|
|
try { |
155
|
|
|
$message = $this->mailer->createMessage(); |
156
|
|
|
$message->setTo([$user->getEMailAddress() => $userId]); |
157
|
|
|
$message->setSubject($subject); |
158
|
|
|
$message->setHtmlBody($mailContent); |
|
|
|
|
159
|
|
|
$message->setFrom([Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]); |
160
|
|
|
$this->mailer->send($message); |
161
|
|
|
} catch (\Exception $e) { |
162
|
|
|
$output->writeln('<error>Can\'t send new user mail to ' . $user->getEMailAddress() . ': ' . $e->getMessage()); |
163
|
|
|
return 1; |
164
|
|
|
} |
165
|
|
|
} else { |
166
|
|
|
if ($emailLink) { |
167
|
|
|
$output->writeln('<error>Email address is not set for the user ' . $user->getUID() . '</error>'); |
168
|
|
|
return 1; |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
$output->writeln('The password reset link is: ' . $mailData['link']); |
172
|
|
|
return 0; |
173
|
|
|
} elseif ($input->isInteractive()) { |
174
|
|
|
/** @var $dialog \Symfony\Component\Console\Helper\QuestionHelper */ |
175
|
|
|
$dialog = $this->getHelperSet()->get('question'); |
176
|
|
|
|
177
|
|
|
if (\OCP\App::isEnabled('encryption')) { |
178
|
|
|
$output->writeln( |
179
|
|
|
'<error>Warning: Resetting the password when using encryption will result in data loss!</error>' |
180
|
|
|
); |
181
|
|
|
if (!$dialog->ask($input, $output, new Question('<question>Do you want to continue?</question>', true))) { |
182
|
|
|
return 1; |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
$q = new Question('<question>Enter a new password: </question>', false); |
187
|
|
|
$q->setHidden(true); |
188
|
|
|
$password = $dialog->ask($input, $output, $q); |
189
|
|
|
if ($password === false) { |
190
|
|
|
// When user presses RETURN key or no password characters are entered, |
191
|
|
|
// $password gets a boolean value false. |
192
|
|
|
$output->writeln("<error>Password cannot be empty!</error>"); |
193
|
|
|
return 1; |
194
|
|
|
} |
195
|
|
|
$q = new Question('<question>Confirm the new password: </question>', false); |
196
|
|
|
$q->setHidden(true); |
197
|
|
|
$confirm = $dialog->ask($input, $output, $q); |
198
|
|
|
if ($password !== $confirm) { |
199
|
|
|
$output->writeln("<error>Passwords did not match!</error>"); |
200
|
|
|
return 1; |
201
|
|
|
} |
202
|
|
|
} else { |
203
|
|
|
$output->writeln("<error>Interactive input or --password-from-env is needed for entering a new password!</error>"); |
204
|
|
|
return 1; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
$success = $user->setPassword($password); |
208
|
|
|
if ($success) { |
209
|
|
|
$output->writeln("<info>Successfully reset password for " . $username . "</info>"); |
210
|
|
|
} else { |
211
|
|
|
$output->writeln("<error>Error while resetting password!</error>"); |
212
|
|
|
return 1; |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
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.