PluginProbe
The WP Remote WordPress Plugin / trunk
The WP Remote WordPress Plugin vtrunk
6.72 6.69 6.65 6.62 6.48 6.47 4.87 4.97 5.05 5.09 5.16 5.22 5.24 5.25 5.38 5.41 5.42 5.45 5.47 5.53 5.56 5.65 5.68 5.72 5.73 All 53 releases
← All changes | callback/wings/security.php +209 -26 6.48trunk View file →
@@ -42,18 +42,25 @@
42 42 }
43 43 // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fread
44 44
45 45 public function setupWP2FA($secrets_by_uids, $to_encrypt, $cipher_algo, $enabled) {
46 - if (!is_array($secrets_by_uids)) {
47 - return array("status" => false, "message" => "secrets_by_uids is not an array.");
46 + if (!is_array($secrets_by_uids) || !is_bool($to_encrypt) ||
47 + (!is_null($cipher_algo) && !is_string($cipher_algo)) ||
48 + (!is_null($enabled) && !is_bool($enabled))) {
49 + return array("status" => false, "message" => "Invalid parameters.");
48 50 }
51 + if (count($secrets_by_uids) < 1) {
52 + return array("status" => false, "message" => "Invalid parameters.");
53 + }
54 + foreach ($secrets_by_uids as $user_id => $secret) {
55 + if (!$this->isValidUserId($user_id) || !is_string($secret)) {
56 + return array("status" => false, "message" => "Invalid parameters.");
57 + }
58 + }
49 59
50 60 $result = array();
61 + $status = true;
51 62 foreach ($secrets_by_uids as $user_id => $secret) {
52 - if (empty($user_id) || !is_string($secret)) {
53 - continue;
54 - }
55 -
56 63 if ($to_encrypt === true) {
57 64 if (empty($cipher_algo)) {
58 65 $cipher_algo = WPRWP2FA::$cipher_algo;
59 66 }
@@ -73,18 +80,51 @@
73 80 "secret" => base64_encode($secret),
74 81 "is_encrypted" => $to_encrypt
75 82 );
76 83
77 - $result[$user_id][WPRWP2FA::SECRET_META_KEY] = update_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, $secret_info);
78 - $result[$user_id][WPRWP2FA::FLAG_META_KEY] = update_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
84 + $email_state_cleared = WPRWP2FAEmailOTP::revoke($user_id);
85 + $attempt_state_cleared = WPRWP2FATimeOTPLogin::clearState($user_id);
86 + $result[$user_id][WPRWP2FA::EMAIL_CHALLENGE_META_KEY] = $email_state_cleared;
87 + if (!$email_state_cleared || !$attempt_state_cleared) {
88 + $status = false;
89 + continue;
90 + }
91 +
92 + update_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, $secret_info);
93 + $secret_saved = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true) === $secret_info;
94 + $result[$user_id][WPRWP2FA::SECRET_META_KEY] = $secret_saved;
95 + if (!$secret_saved) {
96 + $status = false;
97 + continue;
98 + }
99 +
100 + update_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, 'totp');
101 + $method_saved = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true) === 'totp';
102 + $result[$user_id][WPRWP2FA::METHOD_META_KEY] = $method_saved;
103 + if (!$method_saved) {
104 + $status = false;
105 + continue;
106 + }
107 +
108 + update_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
109 + $flag_saved = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true) === '1';
110 + $result[$user_id][WPRWP2FA::FLAG_META_KEY] = $flag_saved;
111 + if (!$flag_saved) {
112 + $status = false;
113 + }
79 114 }
80 115
81 116 if (is_bool($enabled)) {
82 117 $config = array("enabled" => $enabled);
83 - $result[WPRWP2FA::$wp_2fa_option] = $this->settings->updateOption(WPRWP2FA::$wp_2fa_option, $config);
118 + $this->settings->updateOption(WPRWP2FA::$wp_2fa_option, $config);
119 + $option_saved = WPRWP2FA::isEnabled($this->settings) === $enabled;
120 + $result[WPRWP2FA::$wp_2fa_option] = $option_saved;
121 + if (!$option_saved) {
122 + $status = false;
123 + }
84 124 }
85 125
86 - return array("status" => true, "result" => $result);
126 + return array("status" => $status, "result" => $result);
87 127 }
88 128
89 129 public function verifyWP2FACode($user_id, $code, $cipher_algo = null) {
90 130 $encoded_secret_info = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true);
@@ -112,9 +152,9 @@
112 152 return array("status" => false, "message" => "Decryption key not found.");
113 153 }
114 154 }
115 155
116 - return array("status" => WPRWP2FAAuthenticator::verifyCode($secret, $code, 2));
156 + return array("status" => WPRWP2FATimeOTP::verifyCode($secret, $code, 2));
117 157 }
118 158
119 159 public function readWP2FAKeys($user_id) {
120 160 $secret = get_user_meta($user_id, WPRWP2FA::SECRET_META_KEY, true);
@@ -126,27 +166,138 @@
126 166 }
127 167
128 168 public function deleteWP2FAKeys($user_ids, $is_disable = false) {
129 169 $result = array();
170 + $status = true;
130 171
131 172 foreach ($user_ids as $user_id) {
132 - $secret_deleted = delete_user_meta($user_id, WPRWP2FA::SECRET_META_KEY);
133 - $flag_deleted = delete_user_meta($user_id, WPRWP2FA::FLAG_META_KEY);
173 + $secret_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::SECRET_META_KEY);
174 + $flag_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::FLAG_META_KEY);
175 + $method_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::METHOD_META_KEY);
176 + $email_state_deleted = WPRWP2FAEmailOTP::revoke($user_id);
177 + $totp_state_deleted = WPRWP2FATimeOTPLogin::clearState($user_id);
178 + $status = $status && $secret_deleted && $flag_deleted && $method_deleted &&
179 + $email_state_deleted && $totp_state_deleted;
134 180 $result[$user_id] = array(
135 181 WPRWP2FA::SECRET_META_KEY => $secret_deleted,
136 - WPRWP2FA::FLAG_META_KEY => $flag_deleted
182 + WPRWP2FA::FLAG_META_KEY => $flag_deleted,
183 + WPRWP2FA::METHOD_META_KEY => $method_deleted,
184 + WPRWP2FA::EMAIL_CHALLENGE_META_KEY => $email_state_deleted
137 185 );
138 186 }
139 187
140 188 if ($is_disable === true) {
141 - $result[WPRWP2FA::$wp_2fa_option] = $this->settings->deleteOption(WPRWP2FA::$wp_2fa_option);
189 + $this->settings->deleteOption(WPRWP2FA::$wp_2fa_option);
190 + $option_deleted = $this->settings->getOption(WPRWP2FA::$wp_2fa_option) === false;
191 + $result[WPRWP2FA::$wp_2fa_option] = $option_deleted;
192 + $status = $status && $option_deleted;
142 193 }
143 194
144 - return array("status" => true, "result" => $result);
195 + return array("status" => $status, "result" => $result);
145 196 }
146 197
198 + private function deleteUserMetaState($user_id, $key) {
199 + delete_user_meta($user_id, $key);
200 + return !metadata_exists('user', $user_id, $key);
201 + }
202 +
203 + private function restoreEmailWP2FAMeta($user_id, $key, $value) {
204 + if ($value === '') {
205 + delete_user_meta($user_id, $key);
206 + if (get_user_meta($user_id, $key, true) !== '') update_user_meta($user_id, $key, '');
207 + } else {
208 + update_user_meta($user_id, $key, $value);
209 + }
210 + return get_user_meta($user_id, $key, true) === $value;
211 + }
212 +
213 + private function restoreEmailWP2FAUserState($user_id, $method, $flag) {
214 + $method_restored = $this->restoreEmailWP2FAMeta($user_id, WPRWP2FA::METHOD_META_KEY, $method);
215 + $flag_restored = $this->restoreEmailWP2FAMeta($user_id, WPRWP2FA::FLAG_META_KEY, $flag);
216 + return $method_restored && $flag_restored;
217 + }
218 +
219 + private function clearAuthenticatorState($user_id) {
220 + $secret_deleted = $this->deleteUserMetaState($user_id, WPRWP2FA::SECRET_META_KEY);
221 + $attempt_state_deleted = WPRWP2FATimeOTPLogin::clearState($user_id);
222 + return $secret_deleted && $attempt_state_deleted;
223 + }
224 +
225 + private function isValidUserId($user_id) {
226 + $is_integer = is_int($user_id);
227 + $is_integer_string = is_string($user_id) && ctype_digit($user_id);
228 + return ($is_integer || $is_integer_string) && intval($user_id) > 0;
229 + }
230 +
231 + public function setupEmailWP2FA($capability_version, $enabled, $targets) {
232 + if (!is_int($capability_version) || $capability_version !== 1 || $enabled !== true || !is_array($targets) || count($targets) < 1 || count($targets) > 100) return array('status' => false, 'outcomes' => array());
233 + $seen_user_ids = array();
234 + foreach ($targets as $target) {
235 + if (!is_array($target) || !isset($target['user_id']) || !is_int($target['user_id']) || $target['user_id'] < 1 || !array_key_exists('replace_existing', $target) || !is_bool($target['replace_existing']) || isset($seen_user_ids[$target['user_id']])) return array('status' => false, 'outcomes' => array());
236 + $seen_user_ids[$target['user_id']] = true;
237 + }
238 + if (!WPRWP2FAEmailOTP::hasSiteSecret()) {
239 + $outcomes = array();
240 + foreach ($targets as $target) {
241 + $outcomes[] = array('user_id' => $target['user_id'], 'status' => 'rejected', 'reason' => 'secure_secret_unavailable');
242 + }
243 + return array('status' => true, 'outcomes' => $outcomes);
244 + }
245 + $config = $this->settings->getOption(WPRWP2FA::$wp_2fa_option);
246 + if (!is_array($config)) $config = array();
247 + $config['enabled'] = true;
248 + $this->settings->updateOption(WPRWP2FA::$wp_2fa_option, $config);
249 + if (!WPRWP2FA::isEnabled($this->settings)) return array('status' => false, 'outcomes' => array());
250 + $outcomes = array();
251 + foreach ($targets as $target) {
252 + $user_id = isset($target['user_id']) ? $target['user_id'] : null;
253 + $replace = isset($target['replace_existing']) && $target['replace_existing'] === true;
254 + if (!is_int($user_id) || $user_id < 1) continue;
255 + $user = get_userdata($user_id);
256 + if (!$user) { $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'not_found'); continue; }
257 + if (!is_email($user->user_email)) { $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'invalid_email'); continue; }
258 + $current = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true);
259 + $has_2fa = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true) === '1';
260 + $current = ($has_2fa && $current === '') ? 'totp' : $current;
261 + if ($has_2fa && $current === 'email_otp') {
262 + $authenticator_state_cleared = $this->clearAuthenticatorState($user_id);
263 + $outcomes[] = array(
264 + 'user_id' => $user_id,
265 + 'status' => $authenticator_state_cleared ? 'already_configured' : 'rejected',
266 + 'reason' => $authenticator_state_cleared ? null : 'persistence_failed'
267 + );
268 + continue;
269 + }
270 + if ($has_2fa && !$replace) { $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'replacement_required'); continue; }
271 + $previous_method = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true);
272 + $previous_flag = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
273 + if (!WPRWP2FAEmailOTP::revoke($user_id)) {
274 + $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'persistence_failed');
275 + continue;
276 + }
277 + update_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, 'email_otp');
278 + update_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true);
279 + $method_persisted = get_user_meta($user_id, WPRWP2FA::METHOD_META_KEY, true) === 'email_otp';
280 + $flag_persisted = get_user_meta($user_id, WPRWP2FA::FLAG_META_KEY, true) === '1';
281 + if (!$method_persisted || !$flag_persisted) {
282 + $rollback_restored = $this->restoreEmailWP2FAUserState($user_id, $previous_method, $previous_flag);
283 + $reason = $rollback_restored ? 'persistence_failed' : 'rollback_failed';
284 + $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => $reason);
285 + continue;
286 + }
287 + # Dropped only once the switch has stuck, so the rollback above still has it.
288 + if (!$this->clearAuthenticatorState($user_id)) {
289 + $outcomes[] = array('user_id' => $user_id, 'status' => 'rejected', 'reason' => 'persistence_failed');
290 + continue;
291 + }
292 + $outcomes[] = array('user_id' => $user_id, 'status' => 'configured', 'reason' => null);
293 + }
294 + return array('status' => true, 'outcomes' => $outcomes);
295 + }
296 +
147 297 public function process($request) {
148 - $params = $request->params;
298 + $params = isset($request->params) && is_array($request->params) ? $request->params : array();
299 + $invalid_params = array('status' => false, 'message' => 'Invalid parameters.');
149 300
150 301 switch ($request->method) {
151 302 case "gtcrntb":
152 303 $resp = $this->getCrontab();
@@ -151,23 +302,55 @@
151 302 case "gtcrntb":
152 303 $resp = $this->getCrontab();
153 304 break;
154 305 case "stupwp2fa":
155 - $enable_wp_2fa = null;
156 - if (array_key_exists('enable_wp_2fa', $request->params)) {
157 - $enable_wp_2fa = $request->params['enable_wp_2fa'];
306 + $secrets_by_uids = array_key_exists('secrets_by_uids', $params) ? $params['secrets_by_uids'] : null;
307 + $to_encrypt = array_key_exists('to_encrypt', $params) ? $params['to_encrypt'] : null;
308 + $cipher_algo = array_key_exists('cipher_algo', $params) ? $params['cipher_algo'] : null;
309 + $enable_wp_2fa = array_key_exists('enable_wp_2fa', $params) ? $params['enable_wp_2fa'] : null;
310 + if (!is_array($secrets_by_uids) || !is_bool($to_encrypt) ||
311 + (!is_null($cipher_algo) && !is_string($cipher_algo)) ||
312 + (!is_null($enable_wp_2fa) && !is_bool($enable_wp_2fa))) {
313 + $resp = $invalid_params;
314 + break;
158 315 }
159 -
160 - $resp = $this->setupWP2FA($params['secrets_by_uids'], $params['to_encrypt'], $params['cipher_algo'], $enable_wp_2fa);
316 + $resp = $this->setupWP2FA($secrets_by_uids, $to_encrypt, $cipher_algo, $enable_wp_2fa);
161 317 break;
318 + case "stupemail2fa":
319 + $capability_version = array_key_exists('capability_version', $params) ? $params['capability_version'] : null;
320 + $enable_wp_2fa = array_key_exists('enable_wp_2fa', $params) ? $params['enable_wp_2fa'] : null;
321 + $targets = array_key_exists('targets', $params) ? $params['targets'] : null;
322 + $resp = $this->setupEmailWP2FA($capability_version, $enable_wp_2fa, $targets);
323 + break;
162 324 case "vrfywp2fa":
163 - $resp = $this->verifyWP2FACode($params['user_id'], $params['code'], $params['cipher_algo']);
325 + $user_id = array_key_exists('user_id', $params) ? $params['user_id'] : null;
326 + $code = array_key_exists('code', $params) ? $params['code'] : null;
327 + $cipher_algo = array_key_exists('cipher_algo', $params) ? $params['cipher_algo'] : null;
328 + if (!$this->isValidUserId($user_id) || !is_string($code) ||
329 + (!is_null($cipher_algo) && !is_string($cipher_algo))) {
330 + $resp = $invalid_params;
331 + break;
332 + }
333 + $resp = $this->verifyWP2FACode($user_id, $code, $cipher_algo);
164 334 break;
165 335 case "rdwp2fa":
166 - $resp = $this->readWP2FAKeys($params['user_id']);
336 + $user_id = array_key_exists('user_id', $params) ? $params['user_id'] : null;
337 + $resp = $this->isValidUserId($user_id) ? $this->readWP2FAKeys($user_id) : $invalid_params;
167 338 break;
168 339 case "dltewp2fa":
169 - $resp = $this->deleteWP2FAKeys($params['user_ids'], $params['is_disable']);
340 + $user_ids = array_key_exists('user_ids', $params) ? $params['user_ids'] : null;
341 + $is_disable = array_key_exists('is_disable', $params) ? $params['is_disable'] : null;
342 + $valid_user_ids = is_array($user_ids);
343 + if ($valid_user_ids) {
344 + foreach ($user_ids as $user_id) {
345 + if (!$this->isValidUserId($user_id)) {
346 + $valid_user_ids = false;
347 + break;
348 + }
349 + }
350 + }
351 + $resp = ($valid_user_ids && is_bool($is_disable)) ?
352 + $this->deleteWP2FAKeys($user_ids, $is_disable) : $invalid_params;
170 353 break;
171 354 default:
172 355 $resp = false;
173 356 }
@@ -174,5 +357,5 @@
174 357
175 358 return $resp;
176 359 }
177 360 }
178 -endif;
361 +endif;