PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.9.1
WP 2FA – Two-factor authentication for WordPress v2.9.1
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Methods / class-email.php
wp-2fa / includes / classes / Admin / Methods Last commit date
Traits 11 months ago class-backup-codes.php 11 months ago class-email-wizard-steps.php 11 months ago class-email.php 11 months ago class-totp-wizard-steps.php 11 months ago class-totp.php 11 months ago index.php 11 months ago
class-email.php
388 lines
1 <?php
2 /**
3 * Responsible for WP2FA user's email method manipulation.
4 *
5 * @package wp2fa
6 * @subpackage methods
7 *
8 * @copyright 2025 Melapress
9 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
10 *
11 * @see https://wordpress.org/plugins/wp-2fa/
12 *
13 * @since 2.6.0
14 */
15
16 declare(strict_types=1);
17
18 namespace WP2FA\Methods;
19
20 use WP2FA\WP2FA;
21 use WP2FA\Admin\Setup_Wizard;
22 use WP2FA\Utils\Settings_Utils;
23 use WP2FA\Admin\Helpers\User_Helper;
24 use WP2FA\Admin\Controllers\Settings;
25 use WP2FA\Authenticator\Authentication;
26 use WP2FA\Admin\Controllers\API\API_Login;
27 use WP2FA\Methods\Wizards\Email_Wizard_Steps;
28 use WP2FA\Admin\SettingsPages\Settings_Page_White_Label;
29
30 use function WP2FA\Core\i18n;
31
32 /**
33 * Class for handling email codes.
34 *
35 * @since 2.6.0
36 *
37 * @package WP2FA
38 */
39 if ( ! class_exists( '\WP2FA\Methods\Email' ) ) {
40 /**
41 * Email code class, for handling email method code generation and such.
42 *
43 * @since 2.6.0
44 */
45 class Email {
46
47 /**
48 * The name of the method.
49 *
50 * @var string
51 *
52 * @since 2.6.0
53 */
54 public const METHOD_NAME = 'email';
55
56 /**
57 * The name of the method stored in the policy
58 *
59 * @var string
60 *
61 * @since 2.6.0
62 */
63 public const POLICY_SETTINGS_NAME = 'enable_email';
64
65 /**
66 * Is the mail enabled
67 *
68 * @since 2.6.0
69 *
70 * @var bool
71 */
72 private static $email_enabled = null;
73
74 /**
75 * Is the mail enforced
76 *
77 * @since 3.0.0
78 *
79 * @var bool
80 */
81 private static $email_enforced = null;
82
83 /**
84 * Inits the class and sets the filters.
85 *
86 * @return void
87 *
88 * @since 2.6.0
89 */
90 public static function init() {
91
92 \add_filter( WP_2FA_PREFIX . 'providers_translated_names', array( __CLASS__, 'email_provider_name_translated' ) );
93
94 \add_filter( WP_2FA_PREFIX . 'providers', array( __CLASS__, 'email_provider' ) );
95
96 \add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) );
97
98 \add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'settings_loop' ), 10, 1 );
99
100 \add_filter( WP_2FA_PREFIX . 'no_method_enabled', array( __CLASS__, 'return_default_selection' ), 10, 1 );
101
102 // add the HOTP methods to the list of available methods if enabled.
103 \add_filter(
104 WP_2FA_PREFIX . 'available_2fa_methods',
105 function ( $available_methods, $role ) {
106 if ( ! empty( Settings_Utils::get_setting_role( $role, self::POLICY_SETTINGS_NAME ) ) ) {
107 array_push( $available_methods, self::METHOD_NAME );
108 }
109
110 return $available_methods;
111 },
112 10,
113 2
114 );
115
116 // \add_action( WP_2FA_PREFIX . 'before_settings_save_roles', array( __CLASS__, 'set_email_settings_when_enforced' ) );
117
118 // \add_action( WP_2FA_PREFIX . 'before_settings_save', array( __CLASS__, 'set_email_settings_when_enforced' ) );
119
120 \add_filter( WP_2FA_PREFIX . 'white_label_default_settings', array( __CLASS__, 'add_whitelabel_settings' ) );
121
122 \add_action( WP_2FA_PREFIX . 'validate_login_api', array( __CLASS__, 'api_login_validate' ), 10, 3 );
123
124 \add_action( WP_2FA_PREFIX . 'white_label_wizard_options', array( __CLASS__, 'white_label_option_labels' ) );
125
126 Email_Wizard_Steps::init();
127 }
128
129 /**
130 * Checks the provided user and token and validates them. Returns true if valid, false otherwise.
131 *
132 * @param array $valid - The current validation value.
133 * @param integer $user_id - The user ID to check for.
134 * @param string|null $token - The token to validate against user provided.
135 *
136 * @return array
137 *
138 * @since 3.0.0
139 */
140 public static function api_login_validate( array $valid, int $user_id, ?string $token ): array {
141
142 if ( ! Settings::is_provider_enabled_for_role( User_Helper::get_user_role( $user_id ), self::METHOD_NAME ) ) {
143 return $valid;
144 }
145
146 if ( self::METHOD_NAME !== User_Helper::get_enabled_method_for_user( $user_id ) ) {
147 return $valid;
148 }
149
150 if ( ! is_array( $valid ) || ! isset( $valid['valid'] ) ) {
151 $valid['valid'] = false;
152 }
153
154 // If the login is valid, return it as it is.
155 if ( true === $valid['valid'] ) {
156 return $valid;
157 }
158
159 if ( ! isset( $token ) || empty( $token ) ) {
160 return $valid;
161 }
162
163 // Sanitize the token to ensure it is safe to use.
164 $sanitized_token = \sanitize_text_field( $token );
165
166 $is_valid = Authentication::validate_token( User_Helper::get_user( $user_id ), $sanitized_token );
167
168 if ( ! $is_valid ) {
169 $valid[ self::METHOD_NAME ]['error'] = \esc_html__( 'ERROR: Invalid verification code.', 'wp-2fa' );
170 if ( API_Login::check_number_of_attempts( User_Helper::get_user( $user_id ) ) ) {
171
172 if ( empty( WP2FA::get_wp2fa_general_setting( 'brute_force_disable' ) ) ) {
173 Setup_Wizard::send_authentication_setup_email( $user_id, 'nominated_email_address' );
174 if ( ! empty( WP2FA::get_wp2fa_general_setting( 'brute_force_disable' ) ) ) {
175 User_Helper::set_meta( WP_2FA_PREFIX . 'code_sent', true );
176 }
177
178 $valid[ self::METHOD_NAME ]['error'] .= \esc_html__( ' For security reasons you have been sent a new code via email. Please use this new code to log in.', 'wp-2fa' );
179 }
180 }
181 }
182
183 $valid['valid'] = $is_valid;
184
185 return $valid;
186 }
187
188 /**
189 * Adds email provider translatable name
190 *
191 * @param array $providers - Array with all currently supported providers and their translated names.
192 *
193 * @return array
194 *
195 * @since 2.6.0
196 */
197 public static function email_provider_name_translated( array $providers ) {
198 $providers[ self::METHOD_NAME ] = \esc_html__( 'HOTP (Email)', 'wp-2fa' );
199
200 return $providers;
201 }
202
203 /**
204 * Adds email as a provider
205 *
206 * @param array $providers - Array with all currently supported providers.
207 *
208 * @return array
209 *
210 * @since 2.6.0
211 */
212 public static function email_provider( array $providers ) {
213 $providers[ self::class ] = self::METHOD_NAME;
214
215 return $providers;
216 }
217
218 /**
219 * Adds the extension default settings to the main plugin settings
220 *
221 * @param array $default_settings - array with plugin default settings.
222 *
223 * @return array
224 *
225 * @since 2.6.0
226 */
227 public static function add_default_settings( array $default_settings ) {
228 $default_settings[ self::POLICY_SETTINGS_NAME ] = self::POLICY_SETTINGS_NAME;
229 $default_settings['specify-email_hotp'] = false;
230
231 // $default_settings['enforce-email_hotp'] = false;
232
233 return $default_settings;
234 }
235
236 /**
237 * Add extension settings to the loop array
238 *
239 * @param array $loop_settings - Currently available settings array.
240 *
241 * @return array
242 *
243 * @since 2.6.0
244 */
245 public static function settings_loop( array $loop_settings ) {
246 array_push( $loop_settings, self::POLICY_SETTINGS_NAME );
247 array_push( $loop_settings, 'specify-email_hotp' );
248 // array_push( $loop_settings, 'enforce-email_hotp' );
249
250
251 return $loop_settings;
252 }
253
254 /**
255 * Extracts the selected value from the global settings (if set), and adds it to the output array
256 *
257 * @param array $output - The array with output values.
258 *
259 * @return array
260 *
261 * @since 2.6.0
262 */
263 public static function return_default_selection( array $output ) {
264 // No method is enabled, fall back to previous selected one - we don't want to break the logic.
265 $email_enabled = WP2FA::get_wp2fa_setting( self::POLICY_SETTINGS_NAME );
266
267 if ( $email_enabled ) {
268 $output[ self::POLICY_SETTINGS_NAME ] = $email_enabled;
269 }
270
271 return $output;
272 }
273
274 /**
275 * Returns the status of the mail method (enabled | disabled) for the current user role
276 *
277 * @param string $role - The name of the role to check for.
278 *
279 * @since 2.6.0
280 * @since 2.9.0 - Added role parameter to check if the Twilio is enabled for the given role.
281 *
282 * @return boolean
283 */
284 public static function is_enabled( ?string $role = null ): bool {
285 if ( null === self::$email_enabled ) {
286 self::$email_enabled = ! empty( Settings_Utils::get_setting_role( $role, 'enable_email' ) );
287 }
288
289 return self::$email_enabled;
290 }
291
292 /**
293 * Returns the status of the mail method enforcement (enabled | disabled) for the current user role.
294 *
295 * @since 3.0.0
296 *
297 * @return boolean
298 */
299 // public static function is_enforced(): bool {
300 // if ( ! self::is_enabled() ) {
301 // return false;
302 // }
303
304 // if ( null === self::$email_enforced ) {
305 // self::$email_enforced = ! empty( Settings_Utils::get_setting_role( User_Helper::get_user_role(), 'enforce-email_hotp' ) );
306 // }
307
308 // return self::$email_enforced;
309 // }
310
311 /**
312 * If email HOPT is enforced, the option to specify email is disabled - this method makes sure of this. It is called when roles settings are saved and when the main policy settings are saved, that's why it hase such a logic.
313 *
314 * @param array $settings - The array with all the collected settings.
315 *
316 * @return array
317 *
318 * @since 3.0.0
319 */
320 // public static function set_email_settings_when_enforced( array $settings ): array {
321 // foreach ( $settings as $key => &$value ) {
322 // if ( \is_array( $value ) ) {
323 // if ( isset( $value['enforce-email_hotp'] ) && 'enforce-email_hotp' === $value['enforce-email_hotp'] ) {
324 // $value['specify-email_hotp'] = false;
325 // }
326 // } else {
327 // if ( isset( $settings['enforce-email_hotp'] ) && 'enforce-email_hotp' === $settings['enforce-email_hotp'] ) {
328 // $settings['specify-email_hotp'] = false;
329 // }
330
331 // break;
332 // }
333 // }
334 // unset( $value );
335
336 // return $settings;
337 // }
338
339 /**
340 * Fills up the White Label settings array with the method defaults.
341 *
342 * @param array $default_settings - The array with the collected white label settings.
343 *
344 * @return array
345 *
346 * @since 3.0.0
347 */
348 public static function add_whitelabel_settings( array $default_settings ): array {
349
350 $default_settings['method_help_hotp_intro'] = '<h3>' . __( 'Setting up HOTP (one-time code via email)', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the email address where the one-time code should be sent:', 'wp-2fa' ) . '</p>';
351 $default_settings['method_help_hotp_help'] = __( 'To complete the 2FA configuration you will be sent a one-time code over email, therefore you should have access to the mailbox of this email address. If you do not receive the email with the one-time code please check your spam folder and contact your administrator', 'wp-2fa' );
352 $default_settings['method_help_hotp_help_email'] = '<b>' . __( 'IMPORTANT', 'wp-2fa' ) . '</b><p>' . __( 'To ensure you always receive the one-time code whitelist the email address from which the codes are sent. This is {from_email}', 'wp-2fa' ) . '</p>';
353 $default_settings['method_verification_hotp_pre'] = '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent to your email address to finalize the setup', 'wp-2fa' ) . '</p>';
354 $default_settings['hotp_reconfigure_intro'] = '<h3>' . __( '{reconfigure_or_configure_capitalized} one-time code over email method', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} the email address where the one-time code should be sent.', 'wp-2fa' ) . '</p>';
355 $default_settings['email-option-label'] = __( 'One-time code via email', 'wp-2fa' );
356 $default_settings['email-option-label-hint'] = '';
357
358 return $default_settings;
359 }
360
361 /**
362 * Shows the Method option label and hint in the White Label settings.
363 *
364 * @return void
365 *
366 * @since 2.9.0
367 */
368 public static function white_label_option_labels() {
369 ?>
370 <strong class="description"><?php esc_html_e( 'Email option label', 'wp-2fa' ); ?></strong>
371 <br><br>
372 <fieldset>
373 <input type="text" id="email-option-label" name="wp_2fa_white_label[email-option-label]" class="large-text" value="<?php echo \esc_attr( WP2FA::get_wp2fa_white_label_setting( 'email-option-label', true ) ); ?>">
374 </fieldset>
375 <br>
376 <strong class="description"><?php esc_html_e( 'Email option hint', 'wp-2fa' ); ?></strong>
377 <br>
378 <fieldset>
379 <?php
380 echo Settings_Page_White_Label::create_standard_editor( 'email-option-label-hint' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
381 ?>
382 </fieldset>
383 <br>
384 <?php
385 }
386 }
387 }
388