Version Description
- Enhancement: Add Per-Content SSL toggle to the upcoming Block Editor interface.
- Enhancement: Add filter to the recipients list for email notifications: "itsec_notification_{$notification}email_recipients" and "itsec_notificationemail_recipients".
- Enhancement: Add define "ITSEC_DISABLE_TEMP_WHITELIST" to disable the Temporary IP Whitelisting for logged-in administrators.
- Enhancement: Improve redirecting after processing a login interstitial from a front-end login form.
- Enhancement: Add loopback IP detection to Security Check.
- Enhancement: Detect Server IPs in Security Check.
- Tweak: Add additional safety checks when writing to system config files. This will log a "Critical Issue" when the writing of an empty or partial config file is detected and prevented.
- Tweak: Improve File Change locking to help prevent failing scans on sites with inconsistent cron scheduling.
- Tweak: Improve "System Tweaks Suspicious Query Strings SQLI" to reduce false positives.
- Tweak: Improve "System Tweaks Disable PHP" to block PHP files in apache configurations that serve files with a trailing dot.
- Tweak: Remove "Seznam Bot" from HackRepair List as it isn't present in the latest version.
- Bug Fix: Include Hide Backend token when emailing a password reset URL.
- Bug Fix: Notification Center - Only send notifications to users with an exact role match of selected roles instead of a fuzzy match based on selected capabilities.
- Bug Fix: Error when trying to edit reusable blocks with per-post SSL enabled.
- Bug Fix: Resolve warnings on PHP 5.2.
Download this release
Release Info
Developer | TimothyBlynJacobs |
Plugin | iThemes Security (formerly Better WP Security) |
Version | 7.3.0 |
Comparing to | |
See all releases |
Code changes from version 7.2.0 to 7.3.0
- better-wp-security.php +5 -3
- core/admin-pages/js/settings.js +2 -0
- core/admin-pages/module-settings.php +11 -0
- core/admin-pages/page-logs.php +10 -0
- core/admin-pages/page-settings.php +10 -0
- core/core.php +17 -4
- core/history.txt +33 -1
- core/lib.php +113 -3
- core/lib/class-itsec-lib-config-file.php +14 -0
- core/lib/class-itsec-lib-fingerprinting.php +9 -2
- core/lib/class-itsec-lib-login-interstitial.php +548 -292
- core/lib/class-itsec-lib-remote-messages.php +171 -0
- core/lib/class-itsec-mail.php +28 -0
- core/lib/form.php +10 -0
- core/lib/log-util.php +13 -2
- core/lib/login-interstitial/abstract-itsec-login-interstitial.php +138 -0
- core/lib/login-interstitial/class-itsec-login-interstitial-config-driven.php +154 -0
- core/lib/login-interstitial/class-itsec-login-interstitial-session.php +505 -0
- core/lib/login-interstitial/index.php +1 -0
- core/lib/login-interstitial/util.js +131 -0
- core/lib/mail-templates/large-button.html +29 -0
- core/lib/mail-templates/small-code.html +23 -0
- core/lib/schema.php +2 -1
- core/lib/validator.php +7 -1
- core/lockout.php +83 -6
- core/modules.php +9 -3
- core/modules/404-detection/class-itsec-four-oh-four.php +7 -7
- core/modules/backup/class-itsec-backup.php +24 -19
- core/modules/backup/settings-page.php +2 -2
- core/modules/ban-users/lists/hackrepair-apache.inc +0 -1
- core/modules/ban-users/lists/hackrepair-litespeed.inc +0 -1
- core/modules/ban-users/lists/hackrepair-nginx.inc +0 -1
- core/modules/brute-force/class-itsec-brute-force.php +5 -2
- core/modules/brute-force/logs.php +13 -4
- core/modules/file-change/logs.php +5 -1
- core/modules/file-change/scanner.php +65 -14
- core/modules/global/logs.php +52 -0
- core/modules/global/settings.php +1 -0
- core/modules/global/validator.php +4 -2
- core/modules/hide-backend/class-itsec-hide-backend.php +3 -1
- core/modules/malware/class-itsec-malware-scanner.php +13 -4
- core/modules/malware/logs.php +2 -0
- core/modules/notification-center/class-notification-center.php +42 -7
- core/modules/password-requirements/class-itsec-password-requirements.php +4 -2
- core/modules/security-check/active.php +27 -0
- core/modules/security-check/feedback-renderer.php +3 -0
- core/modules/security-check/scanner.php +69 -0
- core/modules/ssl/active.php +4 -6
- core/modules/ssl/class-itsec-ssl-admin.php +36 -2
- core/modules/ssl/js/block-editor.js +37 -0
- core/modules/system-tweaks/config-generators.php +7 -7
- history.txt +18 -1
- readme.txt +21 -4
better-wp-security.php
CHANGED
@@ -6,15 +6,17 @@
|
|
6 |
* Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
|
7 |
* Author: iThemes
|
8 |
* Author URI: https://ithemes.com
|
9 |
-
* Version: 7.
|
10 |
* Text Domain: better-wp-security
|
11 |
* Network: True
|
12 |
* License: GPLv2
|
13 |
*/
|
14 |
|
15 |
function itsec_load_textdomain() {
|
16 |
-
|
17 |
-
if ( function_exists( '
|
|
|
|
|
18 |
$locale = get_user_locale();
|
19 |
} else {
|
20 |
$locale = get_locale();
|
6 |
* Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
|
7 |
* Author: iThemes
|
8 |
* Author URI: https://ithemes.com
|
9 |
+
* Version: 7.3.0
|
10 |
* Text Domain: better-wp-security
|
11 |
* Network: True
|
12 |
* License: GPLv2
|
13 |
*/
|
14 |
|
15 |
function itsec_load_textdomain() {
|
16 |
+
|
17 |
+
if ( function_exists( 'determine_locale' ) ) {
|
18 |
+
$locale = determine_locale();
|
19 |
+
} elseif ( function_exists( 'get_user_locale' ) && is_admin() ) {
|
20 |
$locale = get_user_locale();
|
21 |
} else {
|
22 |
$locale = get_locale();
|
core/admin-pages/js/settings.js
CHANGED
@@ -665,6 +665,8 @@ var itsecSettingsPage = {
|
|
665 |
itsecSettingsPage.isModuleActive( module );
|
666 |
}, 1000 );
|
667 |
|
|
|
|
|
668 |
return;
|
669 |
}
|
670 |
|
665 |
itsecSettingsPage.isModuleActive( module );
|
666 |
}, 1000 );
|
667 |
|
668 |
+
itsecSettingsPage.showErrors( results.errors, results.module, 'closed', 'error' );
|
669 |
+
|
670 |
return;
|
671 |
}
|
672 |
|
core/admin-pages/module-settings.php
CHANGED
@@ -194,10 +194,21 @@ class ITSEC_Module_Settings_Page {
|
|
194 |
*/
|
195 |
public function render( $form ) {
|
196 |
|
|
|
|
|
197 |
?>
|
198 |
<div class="itsec-settings-module-description">
|
199 |
<?php $this->render_description( $form ); ?>
|
200 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
<div class="itsec-settings-module-settings">
|
202 |
<?php $this->render_settings( $form ); ?>
|
203 |
</div>
|
194 |
*/
|
195 |
public function render( $form ) {
|
196 |
|
197 |
+
$messages = ITSEC_Lib_Remote_Messages::get_messages_for_placement( array( 'module' => $this->id ) );
|
198 |
+
|
199 |
?>
|
200 |
<div class="itsec-settings-module-description">
|
201 |
<?php $this->render_description( $form ); ?>
|
202 |
</div>
|
203 |
+
<?php if ( $messages ) : ?>
|
204 |
+
<div class="itsec-settings-module-service-status">
|
205 |
+
<?php foreach ( $messages as $message ): ?>
|
206 |
+
<div class="notice notice-alt notice-<?php echo esc_attr( $message['type'] ); ?> below-h2">
|
207 |
+
<p><?php echo $message['message']; ?></p>
|
208 |
+
</div>
|
209 |
+
<?php endforeach; ?>
|
210 |
+
</div>
|
211 |
+
<?php endif; ?>
|
212 |
<div class="itsec-settings-module-settings">
|
213 |
<?php $this->render_settings( $form ); ?>
|
214 |
</div>
|
core/admin-pages/page-logs.php
CHANGED
@@ -348,11 +348,21 @@ final class ITSEC_Logs_Page {
|
|
348 |
}
|
349 |
|
350 |
$details = wp_list_sort( $details, 'order', 'ASC', true );
|
|
|
351 |
}
|
352 |
|
353 |
ob_start();
|
354 |
|
355 |
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
356 |
<table class="form-table">
|
357 |
<?php foreach ( $details as $row ) : ?>
|
358 |
<tr>
|
348 |
}
|
349 |
|
350 |
$details = wp_list_sort( $details, 'order', 'ASC', true );
|
351 |
+
$messages = ITSEC_Lib_Remote_Messages::get_messages_for_placement( array( 'logs' => array( 'module' => $entry['module'], 'code' => $entry['code'] ) ) );
|
352 |
}
|
353 |
|
354 |
ob_start();
|
355 |
|
356 |
?>
|
357 |
+
<?php if ( $messages ) : ?>
|
358 |
+
<div class="itsec-logs-service-status">
|
359 |
+
<?php foreach ( $messages as $message ): ?>
|
360 |
+
<div class="notice notice-alt notice-<?php echo esc_attr( $message['type'] ); ?> below-h2">
|
361 |
+
<p><?php echo $message['message']; ?></p>
|
362 |
+
</div>
|
363 |
+
<?php endforeach; ?>
|
364 |
+
</div>
|
365 |
+
<?php endif; ?>
|
366 |
<table class="form-table">
|
367 |
<?php foreach ( $details as $row ) : ?>
|
368 |
<tr>
|
core/admin-pages/page-settings.php
CHANGED
@@ -494,6 +494,16 @@ final class ITSEC_Settings_Page {
|
|
494 |
<div id="itsec-sidebar-widget-<?php echo $id; ?>" class="postbox itsec-sidebar-widget">
|
495 |
<h3 class="hndle ui-sortable-handle"><span><?php echo esc_html( $widget->title ); ?></span></h3>
|
496 |
<div class="inside">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
<?php $this->get_widget_settings( $id, $form, true ); ?>
|
498 |
</div>
|
499 |
</div>
|
494 |
<div id="itsec-sidebar-widget-<?php echo $id; ?>" class="postbox itsec-sidebar-widget">
|
495 |
<h3 class="hndle ui-sortable-handle"><span><?php echo esc_html( $widget->title ); ?></span></h3>
|
496 |
<div class="inside">
|
497 |
+
<?php if ( $messages = ITSEC_Lib_Remote_Messages::get_messages_for_placement( array( 'widget' => $id ) ) ) : ?>
|
498 |
+
<div class="itsec-widgets-service-status">
|
499 |
+
<?php foreach ( $messages as $message ): ?>
|
500 |
+
<div class="notice notice-alt notice-<?php echo esc_attr( $message['type'] ); ?> below-h2">
|
501 |
+
<p><?php echo $message['message']; ?></p>
|
502 |
+
</div>
|
503 |
+
<?php endforeach; ?>
|
504 |
+
</div>
|
505 |
+
<?php endif; ?>
|
506 |
+
|
507 |
<?php $this->get_widget_settings( $id, $form, true ); ?>
|
508 |
</div>
|
509 |
</div>
|
core/core.php
CHANGED
@@ -24,7 +24,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
24 |
*
|
25 |
* @access private
|
26 |
*/
|
27 |
-
private $plugin_build =
|
28 |
|
29 |
/**
|
30 |
* Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
|
@@ -50,7 +50,8 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
50 |
$current_time_gmt,
|
51 |
$is_iwp_call,
|
52 |
$request_type,
|
53 |
-
$wp_upload_dir
|
|
|
54 |
|
55 |
|
56 |
/**
|
@@ -121,6 +122,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
121 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-password-requirements.php' );
|
122 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-login-interstitial.php' );
|
123 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-distributed-storage.php' );
|
|
|
124 |
|
125 |
require( $this->plugin_dir . 'core/lib/class-itsec-scheduler.php' );
|
126 |
require( $this->plugin_dir . 'core/lib/class-itsec-job.php' );
|
@@ -162,14 +164,16 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
162 |
add_action( 'admin_bar_menu', array( $this, 'modify_admin_bar' ), 99 );
|
163 |
}
|
164 |
|
165 |
-
$login_interstitial = new ITSEC_Lib_Login_Interstitial();
|
166 |
-
$login_interstitial->run();
|
167 |
|
168 |
if ( defined( 'ITSEC_USE_CRON' ) && ITSEC_USE_CRON !== ITSEC_Lib::use_cron() ) {
|
169 |
ITSEC_Modules::set_setting( 'global', 'use_cron', ITSEC_USE_CRON );
|
170 |
}
|
171 |
|
172 |
do_action( 'itsec_initialized' );
|
|
|
|
|
173 |
}
|
174 |
|
175 |
private function setup_scheduler() {
|
@@ -258,6 +262,15 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
258 |
return $self->scheduler;
|
259 |
}
|
260 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
/**
|
262 |
* Retrieve the global instance of the Sync API.
|
263 |
*
|
24 |
*
|
25 |
* @access private
|
26 |
*/
|
27 |
+
private $plugin_build = 4112;
|
28 |
|
29 |
/**
|
30 |
* Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
|
50 |
$current_time_gmt,
|
51 |
$is_iwp_call,
|
52 |
$request_type,
|
53 |
+
$wp_upload_dir,
|
54 |
+
$login_interstitial;
|
55 |
|
56 |
|
57 |
/**
|
122 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-password-requirements.php' );
|
123 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-login-interstitial.php' );
|
124 |
require( $this->plugin_dir . 'core/lib/class-itsec-lib-distributed-storage.php' );
|
125 |
+
require( $this->plugin_dir . 'core/lib/class-itsec-lib-remote-messages.php' );
|
126 |
|
127 |
require( $this->plugin_dir . 'core/lib/class-itsec-scheduler.php' );
|
128 |
require( $this->plugin_dir . 'core/lib/class-itsec-job.php' );
|
164 |
add_action( 'admin_bar_menu', array( $this, 'modify_admin_bar' ), 99 );
|
165 |
}
|
166 |
|
167 |
+
$this->login_interstitial = new ITSEC_Lib_Login_Interstitial();
|
168 |
+
$this->login_interstitial->run();
|
169 |
|
170 |
if ( defined( 'ITSEC_USE_CRON' ) && ITSEC_USE_CRON !== ITSEC_Lib::use_cron() ) {
|
171 |
ITSEC_Modules::set_setting( 'global', 'use_cron', ITSEC_USE_CRON );
|
172 |
}
|
173 |
|
174 |
do_action( 'itsec_initialized' );
|
175 |
+
|
176 |
+
ITSEC_Lib_Remote_Messages::init();
|
177 |
}
|
178 |
|
179 |
private function setup_scheduler() {
|
262 |
return $self->scheduler;
|
263 |
}
|
264 |
|
265 |
+
/**
|
266 |
+
* Get the login interstitial library instance.
|
267 |
+
*
|
268 |
+
* @return ITSEC_Lib_Login_Interstitial
|
269 |
+
*/
|
270 |
+
public static function get_login_interstitial() {
|
271 |
+
return self::get_instance()->login_interstitial;
|
272 |
+
}
|
273 |
+
|
274 |
/**
|
275 |
* Retrieve the global instance of the Sync API.
|
276 |
*
|
core/history.txt
CHANGED
@@ -753,4 +753,36 @@
|
|
753 |
Bug Fix: Improve detection of blocking the File Change Scan from being scheduled if one is already being run.
|
754 |
Bug Fix: Prevent infinite recursion error when trying to access directories outside of the allowed file tree.
|
755 |
4.8.1 - 2018-10-10 - Chris Jean & Timothy Jacobs
|
756 |
-
Enhancement: Allow for selecting the particular Proxy header a server is configured to use. Improve the language to indicate the importance of configuring this setting. H/t Filippo Cavallarin CEO at wearesegment.com
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
753 |
Bug Fix: Improve detection of blocking the File Change Scan from being scheduled if one is already being run.
|
754 |
Bug Fix: Prevent infinite recursion error when trying to access directories outside of the allowed file tree.
|
755 |
4.8.1 - 2018-10-10 - Chris Jean & Timothy Jacobs
|
756 |
+
Enhancement: Allow for selecting the particular Proxy header a server is configured to use. Improve the language to indicate the importance of configuring this setting. H/t Filippo Cavallarin CEO at wearesegment.com
|
757 |
+
4.8.2 - 2018-10-10 - Chris Jean & Timothy Jacobs
|
758 |
+
Bug Fix: Fix issue with saving Global settings if Security Check Pro has detected the correct Proxy Header to use.
|
759 |
+
4.8.3 - 2018-11-01 - Chris Jean & Timothy Jacobs
|
760 |
+
Enhancement: Add support for displaying status messages about services that might be encountering issues without updating the plugin.
|
761 |
+
4.8.4 - 2018-12-04 - Chris Jean & Timothy Jacobs
|
762 |
+
Enhancement: Add Per-Content SSL toggle to the upcoming Block Editor interface.
|
763 |
+
Enhancement: Add filter to the recipients list for email notifications: "itsec_notification_{$notification}_email_recipients" and "itsec_notification_email_recipients".
|
764 |
+
Enhancement: Detect Server IPs in Security Check.
|
765 |
+
Tweak: Improve File Change locking to help prevent failing scans on sites with inconsistent cron scheduling.
|
766 |
+
Tweak: Improve "System Tweaks – Suspicious Query Strings – SQLI" to reduce false positives.
|
767 |
+
Tweak: Improve "System Tweaks – Disable PHP" to block PHP files in apache configurations that serve files with a trailing dot.
|
768 |
+
Tweak: Add additional safety checks when writing to system config files.
|
769 |
+
Tweak: Remove "Seznam Bot" from HackRepair List as it isn't present in the latest version.
|
770 |
+
Bug Fix: Notification Center - Only send notifications to users with an exact role match of selected roles instead of a fuzzy match based on selected capabilities.
|
771 |
+
Bug Fix: Resolve warnings on PHP 5.2.
|
772 |
+
4.8.5 - 2018-12-04 - Chris Jean & Timothy Jacobs
|
773 |
+
Bug Fix: Don't try to get users with the selected role if no roles are selected.
|
774 |
+
4.8.6 - 2018-12-11 - Chris Jean & Timothy Jacobs
|
775 |
+
Bug Fix: Only re-add Trusted Devices restricted capabilities filter if it was registered in the first place.
|
776 |
+
Bug Fix: Error when trying to edit reusable blocks with per-post SSL enabled.
|
777 |
+
4.9.0 - 2019-01-10 - Chris Jean & Timothy Jacobs
|
778 |
+
Enhancement: Add loopback IP detection to Security Check.
|
779 |
+
Enhancement: Add define "ITSEC_DISABLE_TEMP_WHITELIST" to disable the Temporary IP Whitelisting for logged-in administrators.
|
780 |
+
Tweak: Only run Remote Messages API on Pro versions.
|
781 |
+
4.9.1 - 2019-01-14 - Chris Jean & Timothy Jacobs
|
782 |
+
Bug Fix: Styling issue that made "Identified Loopback IP" look like an error message instead of a success.
|
783 |
+
5.0.0 - 2019-01-16 - Chris Jean & Timothy Jacobs
|
784 |
+
Enhancement: Add support for number inputs and validators.
|
785 |
+
5.1.0 - 2019-02-13 - Chris Jean & Timothy Jacobs
|
786 |
+
Enhancement: Improve redirecting after processing a login interstitial from a front-end login form.
|
787 |
+
Tweak: Add display description for log when safe guarding against an empty config file write.
|
788 |
+
Bug Fix: Include Hide Backend token when emailing a password reset URL.
|
core/lib.php
CHANGED
@@ -1294,15 +1294,15 @@ final class ITSEC_Lib {
|
|
1294 |
$suhosin = preg_split( '/\s*,\s*/', (string) ini_get( 'suhosin.executor.func.blacklist' ) );
|
1295 |
}
|
1296 |
|
1297 |
-
if ( !
|
1298 |
return $cache[ $func ] = false;
|
1299 |
}
|
1300 |
|
1301 |
-
if (
|
1302 |
return $cache[ $func ] = false;
|
1303 |
}
|
1304 |
|
1305 |
-
if (
|
1306 |
return $cache[ $func ] = false;
|
1307 |
}
|
1308 |
|
@@ -1575,4 +1575,114 @@ final class ITSEC_Lib {
|
|
1575 |
public static function clear_cookie( $name ) {
|
1576 |
setcookie( $name, ' ', ITSEC_Core::get_current_time_gmt() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, false );
|
1577 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1578 |
}
|
1294 |
$suhosin = preg_split( '/\s*,\s*/', (string) ini_get( 'suhosin.executor.func.blacklist' ) );
|
1295 |
}
|
1296 |
|
1297 |
+
if ( ! is_callable( $func ) ) {
|
1298 |
return $cache[ $func ] = false;
|
1299 |
}
|
1300 |
|
1301 |
+
if ( in_array( $func, $disabled, true ) ) {
|
1302 |
return $cache[ $func ] = false;
|
1303 |
}
|
1304 |
|
1305 |
+
if ( in_array( $func, $suhosin, true ) ) {
|
1306 |
return $cache[ $func ] = false;
|
1307 |
}
|
1308 |
|
1575 |
public static function clear_cookie( $name ) {
|
1576 |
setcookie( $name, ' ', ITSEC_Core::get_current_time_gmt() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, false );
|
1577 |
}
|
1578 |
+
|
1579 |
+
/**
|
1580 |
+
* Is the current request a loopback request.
|
1581 |
+
*
|
1582 |
+
* @return bool
|
1583 |
+
*/
|
1584 |
+
public static function is_loopback_request() {
|
1585 |
+
return in_array( self::get_ip(), ITSEC_Modules::get_setting( 'global', 'server_ips' ), true );
|
1586 |
+
}
|
1587 |
+
|
1588 |
+
/**
|
1589 |
+
* Version of {@see wp_slash()} that won't cast numbers to strings.
|
1590 |
+
*
|
1591 |
+
* @param array|string $value
|
1592 |
+
*
|
1593 |
+
* @return array|string
|
1594 |
+
*/
|
1595 |
+
public static function slash( $value ) {
|
1596 |
+
if ( is_array( $value ) ) {
|
1597 |
+
foreach ( $value as $k => $v ) {
|
1598 |
+
if ( is_array( $v ) ) {
|
1599 |
+
$value[ $k ] = self::slash( $v );
|
1600 |
+
} elseif ( is_string( $v ) ) {
|
1601 |
+
$value[ $k ] = addslashes( $v );
|
1602 |
+
}
|
1603 |
+
}
|
1604 |
+
} elseif ( is_string( $value ) ) {
|
1605 |
+
$value = addslashes( $value );
|
1606 |
+
}
|
1607 |
+
|
1608 |
+
return $value;
|
1609 |
+
}
|
1610 |
+
|
1611 |
+
/**
|
1612 |
+
* Format as a ISO 8601 date.
|
1613 |
+
*
|
1614 |
+
* @param int|string $date Epoch or strtotime compatible date.
|
1615 |
+
*
|
1616 |
+
* @return string|false
|
1617 |
+
*/
|
1618 |
+
public static function to_rest_date( $date = 0 ) {
|
1619 |
+
if ( ! $date ) {
|
1620 |
+
$date = ITSEC_Core::get_current_time_gmt();
|
1621 |
+
} elseif ( ! is_int( $date ) ) {
|
1622 |
+
$date = strtotime( $date );
|
1623 |
+
}
|
1624 |
+
|
1625 |
+
return gmdate( 'Y-m-d\TH:i:sP', $date );
|
1626 |
+
}
|
1627 |
+
|
1628 |
+
/**
|
1629 |
+
* Flatten an array.
|
1630 |
+
*
|
1631 |
+
* @param array $array
|
1632 |
+
*
|
1633 |
+
* @return array
|
1634 |
+
*/
|
1635 |
+
public static function flatten( $array ) {
|
1636 |
+
if ( ! is_array( $array ) ) {
|
1637 |
+
return array( $array );
|
1638 |
+
}
|
1639 |
+
|
1640 |
+
$merge = array();
|
1641 |
+
|
1642 |
+
foreach ( $array as $value ) {
|
1643 |
+
$merge[] = self::flatten( $value );
|
1644 |
+
}
|
1645 |
+
|
1646 |
+
return $merge ? call_user_func_array( 'array_merge', $merge ) : array();
|
1647 |
+
}
|
1648 |
+
|
1649 |
+
/**
|
1650 |
+
* Preload REST API requests.
|
1651 |
+
*
|
1652 |
+
* @param array $requests
|
1653 |
+
*
|
1654 |
+
* @return array
|
1655 |
+
*/
|
1656 |
+
public static function preload_rest_requests( $requests ) {
|
1657 |
+
$preload = array();
|
1658 |
+
|
1659 |
+
foreach ( $requests as $key => $config ) {
|
1660 |
+
if ( is_string( $config ) ) {
|
1661 |
+
$key = $config;
|
1662 |
+
$config = array( 'route' => $config );
|
1663 |
+
}
|
1664 |
+
|
1665 |
+
$request = new WP_REST_Request(
|
1666 |
+
isset( $config['method'] ) ? $config['method'] : 'GET',
|
1667 |
+
$config['route']
|
1668 |
+
);
|
1669 |
+
|
1670 |
+
if ( ! empty( $config['query'] ) ) {
|
1671 |
+
$request->set_query_params( $config['query'] );
|
1672 |
+
}
|
1673 |
+
|
1674 |
+
$response = rest_do_request( $request );
|
1675 |
+
|
1676 |
+
if ( $response->get_status() >= 200 && $response->get_status() < 300 ) {
|
1677 |
+
rest_send_allow_header( $response, rest_get_server(), $request );
|
1678 |
+
|
1679 |
+
$preload[ $key ] = array(
|
1680 |
+
'body' => rest_get_server()->response_to_data( $response, ! empty( $config['embed'] ) ),
|
1681 |
+
'headers' => $response->get_headers()
|
1682 |
+
);
|
1683 |
+
}
|
1684 |
+
}
|
1685 |
+
|
1686 |
+
return $preload;
|
1687 |
+
}
|
1688 |
}
|
core/lib/class-itsec-lib-config-file.php
CHANGED
@@ -465,6 +465,20 @@ class ITSEC_Lib_Config_File {
|
|
465 |
return $contents;
|
466 |
}
|
467 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
468 |
|
469 |
$modification = ltrim( $modification, "\x0B\r\n\0" );
|
470 |
$modification = rtrim( $modification, " \t\x0B\r\n\0" );
|
465 |
return $contents;
|
466 |
}
|
467 |
|
468 |
+
if ( ! $contents && in_array( $type, array( 'apache', 'wp-config' ), true ) ) {
|
469 |
+
$display_file = str_replace( '\\', '/', $file );
|
470 |
+
$abspath = str_replace( '\\', '/', ABSPATH );
|
471 |
+
$display_file = preg_replace( '/^' . preg_quote( $abspath, '/' ) . '/', '', $display_file );
|
472 |
+
$display_file = ltrim( $display_file, '/' );
|
473 |
+
|
474 |
+
$error = new WP_Error( "itsec-config-file-update-empty::{$type}", sprintf(
|
475 |
+
__( 'Empty file encountered when attempting to update <code>%1$s</code>. Manual configuration for the <code>%1$s</code> file can be found on the Security > Settings page in the Advanced section.', 'better-wp-security' ),
|
476 |
+
$display_file
|
477 |
+
) );
|
478 |
+
ITSEC_Log::add_critical_issue( 'core', $error->get_error_code(), $error );
|
479 |
+
|
480 |
+
return $error;
|
481 |
+
}
|
482 |
|
483 |
$modification = ltrim( $modification, "\x0B\r\n\0" );
|
484 |
$modification = rtrim( $modification, " \t\x0B\r\n\0" );
|
core/lib/class-itsec-lib-fingerprinting.php
CHANGED
@@ -162,7 +162,14 @@ class ITSEC_Lib_Fingerprinting {
|
|
162 |
|
163 |
require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-lib-canonical-roles.php' );
|
164 |
|
165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
}
|
167 |
|
168 |
/**
|
@@ -190,4 +197,4 @@ class ITSEC_Lib_Fingerprinting {
|
|
190 |
|
191 |
return self::$sources;
|
192 |
}
|
193 |
-
}
|
162 |
|
163 |
require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-lib-canonical-roles.php' );
|
164 |
|
165 |
+
$had_filter = remove_filter( 'user_has_cap', array( 'ITSEC_Fingerprinting', 'restrict_capabilities' ), 10 );
|
166 |
+
$applies = ITSEC_Lib_Canonical_Roles::is_user_at_least( $role, $user );
|
167 |
+
|
168 |
+
if ( $had_filter ) {
|
169 |
+
add_filter( 'user_has_cap', array( 'ITSEC_Fingerprinting', 'restrict_capabilities' ), 10, 4 );
|
170 |
+
}
|
171 |
+
|
172 |
+
return $applies;
|
173 |
}
|
174 |
|
175 |
/**
|
197 |
|
198 |
return self::$sources;
|
199 |
}
|
200 |
+
}
|
core/lib/class-itsec-lib-login-interstitial.php
CHANGED
@@ -1,14 +1,29 @@
|
|
1 |
<?php
|
2 |
|
|
|
|
|
|
|
|
|
3 |
/**
|
4 |
* Class ITSEC_Lib_Login_Interstitial
|
5 |
*/
|
6 |
class ITSEC_Lib_Login_Interstitial {
|
7 |
|
8 |
const SHOW_AFTER_LOGIN = 'itsec_after_interstitial';
|
9 |
-
const META_KEY = '_itsec_login_interstitial_token';
|
10 |
const AJAX = 'itsec-login-interstitial-ajax';
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
private $registered = array();
|
13 |
|
14 |
/** @var WP_Error */
|
@@ -17,6 +32,9 @@ class ITSEC_Lib_Login_Interstitial {
|
|
17 |
/** @var string */
|
18 |
private $session_token;
|
19 |
|
|
|
|
|
|
|
20 |
/**
|
21 |
* Initialize the module.
|
22 |
*
|
@@ -35,29 +53,25 @@ class ITSEC_Lib_Login_Interstitial {
|
|
35 |
return;
|
36 |
}
|
37 |
|
38 |
-
$this->registered
|
39 |
|
40 |
-
add_action( '
|
|
|
41 |
add_action( 'wp_login_errors', array( $this, 'handle_token_expired' ) );
|
42 |
add_action( 'login_init', array( $this, 'force_interstitial' ) );
|
43 |
add_action( 'login_form', array( $this, 'ferry_after_login' ) );
|
44 |
add_filter( 'auth_cookie', array( $this, 'capture_session_token' ), 10, 5 );
|
45 |
|
46 |
-
$
|
|
|
47 |
|
48 |
-
foreach ( $this->registered as $id => $
|
49 |
-
if (
|
50 |
-
add_action( "login_form_itsec-{$id}", array( $this, '
|
51 |
}
|
52 |
|
|
|
53 |
add_action( "login_form_itsec-{$id}", array( $this, 'display' ) );
|
54 |
-
|
55 |
-
if ( ! $added_ajax && ! empty( $opts['ajax_handler'] ) ) {
|
56 |
-
add_action( 'wp_ajax_' . self::AJAX, array( $this, 'ajax_handler' ) );
|
57 |
-
add_action( 'wp_ajax_nopriv_' . self::AJAX, array( $this, 'ajax_handler' ) );
|
58 |
-
|
59 |
-
$added_ajax = true;
|
60 |
-
}
|
61 |
}
|
62 |
}
|
63 |
|
@@ -66,36 +80,43 @@ class ITSEC_Lib_Login_Interstitial {
|
|
66 |
*
|
67 |
* @api
|
68 |
*
|
69 |
-
* @param string
|
70 |
-
* @param callable $
|
71 |
-
* @param array
|
72 |
*
|
73 |
* @return bool
|
74 |
*/
|
75 |
-
public function register( $
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
$opts = wp_parse_args( $opts, array(
|
77 |
'force_completion' => true, // Will logout the user's session before displaying the interstitial.
|
78 |
'show_to_user' => true, // Boolean or callable.
|
79 |
'wp_login_only' => false, // Only show the interstitial if the login form is submitted from wp-login.php,
|
80 |
'submit' => false, // Callable called with user when submitting the form.
|
|
|
81 |
'info_message' => false,
|
82 |
'after_submit' => false,
|
83 |
'ajax_handler' => false,
|
84 |
'priority' => 5,
|
85 |
) );
|
86 |
|
87 |
-
$opts['render'] = $
|
88 |
|
89 |
-
|
90 |
-
|
91 |
-
}
|
|
|
|
|
92 |
|
93 |
-
if ( ! is_bool( $opts['force_completion'] ) && ! is_callable( $opts['force_completion'] ) ) {
|
94 |
return false;
|
95 |
}
|
96 |
|
97 |
-
$this->registered[ $action ] = $opts;
|
98 |
-
|
99 |
return true;
|
100 |
}
|
101 |
|
@@ -104,33 +125,107 @@ class ITSEC_Lib_Login_Interstitial {
|
|
104 |
*
|
105 |
* @api
|
106 |
*
|
107 |
-
* @param
|
108 |
-
* @param WP_User $user
|
109 |
*
|
110 |
* @return void
|
111 |
*/
|
112 |
-
public function show_interstitial(
|
113 |
|
114 |
-
if ( ! isset( $this->registered[ $
|
115 |
return;
|
116 |
}
|
117 |
|
118 |
-
$
|
119 |
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
$token = null;
|
125 |
}
|
126 |
|
127 |
-
$this->login_html( $
|
128 |
die;
|
129 |
}
|
130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
/**
|
132 |
* During the login process, check if we have any interstitials to display, and display them.
|
133 |
*
|
|
|
|
|
134 |
* @param string $username
|
135 |
* @param WP_User $user
|
136 |
*/
|
@@ -141,30 +236,65 @@ class ITSEC_Lib_Login_Interstitial {
|
|
141 |
return;
|
142 |
}
|
143 |
|
144 |
-
|
|
|
145 |
|
146 |
-
$
|
147 |
|
148 |
-
if ( isset( $
|
149 |
-
$
|
150 |
}
|
|
|
|
|
|
|
|
|
151 |
}
|
152 |
|
153 |
-
|
154 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
155 |
}
|
156 |
}
|
157 |
|
158 |
/**
|
159 |
* Add a message that the interstitial expired.
|
160 |
*
|
|
|
|
|
161 |
* @param WP_Error $errors
|
162 |
*
|
163 |
* @return WP_Error
|
164 |
*/
|
165 |
public function handle_token_expired( $errors ) {
|
166 |
|
167 |
-
if (
|
168 |
$errors->add(
|
169 |
'itsec-login-interstitial-invalid-token',
|
170 |
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
@@ -176,6 +306,8 @@ class ITSEC_Lib_Login_Interstitial {
|
|
176 |
|
177 |
/**
|
178 |
* Force the requested interstitial to be displayed if the user is already logged-in.
|
|
|
|
|
179 |
*/
|
180 |
public function force_interstitial() {
|
181 |
|
@@ -183,9 +315,9 @@ class ITSEC_Lib_Login_Interstitial {
|
|
183 |
return;
|
184 |
}
|
185 |
|
186 |
-
$
|
187 |
|
188 |
-
if ( 'POST' === $_SERVER['REQUEST_METHOD']
|
189 |
return;
|
190 |
}
|
191 |
|
@@ -193,18 +325,22 @@ class ITSEC_Lib_Login_Interstitial {
|
|
193 |
return;
|
194 |
}
|
195 |
|
196 |
-
$user
|
|
|
197 |
|
198 |
-
if ( ! $
|
199 |
wp_safe_redirect( admin_url() );
|
200 |
die;
|
201 |
}
|
202 |
|
203 |
-
$
|
|
|
204 |
}
|
205 |
|
206 |
/**
|
207 |
* Ferry the after login interstitial query var into the form.
|
|
|
|
|
208 |
*/
|
209 |
public function ferry_after_login() {
|
210 |
if ( ! empty( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) && isset( $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
@@ -215,6 +351,8 @@ class ITSEC_Lib_Login_Interstitial {
|
|
215 |
/**
|
216 |
* Capture the session token to log out the user.
|
217 |
*
|
|
|
|
|
218 |
* @param string $cookie
|
219 |
* @param int $user_id
|
220 |
* @param int $expiration
|
@@ -231,112 +369,188 @@ class ITSEC_Lib_Login_Interstitial {
|
|
231 |
|
232 |
/**
|
233 |
* Handle submitting the interstitial form.
|
|
|
|
|
234 |
*/
|
235 |
public function submit() {
|
236 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
237 |
|
238 |
-
if (
|
239 |
return;
|
240 |
}
|
241 |
|
242 |
-
if (
|
243 |
return;
|
244 |
}
|
245 |
|
246 |
-
$
|
247 |
|
248 |
-
if (
|
249 |
-
|
250 |
-
|
|
|
|
|
|
|
|
|
|
|
251 |
}
|
252 |
|
253 |
-
$
|
|
|
|
|
|
|
|
|
|
|
254 |
|
255 |
-
|
256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
return;
|
259 |
}
|
260 |
|
261 |
-
|
262 |
-
|
|
|
|
|
263 |
}
|
264 |
|
265 |
-
|
266 |
-
|
267 |
-
|
|
|
|
|
268 |
}
|
269 |
|
270 |
-
|
271 |
-
|
|
|
|
|
272 |
}
|
273 |
|
274 |
-
|
275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
}
|
277 |
|
278 |
-
|
279 |
-
|
280 |
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
* @param WP_User $user
|
285 |
-
*/
|
286 |
-
do_action( 'itsec_login_interstitial_logged_in', $user );
|
287 |
|
288 |
-
if (
|
289 |
-
$this->
|
290 |
}
|
291 |
|
292 |
-
if (
|
293 |
-
$
|
294 |
-
$requested = '';
|
295 |
-
} else {
|
296 |
-
$redirect_to = $requested = $_REQUEST['redirect_to'];
|
297 |
}
|
298 |
|
299 |
-
|
300 |
-
|
|
|
|
|
|
|
|
|
301 |
|
302 |
-
|
|
|
|
|
|
|
|
|
|
|
303 |
}
|
304 |
|
305 |
/**
|
306 |
* Ajax Handler.
|
|
|
|
|
307 |
*/
|
308 |
public function ajax_handler() {
|
309 |
|
310 |
-
|
311 |
-
|
312 |
-
}
|
313 |
|
314 |
-
$
|
|
|
|
|
|
|
|
|
|
|
315 |
|
316 |
-
|
317 |
-
wp_send_json_error( array( 'message' => esc_html__( 'Invalid Interstitial Action', 'better-wp-security' ) ) );
|
318 |
}
|
319 |
|
320 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
321 |
|
322 |
-
$
|
323 |
|
324 |
-
if (
|
325 |
-
wp_send_json_error( array( 'message' =>
|
326 |
}
|
327 |
|
328 |
-
|
329 |
-
|
|
|
|
|
330 |
}
|
331 |
|
332 |
$data = $_POST;
|
333 |
-
unset( $data[
|
334 |
|
335 |
-
|
|
|
336 |
}
|
337 |
|
338 |
/**
|
339 |
* Handle displaying the interstitial form.
|
|
|
|
|
340 |
*/
|
341 |
public function display() {
|
342 |
|
@@ -346,49 +560,33 @@ class ITSEC_Lib_Login_Interstitial {
|
|
346 |
return;
|
347 |
}
|
348 |
|
349 |
-
$
|
350 |
-
|
351 |
-
$user = null;
|
352 |
-
$token = isset( $_REQUEST['itsec_interstitial_token'] ) ? $_REQUEST['itsec_interstitial_token'] : null;
|
353 |
-
|
354 |
-
$user = $this->get_user( $action );
|
355 |
-
|
356 |
-
if ( ! $user ) {
|
357 |
-
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
358 |
-
die;
|
359 |
-
}
|
360 |
|
361 |
-
if ( ! $
|
362 |
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
363 |
die;
|
364 |
}
|
365 |
|
366 |
-
$this->login_html( $
|
367 |
die;
|
368 |
}
|
369 |
|
370 |
/**
|
371 |
* Display an interstitial form during the login process.
|
372 |
*
|
373 |
-
* @
|
374 |
-
*
|
375 |
-
* @param
|
376 |
*/
|
377 |
-
protected function login_html(
|
378 |
-
|
379 |
-
$wp_login_url = set_url_scheme( wp_login_url(), 'login_post' );
|
380 |
-
$wp_login_url = add_query_arg( 'action', "itsec-{$action}", $wp_login_url );
|
381 |
-
|
382 |
-
if ( isset( $_GET['wpe-login'] ) && ! preg_match( '/[&?]wpe-login=/', $wp_login_url ) ) {
|
383 |
-
$wp_login_url = add_query_arg( 'wpe-login', $_GET['wpe-login'], $wp_login_url );
|
384 |
-
}
|
385 |
-
|
386 |
-
$interim_login = isset( $_REQUEST['interim-login'] );
|
387 |
-
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
|
388 |
|
389 |
-
$
|
|
|
|
|
390 |
|
391 |
-
|
|
|
392 |
|
393 |
// Prevent JetPack from attempting to SSO the update password form.
|
394 |
add_filter( 'jetpack_sso_allowed_actions', '__return_empty_array' );
|
@@ -397,41 +595,29 @@ class ITSEC_Lib_Login_Interstitial {
|
|
397 |
require_once( dirname( __FILE__ ) . '/includes/function.login-header.php' );
|
398 |
}
|
399 |
|
400 |
-
$opts = $this->registered[ $action ];
|
401 |
-
|
402 |
login_header();
|
|
|
|
|
403 |
?>
|
404 |
|
405 |
<?php if ( $this->error ) : ?>
|
406 |
<div id="login-error" class="message" style="border-left-color: #dc3232;">
|
407 |
<?php echo $this->error->get_error_message(); ?>
|
408 |
</div>
|
409 |
-
<?php elseif ( $message = $
|
410 |
<p class="message"><?php echo $message; ?></p>
|
411 |
<?php endif; ?>
|
412 |
|
413 |
<form name="itsec-<?php echo esc_attr( $action ); ?>" id="itsec-<?php echo esc_attr( $action ); ?>"
|
414 |
action="<?php echo esc_url( $wp_login_url ); ?>" method="post" autocomplete="off">
|
415 |
|
416 |
-
<?php
|
417 |
-
|
418 |
-
<?php if ( $interim_login ) : ?>
|
419 |
-
<input type="hidden" name="interim-login" value="1"/>
|
420 |
-
<?php else : ?>
|
421 |
-
<input type="hidden" name="redirect_to" value="<?php echo esc_url( $redirect_to ); ?>"/>
|
422 |
-
<?php endif; ?>
|
423 |
|
424 |
-
<input type="hidden" name="rememberme" id="rememberme" value="<?php echo esc_attr( $rememberme ); ?>"/>
|
425 |
<input type="hidden" name="action" value="<?php echo esc_attr( "itsec-{$action}" ); ?>">
|
426 |
|
427 |
-
<?php
|
428 |
-
|
429 |
-
|
430 |
-
<?php endif; ?>
|
431 |
-
|
432 |
-
<?php if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) : ?>
|
433 |
-
<input type="hidden" name="<?php echo esc_attr( self::SHOW_AFTER_LOGIN ); ?>" value="<?php echo esc_attr( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ); ?>">
|
434 |
-
<?php endif; ?>
|
435 |
</form>
|
436 |
|
437 |
<p id="backtoblog">
|
@@ -473,11 +659,11 @@ class ITSEC_Lib_Login_Interstitial {
|
|
473 |
|
474 |
<?php if ( $customize_login ) : ?>
|
475 |
<script type="text/javascript">
|
476 |
-
setTimeout( function
|
477 |
new wp.customize.Messenger( {
|
478 |
url : '<?php echo wp_customize_url(); ?>',
|
479 |
-
channel: 'login'
|
480 |
-
} ).send( 'login' )
|
481 |
}, 1000 );
|
482 |
</script>
|
483 |
<?php endif; ?>
|
@@ -486,26 +672,164 @@ class ITSEC_Lib_Login_Interstitial {
|
|
486 |
<?php die;
|
487 |
}
|
488 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
489 |
/**
|
490 |
* Get the next interstitial to be displayed.
|
491 |
*
|
492 |
-
* @param
|
493 |
-
* @param WP_User $user
|
494 |
*
|
495 |
* @return string|false
|
496 |
*/
|
497 |
-
private function get_next_interstitial(
|
498 |
|
499 |
-
$
|
|
|
|
|
500 |
|
501 |
-
foreach ( $this->get_applicable_interstitials( $user ) as $handler => $opts ) {
|
502 |
-
if ( $handler === $current ) {
|
503 |
-
$past_current = true;
|
504 |
-
continue;
|
505 |
}
|
|
|
506 |
|
507 |
-
|
508 |
-
|
|
|
509 |
}
|
510 |
}
|
511 |
|
@@ -523,9 +847,9 @@ class ITSEC_Lib_Login_Interstitial {
|
|
523 |
|
524 |
$applicable = array();
|
525 |
|
526 |
-
foreach ( $this->registered as $action => $
|
527 |
if ( $this->is_interstitial_applicable( $action, $user ) ) {
|
528 |
-
$applicable[ $action ] = $
|
529 |
}
|
530 |
}
|
531 |
|
@@ -535,21 +859,20 @@ class ITSEC_Lib_Login_Interstitial {
|
|
535 |
/**
|
536 |
* Is the interstitial applicable to the given user.
|
537 |
*
|
538 |
-
* @param string $
|
539 |
-
*
|
540 |
* @param WP_User $user
|
541 |
*
|
542 |
* @return bool
|
543 |
*/
|
544 |
-
private function is_interstitial_applicable( $
|
545 |
|
546 |
-
$
|
547 |
|
548 |
-
if ( ! $
|
549 |
return false;
|
550 |
}
|
551 |
|
552 |
-
if ( ! did_action( 'login_init' ) && $
|
553 |
return false;
|
554 |
}
|
555 |
|
@@ -557,83 +880,86 @@ class ITSEC_Lib_Login_Interstitial {
|
|
557 |
}
|
558 |
|
559 |
/**
|
560 |
-
*
|
561 |
*
|
562 |
-
* @param
|
563 |
-
* @param bool $return_error
|
564 |
*
|
565 |
-
* @return WP_Error
|
566 |
*/
|
567 |
-
private function
|
568 |
|
569 |
-
$
|
570 |
-
|
|
|
|
|
571 |
|
572 |
-
if (
|
573 |
-
$
|
574 |
-
} elseif ( ( ! $user = get_userdata( $_REQUEST['itsec_interstitial_user'] ) ) || ! $this->verify_token( $user, $_REQUEST['itsec_interstitial_token'] ) ) {
|
575 |
-
$is_valid = false;
|
576 |
}
|
577 |
|
578 |
-
|
579 |
-
if ( $return_error ) {
|
580 |
-
return new WP_Error(
|
581 |
-
'itsec-login-interstitial-invalid-token',
|
582 |
-
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
583 |
-
);
|
584 |
-
}
|
585 |
|
586 |
-
|
|
|
587 |
}
|
588 |
|
589 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
590 |
}
|
591 |
|
592 |
/**
|
593 |
-
* Get the
|
594 |
*
|
595 |
-
* @param
|
596 |
-
* @param bool $return_error
|
597 |
*
|
598 |
-
* @return WP_Error
|
599 |
*/
|
600 |
-
private function
|
601 |
|
602 |
-
$
|
|
|
|
|
|
|
603 |
|
604 |
-
if (
|
605 |
-
return
|
606 |
}
|
607 |
|
608 |
-
|
609 |
-
$maybe = $this->handle_token( $action, $return_error );
|
610 |
|
611 |
-
|
|
|
612 |
}
|
613 |
|
614 |
-
$
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
|
620 |
-
if ( $
|
621 |
-
return
|
622 |
-
'itsec-login-interstitial-invalid-token',
|
623 |
-
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
624 |
-
);
|
625 |
}
|
626 |
|
627 |
-
|
628 |
}
|
629 |
|
630 |
/**
|
631 |
* Redirect back to the login page with a message that the token is invalid.
|
632 |
-
*
|
633 |
-
* @param string $action
|
634 |
*/
|
635 |
-
private function redirect_invalid_token(
|
636 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
637 |
wp_safe_redirect( set_url_scheme( $redirect, 'login_post' ) );
|
638 |
die;
|
639 |
}
|
@@ -643,90 +969,20 @@ class ITSEC_Lib_Login_Interstitial {
|
|
643 |
*
|
644 |
* @param WP_User $user
|
645 |
*/
|
646 |
-
private function
|
647 |
WP_Session_Tokens::get_instance( $user->ID )->destroy( $this->session_token ? $this->session_token : wp_get_session_token() );
|
648 |
wp_clear_auth_cookie();
|
649 |
}
|
650 |
|
651 |
/**
|
652 |
-
*
|
653 |
-
*
|
654 |
-
* @param WP_User $user
|
655 |
-
* @param string $key
|
656 |
-
*
|
657 |
-
* @return bool
|
658 |
-
*/
|
659 |
-
private function verify_token( $user, $key ) {
|
660 |
-
$expected = get_user_meta( $user->ID, self::META_KEY, true );
|
661 |
-
|
662 |
-
if ( ! $expected || ! is_array( $expected ) ) {
|
663 |
-
return false;
|
664 |
-
}
|
665 |
-
|
666 |
-
if ( empty( $expected['expires'] ) || $expected['expires'] < ITSEC_Core::get_current_time_gmt() ) {
|
667 |
-
return false;
|
668 |
-
}
|
669 |
-
|
670 |
-
return hash_equals( $expected['key'], $key );
|
671 |
-
}
|
672 |
-
|
673 |
-
/**
|
674 |
-
* Set the token for a user.
|
675 |
-
*
|
676 |
-
* @param WP_User $user
|
677 |
-
*
|
678 |
-
* @return string
|
679 |
-
*/
|
680 |
-
private function set_token( $user ) {
|
681 |
-
$key = $this->generate_token();
|
682 |
-
|
683 |
-
update_user_meta( $user->ID, self::META_KEY, array(
|
684 |
-
'key' => $key,
|
685 |
-
'expires' => ITSEC_Core::get_current_time_gmt() + HOUR_IN_SECONDS
|
686 |
-
) );
|
687 |
-
|
688 |
-
return $key;
|
689 |
-
}
|
690 |
-
|
691 |
-
/**
|
692 |
-
* Generate a token to be used to verify intent of submitting a login interstitial.
|
693 |
*
|
694 |
-
*
|
|
|
695 |
*
|
696 |
-
* @return
|
697 |
-
*/
|
698 |
-
private function generate_token() {
|
699 |
-
return sha1( wp_generate_password( 32, true, true ) );
|
700 |
-
}
|
701 |
-
|
702 |
-
/**
|
703 |
-
* Delete the token for a user.
|
704 |
-
*
|
705 |
-
* @param WP_User $user
|
706 |
-
*/
|
707 |
-
private function delete_token( $user ) {
|
708 |
-
delete_user_meta( $user->ID, self::META_KEY );
|
709 |
-
}
|
710 |
-
|
711 |
-
/**
|
712 |
-
* Try and get a value from the provider.
|
713 |
-
*
|
714 |
-
* If it is a function, will call the function with the provided args.
|
715 |
-
*
|
716 |
-
* @param bool|callable $provider
|
717 |
-
* @param array $args
|
718 |
-
*
|
719 |
-
* @return bool|mixed
|
720 |
*/
|
721 |
-
private function
|
722 |
-
|
723 |
-
return $provider;
|
724 |
-
}
|
725 |
-
|
726 |
-
if ( is_callable( $provider, true ) ) {
|
727 |
-
return call_user_func_array( $provider, $args );
|
728 |
-
}
|
729 |
-
|
730 |
-
return $provider;
|
731 |
}
|
732 |
-
}
|
1 |
<?php
|
2 |
|
3 |
+
require_once( dirname( __FILE__ ) . '/login-interstitial/class-itsec-login-interstitial-session.php' );
|
4 |
+
require_once( dirname( __FILE__ ) . '/login-interstitial/abstract-itsec-login-interstitial.php' );
|
5 |
+
require_once( dirname( __FILE__ ) . '/login-interstitial/class-itsec-login-interstitial-config-driven.php' );
|
6 |
+
|
7 |
/**
|
8 |
* Class ITSEC_Lib_Login_Interstitial
|
9 |
*/
|
10 |
class ITSEC_Lib_Login_Interstitial {
|
11 |
|
12 |
const SHOW_AFTER_LOGIN = 'itsec_after_interstitial';
|
|
|
13 |
const AJAX = 'itsec-login-interstitial-ajax';
|
14 |
|
15 |
+
const R_USER = 'itsec_interstitial_user';
|
16 |
+
const R_TOKEN = 'itsec_interstitial_token';
|
17 |
+
const R_SESSION = 'itsec_interstitial_session';
|
18 |
+
const R_EXPIRED = 'itsec_interstitial_expired';
|
19 |
+
const R_INTERSTITIAL = 'itsec_interstitial';
|
20 |
+
const R_ASYNC_ACTION = 'itsec_interstitial_async_action';
|
21 |
+
const R_GET_STATE = 'itsec_interstitial_get_state';
|
22 |
+
|
23 |
+
const C_SAME_BROWSER = 'itsec_interstitial_browser';
|
24 |
+
const SAME_BROWSER_PAYLOAD = 'same-browser';
|
25 |
+
|
26 |
+
/** @var ITSEC_Login_Interstitial[] */
|
27 |
private $registered = array();
|
28 |
|
29 |
/** @var WP_Error */
|
32 |
/** @var string */
|
33 |
private $session_token;
|
34 |
|
35 |
+
/** @var ITSEC_Login_Interstitial_Session|null */
|
36 |
+
private $current_session;
|
37 |
+
|
38 |
/**
|
39 |
* Initialize the module.
|
40 |
*
|
53 |
return;
|
54 |
}
|
55 |
|
56 |
+
uasort( $this->registered, array( $this, '_sort_interstitials' ) );
|
57 |
|
58 |
+
add_action( 'login_enqueue_scripts', array( $this, 'enqueue' ) );
|
59 |
+
add_action( 'wp_login', array( $this, 'wp_login' ), - 1000, 2 );
|
60 |
add_action( 'wp_login_errors', array( $this, 'handle_token_expired' ) );
|
61 |
add_action( 'login_init', array( $this, 'force_interstitial' ) );
|
62 |
add_action( 'login_form', array( $this, 'ferry_after_login' ) );
|
63 |
add_filter( 'auth_cookie', array( $this, 'capture_session_token' ), 10, 5 );
|
64 |
|
65 |
+
add_action( 'wp_ajax_' . self::AJAX, array( $this, 'ajax_handler' ) );
|
66 |
+
add_action( 'wp_ajax_nopriv_' . self::AJAX, array( $this, 'ajax_handler' ) );
|
67 |
|
68 |
+
foreach ( $this->registered as $id => $interstitial ) {
|
69 |
+
if ( $interstitial->has_async_action() ) {
|
70 |
+
add_action( "login_form_itsec-{$id}", array( $this, 'async_action' ), 8 );
|
71 |
}
|
72 |
|
73 |
+
add_action( "login_form_itsec-{$id}", array( $this, 'submit' ), 9 );
|
74 |
add_action( "login_form_itsec-{$id}", array( $this, 'display' ) );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
}
|
76 |
}
|
77 |
|
80 |
*
|
81 |
* @api
|
82 |
*
|
83 |
+
* @param string $slug
|
84 |
+
* @param ITSEC_Login_Interstitial|callable $render_or_class
|
85 |
+
* @param array $opts
|
86 |
*
|
87 |
* @return bool
|
88 |
*/
|
89 |
+
public function register( $slug, $render_or_class, $opts = array() ) {
|
90 |
+
|
91 |
+
if ( $render_or_class instanceof ITSEC_Login_Interstitial ) {
|
92 |
+
$this->registered[ $slug ] = $render_or_class;
|
93 |
+
|
94 |
+
return true;
|
95 |
+
}
|
96 |
+
|
97 |
$opts = wp_parse_args( $opts, array(
|
98 |
'force_completion' => true, // Will logout the user's session before displaying the interstitial.
|
99 |
'show_to_user' => true, // Boolean or callable.
|
100 |
'wp_login_only' => false, // Only show the interstitial if the login form is submitted from wp-login.php,
|
101 |
'submit' => false, // Callable called with user when submitting the form.
|
102 |
+
'async_action' => false, // Callable called when a user clicks a link to perform an interstitial action.
|
103 |
'info_message' => false,
|
104 |
'after_submit' => false,
|
105 |
'ajax_handler' => false,
|
106 |
'priority' => 5,
|
107 |
) );
|
108 |
|
109 |
+
$opts['render'] = $render_or_class;
|
110 |
|
111 |
+
try {
|
112 |
+
$this->registered[ $slug ] = new ITSEC_Login_Interstitial_Config_Driven( $opts );
|
113 |
+
} catch ( Exception $e ) {
|
114 |
+
/** @noinspection ForgottenDebugOutputInspection */
|
115 |
+
error_log( $e->getMessage() );
|
116 |
|
|
|
117 |
return false;
|
118 |
}
|
119 |
|
|
|
|
|
120 |
return true;
|
121 |
}
|
122 |
|
125 |
*
|
126 |
* @api
|
127 |
*
|
128 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
|
|
129 |
*
|
130 |
* @return void
|
131 |
*/
|
132 |
+
public function show_interstitial( ITSEC_Login_Interstitial_Session $session ) {
|
133 |
|
134 |
+
if ( ! isset( $this->registered[ $session->get_current_interstitial() ] ) ) {
|
135 |
return;
|
136 |
}
|
137 |
|
138 |
+
$this->current_session = $session;
|
139 |
|
140 |
+
$interstitial = $this->registered[ $session->get_current_interstitial() ];
|
141 |
+
|
142 |
+
if ( $interstitial->is_completion_forced( $session ) ) {
|
143 |
+
$this->destroy_session_token( $session->get_user() );
|
|
|
144 |
}
|
145 |
|
146 |
+
$this->login_html( $session );
|
147 |
die;
|
148 |
}
|
149 |
|
150 |
+
/**
|
151 |
+
* Handle proceeding the session to the next interstitial.
|
152 |
+
*
|
153 |
+
* @api
|
154 |
+
*
|
155 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
156 |
+
*/
|
157 |
+
public function proceed_to_next( ITSEC_Login_Interstitial_Session $session ) {
|
158 |
+
|
159 |
+
$current = $session->get_current_interstitial();
|
160 |
+
$session->add_completed_interstitial( $current );
|
161 |
+
|
162 |
+
$session->set_current_interstitial( $this->get_next_interstitial( $session ) );
|
163 |
+
$session->save();
|
164 |
+
}
|
165 |
+
|
166 |
+
/**
|
167 |
+
* Get the current interstitial session.
|
168 |
+
*
|
169 |
+
* @api
|
170 |
+
*
|
171 |
+
* @return ITSEC_Login_Interstitial_Session|null
|
172 |
+
*/
|
173 |
+
public function get_current_session() {
|
174 |
+
return $this->current_session;
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Get the URL to an async action.
|
179 |
+
*
|
180 |
+
* @api
|
181 |
+
*
|
182 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
183 |
+
* @param string $action
|
184 |
+
*
|
185 |
+
* @return string
|
186 |
+
*/
|
187 |
+
public function get_async_action_url( ITSEC_Login_Interstitial_Session $session, $action ) {
|
188 |
+
|
189 |
+
$url = $this->get_base_wp_login_url();
|
190 |
+
$url = add_query_arg( array(
|
191 |
+
'action' => "itsec-{$session->get_current_interstitial()}",
|
192 |
+
self::R_USER => $session->get_user()->ID,
|
193 |
+
self::R_TOKEN => $session->get_signature_for_payload( $action ),
|
194 |
+
self::R_SESSION => $session->get_id(),
|
195 |
+
self::R_ASYNC_ACTION => $action,
|
196 |
+
), $url );
|
197 |
+
|
198 |
+
return $url;
|
199 |
+
}
|
200 |
+
|
201 |
+
/**
|
202 |
+
* Initialize the same browser functionality.
|
203 |
+
*
|
204 |
+
* This sets a cookie with a signature payload.
|
205 |
+
*
|
206 |
+
* @api
|
207 |
+
*
|
208 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
209 |
+
*/
|
210 |
+
public function initialize_same_browser( ITSEC_Login_Interstitial_Session $session ) {
|
211 |
+
ITSEC_Lib::set_cookie( self::C_SAME_BROWSER, $session->get_signature_for_payload( self::SAME_BROWSER_PAYLOAD ) );
|
212 |
+
}
|
213 |
+
|
214 |
+
/**
|
215 |
+
* Register the interstitial helper script.
|
216 |
+
*
|
217 |
+
* @internal
|
218 |
+
*/
|
219 |
+
public function enqueue() {
|
220 |
+
wp_register_script( 'itsec-login-interstitial-util', plugin_dir_url( __FILE__ ) . '/login-interstitial/util.js', array( 'jquery', 'wp-util' ), 1 );
|
221 |
+
wp_add_inline_script( 'itsec-login-interstitial-util', '(function() { window.itsecLoginInterstitial = new ITSECLoginInterstitial(); window.itsecLoginInterstitial.init() })()' );
|
222 |
+
}
|
223 |
+
|
224 |
/**
|
225 |
* During the login process, check if we have any interstitials to display, and display them.
|
226 |
*
|
227 |
+
* @internal
|
228 |
+
*
|
229 |
* @param string $username
|
230 |
* @param WP_User $user
|
231 |
*/
|
236 |
return;
|
237 |
}
|
238 |
|
239 |
+
foreach ( $this->get_applicable_interstitials( $user ) as $action => $opts ) {
|
240 |
+
$session = ITSEC_Login_Interstitial_Session::create( $user, $action );
|
241 |
|
242 |
+
$this->build_session_from_global_state( $session );
|
243 |
|
244 |
+
if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
245 |
+
$session->add_show_after( $_REQUEST[ self::SHOW_AFTER_LOGIN ] );
|
246 |
}
|
247 |
+
|
248 |
+
$session->save();
|
249 |
+
|
250 |
+
$this->show_interstitial( $session );
|
251 |
}
|
252 |
|
253 |
+
if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
254 |
+
$session = ITSEC_Login_Interstitial_Session::create( $user, $_REQUEST[ self::SHOW_AFTER_LOGIN ] );
|
255 |
+
$this->build_session_from_global_state( $session );
|
256 |
+
$session->save();
|
257 |
+
$this->show_interstitial( $session );
|
258 |
+
}
|
259 |
+
}
|
260 |
+
|
261 |
+
/**
|
262 |
+
* Build up the interstitial session configuration from the global state.
|
263 |
+
*
|
264 |
+
* This does not set the show afters because this is also used by the show after code.
|
265 |
+
*
|
266 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
267 |
+
*/
|
268 |
+
private function build_session_from_global_state( ITSEC_Login_Interstitial_Session $session ) {
|
269 |
+
if ( isset( $_REQUEST['interim-login'] ) ) {
|
270 |
+
$session->set_interim_login();
|
271 |
+
}
|
272 |
+
|
273 |
+
if ( ! empty( $_REQUEST['redirect_to'] ) ) {
|
274 |
+
$session->set_redirect_to( $_REQUEST['redirect_to'] );
|
275 |
+
} elseif ( ! did_action( 'login_init' ) && ( $ref = wp_get_referer() ) ) {
|
276 |
+
$session->set_redirect_to( $ref );
|
277 |
+
} elseif ( ! did_action( 'login_init' ) ) {
|
278 |
+
$session->set_redirect_to( $_SERVER['REQUEST_URI'] );
|
279 |
+
}
|
280 |
+
|
281 |
+
if ( ! empty( $_REQUEST['rememberme'] ) ) {
|
282 |
+
$session->set_remember_me();
|
283 |
}
|
284 |
}
|
285 |
|
286 |
/**
|
287 |
* Add a message that the interstitial expired.
|
288 |
*
|
289 |
+
* @internal
|
290 |
+
*
|
291 |
* @param WP_Error $errors
|
292 |
*
|
293 |
* @return WP_Error
|
294 |
*/
|
295 |
public function handle_token_expired( $errors ) {
|
296 |
|
297 |
+
if ( isset( $_GET[ self::R_EXPIRED ] ) ) {
|
298 |
$errors->add(
|
299 |
'itsec-login-interstitial-invalid-token',
|
300 |
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
306 |
|
307 |
/**
|
308 |
* Force the requested interstitial to be displayed if the user is already logged-in.
|
309 |
+
*
|
310 |
+
* @internal
|
311 |
*/
|
312 |
public function force_interstitial() {
|
313 |
|
315 |
return;
|
316 |
}
|
317 |
|
318 |
+
$slug = $_REQUEST[ self::SHOW_AFTER_LOGIN ];
|
319 |
|
320 |
+
if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
|
321 |
return;
|
322 |
}
|
323 |
|
325 |
return;
|
326 |
}
|
327 |
|
328 |
+
$user = wp_get_current_user();
|
329 |
+
$interstitial = $this->registered[ $slug ];
|
330 |
|
331 |
+
if ( ! $interstitial->show_to_user( $user, true ) ) {
|
332 |
wp_safe_redirect( admin_url() );
|
333 |
die;
|
334 |
}
|
335 |
|
336 |
+
$session = ITSEC_Login_Interstitial_Session::create( $user, $slug );
|
337 |
+
$this->show_interstitial( $session );
|
338 |
}
|
339 |
|
340 |
/**
|
341 |
* Ferry the after login interstitial query var into the form.
|
342 |
+
*
|
343 |
+
* @internal
|
344 |
*/
|
345 |
public function ferry_after_login() {
|
346 |
if ( ! empty( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) && isset( $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
351 |
/**
|
352 |
* Capture the session token to log out the user.
|
353 |
*
|
354 |
+
* @internal
|
355 |
+
*
|
356 |
* @param string $cookie
|
357 |
* @param int $user_id
|
358 |
* @param int $expiration
|
369 |
|
370 |
/**
|
371 |
* Handle submitting the interstitial form.
|
372 |
+
*
|
373 |
+
* @internal
|
374 |
*/
|
375 |
public function submit() {
|
376 |
+
$session = $this->get_and_verify_session();
|
377 |
+
$slug = $session->get_current_interstitial();
|
378 |
+
|
379 |
+
// If we think we have all finished all the interstitials.
|
380 |
+
// We need to check because another process may have moved the interstitial forward.
|
381 |
+
if ( ! $slug ) {
|
382 |
+
// Double check to ensure we are actually finished.
|
383 |
+
if ( $next = $this->get_next_interstitial( $session ) ) {
|
384 |
+
// If not, display the next interstitial.
|
385 |
+
$session->set_current_interstitial( $next );
|
386 |
+
$session->save();
|
387 |
+
}
|
388 |
+
|
389 |
+
$this->do_next_step( $session );
|
390 |
+
}
|
391 |
|
392 |
+
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] || empty( $_POST['action'] ) ) {
|
393 |
return;
|
394 |
}
|
395 |
|
396 |
+
if ( empty( $this->registered[ $slug ] ) ) {
|
397 |
return;
|
398 |
}
|
399 |
|
400 |
+
$requested_slug = substr( $_POST['action'], strlen( 'itsec-' ) );
|
401 |
|
402 |
+
if ( $slug !== $requested_slug ) {
|
403 |
+
// If we have already completed the action that was requested, then just display
|
404 |
+
// the new interstitial.
|
405 |
+
if ( $session->is_interstitial_completed( $requested_slug ) ) {
|
406 |
+
$this->show_interstitial( $session );
|
407 |
+
} else {
|
408 |
+
$this->redirect_invalid_token();
|
409 |
+
}
|
410 |
}
|
411 |
|
412 |
+
$this->current_session = $session;
|
413 |
+
|
414 |
+
$interstitial = $this->registered[ $slug ];
|
415 |
+
|
416 |
+
if ( $interstitial->has_submit() ) {
|
417 |
+
$maybe_error = $interstitial->submit( $session, $_POST );
|
418 |
|
419 |
+
if ( is_wp_error( $maybe_error ) ) {
|
420 |
+
$this->error = $maybe_error;
|
421 |
+
|
422 |
+
return;
|
423 |
+
}
|
424 |
+
}
|
425 |
+
|
426 |
+
$interstitial->after_submit( $session, $_POST );
|
427 |
+
|
428 |
+
$this->proceed_to_next( $session );
|
429 |
+
$this->do_next_step( $session );
|
430 |
+
}
|
431 |
|
432 |
+
/**
|
433 |
+
* Handle an async GET action to the interstitial.
|
434 |
+
*
|
435 |
+
* @internal
|
436 |
+
*/
|
437 |
+
public function async_action() {
|
438 |
+
|
439 |
+
if ( empty( $_GET[ self::R_ASYNC_ACTION ] ) ) {
|
440 |
return;
|
441 |
}
|
442 |
|
443 |
+
$session = $this->get_and_verify_session_for_async_action( true );
|
444 |
+
|
445 |
+
if ( is_wp_error( $session ) ) {
|
446 |
+
$this->display_wp_login_message( $session );
|
447 |
}
|
448 |
|
449 |
+
$action = $_GET[ self::R_ASYNC_ACTION ];
|
450 |
+
$slug = $session->get_current_interstitial();
|
451 |
+
|
452 |
+
if ( empty( $this->registered[ $slug ] ) ) {
|
453 |
+
$this->display_wp_login_message( new WP_Error( 'unsupported', esc_html__( 'Unsupported Interstitial. Please login again.', 'better-wp-security' ) ) );
|
454 |
}
|
455 |
|
456 |
+
$interstitial = $this->registered[ $slug ];
|
457 |
+
|
458 |
+
if ( ! $interstitial->has_async_action() ) {
|
459 |
+
$this->display_wp_login_message( new WP_Error( 'unsupported', esc_html__( 'Unsupported Interstitial. Please login again.', 'better-wp-security' ) ) );
|
460 |
}
|
461 |
|
462 |
+
$args = array(
|
463 |
+
'same_browser' => false,
|
464 |
+
);
|
465 |
+
|
466 |
+
if (
|
467 |
+
isset( $_COOKIE[ self::C_SAME_BROWSER ] ) &&
|
468 |
+
true === $session->verify_signature_for_payload( self::SAME_BROWSER_PAYLOAD, $_COOKIE[ self::C_SAME_BROWSER ] )
|
469 |
+
) {
|
470 |
+
$args['same_browser'] = true;
|
471 |
}
|
472 |
|
473 |
+
$this->current_session = $session;
|
474 |
+
$result = $interstitial->handle_async_action( $session, $action, $args );
|
475 |
|
476 |
+
if ( null === $result ) {
|
477 |
+
$this->display_wp_login_message( new WP_Error( 'unsupported', esc_html__( 'Unsupported Interstitial. Please login again.', 'better-wp-security' ) ) );
|
478 |
+
}
|
|
|
|
|
|
|
479 |
|
480 |
+
if ( is_wp_error( $result ) ) {
|
481 |
+
$this->display_wp_login_message( $result );
|
482 |
}
|
483 |
|
484 |
+
if ( true === $result ) {
|
485 |
+
$result = array();
|
|
|
|
|
|
|
486 |
}
|
487 |
|
488 |
+
if ( $args['same_browser'] && empty( $args['allow_same_browser'] ) ) {
|
489 |
+
$this->do_next_step( $session, array(
|
490 |
+
'delete' => false,
|
491 |
+
'allow_interim' => false,
|
492 |
+
) );
|
493 |
+
}
|
494 |
|
495 |
+
$result = wp_parse_args( $result, array(
|
496 |
+
'message' => esc_html__( 'Action processed. Please continue in your original browser.', 'better-wp-security' ),
|
497 |
+
'title' => esc_html__( 'Action Processed', 'better-wp-security' ),
|
498 |
+
) );
|
499 |
+
|
500 |
+
$this->display_wp_login_message( $result );
|
501 |
}
|
502 |
|
503 |
/**
|
504 |
* Ajax Handler.
|
505 |
+
*
|
506 |
+
* @internal
|
507 |
*/
|
508 |
public function ajax_handler() {
|
509 |
|
510 |
+
$session = $this->get_and_verify_session( true );
|
511 |
+
$get_state = ! empty( $_REQUEST[ self::R_GET_STATE ] );
|
|
|
512 |
|
513 |
+
if ( is_wp_error( $session ) ) {
|
514 |
+
if ( $get_state && is_user_logged_in() ) {
|
515 |
+
wp_send_json_success( array(
|
516 |
+
'logged_in' => true,
|
517 |
+
) );
|
518 |
+
}
|
519 |
|
520 |
+
wp_send_json_error( array( 'message' => $session->get_error_message() ) );
|
|
|
521 |
}
|
522 |
|
523 |
+
if ( $get_state ) {
|
524 |
+
wp_send_json_success( array(
|
525 |
+
'current' => $session->get_current_interstitial(),
|
526 |
+
'completed' => $session->get_completed_interstitials(),
|
527 |
+
'state' => $session->get_state(),
|
528 |
+
) );
|
529 |
+
}
|
530 |
|
531 |
+
$slug = $session->get_current_interstitial();
|
532 |
|
533 |
+
if ( empty( $this->registered[ $slug ] ) ) {
|
534 |
+
wp_send_json_error( array( 'message' => esc_html__( 'Invalid Interstitial Action', 'better-wp-security' ) ) );
|
535 |
}
|
536 |
|
537 |
+
$interstitial = $this->registered[ $slug ];
|
538 |
+
|
539 |
+
if ( ! $interstitial->has_ajax_handlers() ) {
|
540 |
+
wp_send_json_error( array( 'message' => esc_html__( 'Invalid Interstitial Action', 'better-wp-security' ) ) );
|
541 |
}
|
542 |
|
543 |
$data = $_POST;
|
544 |
+
unset( $data[ self::R_USER ], $data[ self::R_TOKEN ], $data[ self::R_SESSION ] );
|
545 |
|
546 |
+
$this->current_session = $session;
|
547 |
+
$interstitial->handle_ajax( $session, $data );
|
548 |
}
|
549 |
|
550 |
/**
|
551 |
* Handle displaying the interstitial form.
|
552 |
+
*
|
553 |
+
* @internal
|
554 |
*/
|
555 |
public function display() {
|
556 |
|
560 |
return;
|
561 |
}
|
562 |
|
563 |
+
$interstitial = $this->registered[ $action ];
|
564 |
+
$session = $this->get_and_verify_session();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
565 |
|
566 |
+
if ( ! $interstitial->show_to_user( $session->get_user(), $session->is_current_requested() ) ) {
|
567 |
wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
|
568 |
die;
|
569 |
}
|
570 |
|
571 |
+
$this->login_html( $session );
|
572 |
die;
|
573 |
}
|
574 |
|
575 |
/**
|
576 |
* Display an interstitial form during the login process.
|
577 |
*
|
578 |
+
* @internal
|
579 |
+
*
|
580 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
581 |
*/
|
582 |
+
protected function login_html( ITSEC_Login_Interstitial_Session $session ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
583 |
|
584 |
+
$user = $session->get_user();
|
585 |
+
$action = $session->get_current_interstitial();
|
586 |
+
$interstitial = $this->registered[ $action ];
|
587 |
|
588 |
+
$wp_login_url = $this->get_base_wp_login_url();
|
589 |
+
$wp_login_url = add_query_arg( 'action', "itsec-{$action}", $wp_login_url );
|
590 |
|
591 |
// Prevent JetPack from attempting to SSO the update password form.
|
592 |
add_filter( 'jetpack_sso_allowed_actions', '__return_empty_array' );
|
595 |
require_once( dirname( __FILE__ ) . '/includes/function.login-header.php' );
|
596 |
}
|
597 |
|
|
|
|
|
598 |
login_header();
|
599 |
+
|
600 |
+
wp_enqueue_script( 'itsec-login-interstitial-util' );
|
601 |
?>
|
602 |
|
603 |
<?php if ( $this->error ) : ?>
|
604 |
<div id="login-error" class="message" style="border-left-color: #dc3232;">
|
605 |
<?php echo $this->error->get_error_message(); ?>
|
606 |
</div>
|
607 |
+
<?php elseif ( $message = $interstitial->get_info_message( $session ) ): ?>
|
608 |
<p class="message"><?php echo $message; ?></p>
|
609 |
<?php endif; ?>
|
610 |
|
611 |
<form name="itsec-<?php echo esc_attr( $action ); ?>" id="itsec-<?php echo esc_attr( $action ); ?>"
|
612 |
action="<?php echo esc_url( $wp_login_url ); ?>" method="post" autocomplete="off">
|
613 |
|
614 |
+
<?php $interstitial->render( $session, compact( 'wp_login_url' ) ); ?>
|
|
|
|
|
|
|
|
|
|
|
|
|
615 |
|
|
|
616 |
<input type="hidden" name="action" value="<?php echo esc_attr( "itsec-{$action}" ); ?>">
|
617 |
|
618 |
+
<input type="hidden" name="<?php echo esc_attr( self::R_USER ) ?>" value="<?php echo esc_attr( $user->ID ); ?>">
|
619 |
+
<input type="hidden" name="<?php echo esc_attr( self::R_TOKEN ) ?>" value="<?php echo esc_attr( $session->get_signature() ); ?>">
|
620 |
+
<input type="hidden" name="<?php echo esc_attr( self::R_SESSION ) ?>" value="<?php echo esc_attr( $session->get_id() ); ?>">
|
|
|
|
|
|
|
|
|
|
|
621 |
</form>
|
622 |
|
623 |
<p id="backtoblog">
|
659 |
|
660 |
<?php if ( $customize_login ) : ?>
|
661 |
<script type="text/javascript">
|
662 |
+
setTimeout( function() {
|
663 |
new wp.customize.Messenger( {
|
664 |
url : '<?php echo wp_customize_url(); ?>',
|
665 |
+
channel: 'login',
|
666 |
+
} ).send( 'login' );
|
667 |
}, 1000 );
|
668 |
</script>
|
669 |
<?php endif; ?>
|
672 |
<?php die;
|
673 |
}
|
674 |
|
675 |
+
/**
|
676 |
+
* Display a message on the WP-Login screen.
|
677 |
+
*
|
678 |
+
* @param WP_Error|array $message
|
679 |
+
*/
|
680 |
+
private function display_wp_login_message( $message ) {
|
681 |
+
if ( ! function_exists( 'login_header' ) ) {
|
682 |
+
require_once( dirname( __FILE__ ) . '/includes/function.login-header.php' );
|
683 |
+
}
|
684 |
+
|
685 |
+
login_header();
|
686 |
+
|
687 |
+
?>
|
688 |
+
<?php if ( is_wp_error( $message ) ) : ?>
|
689 |
+
<div id="login-error" class="message" style="border-left-color: #dc3232;">
|
690 |
+
<?php echo $message->get_error_message(); ?>
|
691 |
+
</div>
|
692 |
+
<?php elseif ( ! empty( $message['message'] ) ): ?>
|
693 |
+
<p class="message"><?php echo $message['message']; ?></p>
|
694 |
+
<?php endif; ?>
|
695 |
+
|
696 |
+
<p id="backtoblog">
|
697 |
+
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" title="<?php esc_attr_e( 'Are you lost?', 'better-wp-security' ); ?>">
|
698 |
+
<?php echo esc_html( sprintf( __( '← Back to %s', 'better-wp-security' ), get_bloginfo( 'title', 'display' ) ) ); ?>
|
699 |
+
</a>
|
700 |
+
</p>
|
701 |
+
|
702 |
+
</div>
|
703 |
+
<?php do_action( 'login_footer' ); ?>
|
704 |
+
<div class="clear"></div>
|
705 |
+
</body>
|
706 |
+
</html>
|
707 |
+
<?php
|
708 |
+
die;
|
709 |
+
}
|
710 |
+
|
711 |
+
/**
|
712 |
+
* Do the next step for a session.
|
713 |
+
*
|
714 |
+
* If there are more steps, show the next step, otherwise log the user in.
|
715 |
+
*
|
716 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
717 |
+
* @param array $args
|
718 |
+
*/
|
719 |
+
private function do_next_step( ITSEC_Login_Interstitial_Session $session, array $args = array() ) {
|
720 |
+
$args = wp_parse_args( $args, array(
|
721 |
+
'delete' => true,
|
722 |
+
'allow_interim' => true,
|
723 |
+
) );
|
724 |
+
|
725 |
+
if ( $session->get_current_interstitial() ) {
|
726 |
+
$this->show_interstitial( $session );
|
727 |
+
} else {
|
728 |
+
if ( true === $args['delete'] ) {
|
729 |
+
$session->delete();
|
730 |
+
}
|
731 |
+
|
732 |
+
$this->handle_interstitials_completed( $session, $args );
|
733 |
+
}
|
734 |
+
}
|
735 |
+
|
736 |
+
/**
|
737 |
+
* Handle when all of the interstitials have been processed.
|
738 |
+
*
|
739 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
740 |
+
* @param array $args
|
741 |
+
*/
|
742 |
+
private function handle_interstitials_completed( ITSEC_Login_Interstitial_Session $session, array $args ) {
|
743 |
+
|
744 |
+
$user = $session->get_user();
|
745 |
+
$secure = '';
|
746 |
+
|
747 |
+
// If the user wants SSL but the session is not SSL, force a secure cookie.
|
748 |
+
if ( ! force_ssl_admin() && get_user_option( 'use_ssl', $user->ID ) ) {
|
749 |
+
$secure = true;
|
750 |
+
force_ssl_admin( true );
|
751 |
+
}
|
752 |
+
|
753 |
+
if ( ! is_user_logged_in() ) {
|
754 |
+
wp_set_auth_cookie( $user->ID, $session->is_remember_me(), $secure );
|
755 |
+
|
756 |
+
remove_action( 'wp_login', array( $this, 'wp_login' ), - 1000 );
|
757 |
+
do_action( 'wp_login', $user->user_login, $user );
|
758 |
+
|
759 |
+
/**
|
760 |
+
* Fires when a user is re-logged back in after submitting an interstitial.
|
761 |
+
*
|
762 |
+
* @param WP_User $user
|
763 |
+
*/
|
764 |
+
do_action( 'itsec_login_interstitial_logged_in', $user );
|
765 |
+
}
|
766 |
+
|
767 |
+
if ( $args['allow_interim'] && $session->is_interim_login() ) {
|
768 |
+
$this->interim_login();
|
769 |
+
}
|
770 |
+
|
771 |
+
if ( $session->get_redirect_to() ) {
|
772 |
+
$redirect_to = $requested = $session->get_redirect_to();
|
773 |
+
|
774 |
+
if ( $secure && false !== strpos( $redirect_to, 'wp-admin' ) ) {
|
775 |
+
$redirect_to = preg_replace( '|^http://|', 'https://', $redirect_to );
|
776 |
+
}
|
777 |
+
} else {
|
778 |
+
$redirect_to = admin_url();
|
779 |
+
$requested = '';
|
780 |
+
}
|
781 |
+
|
782 |
+
if ( ! $redirect_to || $redirect_to === 'wp-admin/' || $redirect_to === admin_url() ) {
|
783 |
+
// If the user doesn't belong to a blog, send them to user admin. If the user can't edit posts, send them to their profile.
|
784 |
+
if ( is_multisite() && ! get_active_blog_for_user( $user->ID ) && ! is_super_admin( $user->ID ) ) {
|
785 |
+
$redirect_to = user_admin_url();
|
786 |
+
} elseif ( is_multisite() && ! $user->has_cap( 'read' ) ) {
|
787 |
+
$redirect_to = get_dashboard_url( $user->ID );
|
788 |
+
} elseif ( ! $user->has_cap( 'edit_posts' ) ) {
|
789 |
+
$redirect_to = $user->has_cap( 'read' ) ? admin_url( 'profile.php' ) : home_url();
|
790 |
+
}
|
791 |
+
}
|
792 |
+
|
793 |
+
$redirect_to = apply_filters( 'login_redirect', $redirect_to, $requested, $user );
|
794 |
+
wp_safe_redirect( $redirect_to );
|
795 |
+
|
796 |
+
die;
|
797 |
+
}
|
798 |
+
|
799 |
+
/**
|
800 |
+
* Get the base wp login URL.
|
801 |
+
*
|
802 |
+
* @return string
|
803 |
+
*/
|
804 |
+
private function get_base_wp_login_url() {
|
805 |
+
$wp_login_url = set_url_scheme( wp_login_url(), 'login_post' );
|
806 |
+
|
807 |
+
if ( isset( $_GET['wpe-login'] ) && ! preg_match( '/[&?]wpe-login=/', $wp_login_url ) ) {
|
808 |
+
$wp_login_url = add_query_arg( 'wpe-login', $_GET['wpe-login'], $wp_login_url );
|
809 |
+
}
|
810 |
+
|
811 |
+
return $wp_login_url;
|
812 |
+
}
|
813 |
+
|
814 |
/**
|
815 |
* Get the next interstitial to be displayed.
|
816 |
*
|
817 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
|
|
818 |
*
|
819 |
* @return string|false
|
820 |
*/
|
821 |
+
private function get_next_interstitial( ITSEC_Login_Interstitial_Session $session ) {
|
822 |
|
823 |
+
foreach ( $this->get_applicable_interstitials( $session->get_user() ) as $action => $interstitial ) {
|
824 |
+
if ( ! $session->is_interstitial_completed( $action ) ) {
|
825 |
+
return $action;
|
826 |
|
|
|
|
|
|
|
|
|
827 |
}
|
828 |
+
}
|
829 |
|
830 |
+
foreach ( $session->get_show_after() as $action ) {
|
831 |
+
if ( ! $session->is_interstitial_completed( $action ) ) {
|
832 |
+
return $action;
|
833 |
}
|
834 |
}
|
835 |
|
847 |
|
848 |
$applicable = array();
|
849 |
|
850 |
+
foreach ( $this->registered as $action => $interstitial ) {
|
851 |
if ( $this->is_interstitial_applicable( $action, $user ) ) {
|
852 |
+
$applicable[ $action ] = $interstitial;
|
853 |
}
|
854 |
}
|
855 |
|
859 |
/**
|
860 |
* Is the interstitial applicable to the given user.
|
861 |
*
|
862 |
+
* @param string $action
|
|
|
863 |
* @param WP_User $user
|
864 |
*
|
865 |
* @return bool
|
866 |
*/
|
867 |
+
private function is_interstitial_applicable( $action, $user ) {
|
868 |
|
869 |
+
$interstitial = $this->registered[ $action ];
|
870 |
|
871 |
+
if ( ! $interstitial->show_to_user( $user, false ) ) {
|
872 |
return false;
|
873 |
}
|
874 |
|
875 |
+
if ( ! did_action( 'login_init' ) && $interstitial->show_on_wp_login_only( $user ) ) {
|
876 |
return false;
|
877 |
}
|
878 |
|
880 |
}
|
881 |
|
882 |
/**
|
883 |
+
* Get the active session.
|
884 |
*
|
885 |
+
* @param bool $return_error
|
|
|
886 |
*
|
887 |
+
* @return ITSEC_Login_Interstitial_Session|WP_Error
|
888 |
*/
|
889 |
+
private function get_and_verify_session( $return_error = false ) {
|
890 |
|
891 |
+
$error = new WP_Error(
|
892 |
+
'itsec-login-interstitial-invalid-token',
|
893 |
+
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
894 |
+
);
|
895 |
|
896 |
+
if ( ! isset( $_REQUEST[ self::R_USER ], $_REQUEST[ self::R_TOKEN ], $_REQUEST[ self::R_SESSION ] ) ) {
|
897 |
+
return $return_error ? $error : $this->redirect_invalid_token();
|
|
|
|
|
898 |
}
|
899 |
|
900 |
+
$session = ITSEC_Login_Interstitial_Session::get( $_REQUEST[ self::R_SESSION ] );
|
|
|
|
|
|
|
|
|
|
|
|
|
901 |
|
902 |
+
if ( is_wp_error( $session ) ) {
|
903 |
+
return $return_error ? $error : $this->redirect_invalid_token();
|
904 |
}
|
905 |
|
906 |
+
$valid = $session->verify( (int) $_REQUEST[ self::R_USER ], $_REQUEST[ self::R_TOKEN ] );
|
907 |
+
|
908 |
+
if ( true !== $valid ) {
|
909 |
+
return $return_error ? $error : $this->redirect_invalid_token();
|
910 |
+
}
|
911 |
+
|
912 |
+
return $session;
|
913 |
}
|
914 |
|
915 |
/**
|
916 |
+
* Get the active session and verify for performing an async action.
|
917 |
*
|
918 |
+
* @param bool $return_error
|
|
|
919 |
*
|
920 |
+
* @return ITSEC_Login_Interstitial_Session|WP_Error
|
921 |
*/
|
922 |
+
private function get_and_verify_session_for_async_action( $return_error = false ) {
|
923 |
|
924 |
+
$error = new WP_Error(
|
925 |
+
'itsec-login-interstitial-invalid-token',
|
926 |
+
esc_html__( 'Sorry, this request has expired. Please log in again.', 'better-wp-security' )
|
927 |
+
);
|
928 |
|
929 |
+
if ( ! isset( $_REQUEST[ self::R_USER ], $_REQUEST[ self::R_TOKEN ], $_REQUEST[ self::R_SESSION ], $_REQUEST[ self::R_ASYNC_ACTION ] ) ) {
|
930 |
+
return $return_error ? $error : $this->redirect_invalid_token();
|
931 |
}
|
932 |
|
933 |
+
$session = ITSEC_Login_Interstitial_Session::get( $_REQUEST[ self::R_SESSION ] );
|
|
|
934 |
|
935 |
+
if ( is_wp_error( $session ) ) {
|
936 |
+
return $return_error ? $error : $this->redirect_invalid_token();
|
937 |
}
|
938 |
|
939 |
+
$valid = $session->verify_for_payload(
|
940 |
+
$_REQUEST[ self::R_ASYNC_ACTION ],
|
941 |
+
(int) $_REQUEST[ self::R_USER ],
|
942 |
+
$_REQUEST[ self::R_TOKEN ]
|
943 |
+
);
|
944 |
|
945 |
+
if ( true !== $valid ) {
|
946 |
+
return $return_error ? $error : $this->redirect_invalid_token();
|
|
|
|
|
|
|
947 |
}
|
948 |
|
949 |
+
return $session;
|
950 |
}
|
951 |
|
952 |
/**
|
953 |
* Redirect back to the login page with a message that the token is invalid.
|
|
|
|
|
954 |
*/
|
955 |
+
private function redirect_invalid_token() {
|
956 |
+
|
957 |
+
if ( ! isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ] ) && is_user_logged_in() ) {
|
958 |
+
wp_safe_redirect( admin_url() );
|
959 |
+
die;
|
960 |
+
}
|
961 |
+
|
962 |
+
$redirect = add_query_arg( self::R_EXPIRED, 1, wp_login_url() );
|
963 |
wp_safe_redirect( set_url_scheme( $redirect, 'login_post' ) );
|
964 |
die;
|
965 |
}
|
969 |
*
|
970 |
* @param WP_User $user
|
971 |
*/
|
972 |
+
private function destroy_session_token( $user ) {
|
973 |
WP_Session_Tokens::get_instance( $user->ID )->destroy( $this->session_token ? $this->session_token : wp_get_session_token() );
|
974 |
wp_clear_auth_cookie();
|
975 |
}
|
976 |
|
977 |
/**
|
978 |
+
* Sort interstitials according to priority.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
979 |
*
|
980 |
+
* @param ITSEC_Login_Interstitial $a
|
981 |
+
* @param ITSEC_Login_Interstitial $b
|
982 |
*
|
983 |
+
* @return int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
984 |
*/
|
985 |
+
private function _sort_interstitials( $a, $b ) {
|
986 |
+
return $a->get_priority() - $b->get_priority();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
987 |
}
|
988 |
+
}
|
core/lib/class-itsec-lib-remote-messages.php
ADDED
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ITSEC_Lib_Remote_Messages {
|
4 |
+
|
5 |
+
const URL = 'https://ithemes.com/api/itsec-service-status.json';
|
6 |
+
const OPTION = 'itsec_remote_messages';
|
7 |
+
const EVENT = 'remote-messages';
|
8 |
+
|
9 |
+
/** @var array */
|
10 |
+
private static $_response;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Initialize the Remote Messages library.
|
14 |
+
*/
|
15 |
+
public static function init() {
|
16 |
+
if ( ITSEC_Core::is_pro() ) {
|
17 |
+
add_action( 'itsec_scheduled_' . self::EVENT, array( __CLASS__, 'run_event' ) );
|
18 |
+
}
|
19 |
+
}
|
20 |
+
|
21 |
+
public static function get_actions() {
|
22 |
+
|
23 |
+
$response = self::get_response();
|
24 |
+
|
25 |
+
return isset( $response['actions'] ) ? $response['actions'] : array();
|
26 |
+
}
|
27 |
+
|
28 |
+
public static function has_action( $action ) {
|
29 |
+
return in_array( $action, self::get_actions(), true );
|
30 |
+
}
|
31 |
+
|
32 |
+
public static function get_raw_messages() {
|
33 |
+
$response = self::get_response();
|
34 |
+
|
35 |
+
return isset( $response['messages'] ) ? $response['messages'] : array();
|
36 |
+
}
|
37 |
+
|
38 |
+
public static function get_messages_for_placement( $placement ) {
|
39 |
+
|
40 |
+
$matched = array();
|
41 |
+
|
42 |
+
foreach ( self::get_raw_messages() as $message ) {
|
43 |
+
if ( in_array( $placement, $message['placement'], true ) ) {
|
44 |
+
$matched[] = array(
|
45 |
+
'message' => $message['message'],
|
46 |
+
'type' => $message['type'],
|
47 |
+
);
|
48 |
+
}
|
49 |
+
}
|
50 |
+
|
51 |
+
return $matched;
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Run the event to fetch the data.
|
56 |
+
*
|
57 |
+
* @param ITSEC_Job $job
|
58 |
+
*/
|
59 |
+
public static function run_event( $job ) {
|
60 |
+
|
61 |
+
$response = wp_remote_get( self::URL, array(
|
62 |
+
'user-agent' => 'WordPress',
|
63 |
+
) );
|
64 |
+
|
65 |
+
if ( is_wp_error( $response ) ) {
|
66 |
+
$job->reschedule_in( 5 * MINUTE_IN_SECONDS );
|
67 |
+
|
68 |
+
return;
|
69 |
+
}
|
70 |
+
|
71 |
+
$data = wp_remote_retrieve_body( $response );
|
72 |
+
|
73 |
+
if ( ! $data ) {
|
74 |
+
$job->reschedule_in( 5 * MINUTE_IN_SECONDS );
|
75 |
+
|
76 |
+
return;
|
77 |
+
}
|
78 |
+
|
79 |
+
$json = json_decode( $data, true );
|
80 |
+
|
81 |
+
if ( ! $json ) {
|
82 |
+
$job->reschedule_in( 5 * MINUTE_IN_SECONDS );
|
83 |
+
|
84 |
+
return;
|
85 |
+
}
|
86 |
+
|
87 |
+
$json = wp_parse_args( $json, array(
|
88 |
+
'ttl' => HOUR_IN_SECONDS,
|
89 |
+
'messages' => array(),
|
90 |
+
'actions' => array(),
|
91 |
+
) );
|
92 |
+
|
93 |
+
$sanitized = array(
|
94 |
+
'messages' => array(),
|
95 |
+
'actions' => wp_parse_slug_list( $json['actions'] ),
|
96 |
+
);
|
97 |
+
|
98 |
+
foreach ( $json['messages'] as $message ) {
|
99 |
+
$sanitized['messages'][] = array(
|
100 |
+
'message' => self::sanitize_message( $message['message'] ),
|
101 |
+
'type' => self::sanitize_type( $message['type'] ),
|
102 |
+
'placement' => $message['placement'],
|
103 |
+
);
|
104 |
+
}
|
105 |
+
|
106 |
+
update_site_option( self::OPTION, array(
|
107 |
+
'response' => $sanitized,
|
108 |
+
'ttl' => $json['ttl'],
|
109 |
+
'requested' => ITSEC_Core::get_current_time_gmt(),
|
110 |
+
) );
|
111 |
+
}
|
112 |
+
|
113 |
+
private static function sanitize_message( $message ) {
|
114 |
+
return wp_kses( $message, array( 'a' => array( 'href' => true ) ) );
|
115 |
+
}
|
116 |
+
|
117 |
+
private static function sanitize_type( $type ) {
|
118 |
+
if ( in_array( $type, array( 'success', 'info', 'warning', 'error' ), true ) ) {
|
119 |
+
return $type;
|
120 |
+
}
|
121 |
+
|
122 |
+
return 'info';
|
123 |
+
}
|
124 |
+
|
125 |
+
private static function get_response() {
|
126 |
+
|
127 |
+
if ( ! ITSEC_Core::is_pro() ) {
|
128 |
+
return array();
|
129 |
+
}
|
130 |
+
|
131 |
+
if ( isset( self::$_response ) ) {
|
132 |
+
return self::$_response;
|
133 |
+
}
|
134 |
+
|
135 |
+
$data = get_site_option( self::OPTION, array() );
|
136 |
+
$data = wp_parse_args( $data, array(
|
137 |
+
'response' => array(),
|
138 |
+
'requested' => 0,
|
139 |
+
'ttl' => 0,
|
140 |
+
) );
|
141 |
+
|
142 |
+
if ( ! $data['response'] ) {
|
143 |
+
self::schedule_check();
|
144 |
+
|
145 |
+
return self::$_response = array();
|
146 |
+
}
|
147 |
+
|
148 |
+
if ( $data['requested'] + $data['ttl'] < ITSEC_Core::get_current_time_gmt() ) {
|
149 |
+
self::schedule_check();
|
150 |
+
$events = ITSEC_Core::get_scheduler()->get_single_events();
|
151 |
+
|
152 |
+
foreach ( $events as $event ) {
|
153 |
+
if ( self::EVENT === $event['id'] && $event['fire_at'] + HOUR_IN_SECONDS > ITSEC_Core::get_current_time_gmt() ) {
|
154 |
+
return self::$_response = $data['response'];
|
155 |
+
}
|
156 |
+
}
|
157 |
+
|
158 |
+
return self::$_response = array();
|
159 |
+
}
|
160 |
+
|
161 |
+
return self::$_response = $data['response'];
|
162 |
+
}
|
163 |
+
|
164 |
+
private static function schedule_check() {
|
165 |
+
$s = ITSEC_Core::get_scheduler();
|
166 |
+
|
167 |
+
if ( ! $s->is_single_scheduled( self::EVENT, null ) ) {
|
168 |
+
$s->schedule_once( ITSEC_Core::get_current_time_gmt() + 60, self::EVENT );
|
169 |
+
}
|
170 |
+
}
|
171 |
+
}
|
core/lib/class-itsec-mail.php
CHANGED
@@ -169,6 +169,17 @@ final class ITSEC_Mail {
|
|
169 |
return $module;
|
170 |
}
|
171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
public function add_section_heading( $content, $icon_type = false ) {
|
173 |
$this->add_html( $this->get_section_heading( $content, $icon_type ) );
|
174 |
}
|
@@ -236,6 +247,23 @@ final class ITSEC_Mail {
|
|
236 |
return $module;
|
237 |
}
|
238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
239 |
public function add_lockouts_table( $lockouts ) {
|
240 |
$entry = $this->get_template( 'lockouts-entry.html' );
|
241 |
$entries = '';
|
169 |
return $module;
|
170 |
}
|
171 |
|
172 |
+
public function add_small_code( $content ) {
|
173 |
+
$this->add_html( $this->get_small_code( $content ) );
|
174 |
+
}
|
175 |
+
|
176 |
+
public function get_small_code( $content ) {
|
177 |
+
$module = $this->get_template( 'small-code.html' );
|
178 |
+
$module = $this->replace( $module, 'content', $content );
|
179 |
+
|
180 |
+
return $module;
|
181 |
+
}
|
182 |
+
|
183 |
public function add_section_heading( $content, $icon_type = false ) {
|
184 |
$this->add_html( $this->get_section_heading( $content, $icon_type ) );
|
185 |
}
|
247 |
return $module;
|
248 |
}
|
249 |
|
250 |
+
public function add_large_button( $link_text, $href, $style = 'default' ) {
|
251 |
+
$this->add_html( $this->get_large_button( $link_text, $href, $style ) );
|
252 |
+
}
|
253 |
+
|
254 |
+
public function get_large_button( $link_text, $href, $style = 'default' ) {
|
255 |
+
|
256 |
+
$module = $this->get_template( 'large-button.html' );
|
257 |
+
$module = $this->replace_all( $module, array(
|
258 |
+
'href' => $href,
|
259 |
+
'link_text' => $link_text,
|
260 |
+
'bk_color' => 'blue' === $style ? '#0085E0' : '#FFCD08',
|
261 |
+
'txt_color' => 'blue' === $style ? '#FFFFFF' : '#2E280E',
|
262 |
+
) );
|
263 |
+
|
264 |
+
return $module;
|
265 |
+
}
|
266 |
+
|
267 |
public function add_lockouts_table( $lockouts ) {
|
268 |
$entry = $this->get_template( 'lockouts-entry.html' );
|
269 |
$entries = '';
|
core/lib/form.php
CHANGED
@@ -352,6 +352,16 @@ final class ITSEC_Form {
|
|
352 |
$this->add_custom_input( $var, $options );
|
353 |
}
|
354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
public function add_textarea( $var, $options = array() ) {
|
356 |
if ( ! is_array( $options ) ) {
|
357 |
$options = array( 'value' => $options );
|
352 |
$this->add_custom_input( $var, $options );
|
353 |
}
|
354 |
|
355 |
+
public function add_html5_input( $var, $type, $options = array() ) {
|
356 |
+
if ( ! is_array( $options ) ) {
|
357 |
+
$options = array( 'value' => $options );
|
358 |
+
}
|
359 |
+
|
360 |
+
$options['type'] = $type;
|
361 |
+
|
362 |
+
$this->add_custom_input( $var, $options );
|
363 |
+
}
|
364 |
+
|
365 |
public function add_textarea( $var, $options = array() ) {
|
366 |
if ( ! is_array( $options ) ) {
|
367 |
$options = array( 'value' => $options );
|
core/lib/log-util.php
CHANGED
@@ -59,7 +59,7 @@ final class ITSEC_Log_Util {
|
|
59 |
|
60 |
|
61 |
$get_count = false;
|
62 |
-
$min_timestamp = false;
|
63 |
|
64 |
if ( isset( $filters['__get_count'] ) ) {
|
65 |
if ( $filters['__get_count'] ) {
|
@@ -74,6 +74,12 @@ final class ITSEC_Log_Util {
|
|
74 |
unset( $filters['__min_timestamp'] );
|
75 |
}
|
76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
$limit = max( 0, min( 100, intval( $limit ) ) );
|
78 |
$page = max( 1, intval( $page ) );
|
79 |
|
@@ -171,10 +177,15 @@ final class ITSEC_Log_Util {
|
|
171 |
}
|
172 |
|
173 |
if ( false !== $min_timestamp ) {
|
174 |
-
$where_entries[] = '
|
175 |
$prepare_args[] = date( 'Y-m-d H:i:s', $min_timestamp );
|
176 |
}
|
177 |
|
|
|
|
|
|
|
|
|
|
|
178 |
$query .= ' WHERE ' . implode( ' AND ', $where_entries );
|
179 |
|
180 |
|
59 |
|
60 |
|
61 |
$get_count = false;
|
62 |
+
$min_timestamp = $max_timestamp = false;
|
63 |
|
64 |
if ( isset( $filters['__get_count'] ) ) {
|
65 |
if ( $filters['__get_count'] ) {
|
74 |
unset( $filters['__min_timestamp'] );
|
75 |
}
|
76 |
|
77 |
+
if ( isset( $filters['__max_timestamp'] ) ) {
|
78 |
+
$max_timestamp = $filters['__max_timestamp'];
|
79 |
+
unset( $filters['__max_timestamp'] );
|
80 |
+
}
|
81 |
+
|
82 |
+
|
83 |
$limit = max( 0, min( 100, intval( $limit ) ) );
|
84 |
$page = max( 1, intval( $page ) );
|
85 |
|
177 |
}
|
178 |
|
179 |
if ( false !== $min_timestamp ) {
|
180 |
+
$where_entries[] = 'timestamp>%s';
|
181 |
$prepare_args[] = date( 'Y-m-d H:i:s', $min_timestamp );
|
182 |
}
|
183 |
|
184 |
+
if ( false !== $max_timestamp ) {
|
185 |
+
$where_entries[] = 'timestamp<%s';
|
186 |
+
$prepare_args[] = date( 'Y-m-d H:i:s', $max_timestamp );
|
187 |
+
}
|
188 |
+
|
189 |
$query .= ' WHERE ' . implode( ' AND ', $where_entries );
|
190 |
|
191 |
|
core/lib/login-interstitial/abstract-itsec-login-interstitial.php
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_Login_Interstitial
|
5 |
+
*/
|
6 |
+
abstract class ITSEC_Login_Interstitial {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Should this interstitial be shown to the given user.
|
10 |
+
*
|
11 |
+
* @param WP_User $user
|
12 |
+
* @param bool $is_requested
|
13 |
+
*
|
14 |
+
* @return bool
|
15 |
+
*/
|
16 |
+
public function show_to_user( WP_User $user, $is_requested ) {
|
17 |
+
return true;
|
18 |
+
}
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Only show this interstitial if the user logged-in via wp-login.php.
|
22 |
+
*
|
23 |
+
* @param WP_User $user
|
24 |
+
*
|
25 |
+
* @return bool
|
26 |
+
*/
|
27 |
+
public function show_on_wp_login_only( WP_User $user ) {
|
28 |
+
return false;
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Render the interstitial.
|
33 |
+
*
|
34 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
35 |
+
* @param array $args
|
36 |
+
*
|
37 |
+
* @return string
|
38 |
+
*/
|
39 |
+
abstract public function render( ITSEC_Login_Interstitial_Session $session, array $args );
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Must this interstitial be completed by the given user.
|
43 |
+
*
|
44 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
45 |
+
*
|
46 |
+
* @return bool
|
47 |
+
*/
|
48 |
+
public function is_completion_forced( ITSEC_Login_Interstitial_Session $session ) {
|
49 |
+
return true;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Is there a submit handler.
|
54 |
+
*
|
55 |
+
* @return bool
|
56 |
+
*/
|
57 |
+
public function has_submit() {
|
58 |
+
return false;
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Handle submitting the interstitial.
|
63 |
+
*
|
64 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
65 |
+
* @param array $data
|
66 |
+
*/
|
67 |
+
public function submit( ITSEC_Login_Interstitial_Session $session, array $data ) { }
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Does the interstitial have async GET actions.
|
71 |
+
*
|
72 |
+
* @return bool
|
73 |
+
*/
|
74 |
+
public function has_async_action() {
|
75 |
+
return false;
|
76 |
+
}
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Handle an async action.
|
80 |
+
*
|
81 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
82 |
+
* @param string $action
|
83 |
+
* @param array $args
|
84 |
+
*
|
85 |
+
* @return true|array|WP_Error|void
|
86 |
+
* True if success.
|
87 |
+
* Array if success with output customizations.
|
88 |
+
* WP_Error if error.
|
89 |
+
* Void/null if action not processed.
|
90 |
+
* Or display custom HTML and die.
|
91 |
+
*/
|
92 |
+
public function handle_async_action( ITSEC_Login_Interstitial_Session $session, $action, array $args ) { }
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Does the interstitial have ajax handlers.
|
96 |
+
*
|
97 |
+
* @return bool
|
98 |
+
*/
|
99 |
+
public function has_ajax_handlers() {
|
100 |
+
return false;
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Handle an ajax request.
|
105 |
+
*
|
106 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
107 |
+
* @param array $data
|
108 |
+
*/
|
109 |
+
public function handle_ajax( ITSEC_Login_Interstitial_Session $session, array $data ) { }
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Get an info message to display above the interstitial form.
|
113 |
+
*
|
114 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
115 |
+
*
|
116 |
+
* @return string
|
117 |
+
*/
|
118 |
+
public function get_info_message( ITSEC_Login_Interstitial_Session $session ) {
|
119 |
+
return '';
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Execute code after the interstitial has been submitted.
|
124 |
+
*
|
125 |
+
* @param ITSEC_Login_Interstitial_Session $session
|
126 |
+
* @param array $data
|
127 |
+
*/
|
128 |
+
public function after_submit( ITSEC_Login_Interstitial_Session $session, array $data ) { }
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Get the priority. A higher priority number is displayed later.
|
132 |
+
*
|
133 |
+
* @return int
|
134 |
+
*/
|
135 |
+
public function get_priority() {
|
136 |
+
return 5;
|
137 |
+
}
|
138 |
+
}
|
core/lib/login-interstitial/class-itsec-login-interstitial-config-driven.php
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_Login_Interstitial_Config_Driven
|
5 |
+
*/
|
6 |
+
class ITSEC_Login_Interstitial_Config_Driven extends ITSEC_Login_Interstitial {
|
7 |
+
|
8 |
+
/** @var array */
|
9 |
+
private $config;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* ITSEC_Login_Interstitial_Config_Driven constructor.
|
13 |
+
*
|
14 |
+
* @param array $config
|
15 |
+
*
|
16 |
+
* @throws InvalidArgumentException
|
17 |
+
*/
|
18 |
+
public function __construct( array $config ) {
|
19 |
+
$this->config = wp_parse_args( $config, array(
|
20 |
+
'force_completion' => true, // Will logout the user's session before displaying the interstitial.
|
21 |
+
'show_to_user' => true, // Boolean or callable.
|
22 |
+
'wp_login_only' => false, // Only show the interstitial if the login form is submitted from wp-login.php,
|
23 |
+
'submit' => false, // Callable called with user when submitting the form.
|
24 |
+
'async_action' => false, // Callable called when a user clicks a link to perform an interstitial action.
|
25 |
+
'info_message' => false,
|
26 |
+
'after_submit' => false,
|
27 |
+
'ajax_handler' => false,
|
28 |
+
'priority' => 5,
|
29 |
+
) );
|
30 |
+
|
31 |
+
if ( ! is_bool( $this->config['show_to_user'] ) && ! is_callable( $this->config['show_to_user'] ) ) {
|
32 |
+
throw new InvalidArgumentException( 'Show to user is required.' );
|
33 |
+
}
|
34 |
+
|
35 |
+
if ( ! is_bool( $this->config['force_completion'] ) && ! is_callable( $this->config['force_completion'] ) ) {
|
36 |
+
throw new InvalidArgumentException( 'Force completion is required.' );
|
37 |
+
}
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* @inheritDoc
|
42 |
+
*/
|
43 |
+
public function render( ITSEC_Login_Interstitial_Session $session, array $args ) {
|
44 |
+
call_user_func( $this->config['render'], $session->get_user(), array_merge( compact( 'session' ), $args ) );
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @inheritDoc
|
49 |
+
*/
|
50 |
+
public function show_to_user( WP_User $user, $is_requested ) {
|
51 |
+
return $this->result( $this->config['show_to_user'], array( $user, $is_requested ) );
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* @inheritDoc
|
56 |
+
*/
|
57 |
+
public function show_on_wp_login_only( WP_User $user ) {
|
58 |
+
return $this->result( $this->config['wp_login_only'], array( $user ) );
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* @inheritDoc
|
63 |
+
*/
|
64 |
+
public function is_completion_forced( ITSEC_Login_Interstitial_Session $session ) {
|
65 |
+
return $this->result( $this->config['force_completion'], $session->get_user() );
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* @inheritdoc
|
70 |
+
*/
|
71 |
+
public function has_submit() {
|
72 |
+
return (bool) $this->config['submit'];
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* @inheritDoc
|
77 |
+
*/
|
78 |
+
public function submit( ITSEC_Login_Interstitial_Session $session, array $data ) {
|
79 |
+
return call_user_func( $this->config['submit'], $session->get_user(), $data );
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* @inheritDoc
|
84 |
+
*/
|
85 |
+
public function has_async_action() {
|
86 |
+
return (bool) $this->config['async_action'];
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* @inheritDoc
|
91 |
+
*/
|
92 |
+
public function handle_async_action( ITSEC_Login_Interstitial_Session $session, $action, array $args ) {
|
93 |
+
return call_user_func( $this->config['async_action'], $session, $action, $args );
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* @inheritDoc
|
98 |
+
*/
|
99 |
+
public function has_ajax_handlers() {
|
100 |
+
return (bool) $this->config['ajax_handler'];
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* @inheritDoc
|
105 |
+
*/
|
106 |
+
public function handle_ajax( ITSEC_Login_Interstitial_Session $session, array $data ) {
|
107 |
+
call_user_func( $this->config['ajax_handler'], $session->get_user(), $data );
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* @inheritDoc
|
112 |
+
*/
|
113 |
+
public function get_info_message( ITSEC_Login_Interstitial_Session $session ) {
|
114 |
+
return $this->result( $this->config['info_message'], array( $session->get_user() ) );
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* @inheritDoc
|
119 |
+
*/
|
120 |
+
public function after_submit( ITSEC_Login_Interstitial_Session $session, array $data ) {
|
121 |
+
if ( is_callable( $this->config['after_submit'] ) ) {
|
122 |
+
call_user_func( $this->config['after_submit'], $session->get_user(), $data );
|
123 |
+
}
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* @inheritDoc
|
128 |
+
*/
|
129 |
+
public function get_priority() {
|
130 |
+
return $this->config['priority'];
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Try and get a value from the provider.
|
135 |
+
*
|
136 |
+
* If it is a function, will call the function with the provided args.
|
137 |
+
*
|
138 |
+
* @param bool|callable $provider
|
139 |
+
* @param array $args
|
140 |
+
*
|
141 |
+
* @return bool|mixed
|
142 |
+
*/
|
143 |
+
private function result( $provider, $args = array() ) {
|
144 |
+
if ( is_bool( $provider ) ) {
|
145 |
+
return $provider;
|
146 |
+
}
|
147 |
+
|
148 |
+
if ( is_callable( $provider, true ) ) {
|
149 |
+
return call_user_func_array( $provider, $args );
|
150 |
+
}
|
151 |
+
|
152 |
+
return $provider;
|
153 |
+
}
|
154 |
+
}
|
core/lib/login-interstitial/class-itsec-login-interstitial-session.php
ADDED
@@ -0,0 +1,505 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ITSEC_Login_Interstitial_Session {
|
4 |
+
|
5 |
+
const META_KEY = '_itsec_login_interstitial_state';
|
6 |
+
|
7 |
+
/** @var int */
|
8 |
+
private $id;
|
9 |
+
|
10 |
+
/** @var WP_User */
|
11 |
+
private $user;
|
12 |
+
|
13 |
+
/** @var array */
|
14 |
+
private $data;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* ITSEC_Lib_Login_Interstitial_State constructor.
|
18 |
+
*
|
19 |
+
* @param WP_User $user
|
20 |
+
* @param int $id
|
21 |
+
* @param array $data
|
22 |
+
*/
|
23 |
+
public function __construct( WP_User $user, $id, $data ) {
|
24 |
+
$this->user = $user;
|
25 |
+
$this->id = $id;
|
26 |
+
$this->data = $data;
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Set the interstitial that is currently being processed.
|
31 |
+
*
|
32 |
+
* @param string $action
|
33 |
+
*
|
34 |
+
* @return $this
|
35 |
+
*/
|
36 |
+
public function set_current_interstitial( $action ) {
|
37 |
+
$this->data['current'] = $action;
|
38 |
+
$this->data['state'] = array();
|
39 |
+
|
40 |
+
return $this;
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Get the current interstitial being processed.
|
45 |
+
*
|
46 |
+
* @return string
|
47 |
+
*/
|
48 |
+
public function get_current_interstitial() {
|
49 |
+
return $this->data['current'];
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Mark that this session completed an interstitial.
|
54 |
+
*
|
55 |
+
* @param string $action
|
56 |
+
*
|
57 |
+
* @return $this
|
58 |
+
*/
|
59 |
+
public function add_completed_interstitial( $action ) {
|
60 |
+
$this->data['completed'][] = $action;
|
61 |
+
|
62 |
+
return $this;
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Get the completed interstitials.
|
67 |
+
*
|
68 |
+
* @return string[]
|
69 |
+
*/
|
70 |
+
public function get_completed_interstitials() {
|
71 |
+
return $this->data['completed'];
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Add an interstitial to display after the user finishes all required interstitials.
|
76 |
+
*
|
77 |
+
* @param string $action
|
78 |
+
*
|
79 |
+
* @return $this
|
80 |
+
*/
|
81 |
+
public function add_show_after( $action ) {
|
82 |
+
$this->data['show_after'][] = $action;
|
83 |
+
|
84 |
+
return $this;
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Get the interstitials to display after the user finishes all required interstitials.
|
89 |
+
*
|
90 |
+
* @return string[]
|
91 |
+
*/
|
92 |
+
public function get_show_after() {
|
93 |
+
return $this->data['show_after'];
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Is remember me enabled.
|
98 |
+
*
|
99 |
+
* @return bool
|
100 |
+
*/
|
101 |
+
public function is_remember_me() {
|
102 |
+
return ! empty( $this->data['remember_me'] );
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Set the remember me value.
|
107 |
+
*
|
108 |
+
* @param bool $remember
|
109 |
+
*
|
110 |
+
* @return $this
|
111 |
+
*/
|
112 |
+
public function set_remember_me( $remember = true ) {
|
113 |
+
$this->data['remember_me'] = $remember;
|
114 |
+
|
115 |
+
return $this;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Get the redirect URI.
|
120 |
+
*
|
121 |
+
* @return string
|
122 |
+
*/
|
123 |
+
public function get_redirect_to() {
|
124 |
+
return empty( $this->data['redirect_to'] ) ? '' : $this->data['redirect_to'];
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Set the redirect URI.
|
129 |
+
*
|
130 |
+
* @param string $redirect
|
131 |
+
*
|
132 |
+
* @return $this
|
133 |
+
*/
|
134 |
+
public function set_redirect_to( $redirect ) {
|
135 |
+
$this->data['redirect_to'] = $redirect;
|
136 |
+
|
137 |
+
return $this;
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Is this an interim login.
|
142 |
+
*
|
143 |
+
* @return bool
|
144 |
+
*/
|
145 |
+
public function is_interim_login() {
|
146 |
+
return ! empty( $this->data['interim_login'] );
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* Set whether this is an interim login.
|
151 |
+
*
|
152 |
+
* @param bool $is_interim
|
153 |
+
*
|
154 |
+
* @return $this
|
155 |
+
*/
|
156 |
+
public function set_interim_login( $is_interim = true ) {
|
157 |
+
$this->data['interim_login'] = $is_interim;
|
158 |
+
|
159 |
+
return $this;
|
160 |
+
}
|
161 |
+
|
162 |
+
/**
|
163 |
+
* Get state for the current interstitial.
|
164 |
+
*
|
165 |
+
* @return array
|
166 |
+
*/
|
167 |
+
public function get_state() {
|
168 |
+
return $this->data['state'];
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Set the public state.
|
173 |
+
*
|
174 |
+
* This is only around for the duration of this interstitial.
|
175 |
+
*
|
176 |
+
* @param array $state
|
177 |
+
*
|
178 |
+
* @return $this
|
179 |
+
*/
|
180 |
+
public function set_state( array $state ) {
|
181 |
+
$this->data['state'] = $state;
|
182 |
+
|
183 |
+
return $this;
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Verify the session.
|
188 |
+
*
|
189 |
+
* @param int $user_id
|
190 |
+
* @param string $signature
|
191 |
+
*
|
192 |
+
* @return true|WP_Error
|
193 |
+
*/
|
194 |
+
public function verify( $user_id, $signature ) {
|
195 |
+
if ( $this->is_expired() ) {
|
196 |
+
return new WP_Error( 'itsec-lib-login-interstitial-verify-failed-session-expired', esc_html__( 'Session expired.', 'better-wp-security' ) );
|
197 |
+
}
|
198 |
+
|
199 |
+
$signature_verified = $this->verify_signature( $signature );
|
200 |
+
|
201 |
+
if ( is_wp_error( $signature_verified ) ) {
|
202 |
+
return $signature_verified;
|
203 |
+
}
|
204 |
+
|
205 |
+
if ( true !== $signature_verified ) {
|
206 |
+
return new WP_Error( 'itsec-lib-login-interstitial-verify-failed-invalid-signature', esc_html__( 'Invalid signature.', 'better-wp-security' ) );
|
207 |
+
}
|
208 |
+
|
209 |
+
if ( ! $user_id || $this->get_user()->ID !== $user_id ) {
|
210 |
+
return new WP_Error( 'itsec-lib-login-interstitial-verify-failed-invalid-user', esc_html__( 'Invalid user.', 'better-wp-security' ) );
|
211 |
+
}
|
212 |
+
|
213 |
+
return true;
|
214 |
+
}
|
215 |
+
|
216 |
+
/**
|
217 |
+
* Verify the session for a given payload.
|
218 |
+
*
|
219 |
+
* @param string $payload
|
220 |
+
* @param int $user_id
|
221 |
+
* @param string $signature
|
222 |
+
*
|
223 |
+
* @return true|WP_Error
|
224 |
+
*/
|
225 |
+
public function verify_for_payload( $payload, $user_id, $signature ) {
|
226 |
+
if ( $this->is_expired() ) {
|
227 |
+
return new WP_Error( 'itsec-lib-login-interstitial-verify-failed-session-expired', esc_html__( 'Session expired.', 'better-wp-security' ) );
|
228 |
+
}
|
229 |
+
|
230 |
+
$signature_verified = $this->verify_signature_for_payload( $payload, $signature );
|
231 |
+
|
232 |
+
if ( is_wp_error( $signature_verified ) ) {
|
233 |
+
return $signature_verified;
|
234 |
+
}
|
235 |
+
|
236 |
+
if ( true !== $signature_verified ) {
|
237 |
+
return new WP_Error( 'itsec-lib-login-interstitial-verify-failed-invalid-signature', esc_html__( 'Invalid signature.', 'better-wp-security' ) );
|
238 |
+
}
|
239 |
+
|
240 |
+
if ( ! $user_id || $this->get_user()->ID !== $user_id ) {
|
241 |
+
return new WP_Error( 'itsec-lib-login-interstitial-verify-failed-invalid-user', esc_html__( 'Invalid user.', 'better-wp-security' ) );
|
242 |
+
}
|
243 |
+
|
244 |
+
return true;
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Is the session expired.
|
249 |
+
*
|
250 |
+
* @return bool
|
251 |
+
*/
|
252 |
+
public function is_expired() {
|
253 |
+
return $this->data['created_at'] + HOUR_IN_SECONDS < ITSEC_Core::get_current_time_gmt();
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* Verify the signature.
|
258 |
+
*
|
259 |
+
* @param string $actual
|
260 |
+
*
|
261 |
+
* @return bool|WP_Error
|
262 |
+
*/
|
263 |
+
public function verify_signature( $actual ) {
|
264 |
+
$expected = $this->get_signature();
|
265 |
+
|
266 |
+
if ( is_wp_error( $expected ) ) {
|
267 |
+
return $expected;
|
268 |
+
}
|
269 |
+
|
270 |
+
return hash_equals( $expected, $actual );
|
271 |
+
}
|
272 |
+
|
273 |
+
/**
|
274 |
+
* Get the signature for the session state.
|
275 |
+
*
|
276 |
+
* @return string|WP_Error
|
277 |
+
*/
|
278 |
+
public function get_signature() {
|
279 |
+
$to_hash = sprintf(
|
280 |
+
'%s|%s|%s|%s',
|
281 |
+
$this->get_user()->ID,
|
282 |
+
$this->get_id(),
|
283 |
+
$this->data['created_at'],
|
284 |
+
$this->data['uuid']
|
285 |
+
);
|
286 |
+
|
287 |
+
$hash = hash_hmac( 'sha1', $to_hash, wp_salt() );
|
288 |
+
|
289 |
+
if ( ! $hash ) {
|
290 |
+
return new WP_Error( 'itsec-lib-login-interstitial-signature-failed', esc_html__( 'Could not calculate signature.', 'better-wp-security' ) );
|
291 |
+
}
|
292 |
+
|
293 |
+
return $hash;
|
294 |
+
}
|
295 |
+
|
296 |
+
/**
|
297 |
+
* Verify the signature for a given async action.
|
298 |
+
*
|
299 |
+
* @param string $payload
|
300 |
+
* @param string $actual
|
301 |
+
*
|
302 |
+
* @return bool|WP_Error
|
303 |
+
*/
|
304 |
+
public function verify_signature_for_payload( $payload, $actual ) {
|
305 |
+
$expected = $this->get_signature_for_payload( $payload );
|
306 |
+
|
307 |
+
if ( is_wp_error( $expected ) ) {
|
308 |
+
return $expected;
|
309 |
+
}
|
310 |
+
|
311 |
+
return hash_equals( $expected, $actual );
|
312 |
+
}
|
313 |
+
|
314 |
+
/**
|
315 |
+
* Get the signature for a payload.
|
316 |
+
*
|
317 |
+
* @param string $payload
|
318 |
+
*
|
319 |
+
* @return string|WP_Error
|
320 |
+
*/
|
321 |
+
public function get_signature_for_payload( $payload ) {
|
322 |
+
$to_hash = sprintf(
|
323 |
+
'%s|%s|%s|%s|%s',
|
324 |
+
$this->get_user()->ID,
|
325 |
+
$this->get_id(),
|
326 |
+
$this->data['created_at'],
|
327 |
+
$this->data['uuid'],
|
328 |
+
$payload
|
329 |
+
);
|
330 |
+
|
331 |
+
$hash = hash_hmac( 'sha1', $to_hash, wp_salt() );
|
332 |
+
|
333 |
+
if ( ! $hash ) {
|
334 |
+
return new WP_Error( 'itsec-lib-login-interstitial-signature-failed', esc_html__( 'Could not calculate signature.', 'better-wp-security' ) );
|
335 |
+
}
|
336 |
+
|
337 |
+
return $hash;
|
338 |
+
}
|
339 |
+
|
340 |
+
/**
|
341 |
+
* Was the given interstitial completed.
|
342 |
+
*
|
343 |
+
* @param string $interstitial
|
344 |
+
*
|
345 |
+
* @return bool
|
346 |
+
*/
|
347 |
+
public function is_interstitial_completed( $interstitial ) {
|
348 |
+
return in_array( $interstitial, $this->get_completed_interstitials(), true );
|
349 |
+
}
|
350 |
+
|
351 |
+
/**
|
352 |
+
* Is the given interstitial forced.
|
353 |
+
*
|
354 |
+
* @param string $interstitial
|
355 |
+
*
|
356 |
+
* @return bool
|
357 |
+
*/
|
358 |
+
public function is_interstitial_requested( $interstitial ) {
|
359 |
+
return in_array( $interstitial, $this->get_show_after(), true );
|
360 |
+
}
|
361 |
+
|
362 |
+
/**
|
363 |
+
* Is the current interstitial forced to display.
|
364 |
+
*
|
365 |
+
* @return bool
|
366 |
+
*/
|
367 |
+
public function is_current_requested() {
|
368 |
+
return $this->is_interstitial_requested( $this->get_current_interstitial() );
|
369 |
+
}
|
370 |
+
|
371 |
+
/**
|
372 |
+
* Get the session ID.
|
373 |
+
*
|
374 |
+
* @return int
|
375 |
+
*/
|
376 |
+
public function get_id() {
|
377 |
+
return $this->id;
|
378 |
+
}
|
379 |
+
|
380 |
+
/**
|
381 |
+
* Get the session's user.
|
382 |
+
*
|
383 |
+
* @return WP_User
|
384 |
+
*/
|
385 |
+
public function get_user() {
|
386 |
+
return $this->user;
|
387 |
+
}
|
388 |
+
|
389 |
+
/**
|
390 |
+
* Save the session.
|
391 |
+
*
|
392 |
+
* @return bool
|
393 |
+
*/
|
394 |
+
public function save() {
|
395 |
+
return update_metadata_by_mid( 'user', $this->get_id(), $this->data, self::META_KEY );
|
396 |
+
}
|
397 |
+
|
398 |
+
/**
|
399 |
+
* Delete the session state.
|
400 |
+
*
|
401 |
+
* @return bool
|
402 |
+
*/
|
403 |
+
public function delete() {
|
404 |
+
$deleted = delete_metadata_by_mid( 'user', $this->get_id() );
|
405 |
+
|
406 |
+
foreach ( get_user_meta( $this->get_user()->ID, self::META_KEY ) as $entry ) {
|
407 |
+
if ( ! isset( $entry['created_at'] ) || $entry['created_at'] + HOUR_IN_SECONDS < ITSEC_Core::get_current_time_gmt() ) {
|
408 |
+
delete_user_meta( $this->get_user()->ID, self::META_KEY, $entry );
|
409 |
+
}
|
410 |
+
}
|
411 |
+
|
412 |
+
return $deleted;
|
413 |
+
}
|
414 |
+
|
415 |
+
/**
|
416 |
+
* Create a new state session.
|
417 |
+
*
|
418 |
+
* @param WP_User $user The user to create the session for.
|
419 |
+
* @param string $current The current interstitial.
|
420 |
+
*
|
421 |
+
* @return ITSEC_Login_Interstitial_Session|WP_Error
|
422 |
+
*/
|
423 |
+
public static function create( WP_User $user, $current = '' ) {
|
424 |
+
|
425 |
+
$data = array(
|
426 |
+
'uuid' => wp_generate_uuid4(),
|
427 |
+
'current' => $current,
|
428 |
+
'completed' => array(),
|
429 |
+
'created_at' => ITSEC_Core::get_current_time_gmt(),
|
430 |
+
'show_after' => array(),
|
431 |
+
'redirect_to' => '',
|
432 |
+
'remember_me' => false,
|
433 |
+
'interim_login' => false,
|
434 |
+
'state' => array(),
|
435 |
+
);
|
436 |
+
|
437 |
+
if ( ! $mid = add_user_meta( $user->ID, self::META_KEY, $data ) ) {
|
438 |
+
return new WP_Error( 'itsec-lib-login-interstitial-save-failed', esc_html__( 'Failed to create interstitial state.', 'better-wp-security' ) );
|
439 |
+
}
|
440 |
+
|
441 |
+
return new self( $user, $mid, $data );
|
442 |
+
}
|
443 |
+
|
444 |
+
/**
|
445 |
+
* Get a state session.
|
446 |
+
*
|
447 |
+
* @param int $id
|
448 |
+
*
|
449 |
+
* @return ITSEC_Login_Interstitial_Session|WP_Error
|
450 |
+
*/
|
451 |
+
public static function get( $id ) {
|
452 |
+
|
453 |
+
$row = get_metadata_by_mid( 'user', $id );
|
454 |
+
|
455 |
+
if (
|
456 |
+
! $row ||
|
457 |
+
$row->meta_key !== self::META_KEY ||
|
458 |
+
! self::validate_meta( $row->meta_value ) ||
|
459 |
+
! $user = get_userdata( $row->user_id )
|
460 |
+
) {
|
461 |
+
return new WP_Error( 'itsec-lib-login-interstitial-not-found', esc_html__( 'Interstitial state not found.', 'better-wp-security' ) );
|
462 |
+
}
|
463 |
+
|
464 |
+
return new self( $user, $id, $row->meta_value );
|
465 |
+
}
|
466 |
+
|
467 |
+
/**
|
468 |
+
* Get all interstitials for a user.
|
469 |
+
*
|
470 |
+
* @param WP_User $user
|
471 |
+
*
|
472 |
+
* @return ITSEC_Login_Interstitial_Session[]
|
473 |
+
*/
|
474 |
+
public static function get_all( WP_User $user ) {
|
475 |
+
|
476 |
+
global $wpdb;
|
477 |
+
|
478 |
+
$mids = $wpdb->get_col( $wpdb->prepare(
|
479 |
+
"SELECT `umeta_id` FROM {$wpdb->usermeta} WHERE `meta_key` = %s AND `user_id` = %d",
|
480 |
+
self::META_KEY,
|
481 |
+
$user->ID
|
482 |
+
) );
|
483 |
+
|
484 |
+
$sessions = array();
|
485 |
+
|
486 |
+
foreach ( $mids as $meta_id ) {
|
487 |
+
if ( ! is_wp_error( $session = self::get( $meta_id ) ) ) {
|
488 |
+
$sessions[] = $session;
|
489 |
+
}
|
490 |
+
}
|
491 |
+
|
492 |
+
return $sessions;
|
493 |
+
}
|
494 |
+
|
495 |
+
/**
|
496 |
+
* Validate the meta value is valid.
|
497 |
+
*
|
498 |
+
* @param mixed $meta_value
|
499 |
+
*
|
500 |
+
* @return bool
|
501 |
+
*/
|
502 |
+
private static function validate_meta( $meta_value ) {
|
503 |
+
return is_array( $meta_value ) && isset( $meta_value['uuid'], $meta_value['created_at'], $meta_value['completed'], $meta_value['current'], $meta_value['show_after'] );
|
504 |
+
}
|
505 |
+
}
|
core/lib/login-interstitial/index.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php // Silence is golden.
|
core/lib/login-interstitial/util.js
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function( $ ) {
|
2 |
+
|
3 |
+
var VARS = [
|
4 |
+
'itsec_interstitial_user',
|
5 |
+
'itsec_interstitial_token',
|
6 |
+
'itsec_interstitial_session',
|
7 |
+
];
|
8 |
+
|
9 |
+
function ITSECLoginInterstitial( $el, options ) {
|
10 |
+
|
11 |
+
if ( $.isPlainObject( $el ) ) {
|
12 |
+
options = $el;
|
13 |
+
$el = null;
|
14 |
+
}
|
15 |
+
|
16 |
+
if ( !$el ) {
|
17 |
+
$( 'form' ).each( function() {
|
18 |
+
var $form = $( this );
|
19 |
+
|
20 |
+
if ( $form.attr( 'id' ).indexOf( 'itsec-' ) === 0 ) {
|
21 |
+
$el = $form;
|
22 |
+
return false;
|
23 |
+
}
|
24 |
+
} );
|
25 |
+
}
|
26 |
+
|
27 |
+
if ( !$el ) {
|
28 |
+
throw Error( 'No $el found.' );
|
29 |
+
}
|
30 |
+
|
31 |
+
this.$el = $el;
|
32 |
+
this.options = $.extend( {
|
33 |
+
checkInterval: 5000,
|
34 |
+
onStateChange: $.noop,
|
35 |
+
onProgressed : ( function() {
|
36 |
+
this.submitToProceed();
|
37 |
+
} ).bind( this ),
|
38 |
+
}, options || {} );
|
39 |
+
|
40 |
+
this.current = $el.prop( 'id' ).replace( 'itsec-', '' );
|
41 |
+
this.vars = {};
|
42 |
+
this.intervalId = null;
|
43 |
+
this.currentState = [];
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Initialize the interstitial.
|
48 |
+
*/
|
49 |
+
ITSECLoginInterstitial.prototype.init = function() {
|
50 |
+
for ( var i = 0; i < VARS.length; i++ ) {
|
51 |
+
this.vars[ VARS[ i ] ] = $( 'input[name="' + VARS[ i ] + '"]', this.$el ).val();
|
52 |
+
}
|
53 |
+
|
54 |
+
this.intervalId = setInterval( this.checkIfProgressed.bind( this ), this.options.checkInterval );
|
55 |
+
};
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Make an ajax request.
|
59 |
+
*
|
60 |
+
* @return {$.promise}
|
61 |
+
*/
|
62 |
+
ITSECLoginInterstitial.prototype.ajax = function( data ) {
|
63 |
+
return wp.ajax.post(
|
64 |
+
'itsec-login-interstitial-ajax',
|
65 |
+
$.extend( {}, this.vars, data ),
|
66 |
+
);
|
67 |
+
};
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Fetch the latest interstitial state.
|
71 |
+
*
|
72 |
+
* @return {$.promise}
|
73 |
+
*/
|
74 |
+
ITSECLoginInterstitial.prototype.fetchState = function() {
|
75 |
+
return wp.ajax.post(
|
76 |
+
'itsec-login-interstitial-ajax',
|
77 |
+
$.extend( { itsec_interstitial_get_state: true }, this.vars ),
|
78 |
+
);
|
79 |
+
};
|
80 |
+
|
81 |
+
ITSECLoginInterstitial.prototype.checkIfProgressed = function() {
|
82 |
+
this.fetchState().then( ( function( response ) {
|
83 |
+
if ( response.logged_in || response.current !== this.current ) {
|
84 |
+
this.options.onProgressed( response );
|
85 |
+
} else if ( JSON.stringify( response.state ) !== JSON.stringify( this.currentState ) ) {
|
86 |
+
this.options.onStateChange( response.state, this.currentState );
|
87 |
+
this.currentState = response.state;
|
88 |
+
}
|
89 |
+
} ).bind( this ) ).fail( ( function( response ) {
|
90 |
+
console.error( response );
|
91 |
+
clearInterval( this.intervalId );
|
92 |
+
} ).bind( this ) );
|
93 |
+
};
|
94 |
+
|
95 |
+
ITSECLoginInterstitial.prototype.submitToProceed = function() {
|
96 |
+
|
97 |
+
var $form = $( '<form />' )
|
98 |
+
.prop( 'method', 'post' )
|
99 |
+
.prop( 'action', this.$el.attr( 'action' ) )
|
100 |
+
.css( { display: 'none' } );
|
101 |
+
|
102 |
+
$form.append(
|
103 |
+
$( '<input />' )
|
104 |
+
.prop( 'type', 'hidden' )
|
105 |
+
.prop( 'name', 'action' )
|
106 |
+
.prop( 'value', 'itsec-' + this.current ),
|
107 |
+
);
|
108 |
+
|
109 |
+
for ( var i = 0; i < VARS.length; i++ ) {
|
110 |
+
$form.append(
|
111 |
+
$( '<input />' )
|
112 |
+
.prop( 'type', 'hidden' )
|
113 |
+
.prop( 'name', VARS[ i ] )
|
114 |
+
.prop( 'value', this.vars[ VARS[ i ] ] ),
|
115 |
+
);
|
116 |
+
}
|
117 |
+
|
118 |
+
$form.appendTo( document.body );
|
119 |
+
$form.submit();
|
120 |
+
};
|
121 |
+
|
122 |
+
ITSECLoginInterstitial.prototype.setOnProgressed = function( callback ) {
|
123 |
+
this.options.onProgressed = callback;
|
124 |
+
};
|
125 |
+
|
126 |
+
ITSECLoginInterstitial.prototype.setOnStateChange = function( callback ) {
|
127 |
+
this.options.onStateChange = callback;
|
128 |
+
};
|
129 |
+
|
130 |
+
window.ITSECLoginInterstitial = ITSECLoginInterstitial;
|
131 |
+
} )( jQuery );
|
core/lib/mail-templates/large-button.html
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<tr>
|
2 |
+
<td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
3 |
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
4 |
+
<tr>
|
5 |
+
<td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
6 |
+
<table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
7 |
+
<tr>
|
8 |
+
<td class="section-padding section-padding-bottom" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;padding-bottom: 20px;">
|
9 |
+
<table width="100%" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
10 |
+
<tr>
|
11 |
+
<td style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
12 |
+
<table class="module-button" border="0" cellspacing="0" cellpadding="0" align="center" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
13 |
+
<tr>
|
14 |
+
<td class="border-radius" align="center" bgcolor="{{ $bk_color }}" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;-webkit-border-radius: 5px;-moz-border-radius: 5px;border-radius: 5px;">
|
15 |
+
<a class="border-radius" href="{{ $href }}" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: {{ $txt_color }};font-family: Helvetica;font-size: 24px;line-height: 100%;text-align: center;text-decoration: none;background-color: {{ $bk_color }};border: 1px solid {{ $bk_color }};display: inline-block;font-weight: normal;padding-top: 20px;padding-right: 40px;padding-bottom: 20px;padding-left: 40px;-webkit-border-radius: 5px;-moz-border-radius: 5px;border-radius: 5px;">{{ $link_text }}</a>
|
16 |
+
</td>
|
17 |
+
</tr>
|
18 |
+
</table>
|
19 |
+
</td>
|
20 |
+
</tr>
|
21 |
+
</table>
|
22 |
+
</td>
|
23 |
+
</tr>
|
24 |
+
</table>
|
25 |
+
</td>
|
26 |
+
</tr>
|
27 |
+
</table>
|
28 |
+
</td>
|
29 |
+
</tr>
|
core/lib/mail-templates/small-code.html
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<tr>
|
2 |
+
<td class="details-box-container" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-bottom: 20px;">
|
3 |
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
4 |
+
<tr>
|
5 |
+
<td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
6 |
+
<table class="details-box container" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;background-color: #DDDDDD;border: 1px solid #DDDDDD;">
|
7 |
+
<tr>
|
8 |
+
<td class="section-padding" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 10px;padding-right: 20px;padding-left: 20px;">
|
9 |
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
10 |
+
<tr>
|
11 |
+
<td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #545454;font-family: monospace;font-size: 16px;font-weight: bolder;line-height: 150%;text-align: center;padding-bottom: 10px;">
|
12 |
+
{{ $content }}
|
13 |
+
</td>
|
14 |
+
</tr>
|
15 |
+
</table>
|
16 |
+
</td>
|
17 |
+
</tr>
|
18 |
+
</table>
|
19 |
+
</td>
|
20 |
+
</tr>
|
21 |
+
</table>
|
22 |
+
</td>
|
23 |
+
</tr>
|
core/lib/schema.php
CHANGED
@@ -113,7 +113,8 @@ CREATE TABLE {$wpdb->base_prefix}itsec_fingerprints (
|
|
113 |
PRIMARY KEY (fingerprint_id),
|
114 |
UNIQUE KEY fingerprint_user__hash (fingerprint_user,fingerprint_hash),
|
115 |
UNIQUE KEY fingerprint_uuid (fingerprint_uuid)
|
116 |
-
) $charset_collate;
|
|
|
117 |
|
118 |
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
119 |
dbDelta( $tables );
|
113 |
PRIMARY KEY (fingerprint_id),
|
114 |
UNIQUE KEY fingerprint_user__hash (fingerprint_user,fingerprint_hash),
|
115 |
UNIQUE KEY fingerprint_uuid (fingerprint_uuid)
|
116 |
+
) $charset_collate;
|
117 |
+
";
|
118 |
|
119 |
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
120 |
dbDelta( $tables );
|
core/lib/validator.php
CHANGED
@@ -185,6 +185,12 @@ abstract class ITSEC_Validator {
|
|
185 |
} else {
|
186 |
$error = sprintf( __( 'The %1$s value must be a positive integer.', 'better-wp-security' ), $name );
|
187 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
} else if ( 'email' === $type ) {
|
189 |
$this->settings[$var] = sanitize_text_field( $this->settings[$var] );
|
190 |
|
@@ -320,7 +326,7 @@ abstract class ITSEC_Validator {
|
|
320 |
}
|
321 |
} elseif ( 'canonical-roles' === $type ) {
|
322 |
$roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
|
323 |
-
|
324 |
if ( is_array( $this->settings[$var] ) ) {
|
325 |
$invalid_entries = array();
|
326 |
|
185 |
} else {
|
186 |
$error = sprintf( __( 'The %1$s value must be a positive integer.', 'better-wp-security' ), $name );
|
187 |
}
|
188 |
+
} else if ( 'number' === $type ) {
|
189 |
+
if ( is_numeric($this->settings[ $var ] ) ) {
|
190 |
+
$this->settings[ $var ] = (float) $this->settings[ $var ];
|
191 |
+
} else {
|
192 |
+
$error = sprintf( __( 'The %1$s value must be a number.', 'better-wp-security' ), $name );
|
193 |
+
}
|
194 |
} else if ( 'email' === $type ) {
|
195 |
$this->settings[$var] = sanitize_text_field( $this->settings[$var] );
|
196 |
|
326 |
}
|
327 |
} elseif ( 'canonical-roles' === $type ) {
|
328 |
$roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
|
329 |
+
|
330 |
if ( is_array( $this->settings[$var] ) ) {
|
331 |
$invalid_entries = array();
|
332 |
|
core/lockout.php
CHANGED
@@ -76,7 +76,9 @@ final class ITSEC_Lockout {
|
|
76 |
add_filter( 'authenticate', array( $this, 'check_authenticate_lockout' ), 30 );
|
77 |
|
78 |
// Updated temp whitelist to ensure that admin users are automatically added.
|
79 |
-
|
|
|
|
|
80 |
|
81 |
//Register all plugin modules
|
82 |
add_action( 'plugins_loaded', array( $this, 'register_modules' ) );
|
@@ -127,6 +129,10 @@ final class ITSEC_Lockout {
|
|
127 |
*/
|
128 |
public function check_for_host_lockouts() {
|
129 |
|
|
|
|
|
|
|
|
|
130 |
$host = ITSEC_Lib::get_ip();
|
131 |
|
132 |
if ( $this->is_host_locked_out( $host ) || ITSEC_Lib::is_ip_blacklisted() ) {
|
@@ -564,8 +570,8 @@ final class ITSEC_Lockout {
|
|
564 |
'current' => true,
|
565 |
) );
|
566 |
|
567 |
-
$where
|
568 |
-
$wheres = array();
|
569 |
|
570 |
switch ( $type ) {
|
571 |
|
@@ -591,6 +597,16 @@ final class ITSEC_Lockout {
|
|
591 |
$wheres[] = "`lockout_start_gmt` > '{$after}'";
|
592 |
}
|
593 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
594 |
if ( $wheres ) {
|
595 |
$where = ' WHERE ' . implode( ' AND ', $wheres );
|
596 |
}
|
@@ -599,15 +615,40 @@ final class ITSEC_Lockout {
|
|
599 |
$limit = ' LIMIT ' . absint( $args['limit'] );
|
600 |
}
|
601 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
602 |
if ( isset( $args['return'] ) && 'count' === $args['return'] ) {
|
603 |
$select = 'SELECT COUNT(1) as COUNT';
|
604 |
$is_count = true;
|
605 |
} else {
|
606 |
-
$select =
|
607 |
$is_count = false;
|
608 |
}
|
609 |
|
610 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
611 |
|
612 |
if ( $is_count && $results ) {
|
613 |
return $results[0]['COUNT'];
|
@@ -712,6 +753,11 @@ final class ITSEC_Lockout {
|
|
712 |
* @return bool
|
713 |
*/
|
714 |
public function is_visitor_temp_whitelisted() {
|
|
|
|
|
|
|
|
|
|
|
715 |
$whitelist = $this->get_temp_whitelist();
|
716 |
$ip = ITSEC_Lib::get_ip();
|
717 |
|
@@ -988,6 +1034,37 @@ final class ITSEC_Lockout {
|
|
988 |
$this->lockout_modules = apply_filters( 'itsec_lockout_modules', $this->lockout_modules );
|
989 |
}
|
990 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
991 |
/**
|
992 |
* Process clearing lockouts on view log page
|
993 |
*
|
@@ -1017,7 +1094,7 @@ final class ITSEC_Lockout {
|
|
1017 |
)
|
1018 |
);
|
1019 |
|
1020 |
-
return $success
|
1021 |
|
1022 |
}
|
1023 |
}
|
76 |
add_filter( 'authenticate', array( $this, 'check_authenticate_lockout' ), 30 );
|
77 |
|
78 |
// Updated temp whitelist to ensure that admin users are automatically added.
|
79 |
+
if ( ! defined( 'ITSEC_DISABLE_TEMP_WHITELIST' ) || ! ITSEC_DISABLE_TEMP_WHITELIST ) {
|
80 |
+
add_action( 'init', array( $this, 'update_temp_whitelist' ), 0 );
|
81 |
+
}
|
82 |
|
83 |
//Register all plugin modules
|
84 |
add_action( 'plugins_loaded', array( $this, 'register_modules' ) );
|
129 |
*/
|
130 |
public function check_for_host_lockouts() {
|
131 |
|
132 |
+
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
133 |
+
return;
|
134 |
+
}
|
135 |
+
|
136 |
$host = ITSEC_Lib::get_ip();
|
137 |
|
138 |
if ( $this->is_host_locked_out( $host ) || ITSEC_Lib::is_ip_blacklisted() ) {
|
570 |
'current' => true,
|
571 |
) );
|
572 |
|
573 |
+
$where = $limit = $join = $order = '';
|
574 |
+
$wheres = $prepare = array();
|
575 |
|
576 |
switch ( $type ) {
|
577 |
|
597 |
$wheres[] = "`lockout_start_gmt` > '{$after}'";
|
598 |
}
|
599 |
|
600 |
+
if ( ! empty( $args['search'] ) ) {
|
601 |
+
$search = '%' . $wpdb->esc_like( $args['search'] ) . '%';
|
602 |
+
$prepare = array_merge( $prepare, array_pad( array(), 6, $search ) );
|
603 |
+
|
604 |
+
$u = $wpdb->users;
|
605 |
+
$l = $wpdb->base_prefix . 'itsec_lockouts';
|
606 |
+
$join .= " LEFT JOIN `{$u}` ON ( `{$l}`.`lockout_user` = `{$u}`.`ID` )";
|
607 |
+
$wheres[] = "( `{$u}`.`user_login` LIKE %s OR `{$u}`.`user_email` LIKE %s OR `{$u}`.`user_nicename` LIKE %s OR `{$u}`.`display_name` LIKE %s OR `{$l}`.`lockout_username` LIKE %s or `{$l}`.`lockout_host` LIKE %s)";
|
608 |
+
}
|
609 |
+
|
610 |
if ( $wheres ) {
|
611 |
$where = ' WHERE ' . implode( ' AND ', $wheres );
|
612 |
}
|
615 |
$limit = ' LIMIT ' . absint( $args['limit'] );
|
616 |
}
|
617 |
|
618 |
+
if ( ! empty( $args['orderby'] ) ) {
|
619 |
+
$columns = array( 'lockout_id', 'lockout_start', 'lockout_expire' );
|
620 |
+
$direction = isset( $args['order'] ) ? $args['order'] : 'DESC';
|
621 |
+
|
622 |
+
if ( ! in_array( $args['orderby'], $columns, true ) ) {
|
623 |
+
_doing_it_wrong( __METHOD__, "Orderby must be one of 'lockout_id', 'lockout_start', or 'lockout_expire'.", 4109 );
|
624 |
+
|
625 |
+
return array();
|
626 |
+
}
|
627 |
+
|
628 |
+
if ( ! in_array( $direction, array( 'ASC', 'DESC' ), true ) ) {
|
629 |
+
_doing_it_wrong( __METHOD__, "Order must be one of 'ASC' or 'DESC'.", 4109 );
|
630 |
+
|
631 |
+
return array();
|
632 |
+
}
|
633 |
+
|
634 |
+
$order = " ORDER BY `{$args['orderby']}` $direction";
|
635 |
+
}
|
636 |
+
|
637 |
if ( isset( $args['return'] ) && 'count' === $args['return'] ) {
|
638 |
$select = 'SELECT COUNT(1) as COUNT';
|
639 |
$is_count = true;
|
640 |
} else {
|
641 |
+
$select = "SELECT `{$wpdb->base_prefix}itsec_lockouts`.*";
|
642 |
$is_count = false;
|
643 |
}
|
644 |
|
645 |
+
$sql = "{$select} FROM `{$wpdb->base_prefix}itsec_lockouts` {$join}{$where}{$order}{$limit};";
|
646 |
+
|
647 |
+
if ( $prepare ) {
|
648 |
+
$sql = $wpdb->prepare( $sql, $prepare );
|
649 |
+
}
|
650 |
+
|
651 |
+
$results = $wpdb->get_results( $sql, ARRAY_A );
|
652 |
|
653 |
if ( $is_count && $results ) {
|
654 |
return $results[0]['COUNT'];
|
753 |
* @return bool
|
754 |
*/
|
755 |
public function is_visitor_temp_whitelisted() {
|
756 |
+
|
757 |
+
if ( defined( 'ITSEC_DISABLE_TEMP_WHITELIST' ) && ITSEC_DISABLE_TEMP_WHITELIST ) {
|
758 |
+
return false;
|
759 |
+
}
|
760 |
+
|
761 |
$whitelist = $this->get_temp_whitelist();
|
762 |
$ip = ITSEC_Lib::get_ip();
|
763 |
|
1034 |
$this->lockout_modules = apply_filters( 'itsec_lockout_modules', $this->lockout_modules );
|
1035 |
}
|
1036 |
|
1037 |
+
/**
|
1038 |
+
* Get all the registered lockout modules.
|
1039 |
+
*
|
1040 |
+
* @return array
|
1041 |
+
*/
|
1042 |
+
public function get_lockout_modules() {
|
1043 |
+
return $this->lockout_modules;
|
1044 |
+
}
|
1045 |
+
|
1046 |
+
/**
|
1047 |
+
* Get lockout details.
|
1048 |
+
*
|
1049 |
+
* @param int $id
|
1050 |
+
*
|
1051 |
+
* @return array|false
|
1052 |
+
*/
|
1053 |
+
public function get_lockout( $id ) {
|
1054 |
+
global $wpdb;
|
1055 |
+
|
1056 |
+
$results = $wpdb->get_results( $wpdb->prepare(
|
1057 |
+
"SELECT * FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE `lockout_id` = %d",
|
1058 |
+
$id
|
1059 |
+
), ARRAY_A );
|
1060 |
+
|
1061 |
+
if ( ! is_array( $results ) || ! isset( $results[0] ) ) {
|
1062 |
+
return false;
|
1063 |
+
}
|
1064 |
+
|
1065 |
+
return $results[0];
|
1066 |
+
}
|
1067 |
+
|
1068 |
/**
|
1069 |
* Process clearing lockouts on view log page
|
1070 |
*
|
1094 |
)
|
1095 |
);
|
1096 |
|
1097 |
+
return (bool) $success;
|
1098 |
|
1099 |
}
|
1100 |
}
|
core/modules.php
CHANGED
@@ -468,7 +468,9 @@ final class ITSEC_Modules {
|
|
468 |
$was_active = $self->_active_modules[ $module_id ];
|
469 |
}
|
470 |
|
471 |
-
self::load_module_file( 'activate.php', $module_id )
|
|
|
|
|
472 |
|
473 |
$self->_active_modules[ $module_id ] = true;
|
474 |
self::set_active_modules( $self->_active_modules );
|
@@ -556,7 +558,7 @@ final class ITSEC_Modules {
|
|
556 |
* module slugs, ':all' to load the files from all modules, or ':active' to load the
|
557 |
* files from active modules.
|
558 |
*
|
559 |
-
* @return bool True if a module matching the $modules parameter is found, false otherwise.
|
560 |
*/
|
561 |
public static function load_module_file( $file, $modules = ':all' ) {
|
562 |
$self = self::get_instance();
|
@@ -580,7 +582,11 @@ final class ITSEC_Modules {
|
|
580 |
|
581 |
foreach ( $modules as $module ) {
|
582 |
if ( ! empty( $self->_module_paths[$module] ) && file_exists( "{$self->_module_paths[$module]}/{$file}" ) ) {
|
583 |
-
include_once( "{$self->_module_paths[$module]}/{$file}" );
|
|
|
|
|
|
|
|
|
584 |
}
|
585 |
}
|
586 |
|
468 |
$was_active = $self->_active_modules[ $module_id ];
|
469 |
}
|
470 |
|
471 |
+
if ( is_wp_error( $error = self::load_module_file( 'activate.php', $module_id ) ) ) {
|
472 |
+
return $error;
|
473 |
+
}
|
474 |
|
475 |
$self->_active_modules[ $module_id ] = true;
|
476 |
self::set_active_modules( $self->_active_modules );
|
558 |
* module slugs, ':all' to load the files from all modules, or ':active' to load the
|
559 |
* files from active modules.
|
560 |
*
|
561 |
+
* @return bool|WP_Error True if a module matching the $modules parameter is found, false otherwise.
|
562 |
*/
|
563 |
public static function load_module_file( $file, $modules = ':all' ) {
|
564 |
$self = self::get_instance();
|
582 |
|
583 |
foreach ( $modules as $module ) {
|
584 |
if ( ! empty( $self->_module_paths[$module] ) && file_exists( "{$self->_module_paths[$module]}/{$file}" ) ) {
|
585 |
+
$returned = include_once( "{$self->_module_paths[$module]}/{$file}" );
|
586 |
+
|
587 |
+
if ( is_wp_error( $returned ) ) {
|
588 |
+
return $returned;
|
589 |
+
}
|
590 |
}
|
591 |
}
|
592 |
|
core/modules/404-detection/class-itsec-four-oh-four.php
CHANGED
@@ -29,14 +29,14 @@ class ITSEC_Four_Oh_Four {
|
|
29 |
|
30 |
$uri = explode( '?', $_SERVER['REQUEST_URI'] );
|
31 |
|
32 |
-
if (
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
if ( ! in_array( '.' . pathinfo( $uri[0], PATHINFO_EXTENSION ), $this->settings['types'], true ) ) {
|
39 |
$itsec_lockout->do_lockout( 'four_oh_four' );
|
|
|
|
|
40 |
}
|
41 |
}
|
42 |
|
29 |
|
30 |
$uri = explode( '?', $_SERVER['REQUEST_URI'] );
|
31 |
|
32 |
+
if (
|
33 |
+
! in_array( '/' . ITSEC_Lib::get_request_path(), $this->settings['white_list'], true ) &&
|
34 |
+
! in_array( '.' . pathinfo( $uri[0], PATHINFO_EXTENSION ), $this->settings['types'], true )
|
35 |
+
) {
|
36 |
+
ITSEC_Log::add_notice( 'four_oh_four', 'found_404', array( 'SERVER' => $_SERVER ) );
|
|
|
|
|
37 |
$itsec_lockout->do_lockout( 'four_oh_four' );
|
38 |
+
} else {
|
39 |
+
do_action( 'itsec_four_oh_four_whitelisted', $uri );
|
40 |
}
|
41 |
}
|
42 |
|
core/modules/backup/class-itsec-backup.php
CHANGED
@@ -65,13 +65,13 @@ class ITSEC_Backup {
|
|
65 |
/**
|
66 |
* Public function to get lock and call backup.
|
67 |
*
|
68 |
-
* Attempts to get a lock to prevent
|
69 |
*
|
70 |
* @since 4.0.0
|
71 |
*
|
72 |
* @param boolean $one_time whether this is a one time backup
|
73 |
*
|
74 |
-
* @return
|
75 |
*/
|
76 |
public function do_backup( $one_time = false ) {
|
77 |
|
@@ -79,21 +79,25 @@ class ITSEC_Backup {
|
|
79 |
return new WP_Error( 'itsec-backup-do-backup-already-running', __( 'Unable to create a backup at this time since a backup is currently being created. If you wish to create an additional backup, please wait a few minutes before trying again.', 'better-wp-security' ) );
|
80 |
}
|
81 |
|
82 |
-
|
83 |
ITSEC_Lib::set_minimum_memory_limit( '256M' );
|
84 |
-
$this->execute_backup( $one_time );
|
85 |
ITSEC_Lib::release_lock( 'backup' );
|
86 |
|
87 |
switch ( $this->settings['method'] ) {
|
88 |
-
|
89 |
case 0:
|
90 |
-
|
|
|
91 |
case 1:
|
92 |
-
|
|
|
93 |
default:
|
94 |
-
|
95 |
-
|
96 |
}
|
|
|
|
|
|
|
|
|
97 |
}
|
98 |
|
99 |
/**
|
@@ -105,13 +109,11 @@ class ITSEC_Backup {
|
|
105 |
*
|
106 |
* @param bool $one_time whether this is a one-time backup
|
107 |
*
|
108 |
-
* @return
|
109 |
*/
|
110 |
private function execute_backup( $one_time = false ) {
|
111 |
global $wpdb;
|
112 |
|
113 |
-
|
114 |
-
|
115 |
require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-lib-directory.php' );
|
116 |
|
117 |
$dir = $this->settings['location'];
|
@@ -226,6 +228,14 @@ class ITSEC_Backup {
|
|
226 |
$mail_success = null;
|
227 |
}
|
228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
if ( 1 === $this->settings['method'] ) {
|
230 |
@unlink( $file );
|
231 |
} else if ( $this->settings['retain'] > 0 ) {
|
@@ -248,13 +258,6 @@ class ITSEC_Backup {
|
|
248 |
}
|
249 |
}
|
250 |
|
251 |
-
|
252 |
-
$log_data = array(
|
253 |
-
'settings' => $this->settings,
|
254 |
-
'mail_success' => $mail_success,
|
255 |
-
'file' => $backup_file,
|
256 |
-
);
|
257 |
-
|
258 |
if ( 0 === $this->settings['method'] ) {
|
259 |
if ( false === $mail_success ) {
|
260 |
ITSEC_Log::add_warning( 'backup', 'email-failed-file-stored', $log_data );
|
@@ -270,6 +273,8 @@ class ITSEC_Backup {
|
|
270 |
} else {
|
271 |
ITSEC_Log::add_notice( 'backup', 'file-stored', $log_data );
|
272 |
}
|
|
|
|
|
273 |
}
|
274 |
|
275 |
private function send_mail( $file ) {
|
65 |
/**
|
66 |
* Public function to get lock and call backup.
|
67 |
*
|
68 |
+
* Attempts to get a lock to prevent concurrent backups and calls the backup function itself.
|
69 |
*
|
70 |
* @since 4.0.0
|
71 |
*
|
72 |
* @param boolean $one_time whether this is a one time backup
|
73 |
*
|
74 |
+
* @return array|WP_Error false on error or nothing
|
75 |
*/
|
76 |
public function do_backup( $one_time = false ) {
|
77 |
|
79 |
return new WP_Error( 'itsec-backup-do-backup-already-running', __( 'Unable to create a backup at this time since a backup is currently being created. If you wish to create an additional backup, please wait a few minutes before trying again.', 'better-wp-security' ) );
|
80 |
}
|
81 |
|
|
|
82 |
ITSEC_Lib::set_minimum_memory_limit( '256M' );
|
83 |
+
$result = $this->execute_backup( $one_time );
|
84 |
ITSEC_Lib::release_lock( 'backup' );
|
85 |
|
86 |
switch ( $this->settings['method'] ) {
|
|
|
87 |
case 0:
|
88 |
+
$message = __( 'Backup complete. The backup was sent to the selected email recipients and was saved locally.', 'better-wp-security' );
|
89 |
+
break;
|
90 |
case 1:
|
91 |
+
$message = __( 'Backup complete. The backup was sent to the selected email recipients.', 'better-wp-security' );
|
92 |
+
break;
|
93 |
default:
|
94 |
+
$message = __( 'Backup complete. The backup was saved locally.', 'better-wp-security' );
|
95 |
+
break;
|
96 |
}
|
97 |
+
|
98 |
+
$result['message'] = $message;
|
99 |
+
|
100 |
+
return $result;
|
101 |
}
|
102 |
|
103 |
/**
|
109 |
*
|
110 |
* @param bool $one_time whether this is a one-time backup
|
111 |
*
|
112 |
+
* @return array|WP_Error
|
113 |
*/
|
114 |
private function execute_backup( $one_time = false ) {
|
115 |
global $wpdb;
|
116 |
|
|
|
|
|
117 |
require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-lib-directory.php' );
|
118 |
|
119 |
$dir = $this->settings['location'];
|
228 |
$mail_success = null;
|
229 |
}
|
230 |
|
231 |
+
$log_data = array(
|
232 |
+
'settings' => $this->settings,
|
233 |
+
'mail_success' => $mail_success,
|
234 |
+
'file' => $backup_file,
|
235 |
+
'output_file' => $file,
|
236 |
+
'size' => @filesize( $file ),
|
237 |
+
);
|
238 |
+
|
239 |
if ( 1 === $this->settings['method'] ) {
|
240 |
@unlink( $file );
|
241 |
} else if ( $this->settings['retain'] > 0 ) {
|
258 |
}
|
259 |
}
|
260 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
if ( 0 === $this->settings['method'] ) {
|
262 |
if ( false === $mail_success ) {
|
263 |
ITSEC_Log::add_warning( 'backup', 'email-failed-file-stored', $log_data );
|
273 |
} else {
|
274 |
ITSEC_Log::add_notice( 'backup', 'file-stored', $log_data );
|
275 |
}
|
276 |
+
|
277 |
+
return $log_data;
|
278 |
}
|
279 |
|
280 |
private function send_mail( $file ) {
|
core/modules/backup/settings-page.php
CHANGED
@@ -47,8 +47,8 @@ final class ITSEC_Backup_Settings_Page extends ITSEC_Module_Settings_Page {
|
|
47 |
foreach ( $errors as $error ) {
|
48 |
$message .= '<div class="error inline"><p><strong>' . $error . '</strong></p></div>';
|
49 |
}
|
50 |
-
} else if (
|
51 |
-
$message = '<div class="updated fade inline"><p><strong>' . $result . '</strong></p></div>';
|
52 |
} else {
|
53 |
$message = '<div class="error inline"><p><strong>' . sprintf( __( 'The backup request returned an unexpected response. It returned a response of type <code>%1$s</code>.', 'better-wp-security' ), gettype( $result ) ) . '</strong></p></div>';
|
54 |
}
|
47 |
foreach ( $errors as $error ) {
|
48 |
$message .= '<div class="error inline"><p><strong>' . $error . '</strong></p></div>';
|
49 |
}
|
50 |
+
} else if ( is_array( $result ) ) {
|
51 |
+
$message = '<div class="updated fade inline"><p><strong>' . $result['message'] . '</strong></p></div>';
|
52 |
} else {
|
53 |
$message = '<div class="error inline"><p><strong>' . sprintf( __( 'The backup request returned an unexpected response. It returned a response of type <code>%1$s</code>.', 'better-wp-security' ), gettype( $result ) ) . '</strong></p></div>';
|
54 |
}
|
core/modules/ban-users/lists/hackrepair-apache.inc
CHANGED
@@ -153,7 +153,6 @@ RewriteCond %{HTTP_USER_AGENT} "PHPCrawl" [NC,OR]
|
|
153 |
RewriteCond %{HTTP_USER_AGENT} "PleaseCrawl" [NC,OR]
|
154 |
RewriteCond %{HTTP_USER_AGENT} "SBIder" [NC,OR]
|
155 |
RewriteCond %{HTTP_USER_AGENT} "SearchmetricsBot" [NC,OR]
|
156 |
-
RewriteCond %{HTTP_USER_AGENT} "SeznamBot" [NC,OR]
|
157 |
RewriteCond %{HTTP_USER_AGENT} "Snoopy" [NC,OR]
|
158 |
RewriteCond %{HTTP_USER_AGENT} "Steeler" [NC,OR]
|
159 |
RewriteCond %{HTTP_USER_AGENT} "URI\:\:Fetch" [NC,OR]
|
153 |
RewriteCond %{HTTP_USER_AGENT} "PleaseCrawl" [NC,OR]
|
154 |
RewriteCond %{HTTP_USER_AGENT} "SBIder" [NC,OR]
|
155 |
RewriteCond %{HTTP_USER_AGENT} "SearchmetricsBot" [NC,OR]
|
|
|
156 |
RewriteCond %{HTTP_USER_AGENT} "Snoopy" [NC,OR]
|
157 |
RewriteCond %{HTTP_USER_AGENT} "Steeler" [NC,OR]
|
158 |
RewriteCond %{HTTP_USER_AGENT} "URI\:\:Fetch" [NC,OR]
|
core/modules/ban-users/lists/hackrepair-litespeed.inc
CHANGED
@@ -153,7 +153,6 @@ RewriteCond %{HTTP_USER_AGENT} "PHPCrawl" [NC,OR]
|
|
153 |
RewriteCond %{HTTP_USER_AGENT} "PleaseCrawl" [NC,OR]
|
154 |
RewriteCond %{HTTP_USER_AGENT} "SBIder" [NC,OR]
|
155 |
RewriteCond %{HTTP_USER_AGENT} "SearchmetricsBot" [NC,OR]
|
156 |
-
RewriteCond %{HTTP_USER_AGENT} "SeznamBot" [NC,OR]
|
157 |
RewriteCond %{HTTP_USER_AGENT} "Snoopy" [NC,OR]
|
158 |
RewriteCond %{HTTP_USER_AGENT} "Steeler" [NC,OR]
|
159 |
RewriteCond %{HTTP_USER_AGENT} "URI\:\:Fetch" [NC,OR]
|
153 |
RewriteCond %{HTTP_USER_AGENT} "PleaseCrawl" [NC,OR]
|
154 |
RewriteCond %{HTTP_USER_AGENT} "SBIder" [NC,OR]
|
155 |
RewriteCond %{HTTP_USER_AGENT} "SearchmetricsBot" [NC,OR]
|
|
|
156 |
RewriteCond %{HTTP_USER_AGENT} "Snoopy" [NC,OR]
|
157 |
RewriteCond %{HTTP_USER_AGENT} "Steeler" [NC,OR]
|
158 |
RewriteCond %{HTTP_USER_AGENT} "URI\:\:Fetch" [NC,OR]
|
core/modules/ban-users/lists/hackrepair-nginx.inc
CHANGED
@@ -152,7 +152,6 @@ if ($http_user_agent ~* "PHPCrawl"){return 403;}
|
|
152 |
if ($http_user_agent ~* "PleaseCrawl"){return 403;}
|
153 |
if ($http_user_agent ~* "SBIder"){return 403;}
|
154 |
if ($http_user_agent ~* "SearchmetricsBot"){return 403;}
|
155 |
-
if ($http_user_agent ~* "SeznamBot"){return 403;}
|
156 |
if ($http_user_agent ~* "Snoopy"){return 403;}
|
157 |
if ($http_user_agent ~* "Steeler"){return 403;}
|
158 |
if ($http_user_agent ~* "URI\:\:Fetch"){return 403;}
|
152 |
if ($http_user_agent ~* "PleaseCrawl"){return 403;}
|
153 |
if ($http_user_agent ~* "SBIder"){return 403;}
|
154 |
if ($http_user_agent ~* "SearchmetricsBot"){return 403;}
|
|
|
155 |
if ($http_user_agent ~* "Snoopy"){return 403;}
|
156 |
if ($http_user_agent ~* "Steeler"){return 403;}
|
157 |
if ($http_user_agent ~* "URI\:\:Fetch"){return 403;}
|
core/modules/brute-force/class-itsec-brute-force.php
CHANGED
@@ -25,7 +25,7 @@ class ITSEC_Brute_Force {
|
|
25 |
* @param string $username username attempted
|
26 |
* @param string $password password attempted
|
27 |
*
|
28 |
-
* @return
|
29 |
*/
|
30 |
public function authenticate( $user, $username = '', $password = '' ) {
|
31 |
/** @var ITSEC_Lockout $itsec_lockout */
|
@@ -48,6 +48,7 @@ class ITSEC_Brute_Force {
|
|
48 |
$itsec_lockout->do_lockout( 'brute_force_admin_user', $username );
|
49 |
} else {
|
50 |
$user_id = false;
|
|
|
51 |
|
52 |
if ( empty( $username ) ) {
|
53 |
$itsec_lockout->check_lockout( false, false, 'brute_force_empty_username' );
|
@@ -56,12 +57,14 @@ class ITSEC_Brute_Force {
|
|
56 |
|
57 |
if ( empty( $user_id ) ) {
|
58 |
$itsec_lockout->check_lockout( false, $username, 'brute_force_invalid_username' );
|
|
|
59 |
} else {
|
60 |
$itsec_lockout->check_lockout( $user_id, false, 'brute_force_invalid_password' );
|
|
|
61 |
}
|
62 |
}
|
63 |
|
64 |
-
ITSEC_Log::add_notice( 'brute_force',
|
65 |
|
66 |
$itsec_lockout->do_lockout( 'brute_force', $username );
|
67 |
}
|
25 |
* @param string $username username attempted
|
26 |
* @param string $password password attempted
|
27 |
*
|
28 |
+
* @return WP_User|WP_Error|null
|
29 |
*/
|
30 |
public function authenticate( $user, $username = '', $password = '' ) {
|
31 |
/** @var ITSEC_Lockout $itsec_lockout */
|
48 |
$itsec_lockout->do_lockout( 'brute_force_admin_user', $username );
|
49 |
} else {
|
50 |
$user_id = false;
|
51 |
+
$code = 'invalid-login';
|
52 |
|
53 |
if ( empty( $username ) ) {
|
54 |
$itsec_lockout->check_lockout( false, false, 'brute_force_empty_username' );
|
57 |
|
58 |
if ( empty( $user_id ) ) {
|
59 |
$itsec_lockout->check_lockout( false, $username, 'brute_force_invalid_username' );
|
60 |
+
$code = "invalid-login::username-{$username}";
|
61 |
} else {
|
62 |
$itsec_lockout->check_lockout( $user_id, false, 'brute_force_invalid_password' );
|
63 |
+
$code = "invalid-login::user-{$user_id}";
|
64 |
}
|
65 |
}
|
66 |
|
67 |
+
ITSEC_Log::add_notice( 'brute_force', $code, compact( 'details', 'user', 'username', 'user_id', 'SERVER' ) );
|
68 |
|
69 |
$itsec_lockout->do_lockout( 'brute_force', $username );
|
70 |
}
|
core/modules/brute-force/logs.php
CHANGED
@@ -2,16 +2,17 @@
|
|
2 |
|
3 |
final class ITSEC_Brute_Force_Logs {
|
4 |
public function __construct() {
|
5 |
-
add_filter( 'itsec_logs_prepare_brute_force_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ) );
|
6 |
add_filter( 'itsec_logs_prepare_brute_force_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
|
|
|
7 |
}
|
8 |
|
9 |
-
public function filter_entry_for_list_display( $entry ) {
|
10 |
$entry['module_display'] = esc_html__( 'Brute Force', 'better-wp-security' );
|
11 |
|
12 |
-
if ( 'invalid-login' === $
|
13 |
$entry['description'] = esc_html__( 'Invalid Login', 'better-wp-security' );
|
14 |
-
} else if ( 'auto-ban-admin-username' === $
|
15 |
$entry['description'] = esc_html__( 'Banned Use of "admin" Username', 'better-wp-security' );
|
16 |
}
|
17 |
|
@@ -43,5 +44,13 @@ final class ITSEC_Brute_Force_Logs {
|
|
43 |
|
44 |
return $details;
|
45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
}
|
47 |
new ITSEC_Brute_Force_Logs();
|
2 |
|
3 |
final class ITSEC_Brute_Force_Logs {
|
4 |
public function __construct() {
|
5 |
+
add_filter( 'itsec_logs_prepare_brute_force_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ), 10, 2 );
|
6 |
add_filter( 'itsec_logs_prepare_brute_force_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
|
7 |
+
add_filter( 'itsec_logs_prepare_brute_force_filter_row_action_for_code', array( $this, 'code_row_action' ), 10, 4 );
|
8 |
}
|
9 |
|
10 |
+
public function filter_entry_for_list_display( $entry, $code ) {
|
11 |
$entry['module_display'] = esc_html__( 'Brute Force', 'better-wp-security' );
|
12 |
|
13 |
+
if ( 'invalid-login' === $code ) {
|
14 |
$entry['description'] = esc_html__( 'Invalid Login', 'better-wp-security' );
|
15 |
+
} else if ( 'auto-ban-admin-username' === $code ) {
|
16 |
$entry['description'] = esc_html__( 'Banned Use of "admin" Username', 'better-wp-security' );
|
17 |
}
|
18 |
|
44 |
|
45 |
return $details;
|
46 |
}
|
47 |
+
|
48 |
+
public function code_row_action( $vars, $entry, $code, $data ) {
|
49 |
+
if ( 'invalid-login' === $code ) {
|
50 |
+
$vars = array( 'filters[10]' => 'code|invalid-login%' );
|
51 |
+
}
|
52 |
+
|
53 |
+
return $vars;
|
54 |
+
}
|
55 |
}
|
56 |
new ITSEC_Brute_Force_Logs();
|
core/modules/file-change/logs.php
CHANGED
@@ -56,7 +56,11 @@ final class ITSEC_File_Change_Logs {
|
|
56 |
$entry['description'] = esc_html__( 'Scan Failed', 'better-wp-security' );
|
57 |
}
|
58 |
} elseif ( 'rescheduling' === $code ) {
|
59 |
-
$
|
|
|
|
|
|
|
|
|
60 |
}
|
61 |
|
62 |
$entry['remote_ip'] = '';
|
56 |
$entry['description'] = esc_html__( 'Scan Failed', 'better-wp-security' );
|
57 |
}
|
58 |
} elseif ( 'rescheduling' === $code ) {
|
59 |
+
if ( isset( $code_data[0] ) && 'no-lock' === $code_data[0] ) {
|
60 |
+
$entry['description'] = esc_html__( 'Rescheduling: No Lock', 'better-wp-security' );
|
61 |
+
} else {
|
62 |
+
$entry['description'] = esc_html__( 'Rescheduling', 'better-wp-security' );
|
63 |
+
}
|
64 |
}
|
65 |
|
66 |
$entry['remote_ip'] = '';
|
core/modules/file-change/scanner.php
CHANGED
@@ -130,21 +130,59 @@ class ITSEC_File_Change_Scanner {
|
|
130 |
*/
|
131 |
public static function is_running( $scheduler = null, $user_initiated = null ) {
|
132 |
|
|
|
|
|
|
|
133 |
$scheduler = $scheduler ? $scheduler : ITSEC_Core::get_scheduler();
|
|
|
134 |
|
135 |
-
if (
|
136 |
-
if ( $
|
137 |
-
return true;
|
138 |
-
}
|
139 |
-
} elseif ( false === $user_initiated ) {
|
140 |
-
if ( $scheduler->is_single_scheduled( 'file-change' ) ) {
|
141 |
return true;
|
142 |
}
|
143 |
-
|
144 |
-
return $
|
|
|
|
|
|
|
|
|
145 |
}
|
146 |
|
147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
}
|
149 |
|
150 |
/**
|
@@ -251,7 +289,7 @@ class ITSEC_File_Change_Scanner {
|
|
251 |
*/
|
252 |
public static function recover() {
|
253 |
|
254 |
-
if ( ! ITSEC_Lib::get_lock( 'file-change
|
255 |
ITSEC_Log::add_debug( 'file_change', 'skipping-recovery::no-lock' );
|
256 |
|
257 |
return false;
|
@@ -260,6 +298,7 @@ class ITSEC_File_Change_Scanner {
|
|
260 |
$storage = ITSEC_File_Change::make_progress_storage();
|
261 |
|
262 |
if ( $storage->is_empty() ) {
|
|
|
263 |
ITSEC_Log::add_debug( 'file_change', 'skipping-recovery::empty-storage', array(
|
264 |
'backtrace' => debug_backtrace()
|
265 |
) );
|
@@ -286,7 +325,7 @@ class ITSEC_File_Change_Scanner {
|
|
286 |
|
287 |
self::abort();
|
288 |
|
289 |
-
ITSEC_Lib::release_lock( 'file-change
|
290 |
|
291 |
return false;
|
292 |
}
|
@@ -300,7 +339,7 @@ class ITSEC_File_Change_Scanner {
|
|
300 |
|
301 |
self::abort();
|
302 |
|
303 |
-
ITSEC_Lib::release_lock( 'file-change
|
304 |
|
305 |
return false;
|
306 |
}
|
@@ -312,7 +351,7 @@ class ITSEC_File_Change_Scanner {
|
|
312 |
|
313 |
self::abort();
|
314 |
|
315 |
-
ITSEC_Lib::release_lock( 'file-change
|
316 |
|
317 |
return false;
|
318 |
}
|
@@ -320,7 +359,7 @@ class ITSEC_File_Change_Scanner {
|
|
320 |
$job->reschedule_in( 30 );
|
321 |
|
322 |
ITSEC_Log::add_debug( 'file_change', 'recovery-scheduled', compact( 'job' ) );
|
323 |
-
ITSEC_Lib::release_lock( 'file-change
|
324 |
|
325 |
return true;
|
326 |
}
|
@@ -379,7 +418,15 @@ class ITSEC_File_Change_Scanner {
|
|
379 |
return;
|
380 |
}
|
381 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
382 |
if ( ! $this->allow_to_run( $job ) ) {
|
|
|
383 |
ITSEC_Log::add_debug( 'file_change', 'rescheduling', array( 'job' => $data, 'id' => $job->get_id() ) );
|
384 |
$job->reschedule_in( 10 * MINUTE_IN_SECONDS );
|
385 |
|
@@ -426,6 +473,8 @@ class ITSEC_File_Change_Scanner {
|
|
426 |
}
|
427 |
|
428 |
if ( $this->get_storage()->is_empty() ) {
|
|
|
|
|
429 |
return;
|
430 |
}
|
431 |
|
@@ -439,6 +488,8 @@ class ITSEC_File_Change_Scanner {
|
|
439 |
$this->get_storage()->set( 'memory', $memory_used );
|
440 |
$this->get_storage()->set( 'memory_peak', $check_memory );
|
441 |
}
|
|
|
|
|
442 |
}
|
443 |
|
444 |
/**
|
130 |
*/
|
131 |
public static function is_running( $scheduler = null, $user_initiated = null ) {
|
132 |
|
133 |
+
$storage = ITSEC_File_Change::make_progress_storage();
|
134 |
+
$id = $storage->get( 'id' );
|
135 |
+
|
136 |
$scheduler = $scheduler ? $scheduler : ITSEC_Core::get_scheduler();
|
137 |
+
$scheduled = self::is_scheduled( $scheduler, $user_initiated );
|
138 |
|
139 |
+
if ( null === $user_initiated ) {
|
140 |
+
if ( ! $storage->is_empty() ) {
|
|
|
|
|
|
|
|
|
141 |
return true;
|
142 |
}
|
143 |
+
|
144 |
+
return $scheduled === 'user';
|
145 |
+
}
|
146 |
+
|
147 |
+
if ( true === $user_initiated ) {
|
148 |
+
return 'user' === $scheduled || $id === 'file-change-fast';
|
149 |
}
|
150 |
|
151 |
+
if ( false === $user_initiated ) {
|
152 |
+
return 'scheduled' === $scheduled || $id === 'file-change';
|
153 |
+
}
|
154 |
+
|
155 |
+
return false;
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Is there a scan scheduled.
|
160 |
+
*
|
161 |
+
* @param ITSEC_Scheduler $scheduler The scheduler to use.
|
162 |
+
* @param bool $user_initiated Whether the user initiated scan is running or the scheduled loop scan.
|
163 |
+
* Null to check either.
|
164 |
+
*
|
165 |
+
* @return bool Is it scheduled.
|
166 |
+
*/
|
167 |
+
private static function is_scheduled( $scheduler, $user_initiated = null ) {
|
168 |
+
|
169 |
+
if ( true === $user_initiated ) {
|
170 |
+
return $scheduler->is_single_scheduled( 'file-change-fast', null ) ? 'user' : false;
|
171 |
+
}
|
172 |
+
|
173 |
+
if ( false === $user_initiated ) {
|
174 |
+
return $scheduler->is_single_scheduled( 'file-change', null ) ? 'scheduled' : false;
|
175 |
+
}
|
176 |
+
|
177 |
+
if ( $scheduler->is_single_scheduled( 'file-change-fast', null ) ) {
|
178 |
+
return 'user';
|
179 |
+
}
|
180 |
+
|
181 |
+
if ( $scheduler->is_single_scheduled( 'file-change', null ) ) {
|
182 |
+
return 'scheduled';
|
183 |
+
}
|
184 |
+
|
185 |
+
return false;
|
186 |
}
|
187 |
|
188 |
/**
|
289 |
*/
|
290 |
public static function recover() {
|
291 |
|
292 |
+
if ( ! ITSEC_Lib::get_lock( 'file-change' ) ) {
|
293 |
ITSEC_Log::add_debug( 'file_change', 'skipping-recovery::no-lock' );
|
294 |
|
295 |
return false;
|
298 |
$storage = ITSEC_File_Change::make_progress_storage();
|
299 |
|
300 |
if ( $storage->is_empty() ) {
|
301 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
302 |
ITSEC_Log::add_debug( 'file_change', 'skipping-recovery::empty-storage', array(
|
303 |
'backtrace' => debug_backtrace()
|
304 |
) );
|
325 |
|
326 |
self::abort();
|
327 |
|
328 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
329 |
|
330 |
return false;
|
331 |
}
|
339 |
|
340 |
self::abort();
|
341 |
|
342 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
343 |
|
344 |
return false;
|
345 |
}
|
351 |
|
352 |
self::abort();
|
353 |
|
354 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
355 |
|
356 |
return false;
|
357 |
}
|
359 |
$job->reschedule_in( 30 );
|
360 |
|
361 |
ITSEC_Log::add_debug( 'file_change', 'recovery-scheduled', compact( 'job' ) );
|
362 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
363 |
|
364 |
return true;
|
365 |
}
|
418 |
return;
|
419 |
}
|
420 |
|
421 |
+
if ( ! ITSEC_Lib::get_lock( 'file-change', 5 * MINUTE_IN_SECONDS ) ) {
|
422 |
+
ITSEC_Log::add_debug( 'file_change', 'rescheduling::no-lock', array( 'job' => $data, 'id' => $job->get_id() ) );
|
423 |
+
$job->reschedule_in( 2 * MINUTE_IN_SECONDS );
|
424 |
+
|
425 |
+
return;
|
426 |
+
}
|
427 |
+
|
428 |
if ( ! $this->allow_to_run( $job ) ) {
|
429 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
430 |
ITSEC_Log::add_debug( 'file_change', 'rescheduling', array( 'job' => $data, 'id' => $job->get_id() ) );
|
431 |
$job->reschedule_in( 10 * MINUTE_IN_SECONDS );
|
432 |
|
473 |
}
|
474 |
|
475 |
if ( $this->get_storage()->is_empty() ) {
|
476 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
477 |
+
|
478 |
return;
|
479 |
}
|
480 |
|
488 |
$this->get_storage()->set( 'memory', $memory_used );
|
489 |
$this->get_storage()->set( 'memory_peak', $check_memory );
|
490 |
}
|
491 |
+
|
492 |
+
ITSEC_Lib::release_lock( 'file-change' );
|
493 |
}
|
494 |
|
495 |
/**
|
core/modules/global/logs.php
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Class ITSEC_Global_Logs
|
5 |
+
*/
|
6 |
+
class ITSEC_Global_Logs {
|
7 |
+
|
8 |
+
public function __construct() {
|
9 |
+
add_filter( 'itsec_logs_prepare_core_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ), 10, 3 );
|
10 |
+
add_filter( 'itsec_logs_prepare_core_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
|
11 |
+
add_filter( 'itsec_logs_prepare_core_filter_row_action_for_code', array( $this, 'code_row_action' ), 10, 4 );
|
12 |
+
}
|
13 |
+
|
14 |
+
public function filter_entry_for_list_display( $entry, $code, $data ) {
|
15 |
+
$entry['module_display'] = esc_html__( 'Core', 'better-wp-security' );
|
16 |
+
|
17 |
+
|
18 |
+
if ( $description = $this->get_description( $entry, $code, $data ) ) {
|
19 |
+
$entry['description'] = $description;
|
20 |
+
}
|
21 |
+
|
22 |
+
return $entry;
|
23 |
+
}
|
24 |
+
|
25 |
+
public function filter_entry_for_details_display( $details, $entry, $code, $code_data ) {
|
26 |
+
$details['module']['content'] = esc_html__( 'Core', 'better-wp-security' );
|
27 |
+
|
28 |
+
if ( $description = $this->get_description( $entry, $code, $code_data ) ) {
|
29 |
+
$details['description']['content'] = $description;
|
30 |
+
}
|
31 |
+
|
32 |
+
return $details;
|
33 |
+
}
|
34 |
+
|
35 |
+
public function code_row_action( $vars, $entry, $code, $data ) {
|
36 |
+
|
37 |
+
return $vars;
|
38 |
+
}
|
39 |
+
|
40 |
+
private function get_description( $entry, $code, $data ) {
|
41 |
+
switch ( $code ) {
|
42 |
+
case 'itsec-config-file-update-empty':
|
43 |
+
list( $type ) = $data;
|
44 |
+
|
45 |
+
return sprintf( esc_html__( 'Empty file encountered when attempting to update %s config file.', 'better-wp-security' ), '<code>' . esc_html( $type ) . '</code>' );
|
46 |
+
}
|
47 |
+
|
48 |
+
return null;
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
new ITSEC_Global_Logs();
|
core/modules/global/settings.php
CHANGED
@@ -37,6 +37,7 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
|
|
37 |
'use_cron' => true,
|
38 |
'cron_test_time' => 0,
|
39 |
'enable_grade_report' => false,
|
|
|
40 |
);
|
41 |
}
|
42 |
|
37 |
'use_cron' => true,
|
38 |
'cron_test_time' => 0,
|
39 |
'enable_grade_report' => false,
|
40 |
+
'server_ips' => array(),
|
41 |
);
|
42 |
}
|
43 |
|
core/modules/global/validator.php
CHANGED
@@ -19,8 +19,8 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
|
|
19 |
}
|
20 |
|
21 |
|
22 |
-
$this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'show_new_dashboard_notice', 'proxy_override' );
|
23 |
-
$this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_security_check', 'build', 'activation_timestamp', 'lock_file', 'cron_status', 'use_cron', 'cron_test_time' ) );
|
24 |
$this->set_default_if_empty( array( 'log_location', 'nginx_file', 'enable_grade_report' ) );
|
25 |
$this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'proxy_override' ) );
|
26 |
|
@@ -58,6 +58,8 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
|
|
58 |
$this->settings['lockout_message'] = trim( wp_kses( $this->settings['lockout_message'], $allowed_tags ) );
|
59 |
$this->settings['user_lockout_message'] = trim( wp_kses( $this->settings['user_lockout_message'], $allowed_tags ) );
|
60 |
$this->settings['community_lockout_message'] = trim( wp_kses( $this->settings['community_lockout_message'], $allowed_tags ) );
|
|
|
|
|
61 |
}
|
62 |
|
63 |
public function get_proxy_types() {
|
19 |
}
|
20 |
|
21 |
|
22 |
+
$this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'show_new_dashboard_notice', 'proxy_override', 'proxy', 'proxy_header', 'server_ips' );
|
23 |
+
$this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_security_check', 'build', 'activation_timestamp', 'lock_file', 'cron_status', 'use_cron', 'cron_test_time', 'proxy', 'proxy_header', 'server_ips' ) );
|
24 |
$this->set_default_if_empty( array( 'log_location', 'nginx_file', 'enable_grade_report' ) );
|
25 |
$this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'proxy_override' ) );
|
26 |
|
58 |
$this->settings['lockout_message'] = trim( wp_kses( $this->settings['lockout_message'], $allowed_tags ) );
|
59 |
$this->settings['user_lockout_message'] = trim( wp_kses( $this->settings['user_lockout_message'], $allowed_tags ) );
|
60 |
$this->settings['community_lockout_message'] = trim( wp_kses( $this->settings['community_lockout_message'], $allowed_tags ) );
|
61 |
+
|
62 |
+
$this->sanitize_setting( 'newline-separated-ips', 'server_ips', __( 'Server IPs', 'better-wp-security' ) );
|
63 |
}
|
64 |
|
65 |
public function get_proxy_types() {
|
core/modules/hide-backend/class-itsec-hide-backend.php
CHANGED
@@ -258,8 +258,10 @@ class ITSEC_Hide_Backend {
|
|
258 |
if ( false !== strpos( $path, 'action=postpass' ) ) {
|
259 |
// No special handling is needed for a password-protected post.
|
260 |
return $url;
|
261 |
-
}
|
262 |
$url = $this->add_token_to_url( $url, 'register' );
|
|
|
|
|
263 |
} elseif ( 'wp-login.php' !== $request_path || empty( $_GET['action'] ) || 'register' !== $_GET['action'] ) {
|
264 |
$url = $this->add_token_to_url( $url, 'login' );
|
265 |
}
|
258 |
if ( false !== strpos( $path, 'action=postpass' ) ) {
|
259 |
// No special handling is needed for a password-protected post.
|
260 |
return $url;
|
261 |
+
} elseif ( false !== strpos( $path, 'action=register' ) ) {
|
262 |
$url = $this->add_token_to_url( $url, 'register' );
|
263 |
+
} elseif ( false !== strpos( $path, 'action=rp' ) ) {
|
264 |
+
$url = $this->add_token_to_url( $url, 'login' );
|
265 |
} elseif ( 'wp-login.php' !== $request_path || empty( $_GET['action'] ) || 'register' !== $_GET['action'] ) {
|
266 |
$url = $this->add_token_to_url( $url, 'login' );
|
267 |
}
|
core/modules/malware/class-itsec-malware-scanner.php
CHANGED
@@ -27,10 +27,19 @@ final class ITSEC_Malware_Scanner {
|
|
27 |
} else if ( ! empty( $results['SYSTEM']['ERROR'] ) ) {
|
28 |
ITSEC_Log::add_warning( 'malware', 'sucuri-system-error', compact( 'results' ) );
|
29 |
} else if ( ! empty( $results['MALWARE']['WARN'] ) ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
if ( ! empty( $results['BLACKLIST']['WARN'] ) ) {
|
31 |
-
ITSEC_Log::add_critical_issue( 'malware', 'found-malware-and-on-blacklist',
|
32 |
} else {
|
33 |
-
ITSEC_Log::add_critical_issue( 'malware', 'found-malware',
|
34 |
}
|
35 |
} else if ( ! empty( $results['BLACKLIST']['WARN'] ) ) {
|
36 |
ITSEC_Log::add_critical_issue( 'malware', 'on-blacklist', compact( 'results' ) );
|
@@ -103,7 +112,7 @@ final class ITSEC_Malware_Scanner {
|
|
103 |
if ( false === $response ) {
|
104 |
$cached = false;
|
105 |
|
106 |
-
$site_url = apply_filters( 'itsec_test_malware_scan_site_url',
|
107 |
|
108 |
if ( defined( 'ITSEC_TEST_MALWARE_SCAN_SITE_URL' ) ) {
|
109 |
ITSEC_Log::add_process_update( $process_id, array( 'action' => 'define-force-site-url', 'original-site-url' => $site_url, 'define-name' => 'ITSEC_TEST_MALWARE_SCAN_SITE_URL', 'define-value' => ITSEC_TEST_MALWARE_SCAN_SITE_URL ) );
|
@@ -148,7 +157,7 @@ final class ITSEC_Malware_Scanner {
|
|
148 |
*/
|
149 |
protected static function scan_sub_site( $site_id, $process_id ) {
|
150 |
|
151 |
-
$url =
|
152 |
$record = array(
|
153 |
'url' => $url,
|
154 |
'id' => $site_id,
|
27 |
} else if ( ! empty( $results['SYSTEM']['ERROR'] ) ) {
|
28 |
ITSEC_Log::add_warning( 'malware', 'sucuri-system-error', compact( 'results' ) );
|
29 |
} else if ( ! empty( $results['MALWARE']['WARN'] ) ) {
|
30 |
+
$data = compact( 'results' );
|
31 |
+
|
32 |
+
if ( ITSEC_Lib_Remote_Messages::has_action( 'malware-scanner-disable-malware-warnings' ) ) {
|
33 |
+
$data['sucuri_error'] = true;
|
34 |
+
ITSEC_Log::add_warning( 'malware', 'malware-warning-suppressed', $data );
|
35 |
+
|
36 |
+
return $results;
|
37 |
+
}
|
38 |
+
|
39 |
if ( ! empty( $results['BLACKLIST']['WARN'] ) ) {
|
40 |
+
ITSEC_Log::add_critical_issue( 'malware', 'found-malware-and-on-blacklist', $data );
|
41 |
} else {
|
42 |
+
ITSEC_Log::add_critical_issue( 'malware', 'found-malware', $data );
|
43 |
}
|
44 |
} else if ( ! empty( $results['BLACKLIST']['WARN'] ) ) {
|
45 |
ITSEC_Log::add_critical_issue( 'malware', 'on-blacklist', compact( 'results' ) );
|
112 |
if ( false === $response ) {
|
113 |
$cached = false;
|
114 |
|
115 |
+
$site_url = apply_filters( 'itsec_test_malware_scan_site_url', get_home_url() );
|
116 |
|
117 |
if ( defined( 'ITSEC_TEST_MALWARE_SCAN_SITE_URL' ) ) {
|
118 |
ITSEC_Log::add_process_update( $process_id, array( 'action' => 'define-force-site-url', 'original-site-url' => $site_url, 'define-name' => 'ITSEC_TEST_MALWARE_SCAN_SITE_URL', 'define-value' => ITSEC_TEST_MALWARE_SCAN_SITE_URL ) );
|
157 |
*/
|
158 |
protected static function scan_sub_site( $site_id, $process_id ) {
|
159 |
|
160 |
+
$url = get_home_url( $site_id );
|
161 |
$record = array(
|
162 |
'url' => $url,
|
163 |
'id' => $site_id,
|
core/modules/malware/logs.php
CHANGED
@@ -33,6 +33,8 @@ final class ITSEC_Malware_Logs {
|
|
33 |
$entry['description'] = esc_html__( 'Site on Blacklist', 'better-wp-security' );
|
34 |
} else if ( 'scan' === $entry['code'] ) {
|
35 |
$entry['description'] = esc_html__( 'Scan', 'better-wp-security' );
|
|
|
|
|
36 |
}
|
37 |
|
38 |
return $entry;
|
33 |
$entry['description'] = esc_html__( 'Site on Blacklist', 'better-wp-security' );
|
34 |
} else if ( 'scan' === $entry['code'] ) {
|
35 |
$entry['description'] = esc_html__( 'Scan', 'better-wp-security' );
|
36 |
+
} else if ( 'malware-warning-suppressed' === $entry['code'] ) {
|
37 |
+
$entry['description'] = esc_html__( 'Possible Malware, Scanner Experiencing Issues', 'better-wp-security' );
|
38 |
}
|
39 |
|
40 |
return $entry;
|
core/modules/notification-center/class-notification-center.php
CHANGED
@@ -322,17 +322,17 @@ final class ITSEC_Notification_Center {
|
|
322 |
$config = $this->get_notification( $notification );
|
323 |
|
324 |
if ( self::R_ADMIN === $config['recipient'] ) {
|
325 |
-
return array( get_option( 'admin_email' ) );
|
326 |
}
|
327 |
|
328 |
if ( self::R_EMAIL_LIST === $config['recipient'] ) {
|
329 |
$settings = $this->get_notification_settings( $notification );
|
330 |
|
331 |
-
return ! empty( $settings['email_list'] ) ? $settings['email_list'] : array();
|
332 |
}
|
333 |
|
334 |
if ( self::R_USER_LIST !== $config['recipient'] && self::R_USER_LIST_ADMIN_UPGRADE !== $config['recipient'] ) {
|
335 |
-
return array();
|
336 |
}
|
337 |
|
338 |
$settings = $this->get_notification_settings( $notification );
|
@@ -363,8 +363,9 @@ final class ITSEC_Notification_Center {
|
|
363 |
}
|
364 |
}
|
365 |
|
366 |
-
|
367 |
-
|
|
|
368 |
|
369 |
foreach ( $users as $user ) {
|
370 |
if ( is_object( $user ) && ! empty( $user->user_email ) ) {
|
@@ -376,7 +377,41 @@ final class ITSEC_Notification_Center {
|
|
376 |
$addresses = array_merge( $addresses, $settings['previous_emails'] );
|
377 |
}
|
378 |
|
379 |
-
return array_unique( $addresses );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
380 |
}
|
381 |
|
382 |
/**
|
@@ -1132,4 +1167,4 @@ final class ITSEC_Notification_Center {
|
|
1132 |
|
1133 |
return $out;
|
1134 |
}
|
1135 |
-
}
|
322 |
$config = $this->get_notification( $notification );
|
323 |
|
324 |
if ( self::R_ADMIN === $config['recipient'] ) {
|
325 |
+
return $this->filter_recipients( array( get_option( 'admin_email' ) ), $notification );
|
326 |
}
|
327 |
|
328 |
if ( self::R_EMAIL_LIST === $config['recipient'] ) {
|
329 |
$settings = $this->get_notification_settings( $notification );
|
330 |
|
331 |
+
return $this->filter_recipients( ! empty( $settings['email_list'] ) ? $settings['email_list'] : array(), $notification );
|
332 |
}
|
333 |
|
334 |
if ( self::R_USER_LIST !== $config['recipient'] && self::R_USER_LIST_ADMIN_UPGRADE !== $config['recipient'] ) {
|
335 |
+
return $this->filter_recipients( array(), $notification );
|
336 |
}
|
337 |
|
338 |
$settings = $this->get_notification_settings( $notification );
|
363 |
}
|
364 |
}
|
365 |
|
366 |
+
if ( $roles ) {
|
367 |
+
$users = array_merge( $users, get_users( array( 'role__in' => $roles ) ) );
|
368 |
+
}
|
369 |
|
370 |
foreach ( $users as $user ) {
|
371 |
if ( is_object( $user ) && ! empty( $user->user_email ) ) {
|
377 |
$addresses = array_merge( $addresses, $settings['previous_emails'] );
|
378 |
}
|
379 |
|
380 |
+
return $this->filter_recipients( array_unique( $addresses ), $notification );
|
381 |
+
}
|
382 |
+
|
383 |
+
/**
|
384 |
+
* Filter the recipients for a notification.
|
385 |
+
*
|
386 |
+
* @since 4.8.4
|
387 |
+
*
|
388 |
+
* @param string[] $recipients Array of email addresses.
|
389 |
+
* @param string $notification The notification slug.
|
390 |
+
*
|
391 |
+
* @return string[] Filtered array of email addresses.
|
392 |
+
*/
|
393 |
+
private function filter_recipients( $recipients, $notification ) {
|
394 |
+
|
395 |
+
/**
|
396 |
+
* Fitler the email addresses that will receive the given notification.
|
397 |
+
*
|
398 |
+
* The dynamic portion of this hook '$notification' refers to the notification slug.
|
399 |
+
*
|
400 |
+
* @since 4.8.4
|
401 |
+
*
|
402 |
+
* @param string[] $recipients Array of email addresses.
|
403 |
+
*/
|
404 |
+
$recipients = apply_filters( "itsec_notification_{$notification}_email_recipients", $recipients );
|
405 |
+
|
406 |
+
/**
|
407 |
+
* Filter the email addresses that will receive the given notification.
|
408 |
+
*
|
409 |
+
* @since 4.8.4
|
410 |
+
*
|
411 |
+
* @param string[] $recipients Array of email addresses.
|
412 |
+
* @param string $notification The notification slug.
|
413 |
+
*/
|
414 |
+
return apply_filters( 'itsec_notification_email_recipients', $recipients, $notification );
|
415 |
}
|
416 |
|
417 |
/**
|
1167 |
|
1168 |
return $out;
|
1169 |
}
|
1170 |
+
}
|
core/modules/password-requirements/class-itsec-password-requirements.php
CHANGED
@@ -327,7 +327,7 @@ class ITSEC_Password_Requirements {
|
|
327 |
if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
|
328 |
continue;
|
329 |
}
|
330 |
-
|
331 |
$settings = ITSEC_Lib_Password_Requirements::get_requirement_settings( $code );
|
332 |
|
333 |
if ( $requirement['flag_check'] && call_user_func( $requirement['flag_check'], $user, $settings ) ) {
|
@@ -404,6 +404,8 @@ class ITSEC_Password_Requirements {
|
|
404 |
* @param WP_User $user
|
405 |
*/
|
406 |
public function render_interstitial( $user ) {
|
|
|
|
|
407 |
do_action( 'itsec_password_requirements_change_form', $user );
|
408 |
?>
|
409 |
|
@@ -477,4 +479,4 @@ class ITSEC_Password_Requirements {
|
|
477 |
|
478 |
return null;
|
479 |
}
|
480 |
-
}
|
327 |
if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
|
328 |
continue;
|
329 |
}
|
330 |
+
|
331 |
$settings = ITSEC_Lib_Password_Requirements::get_requirement_settings( $code );
|
332 |
|
333 |
if ( $requirement['flag_check'] && call_user_func( $requirement['flag_check'], $user, $settings ) ) {
|
404 |
* @param WP_User $user
|
405 |
*/
|
406 |
public function render_interstitial( $user ) {
|
407 |
+
wp_enqueue_script( 'user-profile' );
|
408 |
+
|
409 |
do_action( 'itsec_password_requirements_change_form', $user );
|
410 |
?>
|
411 |
|
479 |
|
480 |
return null;
|
481 |
}
|
482 |
+
}
|
core/modules/security-check/active.php
CHANGED
@@ -6,3 +6,30 @@ function itsec_security_check_register_sync_verbs( $api ) {
|
|
6 |
$api->register( 'itsec-get-security-check-modules', 'Ithemes_Sync_Verb_ITSEC_Get_Security_Check_Modules', dirname( __FILE__ ) . '/sync-verbs/itsec-get-security-check-modules.php' );
|
7 |
}
|
8 |
add_action( 'ithemes_sync_register_verbs', 'itsec_security_check_register_sync_verbs' );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
$api->register( 'itsec-get-security-check-modules', 'Ithemes_Sync_Verb_ITSEC_Get_Security_Check_Modules', dirname( __FILE__ ) . '/sync-verbs/itsec-get-security-check-modules.php' );
|
7 |
}
|
8 |
add_action( 'ithemes_sync_register_verbs', 'itsec_security_check_register_sync_verbs' );
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Handle the loopback callback test.
|
12 |
+
*/
|
13 |
+
function itsec_security_check_loopback_callback() {
|
14 |
+
if ( ! isset( $_POST['hash'], $_POST['exp'] ) ) {
|
15 |
+
wp_die();
|
16 |
+
}
|
17 |
+
|
18 |
+
$hash = $_POST['hash'];
|
19 |
+
$exp = $_POST['exp'];
|
20 |
+
|
21 |
+
$expected = hash_hmac( 'sha1', "itsec-check-loopback|{$exp}", wp_salt() );
|
22 |
+
|
23 |
+
if ( ! hash_equals( $hash, $expected ) ) {
|
24 |
+
wp_die();
|
25 |
+
}
|
26 |
+
|
27 |
+
if ( $exp < ITSEC_Core::get_current_time_gmt() ) {
|
28 |
+
wp_die();
|
29 |
+
}
|
30 |
+
|
31 |
+
echo ITSEC_Lib::get_ip();
|
32 |
+
die;
|
33 |
+
}
|
34 |
+
|
35 |
+
add_action( 'admin_post_nopriv_itsec-check-loopback', 'itsec_security_check_loopback_callback' );
|
core/modules/security-check/feedback-renderer.php
CHANGED
@@ -17,6 +17,9 @@ final class ITSEC_Security_Check_Feedback_Renderer {
|
|
17 |
if ( isset( $section_groups['confirmation'] ) ) {
|
18 |
self::render_sections( 'confirmation', $section_groups['confirmation'] );
|
19 |
}
|
|
|
|
|
|
|
20 |
}
|
21 |
|
22 |
private static function render_sections( $status, $sections ) {
|
17 |
if ( isset( $section_groups['confirmation'] ) ) {
|
18 |
self::render_sections( 'confirmation', $section_groups['confirmation'] );
|
19 |
}
|
20 |
+
if ( isset( $section_groups['error'] ) ) {
|
21 |
+
self::render_sections( 'error', $section_groups['error'] );
|
22 |
+
}
|
23 |
}
|
24 |
|
25 |
private static function render_sections( $status, $sections ) {
|
core/modules/security-check/scanner.php
CHANGED
@@ -2,6 +2,8 @@
|
|
2 |
|
3 |
final class ITSEC_Security_Check_Scanner {
|
4 |
private static $available_modules;
|
|
|
|
|
5 |
private static $feedback;
|
6 |
|
7 |
|
@@ -93,6 +95,8 @@ final class ITSEC_Security_Check_Scanner {
|
|
93 |
self::enforce_setting( 'global', 'write_files', true, __( 'Enabled the Write to Files setting in Global Settings.', 'better-wp-security' ) );
|
94 |
|
95 |
self::enforce_setting( 'online-files', 'compare_file_hashes', true, __( 'Enabled Online Files Comparison in File Change Detection.', 'better-wp-security' ) );
|
|
|
|
|
96 |
|
97 |
do_action( 'itsec-security-check-after-default-checks', self::$feedback, self::$available_modules );
|
98 |
}
|
@@ -219,4 +223,69 @@ final class ITSEC_Security_Check_Scanner {
|
|
219 |
ITSEC_Response::set_response( '<p>' . __( 'Your site is now using Network Brute Force Protection.', 'better-wp-security' ) . '</p>' );
|
220 |
}
|
221 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
222 |
}
|
2 |
|
3 |
final class ITSEC_Security_Check_Scanner {
|
4 |
private static $available_modules;
|
5 |
+
|
6 |
+
/** @var ITSEC_Security_Check_Feedback */
|
7 |
private static $feedback;
|
8 |
|
9 |
|
95 |
self::enforce_setting( 'global', 'write_files', true, __( 'Enabled the Write to Files setting in Global Settings.', 'better-wp-security' ) );
|
96 |
|
97 |
self::enforce_setting( 'online-files', 'compare_file_hashes', true, __( 'Enabled Online Files Comparison in File Change Detection.', 'better-wp-security' ) );
|
98 |
+
self::check_server_ips();
|
99 |
+
self::do_loopback();
|
100 |
|
101 |
do_action( 'itsec-security-check-after-default-checks', self::$feedback, self::$available_modules );
|
102 |
}
|
223 |
ITSEC_Response::set_response( '<p>' . __( 'Your site is now using Network Brute Force Protection.', 'better-wp-security' ) . '</p>' );
|
224 |
}
|
225 |
}
|
226 |
+
|
227 |
+
private static function check_server_ips() {
|
228 |
+
|
229 |
+
$response = dns_get_record( parse_url( site_url(), PHP_URL_HOST ), DNS_A + ( defined( 'DNS_AAAA' ) ? DNS_AAAA : 0 ) );
|
230 |
+
|
231 |
+
if ( ! $response ) {
|
232 |
+
return;
|
233 |
+
}
|
234 |
+
|
235 |
+
$ips = array();
|
236 |
+
|
237 |
+
foreach ( $response as $record ) {
|
238 |
+
if ( isset( $record['ipv6'] ) ) {
|
239 |
+
$ips[] = $record['ipv6'];
|
240 |
+
}
|
241 |
+
|
242 |
+
if ( isset( $record['ip'] ) ) {
|
243 |
+
$ips[] = $record['ip'];
|
244 |
+
}
|
245 |
+
}
|
246 |
+
|
247 |
+
if ( $ips ) {
|
248 |
+
ITSEC_Modules::set_setting( 'global', 'server_ips', array_merge( $ips, ITSEC_Modules::get_setting( 'global', 'server_ips' ) ) );
|
249 |
+
|
250 |
+
self::$feedback->add_section( 'server-ips', array( 'status' => 'action-taken' ) );
|
251 |
+
self::$feedback->add_text( __( 'Identified server IPs to determine loopback requests.', 'better-wp-security' ) );
|
252 |
+
}
|
253 |
+
}
|
254 |
+
|
255 |
+
private static function do_loopback() {
|
256 |
+
$exp = ITSEC_Core::get_current_time_gmt() + 60;
|
257 |
+
$action = 'itsec-check-loopback';
|
258 |
+
$hash = hash_hmac( 'sha1', "{$action}|{$exp}", wp_salt() );
|
259 |
+
|
260 |
+
$response = wp_remote_post( admin_url( 'admin-post.php' ), array(
|
261 |
+
'body' => array(
|
262 |
+
'action' => $action,
|
263 |
+
'hash' => $hash,
|
264 |
+
'exp' => $exp,
|
265 |
+
),
|
266 |
+
) );
|
267 |
+
|
268 |
+
if ( is_wp_error( $response ) ) {
|
269 |
+
self::$feedback->add_section( 'loopback', array( 'status' => 'error' ) );
|
270 |
+
self::$feedback->add_text( sprintf( __( 'Skipping loopback test: %s', 'better-wp-security' ), $response->get_error_message() ) );
|
271 |
+
|
272 |
+
return;
|
273 |
+
}
|
274 |
+
|
275 |
+
$ip = trim( wp_remote_retrieve_body( $response ) );
|
276 |
+
|
277 |
+
require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-ip-tools.php' );
|
278 |
+
|
279 |
+
if ( ! ITSEC_Lib_IP_Tools::validate( $ip ) ) {
|
280 |
+
self::$feedback->add_section( 'loopback', array( 'status' => 'error' ) );
|
281 |
+
self::$feedback->add_text( sprintf( __( 'Invalid IP returned: %s', 'better-wp-security' ), esc_attr( $ip ) ) );
|
282 |
+
|
283 |
+
return;
|
284 |
+
}
|
285 |
+
|
286 |
+
ITSEC_Modules::set_setting( 'global', 'server_ips', array_merge( array( $ip ), ITSEC_Modules::get_setting( 'global', 'server_ips' ) ) );
|
287 |
+
|
288 |
+
self::$feedback->add_section( 'loopback', array( 'status' => 'action-taken' ) );
|
289 |
+
self::$feedback->add_text( __( 'Identified loopback IP.', 'better-wp-security' ) );
|
290 |
+
}
|
291 |
}
|
core/modules/ssl/active.php
CHANGED
@@ -1,9 +1,7 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
$itsec_ssl_admin->run( ITSEC_Core::get_instance() );
|
7 |
-
}
|
8 |
|
9 |
-
require_once( 'class-itsec-ssl.php' );
|
1 |
<?php
|
2 |
|
3 |
+
require_once( dirname( __FILE__ ) . '/class-itsec-ssl-admin.php' );
|
4 |
+
$itsec_ssl_admin = new ITSEC_SSL_Admin();
|
5 |
+
$itsec_ssl_admin->run();
|
|
|
|
|
6 |
|
7 |
+
require_once( dirname( __FILE__ ) . '/class-itsec-ssl.php' );
|
core/modules/ssl/class-itsec-ssl-admin.php
CHANGED
@@ -5,13 +5,47 @@ class ITSEC_SSL_Admin {
|
|
5 |
$settings = ITSEC_Modules::get_settings( 'ssl' );
|
6 |
|
7 |
if ( 'advanced' === $settings['require_ssl'] && 1 === $settings['frontend'] ) {
|
8 |
-
|
|
|
9 |
add_action( 'post_submitbox_misc_actions', array( $this, 'ssl_enable_per_content' ) );
|
10 |
add_action( 'save_post', array( $this, 'save_post' ) );
|
11 |
-
|
12 |
}
|
13 |
}
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
/**
|
16 |
* Add checkbox to post meta for SSL
|
17 |
*
|
5 |
$settings = ITSEC_Modules::get_settings( 'ssl' );
|
6 |
|
7 |
if ( 'advanced' === $settings['require_ssl'] && 1 === $settings['frontend'] ) {
|
8 |
+
add_action( 'init', array( $this, 'register_meta' ) );
|
9 |
+
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor' ) );
|
10 |
add_action( 'post_submitbox_misc_actions', array( $this, 'ssl_enable_per_content' ) );
|
11 |
add_action( 'save_post', array( $this, 'save_post' ) );
|
|
|
12 |
}
|
13 |
}
|
14 |
|
15 |
+
/**
|
16 |
+
* Register the "Enable SSL" meta key.
|
17 |
+
*/
|
18 |
+
public function register_meta() {
|
19 |
+
register_meta( 'post', 'itsec_enable_ssl', array(
|
20 |
+
'single' => true,
|
21 |
+
'type' => 'boolean',
|
22 |
+
'sanitize_callback' => 'rest_sanitize_boolean',
|
23 |
+
'show_in_rest' => array(
|
24 |
+
'schema' => array(
|
25 |
+
'type' => 'boolean',
|
26 |
+
'context' => array( 'edit' ),
|
27 |
+
)
|
28 |
+
)
|
29 |
+
) );
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Enqueue the JS for the block editor to add the "Enable SSL" checkbox.
|
34 |
+
*/
|
35 |
+
public function enqueue_block_editor() {
|
36 |
+
wp_enqueue_script( 'itsec-ssl-block-editor', plugins_url( 'js/block-editor.js', __FILE__ ), array(
|
37 |
+
'wp-components',
|
38 |
+
'wp-compose',
|
39 |
+
'wp-element',
|
40 |
+
'wp-edit-post',
|
41 |
+
'wp-data',
|
42 |
+
'wp-plugins',
|
43 |
+
), 1, true );
|
44 |
+
wp_localize_script( 'itsec-ssl-block-editor', 'ITSECSSLBlockEditor', array(
|
45 |
+
'enableSSL' => __( 'Enable SSL', 'better-wp-security' ),
|
46 |
+
) );
|
47 |
+
}
|
48 |
+
|
49 |
/**
|
50 |
* Add checkbox to post meta for SSL
|
51 |
*
|
core/modules/ssl/js/block-editor.js
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function( element, components, editPost, compose, data, plugins, l10n ) {
|
2 |
+
const el = element.createElement;
|
3 |
+
const META_KEY = 'itsec_enable_ssl';
|
4 |
+
|
5 |
+
function EnableSSL( props ) {
|
6 |
+
return el( editPost.PluginPostStatusInfo, { className: 'itsec-ssl' }, [
|
7 |
+
el( wp.components.CheckboxControl, {
|
8 |
+
checked : props.isEnabled,
|
9 |
+
label : l10n.enableSSL,
|
10 |
+
onChange: props.update,
|
11 |
+
key : 'enable-ssl',
|
12 |
+
} ),
|
13 |
+
] );
|
14 |
+
}
|
15 |
+
|
16 |
+
plugins.registerPlugin( 'itsec-ssl', {
|
17 |
+
icon : 'hidden',
|
18 |
+
render: compose.compose( [
|
19 |
+
data.withSelect( function( select ) {
|
20 |
+
return {
|
21 |
+
isEnabled: ( select( 'core/editor' ).getEditedPostAttribute( 'meta' ) || {} )[ META_KEY ],
|
22 |
+
};
|
23 |
+
} ),
|
24 |
+
data.withDispatch( function( dispatch ) {
|
25 |
+
return {
|
26 |
+
update: function( isEnabled ) {
|
27 |
+
const edit = { meta: {} };
|
28 |
+
edit.meta[ META_KEY ] = isEnabled;
|
29 |
+
|
30 |
+
dispatch( 'core/editor' ).editPost( edit );
|
31 |
+
},
|
32 |
+
};
|
33 |
+
} ),
|
34 |
+
] )( EnableSSL ),
|
35 |
+
} );
|
36 |
+
|
37 |
+
} )( wp.element, wp.components, wp.editPost, wp.compose, wp.data, wp.plugins, ITSECSSLBlockEditor );
|
core/modules/system-tweaks/config-generators.php
CHANGED
@@ -81,7 +81,7 @@ final class ITSEC_System_Tweaks_Config_Generators {
|
|
81 |
|
82 |
$rewrites .= "\n";
|
83 |
$rewrites .= "\t\t# " . __( 'Disable PHP in Uploads - Security > Settings > System Tweaks > PHP in Uploads', 'better-wp-security' ) . "\n";
|
84 |
-
$rewrites .= "\t\tRewriteRule ^$dir/.*\.(?:php[1-7]?|pht|phtml?|phps)
|
85 |
}
|
86 |
}
|
87 |
|
@@ -93,7 +93,7 @@ final class ITSEC_System_Tweaks_Config_Generators {
|
|
93 |
|
94 |
$rewrites .= "\n";
|
95 |
$rewrites .= "\t\t# " . __( 'Disable PHP in Plugins - Security > Settings > System Tweaks > PHP in Plugins', 'better-wp-security' ) . "\n";
|
96 |
-
$rewrites .= "\t\tRewriteRule ^$dir/.*\.(?:php[1-7]?|pht|phtml?|phps)
|
97 |
}
|
98 |
}
|
99 |
|
@@ -105,7 +105,7 @@ final class ITSEC_System_Tweaks_Config_Generators {
|
|
105 |
|
106 |
$rewrites .= "\n";
|
107 |
$rewrites .= "\t\t# " . __( 'Disable PHP in Themes - Security > Settings > System Tweaks > PHP in Themes', 'better-wp-security' ) . "\n";
|
108 |
-
$rewrites .= "\t\tRewriteRule ^$dir/.*\.(?:php[1-7]?|pht|phtml?|phps)
|
109 |
}
|
110 |
}
|
111 |
|
@@ -130,8 +130,8 @@ final class ITSEC_System_Tweaks_Config_Generators {
|
|
130 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} base64_decode\( [NC,OR]\n";
|
131 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %24&x [NC,OR]\n";
|
132 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} 127\.0 [NC,OR]\n";
|
133 |
-
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (globals|encode|localhost|loopback) [NC,OR]\n";
|
134 |
-
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (concat|insert|union|declare) [NC,OR]\n";
|
135 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %[01][0-9A-F] [NC]\n";
|
136 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^loggedout=true\n";
|
137 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^action=jetpack-sso\n";
|
@@ -259,8 +259,8 @@ final class ITSEC_System_Tweaks_Config_Generators {
|
|
259 |
$modification .= "\tif ( \$args ~* \"base64_decode\(\" ) { set \$susquery 1; }\n";
|
260 |
$modification .= "\tif ( \$args ~* \"%24&x\" ) { set \$susquery 1; }\n";
|
261 |
$modification .= "\tif ( \$args ~* \"127\.0\" ) { set \$susquery 1; }\n";
|
262 |
-
$modification .= "\tif ( \$args ~* \"(globals|encode|localhost|loopback)\" ) { set \$susquery 1; }\n";
|
263 |
-
$modification .= "\tif ( \$args ~* \"(insert|concat|union|declare)\" ) { set \$susquery 1; }\n";
|
264 |
$modification .= "\tif ( \$args ~* \"%[01][0-9A-F]\" ) { set \$susquery 1; }\n";
|
265 |
$modification .= "\tif ( \$args ~ \"^loggedout=true\" ) { set \$susquery 0; }\n";
|
266 |
$modification .= "\tif ( \$args ~ \"^action=jetpack-sso\" ) { set \$susquery 0; }\n";
|
81 |
|
82 |
$rewrites .= "\n";
|
83 |
$rewrites .= "\t\t# " . __( 'Disable PHP in Uploads - Security > Settings > System Tweaks > PHP in Uploads', 'better-wp-security' ) . "\n";
|
84 |
+
$rewrites .= "\t\tRewriteRule ^$dir/.*\.(?:php[1-7]?|pht|phtml?|phps)\\.?$ - [NC,F]\n";
|
85 |
}
|
86 |
}
|
87 |
|
93 |
|
94 |
$rewrites .= "\n";
|
95 |
$rewrites .= "\t\t# " . __( 'Disable PHP in Plugins - Security > Settings > System Tweaks > PHP in Plugins', 'better-wp-security' ) . "\n";
|
96 |
+
$rewrites .= "\t\tRewriteRule ^$dir/.*\.(?:php[1-7]?|pht|phtml?|phps)\\.?$ - [NC,F]\n";
|
97 |
}
|
98 |
}
|
99 |
|
105 |
|
106 |
$rewrites .= "\n";
|
107 |
$rewrites .= "\t\t# " . __( 'Disable PHP in Themes - Security > Settings > System Tweaks > PHP in Themes', 'better-wp-security' ) . "\n";
|
108 |
+
$rewrites .= "\t\tRewriteRule ^$dir/.*\.(?:php[1-7]?|pht|phtml?|phps)\\.?$ - [NC,F]\n";
|
109 |
}
|
110 |
}
|
111 |
|
130 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} base64_decode\( [NC,OR]\n";
|
131 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %24&x [NC,OR]\n";
|
132 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} 127\.0 [NC,OR]\n";
|
133 |
+
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (^|\\W)(globals|encode|localhost|loopback)($|\\W) [NC,OR]\n";
|
134 |
+
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} (^|\\W)(concat|insert|union|declare)($|\\W) [NC,OR]\n";
|
135 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} %[01][0-9A-F] [NC]\n";
|
136 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^loggedout=true\n";
|
137 |
$rewrites .= "\t\tRewriteCond %{QUERY_STRING} !^action=jetpack-sso\n";
|
259 |
$modification .= "\tif ( \$args ~* \"base64_decode\(\" ) { set \$susquery 1; }\n";
|
260 |
$modification .= "\tif ( \$args ~* \"%24&x\" ) { set \$susquery 1; }\n";
|
261 |
$modification .= "\tif ( \$args ~* \"127\.0\" ) { set \$susquery 1; }\n";
|
262 |
+
$modification .= "\tif ( \$args ~* \"(^|\\W)(globals|encode|localhost|loopback)($|\\W)\" ) { set \$susquery 1; }\n";
|
263 |
+
$modification .= "\tif ( \$args ~* \"(^|\\W)(insert|concat|union|declare)($|\\W)\" ) { set \$susquery 1; }\n";
|
264 |
$modification .= "\tif ( \$args ~* \"%[01][0-9A-F]\" ) { set \$susquery 1; }\n";
|
265 |
$modification .= "\tif ( \$args ~ \"^loggedout=true\" ) { set \$susquery 0; }\n";
|
266 |
$modification .= "\tif ( \$args ~ \"^action=jetpack-sso\" ) { set \$susquery 0; }\n";
|
history.txt
CHANGED
@@ -808,4 +808,21 @@
|
|
808 |
Enhancement: Block access to git and svn repositories when System Tweaks -> Protect System Files is enabled.
|
809 |
Tweak: Update jQuery Validation library to 1.17.0
|
810 |
Bug Fix: Improve detection of blocking the File Change Scan from being scheduled if one is already being run.
|
811 |
-
Bug Fix: Prevent infinite recursion error when trying to access directories outside of the allowed file tree.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
808 |
Enhancement: Block access to git and svn repositories when System Tweaks -> Protect System Files is enabled.
|
809 |
Tweak: Update jQuery Validation library to 1.17.0
|
810 |
Bug Fix: Improve detection of blocking the File Change Scan from being scheduled if one is already being run.
|
811 |
+
Bug Fix: Prevent infinite recursion error when trying to access directories outside of the allowed file tree.
|
812 |
+
7.3.0 - 2019-02-14 - Chris Jean & Timothy Jacobs
|
813 |
+
Enhancement: Add Per-Content SSL toggle to the upcoming Block Editor interface.
|
814 |
+
Enhancement: Add filter to the recipients list for email notifications: "itsec_notification_{$notification}_email_recipients" and "itsec_notification_email_recipients".
|
815 |
+
Enhancement: Add define "ITSEC_DISABLE_TEMP_WHITELIST" to disable the Temporary IP Whitelisting for logged-in administrators.
|
816 |
+
Enhancement: Improve redirecting after processing a login interstitial from a front-end login form.
|
817 |
+
Enhancement: Add loopback IP detection to Security Check.
|
818 |
+
Enhancement: Detect Server IPs in Security Check.
|
819 |
+
Tweak: Add additional safety checks when writing to system config files. This will log a "Critical Issue" when the writing of an empty or partial config file is detected and prevented.
|
820 |
+
Tweak: Improve File Change locking to help prevent failing scans on sites with inconsistent cron scheduling.
|
821 |
+
Tweak: Improve "System Tweaks – Suspicious Query Strings – SQLI" to reduce false positives.
|
822 |
+
Tweak: Improve "System Tweaks – Disable PHP" to block PHP files in apache configurations that serve files with a trailing dot.
|
823 |
+
Tweak: Remove "Seznam Bot" from HackRepair List as it isn't present in the latest version.
|
824 |
+
Bug Fix: Include Hide Backend token when emailing a password reset URL.
|
825 |
+
Bug Fix: Notification Center - Only send notifications to users with an exact role match of selected roles instead of a fuzzy match based on selected capabilities.
|
826 |
+
Bug Fix: Error when trying to edit reusable blocks with per-post SSL enabled.
|
827 |
+
Bug Fix: Resolve warnings on PHP 5.2.
|
828 |
+
|
readme.txt
CHANGED
@@ -2,8 +2,8 @@
|
|
2 |
Contributors: ithemes, chrisjean, gerroald, mattdanner, timothyblynjacobs
|
3 |
Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
|
4 |
Requires at least: 4.7
|
5 |
-
Tested up to:
|
6 |
-
Stable tag: 7.
|
7 |
Requires PHP: 5.2
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
@@ -189,6 +189,23 @@ Free support may be available with the help of the community in the <a href="htt
|
|
189 |
|
190 |
== Changelog ==
|
191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
= 7.2.0 =
|
193 |
* Enhancement: Allow for selecting the particular Proxy header a server is configured to use. Improve the language to indicate the importance of configuring this setting. H/t Filippo Cavallarin CEO at wearesegment.com
|
194 |
* Enhancement: Block access to git and svn repositories when System Tweaks -> Protect System Files is enabled.
|
@@ -503,5 +520,5 @@ Free support may be available with the help of the community in the <a href="htt
|
|
503 |
|
504 |
== Upgrade Notice ==
|
505 |
|
506 |
-
= 7.
|
507 |
-
Version 7.
|
2 |
Contributors: ithemes, chrisjean, gerroald, mattdanner, timothyblynjacobs
|
3 |
Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
|
4 |
Requires at least: 4.7
|
5 |
+
Tested up to: 5.1.0
|
6 |
+
Stable tag: 7.3.0
|
7 |
Requires PHP: 5.2
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
189 |
|
190 |
== Changelog ==
|
191 |
|
192 |
+
= 7.3.0 =
|
193 |
+
* Enhancement: Add Per-Content SSL toggle to the upcoming Block Editor interface.
|
194 |
+
* Enhancement: Add filter to the recipients list for email notifications: "itsec_notification_{$notification}_email_recipients" and "itsec_notification_email_recipients".
|
195 |
+
* Enhancement: Add define "ITSEC_DISABLE_TEMP_WHITELIST" to disable the Temporary IP Whitelisting for logged-in administrators.
|
196 |
+
* Enhancement: Improve redirecting after processing a login interstitial from a front-end login form.
|
197 |
+
* Enhancement: Add loopback IP detection to Security Check.
|
198 |
+
* Enhancement: Detect Server IPs in Security Check.
|
199 |
+
* Tweak: Add additional safety checks when writing to system config files. This will log a "Critical Issue" when the writing of an empty or partial config file is detected and prevented.
|
200 |
+
* Tweak: Improve File Change locking to help prevent failing scans on sites with inconsistent cron scheduling.
|
201 |
+
* Tweak: Improve "System Tweaks – Suspicious Query Strings – SQLI" to reduce false positives.
|
202 |
+
* Tweak: Improve "System Tweaks – Disable PHP" to block PHP files in apache configurations that serve files with a trailing dot.
|
203 |
+
* Tweak: Remove "Seznam Bot" from HackRepair List as it isn't present in the latest version.
|
204 |
+
* Bug Fix: Include Hide Backend token when emailing a password reset URL.
|
205 |
+
* Bug Fix: Notification Center - Only send notifications to users with an exact role match of selected roles instead of a fuzzy match based on selected capabilities.
|
206 |
+
* Bug Fix: Error when trying to edit reusable blocks with per-post SSL enabled.
|
207 |
+
* Bug Fix: Resolve warnings on PHP 5.2.
|
208 |
+
|
209 |
= 7.2.0 =
|
210 |
* Enhancement: Allow for selecting the particular Proxy header a server is configured to use. Improve the language to indicate the importance of configuring this setting. H/t Filippo Cavallarin CEO at wearesegment.com
|
211 |
* Enhancement: Block access to git and svn repositories when System Tweaks -> Protect System Files is enabled.
|
520 |
|
521 |
== Upgrade Notice ==
|
522 |
|
523 |
+
= 7.3.0 =
|
524 |
+
Version 7.3.0 contains important bug fixes and improvements. It is recommended for all users.
|