PluginProbe
The WP Remote WordPress Plugin / 6.76
The WP Remote WordPress Plugin v6.76
6.76 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 All 54 releases
← All changes | wp_2fa/wp_2fa.php +124 -138 5.726.76 View file →
@@ -1,176 +1,162 @@
1 1 <?php
2 2 if (!defined('ABSPATH')) exit;
3 3 if (!class_exists('WPRWP2FA')) :
4 4
5 -require_once dirname(__FILE__) . '/authenticator.php';
6 5 require_once dirname(__FILE__) . '/utils.php';
6 +require_once dirname(__FILE__) . '/time_otp.php';
7 +require_once dirname(__FILE__) . '/time_otp_login.php';
8 +require_once dirname(__FILE__) . '/email_otp.php';
9 +require_once dirname(__FILE__) . '/email_otp_template.php';
10 +require_once dirname(__FILE__) . '/email_otp_sender.php';
11 +require_once dirname(__FILE__) . '/email_otp_login.php';
7 12
8 13 class WPRWP2FA {
9 14 const FLAG_META_KEY = 'wpr_2fa_enabled';
10 15 const SECRET_META_KEY = 'wpr_2fa_secret';
16 + const METHOD_META_KEY = 'wpr_2fa_method';
17 + const EMAIL_CHALLENGE_META_KEY = 'wpr_2fa_email_challenge';
18 + const EMAIL_RATE_META_KEY = 'wpr_2fa_email_rate';
19 + const ATTEMPTS_META_KEY = 'wpr_2fa_attempts';
20 + const INVALID_CODE_MESSAGE = 'The 2FA code you entered is incorrect.';
21 + const TOOLTIP_MESSAGE = 'Please contact your administrator if you need assistance.';
22 + const CONTEXT_MESSAGE = 'Two-factor authentication is required in the standard sign-in page.';
23 + const CONFIG_MESSAGE = 'Please contact your administrator to login.';
11 24
12 25 public static $cipher_algo = 'aes-256-cbc';
26 + public static $wp_2fa_option = 'wprWp2faConf';
27 +
28 + private static $whitelabel = null;
13 29
30 + public static function whitelabelMessage($key, $default) {
31 + if (self::$whitelabel === null) {
32 + $info = new WPRInfo(new WPRWPSettings());
33 + $values = $info->getLPWhitelabelInfo();
34 + self::$whitelabel = is_array($values) ? $values : array();
35 + }
36 +
37 + return (isset(self::$whitelabel[$key]) && is_string(self::$whitelabel[$key])) ? self::$whitelabel[$key] : $default;
38 + }
39 +
40 + # Sent as a JSON failure rather than returned as a WP_Error. These are system
41 + # states, not wrong credentials, and a WP_Error would be recorded as a failed
42 + # login against the firewall's lockout counter.
43 + public static function sendFailure($message, $data = array()) {
44 + wp_send_json_error(array_merge($data, array('message' => $message)));
45 + exit;
46 + }
47 +
48 + public static function humanWait($seconds) {
49 + if ($seconds < 90) {
50 + return sprintf('%d seconds', max(1, intval($seconds)));
51 + }
52 +
53 + return sprintf('%d minutes', intval(ceil($seconds / MINUTE_IN_SECONDS)));
54 + }
55 +
56 + public static function isEnabled($settings) {
57 + $config = $settings->getOption(self::$wp_2fa_option);
58 +
59 + return (is_array($config) && array_key_exists('enabled', $config) &&
60 + $config['enabled'] === true);
61 + }
62 +
14 63 public function init() {
15 64 add_filter('authenticate', array($this, 'authenticate'), 25, 3);
16 65 add_action('login_form', array($this, 'custom_login_form'));
66 + add_action('login_enqueue_scripts', array($this, 'enqueue_login_assets'));
17 67 }
18 68
69 + public function enqueue_login_assets() {
70 + wp_enqueue_style('WPR-wp-2fa-login', plugin_dir_url(__FILE__) . 'css/login.css', array(), '1.4');
71 + wp_enqueue_script('WPR-wp-2fa-login', plugin_dir_url(__FILE__) . 'js/login.js', array(), '1.4', true);
72 + }
73 +
19 74 public function authenticate($user, $username, $password) {
20 75 if (!($user instanceof WP_User)) {
21 76 return $user;
22 77 }
23 78
24 - $has_2fa = get_user_meta($user->ID, WPRWP2FA::FLAG_META_KEY, true);
79 + if ('1' !== get_user_meta($user->ID, self::FLAG_META_KEY, true)) {
80 + return $user;
81 + }
25 82
26 - if ('1' === $has_2fa) {
27 - if (empty($_POST['twofa_code'])) {
28 - wp_send_json_success(array('twofa_enabled' => true));
29 - exit;
30 - } else {
31 - $encoded_secret_info = get_user_meta($user->ID, WPRWP2FA::SECRET_META_KEY, true);
83 + if (!self::isInteractiveLogin()) {
84 + return new WP_Error('twofa_context', self::CONTEXT_MESSAGE);
85 + }
32 86
33 - $secret_info = WPRWP2FAUtils::getSecretInfo($encoded_secret_info);
34 - $secret = $secret_info['secret'];
35 - $is_secret_encrypted = $secret_info['is_encrypted'];
87 + $method = get_user_meta($user->ID, self::METHOD_META_KEY, true);
88 + if ($method === 'email_otp') {
89 + return WPRWP2FAEmailOTPLogin::authenticate($user);
90 + }
91 + if ($method !== '' && $method !== 'totp') {
92 + self::sendFailure(self::CONFIG_MESSAGE);
93 + }
36 94
37 - if (is_null($secret) || is_null($is_secret_encrypted)) {
38 - return new WP_Error('invalid_2fa_configuration', __('Please contact your administrator to login.'));
39 - }
95 + return WPRWP2FATimeOTPLogin::authenticate($user);
96 + }
40 97
41 - if (defined('SECURE_AUTH_KEY') && $is_secret_encrypted === true) {
42 - $decryption_result = WPRHelper::opensslDecrypt($secret, self::$cipher_algo, SECURE_AUTH_KEY);
43 - if ($decryption_result[0] === false) {
44 - return new WP_Error('2fa_secret_key_decryption_error', __('Please contact your administrator to login.'));
45 - }
46 - $secret = $decryption_result[1];
47 - }
98 + private static function isInteractiveLogin() {
99 + global $pagenow;
48 100
49 - if (empty($secret) || !is_string($secret) || 32 !== strlen($secret)) {
50 - return new WP_Error('invalid_2fa_configuration', __('Please contact your administrator to login.'));
51 - }
52 -
53 - $submitted_code = sanitize_text_field($_POST['twofa_code']);
54 -
55 - if (is_string($submitted_code) && ctype_digit($submitted_code) &&
56 - true === WPRWP2FAAuthenticator::verifyCode($secret, $submitted_code)) {
57 -
58 - return $user;
59 - } else {
60 - return new WP_Error('invalid_2fa_code', __('The 2FA code you entered is incorrect.'));
61 - }
62 - }
101 + if ((defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) ||
102 + (defined('REST_REQUEST') && REST_REQUEST) ||
103 + (defined('WP_CLI') && WP_CLI)) {
104 + return false;
63 105 }
64 106
65 - return $user;
107 + return $pagenow === 'wp-login.php';
66 108 }
67 109
68 110 function custom_login_form() {
69 -?>
70 - <script type="text/javascript">
71 - document.addEventListener('DOMContentLoaded', function() {
72 - const loginForm = document.getElementById('loginform');
73 - const usernameField = document.getElementById('user_login');
74 - const passwordField = document.getElementById('user_pass');
75 - let loginError = document.getElementById('login_error');
76 - let isTwoFAEnabled = false;
111 + $tooltip_message = self::whitelabelMessage('2fa_tooltip', self::TOOLTIP_MESSAGE);
112 + $is_url = filter_var($tooltip_message, FILTER_VALIDATE_URL);
113 + $allowed_tooltip_html = array(
114 + 'a' => array(
115 + 'class' => true,
116 + 'href' => true,
117 + 'rel' => true,
118 + 'target' => true
119 + ),
120 + 'span' => array(
121 + 'class' => true,
122 + 'id' => true,
123 + 'title' => true
124 + )
125 + );
77 126
78 - if (loginForm && usernameField && passwordField) {
79 - loginForm.addEventListener('submit', handleSubmit);
80 - }
127 + $icon_html = '<span
128 + id="twofa-help-icon"
129 + class="dashicons dashicons-editor-help"></span>';
81 130
82 - function handleSubmit(event) {
83 - event.preventDefault();
84 -
85 - const formData = new FormData(loginForm);
86 -
87 - fetch(loginForm.action, {
88 - method: 'POST',
89 - body: formData,
90 - credentials: 'same-origin'
91 - }).then(response => response.text())
92 - .then(text => {
93 - try {
94 - return JSON.parse(text);
95 - } catch (e) {
96 - return { success: false, html: text };
97 - }
98 - })
99 - .then(data => {
100 - if (data.success && data.data && data.data.twofa_enabled) {
101 - isTwoFAEnabled = true;
102 - showTwoFAField();
103 - clearLoginError();
104 - } else {
105 - if (data.html) {
106 - handleHtmlResponse(data.html);
107 - } else if (data.data && data.data.message) {
108 - displayError(data.data.message);
109 - } else {
110 - displayError('An unknown error occurred');
111 - }
112 - if (isTwoFAEnabled) {
113 - showTwoFAField();
114 - }
115 - }
116 - })
117 - .catch(error => {
118 - displayError('An error occurred while processing your request');
119 - if (isTwoFAEnabled) {
120 - showTwoFAField();
121 - }
122 - });
123 - }
124 -
125 - function handleHtmlResponse(html) {
126 - const parser = new DOMParser();
127 - const doc = parser.parseFromString(html, 'text/html');
128 - const errorElement = doc.getElementById('login_error');
129 - if (errorElement) {
130 - displayError(errorElement.innerText.trim());
131 - } else {
132 - proceedWithLogin();
133 - }
134 - }
135 -
136 - function showTwoFAField() {
137 - let twofaField = document.getElementById('twofa_code_field');
138 - if (!twofaField) {
139 - twofaField = document.createElement('p');
140 - twofaField.id = 'twofa_code_field';
141 - twofaField.innerHTML = '<label for="twofa_code">2FA Code<br><input type="text" name="twofa_code" id="twofa_code" class="input" value="" size="20"></label>';
142 - passwordField.parentNode.insertBefore(twofaField, passwordField.nextSibling);
143 - }
144 - twofaField.style.display = 'block';
145 - document.getElementById('twofa_code').value = '';
146 - }
147 -
148 - function clearLoginError() {
149 - if (loginError) {
150 - loginError.style.display = 'none';
151 - }
152 - }
153 -
154 - function displayError(message) {
155 - if (!loginError) {
156 - loginError = document.createElement('div');
157 - loginError.id = 'login_error';
158 - loginForm.parentNode.insertBefore(loginError, loginForm);
159 - }
160 - loginError.innerHTML = message;
161 - loginError.style.display = 'block';
162 - }
163 -
164 - function proceedWithLogin() {
165 - loginForm.removeEventListener('submit', handleSubmit);
166 - loginForm.submit();
167 - }
168 - });
169 - </script>
131 + if ($is_url) {
132 + $tooltip_html = '<a
133 + href="' . esc_url($tooltip_message) . '"
134 + target="_blank"
135 + rel="noopener noreferrer"
136 + class="twofa-help-link">' . $icon_html . '</a>';
137 + } else {
138 + $tooltip_html = '<span
139 + id="twofa-help-icon"
140 + class="dashicons dashicons-editor-help"
141 + title="' . esc_attr($tooltip_message) . '"></span>';
142 + }
143 +?>
144 + <div class="wp2fa-progress-bar">
145 + <div class="progress-bar-inner"></div>
146 + </div>
147 + <template id="twofa-field-template">
148 + <p id="twofa-code-field">
149 + <label for="twofa-code">
150 + 2FA Code
151 + <?php echo wp_kses($tooltip_html, $allowed_tooltip_html); ?>
152 + </label>
153 + <input type="text" required name="twofa_code" id="twofa-code" class="input" value="" maxlength="6" minlength="6" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code">
154 + <span id="twofa-destination" role="status" aria-live="polite"></span>
155 + <button type="button" id="twofa-resend" class="button-link">Send a new code</button>
156 + <span id="twofa-resend-status" role="status" aria-live="polite"></span>
157 + </p>
158 + </template>
170 159 <?php
171 160 }
172 161 }
173 162 endif;
174 -
175 -$wp_2fa = new WPRWP2FA();
176 -$wp_2fa->init();