Completed
Push — master ( d36751...81d373 )
by Lukas
26:06 queued 14:56
created

LostController::sendEmail()   A

Complexity

Conditions 3
Paths 9

Size

Total Lines 57
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 37
nc 9
nop 1
dl 0
loc 57
rs 9.6818
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bernhard Posselt <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Julius Haertl <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Victor Dubiniuk <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\Core\Controller;
32
33
use OCA\Encryption\Exceptions\PrivateKeyMissingException;
34
use \OCP\AppFramework\Controller;
35
use \OCP\AppFramework\Http\TemplateResponse;
36
use OCP\AppFramework\Utility\ITimeFactory;
37
use OCP\Defaults;
38
use OCP\Encryption\IManager;
39
use \OCP\IURLGenerator;
40
use \OCP\IRequest;
41
use \OCP\IL10N;
42
use \OCP\IConfig;
43
use OCP\IUser;
44
use OCP\IUserManager;
45
use OCP\Mail\IMailer;
46
use OCP\Security\ICrypto;
47
use OCP\Security\ISecureRandom;
48
49
/**
50
 * Class LostController
51
 *
52
 * Successfully changing a password will emit the post_passwordReset hook.
53
 *
54
 * @package OC\Core\Controller
55
 */
56
class LostController extends Controller {
57
58
	/** @var IURLGenerator */
59
	protected $urlGenerator;
60
	/** @var IUserManager */
61
	protected $userManager;
62
	/** @var Defaults */
63
	protected $defaults;
64
	/** @var IL10N */
65
	protected $l10n;
66
	/** @var string */
67
	protected $from;
68
	/** @var IManager */
69
	protected $encryptionManager;
70
	/** @var IConfig */
71
	protected $config;
72
	/** @var ISecureRandom */
73
	protected $secureRandom;
74
	/** @var IMailer */
75
	protected $mailer;
76
	/** @var ITimeFactory */
77
	protected $timeFactory;
78
	/** @var ICrypto */
79
	protected $crypto;
80
81
	/**
82
	 * @param string $appName
83
	 * @param IRequest $request
84
	 * @param IURLGenerator $urlGenerator
85
	 * @param IUserManager $userManager
86
	 * @param Defaults $defaults
87
	 * @param IL10N $l10n
88
	 * @param IConfig $config
89
	 * @param ISecureRandom $secureRandom
90
	 * @param string $defaultMailAddress
91
	 * @param IManager $encryptionManager
92
	 * @param IMailer $mailer
93
	 * @param ITimeFactory $timeFactory
94
	 * @param ICrypto $crypto
95
	 */
96
	public function __construct($appName,
97
								IRequest $request,
98
								IURLGenerator $urlGenerator,
99
								IUserManager $userManager,
100
								Defaults $defaults,
101
								IL10N $l10n,
102
								IConfig $config,
103
								ISecureRandom $secureRandom,
104
								$defaultMailAddress,
105
								IManager $encryptionManager,
106
								IMailer $mailer,
107
								ITimeFactory $timeFactory,
108
								ICrypto $crypto) {
109
		parent::__construct($appName, $request);
110
		$this->urlGenerator = $urlGenerator;
111
		$this->userManager = $userManager;
112
		$this->defaults = $defaults;
113
		$this->l10n = $l10n;
114
		$this->secureRandom = $secureRandom;
115
		$this->from = $defaultMailAddress;
116
		$this->encryptionManager = $encryptionManager;
117
		$this->config = $config;
118
		$this->mailer = $mailer;
119
		$this->timeFactory = $timeFactory;
120
		$this->crypto = $crypto;
121
	}
122
123
	/**
124
	 * Someone wants to reset their password:
125
	 *
126
	 * @PublicPage
127
	 * @NoCSRFRequired
128
	 *
129
	 * @param string $token
130
	 * @param string $userId
131
	 * @return TemplateResponse
132
	 */
133
	public function resetform($token, $userId) {
134
		try {
135
			$this->checkPasswordResetToken($token, $userId);
136
		} catch (\Exception $e) {
137
			return new TemplateResponse(
138
				'core', 'error', [
139
					"errors" => array(array("error" => $e->getMessage()))
140
				],
141
				'guest'
142
			);
143
		}
144
145
		return new TemplateResponse(
146
			'core',
147
			'lostpassword/resetpassword',
148
			array(
149
				'link' => $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', array('userId' => $userId, 'token' => $token)),
150
			),
151
			'guest'
152
		);
153
	}
154
155
	/**
156
	 * @param string $token
157
	 * @param string $userId
158
	 * @throws \Exception
159
	 */
160
	protected function checkPasswordResetToken($token, $userId) {
161
		$user = $this->userManager->get($userId);
162
		if($user === null) {
163
			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
164
		}
165
166
		try {
167
			$encryptedToken = $this->config->getUserValue($userId, 'core', 'lostpassword', null);
168
			$mailAddress = !is_null($user->getEMailAddress()) ? $user->getEMailAddress() : '';
169
			$decryptedToken = $this->crypto->decrypt($encryptedToken, $mailAddress.$this->config->getSystemValue('secret'));
170
		} catch (\Exception $e) {
171
			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
172
		}
173
174
		$splittedToken = explode(':', $decryptedToken);
175
		if(count($splittedToken) !== 2) {
176
			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
177
		}
178
179
		if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*12) ||
180
			$user->getLastLogin() > $splittedToken[0]) {
181
			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
182
		}
183
184
		if (!hash_equals($splittedToken[1], $token)) {
185
			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
186
		}
187
	}
188
189
	/**
190
	 * @param $message
191
	 * @param array $additional
192
	 * @return array
193
	 */
194
	private function error($message, array $additional=array()) {
195
		return array_merge(array('status' => 'error', 'msg' => $message), $additional);
196
	}
197
198
	/**
199
	 * @return array
200
	 */
201
	private function success() {
202
		return array('status'=>'success');
203
	}
204
205
	/**
206
	 * @PublicPage
207
	 * @BruteForceProtection(action=passwordResetEmail)
208
	 *
209
	 * @param string $user
210
	 * @return array
211
	 */
212
	public function email($user){
213
		// FIXME: use HTTP error codes
214
		try {
215
			$this->sendEmail($user);
216
		} catch (\Exception $e){
217
			return $this->error($e->getMessage());
218
		}
219
220
		return $this->success();
221
	}
222
223
	/**
224
	 * @PublicPage
225
	 * @param string $token
226
	 * @param string $userId
227
	 * @param string $password
228
	 * @param boolean $proceed
229
	 * @return array
230
	 */
231
	public function setPassword($token, $userId, $password, $proceed) {
232
		if ($this->encryptionManager->isEnabled() && !$proceed) {
233
			return $this->error('', array('encryption' => true));
234
		}
235
236
		try {
237
			$this->checkPasswordResetToken($token, $userId);
238
			$user = $this->userManager->get($userId);
239
240
			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', array('uid' => $userId, 'password' => $password));
241
242
			if (!$user->setPassword($password)) {
243
				throw new \Exception();
244
			}
245
246
			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', array('uid' => $userId, 'password' => $password));
247
248
			$this->config->deleteUserValue($userId, 'core', 'lostpassword');
249
			@\OC_User::unsetMagicInCookie();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
250
		} catch (\Exception $e){
251
			return $this->error($e->getMessage());
252
		}
253
254
		return $this->success();
255
	}
256
257
	/**
258
	 * @param string $input
259
	 * @throws \Exception
260
	 */
261
	protected function sendEmail($input) {
262
		$user = $this->findUserByIdOrMail($input);
263
		$email = $user->getEMailAddress();
264
265
		if (empty($email)) {
266
			throw new \Exception(
267
				$this->l10n->t('Could not send reset email because there is no email address for this username. Please contact your administrator.')
268
			);
269
		}
270
271
		// Generate the token. It is stored encrypted in the database with the
272
		// secret being the users' email address appended with the system secret.
273
		// This makes the token automatically invalidate once the user changes
274
		// their email address.
275
		$token = $this->secureRandom->generate(
276
			21,
277
			ISecureRandom::CHAR_DIGITS.
278
			ISecureRandom::CHAR_LOWER.
279
			ISecureRandom::CHAR_UPPER
280
		);
281
		$tokenValue = $this->timeFactory->getTime() .':'. $token;
282
		$encryptedValue = $this->crypto->encrypt($tokenValue, $email . $this->config->getSystemValue('secret'));
283
		$this->config->setUserValue($user->getUID(), 'core', 'lostpassword', $encryptedValue);
284
285
		$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', array('userId' => $user->getUID(), 'token' => $token));
286
287
		$emailTemplate = $this->mailer->createEMailTemplate();
288
289
		$emailTemplate->addHeader();
290
		$emailTemplate->addHeading($this->l10n->t('Password reset'));
291
292
		$emailTemplate->addBodyText(
293
			$this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.'),
294
			$this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
295
		);
296
297
		$emailTemplate->addBodyButton(
298
			$this->l10n->t('Reset your password'),
299
			$link,
300
			false
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
301
		);
302
		$emailTemplate->addFooter();
303
304
		try {
305
			$message = $this->mailer->createMessage();
306
			$message->setTo([$email => $user->getUID()]);
307
			$message->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
308
			$message->setPlainBody($emailTemplate->renderText());
309
			$message->setHtmlBody($emailTemplate->renderHTML());
310
			$message->setFrom([$this->from => $this->defaults->getName()]);
311
			$this->mailer->send($message);
312
		} catch (\Exception $e) {
313
			throw new \Exception($this->l10n->t(
314
				'Couldn\'t send reset email. Please contact your administrator.'
315
			));
316
		}
317
	}
318
319
	/**
320
	 * @param string $input
321
	 * @return IUser
322
	 * @throws \Exception
323
	 */
324
	protected function findUserByIdOrMail($input) {
325
		$user = $this->userManager->get($input);
326
		if ($user instanceof IUser) {
327
			return $user;
328
		}
329
		$users = $this->userManager->getByEmail($input);
330
		if (count($users) === 1) {
331
			return $users[0];
332
		}
333
334
		throw new \InvalidArgumentException($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
335
	}
336
}
337