iThemes Security (formerly Better WP Security) - Version 7.0.2

Version Description

  • Enhancement: Add UI to cancel in progress File Scan.
  • Enhancement: Add basic admin debug page to help diagnosing and resolving issues. Particularly with the events.
  • Enhancement: Add debug settings JSON editor.
  • Enhancement: Continually evaluate password strength for users instead of only during registration.
  • Enhancement: Introduce Password Requirements module for managing and enforcing password requirements.
  • Bug Fix: Accessing password requirement settings would not resolve properly in some instances.
  • Bug Fix: Away Mode would not lock out users who were already logged-in during the "away" period.
  • Bug Fix: Enforce the Strong Passwords requirement during Security Check.
  • Bug Fix: Ensure scheduling lock is cleared by the Cron Scheduler when not proceeding with running events.
  • Bug Fix: If a password requirement has been disabled or is no longer available, don't consider the password as needing a change.
  • Bug Fix: Only hide "Acknowledge Weak Password" checkbox if the user was not allowed to use a weak password.
  • Bug Fix: Password strength would not be evaluated if password was set using custom PHP or CLI commands.
  • Bug Fix: Prevent File Change from getting stuck in an infinite rescheduling loop on the first step.
  • Bug Fix: Remove distributed storage table on uninstall.
  • Tweak: Don't write to the tracked files setting if the file hash has not changed.
  • Tweak: If no last password change date is recorded for the user, treat their registration date as the last change date.
Download this release

Release Info

Developer chrisjean
Plugin Icon 128x128 iThemes Security (formerly Better WP Security)
Version 7.0.2
Comparing to
See all releases

Code changes from version 7.0.1 to 7.0.2

Files changed (49) hide show
  1. better-wp-security.php +1 -1
  2. core/admin-pages/css/style.css +16 -0
  3. core/admin-pages/init.php +6 -1
  4. core/admin-pages/js/debug.js +86 -0
  5. core/admin-pages/js/util.js +37 -1
  6. core/admin-pages/page-debug.php +425 -0
  7. core/admin-pages/page-logs.php +2 -0
  8. core/admin-pages/page-settings.php +2 -0
  9. core/core.php +7 -9
  10. core/history.txt +22 -1
  11. core/lib.php +33 -3
  12. core/lib/class-itsec-lib-canonical-roles.php +43 -0
  13. core/lib/class-itsec-lib-password-requirements.php +156 -159
  14. core/lib/class-itsec-mail.php +110 -26
  15. core/lib/class-itsec-scheduler-cron.php +4 -0
  16. core/lib/form.php +18 -0
  17. core/lib/mail-templates/header.html +1 -0
  18. core/lib/schema.php +1 -0
  19. core/lib/validator.php +31 -1
  20. core/lockout.php +0 -3
  21. core/modules/away-mode/class-itsec-away-mode.php +6 -1
  22. core/modules/file-change/class-itsec-file-change.php +1 -1
  23. core/modules/file-change/css/settings.css +14 -0
  24. core/modules/file-change/js/settings-page.js +15 -0
  25. core/modules/file-change/logs.php +11 -1
  26. core/modules/file-change/scanner.php +18 -8
  27. core/modules/file-change/settings-page.php +15 -4
  28. core/modules/notification-center/class-notification-center.php +4 -2
  29. core/modules/notification-center/debug.php +92 -0
  30. core/modules/notification-center/js/debug.js +22 -0
  31. core/modules/password-requirements/active.php +6 -0
  32. core/modules/password-requirements/class-itsec-password-requirements.php +480 -0
  33. core/modules/password-requirements/css/index.php +1 -0
  34. core/modules/password-requirements/css/settings-page.css +3 -0
  35. core/modules/password-requirements/index.php +1 -0
  36. core/modules/password-requirements/js/index.php +1 -0
  37. core/modules/password-requirements/js/settings-page.js +24 -0
  38. core/modules/password-requirements/settings-page.php +115 -0
  39. core/modules/password-requirements/settings.php +47 -0
  40. core/modules/password-requirements/validator.php +62 -0
  41. core/modules/pro/settings-page.php +0 -16
  42. core/modules/security-check/scanner.php +16 -1
  43. core/modules/strong-passwords/class-itsec-strong-passwords.php +139 -168
  44. core/modules/strong-passwords/js/script.js +1 -8
  45. core/modules/strong-passwords/settings-page.php +0 -51
  46. core/modules/strong-passwords/setup.php +26 -4
  47. core/notify.php +10 -5
  48. history.txt +17 -0
  49. readme.txt +21 -3
better-wp-security.php CHANGED
@@ -6,7 +6,7 @@
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.0.1
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
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.0.2
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
core/admin-pages/css/style.css CHANGED
@@ -851,3 +851,19 @@ body.security_page_itsec-logs #old-logs-migration-status img {
851
  body.security_page_itsec-logs #old-logs-migration-status p {
852
  line-height: 1.4em;
853
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851
  body.security_page_itsec-logs #old-logs-migration-status p {
852
  line-height: 1.4em;
853
  }
854
+ #itsec-system-info, #itsec-settings-editor {
855
+ font-family: monospace;
856
+ width: 100%;
857
+ margin: 0;
858
+ height: 300px;
859
+ padding: 20px;
860
+ border-radius: 0;
861
+ resize: none;
862
+ font-size: 12px;
863
+ line-height: 20px;
864
+ outline: 0;
865
+ }
866
+
867
+ #itsec-settings-editor:empty {
868
+ display: none;
869
+ }
core/admin-pages/init.php CHANGED
@@ -16,6 +16,7 @@ final class ITSEC_Admin_Page_Loader {
16
  add_action( 'wp_ajax_itsec_settings_page', array( $this, 'handle_ajax_request' ) );
17
  add_action( 'wp_ajax_itsec_logs_page', array( $this, 'handle_ajax_request' ) );
18
  add_action( 'wp_ajax_itsec_help_page', array( $this, 'handle_ajax_request' ) );
 
19
  add_action( 'wp_ajax_itsec-set-user-setting', array( $this, 'handle_user_setting' ) );
20
 
21
  // Filters for validating user settings
@@ -23,7 +24,7 @@ final class ITSEC_Admin_Page_Loader {
23
  }
24
 
25
  public function add_scripts() {
26
- ITSEC_Lib::enqueue_util();
27
  }
28
 
29
  public function add_styles() {
@@ -46,6 +47,10 @@ final class ITSEC_Admin_Page_Loader {
46
  $page_refs[] = add_submenu_page( 'itsec', '', '<span style="color:#2EA2CC">' . __( 'Go Pro', 'better-wp-security' ) . '</span>', $capability, 'itsec-go-pro', array( $this, 'show_page' ) );
47
  }
48
 
 
 
 
 
49
  foreach ( $page_refs as $page_ref ) {
50
  add_action( "load-$page_ref", array( $this, 'load' ) );
51
  }
16
  add_action( 'wp_ajax_itsec_settings_page', array( $this, 'handle_ajax_request' ) );
17
  add_action( 'wp_ajax_itsec_logs_page', array( $this, 'handle_ajax_request' ) );
18
  add_action( 'wp_ajax_itsec_help_page', array( $this, 'handle_ajax_request' ) );
19
+ add_action( 'wp_ajax_itsec_debug_page', array( $this, 'handle_ajax_request' ) );
20
  add_action( 'wp_ajax_itsec-set-user-setting', array( $this, 'handle_user_setting' ) );
21
 
22
  // Filters for validating user settings
24
  }
25
 
26
  public function add_scripts() {
27
+
28
  }
29
 
30
  public function add_styles() {
47
  $page_refs[] = add_submenu_page( 'itsec', '', '<span style="color:#2EA2CC">' . __( 'Go Pro', 'better-wp-security' ) . '</span>', $capability, 'itsec-go-pro', array( $this, 'show_page' ) );
48
  }
49
 
50
+ if ( defined( 'ITSEC_DEBUG' ) && ITSEC_DEBUG ) {
51
+ $page_refs[] = add_submenu_page( 'itsec', __( 'iThemes Security Debug', 'better-wp-security' ), __( 'Debug' ), $capability, 'itsec-debug', array( $this, 'show_page' ) );
52
+ }
53
+
54
  foreach ( $page_refs as $page_ref ) {
55
  add_action( "load-$page_ref", array( $this, 'load' ) );
56
  }
core/admin-pages/js/debug.js ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function ( $, itsecUtil, codeEditor ) {
2
+ $( function () {
3
+ var $messages = $( "#itsec-messages" );
4
+
5
+ $( '#itsec-scheduler-events' ).on( 'click', '.button', function () {
6
+
7
+ var $btn = $( this );
8
+ $btn.prop( 'disabled', true );
9
+
10
+ // We are purposely using attr() so as not to parse the data string as json.
11
+ itsecUtil.sendAJAXRequest( '', 'run_event', { id: $btn.data( 'id' ), data: $btn.attr( 'data-data' ) }, function ( result ) {
12
+
13
+ $btn.prop( 'disabled', false );
14
+
15
+ if ( result.success ) {
16
+ $( 'table', '#itsec-scheduler-events' ).replaceWith( result.response );
17
+ }
18
+
19
+ itsecUtil.displayNotices( result, $messages );
20
+ } );
21
+ } );
22
+
23
+ $( '#itsec-scheduler-reset' ).on( 'click', function () {
24
+
25
+ var $btn = $( this );
26
+ $btn.prop( 'disabled', true );
27
+
28
+ itsecUtil.sendAJAXRequest( '', 'reset_scheduler', {}, function ( result ) {
29
+
30
+ $btn.prop( 'disabled', false );
31
+
32
+ if ( result.success ) {
33
+ $( 'table', '#itsec-scheduler-events' ).replaceWith( result.response );
34
+ }
35
+
36
+ itsecUtil.displayNotices( result, $messages );
37
+ } );
38
+ } );
39
+
40
+ var $saveBtn = $( '#itsec-settings-save' ), $loadBtn = $( "#itsec-settings-load" );
41
+
42
+ $loadBtn.on( 'click', function () {
43
+ $loadBtn.prop( 'disabled', true );
44
+
45
+ itsecUtil.sendAJAXRequest( $( '#itsec-settings-module' ).val(), 'load_settings', {}, function ( result ) {
46
+ itsecUtil.displayNotices( result, $messages );
47
+
48
+ $loadBtn.prop( 'disabled', false );
49
+ $saveBtn.prop( 'disabled', false );
50
+ setEditorContent( JSON.stringify( result.response, null, 4 ) );
51
+ } );
52
+ } );
53
+
54
+ $saveBtn.on( 'click', function () {
55
+
56
+ $loadBtn.prop( 'disabled', true );
57
+ $saveBtn.prop( 'disabled', true );
58
+
59
+ itsecUtil.sendAJAXRequest( $( '#itsec-settings-module' ).val(), 'save_settings', getEditorContent(), function ( result ) {
60
+ itsecUtil.displayNotices( result, $messages );
61
+
62
+ $loadBtn.prop( 'disabled', false );
63
+ $saveBtn.prop( 'disabled', false );
64
+
65
+ if ( result.success ) {
66
+ setEditorContent( JSON.stringify( result.response, null, 4 ) );
67
+ }
68
+ } );
69
+ } );
70
+
71
+ var $editor = $( "#itsec-settings-editor" ), editor;
72
+
73
+ function setEditorContent( content ) {
74
+ if ( codeEditor ) {
75
+ if ( !editor ) editor = codeEditor.initialize( $editor );
76
+ editor.codemirror.setValue( content );
77
+ } else {
78
+ $editor.val( content );
79
+ }
80
+ }
81
+
82
+ function getEditorContent() {
83
+ return editor ? editor.codemirror.getValue() : $editor.val();
84
+ }
85
+ } );
86
+ })( jQuery, window.itsecUtil, wp.codeEditor );
core/admin-pages/js/util.js CHANGED
@@ -166,6 +166,42 @@ var itsecUtil = {
166
  }
167
  // If the requested parameter doesn't exist, return false
168
  return false;
169
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  };
166
  }
167
  // If the requested parameter doesn't exist, return false
168
  return false;
169
+ },
170
+
171
+ buildNotices: function ( response, asAlt ) {
172
+ var notices = [],
173
+ types = ['error', 'warning', 'message', 'info'];
174
+
175
+ for ( var i = 0; i < types.length; i++ ) {
176
+ for ( var j = 0; j < response[types[i] + 's'].length; j++ ) {
177
+ notices.push( itsecUtil.makeNotice( response[types[i] + 's'][j], types[i], asAlt ) );
178
+ }
179
+ }
180
+
181
+ return notices;
182
+ },
183
+
184
+ makeNotice: function ( message, type, asAlt ) {
185
+ type = type === 'message' ? 'success' : type;
186
 
187
+ var className = 'notice notice-' + type;
188
+
189
+ if ( asAlt ) {
190
+ className += ' notice-alt';
191
+ }
192
+
193
+ return jQuery( '<div>', { class: className } )
194
+ .append( jQuery( '<p>', { html: message } ) );
195
+ },
196
+
197
+ displayNotices: function ( response, $container, asAlt ) {
198
+ var notices = itsecUtil.buildNotices( response, asAlt );
199
+
200
+ for ( var i = 0; i < notices.length; i++ ) {
201
+ (function ( $notice ) {
202
+ $container.append( $notice );
203
+ setTimeout( function () {$notice.remove();}, 10000 );
204
+ })( notices[i].clone() );
205
+ }
206
+ },
207
  };
core/admin-pages/page-debug.php ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ final class ITSEC_Debug_Page {
4
+
5
+ /** @var string */
6
+ private $self_url;
7
+
8
+ public function __construct() {
9
+ add_action( 'itsec-page-show', array( $this, 'handle_page_load' ) );
10
+ add_action( 'itsec-page-ajax', array( $this, 'handle_ajax_request' ) );
11
+ add_action( 'admin_print_scripts', array( $this, 'add_scripts' ) );
12
+ add_action( 'admin_print_styles', array( $this, 'add_styles' ) );
13
+
14
+ ITSEC_Modules::load_module_file( 'debug.php', ':active' );
15
+ }
16
+
17
+ public function handle_page_load( $self_url ) {
18
+ $this->self_url = $self_url;
19
+
20
+ $this->show_settings_page();
21
+ }
22
+
23
+ public function add_scripts() {
24
+
25
+ $deps = array( 'itsec-util' );
26
+
27
+ if ( function_exists( 'wp_enqueue_code_editor' ) ) {
28
+ $deps[] = 'code-editor';
29
+ wp_enqueue_code_editor( array(
30
+ 'type' => 'application/json'
31
+ ) );
32
+ }
33
+
34
+ ITSEC_Lib::enqueue_util( array( 'action' => 'itsec_debug_page', 'nonce' => 'itsec-debug-page' ) );
35
+ wp_enqueue_script( 'itsec-debug', plugins_url( 'js/debug.js', __FILE__ ), $deps, ITSEC_Core::get_plugin_build() );
36
+
37
+ do_action( 'itsec_debug_page_enqueue' );
38
+ }
39
+
40
+ public function add_styles() {
41
+ wp_enqueue_style( 'itsec-debug-page-style', plugins_url( 'css/style.css', __FILE__ ), array(), ITSEC_Core::get_plugin_build() );
42
+ }
43
+
44
+ public function handle_ajax_request() {
45
+ if ( WP_DEBUG ) {
46
+ ini_set( 'display_errors', 1 );
47
+ }
48
+
49
+ ITSEC_Core::set_interactive( true );
50
+
51
+ $method = ( isset( $_POST['method'] ) && is_string( $_POST['method'] ) ) ? $_POST['method'] : '';
52
+ $module = ( isset( $_POST['module'] ) && is_string( $_POST['module'] ) ) ? $_POST['module'] : '';
53
+
54
+ if ( empty( $GLOBALS['hook_suffix'] ) ) {
55
+ $GLOBALS['hook_suffix'] = 'security_page_itsec-debug';
56
+ }
57
+
58
+ if ( false === check_ajax_referer( 'itsec-debug-page', 'nonce', false ) ) {
59
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-failed-nonce', __( 'A nonce security check failed, preventing the request from completing as expected. Please try reloading the page and trying again.', 'better-wp-security' ) ) );
60
+ } elseif ( ! ITSEC_Core::current_user_can_manage() ) {
61
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-insufficient-privileges', __( 'A permissions security check failed, preventing the request from completing as expected. The currently logged in user does not have sufficient permissions to make this request. Please try reloading the page and trying again.', 'better-wp-security' ) ) );
62
+ } elseif ( empty( $method ) ) {
63
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-missing-method', __( 'The server did not receive a valid request. The required "method" argument is missing. Please try again.', 'better-wp-security' ) ) );
64
+ } elseif ( 'handle_module_request' === $method && empty( $module ) ) {
65
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-missing-module', __( 'The server did not receive a valid request. The required "module" argument is missing. Please try again.', 'better-wp-security' ) ) );
66
+ } elseif ( 'handle_module_request' === $method ) {
67
+ if ( isset( $_POST['data'] ) ) {
68
+ ITSEC_Modules::load_module_file( 'debug.php', ':active' );
69
+ /**
70
+ * Fires when an ajax request is being made to a module.
71
+ *
72
+ * At some point this will probably be replaced by a more thought-out framework, but this hook will probably power it.
73
+ *
74
+ * The dynamic portion of this hook, {$module}, refers to the module name. For example, 'notification-center'.
75
+ *
76
+ * @param array $data
77
+ */
78
+ do_action( "itsec_debug_module_request_{$module}", $_POST['data'] );
79
+ } else {
80
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-module-request-missing-data', __( 'The server did not receive a valid request. The required "data" argument for the module is missing. Please try again.', 'better-wp-security' ) ) );
81
+ }
82
+ } elseif ( 'reset_scheduler' === $method ) {
83
+ ITSEC_Core::get_scheduler()->uninstall();
84
+ ITSEC_Core::get_scheduler()->register_events();
85
+ ITSEC_Response::set_response( $this->get_events_table() );
86
+ ITSEC_Response::set_success( true );
87
+ ITSEC_Response::add_message( __( 'Scheduler reset.', 'better-wp-security' ) );
88
+ } elseif ( 'run_event' === $method ) {
89
+ if ( empty( $_POST['data']['id'] ) ) {
90
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-run-event-missing-id', __( 'The server did not receive a valid request. The required "data.id" argument for the "run_event" method is missing.', 'better-wp-security' ) ) );
91
+ } elseif ( ! empty( $_POST['data']['data'] ) ) {
92
+ $data = json_decode( wp_unslash( $_POST['data']['data'] ), true );
93
+
94
+ if ( ! is_array( $data ) ) {
95
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-run-event-invalid-data', __( 'The server did not receive a valid request. The "data.data" argument for the "run_event" method is invalid JSON.', 'better-wp-security' ) ) );
96
+ } else {
97
+ ITSEC_Core::get_scheduler()->run_single_event( $_POST['data']['id'], $data );
98
+ ITSEC_Response::set_response( $this->get_events_table() );
99
+ ITSEC_Response::set_success( true );
100
+ ITSEC_Response::add_message( __( 'Event successfully run.', 'better-wp-security' ) );
101
+ }
102
+ } else {
103
+ ITSEC_Core::get_scheduler()->run_recurring_event( $_POST['data']['id'] );
104
+ ITSEC_Response::set_response( $this->get_events_table() );
105
+ ITSEC_Response::set_success( true );
106
+ ITSEC_Response::add_message( __( 'Event successfully run.', 'better-wp-security' ) );
107
+ }
108
+ } elseif ( 'load_settings' === $method ) {
109
+ ITSEC_Response::set_response( ITSEC_Modules::get_settings( $module ) );
110
+ } elseif ( 'save_settings' === $method ) {
111
+ $data = json_decode( wp_unslash( $_POST['data'] ), true );
112
+
113
+ if ( ! is_array( $data ) ) {
114
+ ITSEC_Response::set_success( false );
115
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-run-event-invalid-data', __( 'The server did not receive a valid request. The "data" argument for the "save_settings" method is invalid.', 'better-wp-security' ) ) );
116
+ } else {
117
+ $result = ITSEC_Modules::set_settings( $module, $data );
118
+
119
+ if ( is_wp_error( $result ) ) {
120
+ ITSEC_Response::set_success( false );
121
+ ITSEC_Response::add_error( $result );
122
+ } else {
123
+ ITSEC_Response::set_response( ITSEC_Modules::get_settings( $module ) );
124
+
125
+ if ( $result['saved'] ) {
126
+ ITSEC_Response::add_message( esc_html__( 'Module settings updated.', 'better-wp-security' ) );
127
+ }
128
+ }
129
+ }
130
+ } else {
131
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-unknown-method', __( 'The server did not receive a valid request. An unknown "method" argument was supplied. Please try again.', 'better-wp-security' ) ) );
132
+ }
133
+
134
+ ITSEC_Response::send_json();
135
+ }
136
+
137
+ private function show_settings_page() {
138
+
139
+ $sysinfo = $this->get_sysinfo();
140
+
141
+ $out = '';
142
+
143
+ foreach ( $sysinfo as $category => $info ) {
144
+ if ( $out ) {
145
+ $out .= "\r\n";
146
+ }
147
+
148
+ $out .= "### {$category} ###\r\n";
149
+
150
+ foreach ( $info as $label => $value ) {
151
+ $out .= "{$label}: {$value}\r\n";
152
+ }
153
+ }
154
+
155
+ $out = rtrim( $out );
156
+
157
+ $scheduler = ITSEC_Core::get_scheduler();
158
+
159
+ $modules = array();
160
+
161
+ foreach ( ITSEC_Modules::get_available_modules() as $module ) {
162
+ if ( ITSEC_Modules::get_settings_obj( $module ) ) {
163
+ $modules[ $module ] = $module;
164
+ }
165
+ }
166
+
167
+ sort( $modules );
168
+ ?>
169
+ <div class="wrap">
170
+ <h1>
171
+ <?php _e( 'iThemes Security', 'better-wp-security' ); ?>
172
+ <a href="<?php echo esc_url( ITSEC_Core::get_settings_page_url() ); ?>" class="page-title-action"><?php _e( 'Manage Settings', 'better-wp-security' ); ?></a>
173
+ <a href="<?php echo esc_url( apply_filters( 'itsec_support_url', 'https://wordpress.org/support/plugin/better-wp-security' ) ); ?>" class="page-title-action">
174
+ <?php _e( 'Support', 'better-wp-security' ); ?>
175
+ </a>
176
+ </h1>
177
+
178
+ <div id="itsec-messages"></div>
179
+
180
+ <div>
181
+ <h2><?php esc_html_e( 'System Info', 'better-wp-security' ); ?></h2>
182
+ <label for="itsec-system-info"><?php esc_html__( 'System Info Summary', 'better-wp-security' ); ?></label>
183
+ <textarea readonly id="itsec-system-info"><?php echo esc_textarea( $out ); ?></textarea>
184
+ </div>
185
+
186
+ <div>
187
+ <h2><?php esc_html_e( 'Settings', 'better-wp-security' ); ?></h2>
188
+ <p>
189
+ <label for="itsec-settings-module" class="screen-reader-text"><?php esc_html_e( 'Module', 'better-wp-security' ); ?></label>
190
+ <select id="itsec-settings-module">
191
+ <?php foreach ( $modules as $module ) : ?>
192
+ <option value="<?php echo esc_attr( $module ); ?>"><?php echo esc_html( $module ); ?></option>
193
+ <?php endforeach; ?>
194
+ </select>
195
+ <button class="button" id="itsec-settings-load"><?php esc_html_e( 'Load', 'better-wp-security' ); ?></button>
196
+ <button class="button" id="itsec-settings-save" disabled><?php esc_html_e( 'Save', 'better-wp-security' ) ?></button>
197
+ </p>
198
+ <label for="itsec-settings-editor" class="screen-reader-text"><?php esc_html_e( 'Edit Settings', 'better-wp-security' ); ?></label>
199
+ <textarea id="itsec-settings-editor"></textarea>
200
+ </div>
201
+
202
+ <div id="itsec-scheduler-events">
203
+ <h2><?php esc_html_e( 'Scheduler', 'better-wp-security' ); ?></h2>
204
+ <?php echo $this->get_events_table(); ?>
205
+ <p style="text-align: right;">
206
+ <code><?php echo get_class( $scheduler ); ?></code>
207
+ <button class="button" id="itsec-scheduler-reset"><?php esc_html_e( 'Reset', 'better-wp-security' ) ?></button>
208
+ </p>
209
+ </div>
210
+
211
+ <?php do_action( 'itsec_debug_page' ); ?>
212
+ </div>
213
+ <?php
214
+ }
215
+
216
+ private function get_events_table() {
217
+ $scheduler = ITSEC_Core::get_scheduler();
218
+ ob_start();
219
+
220
+ ?>
221
+
222
+ <table class="widefat striped">
223
+ <thead>
224
+ <tr>
225
+ <th><?php esc_html_e( 'ID', 'better-wp-security' ) ?></th>
226
+ <th><?php esc_html_e( 'Fire At', 'better-wp-security' ) ?></th>
227
+ <th><?php esc_html_e( 'Schedule', 'better-wp-security' ) ?></th>
228
+ <th><?php esc_html_e( 'Data', 'better-wp-security' ) ?></th>
229
+ <th></th>
230
+ </tr>
231
+ </thead>
232
+ <tbody>
233
+ <?php foreach ( array_merge( $scheduler->get_recurring_events(), $scheduler->get_single_events() ) as $event ) : ?>
234
+ <tr>
235
+ <td><?php echo esc_html( $event['id'] ); ?></td>
236
+ <td><?php echo date( 'Y-m-d H:i:s', $event['fire_at'] ); ?></td>
237
+ <td><?php echo isset( $event['schedule'] ) ? $event['schedule'] : '–'; ?></td>
238
+ <td><?php $event['data'] ? ITSEC_Lib::print_r( $event['data'] ) : print( '–' ); ?></td>
239
+ <td>
240
+ <button class="button" data-id="<?php echo esc_attr( $event['id'] ); ?>"
241
+ data-data="<?php echo isset( $event['schedule'] ) ? '' : esc_attr( wp_json_encode( $event['data'] ) ); ?>">
242
+ <?php esc_html_e( 'Run', 'better-wp-security' ) ?>
243
+ </button>
244
+ </td>
245
+ </tr>
246
+ <?php endforeach; ?>
247
+ </tbody>
248
+ </table>
249
+
250
+ <?php
251
+
252
+ return ob_get_clean();
253
+ }
254
+
255
+ private function get_sysinfo() {
256
+
257
+ /** @var $wpdb wpdb */
258
+ global $wpdb;
259
+
260
+ $info = array();
261
+
262
+ $info['Site Info'] = array(
263
+ 'Site URL' => site_url(),
264
+ 'Home URL' => home_url(),
265
+ 'Multisite' => is_multisite() ? 'Yes' : 'No'
266
+ );
267
+
268
+ $wp_config = array(
269
+ 'Version' => get_bloginfo( 'version' ),
270
+ 'Language' => defined( 'WPLANG' ) && WPLANG ? WPLANG : 'en_US',
271
+ 'Permalink' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default',
272
+ 'Theme' => wp_get_theme()->Name . ' ' . wp_get_theme()->Version,
273
+ 'Show on Front' => get_option( 'show_on_front' )
274
+ );
275
+
276
+ if ( get_option( 'show_on_front' ) === 'page' ) {
277
+ $front_page_id = get_option( 'page_on_front' );
278
+ $blog_page_id = get_option( 'page_for_posts' );
279
+
280
+ $wp_config['Page On Front'] = $front_page_id ? get_the_title( $front_page_id ) . " (#$front_page_id)" : 'Unset';
281
+ $wp_config['Page For Posts'] = $blog_page_id ? get_the_title( $blog_page_id ) . " (#$blog_page_id)" : 'Unset';
282
+ }
283
+
284
+ $wp_config['ABSPATH'] = ABSPATH;
285
+ $wp_config['Table Prefix'] = 'Length: ' . strlen( $wpdb->prefix ) . ' Status: ' . ( strlen( $wpdb->prefix ) > 16 ? 'Too long' : 'Acceptable' );
286
+ $wp_config['WP_DEBUG'] = defined( 'WP_DEBUG' ) ? WP_DEBUG ? 'Enabled' : 'Disabled' : 'Not set';
287
+ $wp_config['WP_DEBUG_LOG'] = defined( 'WP_DEBUG_LOG' ) ? WP_DEBUG_LOG ? 'Enabled' : 'Disabled' : 'Not set';
288
+ $wp_config['SCRIPT_DEBUG'] = defined( 'SCRIPT_DEBUG' ) ? SCRIPT_DEBUG ? 'Enabled' : 'Disabled' : 'Not set';
289
+ $wp_config['Object Cache'] = wp_using_ext_object_cache() ? 'Yes' : 'No';
290
+ $wp_config['Memory Limit'] = WP_MEMORY_LIMIT;
291
+ $info['WordPress Configuration'] = $wp_config;
292
+
293
+ $defines = array(
294
+ 'ITSEC_USE_CRON',
295
+ 'ITSEC_DISABLE_PASSWORD_REQUIREMENTS',
296
+ 'ITSEC_DEVELOPMENT',
297
+ 'ITSEC_DISABLE_MODULES',
298
+ 'ITSEC_DISABLE_TWO_FACTOR',
299
+ 'ITSEC_DISABLE_CRON_TEST',
300
+ 'ITSEC_SERVER_OVERRIDE',
301
+ 'ITSEC_DOING_FILE_CHECK',
302
+ 'ITSEC_TEST_MALWARE_SCAN_SKIP_CACHE',
303
+ 'ITSEC_TEST_MALWARE_SCAN_SITE_URL',
304
+ 'ITSEC_TEST_MALWARE_SCAN_DISABLE_SSL_VERIFY',
305
+ 'ITSEC_SUCURI_KEY',
306
+ 'ITSEC_NOTIFY_USE_CRON',
307
+ 'ITSEC_DISABLE_SECURITY_CHECK_PRO',
308
+ 'ITSEC_DISABLE_AUTOMATIC_REMOTE_IP_DETECTION',
309
+ 'ITSEC_DISABLE_PASSWORD_STRENGTH',
310
+ 'ITSEC_DISABLE_INACTIVE_USER_CHECK',
311
+ );
312
+
313
+ $info['iThemes Security'] = array(
314
+ 'Build' => ITSEC_Core::get_plugin_build(),
315
+ 'Pro' => ITSEC_Core::is_pro(),
316
+ 'Modules' => wp_sprintf( '%l', ITSEC_Modules::get_active_modules() ),
317
+ 'Cron' => ITSEC_Lib::use_cron(),
318
+ 'Cron Status' => ITSEC_Lib::is_cron_working(),
319
+ 'Scheduler' => get_class( ITSEC_Core::get_scheduler() ),
320
+ );
321
+
322
+ foreach ( $defines as $define ) {
323
+ if ( defined( $define ) ) {
324
+ $value = constant( $define );
325
+ $info['iThemes Security'][ $define ] = $value === true ? 'Enabled' : $value === false ? 'Disabled' : $value;
326
+ }
327
+ }
328
+
329
+ $plugins = get_plugins();
330
+ $active_plugins = get_option( 'active_plugins', array() );
331
+
332
+ foreach ( $plugins as $plugin_path => $plugin ) {
333
+
334
+ if ( ! in_array( $plugin_path, $active_plugins, true ) ) {
335
+ continue;
336
+ }
337
+
338
+ $info['Active Plugins'][ $plugin['Name'] ] = $plugin['Version'];
339
+ }
340
+
341
+ foreach ( get_mu_plugins() as $plugin ) {
342
+ $info['MU Plugins'][ $plugin['Name'] ] = $plugin['Version'];
343
+ }
344
+
345
+ if ( is_multisite() ) {
346
+ $plugins = wp_get_active_network_plugins();
347
+ $active_plugins = get_site_option( 'active_sitewide_plugins', array() );
348
+
349
+ foreach ( $plugins as $plugin_path ) {
350
+
351
+ $plugin_base = plugin_basename( $plugin_path );
352
+
353
+ if ( ! array_key_exists( $plugin_base, $active_plugins ) ) {
354
+ continue;
355
+ }
356
+
357
+ $plugin = get_plugin_data( $plugin_path );
358
+
359
+ $info['Network Active Plugins'][ $plugin['Name'] ] = $plugin['Version'];
360
+ }
361
+ }
362
+
363
+ $info['Webserver Configuration'] = array(
364
+ 'PHP Version' => PHP_VERSION,
365
+ 'MySQL Version' => $wpdb->db_version(),
366
+ 'Use MySQLi' => $wpdb->use_mysqli ? 'Yes' : 'No',
367
+ 'Webserver Info' => ITSEC_Lib::get_server(),
368
+ 'Host' => $this->get_host(),
369
+ );
370
+
371
+ $info['PHP Configuration'] = array(
372
+ 'Safe Mode' => ini_get( 'safe_mode' ) ? 'Enabled' : 'Disabled',
373
+ 'Memory Limit' => ini_get( 'memory_limit' ),
374
+ 'Upload Max Size' => ini_get( 'upload_max_filesize' ),
375
+ 'Post Max Size' => ini_get( 'post_max_size' ),
376
+ 'Upload Max Filesize' => ini_get( 'upload_max_filesize' ),
377
+ 'Time Limit' => ini_get( 'max_execution_time' ),
378
+ 'Max Input Vars' => ini_get( 'max_input_vars' ),
379
+ 'Display Errors' => ini_get( 'display_errors' ) ? 'On (' . ini_get( 'display_errors' ) . ')' : 'N/A'
380
+ );
381
+
382
+ $info['PHP Extensions'] = array(
383
+ 'cURL' => function_exists( 'curl_init' ) ? 'Supported' : 'Not Supported',
384
+ 'fsockopen' => function_exists( 'fsockopen' ) ? 'Supported' : 'Not Supported',
385
+ 'SOAP Client' => class_exists( 'SoapClient' ) ? 'Installed' : 'Not Installed',
386
+ 'Suhosin' => extension_loaded( 'suhosin' ) ? 'Installed' : 'Not Installed'
387
+ );
388
+
389
+ return $info;
390
+ }
391
+
392
+ private function get_host() {
393
+
394
+ if ( defined( 'WPE_APIKEY' ) ) {
395
+ $host = 'WP Engine';
396
+ } elseif ( defined( 'PAGELYBIN' ) ) {
397
+ $host = 'Pagely';
398
+ } elseif ( DB_HOST === 'localhost:/tmp/mysql5.sock' ) {
399
+ $host = 'ICDSoft';
400
+ } elseif ( DB_HOST === 'mysqlv5' ) {
401
+ $host = 'NetworkSolutions';
402
+ } elseif ( strpos( DB_HOST, 'ipagemysql.com' ) !== false ) {
403
+ $host = 'iPage';
404
+ } elseif ( strpos( DB_HOST, 'ipowermysql.com' ) !== false ) {
405
+ $host = 'IPower';
406
+ } elseif ( strpos( DB_HOST, '.gridserver.com' ) !== false ) {
407
+ $host = 'MediaTemple Grid';
408
+ } elseif ( strpos( DB_HOST, '.pair.com' ) !== false ) {
409
+ $host = 'pair Networks';
410
+ } elseif ( strpos( DB_HOST, '.stabletransit.com' ) !== false ) {
411
+ $host = 'Rackspace Cloud';
412
+ } elseif ( strpos( DB_HOST, '.sysfix.eu' ) !== false ) {
413
+ $host = 'SysFix.eu Power Hosting';
414
+ } elseif ( isset( $_SERVER['SERVER_NAME'] ) && strpos( $_SERVER['SERVER_NAME'], 'Flywheel' ) !== false ) {
415
+ $host = 'Flywheel';
416
+ } else {
417
+ // Adding a general fallback for data gathering
418
+ $host = 'DBH/' . DB_HOST . ', SRV/' . ( isset( $_SERVER['SERVER_NAME'] ) ? $_SERVER['SERVER_NAME'] : '' );
419
+ }
420
+
421
+ return $host;
422
+ }
423
+ }
424
+
425
+ new ITSEC_Debug_Page();
core/admin-pages/page-logs.php CHANGED
@@ -37,6 +37,8 @@ final class ITSEC_Logs_Page {
37
  }
38
 
39
  public function add_scripts() {
 
 
40
  foreach ( $this->modules as $id => $module ) {
41
  $module->enqueue_scripts_and_styles();
42
  }
37
  }
38
 
39
  public function add_scripts() {
40
+ ITSEC_Lib::enqueue_util();
41
+
42
  foreach ( $this->modules as $id => $module ) {
43
  $module->enqueue_scripts_and_styles();
44
  }
core/admin-pages/page-settings.php CHANGED
@@ -68,6 +68,8 @@ final class ITSEC_Settings_Page {
68
  }
69
 
70
  public function add_scripts() {
 
 
71
  foreach ( $this->modules as $id => $module ) {
72
  $module->enqueue_scripts_and_styles();
73
  }
68
  }
69
 
70
  public function add_scripts() {
71
+ ITSEC_Lib::enqueue_util();
72
+
73
  foreach ( $this->modules as $id => $module ) {
74
  $module->enqueue_scripts_and_styles();
75
  }
core/core.php CHANGED
@@ -24,7 +24,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
24
  *
25
  * @access private
26
  */
27
- private $plugin_build = 4095;
28
 
29
  /**
30
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
@@ -163,13 +163,6 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
163
  add_action( 'admin_bar_menu', array( $this, 'modify_admin_bar' ), 99 );
164
  }
165
 
166
- $disabled = defined( 'ITSEC_DISABLE_PASSWORD_REQUIREMENTS') && ITSEC_DISABLE_PASSWORD_REQUIREMENTS;
167
-
168
- if ( ! $disabled && has_action( 'itsec_validate_password' ) ) {
169
- $pass_requirements = new ITSEC_Lib_Password_Requirements();
170
- $pass_requirements->run();
171
- }
172
-
173
  $login_interstitial = new ITSEC_Lib_Login_Interstitial();
174
  $login_interstitial->run();
175
 
@@ -328,8 +321,13 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
328
  }
329
 
330
  ITSEC_Modules::register_module( 'network-brute-force', "$path/modules/ipcheck", 'default-active' );
 
 
 
 
 
331
  ITSEC_Modules::register_module( 'ssl', "$path/modules/ssl" );
332
- ITSEC_Modules::register_module( 'strong-passwords', "$path/modules/strong-passwords", 'default-active' );
333
  ITSEC_Modules::register_module( 'system-tweaks', "$path/modules/system-tweaks" );
334
  ITSEC_Modules::register_module( 'wordpress-salts', "$path/modules/salts", 'always-active' );
335
  ITSEC_Modules::register_module( 'wordpress-tweaks', "$path/modules/wordpress-tweaks", 'default-active' );
24
  *
25
  * @access private
26
  */
27
+ private $plugin_build = 4097;
28
 
29
  /**
30
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
163
  add_action( 'admin_bar_menu', array( $this, 'modify_admin_bar' ), 99 );
164
  }
165
 
 
 
 
 
 
 
 
166
  $login_interstitial = new ITSEC_Lib_Login_Interstitial();
167
  $login_interstitial->run();
168
 
321
  }
322
 
323
  ITSEC_Modules::register_module( 'network-brute-force', "$path/modules/ipcheck", 'default-active' );
324
+
325
+ if ( ! defined( 'ITSEC_DISABLE_PASSWORD_REQUIREMENTS') || ! ITSEC_DISABLE_PASSWORD_REQUIREMENTS ) {
326
+ ITSEC_Modules::register_module( 'password-requirements', "$path/modules/password-requirements/", 'always-active' );
327
+ }
328
+
329
  ITSEC_Modules::register_module( 'ssl', "$path/modules/ssl" );
330
+ ITSEC_Modules::register_module( 'strong-passwords', "$path/modules/strong-passwords", 'always-active' );
331
  ITSEC_Modules::register_module( 'system-tweaks', "$path/modules/system-tweaks" );
332
  ITSEC_Modules::register_module( 'wordpress-salts', "$path/modules/salts", 'always-active' );
333
  ITSEC_Modules::register_module( 'wordpress-tweaks', "$path/modules/wordpress-tweaks", 'default-active' );
core/history.txt CHANGED
@@ -697,4 +697,25 @@
697
  Bug Fix: Changed the rules generated by the Filter Suspicious Query Strings feature in order to avoid blocking privacy export/erasure request confirmations.
698
  4.5.1 - 2018-05-25 - Chris Jean & Timothy Jacobs
699
  Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
700
- Bug Fix: Skip recovery if File Change storage is empty.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  Bug Fix: Changed the rules generated by the Filter Suspicious Query Strings feature in order to avoid blocking privacy export/erasure request confirmations.
698
  4.5.1 - 2018-05-25 - Chris Jean & Timothy Jacobs
699
  Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
700
+ Bug Fix: Skip recovery if File Change storage is empty.
701
+ 4.5.2 - 2018-05-31 - Chris Jean & Timothy Jacobs
702
+ Enhancement: Add UI to cancel in progress File Scan.
703
+ Tweak: Don't write to the tracked files setting if the file hash has not changed.
704
+ Bug Fix: Ensure scheduling lock is cleared by the Cron Scheduler when not proceeding with running events.
705
+ Bug Fix: Away Mode would not lock out users who were already logged-in during the "away" period.
706
+ Bug Fix: Prevent File Change from getting stuck in an infinite rescheduling loop on the first step.
707
+ 4.6.0 - 2018-06-07 - Chris Jean & Timothy Jacobs
708
+ Enhancement: Introduce Password Requirements module for managing and enforcing password requirements.
709
+ Enhancement: Continually evaluate password strength for users instead of only during registration.
710
+ Enhancement: Add basic admin debug page to help diagnosing and resolving issues. Particularly with the events.
711
+ Bug Fix: Password strength would not be evaluated if password was set using custom PHP or CLI commands.
712
+ Bug Fix: Only hide "Acknowledge Weak Password" checkbox if the user was not allowed to use a weak password.
713
+ 4.6.1 - 2018-06-11 - Chris Jean & Timothy Jacobs
714
+ Enhancement: Add debug settings JSON editor.
715
+ Tweak: If no last password change date is recorded for the user, treat their registration date as the last change date.
716
+ Bug Fix: If a password requirement has been disabled or is no longer available, don't consider the password as needing a change.
717
+ Bug Fix: Remove distributed storage table on uninstall.
718
+ 4.6.2 - 2018-06-12 - Chris Jean & Timothy Jacobs
719
+ Bug Fix: Accessing password requirement settings would not resolve properly in some instances.
720
+ 4.6.3 - 2018-06-14 - Chris Jean & Timothy Jacobs
721
+ Bug Fix: Enforce the Strong Passwords requirement during Security Check.
core/lib.php CHANGED
@@ -1068,8 +1068,10 @@ final class ITSEC_Lib {
1068
  * Enqueue the itsec_util script.
1069
  *
1070
  * Will only be included once per page.
 
 
1071
  */
1072
- public static function enqueue_util() {
1073
 
1074
  static $enqueued = false;
1075
 
@@ -1097,8 +1099,8 @@ final class ITSEC_Lib {
1097
 
1098
  wp_enqueue_script( 'itsec-util', plugins_url( 'admin-pages/js/util.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build(), true );
1099
  wp_localize_script( 'itsec-util', 'itsec_util', array(
1100
- 'ajax_action' => 'itsec_settings_page',
1101
- 'ajax_nonce' => wp_create_nonce( 'itsec-settings-nonce' ),
1102
  'translations' => $translations,
1103
  ) );
1104
 
@@ -1153,4 +1155,32 @@ final class ITSEC_Lib {
1153
 
1154
  return $array;
1155
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1156
  }
1068
  * Enqueue the itsec_util script.
1069
  *
1070
  * Will only be included once per page.
1071
+ *
1072
+ * @param array $args
1073
  */
1074
+ public static function enqueue_util( $args = array() ) {
1075
 
1076
  static $enqueued = false;
1077
 
1099
 
1100
  wp_enqueue_script( 'itsec-util', plugins_url( 'admin-pages/js/util.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build(), true );
1101
  wp_localize_script( 'itsec-util', 'itsec_util', array(
1102
+ 'ajax_action' => isset( $args['action'] ) ? $args['action'] : 'itsec_settings_page',
1103
+ 'ajax_nonce' => wp_create_nonce( isset( $args['nonce'] ) ? $args['nonce'] : 'itsec-settings-nonce' ),
1104
  'translations' => $translations,
1105
  ) );
1106
 
1155
 
1156
  return $array;
1157
  }
1158
+
1159
+ /**
1160
+ * Insert an element after a given key.
1161
+ *
1162
+ * @param string|int $key
1163
+ * @param array $array
1164
+ * @param string|int $new_key
1165
+ * @param mixed $new_value
1166
+ *
1167
+ * @return array
1168
+ */
1169
+ public static function array_insert_after( $key, $array, $new_key, $new_value ) {
1170
+ if ( array_key_exists( $key, $array ) ) {
1171
+ $new = array();
1172
+ foreach ( $array as $k => $value ) {
1173
+ $new[ $k ] = $value;
1174
+ if ( $k === $key ) {
1175
+ $new[ $new_key ] = $new_value;
1176
+ }
1177
+ }
1178
+
1179
+ return $new;
1180
+ }
1181
+
1182
+ $array[ $new_key ] = $new_value;
1183
+
1184
+ return $array;
1185
+ }
1186
  }
core/lib/class-itsec-lib-canonical-roles.php CHANGED
@@ -122,6 +122,49 @@ final class ITSEC_Lib_Canonical_Roles {
122
  return '';
123
  }
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  /**
126
  * Get a list of all of the capabilities that are unique to each role.
127
  *
122
  return '';
123
  }
124
 
125
+ /**
126
+ * Get the canonical role from any WordPress role.
127
+ *
128
+ * @param string $role
129
+ *
130
+ * @return string
131
+ */
132
+ public static function get_canonical_role_from_role( $role ) {
133
+ return self::get_role_from_caps( array_keys( array_filter( wp_roles()->get_role( $role )->capabilities ) ) );
134
+ }
135
+
136
+ /**
137
+ * Retrieve a canonical role for a user and a role.
138
+ *
139
+ * @param string $role
140
+ * @param WP_User $user
141
+ *
142
+ * @return string
143
+ */
144
+ public static function get_canonical_role_from_role_and_user( $role, $user ) {
145
+ $user = ITSEC_Lib::get_user( $user );
146
+
147
+ if ( empty( $role ) ) {
148
+ $role_caps = array();
149
+ } else {
150
+ $role_caps = array_keys( array_filter( wp_roles()->get_role( $role )->capabilities ) );
151
+ }
152
+
153
+ $user_caps = array();
154
+
155
+ if ( isset( $user->caps ) ) {
156
+ $wp_roles = wp_roles();
157
+
158
+ foreach ( $user->caps as $cap => $has ) {
159
+ if ( $has && ! $wp_roles->is_role( $cap ) ) {
160
+ $user_caps[] = $has;
161
+ }
162
+ }
163
+ }
164
+
165
+ return self::get_role_from_caps( array_merge( $role_caps, $user_caps ) );
166
+ }
167
+
168
  /**
169
  * Get a list of all of the capabilities that are unique to each role.
170
  *
core/lib/class-itsec-lib-password-requirements.php CHANGED
@@ -1,186 +1,75 @@
1
  <?php
2
- /**
3
- * Tool to manage password requirements across modules.
4
- *
5
- * @since 3.9.0
6
- * @license GPLv2+
7
- */
8
 
9
  /**
10
  * Class ITSEC_Lib_Password_Requirements
11
  */
12
  class ITSEC_Lib_Password_Requirements {
13
 
14
- const LOGIN_ACTION = 'itsec_update_password';
15
- const META_KEY = '_itsec_update_password_key';
16
-
17
- /** @var string */
18
- private $error_message = '';
19
-
20
- public function run() {
21
-
22
- add_action( 'user_profile_update_errors', array( $this, 'forward_profile_pass_update' ), 0, 3 );
23
- add_action( 'validate_password_reset', array( $this, 'forward_reset_pass' ), 10, 2 );
24
- add_action( 'profile_update', array( $this, 'set_password_last_updated' ), 10, 2 );
25
-
26
- add_action( 'itsec_login_interstitial_init', array( $this, 'register_interstitial' ) );
27
- }
28
 
29
  /**
30
- * When a user's password is updated, or a new user created, verify that the new password is valid.
31
  *
32
- * @param WP_Error $errors
33
- * @param bool $update
34
- * @param WP_User|stdClass $user
35
  */
36
- public function forward_profile_pass_update( $errors, $update, $user ) {
37
-
38
- if ( ! isset( $user->user_pass ) ) {
39
- return;
40
- }
41
-
42
- if ( ! $update ) {
43
- $context = 'admin-user-create';
44
- } elseif ( isset( $user->ID ) && $user->ID === get_current_user_id() ) {
45
- $context = 'profile-update';
46
- } else {
47
- $context = 'admin-profile-update';
48
- }
49
-
50
- $args = array(
51
- 'error' => $errors,
52
- 'context' => $context
53
- );
54
-
55
- if ( isset( $user->role ) ) {
56
- $args['role'] = $user->role;
57
  }
58
 
59
- self::validate_password( $user, $user->user_pass, $args );
60
  }
61
 
62
  /**
63
- * When a user attempts to reset their password, verify that the new password is valid.
64
  *
65
- * @param WP_Error $errors
66
- * @param WP_User $user
67
  */
68
- public function forward_reset_pass( $errors, $user ) {
69
-
70
- if ( ! isset( $_POST['pass1'] ) || is_wp_error( $user ) ) {
71
- // The validate_password_reset action fires when first rendering the reset page and when handling the form
72
- // submissions. Since the pass1 data is missing, this must be the initial page render. So, we don't need to
73
- // do anything yet.
74
- return;
75
- }
76
-
77
- self::validate_password( $user, $_POST['pass1'], array(
78
- 'error' => $errors,
79
- 'context' => 'reset-password',
80
  ) );
81
- }
82
-
83
- /**
84
- * Whenever a user object is updated, set when their password was last updated.
85
- *
86
- * @param int $user_id
87
- * @param object $old_user_data
88
- */
89
- public function set_password_last_updated( $user_id, $old_user_data ) {
90
-
91
- $user = get_userdata( $user_id );
92
 
93
- if ( $user->user_pass === $old_user_data->user_pass ) {
 
 
 
94
  return;
95
  }
96
 
97
- delete_user_meta( $user_id, 'itsec_password_change_required' );
98
- update_user_meta( $user_id, 'itsec_last_password_change', ITSEC_Core::get_current_time_gmt() );
99
- }
100
-
101
- /**
102
- * Register the password change interstitial.
103
- *
104
- * @param ITSEC_Lib_Login_Interstitial $lib
105
- */
106
- public function register_interstitial( $lib ) {
107
- $lib->register( 'update-password', array( $this, 'render_interstitial' ), array(
108
- 'show_to_user' => array( __CLASS__, 'password_change_required' ),
109
- 'info_message' => array( __CLASS__, 'get_message_for_password_change_reason' ),
110
- 'submit' => array( $this, 'submit' ),
111
- ) );
112
- }
113
-
114
- /**
115
- * Render the interstitial.
116
- *
117
- * @param WP_User $user
118
- */
119
- public function render_interstitial( $user ) {
120
- ?>
121
-
122
- <div class="user-pass1-wrap">
123
- <p><label for="pass1"><?php _e( 'New Password', 'better-wp-security' ); ?></label></p>
124
- </div>
125
-
126
- <div class="wp-pwd">
127
- <span class="password-input-wrapper">
128
- <input type="password" data-reveal="1"
129
- data-pw="<?php echo esc_attr( wp_generate_password( 16 ) ); ?>" name="pass1" id="pass1"
130
- class="input" size="20" value="" autocomplete="off" aria-describedby="pass-strength-result"/>
131
- </span>
132
- <div id="pass-strength-result" class="hide-if-no-js" aria-live="polite"><?php _e( 'Strength indicator', 'better-wp-security' ); ?></div>
133
- </div>
134
-
135
- <p class="user-pass2-wrap">
136
- <label for="pass2"><?php _e( 'Confirm new password' ) ?></label><br/>
137
- <input type="password" name="pass2" id="pass2" class="input" size="20" value="" autocomplete="off"/>
138
- </p>
139
-
140
- <p class="description indicator-hint"><?php echo wp_get_password_hint(); ?></p>
141
- <br class="clear"/>
142
-
143
- <p class="submit">
144
- <input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large"
145
- value="<?php esc_attr_e( 'Update Password', 'better-wp-security' ); ?>"/>
146
- </p>
147
-
148
- <?php
149
- }
150
-
151
- /**
152
- * Handle the request to update the user's password.
153
- *
154
- * @param WP_User $user
155
- * @param array $data POSTed data.
156
- *
157
- * @return WP_Error|null
158
- */
159
- public function submit( $user, $data ) {
160
-
161
- if ( empty( $data['pass1'] ) ) {
162
- return new WP_Error(
163
- 'itsec-password-requirements-empty-password',
164
- __( 'Please enter your new password.', 'better-wp-security' )
165
- );
166
  }
167
 
168
- $error = self::validate_password( $user, $data['pass1'] );
 
 
 
169
 
170
- if ( $error->get_error_message() ) {
171
- return $error;
 
172
  }
173
 
174
- $error = wp_update_user( array(
175
- 'ID' => $user->ID,
176
- 'user_pass' => $data['pass1']
177
- ) );
178
-
179
- if ( is_wp_error( $error ) ) {
180
- return $error;
181
  }
182
 
183
- return null;
184
  }
185
 
186
  /**
@@ -196,14 +85,24 @@ class ITSEC_Lib_Password_Requirements {
196
  return '';
197
  }
198
 
 
 
 
 
 
 
 
 
 
199
  /**
200
  * Retrieve a human readable description as to why a password change has been required for the current user.
201
  *
202
  * Modules MUST HTML escape their reason strings before returning them with this filter.
203
  *
204
- * @param string $message
 
205
  */
206
- $message = apply_filters( "itsec_password_change_requirement_description_for_{$reason}", '' );
207
 
208
  if ( $message ) {
209
  return $message;
@@ -225,12 +124,12 @@ class ITSEC_Lib_Password_Requirements {
225
 
226
  $args = wp_parse_args( $args, array(
227
  'error' => new WP_Error(),
228
- 'context' => ''
229
  ) );
230
 
231
- $error = isset( $args['error'] ) ? $args['error'] : new WP_Error();
232
-
233
- $user = $user instanceof stdClass ? $user : ITSEC_Lib::get_user( $user );
234
 
235
  if ( ! $user ) {
236
  $error->add( 'invalid_user', esc_html__( 'Invalid User', 'better-wp-security' ) );
@@ -238,6 +137,27 @@ class ITSEC_Lib_Password_Requirements {
238
  return $error;
239
  }
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  /**
242
  * Fires when modules should validate a password according to their rules.
243
  *
@@ -287,9 +207,28 @@ class ITSEC_Lib_Password_Requirements {
287
  return false;
288
  }
289
 
 
 
 
 
 
 
 
 
 
 
290
  return $reason;
291
  }
292
 
 
 
 
 
 
 
 
 
 
293
  /**
294
  * Get the GMT time the user's password has last been changed.
295
  *
@@ -312,6 +251,64 @@ class ITSEC_Lib_Password_Requirements {
312
  return $deprecated;
313
  }
314
 
 
 
 
 
315
  return $changed;
316
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  }
1
  <?php
 
 
 
 
 
 
2
 
3
  /**
4
  * Class ITSEC_Lib_Password_Requirements
5
  */
6
  class ITSEC_Lib_Password_Requirements {
7
 
8
+ /** @var array[] */
9
+ private static $requirements;
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  /**
12
+ * Get all registered password requirements.
13
  *
14
+ * @return array
 
 
15
  */
16
+ public static function get_registered() {
17
+ if ( null === self::$requirements ) {
18
+ self::$requirements = array();
19
+
20
+ /**
21
+ * Fires when password requirements should be registered.
22
+ */
23
+ do_action( 'itsec_register_password_requirements' );
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
+ return self::$requirements;
27
  }
28
 
29
  /**
30
+ * Register a password requirement.
31
  *
32
+ * @param string $reason_code
33
+ * @param array $opts
34
  */
35
+ public static function register( $reason_code, $opts ) {
36
+ $merged = wp_parse_args( $opts, array(
37
+ 'evaluate' => null,
38
+ 'validate' => null,
39
+ 'flag_check' => null,
40
+ 'reason' => null,
41
+ 'defaults' => null,
42
+ 'settings_config' => null, // Callable returning label, description, render & sanitize callbacks.
43
+ 'meta' => "_itsec_password_evaluation_{$reason_code}",
44
+ 'evaluate_if_not_enabled' => false,
 
 
45
  ) );
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ if (
48
+ ( array_key_exists( 'validate', $opts ) || array_key_exists( 'evaluate', $opts ) ) &&
49
+ ( ! is_callable( $merged['validate'] ) || ! is_callable( $merged['evaluate'] ) )
50
+ ) {
51
  return;
52
  }
53
 
54
+ if ( array_key_exists( 'flag_check', $opts ) && ! is_callable( $merged['flag_check'] ) ) {
55
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
+ if ( array_key_exists( 'defaults', $opts ) ) {
59
+ if ( ! is_array( $merged['defaults'] ) ) {
60
+ return;
61
+ }
62
 
63
+ if ( ! array_key_exists( 'settings_config', $opts ) ) {
64
+ return;
65
+ }
66
  }
67
 
68
+ if ( array_key_exists( 'settings_config', $opts ) && ! is_callable( $merged['settings_config'] ) ) {
69
+ return;
 
 
 
 
 
70
  }
71
 
72
+ self::$requirements[ $reason_code ] = $merged;
73
  }
74
 
75
  /**
85
  return '';
86
  }
87
 
88
+ $message = '';
89
+
90
+ $registered = self::get_registered();
91
+
92
+ if ( isset( $registered[ $reason ] ) ) {
93
+ $settings = self::get_requirement_settings( $reason );
94
+ $message = call_user_func( $registered[ $reason ]['reason'], get_user_meta( $user->ID, $registered[ $reason ]['meta'], true ), $settings );
95
+ }
96
+
97
  /**
98
  * Retrieve a human readable description as to why a password change has been required for the current user.
99
  *
100
  * Modules MUST HTML escape their reason strings before returning them with this filter.
101
  *
102
+ * @param string $message
103
+ * @param WP_User $user
104
  */
105
+ $message = apply_filters( "itsec_password_change_requirement_description_for_{$reason}", $message, $user );
106
 
107
  if ( $message ) {
108
  return $message;
124
 
125
  $args = wp_parse_args( $args, array(
126
  'error' => new WP_Error(),
127
+ 'context' => '',
128
  ) );
129
 
130
+ /** @var WP_Error $error */
131
+ $error = $args['error'];
132
+ $user = $user instanceof stdClass ? $user : ITSEC_Lib::get_user( $user );
133
 
134
  if ( ! $user ) {
135
  $error->add( 'invalid_user', esc_html__( 'Invalid User', 'better-wp-security' ) );
137
  return $error;
138
  }
139
 
140
+ if ( ! empty( $user->ID ) && wp_check_password( $new_password, get_userdata( $user->ID )->user_pass, $user->ID ) ) {
141
+ $message = wp_kses( __( '<strong>ERROR</strong>: The password you have chosen appears to have been used before. You must choose a new password.', 'better-wp-security' ), array( 'strong' => array() ) );
142
+ $error->add( 'pass', $message );
143
+
144
+ return $error;
145
+ }
146
+
147
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
148
+
149
+ if ( isset( $args['role'] ) && $user instanceof WP_User ) {
150
+ $canonical = ITSEC_Lib_Canonical_Roles::get_canonical_role_from_role_and_user( $args['role'], $user );
151
+ } elseif ( isset( $args['role'] ) ) {
152
+ $canonical = ITSEC_Lib_Canonical_Roles::get_canonical_role_from_role( $args['role'] );
153
+ } elseif ( empty( $user->ID ) || ! is_numeric( $user->ID ) ) {
154
+ $canonical = ITSEC_Lib_Canonical_Roles::get_canonical_role_from_role( get_option( 'default_role', 'subscriber' ) );
155
+ } else {
156
+ $canonical = ITSEC_Lib_Canonical_Roles::get_user_role( $user );
157
+ }
158
+
159
+ $args['canonical'] = $canonical;
160
+
161
  /**
162
  * Fires when modules should validate a password according to their rules.
163
  *
207
  return false;
208
  }
209
 
210
+ $registered = self::get_registered();
211
+
212
+ if ( isset( $registered[ $reason ] ) ) {
213
+ return self::is_requirement_enabled( $reason ) ? $reason : false;
214
+ }
215
+
216
+ if ( ! has_filter( "itsec_password_change_requirement_description_for_{$reason}" ) ) {
217
+ return false;
218
+ }
219
+
220
  return $reason;
221
  }
222
 
223
+ /**
224
+ * Globally clear all required password changes with a particular reason code.
225
+ *
226
+ * @param string $reason
227
+ */
228
+ public static function global_clear_required_password_change( $reason ) {
229
+ delete_metadata( 'user', 0, 'itsec_password_change_required', $reason, true );
230
+ }
231
+
232
  /**
233
  * Get the GMT time the user's password has last been changed.
234
  *
251
  return $deprecated;
252
  }
253
 
254
+ if ( ! $changed ) {
255
+ return strtotime( $user->user_registered );
256
+ }
257
+
258
  return $changed;
259
  }
260
+
261
+ /**
262
+ * Is a password requirement enabled.
263
+ *
264
+ * @param string $requirement
265
+ *
266
+ * @return bool
267
+ */
268
+ public static function is_requirement_enabled( $requirement ) {
269
+
270
+ $requirements = self::get_registered();
271
+
272
+ if ( ! isset( $requirements[ $requirement ] ) ) {
273
+ return false;
274
+ }
275
+
276
+ // If the requirement does not have any settings, than it is always enabled.
277
+ if ( null === $requirements[ $requirement ]['settings_config'] ) {
278
+ return true;
279
+ }
280
+
281
+ $enabled = ITSEC_Modules::get_setting( 'password-requirements', 'enabled_requirements' );
282
+
283
+ if ( ! empty( $enabled[ $requirement ] ) ) {
284
+ return true;
285
+ }
286
+
287
+ return false;
288
+ }
289
+
290
+ /**
291
+ * Get requirement settings.
292
+ *
293
+ * @param string $requirement
294
+ *
295
+ * @return array|false
296
+ */
297
+ public static function get_requirement_settings( $requirement ) {
298
+
299
+ $requirements = self::get_registered();
300
+
301
+ if ( ! isset( $requirements[ $requirement ] ) ) {
302
+ return false;
303
+ }
304
+
305
+ if ( null === $requirements[ $requirement ]['settings_config'] ) {
306
+ return false;
307
+ }
308
+
309
+ $all_settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
310
+ $settings = isset( $all_settings[ $requirement ] ) ? $all_settings[ $requirement ] : array();
311
+
312
+ return wp_parse_args( $settings, $requirements[ $requirement ]['defaults'] );
313
+ }
314
  }
core/lib/class-itsec-mail.php CHANGED
@@ -1,14 +1,19 @@
1
  <?php
2
 
3
  final class ITSEC_Mail {
 
4
  private $content = '';
 
 
 
5
  private $subject = '';
6
  private $recipients = array();
7
  private $attachments = array();
8
  private $template_path = '';
9
 
10
- public function __construct() {
11
  $this->template_path = dirname( __FILE__ ) . '/mail-templates/';
 
12
  }
13
 
14
  public function add_header( $title, $banner_title, $use_site_logo = false ) {
@@ -31,7 +36,7 @@ final class ITSEC_Mail {
31
  'title' => $title,
32
  );
33
 
34
- $this->content .= $this->replace_all( $header, $replacements );
35
  }
36
 
37
  public function add_footer() {
@@ -74,13 +79,13 @@ final class ITSEC_Mail {
74
 
75
  );
76
 
77
- $this->content .= $this->replace_all( $footer, $replacements );
78
 
79
  if ( defined( 'ITSEC_DEBUG' ) && ITSEC_DEBUG ) {
80
  $this->include_debug_info();
81
  }
82
 
83
- $this->content .= $this->get_template( 'close.html' );
84
  }
85
 
86
  public function add_user_footer() {
@@ -96,51 +101,79 @@ final class ITSEC_Mail {
96
  ) );
97
 
98
  $footer .= $this->get_template( 'close.html' );
99
- $this->content .= $footer;
100
  }
101
 
102
  public function add_text( $content ) {
 
 
 
 
103
  $module = $this->get_template( 'text.html' );
104
  $module = $this->replace( $module, 'content', $content );
105
 
106
- $this->content .= $module;
107
  }
108
 
109
  public function add_divider() {
110
- $this->content .= $this->get_template( 'divider.html' );
 
 
 
 
111
  }
112
 
113
  public function add_large_text( $content ) {
 
 
 
 
114
  $module = $this->get_template( 'large-text.html' );
115
  $module = $this->replace( $module, 'content', $content );
116
 
117
- $this->content .= $module;
118
  }
119
 
120
  public function add_info_box( $content, $icon_type = 'info' ) {
 
 
 
 
121
  $icon_url = $this->get_image_url( $icon_type === 'warning' ? 'warning_icon_yellow' : "{$icon_type}_icon" );
122
 
123
  $module = $this->get_template( 'info-box.html' );
124
  $module = $this->replace_all( $module, compact( 'content', 'icon_url' ) );
125
 
126
- $this->content .= $module;
127
  }
128
 
129
  public function add_details_box( $content ) {
 
 
 
 
130
  $module = $this->get_template( 'details-box.html' );
131
  $module = $this->replace( $module, 'content', $content );
132
 
133
- $this->content .= $module;
134
  }
135
 
136
  public function add_large_code( $content ) {
 
 
 
 
137
  $module = $this->get_template( 'large-code.html' );
138
  $module = $this->replace( $module, 'content', $content );
139
 
140
- $this->content .= $module;
141
  }
142
 
143
  public function add_section_heading( $content, $icon_type = false ) {
 
 
 
 
144
  if ( empty( $icon_type ) ) {
145
  $heading = $this->get_template( 'section-heading.html' );
146
  $heading = $this->replace_all( $heading, compact( 'content' ) );
@@ -151,7 +184,7 @@ final class ITSEC_Mail {
151
  $heading = $this->replace_all( $heading, compact( 'content', 'icon_url' ) );
152
  }
153
 
154
- $this->content .= $heading;
155
  }
156
 
157
  public function add_lockouts_summary( $user_count, $host_count ) {
@@ -166,7 +199,7 @@ final class ITSEC_Mail {
166
 
167
  $lockouts = $this->replace_all( $lockouts, $replacements );
168
 
169
- $this->content .= $lockouts;
170
  }
171
 
172
  public function add_file_change_summary( $added, $removed, $modified ) {
@@ -183,19 +216,24 @@ final class ITSEC_Mail {
183
 
184
  $lockouts = $this->replace_all( $lockouts, $replacements );
185
 
186
- $this->content .= $lockouts;
187
  }
188
 
189
  public function add_button( $link_text, $href ) {
 
 
 
 
 
190
  $module = $this->get_template( 'module-button.html' );
191
  $module = $this->replace( $module, 'href', $href );
192
  $module = $this->replace( $module, 'link_text', $link_text );
193
 
194
- $this->content .= $module;
195
  }
196
 
197
  public function add_lockouts_table( $lockouts ) {
198
- $entry = $this->get_template( 'lockouts-entry.html' );
199
  $entries = '';
200
 
201
  foreach ( $lockouts as $lockout ) {
@@ -213,15 +251,15 @@ final class ITSEC_Mail {
213
  $table = $this->get_template( 'lockouts-table.html' );
214
 
215
  $replacements = array(
216
- 'heading_types' => __( 'Host/User', 'better-wp-security' ),
217
- 'heading_until' => __( 'Lockout in Effect Until', 'better-wp-security' ),
218
- 'heading_reason' => __( 'Reason', 'better-wp-security' ),
219
- 'entries' => $entries,
220
  );
221
 
222
  $table = $this->replace_all( $table, $replacements );
223
 
224
- $this->content .= $table;
225
  }
226
 
227
  /**
@@ -231,6 +269,10 @@ final class ITSEC_Mail {
231
  * @param array[] $entries
232
  */
233
  public function add_table( $headers, $entries ) {
 
 
 
 
234
 
235
  $template = $this->get_template( 'table.html' );
236
  $html = $this->build_table_header( $headers );
@@ -239,7 +281,7 @@ final class ITSEC_Mail {
239
  $html .= $this->build_table_row( $entry, count( $headers ) );
240
  }
241
 
242
- $this->content .= $this->replace( $template, 'html', $html );
243
  }
244
 
245
  /**
@@ -306,6 +348,10 @@ final class ITSEC_Mail {
306
  * @param bool $bold_first Whether to emphasize the first item of the list.
307
  */
308
  public function add_list( $items, $bold_first = false ) {
 
 
 
 
309
 
310
  $template = $this->get_template( 'list.html' );
311
  $html = '';
@@ -314,7 +360,7 @@ final class ITSEC_Mail {
314
  $html .= $this->build_list_item( $item, $bold_first && 0 === $i );
315
  }
316
 
317
- $this->content .= $this->replace( $template, 'html', $html );
318
  }
319
 
320
  private function build_list_item( $item, $bold = false ) {
@@ -323,6 +369,31 @@ final class ITSEC_Mail {
323
  return "<li style=\"margin: 0; padding: 5px 10px;{$bold_tag}\">{$item}</li>";
324
  }
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  /**
327
  * Include debug info in the email.
328
  *
@@ -359,7 +430,20 @@ final class ITSEC_Mail {
359
  }
360
 
361
  public function get_content() {
362
- return $this->content;
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  }
364
 
365
  public function set_subject( $subject, $add_site_url = true ) {
@@ -396,7 +480,7 @@ final class ITSEC_Mail {
396
  }
397
 
398
  public function set_default_recipients() {
399
- $recipients = ITSEC_Modules::get_setting( 'global', 'notification_email' );
400
  $this->set_recipients( $recipients );
401
  }
402
 
@@ -421,7 +505,7 @@ final class ITSEC_Mail {
421
  $this->set_default_subject();
422
  }
423
 
424
- return wp_mail( $this->recipients, $this->subject, $this->content, array( 'Content-Type: text/html; charset=UTF-8' ), $this->attachments );
425
  }
426
 
427
  /**
1
  <?php
2
 
3
  final class ITSEC_Mail {
4
+ private $name;
5
  private $content = '';
6
+ private $groups = array();
7
+ private $current_group;
8
+ private $deferred = '';
9
  private $subject = '';
10
  private $recipients = array();
11
  private $attachments = array();
12
  private $template_path = '';
13
 
14
+ public function __construct( $name = '' ) {
15
  $this->template_path = dirname( __FILE__ ) . '/mail-templates/';
16
+ $this->name = $name;
17
  }
18
 
19
  public function add_header( $title, $banner_title, $use_site_logo = false ) {
36
  'title' => $title,
37
  );
38
 
39
+ $this->add_html( $this->replace_all( $header, $replacements ), 'header' );
40
  }
41
 
42
  public function add_footer() {
79
 
80
  );
81
 
82
+ $this->add_html( $this->replace_all( $footer, $replacements ) );
83
 
84
  if ( defined( 'ITSEC_DEBUG' ) && ITSEC_DEBUG ) {
85
  $this->include_debug_info();
86
  }
87
 
88
+ $this->add_html( $this->get_template( 'close.html' ), 'footer' );
89
  }
90
 
91
  public function add_user_footer() {
101
  ) );
102
 
103
  $footer .= $this->get_template( 'close.html' );
104
+ $this->add_html( $footer, 'user-footer' );
105
  }
106
 
107
  public function add_text( $content ) {
108
+ $this->add_html( $this->get_text( $content ) );
109
+ }
110
+
111
+ public function get_text( $content ) {
112
  $module = $this->get_template( 'text.html' );
113
  $module = $this->replace( $module, 'content', $content );
114
 
115
+ return $module;
116
  }
117
 
118
  public function add_divider() {
119
+ $this->add_html( $this->get_divider() );
120
+ }
121
+
122
+ public function get_divider() {
123
+ return $this->get_template( 'divider.html' );
124
  }
125
 
126
  public function add_large_text( $content ) {
127
+ $this->add_html( $this->get_large_text( $content ) );
128
+ }
129
+
130
+ public function get_large_text( $content ) {
131
  $module = $this->get_template( 'large-text.html' );
132
  $module = $this->replace( $module, 'content', $content );
133
 
134
+ return $module;
135
  }
136
 
137
  public function add_info_box( $content, $icon_type = 'info' ) {
138
+ $this->add_html( $this->get_info_box( $content, $icon_type ) );
139
+ }
140
+
141
+ public function get_info_box( $content, $icon_type = 'info' ) {
142
  $icon_url = $this->get_image_url( $icon_type === 'warning' ? 'warning_icon_yellow' : "{$icon_type}_icon" );
143
 
144
  $module = $this->get_template( 'info-box.html' );
145
  $module = $this->replace_all( $module, compact( 'content', 'icon_url' ) );
146
 
147
+ return $module;
148
  }
149
 
150
  public function add_details_box( $content ) {
151
+ $this->add_html( $this->get_details_box( $content ) );
152
+ }
153
+
154
+ public function get_details_box( $content ) {
155
  $module = $this->get_template( 'details-box.html' );
156
  $module = $this->replace( $module, 'content', $content );
157
 
158
+ return $module;
159
  }
160
 
161
  public function add_large_code( $content ) {
162
+ $this->add_html( $this->get_large_code( $content ) );
163
+ }
164
+
165
+ public function get_large_code( $content ) {
166
  $module = $this->get_template( 'large-code.html' );
167
  $module = $this->replace( $module, 'content', $content );
168
 
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
+ }
175
+
176
+ public function get_section_heading( $content, $icon_type = false ) {
177
  if ( empty( $icon_type ) ) {
178
  $heading = $this->get_template( 'section-heading.html' );
179
  $heading = $this->replace_all( $heading, compact( 'content' ) );
184
  $heading = $this->replace_all( $heading, compact( 'content', 'icon_url' ) );
185
  }
186
 
187
+ return $heading;
188
  }
189
 
190
  public function add_lockouts_summary( $user_count, $host_count ) {
199
 
200
  $lockouts = $this->replace_all( $lockouts, $replacements );
201
 
202
+ $this->add_html( $lockouts, 'lockouts-summary' );
203
  }
204
 
205
  public function add_file_change_summary( $added, $removed, $modified ) {
216
 
217
  $lockouts = $this->replace_all( $lockouts, $replacements );
218
 
219
+ $this->add_html( $lockouts, 'file-change-summary' );
220
  }
221
 
222
  public function add_button( $link_text, $href ) {
223
+ $this->add_html( $this->get_button( $link_text, $href ) );
224
+ }
225
+
226
+ public function get_button( $link_text, $href ) {
227
+
228
  $module = $this->get_template( 'module-button.html' );
229
  $module = $this->replace( $module, 'href', $href );
230
  $module = $this->replace( $module, 'link_text', $link_text );
231
 
232
+ return $module;
233
  }
234
 
235
  public function add_lockouts_table( $lockouts ) {
236
+ $entry = $this->get_template( 'lockouts-entry.html' );
237
  $entries = '';
238
 
239
  foreach ( $lockouts as $lockout ) {
251
  $table = $this->get_template( 'lockouts-table.html' );
252
 
253
  $replacements = array(
254
+ 'heading_types' => __( 'Host/User', 'better-wp-security' ),
255
+ 'heading_until' => __( 'Lockout in Effect Until', 'better-wp-security' ),
256
+ 'heading_reason' => __( 'Reason', 'better-wp-security' ),
257
+ 'entries' => $entries,
258
  );
259
 
260
  $table = $this->replace_all( $table, $replacements );
261
 
262
+ $this->add_html( $table, 'lockouts-table' );
263
  }
264
 
265
  /**
269
  * @param array[] $entries
270
  */
271
  public function add_table( $headers, $entries ) {
272
+ $this->add_html( $this->get_table( $headers, $entries ) );
273
+ }
274
+
275
+ public function get_table( $headers, $entries ) {
276
 
277
  $template = $this->get_template( 'table.html' );
278
  $html = $this->build_table_header( $headers );
281
  $html .= $this->build_table_row( $entry, count( $headers ) );
282
  }
283
 
284
+ return $this->replace( $template, 'html', $html );
285
  }
286
 
287
  /**
348
  * @param bool $bold_first Whether to emphasize the first item of the list.
349
  */
350
  public function add_list( $items, $bold_first = false ) {
351
+ $this->add_html( $this->get_list( $items, $bold_first ) );
352
+ }
353
+
354
+ public function get_list( $items, $bold_first = false ) {
355
 
356
  $template = $this->get_template( 'list.html' );
357
  $html = '';
360
  $html .= $this->build_list_item( $item, $bold_first && 0 === $i );
361
  }
362
 
363
+ return $this->replace( $template, 'html', $html );
364
  }
365
 
366
  private function build_list_item( $item, $bold = false ) {
369
  return "<li style=\"margin: 0; padding: 5px 10px;{$bold_tag}\">{$item}</li>";
370
  }
371
 
372
+ private function add_html( $html, $identifier = null ) {
373
+
374
+ if ( null !== $this->current_group ) {
375
+ $this->deferred .= $html;
376
+ } elseif ( null !== $identifier ) {
377
+ $this->groups[ $identifier ] = $html;
378
+ } else {
379
+ $this->groups[] = $html;
380
+ }
381
+ }
382
+
383
+ public function start_group( $identifier ) {
384
+ $this->current_group = $identifier;
385
+ }
386
+
387
+ public function end_group() {
388
+ $group = $this->current_group;
389
+ $deferred = $this->deferred;
390
+
391
+ $this->current_group = null;
392
+ $this->deferred = '';
393
+
394
+ $this->add_html( $deferred, $group );
395
+ }
396
+
397
  /**
398
  * Include debug info in the email.
399
  *
430
  }
431
 
432
  public function get_content() {
433
+
434
+ $groups = $this->groups;
435
+
436
+ if ( $this->name ) {
437
+ /**
438
+ * Filter the HTML groups before building the content.
439
+ *
440
+ * @param array $groups
441
+ * @param ITSEC_Mail $this
442
+ */
443
+ $groups = apply_filters( "itsec_mail_{$this->name}", $groups, $this );
444
+ }
445
+
446
+ return implode( '', $groups );
447
  }
448
 
449
  public function set_subject( $subject, $add_site_url = true ) {
480
  }
481
 
482
  public function set_default_recipients() {
483
+ $recipients = ITSEC_Modules::get_setting( 'global', 'notification_email' );
484
  $this->set_recipients( $recipients );
485
  }
486
 
505
  $this->set_default_subject();
506
  }
507
 
508
+ return wp_mail( $this->recipients, $this->get_subject(), $this->content ? $this->content : $this->get_content(), array( 'Content-Type: text/html; charset=UTF-8' ), $this->attachments );
509
  }
510
 
511
  /**
core/lib/class-itsec-scheduler-cron.php CHANGED
@@ -88,6 +88,8 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
88
  }
89
 
90
  if ( ! $crons = _get_cron_array() ) {
 
 
91
  return;
92
  }
93
 
@@ -97,12 +99,14 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
97
  }
98
 
99
  if ( get_transient( 'doing_cron' ) ) {
 
100
  is_multisite() && restore_current_blog();
101
 
102
  return;
103
  }
104
 
105
  if ( ITSEC_Lib::get_uncached_option( '_transient_doing_cron' ) ) {
 
106
  is_multisite() && restore_current_blog();
107
 
108
  return;
88
  }
89
 
90
  if ( ! $crons = _get_cron_array() ) {
91
+ ITSEC_Lib::release_lock( 'scheduler' );
92
+
93
  return;
94
  }
95
 
99
  }
100
 
101
  if ( get_transient( 'doing_cron' ) ) {
102
+ ITSEC_Lib::release_lock( 'scheduler' );
103
  is_multisite() && restore_current_blog();
104
 
105
  return;
106
  }
107
 
108
  if ( ITSEC_Lib::get_uncached_option( '_transient_doing_cron' ) ) {
109
+ ITSEC_Lib::release_lock( 'scheduler' );
110
  is_multisite() && restore_current_blog();
111
 
112
  return;
core/lib/form.php CHANGED
@@ -462,6 +462,24 @@ final class ITSEC_Form {
462
  $this->add_custom_input( $var, $options );
463
  }
464
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  private function add_custom_input( $var, $options ) {
466
  if ( empty( $options['type'] ) ) {
467
  trigger_error( 'add_custom_input called without a type option set' );
462
  $this->add_custom_input( $var, $options );
463
  }
464
 
465
+ public function add_canonical_roles( $var, $options = array() ) {
466
+ $roles = array(
467
+ 'administrator' => translate_user_role( 'Administrator' ),
468
+ 'editor' => translate_user_role( 'Editor' ),
469
+ 'author' => translate_user_role( 'Author' ),
470
+ 'contributor' => translate_user_role( 'Contributor' ),
471
+ 'subscriber' => translate_user_role( 'Subscriber' ),
472
+ );
473
+
474
+ if ( isset( $options['value'] ) ) {
475
+ $options['value'] = wp_parse_args( $options['value'], $roles );
476
+ } else {
477
+ $options['value'] = $roles;
478
+ }
479
+
480
+ $this->add_select( $var, $options );
481
+ }
482
+
483
  private function add_custom_input( $var, $options ) {
484
  if ( empty( $options['type'] ) ) {
485
  trigger_error( 'add_custom_input called without a type option set' );
core/lib/mail-templates/header.html CHANGED
@@ -77,6 +77,7 @@
77
  #security-guide a{font-weight:bold;}
78
  #footer-source-details .container-cell{line-height:200%;padding-top:60px;padding-bottom:0;}
79
  #footer-source-details a{font-size:11px;font-weight:bold;line-height:200%;}
 
80
 
81
  @media only screen and (max-width:600px){
82
  body{width:100% !important;min-width:100% !important;}
77
  #security-guide a{font-weight:bold;}
78
  #footer-source-details .container-cell{line-height:200%;padding-top:60px;padding-bottom:0;}
79
  #footer-source-details a{font-size:11px;font-weight:bold;line-height:200%;}
80
+ .template-container {max-width: 600px !important;}
81
 
82
  @media only screen and (max-width:600px){
83
  body{width:100% !important;min-width:100% !important;}
core/lib/schema.php CHANGED
@@ -96,5 +96,6 @@ CREATE TABLE {$wpdb->base_prefix}itsec_distributed_storage (
96
  $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}itsec_log;" );
97
  $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}itsec_lockouts;" );
98
  $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}itsec_temp;" );
 
99
  }
100
  }
96
  $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}itsec_log;" );
97
  $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}itsec_lockouts;" );
98
  $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}itsec_temp;" );
99
+ $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}itsec_distributed_storage;" );
100
  }
101
  }
core/lib/validator.php CHANGED
@@ -305,6 +305,32 @@ abstract class ITSEC_Validator {
305
  $error = wp_sprintf( _n( 'The valid value for %1$s is: %2$l.', 'The valid values for %1$s are: %2$l.', count( $type ), 'better-wp-security' ), $name, $type );
306
  $type = 'array';
307
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  } else if ( 'newline-separated-array' === $type ) {
309
  $this->settings[$var] = $this->convert_string_to_array( $this->settings[$var] );
310
 
@@ -394,7 +420,7 @@ abstract class ITSEC_Validator {
394
  }
395
 
396
  if ( false !== $error ) {
397
- $this->add_error( new WP_Error( "itsec-validator-$id-invalid-type-$var-$type", $error ) );
398
  $this->vars_to_skip_validate_matching_types[] = $var;
399
 
400
  if ( $prevent_save_on_error && ITSEC_Core::is_interactive() ) {
@@ -407,6 +433,10 @@ abstract class ITSEC_Validator {
407
  return true;
408
  }
409
 
 
 
 
 
410
  final protected function convert_string_to_array( $string ) {
411
  if ( is_string( $string ) ) {
412
  $array = preg_split( "/[\r\n]+/", $string );
305
  $error = wp_sprintf( _n( 'The valid value for %1$s is: %2$l.', 'The valid values for %1$s are: %2$l.', count( $type ), 'better-wp-security' ), $name, $type );
306
  $type = 'array';
307
  }
308
+ } elseif ( 'canonical-roles' === $type ) {
309
+ $roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
310
+
311
+ if ( is_array( $this->settings[$var] ) ) {
312
+ $invalid_entries = array();
313
+
314
+ foreach ( $this->settings[$var] as $index => $entry ) {
315
+ $entry = sanitize_text_field( trim( $entry ) );
316
+ $this->settings[$var][$index] = $entry;
317
+
318
+ if ( empty( $entry ) ) {
319
+ unset( $this->settings[$var][$index] );
320
+ } else if ( ! in_array( $entry, $roles, true ) ) {
321
+ $invalid_entries[] = $entry;
322
+ }
323
+ }
324
+
325
+ $this->settings[$var] = array_unique( $this->settings[$var] );
326
+
327
+ if ( ! empty( $invalid_entries ) ) {
328
+ $error = wp_sprintf( _n( 'The following entry in %1$s is invalid: %2$l', 'The following entries in %1$s are invalid: %2$l', count( $invalid_entries ), 'better-wp-security' ), $name, $invalid_entries );
329
+ }
330
+ } else if ( ! in_array( $this->settings[$var], $roles, true ) ) {
331
+ $error = wp_sprintf( _n( 'The valid value for %1$s is: %2$l.', 'The valid values for %1$s are: %2$l.', count( $roles ), 'better-wp-security' ), $name, $roles );
332
+ $type = 'array';
333
+ }
334
  } else if ( 'newline-separated-array' === $type ) {
335
  $this->settings[$var] = $this->convert_string_to_array( $this->settings[$var] );
336
 
420
  }
421
 
422
  if ( false !== $error ) {
423
+ $this->add_error( $this->generate_error( $id, $var, $type, $error ) );
424
  $this->vars_to_skip_validate_matching_types[] = $var;
425
 
426
  if ( $prevent_save_on_error && ITSEC_Core::is_interactive() ) {
433
  return true;
434
  }
435
 
436
+ protected function generate_error( $id, $var, $type, $error ) {
437
+ return new WP_Error( "itsec-validator-$id-invalid-type-$var-$type", $error );
438
+ }
439
+
440
  final protected function convert_string_to_array( $string ) {
441
  if ( is_string( $string ) ) {
442
  $array = preg_split( "/[\r\n]+/", $string );
core/lockout.php CHANGED
@@ -84,9 +84,6 @@ final class ITSEC_Lockout {
84
  //Set an error message on improper logout
85
  add_action( 'login_head', array( $this, 'set_lockout_error' ) );
86
 
87
- //Process clear lockout form
88
- add_action( 'itsec_admin_init', array( $this, 'release_lockout' ) );
89
-
90
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
91
  add_filter( 'itsec-filter-itsec-get-everything-verbs', array( $this, 'register_sync_get_everything_verbs' ) );
92
 
84
  //Set an error message on improper logout
85
  add_action( 'login_head', array( $this, 'set_lockout_error' ) );
86
 
 
 
 
87
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
88
  add_filter( 'itsec-filter-itsec-get-everything-verbs', array( $this, 'register_sync_get_everything_verbs' ) );
89
 
core/modules/away-mode/class-itsec-away-mode.php CHANGED
@@ -5,7 +5,7 @@ final class ITSEC_Away_Mode {
5
  public function run() {
6
 
7
  //Execute away mode functions on admin init
8
- add_action( 'itsec_admin_init', array( $this, 'run_active_check' ) );
9
  add_action( 'login_init', array( $this, 'run_active_check' ) );
10
 
11
  add_filter( 'itsec_managed_files', array( $this, 'register_managed_file' ) );
@@ -78,6 +78,11 @@ final class ITSEC_Away_Mode {
78
  * @return void
79
  */
80
  public function run_active_check() {
 
 
 
 
 
81
  $away_mode_details = self::is_active( true );
82
 
83
  if ( $away_mode_details['active'] ) {
5
  public function run() {
6
 
7
  //Execute away mode functions on admin init
8
+ add_action( 'admin_init', array( $this, 'run_active_check' ) );
9
  add_action( 'login_init', array( $this, 'run_active_check' ) );
10
 
11
  add_filter( 'itsec_managed_files', array( $this, 'register_managed_file' ) );
78
  * @return void
79
  */
80
  public function run_active_check() {
81
+
82
+ if ( wp_doing_ajax() ) {
83
+ return;
84
+ }
85
+
86
  $away_mode_details = self::is_active( true );
87
 
88
  if ( $away_mode_details['active'] ) {
core/modules/file-change/class-itsec-file-change.php CHANGED
@@ -77,7 +77,7 @@ class ITSEC_File_Change {
77
  $hashes = ITSEC_Modules::get_setting( 'file-change', 'expected_hashes', array() );
78
  $hash = @md5_file( $file );
79
 
80
- if ( $hash ) {
81
  $hashes[ $file ] = $hash;
82
  ITSEC_Modules::set_setting( 'file-change', 'expected_hashes', $hashes );
83
  }
77
  $hashes = ITSEC_Modules::get_setting( 'file-change', 'expected_hashes', array() );
78
  $hash = @md5_file( $file );
79
 
80
+ if ( $hash && ( ! isset( $hashes[ $file ] ) || $hashes[ $file ] !== $hash ) ) {
81
  $hashes[ $file ] = $hash;
82
  ITSEC_Modules::set_setting( 'file-change', 'expected_hashes', $hashes );
83
  }
core/modules/file-change/css/settings.css CHANGED
@@ -90,3 +90,17 @@ UL.jqueryFileTree LI {
90
  width : 100%;
91
  }
92
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  width : 100%;
91
  }
92
  }
93
+
94
+ #itsec-file-change-abort {
95
+ color: #a00;
96
+ text-decoration: none;
97
+ border-color: transparent;
98
+ box-shadow: none;
99
+ background: transparent;
100
+ }
101
+
102
+ #itsec-file-change-abort:hover {
103
+ background: #d54e21;
104
+ color: #fff;
105
+ border-color: #d54e21;
106
+ }
core/modules/file-change/js/settings-page.js CHANGED
@@ -62,4 +62,19 @@ jQuery( document ).ready( function ( $ ) {
62
  }
63
 
64
  initializeScan();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  } );
62
  }
63
 
64
  initializeScan();
65
+
66
+ $( document ).on( 'click', '#itsec-file-change-abort', function () {
67
+ var $this = $( this );
68
+
69
+ $this.prop( 'disabled', true );
70
+
71
+ itsecUtil.sendModuleAJAXRequest( 'file-change', { method: 'abort' }, function ( results ) {
72
+ var $button = $( '#itsec-file-change-one_time_check' );
73
+ $button.prop( 'disabled', false );
74
+ $button.prop( 'class', 'button-primary' );
75
+ $button.val( ITSECFileChangeScannerl10n.button_text );
76
+
77
+ $this.remove();
78
+ } );
79
+ } );
80
  } );
core/modules/file-change/logs.php CHANGED
@@ -44,7 +44,17 @@ final class ITSEC_File_Change_Logs {
44
  } elseif ( 'recovery-scheduled' === $code ) {
45
  $entry['description'] = esc_html__( 'Recovery Scheduled', 'better-wp-security' );
46
  } elseif ( 'file-scan-aborted' === $code ) {
47
- $entry['description'] = esc_html__( 'Scan Failed', 'better-wp-security' );
 
 
 
 
 
 
 
 
 
 
48
  } elseif ( 'rescheduling' === $code ) {
49
  $entry['description'] = esc_html__( 'Rescheduling', 'better-wp-security' );
50
  }
44
  } elseif ( 'recovery-scheduled' === $code ) {
45
  $entry['description'] = esc_html__( 'Recovery Scheduled', 'better-wp-security' );
46
  } elseif ( 'file-scan-aborted' === $code ) {
47
+ if ( ! empty( $code_data[0] ) ) {
48
+ if ( $user = get_userdata( $code_data[0] ) ) {
49
+ $by = $user->display_name;
50
+ } else {
51
+ $by = "#{$code_data[0]}";
52
+ }
53
+
54
+ $entry['description'] = sprintf( esc_html__( 'Scan Cancelled by %s', 'better-wp-security' ), $by );
55
+ } else {
56
+ $entry['description'] = esc_html__( 'Scan Failed', 'better-wp-security' );
57
+ }
58
  } elseif ( 'rescheduling' === $code ) {
59
  $entry['description'] = esc_html__( 'Rescheduling', 'better-wp-security' );
60
  }
core/modules/file-change/scanner.php CHANGED
@@ -21,7 +21,6 @@ do_action( 'itsec_load_file_change_scanner' );
21
  class ITSEC_File_Change_Scanner {
22
 
23
  const DESTROYED = 'itsec_file_change_scan_destroyed';
24
- const FILE_LIST = 'itsec_file_list';
25
 
26
  const C_ADMIN = 'admin';
27
  const C_INCLUDES = 'includes';
@@ -287,7 +286,7 @@ class ITSEC_File_Change_Scanner {
287
  $job_data['step'] = $store['step'];
288
  $job_data['chunk'] = $store['chunk'];
289
 
290
- if ( 'get-files' === $job_data['step'] && self::C_ADMIN === $job_data['chunk'] ) {
291
  ITSEC_Log::add_debug( 'file_change', 'recovery-failed-first-loop' );
292
 
293
  self::abort();
@@ -319,8 +318,10 @@ class ITSEC_File_Change_Scanner {
319
 
320
  /**
321
  * Abort an in-progress scan.
 
 
322
  */
323
- public static function abort() {
324
  $storage = ITSEC_File_Change::make_progress_storage();
325
 
326
  if ( 'file-change-fast' === $storage->get( 'id' ) ) {
@@ -334,11 +335,20 @@ class ITSEC_File_Change_Scanner {
334
  ITSEC_Log::add_process_stop( $process, array( 'aborted' => true ) );
335
  }
336
 
337
- ITSEC_Log::add_fatal_error( 'file_change', 'file-scan-aborted', array(
338
- 'id' => $storage->get( 'id' ),
339
- 'step' => $storage->get( 'step' ),
340
- 'chunk' => $storage->get( 'chunk' ),
341
- ) );
 
 
 
 
 
 
 
 
 
342
 
343
  $storage->clear();
344
  update_site_option( self::DESTROYED, ITSEC_Core::get_current_time_gmt() );
21
  class ITSEC_File_Change_Scanner {
22
 
23
  const DESTROYED = 'itsec_file_change_scan_destroyed';
 
24
 
25
  const C_ADMIN = 'admin';
26
  const C_INCLUDES = 'includes';
286
  $job_data['step'] = $store['step'];
287
  $job_data['chunk'] = $store['chunk'];
288
 
289
+ if ( 1 === $job_data['loop_item'] || ( 'get-files' === $job_data['step'] && self::C_ADMIN === $job_data['chunk'] ) ) {
290
  ITSEC_Log::add_debug( 'file_change', 'recovery-failed-first-loop' );
291
 
292
  self::abort();
318
 
319
  /**
320
  * Abort an in-progress scan.
321
+ *
322
+ * @param bool $user_initiated
323
  */
324
+ public static function abort( $user_initiated = false ) {
325
  $storage = ITSEC_File_Change::make_progress_storage();
326
 
327
  if ( 'file-change-fast' === $storage->get( 'id' ) ) {
335
  ITSEC_Log::add_process_stop( $process, array( 'aborted' => true ) );
336
  }
337
 
338
+ if ( $user_initiated ) {
339
+ $user = get_current_user_id();
340
+ ITSEC_Log::add_warning( 'file_change', "file-scan-aborted::{$user}", array(
341
+ 'id' => $storage->get( 'id' ),
342
+ 'step' => $storage->get( 'step' ),
343
+ 'chunk' => $storage->get( 'chunk' ),
344
+ ) );
345
+ } else {
346
+ ITSEC_Log::add_fatal_error( 'file_change', 'file-scan-aborted', array(
347
+ 'id' => $storage->get( 'id' ),
348
+ 'step' => $storage->get( 'step' ),
349
+ 'chunk' => $storage->get( 'chunk' ),
350
+ ) );
351
+ }
352
 
353
  $storage->clear();
354
  update_site_option( self::DESTROYED, ITSEC_Core::get_current_time_gmt() );
core/modules/file-change/settings-page.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
 
3
  final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
4
- private $script_version = 3;
5
 
6
 
7
  public function __construct() {
@@ -26,8 +26,9 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
26
  require_once( dirname( __FILE__ ) . '/admin.php' );
27
  }
28
 
 
29
  ITSEC_File_Change_Admin::enqueue_scanner();
30
- wp_enqueue_script( 'itsec-file-change-settings-script', plugins_url( 'js/settings-page.js', __FILE__ ), array( 'jquery', 'itsec-file-change-scanner' ), $this->script_version, true );
31
  wp_localize_script( 'itsec-file-change-settings-script', 'itsec_file_change_settings', $vars );
32
 
33
 
@@ -54,6 +55,11 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
54
  } else {
55
  ITSEC_Response::set_success( true );
56
  }
 
 
 
 
 
57
  } else if ( 'get-filetree-data' === $data['method'] ) {
58
  ITSEC_Response::set_response( $this->get_filetree_data( $data ) );
59
  }
@@ -81,7 +87,7 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
81
 
82
  require_once( dirname( __FILE__ ) . '/scanner.php' );
83
 
84
- if ( ITSEC_File_Change_Scanner::is_running() ) {
85
  $status = ITSEC_File_Change_Scanner::get_status();
86
 
87
  $button = array(
@@ -98,7 +104,12 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
98
  ?>
99
  <div class="hide-if-no-js">
100
  <p><?php _e( "Press the button below to scan your site's files for changes. Note that if changes are found this will take you to the logs page for details.", 'better-wp-security' ); ?></p>
101
- <p><?php $form->add_button( 'one_time_check', $button ); ?></p>
 
 
 
 
 
102
  <div id="itsec_file_change_status"></div>
103
  </div>
104
 
1
  <?php
2
 
3
  final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
4
+ private $script_version = 4;
5
 
6
 
7
  public function __construct() {
26
  require_once( dirname( __FILE__ ) . '/admin.php' );
27
  }
28
 
29
+ ITSEC_Lib::enqueue_util();
30
  ITSEC_File_Change_Admin::enqueue_scanner();
31
+ wp_enqueue_script( 'itsec-file-change-settings-script', plugins_url( 'js/settings-page.js', __FILE__ ), array( 'jquery', 'itsec-file-change-scanner', 'itsec-util' ), $this->script_version, true );
32
  wp_localize_script( 'itsec-file-change-settings-script', 'itsec_file_change_settings', $vars );
33
 
34
 
55
  } else {
56
  ITSEC_Response::set_success( true );
57
  }
58
+ } elseif ( 'abort' === $data['method'] ) {
59
+ require_once( dirname( __FILE__ ) . '/scanner.php' );
60
+ ITSEC_File_Change_Scanner::abort( true );
61
+
62
+ ITSEC_Response::set_success( true );
63
  } else if ( 'get-filetree-data' === $data['method'] ) {
64
  ITSEC_Response::set_response( $this->get_filetree_data( $data ) );
65
  }
87
 
88
  require_once( dirname( __FILE__ ) . '/scanner.php' );
89
 
90
+ if ( $is_running = ITSEC_File_Change_Scanner::is_running() ) {
91
  $status = ITSEC_File_Change_Scanner::get_status();
92
 
93
  $button = array(
104
  ?>
105
  <div class="hide-if-no-js">
106
  <p><?php _e( "Press the button below to scan your site's files for changes. Note that if changes are found this will take you to the logs page for details.", 'better-wp-security' ); ?></p>
107
+ <p>
108
+ <?php $form->add_button( 'one_time_check', $button ); ?>
109
+ <?php if ( $is_running ) : ?>
110
+ <?php $form->add_button( 'abort', array( 'value' => _x( 'Cancel', 'Cancel File Change scan.', 'better-wp-security' ), 'class' => 'button' ) ); ?>
111
+ <?php endif; ?>
112
+ </p>
113
  <div id="itsec_file_change_status"></div>
114
  </div>
115
 
core/modules/notification-center/class-notification-center.php CHANGED
@@ -411,12 +411,14 @@ final class ITSEC_Notification_Center {
411
  /**
412
  * Initialize a Mail instance.
413
  *
 
 
414
  * @return ITSEC_Mail
415
  */
416
- public function mail() {
417
  require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-mail.php' );
418
 
419
- return new ITSEC_Mail();
420
  }
421
 
422
  /**
411
  /**
412
  * Initialize a Mail instance.
413
  *
414
+ * @param string $name
415
+ *
416
  * @return ITSEC_Mail
417
  */
418
+ public function mail( $name = '' ) {
419
  require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-mail.php' );
420
 
421
+ return new ITSEC_Mail( $name );
422
  }
423
 
424
  /**
core/modules/notification-center/debug.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ITSEC_Notification_Center_Debug
5
+ */
6
+ class ITSEC_Notification_Center_Debug {
7
+
8
+ public function __construct() {
9
+ add_action( 'itsec_debug_page', array( $this, 'render' ) );
10
+ add_action( 'itsec_debug_page_enqueue', array( $this, 'enqueue_scripts_and_styles' ) );
11
+ add_action( 'itsec_debug_module_request_notification-center', array( $this, 'handle_ajax_request' ) );
12
+ }
13
+
14
+ public function enqueue_scripts_and_styles() {
15
+ wp_enqueue_script( 'itsec-notification-center-debug', plugins_url( 'js/debug.js', __FILE__ ), array( 'itsec-util' ), ITSEC_Core::get_plugin_build() );
16
+ }
17
+
18
+ public function handle_ajax_request( $data ) {
19
+ if ( empty( $data['id'] ) ) {
20
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-notification-center-missing-id', __( 'The server did not receive a valid request. The notification id is missing.', 'better-wp-security' ) ) );
21
+
22
+ return;
23
+ }
24
+
25
+ $result = ITSEC_Core::get_notification_center()->send_scheduled_notifications( array( $data['id'] ), ! empty( $data['silent'] ) );
26
+
27
+ if ( is_wp_error( $result ) ) {
28
+ ITSEC_Response::add_error( $result );
29
+ } elseif ( ! $result ) {
30
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-notification-center-send-failed', __( 'The server could not send the requested notification.', 'better-wp-security' ) ) );
31
+ } else {
32
+ ITSEC_Response::set_response( $this->get_table() );
33
+ ITSEC_Response::set_success( true );
34
+ ITSEC_Response::add_message( __( 'Notification sent.', 'better-wp-security' ) );
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Render our data to the Debug Page.
40
+ */
41
+ public function render() {
42
+ ?>
43
+
44
+ <div id="itsec-notification-center-notifications">
45
+ <h2><?php esc_html_e( 'Notification Center', 'better-wp-security' ); ?></h2>
46
+ <?php echo $this->get_table(); ?>
47
+ </div>
48
+
49
+ <?php
50
+ }
51
+
52
+ private function get_table() {
53
+ $nc = ITSEC_Core::get_notification_center();
54
+ ob_start();
55
+ ?>
56
+ <table class="widefat striped">
57
+ <thead>
58
+ <tr>
59
+ <th><?php esc_html_e( 'ID', 'better-wp-security' ) ?></th>
60
+ <th><?php esc_html_e( 'Last Sent', 'better-wp-security' ) ?></th>
61
+ <th><?php esc_html_e( 'Next Send', 'better-wp-security' ) ?></th>
62
+ <th><?php esc_html_e( 'Schedule', 'better-wp-security' ) ?></th>
63
+ <th></th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ <?php foreach ( $nc->get_notifications() as $slug => $notification ) : $scheduled = ITSEC_Notification_Center::S_NONE !== $notification['schedule']; ?>
68
+ <tr>
69
+ <td><?php echo esc_html( $slug ); ?></td>
70
+ <td><?php echo $scheduled ? date( 'Y-m-d H:i:s', $nc->get_last_sent( $slug ) ) : '–'; ?></td>
71
+ <td><?php echo $scheduled ? date( 'Y-m-d H:i:s', $nc->get_next_send_time( $slug ) ) : '–'; ?></td>
72
+ <td><?php echo $nc->get_schedule( $slug ); ?></td>
73
+ <td>
74
+ <?php if ( $scheduled ): ?>
75
+ <button class="button itsec__send-notification itsec__send-notification--force" data-id="<?php echo esc_attr( $slug ); ?>">
76
+ <?php esc_html_e( 'Force', 'better-wp-security' ) ?>
77
+ </button>
78
+ <button class="button itsec__send-notification itsec__send-notification--silent" data-id="<?php echo esc_attr( $slug ); ?>">
79
+ <?php esc_html_e( 'Silent', 'better-wp-security' ) ?>
80
+ </button>
81
+ <?php endif; ?>
82
+ </td>
83
+ </tr>
84
+ <?php endforeach; ?>
85
+ </tbody>
86
+ </table>
87
+ <?php
88
+ return ob_get_clean();
89
+ }
90
+ }
91
+
92
+ new ITSEC_Notification_Center_Debug();
core/modules/notification-center/js/debug.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function ( $, itsecUtil ) {
2
+ "use strict";
3
+
4
+ $( function () {
5
+ $( '#itsec-notification-center-notifications' ).on( 'click', '.button', function () {
6
+
7
+ var $btn = $( this );
8
+ $btn.prop( 'disabled', true );
9
+
10
+ itsecUtil.sendModuleAJAXRequest( 'notification-center', { id: $btn.data( 'id' ), silent: $btn.hasClass( 'itsec__send-notification--silent' ) ? 1 : 0 }, function ( response ) {
11
+
12
+ $btn.prop( 'disabled', false );
13
+
14
+ if ( response.success ) {
15
+ $( 'table', '#itsec-notification-center-notifications' ).replaceWith( response.response );
16
+ }
17
+
18
+ itsecUtil.displayNotices( response, $( '#itsec-messages' ) );
19
+ } );
20
+ } );
21
+ } );
22
+ })( jQuery, window.itsecUtil );
core/modules/password-requirements/active.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once( dirname( __FILE__ ) . '/class-itsec-password-requirements.php' );
4
+
5
+ $requirements = new ITSEC_Password_Requirements();
6
+ $requirements->run();
core/modules/password-requirements/class-itsec-password-requirements.php ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ITSEC_Password_Requirements
5
+ */
6
+ class ITSEC_Password_Requirements {
7
+
8
+ const META_KEY = '_itsec_password_requirements';
9
+
10
+ public function run() {
11
+
12
+ add_action( 'user_profile_update_errors', array( $this, 'forward_profile_pass_update' ), 0, 3 );
13
+ add_action( 'validate_password_reset', array( $this, 'forward_reset_pass' ), 10, 2 );
14
+
15
+ add_action( 'profile_update', array( $this, 'handle_update_user' ), 10, 2 );
16
+ add_action( 'password_reset', array( $this, 'handle_password_reset' ), 10, 2 );
17
+ add_filter( 'wp_authenticate_user', array( $this, 'check_password_on_login' ), 999, 2 );
18
+
19
+ add_action( 'add_user_role', array( $this, 'handle_role_change' ) );
20
+ add_action( 'set_user_role', array( $this, 'handle_role_change' ) );
21
+ add_action( 'remove_user_role', array( $this, 'handle_role_change' ) );
22
+
23
+ add_action( 'itsec_validate_password', array( $this, 'validate_password' ), 10, 4 );
24
+
25
+ add_action( 'wp_login', array( $this, 'flag_check' ), 9, 2 );
26
+
27
+ add_action( 'itsec_login_interstitial_init', array( $this, 'register_interstitial' ) );
28
+ }
29
+
30
+ /**
31
+ * When a user's password is updated, or a new user created, verify that the new password is valid.
32
+ *
33
+ * @param WP_Error $errors
34
+ * @param bool $update
35
+ * @param WP_User|stdClass $user
36
+ */
37
+ public function forward_profile_pass_update( $errors, $update, $user ) {
38
+
39
+ if ( $errors->get_error_message( 'pass' ) ) {
40
+ return;
41
+ }
42
+
43
+ if ( isset( $user->user_pass ) ) {
44
+ $this->handle_profile_update_password( $errors, $update, $user );
45
+ } elseif ( $update && isset( $user->role ) ) {
46
+ $this->handle_profile_update_role( $errors, $user );
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Handle the password being updated for a user.
52
+ *
53
+ * @param WP_Error $errors
54
+ * @param bool $update
55
+ * @param WP_User|stdClass $user
56
+ */
57
+ private function handle_profile_update_password( $errors, $update, $user ) {
58
+ if ( ! $update ) {
59
+ $context = 'admin-user-create';
60
+ } elseif ( isset( $user->ID ) && $user->ID === get_current_user_id() ) {
61
+ $context = 'profile-update';
62
+ } else {
63
+ $context = 'admin-profile-update';
64
+ }
65
+
66
+ $args = array(
67
+ 'error' => $errors,
68
+ 'context' => $context
69
+ );
70
+
71
+ if ( isset( $user->role ) ) {
72
+ $args['role'] = $user->role;
73
+ }
74
+
75
+ ITSEC_Lib_Password_Requirements::validate_password( $user, $user->user_pass, $args );
76
+ }
77
+
78
+ /**
79
+ * Handle the user's role being updated.
80
+ *
81
+ * @param WP_Error $errors
82
+ * @param WP_User|stdClass $user
83
+ */
84
+ private function handle_profile_update_role( $errors, $user ) {
85
+
86
+ $settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
87
+
88
+ foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
89
+
90
+ if ( ! $requirement['validate'] || ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
91
+ continue;
92
+ }
93
+
94
+ $evaluation = get_user_meta( $user->ID, $requirement['meta'], true );
95
+
96
+ if ( '' === $evaluation ) {
97
+ continue;
98
+ }
99
+
100
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
101
+
102
+ $args = array(
103
+ 'role' => $user->role,
104
+ 'canonical' => ITSEC_Lib_Canonical_Roles::get_canonical_role_from_role_and_user( $user->role, $user ),
105
+ );
106
+
107
+ $validated = call_user_func( $requirement['validate'], $evaluation, $user, $settings[ $code ], $args );
108
+
109
+ if ( true === $validated ) {
110
+ continue;
111
+ }
112
+
113
+ $message = $validated ? $validated : esc_html__( "The provided password does not meet this site's requirements.", 'better-wp-security' );
114
+ $errors->add( 'pass', $message );
115
+ }
116
+ }
117
+
118
+ /**
119
+ * When a user attempts to reset their password, verify that the new password is valid.
120
+ *
121
+ * @param WP_Error $errors
122
+ * @param WP_User $user
123
+ */
124
+ public function forward_reset_pass( $errors, $user ) {
125
+
126
+ if ( ! isset( $_POST['pass1'] ) || is_wp_error( $user ) ) {
127
+ // The validate_password_reset action fires when first rendering the reset page and when handling the form
128
+ // submissions. Since the pass1 data is missing, this must be the initial page render. So, we don't need to
129
+ // do anything yet.
130
+ return;
131
+ }
132
+
133
+ ITSEC_Lib_Password_Requirements::validate_password( $user, $_POST['pass1'], array(
134
+ 'error' => $errors,
135
+ 'context' => 'reset-password',
136
+ ) );
137
+ }
138
+
139
+ /**
140
+ * Whenever a user object is updated, set when their password was last updated.
141
+ *
142
+ * @param int $user_id
143
+ * @param object $old_user_data
144
+ */
145
+ public function handle_update_user( $user_id, $old_user_data ) {
146
+
147
+ $user = get_userdata( $user_id );
148
+
149
+ if ( $user->user_pass === $old_user_data->user_pass ) {
150
+ return;
151
+ }
152
+
153
+ $this->handle_password_updated( $user );
154
+ }
155
+
156
+ /**
157
+ * When a user resets their password, update the last change time.
158
+ *
159
+ * For some unknown reason, the password reset routine uses {@see wp_set_password()} instead of {@see wp_update_user()}.
160
+ *
161
+ * @param WP_User $user
162
+ * @param string $new_password
163
+ */
164
+ public function handle_password_reset( $user, $new_password ) {
165
+ $this->handle_password_updated( $user );
166
+ $this->handle_plain_text_password_available( $user, $new_password );
167
+ }
168
+
169
+ /**
170
+ * When a user logs in, if their password hasn't been validated yet,
171
+ * validate it.
172
+ *
173
+ * @param WP_User $user
174
+ * @param string $password
175
+ *
176
+ * @return WP_User
177
+ */
178
+ public function check_password_on_login( $user, $password ) {
179
+
180
+ if ( ! $user instanceof WP_User ) {
181
+ return $user;
182
+ }
183
+
184
+ if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
185
+ return $user;
186
+ }
187
+
188
+ $this->handle_plain_text_password_available( $user, $password );
189
+
190
+ return $user;
191
+ }
192
+
193
+ /**
194
+ * When a password is updated, set the last updated time and delete any pending required change.
195
+ *
196
+ * @param WP_User $user
197
+ */
198
+ protected function handle_password_updated( $user ) {
199
+ delete_user_meta( $user->ID, 'itsec_password_change_required' );
200
+ update_user_meta( $user->ID, 'itsec_last_password_change', ITSEC_Core::get_current_time_gmt() );
201
+ }
202
+
203
+ /**
204
+ * When a plain text password is available, we perform any evaluations that have not yet been performed for this password.
205
+ *
206
+ * @param WP_User $user
207
+ * @param string $password
208
+ */
209
+ protected function handle_plain_text_password_available( $user, $password ) {
210
+
211
+ $config = wp_parse_args( get_user_meta( $user->ID, self::META_KEY, true ), array(
212
+ 'evaluation_times' => array(),
213
+ ) );
214
+
215
+ $last_updated = ITSEC_Lib_Password_Requirements::password_last_changed( $user );
216
+
217
+ $settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
218
+
219
+ foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
220
+
221
+ if ( ! $requirement['evaluate'] ) {
222
+ continue;
223
+ }
224
+
225
+ if ( ! $requirement['evaluate_if_not_enabled'] && ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
226
+ continue;
227
+ }
228
+
229
+ if ( isset( $config['evaluation_times'][ $code ] ) && $config['evaluation_times'][ $code ] >= $last_updated ) {
230
+ continue;
231
+ }
232
+
233
+ $evaluation = call_user_func( $requirement['evaluate'], $password, $user );
234
+
235
+ if ( is_wp_error( $evaluation ) ) {
236
+ continue;
237
+ }
238
+
239
+ $config['evaluation_times'][ $code ] = ITSEC_Core::get_current_time_gmt();
240
+ update_user_meta( $user->ID, $requirement['meta'], $evaluation );
241
+
242
+ if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
243
+ continue;
244
+ }
245
+
246
+ $validated = call_user_func( $requirement['validate'], $evaluation, $user, $settings[ $code ], array() );
247
+
248
+ if ( true === $validated ) {
249
+ continue;
250
+ }
251
+
252
+ ITSEC_Lib_Password_Requirements::flag_password_change_required( $user, $code );
253
+ }
254
+
255
+ update_user_meta( $user->ID, self::META_KEY, $config );
256
+ }
257
+
258
+ /**
259
+ * Validate password.
260
+ *
261
+ * @param \WP_Error $error
262
+ * @param \WP_User|stdClass $user
263
+ * @param string $new_password
264
+ * @param array $args
265
+ */
266
+ public function validate_password( $error, $user, $new_password, $args ) {
267
+
268
+ $settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
269
+
270
+ foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
271
+
272
+ if ( ! $requirement['evaluate'] || ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( $code ) ) {
273
+ continue;
274
+ }
275
+
276
+ $evaluation = call_user_func( $requirement['evaluate'], $new_password, $user );
277
+
278
+ if ( is_wp_error( $evaluation ) ) {
279
+ continue;
280
+ }
281
+
282
+ $validated = call_user_func( $requirement['validate'], $evaluation, $user, $settings[ $code ], $args );
283
+
284
+ if ( true === $validated ) {
285
+ continue;
286
+ }
287
+
288
+ // The default error message is a safeguard that should never occur.
289
+ $message = $validated ? $validated : esc_html__( "The provided password does not meet this site's requirements.", 'better-wp-security' );
290
+
291
+ switch ( $args['context'] ) {
292
+ case 'admin-user-create':
293
+ $message .= ' ' . __( 'The user has not been created.', 'better-wp-security' );
294
+ break;
295
+ case 'admin-profile-update':
296
+ $message .= ' ' . __( 'The user changes have not been saved.', 'better-wp-security' );
297
+ break;
298
+ case 'profile-update':
299
+ $message .= ' ' . __( 'Your profile has not been updated.', 'better-wp-security' );
300
+ break;
301
+ case 'reset-password':
302
+ $message .= ' ' . __( 'The password has not been updated.', 'better-wp-security' );
303
+ break;
304
+ }
305
+
306
+ $error->add( 'pass', $message );
307
+ }
308
+ }
309
+
310
+ /**
311
+ * When a user logs in, run any flag checks to see if a password change should be forced.
312
+ *
313
+ * @param string $username
314
+ * @param WP_User|null $user
315
+ */
316
+ public function flag_check( $username, $user = null ) {
317
+
318
+ if ( ! $user && is_user_logged_in() ) {
319
+ $user = wp_get_current_user();
320
+ }
321
+
322
+ if ( ! $user instanceof WP_User || ! $user->exists() ) {
323
+ return;
324
+ }
325
+
326
+ foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
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 ) ) {
334
+ ITSEC_Lib_Password_Requirements::flag_password_change_required( $user, $code );
335
+
336
+ return;
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Is a given requirement enabled.
343
+ *
344
+ * @param string $requirement
345
+ *
346
+ * @return bool
347
+ */
348
+ protected function is_requirement_enabled( $requirement ) {
349
+
350
+ $requirements = ITSEC_Lib_Password_Requirements::get_registered();
351
+
352
+ if ( ! isset( $requirements[ $requirement ] ) ) {
353
+ return false;
354
+ }
355
+
356
+ // If the requirement does not have any settings, than it is always enabled.
357
+ if ( null === $requirements[ $requirement ]['settings_config'] ) {
358
+ return true;
359
+ }
360
+
361
+ $enabled = ITSEC_Modules::get_setting( 'password-requirements', 'enabled_requirements' );
362
+
363
+ if ( ! empty( $enabled[ $requirement ] ) ) {
364
+ return true;
365
+ }
366
+
367
+ return false;
368
+ }
369
+
370
+ /**
371
+ * When a user's role changes, clear all the evaluation times as evaluat
372
+ *
373
+ * @param int $user_id
374
+ */
375
+ public function handle_role_change( $user_id ) {
376
+
377
+ $config = get_user_meta( $user_id, self::META_KEY, true );
378
+
379
+ if ( ! $config || ! is_array( $config ) ) {
380
+ return;
381
+ }
382
+
383
+ $config['evaluation_times'] = array();
384
+
385
+ update_user_meta( $user_id, self::META_KEY, $config );
386
+ }
387
+
388
+ /**
389
+ * Register the password change interstitial.
390
+ *
391
+ * @param ITSEC_Lib_Login_Interstitial $lib
392
+ */
393
+ public function register_interstitial( $lib ) {
394
+ $lib->register( 'update-password', array( $this, 'render_interstitial' ), array(
395
+ 'show_to_user' => array( 'ITSEC_Lib_Password_Requirements', 'password_change_required' ),
396
+ 'info_message' => array( 'ITSEC_Lib_Password_Requirements', 'get_message_for_password_change_reason' ),
397
+ 'submit' => array( $this, 'submit' ),
398
+ ) );
399
+ }
400
+
401
+ /**
402
+ * Render the interstitial.
403
+ *
404
+ * @param WP_User $user
405
+ */
406
+ public function render_interstitial( $user ) {
407
+ do_action( 'itsec_password_requirements_change_form', $user );
408
+ ?>
409
+
410
+ <div class="user-pass1-wrap">
411
+ <p><label for="pass1"><?php _e( 'New Password', 'better-wp-security' ); ?></label></p>
412
+ </div>
413
+
414
+ <div class="wp-pwd">
415
+ <span class="password-input-wrapper">
416
+ <input type="password" data-reveal="1"
417
+ data-pw="<?php echo esc_attr( wp_generate_password( 16 ) ); ?>" name="pass1" id="pass1"
418
+ class="input" size="20" value="" autocomplete="off" aria-describedby="pass-strength-result"/>
419
+ </span>
420
+ <div id="pass-strength-result" class="hide-if-no-js" aria-live="polite"><?php _e( 'Strength indicator', 'better-wp-security' ); ?></div>
421
+ <div class="pw-weak">
422
+ <label>
423
+ <input type="checkbox" name="pw_weak" class="pw-checkbox" />
424
+ <?php _e( 'Confirm use of weak password' ); ?>
425
+ </label>
426
+ </div>
427
+ </div>
428
+
429
+ <p class="user-pass2-wrap">
430
+ <label for="pass2"><?php _e( 'Confirm new password' ) ?></label><br/>
431
+ <input type="password" name="pass2" id="pass2" class="input" size="20" value="" autocomplete="off"/>
432
+ </p>
433
+
434
+ <p class="description indicator-hint"><?php echo wp_get_password_hint(); ?></p>
435
+ <br class="clear"/>
436
+
437
+ <p class="submit">
438
+ <input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="<?php esc_attr_e( 'Update Password', 'better-wp-security' ); ?>"/>
439
+ </p>
440
+
441
+ <?php
442
+ }
443
+
444
+ /**
445
+ * Handle the request to update the user's password.
446
+ *
447
+ * @param WP_User $user
448
+ * @param array $data POSTed data.
449
+ *
450
+ * @return WP_Error|null
451
+ */
452
+ public function submit( $user, $data ) {
453
+
454
+ if ( empty( $data['pass1'] ) ) {
455
+ return new WP_Error(
456
+ 'itsec-password-requirements-empty-password',
457
+ __( 'Please enter your new password.', 'better-wp-security' )
458
+ );
459
+ }
460
+
461
+ $error = ITSEC_Lib_Password_Requirements::validate_password( $user, $data['pass1'], array(
462
+ 'context' => 'interstitial',
463
+ ) );
464
+
465
+ if ( $error->get_error_message() ) {
466
+ return $error;
467
+ }
468
+
469
+ $error = wp_update_user( array(
470
+ 'ID' => $user->ID,
471
+ 'user_pass' => $data['pass1']
472
+ ) );
473
+
474
+ if ( is_wp_error( $error ) ) {
475
+ return $error;
476
+ }
477
+
478
+ return null;
479
+ }
480
+ }
core/modules/password-requirements/css/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/password-requirements/css/settings-page.css ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ #itsec-module-card-password-requirements h4 {
2
+ margin: .25em 0;
3
+ }
core/modules/password-requirements/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php //You don't belong here. ?>
core/modules/password-requirements/js/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/password-requirements/js/settings-page.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function ( $ ) {
2
+
3
+ $( function () {
4
+
5
+ $( '.itsec-password-requirements-container' ).each( function () {
6
+ updateVisibility( $( this ).data( 'code' ) );
7
+ } );
8
+
9
+ $( '.itsec-password-requirements-container__enabled-wrap input[type="checkbox"]' ).on( 'change', function ( e ) {
10
+ updateVisibility( $( this ).parents( '.itsec-password-requirements-container' ).data( 'code' ) );
11
+ } )
12
+ } );
13
+
14
+ function updateVisibility( code ) {
15
+ var $checkbox = $( '.itsec-password-requirements-container__enabled-wrap--' + code + ' input[type="checkbox"]' ),
16
+ $details = $( '.itsec-password-requirements-container__settings-wrap--' + code );
17
+
18
+ if ( $checkbox.is( ':checked' ) ) {
19
+ $details.show();
20
+ } else {
21
+ $details.hide();
22
+ }
23
+ }
24
+ })( jQuery );
core/modules/password-requirements/settings-page.php ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ITSEC_Password_Requirements_Settings_Page
5
+ */
6
+ class ITSEC_Password_Requirements_Settings_Page extends ITSEC_Module_Settings_Page {
7
+
8
+ /**
9
+ * ITSEC_Password_Requirements_Settings_Page constructor.
10
+ */
11
+ public function __construct() {
12
+ $this->id = 'password-requirements';
13
+ $this->title = __( 'Password Requirements', 'better-wp-security' );
14
+ $this->description = __( 'Manage and configure Password Requirements for users.', 'better-wp-security' );
15
+ $this->can_save = true;
16
+
17
+ parent::__construct();
18
+ }
19
+
20
+ protected function render_description( $form ) {
21
+ ?>
22
+ <p><?php esc_html_e( 'Manage and configure Password Requirements for users.', 'better-wp-security' ); ?></p>
23
+ <?php
24
+ }
25
+
26
+ public function enqueue_scripts_and_styles() {
27
+ wp_enqueue_script( 'itsec-password-requirements-settings-page', plugins_url( 'js/settings-page.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build() );
28
+ wp_enqueue_style( 'itsec-password-requirements-settings-page', plugins_url( 'css/settings-page.css', __FILE__ ), array(), ITSEC_Core::get_plugin_build() );
29
+
30
+ do_action( 'itsec_password_requirements_enqueue_scripts_and_styles' );
31
+ }
32
+
33
+ public function handle_ajax_request( $data ) {
34
+ if ( ! isset( $data['password_requirement'] ) ) {
35
+ return;
36
+ }
37
+
38
+ /**
39
+ * Fires when Password Requirement ajax request is incoming.
40
+ *
41
+ * The dynamic portion of the hook, $data['password_requirement'] refers to the reason code of the requirement.
42
+ *
43
+ * @param array $data
44
+ */
45
+ do_action( 'itsec_password_requirements_ajax_' . $data['password_requirement'], $data );
46
+ }
47
+
48
+ /**
49
+ * Render settings.
50
+ *
51
+ * @param ITSEC_Form $form
52
+ */
53
+ protected function render_settings( $form ) {
54
+
55
+ $requirements = ITSEC_Lib_Password_Requirements::get_registered();
56
+ ?>
57
+
58
+ <?php do_action( 'itsec_password_requirements_settings_before', $form ); ?>
59
+
60
+ <div class="itsec-password-requirements-settings">
61
+ <?php do_action( 'itsec_password_requirements_settings_begin', $form ); ?>
62
+ <?php foreach ( $requirements as $code => $requirement ):
63
+
64
+ if ( null === $requirement['settings_config'] ) {
65
+ continue;
66
+ }
67
+
68
+ $config = call_user_func( $requirement['settings_config'] );
69
+
70
+ $form->add_input_group( 'enabled_requirements' );
71
+ ?>
72
+ <div class="itsec-settings-section itsec-password-requirements-container itsec-password-requirements-container--<?php echo esc_attr( $code ); ?>"
73
+ data-code="<?php echo esc_attr( $code ) ?>">
74
+ <h4><?php echo esc_html( isset( $config['label'] ) ? $config['label'] : $code ); ?></h4>
75
+
76
+ <?php if ( ! empty( $config['description'] ) ): ?>
77
+ <p class="description"><?php echo $config['description']; ?></p>
78
+ <?php endif; ?>
79
+
80
+ <table class="form-table">
81
+ <thead class="itsec-password-requirements-container__enabled-wrap itsec-password-requirements-container__enabled-wrap--<?php echo esc_attr( $code ); ?>">
82
+ <tr>
83
+ <th scope="row">
84
+ <label for="itsec-password-requirements-enabled_requirements-<?php echo esc_attr( $code ); ?>">
85
+ <?php esc_html_e( 'Enabled', 'better-wp-security' ); ?>
86
+ </label>
87
+ </th>
88
+ <td><?php $form->add_checkbox( $code ); ?></td>
89
+ </tr>
90
+ </thead>
91
+ <?php
92
+ $form->remove_input_group();
93
+
94
+ if ( ! empty( $config['render'] ) ) :
95
+ $form->add_input_group( 'requirement_settings', $code );
96
+ ?>
97
+ <tbody class="itsec-password-requirements-container__settings-wrap itsec-password-requirements-container__settings-wrap--<?php echo esc_attr( $code ); ?>">
98
+ <?php call_user_func( $config['render'], $form ) ?>
99
+ </tbody>
100
+ <?php
101
+ $form->remove_input_group();
102
+ $form->remove_input_group();
103
+ endif; ?>
104
+ </table>
105
+ </div>
106
+ <?php endforeach; ?>
107
+ <?php do_action( 'itsec_password_requirements_settings_end', $form ); ?>
108
+ </div>
109
+ <?php do_action( 'itsec_password_requirements_settings_after', $form ); ?>
110
+ <?php
111
+
112
+ }
113
+ }
114
+
115
+ new ITSEC_Password_Requirements_Settings_Page();
core/modules/password-requirements/settings.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ITSEC_Password_Requirements_Settings
5
+ */
6
+ class ITSEC_Password_Requirements_Settings extends ITSEC_Settings {
7
+
8
+ public function get_id() {
9
+ return 'password-requirements';
10
+ }
11
+
12
+ public function get_defaults() {
13
+ return array(
14
+ 'enabled_requirements' => array(),
15
+ 'requirement_settings' => array(),
16
+ );
17
+ }
18
+
19
+ public function load() {
20
+
21
+ $this->settings = ITSEC_Storage::get( $this->get_id() );
22
+ $defaults = $this->get_defaults();
23
+
24
+ if ( ! is_array( $this->settings ) ) {
25
+ $this->settings = array();
26
+ }
27
+
28
+ $this->settings = array_merge( $defaults, $this->settings );
29
+
30
+ foreach ( ITSEC_Lib_Password_Requirements::get_registered() as $code => $requirement ) {
31
+
32
+ if ( null === $requirement['defaults'] ) {
33
+ continue;
34
+ }
35
+
36
+ if ( isset( $this->settings['requirement_settings'][ $code ] ) ) {
37
+ $current = $this->settings['requirement_settings'][ $code ];
38
+ } else {
39
+ $current = array();
40
+ }
41
+
42
+ $this->settings['requirement_settings'][ $code ] = wp_parse_args( $current, $requirement['defaults'] );
43
+ }
44
+ }
45
+ }
46
+
47
+ ITSEC_Modules::register_settings( new ITSEC_Password_Requirements_Settings() );
core/modules/password-requirements/validator.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ITSEC_Password_Requirements_Validator
5
+ */
6
+ class ITSEC_Password_Requirements_Validator extends ITSEC_Validator {
7
+
8
+ /** @var string */
9
+ private $current_requirement;
10
+
11
+ public function get_id() {
12
+ return 'password-requirements';
13
+ }
14
+
15
+ protected function sanitize_settings() {
16
+ $this->sanitize_setting( 'array', 'enabled_requirements', __( 'Enabled Requirements', 'better-wp-security' ) );
17
+ $this->sanitize_setting( 'array', 'requirement_settings', __( 'Requirement Settings', 'better-wp-security' ) );
18
+
19
+ $requirements = ITSEC_Lib_Password_Requirements::get_registered();
20
+
21
+ $settings = $this->settings;
22
+
23
+ foreach ( $requirements as $code => $requirement ) {
24
+ if ( null === $requirement['settings_config'] ) {
25
+ continue;
26
+ }
27
+
28
+ $config = call_user_func( $requirement['settings_config'] );
29
+ $sanitize = call_user_func( $config['sanitize'], $this->settings );
30
+
31
+ if ( is_wp_error( $sanitize ) ) {
32
+ $this->add_error( $sanitize );
33
+
34
+ if ( ITSEC_Core::is_interactive() ) {
35
+ $this->set_can_save( false );
36
+ }
37
+ } elseif ( is_array( $sanitize ) ) {
38
+ $this->settings = isset( $settings['requirement_settings'][ $code ] ) ? $settings['requirement_settings'][ $code ] : $requirement['defaults'];
39
+ $this->current_requirement = $code;
40
+
41
+ foreach ( $sanitize as $args ) {
42
+ call_user_func_array( array( $this, 'sanitize_setting' ), $args );
43
+ }
44
+
45
+ $settings['requirement_settings'][ $code ] = $this->settings;
46
+ $this->settings = $settings;
47
+ $this->current_requirement = null;
48
+ }
49
+
50
+ }
51
+ }
52
+
53
+ protected function generate_error( $id, $var, $type, $error ) {
54
+ if ( null === $this->current_requirement ) {
55
+ return parent::generate_error( $id, $var, $type, $error );
56
+ }
57
+
58
+ return new WP_Error( "itsec-validator-$id-invalid-type-enabled_requirements-{$this->current_requirement}-$var-$type", $error );
59
+ }
60
+ }
61
+
62
+ ITSEC_Modules::register_validator( new ITSEC_Password_Requirements_Validator() );
core/modules/pro/settings-page.php CHANGED
@@ -36,22 +36,6 @@ final class ITSEC_Malware_Scheduling_Settings_Page extends ITSEC_Module_Settings
36
  new ITSEC_Malware_Scheduling_Settings_Page();
37
 
38
 
39
- final class ITSEC_Password_Expiration_Settings_Page extends ITSEC_Module_Settings_Page {
40
- public function __construct() {
41
- $this->id = 'password-expiration';
42
- $this->title = __( 'Password Expiration', 'better-wp-security' );
43
- $this->description = __( 'Strengthen the passwords on the site with automated password expiration.', 'better-wp-security' );
44
- $this->type = 'recommended';
45
- $this->pro = true;
46
- $this->upsell = true;
47
- $this->upsell_url = 'https://ithemes.com/security/wordpress-password-security/?utm_source=wordpressadmin&utm_medium=widget&utm_campaign=itsecfreecta';
48
-
49
- parent::__construct();
50
- }
51
- }
52
- new ITSEC_Password_Expiration_Settings_Page();
53
-
54
-
55
  final class ITSEC_Privilege_Escalation_Settings_Page extends ITSEC_Module_Settings_Page {
56
  public function __construct() {
57
  $this->id = 'privilege';
36
  new ITSEC_Malware_Scheduling_Settings_Page();
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  final class ITSEC_Privilege_Escalation_Settings_Page extends ITSEC_Module_Settings_Page {
40
  public function __construct() {
41
  $this->id = 'privilege';
core/modules/security-check/scanner.php CHANGED
@@ -56,7 +56,7 @@ final class ITSEC_Security_Check_Scanner {
56
 
57
  self::add_network_brute_force_signup();
58
 
59
- self::enforce_activation( 'strong-passwords', __( 'Strong Password Enforcement', 'better-wp-security' ) );
60
  self::enforce_activation( 'two-factor', __( 'Two-Factor Authentication', 'better-wp-security' ) );
61
  self::enforce_setting( 'two-factor', 'available_methods', 'all', esc_html__( 'Changed the Authentication Methods Available to Users setting in Two-Factor Authentication to "All Methods".', 'better-wp-security' ) );
62
  self::enforce_setting( 'two-factor', 'protect_user_type', 'privileged_users', esc_html__( 'Changed the User Type Protection setting in Two-Factor Authentication to "Privileged Users".', 'better-wp-security' ) );
@@ -155,6 +155,21 @@ final class ITSEC_Security_Check_Scanner {
155
  self::$feedback->add_text( sprintf( $text, $name ) );
156
  }
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  public static function activate_network_brute_force( $data ) {
159
  if ( ! isset( $data['email'] ) ) {
160
  ITSEC_Response::add_error( new WP_Error( 'itsec-security-check-missing-email', __( 'The email value is missing.', 'better-wp-security' ) ) );
56
 
57
  self::add_network_brute_force_signup();
58
 
59
+ self::enforce_password_requirement_enabled( 'strength', __( 'Strong Password Enforcement', 'better-wp-security' ) );
60
  self::enforce_activation( 'two-factor', __( 'Two-Factor Authentication', 'better-wp-security' ) );
61
  self::enforce_setting( 'two-factor', 'available_methods', 'all', esc_html__( 'Changed the Authentication Methods Available to Users setting in Two-Factor Authentication to "All Methods".', 'better-wp-security' ) );
62
  self::enforce_setting( 'two-factor', 'protect_user_type', 'privileged_users', esc_html__( 'Changed the User Type Protection setting in Two-Factor Authentication to "Privileged Users".', 'better-wp-security' ) );
155
  self::$feedback->add_text( sprintf( $text, $name ) );
156
  }
157
 
158
+ private static function enforce_password_requirement_enabled( $requirement, $description ) {
159
+
160
+ $active = ITSEC_Modules::get_setting( 'password-requirements', 'enabled_requirements' );
161
+
162
+ if ( ! empty( $active[ $requirement ] ) ) {
163
+ return;
164
+ }
165
+
166
+ $active[ $requirement ] = true;
167
+
168
+ ITSEC_Modules::set_setting( 'password-requirements', 'enabled_requirements', $active );
169
+ self::$feedback->add_section( 'enforce-setting-password-requirements-enabled_requirements', array( 'status' => 'action-taken' ) );
170
+ self::$feedback->add_text( $description );
171
+ }
172
+
173
  public static function activate_network_brute_force( $data ) {
174
  if ( ! isset( $data['email'] ) ) {
175
  ITSEC_Response::add_error( new WP_Error( 'itsec-security-check-missing-email', __( 'The email value is missing.', 'better-wp-security' ) ) );
core/modules/strong-passwords/class-itsec-strong-passwords.php CHANGED
@@ -1,247 +1,218 @@
1
  <?php
2
 
3
  final class ITSEC_Strong_Passwords {
 
 
 
4
  public function __construct() {
5
 
6
- add_filter( 'itsec_password_change_requirement_description_for_strength', array( $this, 'strength_reason' ) );
7
- add_action( 'user_profile_update_errors', array( $this, 'filter_user_profile_update_errors' ), 0, 3 );
8
- add_action( 'itsec_validate_password', array( $this, 'validate_password' ), 10, 4 );
9
 
10
  add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts' ) );
11
- add_action( 'login_enqueue_scripts', array( $this, 'add_scripts' ) );
 
12
  }
13
 
14
  /**
15
- * Enqueue script to add measured password strength to the form submission data.
16
- *
17
- * @return void
18
  */
19
- public function add_scripts() {
20
- wp_enqueue_script( 'itsec_strong_passwords', plugins_url( 'js/script.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build() );
 
 
 
 
 
 
 
 
21
  }
22
 
23
  /**
24
- * Get the reason description for why a password change was set to 'strength'.
25
  *
26
- * @return string
27
  */
28
- public function strength_reason() {
29
 
30
- $message = __( 'Due to site rules, a strong password is required for your account. Please choose a new password that rates as <strong>Strong</strong> on the meter.', 'better-wp-security' );
31
 
32
- return wp_kses( $message, array( 'strong' => '' ) );
33
- }
34
-
35
- /**
36
- * Handle submission of a form to create or edit a user.
37
- *
38
- * @param WP_Error $errors WP_Error object.
39
- * @param bool $update Whether this is a user update.
40
- * @param stdClass $user User object.
41
- *
42
- * @return WP_Error
43
- */
44
- public function filter_user_profile_update_errors( $errors, $update, $user ) {
45
-
46
- // An error regarding the password was already found.
47
- if ( $errors->get_error_data( 'pass' ) ) {
48
- return $errors;
49
  }
50
 
51
- if ( isset( $user->user_pass ) || ! $update ) {
52
- return $errors;
53
  }
54
 
55
- // The password was not changed, but an update is occurring. Test to see if we need to prompt for a password change.
56
- // This also handles the case where a user's role is being changed to one that requires strong password enforcement.
57
-
58
- $strength = get_user_meta( $user->ID, 'itsec-password-strength', true );
59
-
60
- if ( ! is_numeric( $strength ) || $strength < 0 || $strength > 4 ) {
61
- // Not enough data to determine whether a change of password is required.
62
- return $errors;
63
- }
64
 
65
  require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
66
 
67
- if ( isset( $user->role ) ) {
68
- $role = $this->get_canonical_role_from_role_and_user( $user->role, $user );
69
- } else {
70
- $role = ITSEC_Lib_Canonical_Roles::get_user_role( $user );
71
  }
72
-
73
- if ( ! $this->role_requires_strong_password( $role ) ) {
74
- return $errors;
75
- }
76
-
77
- if ( 4 === (int) $strength ) {
78
- return $errors;
79
- }
80
-
81
- if ( ! $update ) {
82
- $context = 'admin-user-create';
83
- } elseif ( $user->ID === get_current_user_id() ) {
84
- $context = 'profile-update';
85
- } else {
86
- $context = 'admin-profile-update';
87
- }
88
-
89
- $errors->add( 'pass', $this->make_error_message( $context ) );
90
-
91
- return $errors;
92
  }
93
 
94
  /**
95
- * Validate a new password according to the configured strength rules.
 
 
96
  *
97
- * @param WP_Error $error
98
- * @param WP_User $user
99
- * @param string $new_password
100
- * @param array $args
101
  */
102
- public function validate_password( $error, $user, $new_password, $args = array() ) {
103
 
104
- if ( isset( $args['strength'] ) ) {
105
- $reported_strength = $args['strength'];
106
- } else {
107
- $reported_strength = false;
108
  }
109
 
110
- if ( empty( $user->ID ) || ! is_numeric( $user->ID ) ) {
111
- $role = isset( $args['role'] ) ? $args['role'] : get_option( 'default_role', 'subscriber' );
112
- } elseif ( isset( $args['role'] ) ) {
113
- $role = $this->get_canonical_role_from_role_and_user( $args['role'], $user );
114
- } else {
115
- require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
116
- $role = ITSEC_Lib_Canonical_Roles::get_user_role( $user );
117
- }
118
 
119
- if ( ! $this->role_requires_strong_password( $role ) ) {
120
- return;
121
- }
122
 
123
- if ( ! $this->fails_enforcement( $user, $new_password, $reported_strength ) ) {
124
- return;
125
  }
 
126
 
127
- $message = $this->make_error_message( $args['context'] );
128
-
129
- $error->add( 'pass', $message );
 
 
 
 
 
 
130
  }
131
 
132
  /**
133
- * Retrieve a canonical role for a user and a role.
134
  *
135
- * @param string $role
136
  * @param WP_User $user
137
  *
138
- * @return string
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  */
140
- private function get_canonical_role_from_role_and_user( $role, $user ) {
141
- if ( empty( $role ) ) {
142
- $role_caps = array();
143
- } else {
144
- $role_caps = array_keys( array_filter( wp_roles()->get_role( $role )->capabilities ) );
145
  }
146
 
147
- $user_caps = array();
148
 
149
- if ( isset( $user->caps ) ) {
150
- $wp_roles = wp_roles();
151
 
152
- foreach ( $user->caps as $cap => $has ) {
153
- if ( $has && ! $wp_roles->is_role( $cap ) ) {
154
- $user_caps[] = $has;
155
- }
156
- }
157
  }
158
 
159
- require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
 
160
 
161
- return ITSEC_Lib_Canonical_Roles::get_role_from_caps( array_merge( $role_caps, $user_caps ) );
 
 
 
 
 
 
162
  }
163
 
164
  /**
165
- * Get the strong password error message according to the given context.
166
- *
167
- * @param string $context
168
  *
169
- * @return string
170
  */
171
- private function make_error_message( $context ) {
172
- $message = __( '<strong>Error</strong>: Due to site rules, a strong password is required. Please choose a new password that rates as <strong>Strong</strong> on the meter.', 'better-wp-security' );
173
-
174
- if ( 'admin-user-create' === $context ) {
175
- $message .= ' ' . __( 'The user has not been created.', 'better-wp-security' );
176
- } elseif ( 'admin-profile-update' === $context ) {
177
- $message .= ' ' . __( 'The user changes have not been saved.', 'better-wp-security' );
178
- } elseif ( 'profile-update' === $context ) {
179
- $message .= ' ' . __( 'Your profile has not been updated.', 'better-wp-security' );
180
- } elseif ( 'reset-password' === $context ) {
181
- $message .= ' ' . __( 'The password has not been updated.', 'better-wp-security' );
182
- }
183
-
184
- return wp_kses( $message, array( 'strong' => array() ) );
 
 
 
 
 
 
185
  }
186
 
187
  /**
188
- * Does the given role require a strong password.
189
  *
190
- * @param string $role The user's canonical role.
191
  *
192
- * @return bool
193
  */
194
- private function role_requires_strong_password( $role ) {
195
- require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
 
 
 
 
196
 
197
- $min_role = ITSEC_Modules::get_setting( 'strong-passwords', 'role' );
 
 
 
 
 
 
198
 
199
- return ITSEC_Lib_Canonical_Roles::is_canonical_role_at_least( $min_role, $role );
200
  }
201
 
202
  /**
203
- * Determine if the user requires enforcement and if it fails that enforcement.
204
  *
205
- * @param WP_User|stdClass $user Requires either a valid WP_User object or an object that has the following members:
206
- * user_login, first_name, last_name, nickname, display_name, user_email, user_url, and
207
- * description. A member of user_pass is required if $password_strength is false.
208
- * @param string $new_password The user's new password.
209
- * @param int|boolean $password_strength [optional] An integer value representing the password strength, if known, or false.
210
- * Defaults to false.
211
  *
212
- * @return boolean True if the user requires enforcement and has a password weaker than strong. False otherwise.
213
  */
214
- private function fails_enforcement( $user, $new_password, $password_strength = false ) {
215
-
216
- if ( false !== $password_strength ) {
217
- return $password_strength < 4;
218
- }
219
-
220
- if ( ! empty( $_POST['password_strength'] ) && 'strong' !== $_POST['password_strength'] ) {
221
- // We want to validate the password strength if the form data says that the password is strong since we want
222
- // to protect against spoofing. If the form data says that the password isn't strong, believe it.
223
-
224
- $password_strength = 1;
225
- } else {
226
- // The form data does not indicate a password strength or the data claimed that the password is strong,
227
- // which is a claim that must be validated. Use the zxcvbn library to find the password strength score.
228
 
229
- $penalty_strings = array(
230
- get_site_option( 'admin_email' )
231
- );
232
- $user_properties = array( 'user_login', 'first_name', 'last_name', 'nickname', 'display_name', 'user_email', 'user_url', 'description' );
233
 
234
- foreach ( $user_properties as $user_property ) {
235
- if ( isset( $user->$user_property ) ) {
236
- $penalty_strings[] = $user->$user_property;
237
- }
238
  }
239
-
240
- $results = ITSEC_Lib::get_password_strength_results( $new_password, $penalty_strings );
241
- $password_strength = $results->score;
242
  }
243
 
244
- return $password_strength < 4;
 
 
245
  }
246
  }
247
 
1
  <?php
2
 
3
  final class ITSEC_Strong_Passwords {
4
+
5
+ const STRENGTH_KEY = 'itsec-password-strength';
6
+
7
  public function __construct() {
8
 
9
+ add_action( 'itsec_register_password_requirements', array( $this, 'register_requirements' ) );
 
 
10
 
11
  add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts' ) );
12
+ add_action( 'resetpass_form', array( $this, 'add_scripts_to_wp_login' ) );
13
+ add_action( 'itsec_password_requirements_change_form', array( $this, 'add_scripts_to_wp_login' ) );
14
  }
15
 
16
  /**
17
+ * Register the Strong Passwords requirement.
 
 
18
  */
19
+ public function register_requirements() {
20
+ ITSEC_Lib_Password_Requirements::register( 'strength', array(
21
+ 'evaluate' => array( $this, 'evaluate' ),
22
+ 'validate' => array( $this, 'validate' ),
23
+ 'reason' => array( $this, 'reason' ),
24
+ 'meta' => self::STRENGTH_KEY,
25
+ 'evaluate_if_not_enabled' => true,
26
+ 'defaults' => array( 'role' => 'administrator' ),
27
+ 'settings_config' => array( $this, 'get_settings_config' ),
28
+ ) );
29
  }
30
 
31
  /**
32
+ * Enqueue script to hide the acknowledge weak password checkbox.
33
  *
34
+ * @return void
35
  */
36
+ public function add_scripts() {
37
 
38
+ global $pagenow;
39
 
40
+ if ( 'profile.php' !== $pagenow ) {
41
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
 
44
+ if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( 'strength' ) ) {
45
+ return;
46
  }
47
 
48
+ $settings = ITSEC_Lib_Password_Requirements::get_requirement_settings( 'strength' );
49
+ $role = isset( $settings['role'] ) ? $settings['role'] : 'administrator';
 
 
 
 
 
 
 
50
 
51
  require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
52
 
53
+ if ( ITSEC_Lib_Canonical_Roles::is_user_at_least( $role ) ) {
54
+ wp_enqueue_script( 'itsec_strong_passwords', plugins_url( 'js/script.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build() );
 
 
55
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
  /**
59
+ * On the reset password and login interstitial form, render the Strong Passwords JS to hide the acknowledge weak password checkbox.
60
+ *
61
+ * We have to do this in these late actions so we have access to the correct user data.
62
  *
63
+ * @param WP_User $user
 
 
 
64
  */
65
+ public function add_scripts_to_wp_login( $user ) {
66
 
67
+ if ( ! ITSEC_Lib_Password_Requirements::is_requirement_enabled( 'strength' ) ) {
68
+ return;
 
 
69
  }
70
 
71
+ $settings = ITSEC_Lib_Password_Requirements::get_requirement_settings( 'strength' );
72
+ $role = isset( $settings['role'] ) ? $settings['role'] : 'administrator';
 
 
 
 
 
 
73
 
74
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
 
 
75
 
76
+ if ( ITSEC_Lib_Canonical_Roles::is_user_at_least( $role, $user ) ) {
77
+ wp_enqueue_script( 'itsec_strong_passwords', plugins_url( 'js/script.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build() );
78
  }
79
+ }
80
 
81
+ /**
82
+ * Provide the reason string displayed to users on the change password form.
83
+ *
84
+ * @param $evaluation
85
+ *
86
+ * @return string
87
+ */
88
+ public function reason( $evaluation ) {
89
+ return esc_html__( 'Due to site rules, a strong password is required for your account. Please choose a new password that rates as strong on the meter.', 'better-wp-security' );
90
  }
91
 
92
  /**
93
+ * Evaluate the strength of a password.
94
  *
95
+ * @param string $password
96
  * @param WP_User $user
97
  *
98
+ * @return int
99
+ */
100
+ public function evaluate( $password, $user ) {
101
+ return $this->get_password_strength( $user, $password );
102
+ }
103
+
104
+ /**
105
+ * Validate whether a password strength is acceptable for a given user.
106
+ *
107
+ * @param int $strength
108
+ * @param WP_User|stdClass $user
109
+ * @param array $settings
110
+ * @param array $args
111
+ *
112
+ * @return bool
113
  */
114
+ public function validate( $strength, $user, $settings, $args ) {
115
+
116
+ if ( (int) $strength === 4 ) {
117
+ return true;
 
118
  }
119
 
120
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
121
 
122
+ $role = isset( $args['canonical'] ) ? $args['canonical'] : ITSEC_Lib_Canonical_Roles::get_user_role( $user );
 
123
 
124
+ if ( ! ITSEC_Lib_Canonical_Roles::is_canonical_role_at_least( $settings['role'], $role ) ) {
125
+ return true;
 
 
 
126
  }
127
 
128
+ return $this->make_error_message();
129
+ }
130
 
131
+ public function get_settings_config() {
132
+ return array(
133
+ 'label' => esc_html__( 'Strong Passwords', 'better-wp-security' ),
134
+ 'description' => esc_html__( 'Force users to use strong passwords as rated by the WordPress password meter.', 'better-wp-security' ),
135
+ 'render' => array( $this, 'render_settings' ),
136
+ 'sanitize' => array( $this, 'sanitize_settings' ),
137
+ );
138
  }
139
 
140
  /**
141
+ * Render the Settings Page.
 
 
142
  *
143
+ * @param ITSEC_Form $form
144
  */
145
+ public function render_settings( $form ) {
146
+
147
+ $href = 'http://codex.wordpress.org/Roles_and_Capabilities';
148
+ $link = '<a href="' . $href . '" target="_blank" rel="noopener noreferrer">' . $href . '</a>';
149
+ ?>
150
+ <tr>
151
+ <th scope="row">
152
+ <label for="itsec-password-requirements-requirement_settings-strength-role">
153
+ <?php esc_html_e( 'Minimum Role', 'better-wp-security' ); ?>
154
+ </label>
155
+ </th>
156
+ <td>
157
+ <?php $form->add_canonical_roles( 'role' ); ?>
158
+ <br/>
159
+ <label for="itsec-password-requirements-requirement_settings-strength-role"><?php _e( 'Minimum role at which a user must choose a strong password.', 'better-wp-security' ); ?></label>
160
+ <p class="description"><?php printf( __( 'For more information on WordPress roles and capabilities please see %s.', 'better-wp-security' ), $link ); ?></p>
161
+ <p class="warningtext description"><?php _e( 'Warning: If your site invites public registrations setting the role too low may annoy your members.', 'better-wp-security' ); ?></p>
162
+ </td>
163
+ </tr>
164
+ <?php
165
  }
166
 
167
  /**
168
+ * Get a list of the sanitizer rules to apply.
169
  *
170
+ * @param array $settings
171
  *
172
+ * @return array
173
  */
174
+ public function sanitize_settings( $settings ) {
175
+ return array(
176
+ array( 'string', 'role', esc_html__( 'Minimum Role for Strong Passwords', 'better-wp-security' ) ),
177
+ array( 'canonical-roles', 'role', esc_html__( 'Minimum Role for Strong Passwords', 'better-wp-security' ) ),
178
+ );
179
+ }
180
 
181
+ /**
182
+ * Get the strong password error message according to the given context.
183
+ *
184
+ * @return string
185
+ */
186
+ private function make_error_message() {
187
+ $message = __( '<strong>Error</strong>: Due to site rules, a strong password is required. Please choose a new password that rates as <strong>Strong</strong> on the meter.', 'better-wp-security' );
188
 
189
+ return wp_kses( $message, array( 'strong' => array() ) );
190
  }
191
 
192
  /**
193
+ * Calculate the strength of a password.
194
  *
195
+ * @param WP_User $user
196
+ * @param string $password
 
 
 
 
197
  *
198
+ * @return int
199
  */
200
+ private function get_password_strength( $user, $password ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ $penalty_strings = array(
203
+ get_site_option( 'admin_email' )
204
+ );
205
+ $user_properties = array( 'user_login', 'first_name', 'last_name', 'nickname', 'display_name', 'user_email', 'user_url', 'description' );
206
 
207
+ foreach ( $user_properties as $user_property ) {
208
+ if ( isset( $user->$user_property ) ) {
209
+ $penalty_strings[] = $user->$user_property;
 
210
  }
 
 
 
211
  }
212
 
213
+ $results = ITSEC_Lib::get_password_strength_results( $password, $penalty_strings );
214
+
215
+ return $results->score;
216
  }
217
  }
218
 
core/modules/strong-passwords/js/script.js CHANGED
@@ -1,10 +1,3 @@
1
  jQuery( document ).ready( function () {
2
-
3
- jQuery( '#resetpassform, #your-profile, #createuser' ).submit( function () {
4
-
5
- jQuery( '#submit, #createusersub, #resetpassform' ).append( '<input type="hidden" name="password_strength" id="password_strength" value="' + jQuery( '#pass-strength-result' ).attr( 'class' ).replace( /hide-if-no-js /, '' ) + '">' );
6
-
7
- } );
8
-
9
  jQuery( '.pw-weak' ).remove();
10
- } );
1
  jQuery( document ).ready( function () {
 
 
 
 
 
 
 
2
  jQuery( '.pw-weak' ).remove();
3
+ } );
core/modules/strong-passwords/settings-page.php DELETED
@@ -1,51 +0,0 @@
1
- <?php
2
-
3
- final class ITSEC_Strong_Passwords_Settings_Page extends ITSEC_Module_Settings_Page {
4
- private $script_version = 1;
5
-
6
-
7
- public function __construct() {
8
- $this->id = 'strong-passwords';
9
- $this->title = __( 'Strong Password Enforcement', 'better-wp-security' );
10
- $this->description = __( 'Force users to use strong passwords as rated by the WordPress password meter.', 'better-wp-security' );
11
- $this->type = 'recommended';
12
-
13
- parent::__construct();
14
- }
15
-
16
- protected function render_description( $form ) {
17
-
18
- ?>
19
- <p><?php _e( 'Force users to use strong passwords as rated by the WordPress password meter.', 'better-wp-security' ); ?></p>
20
- <?php
21
-
22
- }
23
-
24
- protected function render_settings( $form ) {
25
- $roles = array(
26
- 'administrator' => translate_user_role( 'Administrator' ),
27
- 'editor' => translate_user_role( 'Editor' ),
28
- 'author' => translate_user_role( 'Author' ),
29
- 'contributor' => translate_user_role( 'Contributor' ),
30
- 'subscriber' => translate_user_role( 'Subscriber' ),
31
- );
32
-
33
- ?>
34
- <table class="form-table itsec-settings-section">
35
- <tr>
36
- <th scope="row"><label for="itsec-strong-passwords-role"><?php _e( 'Select Role for Strong Passwords', 'better-wp-security' ); ?></label></th>
37
- <td>
38
- <?php $form->add_select( 'role', $roles ); ?>
39
- <br />
40
- <label for="itsec-strong-passwords-role"><?php _e( 'Minimum role at which a user must choose a strong password.', 'better-wp-security' ); ?></label>
41
- <p class="description"><?php printf( __( 'For more information on WordPress roles and capabilities please see <a href="%1$s" target="_blank" rel="noopener noreferrer">%1$s</a>.', 'better-wp-security' ), 'http://codex.wordpress.org/Roles_and_Capabilities' ); ?></p>
42
- <p class="warningtext description"><?php _e( 'Warning: If your site invites public registrations setting the role too low may annoy your members.', 'better-wp-security' ); ?></p>
43
- </td>
44
- </tr>
45
- </table>
46
- <?php
47
-
48
- }
49
- }
50
-
51
- new ITSEC_Strong_Passwords_Settings_Page();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/modules/strong-passwords/setup.php CHANGED
@@ -6,10 +6,10 @@ if ( ! class_exists( 'ITSEC_Strong_Passwords_Setup' ) ) {
6
 
7
  public function __construct() {
8
 
9
- add_action( 'itsec_modules_do_plugin_activation', array( $this, 'execute_activate' ) );
10
- add_action( 'itsec_modules_do_plugin_deactivation', array( $this, 'execute_deactivate' ) );
11
- add_action( 'itsec_modules_do_plugin_uninstall', array( $this, 'execute_uninstall' ) );
12
- add_action( 'itsec_modules_do_plugin_upgrade', array( $this, 'execute_upgrade' ), null, 2 );
13
 
14
  }
15
 
@@ -86,6 +86,28 @@ if ( ! class_exists( 'ITSEC_Strong_Passwords_Setup' ) ) {
86
  }
87
  }
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
 
91
  }
6
 
7
  public function __construct() {
8
 
9
+ add_action( 'itsec_modules_do_plugin_activation', array( $this, 'execute_activate' ) );
10
+ add_action( 'itsec_modules_do_plugin_deactivation', array( $this, 'execute_deactivate' ) );
11
+ add_action( 'itsec_modules_do_plugin_uninstall', array( $this, 'execute_uninstall' ) );
12
+ add_action( 'itsec_modules_do_plugin_upgrade', array( $this, 'execute_upgrade' ), null, 2 );
13
 
14
  }
15
 
86
  }
87
  }
88
 
89
+ if ( $itsec_old_version < 4096 ) {
90
+ $active = get_site_option( 'itsec_active_modules', array() );
91
+
92
+ if ( ! empty( $active['strong-passwords'] ) ) {
93
+ $active_requirements = ITSEC_Modules::get_setting( 'password-requirements', 'enabled_requirements' );
94
+ $active_requirements['strength'] = true;
95
+ ITSEC_Modules::set_setting( 'password-requirements', 'enabled_requirements', $active_requirements );
96
+ }
97
+
98
+ $requirement_settings = ITSEC_Modules::get_setting( 'password-requirements', 'requirement_settings' );
99
+ $requirement_settings['strength']['role'] = ITSEC_Modules::get_setting( 'strong-passwords', 'role', 'administrator' );
100
+ ITSEC_Modules::set_setting( 'password-requirements', 'requirement_settings', $requirement_settings );
101
+
102
+ unset( $active['strong-passwords'] );
103
+
104
+ // Need to do this directly to be able to remove a module from the list entirely.
105
+ if ( is_multisite() ) {
106
+ update_site_option( 'itsec_active_modules', $active );
107
+ } else {
108
+ update_option( 'itsec_active_modules', $active );
109
+ }
110
+ }
111
  }
112
 
113
  }
core/notify.php CHANGED
@@ -124,9 +124,11 @@ class ITSEC_Notify {
124
 
125
  $data_proxy = new ITSEC_Notify_Data_Proxy( $data );
126
 
127
- $mail = $nc->mail();
128
  $mail->add_header( $title, $banner_title );
 
129
  $mail->add_info_box( sprintf( esc_html__( 'The following is a summary of security related activity on your site: %s', 'better-wp-security' ), '<b>' . $mail->get_display_url() . '</b>' ) );
 
130
 
131
  $content = $mail->get_content();
132
 
@@ -203,10 +205,13 @@ class ITSEC_Notify {
203
  '<a href="' . ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_logs_page_url() ) . '"><b>',
204
  '</b></a>'
205
  ) );
206
- $mail->add_divider();
207
- $mail->add_large_text( esc_html__( 'Is your site as secure as it could be?', 'better-wp-security' ) );
208
- $mail->add_text( esc_html__( 'Ensure your site is using recommended settings and features with a security check.', 'better-wp-security' ) );
209
- $mail->add_button( esc_html__( 'Run a Security Check ', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_security_check_page_url() ) );
 
 
 
210
 
211
  $mail->add_footer();
212
 
124
 
125
  $data_proxy = new ITSEC_Notify_Data_Proxy( $data );
126
 
127
+ $mail = $nc->mail( 'digest' );
128
  $mail->add_header( $title, $banner_title );
129
+ $mail->start_group( 'intro' );
130
  $mail->add_info_box( sprintf( esc_html__( 'The following is a summary of security related activity on your site: %s', 'better-wp-security' ), '<b>' . $mail->get_display_url() . '</b>' ) );
131
+ $mail->end_group();
132
 
133
  $content = $mail->get_content();
134
 
205
  '<a href="' . ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_logs_page_url() ) . '"><b>',
206
  '</b></a>'
207
  ) );
208
+
209
+ if ( apply_filters( 'itsec_security_digest_include_security_check', true ) ) {
210
+ $mail->add_divider();
211
+ $mail->add_large_text( esc_html__( 'Is your site as secure as it could be?', 'better-wp-security' ) );
212
+ $mail->add_text( esc_html__( 'Ensure your site is using recommended settings and features with a security check.', 'better-wp-security' ) );
213
+ $mail->add_button( esc_html__( 'Run a Security Check ✓', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_security_check_page_url() ) );
214
+ }
215
 
216
  $mail->add_footer();
217
 
history.txt CHANGED
@@ -765,3 +765,20 @@
765
  7.0.1 - 2018-05-25 - Chris Jean & Timothy Jacobs
766
  Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
767
  Bug Fix: Skip recovery if File Change storage is empty.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765
  7.0.1 - 2018-05-25 - Chris Jean & Timothy Jacobs
766
  Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
767
  Bug Fix: Skip recovery if File Change storage is empty.
768
+ 7.0.2 - 2018-06-14 - Chris Jean & Timothy Jacobs
769
+ Enhancement: Add UI to cancel in progress File Scan.
770
+ Enhancement: Add basic admin debug page to help diagnosing and resolving issues. Particularly with the events.
771
+ Enhancement: Add debug settings JSON editor.
772
+ Enhancement: Continually evaluate password strength for users instead of only during registration.
773
+ Enhancement: Introduce Password Requirements module for managing and enforcing password requirements.
774
+ Bug Fix: Accessing password requirement settings would not resolve properly in some instances.
775
+ Bug Fix: Away Mode would not lock out users who were already logged-in during the "away" period.
776
+ Bug Fix: Enforce the Strong Passwords requirement during Security Check.
777
+ Bug Fix: Ensure scheduling lock is cleared by the Cron Scheduler when not proceeding with running events.
778
+ Bug Fix: If a password requirement has been disabled or is no longer available, don't consider the password as needing a change.
779
+ Bug Fix: Only hide "Acknowledge Weak Password" checkbox if the user was not allowed to use a weak password.
780
+ Bug Fix: Password strength would not be evaluated if password was set using custom PHP or CLI commands.
781
+ Bug Fix: Prevent File Change from getting stuck in an infinite rescheduling loop on the first step.
782
+ Bug Fix: Remove distributed storage table on uninstall.
783
+ Tweak: Don't write to the tracked files setting if the file hash has not changed.
784
+ Tweak: If no last password change date is recorded for the user, treat their registration date as the last change date.
readme.txt CHANGED
@@ -3,7 +3,7 @@ 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: 4.9.6
6
- Stable tag: 7.0.1
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,24 @@ Free support may be available with the help of the community in the <a href="htt
189
 
190
  == Changelog ==
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  = 7.0.1 =
193
  * Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
194
  * Bug Fix: Skip recovery if File Change storage is empty.
@@ -454,5 +472,5 @@ Free support may be available with the help of the community in the <a href="htt
454
 
455
  == Upgrade Notice ==
456
 
457
- = 7.0.1 =
458
- Version 7.0.0 contains important additions to support privacy controls, bug fixes, and various enhancements. It is recommended for all users.
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: 4.9.6
6
+ Stable tag: 7.0.2
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.0.2 =
193
+ * Enhancement: Add UI to cancel in progress File Scan.
194
+ * Enhancement: Add basic admin debug page to help diagnosing and resolving issues. Particularly with the events.
195
+ * Enhancement: Add debug settings JSON editor.
196
+ * Enhancement: Continually evaluate password strength for users instead of only during registration.
197
+ * Enhancement: Introduce Password Requirements module for managing and enforcing password requirements.
198
+ * Bug Fix: Accessing password requirement settings would not resolve properly in some instances.
199
+ * Bug Fix: Away Mode would not lock out users who were already logged-in during the "away" period.
200
+ * Bug Fix: Enforce the Strong Passwords requirement during Security Check.
201
+ * Bug Fix: Ensure scheduling lock is cleared by the Cron Scheduler when not proceeding with running events.
202
+ * Bug Fix: If a password requirement has been disabled or is no longer available, don't consider the password as needing a change.
203
+ * Bug Fix: Only hide "Acknowledge Weak Password" checkbox if the user was not allowed to use a weak password.
204
+ * Bug Fix: Password strength would not be evaluated if password was set using custom PHP or CLI commands.
205
+ * Bug Fix: Prevent File Change from getting stuck in an infinite rescheduling loop on the first step.
206
+ * Bug Fix: Remove distributed storage table on uninstall.
207
+ * Tweak: Don't write to the tracked files setting if the file hash has not changed.
208
+ * Tweak: If no last password change date is recorded for the user, treat their registration date as the last change date.
209
+
210
  = 7.0.1 =
211
  * Bug Fix: Fixed an "Uncaught Error: Call to undefined function esc_like()" error that could occur when exporting or erasing personal data.
212
  * Bug Fix: Skip recovery if File Change storage is empty.
472
 
473
  == Upgrade Notice ==
474
 
475
+ = 7.0.2 =
476
+ Version 7.0.2 contains important bug fixes and various enhancements. It is recommended for all users.