Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

PasswordService::forcePasswordReset()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 2
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 10
cp 0
crap 12
1
<?php
2
namespace Elgg;
3
4
/**
5
 * PRIVATE CLASS. API IN FLUX. DO NOT USE DIRECTLY.
6
 *
7
 * @package Elgg.Core
8
 * @access  private
9
 * @since   1.10.0
10
 */
11
final class PasswordService {
12
13
	/**
14
	 * Constructor
15
	 */
16 12
	public function __construct() {
17 12
		if (!function_exists('password_hash')) {
18
			throw new \RuntimeException("password_hash and associated functions are required.");
19
		}
20 12
	}
21
22
	/**
23
	 * Determine if the password hash needs to be rehashed
24
	 *
25
	 * If the answer is true, after validating the password using password_verify, rehash it.
26
	 *
27
	 * @param string $hash The hash to test
28
	 *
29
	 * @return boolean True if the password needs to be rehashed.
30
	 */
31 5
	function needsRehash($hash) {
0 ignored issues
show
Best Practice introduced by Steve Clay
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
32 5
		return password_needs_rehash($hash, PASSWORD_DEFAULT);
33
	}
34
35
	/**
36
	 * Verify a password against a hash using a timing attack resistant approach
37
	 *
38
	 * @param string $password The password to verify
39
	 * @param string $hash     The hash to verify against
40
	 *
41
	 * @return boolean If the password matches the hash
42
	 */
43 6
	function verify($password, $hash) {
0 ignored issues
show
Best Practice introduced by Steve Clay
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
44 6
		return password_verify($password, $hash);
45
	}
46
47
	/**
48
	 * Hash a password for storage using password_hash()
49
	 *
50
	 * @param string $password Password in clear text
51
	 *
52
	 * @return string
53
	 */
54 162
	function generateHash($password) {
0 ignored issues
show
Best Practice introduced by Steve Clay
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
55 162
		return password_hash($password, PASSWORD_DEFAULT);
56
	}
57
58
	/**
59
	 * Generate and send a password request email to a given user's registered email address.
60
	 *
61
	 * @param int $user_guid User GUID
62
	 *
63
	 * @return bool
64
	 */
65
	function sendNewPasswordRequest($user_guid) {
0 ignored issues
show
Best Practice introduced by Steve Clay
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
66
		$user_guid = (int) $user_guid;
67
68
		$user = _elgg_services()->entityTable->get($user_guid);
69
		if (!$user instanceof \ElggUser) {
70
			return false;
71
		}
72
73
		// generate code
74
		$code = generate_random_cleartext_password();
75
		$user->setPrivateSetting('passwd_conf_code', $code);
76
		$user->setPrivateSetting('passwd_conf_time', time());
77
78
		// generate link
79
		$link = _elgg_config()->wwwroot . "changepassword?u=$user_guid&c=$code";
80
		$link = _elgg_services()->urlSigner->sign($link, '+1 day');
81
		
82
		// generate email
83
		$ip_address = _elgg_services()->request->getClientIp();
84
		$message = _elgg_services()->translator->translate(
85
			'email:changereq:body', [$user->name, $ip_address, $link], $user->language);
86
		$subject = _elgg_services()->translator->translate(
87
			'email:changereq:subject', [], $user->language);
88
89
		$params = [
90
			'action' => 'requestnewpassword',
91
			'object' => $user,
92
			'ip_address' => $ip_address,
93
			'link' => $link,
94
		];
95
		
96
		return notify_user($user->guid, elgg_get_site_entity()->guid, $subject, $message, $params, 'email');
0 ignored issues
show
Bug Best Practice introduced by Ismayil Khayredinov
The expression return notify_user($user...sage, $params, 'email') returns the type array which is incompatible with the documented return type boolean.
Loading history...
97
	}
98
99
	/**
100
	 * Set a user's new password and save the entity.
101
	 *
102
	 * This can only be called from execute_new_password_request().
103
	 *
104
	 * @param \ElggUser|int $user     The user GUID or entity
105
	 * @param string        $password Text (which will then be converted into a hash and stored)
106
	 *
107
	 * @return bool
108
	 */
109
	function forcePasswordReset($user, $password) {
0 ignored issues
show
Best Practice introduced by Steve Clay
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
110
		if (!$user instanceof \ElggUser) {
111
			$user = _elgg_services()->entityTable->get($user, 'user');
112
			if (!$user) {
113
				return false;
114
			}
115
		}
116
117
		$user->setPassword($password);
118
119
		$ia = elgg_set_ignore_access(true);
120
		$result = (bool) $user->save();
121
		elgg_set_ignore_access($ia);
122
123
		return $result;
124
	}
125
126
	/**
127
	 * Validate and change password for a user.
128
	 *
129
	 * @param int    $user_guid The user id
130
	 * @param string $conf_code Confirmation code as sent in the request email.
131
	 * @param string $password  Optional new password, if not randomly generated.
132
	 *
133
	 * @return bool True on success
134
	 */
135
	function executeNewPasswordReset($user_guid, $conf_code, $password = null) {
0 ignored issues
show
Best Practice introduced by Steve Clay
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
136
		$user_guid = (int) $user_guid;
137
		$user = get_entity($user_guid);
138
139
		if ($password === null) {
140
			$password = generate_random_cleartext_password();
141
			$reset = true;
142
		} else {
143
			$reset = false;
144
		}
145
146
		if (!$user instanceof \ElggUser) {
147
			return false;
148
		}
149
150
		$saved_code = $user->getPrivateSetting('passwd_conf_code');
151
		$code_time = (int) $user->getPrivateSetting('passwd_conf_time');
152
		$codes_match = _elgg_services()->crypto->areEqual($saved_code, $conf_code);
0 ignored issues
show
Bug introduced by Steve Clay
It seems like $saved_code can also be of type false; however, parameter $str1 of ElggCrypto::areEqual() 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

152
		$codes_match = _elgg_services()->crypto->areEqual(/** @scrutinizer ignore-type */ $saved_code, $conf_code);
Loading history...
153
154
		if (!$saved_code || !$codes_match) {
155
			return false;
156
		}
157
158
		// Discard for security if it is 24h old
159
		if (!$code_time || $code_time < time() - 24 * 60 * 60) {
160
			return false;
161
		}
162
163
		if (!$this->forcePasswordReset($user, $password)) {
164
			return false;
165
		}
166
167
		remove_private_setting($user_guid, 'passwd_conf_code');
168
		remove_private_setting($user_guid, 'passwd_conf_time');
169
		// clean the logins failures
170
		reset_login_failure_count($user_guid);
171
172
		$ns = $reset ? 'resetpassword' : 'changepassword';
173
174
		$message = _elgg_services()->translator->translate(
175
			"email:$ns:body", [$user->username, $password], $user->language);
176
		$subject = _elgg_services()->translator->translate("email:$ns:subject", [], $user->language);
177
178
		$params = [
179
			'action' => $ns,
180
			'object' => $user,
181
			'password' => $password,
182
		];
183
184
		notify_user($user->guid, elgg_get_site_entity()->guid, $subject, $message, $params, 'email');
185
186
		return true;
187
	}
188
}
189