Completed
Pull Request — stable8.2 (#27029)
by
unknown
17:24
created

LostController   B

Complexity

Total Complexity 19

Size/Duplication

Total Lines 211
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 91.03%

Importance

Changes 0
Metric Value
dl 0
loc 211
ccs 71
cts 78
cp 0.9103
rs 8.4614
c 0
b 0
f 0
wmc 19
lcom 1
cbo 16

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 26 1
A resetform() 0 10 1
A error() 0 3 1
A success() 0 3 1
A email() 0 10 2
D setPassword() 0 37 9
B sendEmail() 0 37 4
1
<?php
2
/**
3
 * @author Bernhard Posselt <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Lukas Reschke <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Thomas Müller <[email protected]>
8
 * @author Victor Dubiniuk <[email protected]>
9
 *
10
 * @copyright Copyright (c) 2015, ownCloud, Inc.
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OC\Core\LostPassword\Controller;
28
29
use \OCP\AppFramework\Controller;
30
use \OCP\AppFramework\Http\TemplateResponse;
31
use OCP\AppFramework\Utility\ITimeFactory;
32
use OCP\ILogger;
33
use \OCP\IURLGenerator;
34
use \OCP\IRequest;
35
use \OCP\IL10N;
36
use \OCP\IConfig;
37
use OCP\IUserManager;
38
use OCP\Mail\IMailer;
39
use OCP\Security\ISecureRandom;
40
use \OC_Defaults;
41
use OCP\Security\StringUtils;
42
43
/**
44
 * Class LostController
45
 *
46
 * Successfully changing a password will emit the post_passwordReset hook.
47
 *
48
 * @package OC\Core\LostPassword\Controller
49
 */
50
class LostController extends Controller {
51
52
	/** @var IURLGenerator */
53
	protected $urlGenerator;
54
	/** @var IUserManager */
55
	protected $userManager;
56
	// FIXME: Inject a non-static factory of OC_Defaults for better unit-testing
57
	/** @var OC_Defaults */
58
	protected $defaults;
59
	/** @var IL10N */
60
	protected $l10n;
61
	/** @var string */
62
	protected $from;
63
	/** @var bool */
64
	protected $isDataEncrypted;
65
	/** @var IConfig */
66
	protected $config;
67
	/** @var ISecureRandom */
68
	protected $secureRandom;
69
	/** @var IMailer */
70
	protected $mailer;
71
	/** @var ITimeFactory */
72
	protected $timeFactory;
73
	/** @var ILogger */
74
	protected $logger;
75
76
	/**
77
	 * @param string $appName
78
	 * @param IRequest $request
79
	 * @param IURLGenerator $urlGenerator
80
	 * @param IUserManager $userManager
81
	 * @param OC_Defaults $defaults
82
	 * @param IL10N $l10n
83
	 * @param IConfig $config
84
	 * @param ISecureRandom $secureRandom
85
	 * @param string $from
86
	 * @param string $isDataEncrypted
87 10
	 * @param IMailer $mailer
88
	 * @param ITimeFactory $timeFactory
89
	 * @param ILogger $logger
90
	 */
91
	public function __construct($appName,
92
								IRequest $request,
93
								IURLGenerator $urlGenerator,
94
								IUserManager $userManager,
95
								OC_Defaults $defaults,
96
								IL10N $l10n,
97
								IConfig $config,
98
								ISecureRandom $secureRandom,
99 10
								$from,
100 10
								$isDataEncrypted,
101 10
								IMailer $mailer,
102 10
								ITimeFactory $timeFactory,
103 10
								ILogger $logger) {
104 10
		parent::__construct($appName, $request);
105 10
		$this->urlGenerator = $urlGenerator;
106 10
		$this->userManager = $userManager;
107 10
		$this->defaults = $defaults;
108 10
		$this->l10n = $l10n;
109 10
		$this->secureRandom = $secureRandom;
110 10
		$this->from = $from;
111
		$this->isDataEncrypted = $isDataEncrypted;
0 ignored issues
show
Documentation Bug introduced by
The property $isDataEncrypted was declared of type boolean, but $isDataEncrypted is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
112
		$this->config = $config;
113
		$this->mailer = $mailer;
114
		$this->timeFactory = $timeFactory;
115
		$this->logger = $logger;
116
	}
117
118
	/**
119
	 * Someone wants to reset their password:
120
	 *
121
	 * @PublicPage
122 1
	 * @NoCSRFRequired
123 1
	 *
124 1
	 * @param string $token
125 1
	 * @param string $userId
126
	 * @return TemplateResponse
127 1
	 */
128 1
	public function resetform($token, $userId) {
129
		return new TemplateResponse(
130 1
			'core/lostpassword',
131
			'resetpassword',
132
			array(
133
				'link' => $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', array('userId' => $userId, 'token' => $token)),
134
			),
135
			'guest'
136
		);
137
	}
138 7
139 7
	/**
140
	 * @param $message
141
	 * @param array $additional
142
	 * @return array
143
	 */
144
	private function error($message, array $additional=array()) {
145 2
		return array_merge(array('status' => 'error', 'msg' => $message), $additional);
146 2
	}
147
148
	/**
149
	 * @return array
150
	 */
151
	private function success() {
152
		return array('status'=>'success');
153
	}
154
155 3
	/**
156
	 * @PublicPage
157
	 *
158 3
	 * @param string $user
159 3
	 * @return array
160 2
	 */
161
	public function email($user){
162
		// FIXME: use HTTP error codes
163 1
		try {
164
			$this->sendEmail($user);
165
		} catch (\Exception $e){
166
			return $this->error($e->getMessage());
167
		}
168
169
		return $this->success();
170
	}
171
172
	/**
173
	 * @PublicPage
174 6
	 * @param string $token
175 6
	 * @param string $userId
176 1
	 * @param string $password
177
	 * @param boolean $proceed
178
	 * @return array
179
	 */
180 6
	public function setPassword($token, $userId, $password, $proceed) {
181
		if ($this->isDataEncrypted && !$proceed) {
182 6
			return $this->error('', array('encryption' => true));
183 6
		}
184 3
185
		try {
186
			$user = $this->userManager->get($userId);
187 3
188 3
			$splittedToken = explode(':', $this->config->getUserValue($userId, 'owncloud', 'lostpassword', null));
189 2
			if(count($splittedToken) !== 2) {
190
				throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
191
			}
192 1
193
			if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*12) ||
194
				$user->getLastLogin() > $splittedToken[0]) {
195
				throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
196 1
			}
197
198
			if (!StringUtils::equals($splittedToken[1], $token)) {
199
				throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
200 1
			}
201
202 1
			if (!$user->setPassword($password)) {
203 1
				throw new \Exception();
204
			}
205 6
206 5
			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', array('uid' => $userId, 'password' => $password));
207
208
			$this->config->deleteUserValue($userId, 'owncloud', 'lostpassword');
209 1
			@\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...
210
211
		} catch (\Exception $e){
212
			return $this->error($e->getMessage());
213
		}
214
215
		return $this->success();
216 3
	}
217 3
218 1
	/**
219
	 * @param string $user
220
	 * @throws \Exception
221 2
	 */
222
	protected function sendEmail($user) {
223 2
		if ($this->userManager->userExists($user)) {
224
225
			$email = $this->config->getUserValue($user, 'settings', 'email');
226
227
			if (!empty($email)) {
228
				$token = $this->secureRandom->generate(21,
229
					ISecureRandom::CHAR_DIGITS .
230
					ISecureRandom::CHAR_LOWER .
231 2
					ISecureRandom::CHAR_UPPER);
232 2
				$this->config->setUserValue($user, 'owncloud', 'lostpassword', $this->timeFactory->getTime() . ':' . $token);
233 2
234 2
				$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user, 'token' => $token]);
235 2
236
				$tmpl = new \OC_Template('core/lostpassword', 'email');
237 2
				$tmpl->assign('link', $link);
238
				$msg = $tmpl->fetchPage();
239 2
240 2
				try {
241 2
					$message = $this->mailer->createMessage();
242
					$message->setTo([$email => $user]);
243
					$message->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
244 2
					$message->setPlainBody($msg);
0 ignored issues
show
Bug introduced by
It seems like $msg defined by $tmpl->fetchPage() on line 238 can also be of type boolean; however, OC\Mail\Message::setPlainBody() 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...
245 2
					$message->setFrom([$this->from => $this->defaults->getName()]);
246 2
					$this->mailer->send($message);
247 2
				} catch (\Exception $e) {
248 2
					throw new \Exception($this->l10n->t(
249 2
						'Couldn\'t send reset email. Please contact your administrator.'
250 2
					));
251 1
				}
252
			} else {
253 1
				$this->logger->error('Could not send reset email because there is no email address for this username. User: {user}', ['app' => 'core', 'user' => $user]);
254
			}
255 1
		} else {
256
			$this->logger->error('Could not send reset email because User does not exist. User: {user}', ['app' => 'core', 'user' => $user]);
257
		}
258
	}
259
260
}
261