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

engine/classes/Elgg/PasswordService.php (1 issue)

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) {
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) {
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) {
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) {
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) {
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) {
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);
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