iThemes Security (formerly Better WP Security) - Version 6.3.0

Version Description

  • Important: The way that Hide Backend functions changes in this release. Previously, if your Hide Backend Login Slug was wplogin, going to example.com/wplogin would result in the URL remaining example.com/wplogin. The new implementation of this feature results in a redirect to a URL that looks as follows: example.com/wp-login.php?itsec-hb-token=wplogin. While this may not be desireable for some users, this change was necessary to fix longstanding compatibility issues with other plugins. Once you access the login page using the Login Slug page, a cookie is set with an expiration time of one hour. As long as the cookie remains, you can access example.com/wp-login.php without having to access the Hide Backend Login Slug first. If you wish to confirm that Hide Backend is working properly on your site, opening up a private browsing window is a quick way to test without having to log out and clear cookies.
    • New Feature: Added support for iThemes Sync to run the Security Check feature from inside the Sync service.
    • New Feature: Added support for the ITSEC_DISABLE_MODULES define.
    • Bug Fix: Removed warning: "Non-static method ITSEC_Setup::uninstall() should not be called statically".
    • Bug Fix: Fixed the ability to manually enter a page number to navigate to on the Security > Logs page.
    • Bug Fix: Fixed source of warning that could appear when creating a backup while running a PHP version less than 5.4.
    • Bug Fix: Fixed source of notice that could appear when reseting a user's password when the Strong Passwords Enforcement feature is enabled.
    • Bug Fix: Fixed bugs that prevented reporting of specific error messages related to updating the wp-config.php file.
    • Bug Fix: Fixed an infinite loop that could occur when expiring a cookie and Hide Backend is enabled.
    • Bug Fix: Fixed compatibility issue with the Jetpack plugin when Hide Backend is enabled which could prevent Jetpack from redirecting users to the wordpress.com login page.
    • Bug Fix: Fixed issue where access to wp-admin/admin-post.php when Hide Backend is enabled.
    • Bug Fix: Fixed issue that could prevent "Register" and "Lost your password?" links from working properly on the login page when Hide Backend is enabled.
    • Bug Fix: Fix fatal error when updating a profile.
    • Bug Fix: Fix strong passwords not being recognized as strong on the profile page.
    • Bug Fix: Fix fatal error when registering a new user without specifying a role ( iThemes Exchange ).
    • Bug Fix: Compatability with JetPack SSO and Password Requirements.
    • Bug Fix: Ensure viewport meta is defined when loading the password requirements update password form.
    • Bug Fix: Hide Backend is now compatible with Jetpack Single Sign On.
    • Bug Fix: Hide Backend now hides registration pages on multisite sites.
    • Bug Fix: Fixed password-protected posts not properly handling the password when Hide Backend is enabled.
    • Enhancement: Removed AhrefsBot from the HackRepair blacklist as they are legitimate bot.
    • Enhancement: Improved efficiency of Hide Backend code, increasing site performance when the feature is enabled.
    • Enhancement: Enforce strong passwords during log-in. Can be disabled via the ITSEC_DISABLE_PASSWORD_REQUIREMENTS constant.
    • Enhancement: Use canonical roles library to determine if a new user or an updated role requires a strong password.
    • Enhancement: Introduce password requirements module to centralize handling of password updates.
    • Enhancement: The Hide Backend hidden login URL is no longer leaked by password-protected content.
    • Enhancement: Allow for searching through modules and settings.
    • Enhancement: Link to other module settings pages without forcing the page to refresh.
    • Enhancement: Fire an action, "itsec_change_admin_user_id", when the admin user id changes.
    • Enhancement: Changed default Hide Backend Register Slug from wp-register.php to wp-signup.php since WordPress switched from using wp-register.php to wp-signup.php for registrations. This will not affect existing sites.
    • Enhancement: Hide Backend functions purely in PHP code now rather than relying half on PHP code and half on .htaccess and nginx.conf modifications. This allows Hide Backend to function on web servers and server configurations that it was previously not compatible with.
    • Misc: Updated or added phpDoc to many functions.
    • Misc: Updated Disable File Locking description.
Download this release

Release Info

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

Code changes from version 6.2.1 to 6.3.0

Files changed (69) hide show
  1. better-wp-security.php +2 -2
  2. core/admin-pages/css/style.css +23 -7
  3. core/admin-pages/js/script.js +177 -14
  4. core/admin-pages/module-settings.php +2 -2
  5. core/admin-pages/page-logs.php +34 -11
  6. core/admin-pages/page-settings.php +7 -2
  7. core/{class-itsec-core.php → core.php} +195 -19
  8. core/{class-itsec-files.php → files.php} +0 -0
  9. core/history.txt +39 -0
  10. core/js/scrollTo.js +7 -0
  11. core/js/url.js +0 -93
  12. core/{class-itsec-lib.php → lib.php} +81 -13
  13. core/lib/class-itsec-lib-canonical-roles.php +43 -0
  14. core/lib/class-itsec-lib-password-requirements.php +532 -0
  15. core/lib/includes/function.login-header.php +204 -0
  16. core/lib/includes/index.php +1 -0
  17. core/{lock.php → lib/lock.php} +0 -0
  18. core/{class-itsec-lockout.php → lockout.php} +138 -27
  19. core/{class-itsec-logger-all-logs.php → logger-all-logs.php} +0 -0
  20. core/{class-itsec-logger.php → logger.php} +13 -13
  21. core/{class-itsec-modules.php → modules.php} +180 -2
  22. core/modules/404-detection/class-itsec-four-oh-four.php +1 -0
  23. core/modules/admin-user/validator.php +9 -0
  24. core/modules/away-mode/class-itsec-away-mode.php +3 -1
  25. core/modules/away-mode/utilities.php +39 -0
  26. core/modules/backup/class-itsec-backup.php +1 -1
  27. core/modules/ban-users/lists/hackrepair-apache.inc +0 -1
  28. core/modules/ban-users/lists/hackrepair-litespeed.inc +0 -1
  29. core/modules/ban-users/lists/hackrepair-nginx.inc +0 -1
  30. core/modules/brute-force/class-itsec-brute-force.php +5 -0
  31. core/modules/content-directory/settings-page.php +1 -1
  32. core/modules/database-prefix/settings-page.php +1 -1
  33. core/modules/database-prefix/utility.php +2 -2
  34. core/modules/file-change/class-itsec-file-change.php +1 -1
  35. core/modules/global/active.php +9 -0
  36. core/modules/global/settings-page.php +1 -1
  37. core/modules/hide-backend/class-itsec-hide-backend.php +249 -253
  38. core/modules/hide-backend/config-generators.php +0 -43
  39. core/modules/hide-backend/settings-page.php +2 -2
  40. core/modules/hide-backend/settings.php +2 -2
  41. core/modules/hide-backend/setup.php +5 -1
  42. core/modules/hide-backend/validator.php +2 -9
  43. core/modules/ipcheck/class-itsec-ipcheck.php +4 -0
  44. core/modules/malware/class-itsec-malware-scanner.php +2 -1
  45. core/modules/malware/class-itsec-malware.php +1 -1
  46. core/modules/security-check/active.php +8 -0
  47. core/modules/security-check/feedback-renderer.php +142 -0
  48. core/modules/security-check/feedback.php +94 -0
  49. core/modules/security-check/js/settings-page.js +58 -21
  50. core/modules/security-check/scanner.php +82 -77
  51. core/modules/security-check/settings-page.php +12 -24
  52. core/modules/security-check/sync-verbs/index.php +1 -0
  53. core/modules/security-check/sync-verbs/itsec-do-security-check.php +12 -0
  54. core/modules/security-check/sync-verbs/itsec-get-security-check-feedback-response.php +20 -0
  55. core/modules/security-check/sync-verbs/itsec-get-security-check-modules.php +12 -0
  56. core/modules/ssl/class-itsec-ssl-admin.php +9 -1
  57. core/modules/strong-passwords/class-itsec-strong-passwords.php +157 -95
  58. core/modules/system-tweaks/class-itsec-system-tweaks.php +3 -0
  59. core/modules/wordpress-tweaks/class-itsec-wordpress-tweaks.php +36 -6
  60. core/{class-itsec-notify.php → notify.php} +2 -0
  61. core/{class-itsec-response.php → response.php} +7 -1
  62. core/{class-itsec-setup.php → setup.php} +1 -2
  63. core/sidebar-widget-active-lockouts.php +4 -0
  64. core/sidebar-widget-temp-whitelist.php +2 -0
  65. core/sync-verbs/itsec-get-temp-whitelist.php +1 -0
  66. core/sync-verbs/itsec-release-lockout.php +1 -0
  67. core/sync-verbs/itsec-set-temp-whitelist.php +1 -0
  68. history.txt +35 -0
  69. readme.txt +40 -5
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: 6.2.1
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
@@ -29,6 +29,6 @@ if ( is_admin() ) {
29
  require( "$itsec_dir/lib/icon-fonts/load.php" );
30
  }
31
 
32
- require( "$itsec_dir/core/class-itsec-core.php" );
33
  $itsec_core = ITSEC_Core::get_instance();
34
  $itsec_core->init( __FILE__, esc_html__( 'iThemes Security', 'better-wp-security' ) );
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: 6.3.0
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
29
  require( "$itsec_dir/lib/icon-fonts/load.php" );
30
  }
31
 
32
+ require( "$itsec_dir/core/core.php" );
33
  $itsec_core = ITSEC_Core::get_instance();
34
  $itsec_core->init( __FILE__, esc_html__( 'iThemes Security', 'better-wp-security' ) );
core/admin-pages/css/style.css CHANGED
@@ -295,6 +295,14 @@ body.itsec-modal-open {
295
  margin-bottom: 1.5em;
296
  }
297
 
 
 
 
 
 
 
 
 
298
 
299
 
300
  /*
@@ -322,9 +330,11 @@ body.itsec-modal-open {
322
  .itsec-settings-view-toggle a.itsec-selected {
323
  color: #00a0d2;
324
  }
325
- .itsec-feature-search {
326
  float: left;
 
327
  }
 
328
  .itsec-feature-tabs {
329
  float: right;
330
  margin: 0;
@@ -527,8 +537,8 @@ body.itsec-modal-open {
527
  .itsec-module-card .module-description {
528
  display: none;
529
  }
530
- .itsec-feature-search,
531
- .itsec-feature-search input {
532
  width: 100%;
533
  margin-bottom: 1em;
534
  }
@@ -618,12 +628,15 @@ body.itsec-modal-open {
618
  margin: -9px 0 0 0;
619
  }
620
 
621
- .itsec-security-check-container-complete:before {
 
 
622
  content: "\f147";
623
  color: #46b450;
624
  }
625
 
626
- .itsec-security-check-container-incomplete:before {
 
627
  content: "\f534";
628
  color: #f2dd28;
629
  }
@@ -633,11 +646,14 @@ body.itsec-modal-open {
633
  color: #dc3232;
634
  }
635
 
636
- .itsec-security-check-container-complete {
 
 
637
  border-left-color: #46b450;
638
  }
639
 
640
- .itsec-security-check-container-incomplete {
 
641
  border-left-color: #f2dd28;
642
  }
643
 
295
  margin-bottom: 1.5em;
296
  }
297
 
298
+ .itsec-settings-module-settings .form-table th {
299
+ padding-left: 15px;
300
+ }
301
+
302
+ .itsec-settings-module-settings .itsec-highlighted-setting {
303
+ background: #e5f5fa;
304
+ }
305
+
306
 
307
 
308
  /*
330
  .itsec-settings-view-toggle a.itsec-selected {
331
  color: #00a0d2;
332
  }
333
+ .itsec-module-search {
334
  float: left;
335
+ margin-right: 1em;
336
  }
337
+
338
  .itsec-feature-tabs {
339
  float: right;
340
  margin: 0;
537
  .itsec-module-card .module-description {
538
  display: none;
539
  }
540
+ .itsec-module-search,
541
+ .itsec-module-search input {
542
  width: 100%;
543
  margin-bottom: 1em;
544
  }
628
  margin: -9px 0 0 0;
629
  }
630
 
631
+ .itsec-security-check-container-action-taken:before,
632
+ .itsec-security-check-container-complete:before,
633
+ .itsec-security-check-container-confirmation:before {
634
  content: "\f147";
635
  color: #46b450;
636
  }
637
 
638
+ .itsec-security-check-container-incomplete:before,
639
+ .itsec-security-check-container-call-to-action:before {
640
  content: "\f534";
641
  color: #f2dd28;
642
  }
646
  color: #dc3232;
647
  }
648
 
649
+ .itsec-security-check-container-action-taken,
650
+ .itsec-security-check-container-complete,
651
+ .itsec-security-check-container-confirmation {
652
  border-left-color: #46b450;
653
  }
654
 
655
+ .itsec-security-check-container-incomplete,
656
+ .itsec-security-check-container-call-to-action {
657
  border-left-color: #f2dd28;
658
  }
659
 
core/admin-pages/js/script.js CHANGED
@@ -40,6 +40,7 @@ var itsecSettingsPage = {
40
  $container.on( 'itsec-popstate', '.itsec-module-filter a', this.filterView );
41
  $container.on( 'click', '.itsec-settings-view-toggle a', this.toggleView );
42
  // $container.on( 'click', '.itsec-toggle-settings, .itsec-module-card-content h2', this.toggleSettings );
 
43
  $container.on( 'click', '.list .itsec-module-card:not(.itsec-module-pro-upsell) .itsec-module-card-content, .itsec-toggle-settings, .itsec-module-settings-cancel', this.toggleSettings );
44
  $container.on( 'itsec-popstate', '.list .itsec-module-card-content, .itsec-toggle-settings', this.toggleSettings );
45
  $container.on( 'click', '.itsec-close-modal, .itsec-modal-background', this.closeGridSettingsModal );
@@ -291,6 +292,35 @@ var itsecSettingsPage = {
291
  $cardContainer.fadeIn( 100 );
292
  },
293
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  toggleSettings: function( e ) {
295
  e.stopPropagation();
296
 
@@ -323,7 +353,28 @@ var itsecSettingsPage = {
323
  $container = $container.parents( '.itsec-module-card' ).find( '.itsec-module-card-content' );
324
  }
325
 
326
- $container.siblings( '.itsec-module-settings-container' ).stop().slideToggle( 300 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
  var $button = $container.find( '.itsec-toggle-settings' );
329
 
@@ -353,23 +404,46 @@ var itsecSettingsPage = {
353
  showGridSettingsModal: function( e ) {
354
  e.preventDefault();
355
 
356
- var $settingsContainer = jQuery(this).parents( '.itsec-module-card' ).find( '.itsec-module-settings-container' ),
 
357
  $modalBackground = jQuery( '.itsec-modal-background' );
358
 
359
- $modalBackground.show();
360
- $settingsContainer
361
- .show()
362
- .find( '.itsec-close' )
363
- .focus();
364
 
365
  jQuery( 'body' ).addClass( 'itsec-modal-open' );
366
 
 
367
 
368
- /* if ( jQuery(e.currentTarget).hasClass( 'page-title-action' ) ) {
369
- $modal.first().find( '.hidden' ).removeClass( 'hidden' );
 
 
 
 
 
 
370
  } else {
371
- $modal.first().find( '.itsec-right, .itsec-left' ).addClass( 'hidden' );
372
- }*/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  },
374
 
375
  closeGridSettingsModal: function( e ) {
@@ -382,8 +456,8 @@ var itsecSettingsPage = {
382
  }
383
  }
384
 
385
- jQuery( '.itsec-modal-background' ).hide();
386
- jQuery( '.itsec-module-settings-container' ).hide();
387
  jQuery( 'body' ).removeClass( 'itsec-modal-open' );
388
 
389
  if ( 'undefined' === typeof e || 'popstate' !== e.type ) {
@@ -393,6 +467,10 @@ var itsecSettingsPage = {
393
  }
394
  window.history.pushState( {'module':'', 'module_type':module_type}, module_type, '?page=itsec&module_type=' + module_type );
395
  }
 
 
 
 
396
  },
397
 
398
  toggleModuleActivation: function( e ) {
@@ -678,7 +756,7 @@ var itsecSettingsPage = {
678
  }
679
  };
680
 
681
- jQuery(document).ready(function() {
682
  itsecSettingsPage.init();
683
 
684
  if ( itsec_page.show_security_check ) {
@@ -715,4 +793,89 @@ jQuery(document).ready(function() {
715
  jQuery( '.ui-dialog :button' ).blur();
716
 
717
  } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
  });
40
  $container.on( 'itsec-popstate', '.itsec-module-filter a', this.filterView );
41
  $container.on( 'click', '.itsec-settings-view-toggle a', this.toggleView );
42
  // $container.on( 'click', '.itsec-toggle-settings, .itsec-module-card-content h2', this.toggleSettings );
43
+ $container.on( 'click', 'a[data-module-link]', this.openModuleFromLink );
44
  $container.on( 'click', '.list .itsec-module-card:not(.itsec-module-pro-upsell) .itsec-module-card-content, .itsec-toggle-settings, .itsec-module-settings-cancel', this.toggleSettings );
45
  $container.on( 'itsec-popstate', '.list .itsec-module-card-content, .itsec-toggle-settings', this.toggleSettings );
46
  $container.on( 'click', '.itsec-close-modal, .itsec-modal-background', this.closeGridSettingsModal );
292
  $cardContainer.fadeIn( 100 );
293
  },
294
 
295
+ openModuleFromLink: function( e ) {
296
+
297
+ var $link = jQuery( this ), module = $link.data( 'module-link' ),
298
+ $module = jQuery( '.itsec-module-card[data-module-id="' + module + '"]' );
299
+
300
+ if ( ! $module.length ) {
301
+ return; // safety check
302
+ }
303
+
304
+ e.preventDefault();
305
+
306
+ jQuery( '.itsec-module-settings-container:visible' ).hide();
307
+
308
+ var $listClassElement = $module.parents( '.itsec-module-cards-container' ),
309
+ $toggleButton = $module.find( '.itsec-toggle-settings' );
310
+
311
+ console.log( $toggleButton );
312
+
313
+ if ( $listClassElement.hasClass( 'list' ) ) {
314
+ itsecSettingsPage.toggleListSettingsCard.call( $toggleButton, e );
315
+ } else if ( $listClassElement.hasClass( 'grid' ) ) {
316
+ itsecSettingsPage.showGridSettingsModal.call( $toggleButton, e );
317
+ }
318
+
319
+ var type = $module.hasClass( 'itsec-module-type-advanced' ) ? 'advanced' : 'recommended';
320
+
321
+ window.history.pushState( {module: module}, module, '?page=itsec&module=' + module + '&module_type=' + type );
322
+ },
323
+
324
  toggleSettings: function( e ) {
325
  e.stopPropagation();
326
 
353
  $container = $container.parents( '.itsec-module-card' ).find( '.itsec-module-card-content' );
354
  }
355
 
356
+ var $settings = $container.siblings( '.itsec-module-settings-container' ),
357
+ isVisible = $settings.is( ':visible' );
358
+ $settings.stop().slideToggle( 300 );
359
+
360
+ if ( ! isVisible ) {
361
+ var $highlighted = jQuery( '.itsec-highlighted-setting', $settings );
362
+
363
+ if ( $highlighted.length ) {
364
+ setTimeout( function () {
365
+ jQuery.scrollTo( $highlighted.first(), 'swing', {
366
+ offset: { top: -30 },
367
+ onAfter: function() {
368
+ var $el = jQuery( 'input[type!="button"], textarea, select', $highlighted ).not( ':hidden' ).first();
369
+ itsecSettingsPage.focus( $el, $highlighted );
370
+ }
371
+ } );
372
+ }, 50 );
373
+ } else {
374
+ var $el = jQuery( 'input[type!="button"], textarea, select', $settings ).not( ':hidden' ).first();
375
+ itsecSettingsPage.focus( $el, $settings );
376
+ }
377
+ }
378
 
379
  var $button = $container.find( '.itsec-toggle-settings' );
380
 
404
  showGridSettingsModal: function( e ) {
405
  e.preventDefault();
406
 
407
+ var $module = jQuery(this).parents( '.itsec-module-card' ),
408
+ $settingsContainer = $module.find( '.itsec-module-settings-container' ),
409
  $modalBackground = jQuery( '.itsec-modal-background' );
410
 
411
+ $module.show();
412
+
413
+ $modalBackground.fadeIn();
414
+ $settingsContainer.fadeIn( 200 );
 
415
 
416
  jQuery( 'body' ).addClass( 'itsec-modal-open' );
417
 
418
+ var $highlighted = jQuery( '.itsec-highlighted-setting', $module ).first();
419
 
420
+ if ( $highlighted.length ) {
421
+ jQuery( '.itsec-module-settings-content-container', $module ).scrollTo( $highlighted, 'swing', {
422
+ offset: { top: -20 },
423
+ onAfter: function() {
424
+ var $el = jQuery( 'input[type!="button"], textarea, select', $highlighted ).not( ':hidden' ).first();
425
+ itsecSettingsPage.focus( $el, $highlighted );
426
+ }
427
+ } );
428
  } else {
429
+ var $el = jQuery( 'input[type!="button"], textarea, select', $settingsContainer ).not( ':hidden' ).first();
430
+ itsecSettingsPage.focus( $el, $settingsContainer );
431
+ }
432
+ },
433
+
434
+ focus: function( $el, $fallback ) {
435
+ if ( itsecSettingsPage.isElementVisible( $el ) && jQuery( window ).height() > 800 ) {
436
+ $el.focus();
437
+ } else {
438
+ $fallback.prop( 'tabindex', -1 ).focus();
439
+ }
440
+ },
441
+
442
+ isElementVisible: function( $el ) {
443
+
444
+ var $window = jQuery( window ), height = $window.height(), width = $window.width(), offset = $el.offset();
445
+
446
+ return offset.top < height && offset.left < width;
447
  },
448
 
449
  closeGridSettingsModal: function( e ) {
456
  }
457
  }
458
 
459
+ jQuery( '.itsec-modal-background' ).fadeOut();
460
+ jQuery( '.itsec-module-settings-container' ).fadeOut( 200 );
461
  jQuery( 'body' ).removeClass( 'itsec-modal-open' );
462
 
463
  if ( 'undefined' === typeof e || 'popstate' !== e.type ) {
467
  }
468
  window.history.pushState( {'module':'', 'module_type':module_type}, module_type, '?page=itsec&module_type=' + module_type );
469
  }
470
+
471
+ if ( jQuery( '#search' ).val().length ) {
472
+ jQuery( '#search' ).focus();
473
+ }
474
  },
475
 
476
  toggleModuleActivation: function( e ) {
756
  }
757
  };
758
 
759
+ jQuery(document).ready(function( $ ) {
760
  itsecSettingsPage.init();
761
 
762
  if ( itsec_page.show_security_check ) {
793
  jQuery( '.ui-dialog :button' ).blur();
794
 
795
  } );
796
+
797
+ var regex = /[^\w]/ig;
798
+
799
+ var $search = $( '#search' ), $cardsContainer = $( '.itsec-module-cards' ),
800
+ $cards = $( '.itsec-module-card', $cardsContainer ),
801
+ $searchFilter = $( '#itsec-module-filter-search' ),
802
+ $currentFilter = $( '.itsec-feature-tabs .current' ).parent();
803
+
804
+ $search.on( 'input', _.debounce( function () {
805
+ var query = $search.val().trim().replace( regex, ' ' );
806
+
807
+ var $maybeCurrent = $( '.itsec-feature-tabs .current' ).parent();
808
+
809
+ if ( $maybeCurrent && $maybeCurrent.prop( 'id' ) !== 'itsec-module-filter-search' ) {
810
+ $currentFilter = $maybeCurrent;
811
+ }
812
+
813
+ $( '.itsec-highlighted-setting', $cards ).removeClass( 'itsec-highlighted-setting' );
814
+
815
+ if ( !query.length ) {
816
+ $searchFilter.addClass( 'hide-if-js' );
817
+ $( 'a', $searchFilter ).removeClass( 'current' );
818
+ $( 'a', $currentFilter ).addClass( 'current' );
819
+
820
+ var type = $currentFilter.prop( 'id' ).substr( 20 );
821
+
822
+ if ( 'all' === type ) {
823
+ $cards.show();
824
+ } else {
825
+ $( '.itsec-module-type-' + type ).show();
826
+ $( '.itsec-module-card' ).not( '.itsec-module-type-' + type ).hide();
827
+ }
828
+
829
+ return;
830
+ }
831
+
832
+ var $titleMatches = $( ".itsec-module-card-content > h2:itsecContains('" + query + "')", $cards ),
833
+ $titleMatchesCards = $titleMatches.parents( '.itsec-module-card' );
834
+
835
+ var $descriptionMatches = $( ".itsec-module-card-content > p:itsecContains('" + query + "')", $cards ),
836
+ $descriptionMatchesCards = $descriptionMatches.parents( '.itsec-module-card' );
837
+
838
+ var $settingMatches = $( ".itsec-module-settings-container .form-table tr > th > label:itsecContains('" + query + "')", $cards ),
839
+ $settingMatchesCards = $settingMatches.parents( '.itsec-module-card' );
840
+
841
+
842
+ var $matches = $titleMatchesCards.add( $descriptionMatchesCards ).add( $settingMatchesCards );
843
+
844
+ $searchFilter.removeClass( 'hide-if-js' );
845
+ $( 'a', $currentFilter ).removeClass( 'current' );
846
+ $( 'a', $searchFilter ).addClass( 'current' );
847
+ $( '.count', $searchFilter ).text( '(' + $matches.length + ')' );
848
+
849
+ $cards.hide();
850
+ $matches.show();
851
+
852
+ $settingMatches.parents( 'tr' ).addClass( 'itsec-highlighted-setting' );
853
+
854
+ if ( $matches.length === 1 ) {
855
+ $( '.itsec-toggle-settings', $matches.first() ).click();
856
+ }
857
+
858
+ }, 250 ) );
859
+
860
+ $.expr[":"].itsecContains = $.expr.createPseudo( function ( arg ) {
861
+
862
+ return function ( elem ) {
863
+
864
+ var candidate = $( elem ).text().toUpperCase().replace( regex, ' ' ), term = arg.toUpperCase();
865
+ var index = candidate.indexOf( term );
866
+
867
+ if ( index === -1 ) {
868
+ return false;
869
+ }
870
+
871
+ if ( index === 0 ) {
872
+ return true;
873
+ }
874
+
875
+ var prior = candidate.charAt( index - 1 ), next = candidate.charAt( term.length + index );
876
+
877
+ // full word
878
+ return prior === ' ' && ( next === ' ' || next === '' );
879
+ };
880
+ } );
881
  });
core/admin-pages/module-settings.php CHANGED
@@ -183,7 +183,7 @@ class ITSEC_Module_Settings_Page {
183
  *
184
  * @access public
185
  *
186
- * @param object $form ITSEC_Form object used to create inputs.
187
  */
188
  public function render( $form ) {
189
 
@@ -224,7 +224,7 @@ class ITSEC_Module_Settings_Page {
224
  *
225
  * @access protected
226
  *
227
- * @param object $form ITSEC_Form object used to create inputs.
228
  */
229
  protected function render_settings( $form ) {
230
 
183
  *
184
  * @access public
185
  *
186
+ * @param ITSEC_Form $form ITSEC_Form object used to create inputs.
187
  */
188
  public function render( $form ) {
189
 
224
  *
225
  * @access protected
226
  *
227
+ * @param ITSEC_Form $form ITSEC_Form object used to create inputs.
228
  */
229
  protected function render_settings( $form ) {
230
 
core/admin-pages/page-logs.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
 
4
  final class ITSEC_Logs_Page {
5
- private $version = 1.3;
6
 
7
  private $self_url = '';
8
  private $modules = array();
@@ -283,7 +283,9 @@ final class ITSEC_Logs_Page {
283
  <p><?php $form->add_select( 'filter', $filters ); ?></p>
284
  <?php $form->end_form(); ?>
285
 
286
- <?php $this->show_filtered_logs( $filter ); ?>
 
 
287
  </div>
288
  <?php endif; ?>
289
  </div>
@@ -315,6 +317,8 @@ final class ITSEC_Logs_Page {
315
  }
316
 
317
  private function show_filtered_logs( $filter ) {
 
 
318
  foreach ( $this->logger_displays as $display ) {
319
  if ( $display['module'] === $filter ) {
320
  $callback = $display['callback'];
@@ -322,11 +326,20 @@ final class ITSEC_Logs_Page {
322
  }
323
  }
324
 
325
- if ( ! isset( $callback ) ) {
326
- $callback = array( $this, 'all_logs_content' );
 
 
 
 
 
327
  }
328
 
329
- call_user_func_array( $callback, array() );
 
 
 
 
330
  }
331
 
332
  /**
@@ -334,18 +347,29 @@ final class ITSEC_Logs_Page {
334
  *
335
  * @since 4.3
336
  *
 
 
337
  * @return void
338
  */
339
- public function all_logs_content() {
340
-
341
- global $wpdb;
342
 
343
- require_once( ITSEC_Core::get_core_dir() . '/class-itsec-logger-all-logs.php' );
344
 
345
  $log_display = new ITSEC_Logger_All_Logs();
346
  $log_display->prepare_items();
347
  $log_display->display();
348
 
 
 
 
 
 
 
 
 
 
 
 
349
  $log_count = $wpdb->get_var( "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_log`;" );
350
 
351
  ?>
@@ -371,8 +395,7 @@ final class ITSEC_Logs_Page {
371
  </tr>
372
  </table>
373
  </form>
374
- <?php
375
-
376
  }
377
  }
378
 
2
 
3
 
4
  final class ITSEC_Logs_Page {
5
+ private $version = 1.5;
6
 
7
  private $self_url = '';
8
  private $modules = array();
283
  <p><?php $form->add_select( 'filter', $filters ); ?></p>
284
  <?php $form->end_form(); ?>
285
 
286
+ <?php $form->start_form( array( 'method' => 'GET' ) ); ?>
287
+ <?php $this->show_filtered_logs( $filter ); ?>
288
+ <?php $form->end_form(); ?>
289
  </div>
290
  <?php endif; ?>
291
  </div>
317
  }
318
 
319
  private function show_filtered_logs( $filter ) {
320
+ $callback = null;
321
+
322
  foreach ( $this->logger_displays as $display ) {
323
  if ( $display['module'] === $filter ) {
324
  $callback = $display['callback'];
326
  }
327
  }
328
 
329
+ echo '<form method="get">';
330
+ echo '<input type="hidden" name="page" value="' . ( isset( $_GET['page'] ) ? esc_attr( $_GET['page'] ) : '' ) . '">';
331
+
332
+ if ( $callback ) {
333
+ call_user_func( $callback );
334
+ } else {
335
+ $this->all_logs_content( false );
336
  }
337
 
338
+ echo '</form>';
339
+
340
+ if ( ! $callback ) {
341
+ $this->clear_logs_form();
342
+ }
343
  }
344
 
345
  /**
347
  *
348
  * @since 4.3
349
  *
350
+ * @param bool $include_clear_logs_form Whether to include the form to clear all logs.
351
+ *
352
  * @return void
353
  */
354
+ public function all_logs_content( $include_clear_logs_form = true ) {
 
 
355
 
356
+ require_once( ITSEC_Core::get_core_dir() . '/logger-all-logs.php' );
357
 
358
  $log_display = new ITSEC_Logger_All_Logs();
359
  $log_display->prepare_items();
360
  $log_display->display();
361
 
362
+ if ( $include_clear_logs_form ) {
363
+ $this->clear_logs_form();
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Display the clear logs form.
369
+ */
370
+ public function clear_logs_form() {
371
+
372
+ global $wpdb;
373
  $log_count = $wpdb->get_var( "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_log`;" );
374
 
375
  ?>
395
  </tr>
396
  </table>
397
  </form>
398
+ <?php
 
399
  }
400
  }
401
 
core/admin-pages/page-settings.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
 
4
  final class ITSEC_Settings_Page {
5
- private $version = 1.4;
6
 
7
  private $self_url = '';
8
  private $modules = array();
@@ -83,7 +83,8 @@ final class ITSEC_Settings_Page {
83
  }
84
  }
85
 
86
- wp_enqueue_script( 'itsec-settings-page-script', plugins_url( 'js/script.js', __FILE__ ), array(), $this->version, true );
 
87
  wp_localize_script( 'itsec-settings-page-script', 'itsec_page', $vars );
88
  }
89
 
@@ -465,8 +466,12 @@ final class ITSEC_Settings_Page {
465
  <a class="itsec-grid<?php if ( 'grid' === $view ) { echo ' itsec-selected'; } ?>"><span class="dashicons dashicons-grid-view"></span></a>
466
  <a class="itsec-list<?php if ( 'list' === $view ) { echo ' itsec-selected'; } ?>"><span class="dashicons dashicons-list-view"></span></a>
467
  </div>
 
 
 
468
  <ul class="subsubsub itsec-feature-tabs hide-if-no-js">
469
  <?php echo implode( $feature_tabs, " |</li>\n" ) . "</li>\n"; ?>
 
470
  </ul>
471
  </div>
472
  <div class="itsec-module-cards-container <?php echo $view; ?> hide-if-js">
2
 
3
 
4
  final class ITSEC_Settings_Page {
5
+ private $version = 1.5;
6
 
7
  private $self_url = '';
8
  private $modules = array();
83
  }
84
  }
85
 
86
+ wp_enqueue_script( 'itsec-scrollTo', plugins_url( 'js/scrollTo.js', dirname( __FILE__ ) ), array( 'jquery' ) );
87
+ wp_enqueue_script( 'itsec-settings-page-script', plugins_url( 'js/script.js', __FILE__ ), array( 'underscore' ), $this->version, true );
88
  wp_localize_script( 'itsec-settings-page-script', 'itsec_page', $vars );
89
  }
90
 
466
  <a class="itsec-grid<?php if ( 'grid' === $view ) { echo ' itsec-selected'; } ?>"><span class="dashicons dashicons-grid-view"></span></a>
467
  <a class="itsec-list<?php if ( 'list' === $view ) { echo ' itsec-selected'; } ?>"><span class="dashicons dashicons-list-view"></span></a>
468
  </div>
469
+ <div class="itsec-module-search">
470
+ <input type="search" placeholder="<?php esc_attr_e( 'Search Modules', 'better-wp-security' ); ?>" id="search" spellcheck="false" autocomplete="off" autofill="off" x-autocomplete="false">
471
+ </div>
472
  <ul class="subsubsub itsec-feature-tabs hide-if-no-js">
473
  <?php echo implode( $feature_tabs, " |</li>\n" ) . "</li>\n"; ?>
474
+ <li class="itsec-module-filter hide-if-js" id="itsec-module-filter-search">| <a><?php esc_html_e( 'Search', 'better-wp-security' ); ?></a> <span class="count"></span></li>
475
  </ul>
476
  </div>
477
  <div class="itsec-module-cards-container <?php echo $view; ?> hide-if-js">
core/{class-itsec-core.php → core.php} RENAMED
@@ -25,7 +25,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
25
  *
26
  * @access private
27
  */
28
- private $plugin_build = 4070;
29
 
30
  /**
31
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
@@ -105,17 +105,18 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
105
  register_uninstall_hook( $this->plugin_file, array( 'ITSEC_Core', 'handle_uninstall' ) );
106
 
107
 
108
- require( $this->plugin_dir . 'core/class-itsec-modules.php' );
109
  add_action( 'itsec-register-modules', array( $this, 'register_modules' ) );
110
  ITSEC_Modules::init_modules();
111
 
112
- require( $this->plugin_dir . 'core/class-itsec-lib.php' );
113
- require( $this->plugin_dir . 'core/class-itsec-logger.php' );
114
- require( $this->plugin_dir . 'core/class-itsec-lockout.php' );
115
- require( $this->plugin_dir . 'core/class-itsec-files.php' );
116
- require( $this->plugin_dir . 'core/class-itsec-notify.php' );
117
- require( $this->plugin_dir . 'core/class-itsec-response.php' );
118
  require( $this->plugin_dir . 'core/lib/class-itsec-lib-user-activity.php' );
 
119
 
120
  $this->itsec_files = ITSEC_Files::get_instance();
121
  $this->itsec_notify = new ITSEC_Notify();
@@ -138,9 +139,13 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
138
 
139
  add_action( 'plugins_loaded', array( $this, 'continue_init' ), -90 );
140
  add_action( 'wp_login_failed', array( 'ITSEC_Lib', 'handle_wp_login_failed' ) );
 
141
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
142
  }
143
 
 
 
 
144
  public function continue_init() {
145
  ITSEC_Modules::run_active_modules();
146
 
@@ -149,24 +154,53 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
149
  add_action( 'admin_bar_menu', array( $this, 'modify_admin_bar' ), 99 );
150
  }
151
 
 
 
 
 
 
 
 
152
  do_action( 'itsec_initialized' );
153
  }
154
 
 
 
 
 
 
155
  public static function get_itsec_files() {
156
  $self = self::get_instance();
157
  return $self->itsec_files;
158
  }
159
 
 
 
 
 
 
160
  public static function get_itsec_notify() {
161
  $self = self::get_instance();
162
  return $self->itsec_notify;
163
  }
164
 
 
 
 
 
 
 
 
165
  public static function get_sync_api() {
166
  $self = self::get_instance();
167
  return $self->sync_api;
168
  }
169
 
 
 
 
 
 
170
  public function register_sync_verbs( $sync_api ) {
171
  // For use by the itsec-get-everything verb as it has to run other verbs to get their details.
172
  $this->sync_api = $sync_api;
@@ -174,6 +208,9 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
174
  $sync_api->register( 'itsec-get-everything', 'Ithemes_Sync_Verb_ITSEC_Get_Everything', dirname( __FILE__ ) . '/sync-verbs/itsec-get-everything.php' );
175
  }
176
 
 
 
 
177
  public function register_modules() {
178
  $path = dirname( __FILE__ );
179
 
@@ -218,13 +255,12 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
218
  *
219
  * @since 4.0
220
  *
221
- * @param object $links Array of WordPress links
222
  * @param string $file String name of current file
223
  *
224
- * @return object Array of WordPress links
225
- *
226
  */
227
- function add_action_link( $links, $file ) {
228
 
229
  static $this_plugin;
230
 
@@ -264,10 +300,12 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
264
  }
265
 
266
  /**
267
- * Add admin bar item
268
  *
269
  * @since 4.0
270
  *
 
 
271
  * @return void
272
  */
273
  public function modify_admin_bar( $wp_admin_bar ) {
@@ -313,28 +351,39 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
313
  );
314
  }
315
 
 
 
 
 
 
316
  public function handle_upgrade( $build = false ) {
317
  $this->doing_data_upgrade = true;
318
 
319
- require_once( self::get_core_dir() . '/class-itsec-setup.php' );
320
  ITSEC_Setup::handle_upgrade( $build );
321
  }
322
 
323
  public static function handle_activation() {
324
- require_once( self::get_core_dir() . '/class-itsec-setup.php' );
325
  ITSEC_Setup::handle_activation();
326
  }
327
 
328
  public static function handle_deactivation() {
329
- require_once( self::get_core_dir() . '/class-itsec-setup.php' );
330
  ITSEC_Setup::handle_deactivation();
331
  }
332
 
333
  public static function handle_uninstall() {
334
- require_once( self::get_core_dir() . '/class-itsec-setup.php' );
335
  ITSEC_Setup::handle_uninstall();
336
  }
337
 
 
 
 
 
 
 
338
  public static function add_notice( $callback, $all_pages = false ) {
339
  global $pagenow, $plugin_page;
340
 
@@ -358,10 +407,20 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
358
  }
359
  }
360
 
 
 
 
 
 
361
  public static function get_required_cap() {
362
  return apply_filters( 'itsec_cap_required', is_multisite() ? 'manage_network_options' : 'manage_options' );
363
  }
364
 
 
 
 
 
 
365
  public static function current_user_can_manage() {
366
  return current_user_can( self::get_required_cap() );
367
  }
@@ -396,20 +455,46 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
396
  return $self->plugin_name;
397
  }
398
 
 
 
 
 
 
 
 
399
  public static function is_pro() {
400
  return is_dir( self::get_plugin_dir() . 'pro' );
401
  }
402
 
 
 
 
 
 
 
 
403
  public static function get_current_time() {
404
  $self = self::get_instance();
405
  return $self->current_time;
406
  }
407
 
 
 
 
 
 
 
 
408
  public static function get_current_time_gmt() {
409
  $self = self::get_instance();
410
  return $self->current_time_gmt;
411
  }
412
 
 
 
 
 
 
413
  public static function get_time_offset() {
414
  $self = self::get_instance();
415
  return $self->current_time - $self->current_time_gmt;
@@ -447,16 +532,31 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
447
  return network_admin_url( 'admin.php?page=itsec&module=' . $module );
448
  }
449
 
 
 
 
 
 
450
  public static function set_interactive( $interactive ) {
451
  $self = self::get_instance();
452
  $self->interactive = (bool) $interactive;
453
  }
454
 
 
 
 
 
 
455
  public static function is_interactive() {
456
  $self = self::get_instance();
457
  return $self->interactive;
458
  }
459
 
 
 
 
 
 
460
  public static function is_iwp_call() {
461
  $self = self::get_instance();
462
 
@@ -485,6 +585,16 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
485
  return $self->is_iwp_call;
486
  }
487
 
 
 
 
 
 
 
 
 
 
 
488
  public static function get_wp_upload_dir() {
489
  $self = self::get_instance();
490
 
@@ -511,6 +621,12 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
511
  return $self->wp_upload_dir;
512
  }
513
 
 
 
 
 
 
 
514
  public static function update_wp_upload_dir( $old_dir, $new_dir ) {
515
  $self = self::get_instance();
516
 
@@ -523,6 +639,13 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
523
  delete_site_transient( 'itsec_wp_upload_dir' );
524
  }
525
 
 
 
 
 
 
 
 
526
  public static function get_storage_dir( $dir = '' ) {
527
  $self = self::get_instance();
528
 
@@ -549,6 +672,10 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
549
  }
550
 
551
  public static function is_ajax_request() {
 
 
 
 
552
  return defined( 'DOING_AJAX' ) && DOING_AJAX;
553
  }
554
 
@@ -556,6 +683,13 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
556
  return defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST;
557
  }
558
 
 
 
 
 
 
 
 
559
  public static function is_rest_api_request() {
560
  if ( isset( $GLOBALS['__itsec_core_is_rest_api_request'] ) ) {
561
  return $GLOBALS['__itsec_core_is_rest_api_request'];
@@ -578,23 +712,65 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
578
 
579
  if ( 0 === strpos( $_SERVER['REQUEST_URI'], $rest_api_path ) ) {
580
  $GLOBALS['__itsec_core_is_rest_api_request'] = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  return true;
582
  }
583
 
584
- $GLOBALS['__itsec_core_is_rest_api_request'] = false;
585
  return false;
586
  }
587
 
588
- public static function is_api_request( $include_ajax = true ) {
 
 
 
 
 
 
 
 
 
589
  if ( $include_ajax && self::is_ajax_request() ) {
590
  return true;
591
  }
592
 
 
 
 
 
593
  if ( self::is_rest_api_request() || self::is_xmlrpc_request() ) {
594
  return true;
595
  }
596
 
597
  return false;
598
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
  }
600
  }
25
  *
26
  * @access private
27
  */
28
+ private $plugin_build = 4072;
29
 
30
  /**
31
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
105
  register_uninstall_hook( $this->plugin_file, array( 'ITSEC_Core', 'handle_uninstall' ) );
106
 
107
 
108
+ require( $this->plugin_dir . 'core/modules.php' );
109
  add_action( 'itsec-register-modules', array( $this, 'register_modules' ) );
110
  ITSEC_Modules::init_modules();
111
 
112
+ require( $this->plugin_dir . 'core/lib.php' );
113
+ require( $this->plugin_dir . 'core/logger.php' );
114
+ require( $this->plugin_dir . 'core/lockout.php' );
115
+ require( $this->plugin_dir . 'core/files.php' );
116
+ require( $this->plugin_dir . 'core/notify.php' );
117
+ require( $this->plugin_dir . 'core/response.php' );
118
  require( $this->plugin_dir . 'core/lib/class-itsec-lib-user-activity.php' );
119
+ require( $this->plugin_dir . 'core/lib/class-itsec-lib-password-requirements.php' );
120
 
121
  $this->itsec_files = ITSEC_Files::get_instance();
122
  $this->itsec_notify = new ITSEC_Notify();
139
 
140
  add_action( 'plugins_loaded', array( $this, 'continue_init' ), -90 );
141
  add_action( 'wp_login_failed', array( 'ITSEC_Lib', 'handle_wp_login_failed' ) );
142
+
143
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
144
  }
145
 
146
+ /**
147
+ * Perform initialization that requires the plugins_loaded hook to be fired.
148
+ */
149
  public function continue_init() {
150
  ITSEC_Modules::run_active_modules();
151
 
154
  add_action( 'admin_bar_menu', array( $this, 'modify_admin_bar' ), 99 );
155
  }
156
 
157
+ $disabled = defined( 'ITSEC_DISABLE_PASSWORD_REQUIREMENTS') && ITSEC_DISABLE_PASSWORD_REQUIREMENTS;
158
+
159
+ if ( ! $disabled && has_action( 'itsec_validate_password' ) ) {
160
+ $pass_requirements = new ITSEC_Lib_Password_Requirements();
161
+ $pass_requirements->run();
162
+ }
163
+
164
  do_action( 'itsec_initialized' );
165
  }
166
 
167
+ /**
168
+ * Retrieve the global instance of the files utility.
169
+ *
170
+ * @return ITSEC_Files
171
+ */
172
  public static function get_itsec_files() {
173
  $self = self::get_instance();
174
  return $self->itsec_files;
175
  }
176
 
177
+ /**
178
+ * Retrieve the global instance of the user notification utility.
179
+ *
180
+ * @return ITSEC_Notify
181
+ */
182
  public static function get_itsec_notify() {
183
  $self = self::get_instance();
184
  return $self->itsec_notify;
185
  }
186
 
187
+ /**
188
+ * Retrieve the global instance of the Sync API.
189
+ *
190
+ * The API is not available until iThemes Sync verbs have been registered ( init#11 ).
191
+ *
192
+ * @return Ithemes_Sync_API|null
193
+ */
194
  public static function get_sync_api() {
195
  $self = self::get_instance();
196
  return $self->sync_api;
197
  }
198
 
199
+ /**
200
+ * Register ITSEC verbs with sync.
201
+ *
202
+ * @param Ithemes_Sync_API $sync_api
203
+ */
204
  public function register_sync_verbs( $sync_api ) {
205
  // For use by the itsec-get-everything verb as it has to run other verbs to get their details.
206
  $this->sync_api = $sync_api;
208
  $sync_api->register( 'itsec-get-everything', 'Ithemes_Sync_Verb_ITSEC_Get_Everything', dirname( __FILE__ ) . '/sync-verbs/itsec-get-everything.php' );
209
  }
210
 
211
+ /**
212
+ * Register core modules.
213
+ */
214
  public function register_modules() {
215
  $path = dirname( __FILE__ );
216
 
255
  *
256
  * @since 4.0
257
  *
258
+ * @param array $links Array of WordPress links
259
  * @param string $file String name of current file
260
  *
261
+ * @return array Array of WordPress links
 
262
  */
263
+ public function add_action_link( $links, $file ) {
264
 
265
  static $this_plugin;
266
 
300
  }
301
 
302
  /**
303
+ * Add admin bar items
304
  *
305
  * @since 4.0
306
  *
307
+ * @param WP_Admin_Bar $wp_admin_bar
308
+ *
309
  * @return void
310
  */
311
  public function modify_admin_bar( $wp_admin_bar ) {
351
  );
352
  }
353
 
354
+ /**
355
+ * Dispatch a request to upgrade the data schema to another version.
356
+ *
357
+ * @param int|bool $build The version of the data storage format. Pass false to default to the current version.
358
+ */
359
  public function handle_upgrade( $build = false ) {
360
  $this->doing_data_upgrade = true;
361
 
362
+ require_once( self::get_core_dir() . '/setup.php' );
363
  ITSEC_Setup::handle_upgrade( $build );
364
  }
365
 
366
  public static function handle_activation() {
367
+ require_once( self::get_core_dir() . '/setup.php' );
368
  ITSEC_Setup::handle_activation();
369
  }
370
 
371
  public static function handle_deactivation() {
372
+ require_once( self::get_core_dir() . '/setup.php' );
373
  ITSEC_Setup::handle_deactivation();
374
  }
375
 
376
  public static function handle_uninstall() {
377
+ require_once( self::get_core_dir() . '/setup.php' );
378
  ITSEC_Setup::handle_uninstall();
379
  }
380
 
381
+ /**
382
+ * Register a notice to be displayed in the WordPress admin.
383
+ *
384
+ * @param callable $callback Function that will render a notice.
385
+ * @param bool $all_pages Display the notice on all pages or only on ITSEC, plugins, and upgrade page.
386
+ */
387
  public static function add_notice( $callback, $all_pages = false ) {
388
  global $pagenow, $plugin_page;
389
 
407
  }
408
  }
409
 
410
+ /**
411
+ * Get the required capability to manage ITSEC.
412
+ *
413
+ * @return string
414
+ */
415
  public static function get_required_cap() {
416
  return apply_filters( 'itsec_cap_required', is_multisite() ? 'manage_network_options' : 'manage_options' );
417
  }
418
 
419
+ /**
420
+ * Does the current user have permission to manage ITSEC.
421
+ *
422
+ * @return bool
423
+ */
424
  public static function current_user_can_manage() {
425
  return current_user_can( self::get_required_cap() );
426
  }
455
  return $self->plugin_name;
456
  }
457
 
458
+ /**
459
+ * Is this an iThemes Security Pro installation.
460
+ *
461
+ * This value is not cached.
462
+ *
463
+ * @return bool
464
+ */
465
  public static function is_pro() {
466
  return is_dir( self::get_plugin_dir() . 'pro' );
467
  }
468
 
469
+ /**
470
+ * Get the current local timestamp.
471
+ *
472
+ * This value will be the same throughout the entire request.
473
+ *
474
+ * @return int
475
+ */
476
  public static function get_current_time() {
477
  $self = self::get_instance();
478
  return $self->current_time;
479
  }
480
 
481
+ /**
482
+ * Get the current UTC timestamp.
483
+ *
484
+ * This value will be the same throughout the entire request.
485
+ *
486
+ * @return int
487
+ */
488
  public static function get_current_time_gmt() {
489
  $self = self::get_instance();
490
  return $self->current_time_gmt;
491
  }
492
 
493
+ /**
494
+ * Get the UTC offset in seconds.
495
+ *
496
+ * @return int
497
+ */
498
  public static function get_time_offset() {
499
  $self = self::get_instance();
500
  return $self->current_time - $self->current_time_gmt;
532
  return network_admin_url( 'admin.php?page=itsec&module=' . $module );
533
  }
534
 
535
+ /**
536
+ * Specify whether there is a user modifying settings or if an API is modifying settings.
537
+ *
538
+ * @param bool $interactive
539
+ */
540
  public static function set_interactive( $interactive ) {
541
  $self = self::get_instance();
542
  $self->interactive = (bool) $interactive;
543
  }
544
 
545
+ /**
546
+ * Is a user modifying settings or the API modifying settings (such as from Sync requests).
547
+ *
548
+ * @return bool
549
+ */
550
  public static function is_interactive() {
551
  $self = self::get_instance();
552
  return $self->interactive;
553
  }
554
 
555
+ /**
556
+ * Determine whether the current request is an Infinite WP API call.
557
+ *
558
+ * @return bool
559
+ */
560
  public static function is_iwp_call() {
561
  $self = self::get_instance();
562
 
585
  return $self->is_iwp_call;
586
  }
587
 
588
+ /**
589
+ * Get the configured WordPress upload directory of the main site.
590
+ *
591
+ * This value is cached for both the lifetime of the request and possibly indefinitely when WordPress is
592
+ * using an object cache.
593
+ *
594
+ * @see wp_upload_dir
595
+ *
596
+ * @return array
597
+ */
598
  public static function get_wp_upload_dir() {
599
  $self = self::get_instance();
600
 
621
  return $self->wp_upload_dir;
622
  }
623
 
624
+ /**
625
+ * Set a new upload directory and ensure the previously cached value is cleared.
626
+ *
627
+ * @param string $old_dir
628
+ * @param string $new_dir
629
+ */
630
  public static function update_wp_upload_dir( $old_dir, $new_dir ) {
631
  $self = self::get_instance();
632
 
639
  delete_site_transient( 'itsec_wp_upload_dir' );
640
  }
641
 
642
+ /**
643
+ * Retrieve and/or create a directory for ITSEC to store data.
644
+ *
645
+ * @param string $dir Optionally specify an additional sub-directory.
646
+ *
647
+ * @return string
648
+ */
649
  public static function get_storage_dir( $dir = '' ) {
650
  $self = self::get_instance();
651
 
672
  }
673
 
674
  public static function is_ajax_request() {
675
+ if ( function_exists( 'wp_doing_ajax' ) ) {
676
+ return wp_doing_ajax();
677
+ }
678
+
679
  return defined( 'DOING_AJAX' ) && DOING_AJAX;
680
  }
681
 
683
  return defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST;
684
  }
685
 
686
+ /**
687
+ * Is this a WordPress REST API request.
688
+ *
689
+ * This function is suitable to be used immediately, not just after REST_REQUEST has been defined.
690
+ *
691
+ * @return bool
692
+ */
693
  public static function is_rest_api_request() {
694
  if ( isset( $GLOBALS['__itsec_core_is_rest_api_request'] ) ) {
695
  return $GLOBALS['__itsec_core_is_rest_api_request'];
712
 
713
  if ( 0 === strpos( $_SERVER['REQUEST_URI'], $rest_api_path ) ) {
714
  $GLOBALS['__itsec_core_is_rest_api_request'] = true;
715
+ } else {
716
+ $GLOBALS['__itsec_core_is_rest_api_request'] = false;
717
+ }
718
+
719
+ return $GLOBALS['__itsec_core_is_rest_api_request'];
720
+ }
721
+
722
+ /**
723
+ * Is this a request to wp-admin/admin-post.php?
724
+ *
725
+ * @return bool
726
+ */
727
+ public static function is_admin_post_php_request() {
728
+ if ( 'wp-admin/admin-post.php' === ITSEC_Lib::get_request_path() ) {
729
  return true;
730
  }
731
 
 
732
  return false;
733
  }
734
 
735
+ /**
736
+ * Is the current request being made by a WordPress API.
737
+ *
738
+ * @param bool $include_ajax Whether to include Ajax requests as a subset of API requests.
739
+ * @param bool $include_admin_post_php Whether to include wp-admin/admin-post.php requests as a subset of API
740
+ * requests.
741
+ *
742
+ * @return bool
743
+ */
744
+ public static function is_api_request( $include_ajax = true, $include_admin_post_php = true ) {
745
  if ( $include_ajax && self::is_ajax_request() ) {
746
  return true;
747
  }
748
 
749
+ if ( $include_admin_post_php && self::is_admin_post_php_request() ) {
750
+ return true;
751
+ }
752
+
753
  if ( self::is_rest_api_request() || self::is_xmlrpc_request() ) {
754
  return true;
755
  }
756
 
757
  return false;
758
  }
759
+
760
+ /**
761
+ * Check to see if the define to disable all active modules is set.
762
+ *
763
+ * Note that the ITSEC_DISABLE_MODULES should only be used to gain access to a site that you are locked out of.
764
+ * Once logged in, you should remove the define to re-enable the protections offered by iThemes Security.
765
+ *
766
+ * @return bool true if the define is set to a truthy value, false otherwise.
767
+ */
768
+ public static function is_temp_disable_modules_set() {
769
+ if ( defined( 'ITSEC_DISABLE_MODULES' ) && ITSEC_DISABLE_MODULES ) {
770
+ return true;
771
+ }
772
+
773
+ return false;
774
+ }
775
  }
776
  }
core/{class-itsec-files.php → files.php} RENAMED
File without changes
core/history.txt CHANGED
@@ -509,3 +509,42 @@
509
  Bug Fix: When a requesting IP address cannot be found, default to 127.0.0.1. This fixes issues with some alternate cron setups.
510
  Bug Fix: Having more than one iThemes Security modification in a .htaccess, nginx.conf, or wp-config.php file will no longer result in having all the file content between each section removed when updating the file.
511
  Bug Fix: Modifications to the wp-config.php file added by W3 Total Cache now have their Windows-style newlines preserved when iThemes Security updates the file.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  Bug Fix: When a requesting IP address cannot be found, default to 127.0.0.1. This fixes issues with some alternate cron setups.
510
  Bug Fix: Having more than one iThemes Security modification in a .htaccess, nginx.conf, or wp-config.php file will no longer result in having all the file content between each section removed when updating the file.
511
  Bug Fix: Modifications to the wp-config.php file added by W3 Total Cache now have their Windows-style newlines preserved when iThemes Security updates the file.
512
+ 3.1.3 - 2017-04-11 - Chris Jean
513
+ Bug Fix: Removed warning: "Non-static method ITSEC_Setup::uninstall() should not be called statically".
514
+ Enhancement: Removed AhrefsBot from the HackRepair blacklist as they are legitimate bot.
515
+ 3.2.1 - 2017-05-25 - Chris Jean & Timothy Jacobs
516
+ New Feature: Added support for iThemes Sync to run the Security Check feature from inside the Sync service.
517
+ Bug Fix: Fixed the ability to manually enter a page number to navigate to on the Security > Logs page.
518
+ Bug Fix: Fixed source of warning that could appear when creating a backup while running a PHP version less than 5.4.
519
+ Bug Fix: Fixed source of notice that could appear when reseting a user's password when the Strong Passwords Enforcement feature is enabled.
520
+ Bug Fix: Fixed bugs that prevented reporting of specific error messages related to updating the wp-config.php file.
521
+ Misc : Updated or added phpDoc to many functions.
522
+ 3.3.0 - 2017-06-21 - Chris Jean & Timothy Jacobs
523
+ Bug Fix: Fixed an infinite loop that could occur when expiring a cookie and Hide Backend is enabled.
524
+ Bug Fix: Fixed compatibility issue with the Jetpack plugin when Hide Backend is enabled which could prevent Jetpack from redirecting users to the wordpress.com login page.
525
+ Bug Fix: Fixed issue where access to wp-admin/admin-post.php when Hide Backend is enabled.
526
+ Enhancement: Improved efficiency of Hide Backend code, increasing site performance when the feature is enabled.
527
+ Enhancement: Enforce strong passwords during log-in. Can be disabled via the ITSEC_DISABLE_PASSWORD_REQUIREMENTS constant.
528
+ Enhancement: Use canonical roles library to determine if a new user or an updated role requires a strong password.
529
+ Enhancement: Introduce password requirements module to centralize handling of password updates.
530
+ Misc: Updated Disable File Locking description.
531
+ 3.4.0 - 2017-07-05 - Chris Jean & Timothy Jacobs
532
+ Important: The way that Hide Backend functions changes in this release. Previously, if your Hide Backend Login Slug was wplogin, going to example.com/wplogin would result in the URL remaining example.com/wplogin. The new implementation of this feature results in a redirect to a URL that looks as follows: example.com/wp-login.php?itsec-hb-token=wplogin. While this may not be desireable for some users, this change was necessary to fix longstanding compatibility issues with other plugins. Once you access the login page using the Login Slug page, a cookie is set with an expiration time of one hour. As long as the cookie remains, you can access example.com/wp-login.php without having to access the Hide Backend Login Slug first. If you wish to confirm that Hide Backend is working properly on your site, opening up a private browsing window is a quick way to test without having to log out and clear cookies.
533
+
534
+ Bug Fix: Fixed issue that could prevent "Register" and "Lost your password?" links from working properly on the login page when Hide Backend is enabled.
535
+ Bug Fix: Fix fatal error when updating a profile.
536
+ Bug Fix: Fix strong passwords not being recognized as strong on the profile page.
537
+ Bug Fix: Fix fatal error when registering a new user without specifying a role ( iThemes Exchange ).
538
+ Bug Fix: Compatability with JetPack SSO and Password Requirements.
539
+ Bug Fix: Ensure viewport meta is defined when loading the password requirements update password form.
540
+ Bug Fix: Hide Backend is now compatible with Jetpack Single Sign On.
541
+ Bug Fix: Hide Backend now hides registration pages on multisite sites.
542
+ Enhancement: The Hide Backend hidden login URL is no longer leaked by password-protected content.
543
+ Enhancement: Allow for searching through modules and settings.
544
+ Enhancement: Link to other module settings pages without forcing the page to refresh.
545
+ Enhancement: Fire an action, "itsec_change_admin_user_id", when the admin user id changes.
546
+ Enhancement: Changed default Hide Backend Register Slug from wp-register.php to wp-signup.php since WordPress switched from using wp-register.php to wp-signup.php for registrations. This will not affect existing sites.
547
+ Enhancement: Hide Backend functions purely in PHP code now rather than relying half on PHP code and half on .htaccess and nginx.conf modifications. This allows Hide Backend to function on web servers and server configurations that it was previously not compatible with.
548
+ New Feature: Added support for the ITSEC_DISABLE_MODULES define.
549
+ 3.4.1 - 2017-07-05 - Chris Jean & Timothy Jacobs
550
+ Bug Fix: Fixed password-protected posts not properly handling the password when Hide Backend is enabled.
core/js/scrollTo.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) 2007-2015 Ariel Flesler - aflesler ○ gmail • com | http://flesler.blogspot.com
3
+ * Licensed under MIT
4
+ * @author Ariel Flesler
5
+ * @version 2.1.3
6
+ */
7
+ ;(function(f){"use strict";"function"===typeof define&&define.amd?define(["jquery"],f):"undefined"!==typeof module&&module.exports?module.exports=f(require("jquery")):f(jQuery)})(function($){"use strict";function n(a){return!a.nodeName||-1!==$.inArray(a.nodeName.toLowerCase(),["iframe","#document","html","body"])}function h(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}var p=$.scrollTo=function(a,d,b){return $(window).scrollTo(a,d,b)};p.defaults={axis:"xy",duration:0,limit:!0};$.fn.scrollTo=function(a,d,b){"object"=== typeof d&&(b=d,d=0);"function"===typeof b&&(b={onAfter:b});"max"===a&&(a=9E9);b=$.extend({},p.defaults,b);d=d||b.duration;var u=b.queue&&1<b.axis.length;u&&(d/=2);b.offset=h(b.offset);b.over=h(b.over);return this.each(function(){function k(a){var k=$.extend({},b,{queue:!0,duration:d,complete:a&&function(){a.call(q,e,b)}});r.animate(f,k)}if(null!==a){var l=n(this),q=l?this.contentWindow||window:this,r=$(q),e=a,f={},t;switch(typeof e){case "number":case "string":if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(e)){e= h(e);break}e=l?$(e):$(e,q);case "object":if(e.length===0)return;if(e.is||e.style)t=(e=$(e)).offset()}var v=$.isFunction(b.offset)&&b.offset(q,e)||b.offset;$.each(b.axis.split(""),function(a,c){var d="x"===c?"Left":"Top",m=d.toLowerCase(),g="scroll"+d,h=r[g](),n=p.max(q,c);t?(f[g]=t[m]+(l?0:h-r.offset()[m]),b.margin&&(f[g]-=parseInt(e.css("margin"+d),10)||0,f[g]-=parseInt(e.css("border"+d+"Width"),10)||0),f[g]+=v[m]||0,b.over[m]&&(f[g]+=e["x"===c?"width":"height"]()*b.over[m])):(d=e[m],f[g]=d.slice&& "%"===d.slice(-1)?parseFloat(d)/100*n:d);b.limit&&/^\d+$/.test(f[g])&&(f[g]=0>=f[g]?0:Math.min(f[g],n));!a&&1<b.axis.length&&(h===f[g]?f={}:u&&(k(b.onAfterFirst),f={}))});k(b.onAfter)}})};p.max=function(a,d){var b="x"===d?"Width":"Height",h="scroll"+b;if(!n(a))return a[h]-$(a)[b.toLowerCase()]();var b="client"+b,k=a.ownerDocument||a.document,l=k.documentElement,k=k.body;return Math.max(l[h],k[h])-Math.min(l[b],k[b])};$.Tween.propHooks.scrollLeft=$.Tween.propHooks.scrollTop={get:function(a){return $(a.elem)[a.prop]()}, set:function(a){var d=this.get(a);if(a.options.interrupt&&a._last&&a._last!==d)return $(a.elem).stop();var b=Math.round(a.now);d!==b&&($(a.elem)[a.prop](b),a._last=this.get(a))}};return p});
core/js/url.js DELETED
@@ -1,93 +0,0 @@
1
- /*! URI.js v1.13.1 http://medialize.github.io/URI.js/ */
2
- /* build contains: IPv6.js, punycode.js, SecondLevelDomains.js, URI.js, URITemplate.js, jquery.URI.js, URI.fragmentQuery.js */
3
- (function(f,g){"object"===typeof exports?module.exports=g():"function"===typeof define&&define.amd?define(g):f.IPv6=g(f)})(this,function(f){var g=f&&f.IPv6;return{best:function(f){f=f.toLowerCase().split(":");var k=f.length,b=8;""===f[0]&&""===f[1]&&""===f[2]?(f.shift(),f.shift()):""===f[0]&&""===f[1]?f.shift():""===f[k-1]&&""===f[k-2]&&f.pop();k=f.length;-1!==f[k-1].indexOf(".")&&(b=7);var g;for(g=0;g<k&&""!==f[g];g++);if(g<b)for(f.splice(g,1,"0000");f.length<b;)f.splice(g,0,"0000");for(g=0;g<b;g++){for(var k=
4
- f[g].split(""),r=0;3>r;r++)if("0"===k[0]&&1<k.length)k.splice(0,1);else break;f[g]=k.join("")}var k=-1,q=r=0,h=-1,w=!1;for(g=0;g<b;g++)w?"0"===f[g]?q+=1:(w=!1,q>r&&(k=h,r=q)):"0"===f[g]&&(w=!0,h=g,q=1);q>r&&(k=h,r=q);1<r&&f.splice(k,r,"");k=f.length;b="";""===f[0]&&(b=":");for(g=0;g<k;g++){b+=f[g];if(g===k-1)break;b+=":"}""===f[k-1]&&(b+=":");return b},noConflict:function(){f.IPv6===this&&(f.IPv6=g);return this}}});
5
- (function(f){function g(a){throw RangeError(z[a]);}function s(a,c){for(var d=a.length;d--;)a[d]=c(a[d]);return a}function k(a,c){return s(a.split(p),c).join(".")}function b(a){for(var c=[],d=0,b=a.length,p,e;d<b;)p=a.charCodeAt(d++),55296<=p&&56319>=p&&d<b?(e=a.charCodeAt(d++),56320==(e&64512)?c.push(((p&1023)<<10)+(e&1023)+65536):(c.push(p),d--)):c.push(p);return c}function u(a){return s(a,function(a){var c="";65535<a&&(a-=65536,c+=D(a>>>10&1023|55296),a=56320|a&1023);return c+=D(a)}).join("")}function r(a,
6
- c){return a+22+75*(26>a)-((0!=c)<<5)}function q(a,c,d){var b=0;a=d?B(a/H):a>>1;for(a+=B(a/c);a>v*y>>1;b+=l)a=B(a/v);return B(b+(v+1)*a/(a+E))}function h(c){var d=[],b=c.length,p,e=0,f=F,z=C,h,m,v,n,k;h=c.lastIndexOf(a);0>h&&(h=0);for(m=0;m<h;++m)128<=c.charCodeAt(m)&&g("not-basic"),d.push(c.charCodeAt(m));for(h=0<h?h+1:0;h<b;){m=e;p=1;for(v=l;;v+=l){h>=b&&g("invalid-input");n=c.charCodeAt(h++);n=10>n-48?n-22:26>n-65?n-65:26>n-97?n-97:l;(n>=l||n>B((t-e)/p))&&g("overflow");e+=n*p;k=v<=z?x:v>=z+y?y:
7
- v-z;if(n<k)break;n=l-k;p>B(t/n)&&g("overflow");p*=n}p=d.length+1;z=q(e-m,p,0==m);B(e/p)>t-f&&g("overflow");f+=B(e/p);e%=p;d.splice(e++,0,f)}return u(d)}function w(c){var d,p,e,f,z,h,m,n,v,k=[],w,s,A;c=b(c);w=c.length;d=F;p=0;z=C;for(h=0;h<w;++h)v=c[h],128>v&&k.push(D(v));for((e=f=k.length)&&k.push(a);e<w;){m=t;for(h=0;h<w;++h)v=c[h],v>=d&&v<m&&(m=v);s=e+1;m-d>B((t-p)/s)&&g("overflow");p+=(m-d)*s;d=m;for(h=0;h<w;++h)if(v=c[h],v<d&&++p>t&&g("overflow"),v==d){n=p;for(m=l;;m+=l){v=m<=z?x:m>=z+y?y:m-z;
8
- if(n<v)break;A=n-v;n=l-v;k.push(D(r(v+A%n,0)));n=B(A/n)}k.push(D(r(n,0)));z=q(p,s,e==f);p=0;++e}++p;++d}return k.join("")}var A="object"==typeof exports&&exports,m="object"==typeof module&&module&&module.exports==A&&module,n="object"==typeof global&&global;if(n.global===n||n.window===n)f=n;var e,t=2147483647,l=36,x=1,y=26,E=38,H=700,C=72,F=128,a="-",c=/^xn--/,d=/[^ -~]/,p=/\x2E|\u3002|\uFF0E|\uFF61/g,z={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)",
9
- "invalid-input":"Invalid input"},v=l-x,B=Math.floor,D=String.fromCharCode,G;e={version:"1.2.3",ucs2:{decode:b,encode:u},decode:h,encode:w,toASCII:function(a){return k(a,function(a){return d.test(a)?"xn--"+w(a):a})},toUnicode:function(a){return k(a,function(a){return c.test(a)?h(a.slice(4).toLowerCase()):a})}};if("function"==typeof define&&"object"==typeof define.amd&&define.amd)define(function(){return e});else if(A&&!A.nodeType)if(m)m.exports=e;else for(G in e)e.hasOwnProperty(G)&&(A[G]=e[G]);else f.punycode=
10
- e})(this);
11
- (function(f,g){"object"===typeof exports?module.exports=g():"function"===typeof define&&define.amd?define(g):f.SecondLevelDomains=g(f)})(this,function(f){var g=f&&f.SecondLevelDomains,s={list:{ac:" com gov mil net org ",ae:" ac co gov mil name net org pro sch ",af:" com edu gov net org ",al:" com edu gov mil net org ",ao:" co ed gv it og pb ",ar:" com edu gob gov int mil net org tur ",at:" ac co gv or ",au:" asn com csiro edu gov id net org ",ba:" co com edu gov mil net org rs unbi unmo unsa untz unze ",bb:" biz co com edu gov info net org store tv ",
12
- bh:" biz cc com edu gov info net org ",bn:" com edu gov net org ",bo:" com edu gob gov int mil net org tv ",br:" adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ",bs:" com edu gov net org ",bz:" du et om ov rg ",ca:" ab bc mb nb nf nl ns nt nu on pe qc sk yk ",ck:" biz co edu gen gov info net org ",
13
- cn:" ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ",co:" com edu gov mil net nom org ",cr:" ac c co ed fi go or sa ",cy:" ac biz com ekloges gov ltd name net org parliament press pro tm ","do":" art com edu gob gov mil net org sld web ",dz:" art asso com edu gov net org pol ",ec:" com edu fin gov info med mil net org pro ",eg:" com edu eun gov mil name net org sci ",er:" com edu gov ind mil net org rochest w ",es:" com edu gob nom org ",
14
- et:" biz com edu gov info name net org ",fj:" ac biz com info mil name net org pro ",fk:" ac co gov net nom org ",fr:" asso com f gouv nom prd presse tm ",gg:" co net org ",gh:" com edu gov mil org ",gn:" ac com gov net org ",gr:" com edu gov mil net org ",gt:" com edu gob ind mil net org ",gu:" com edu gov net org ",hk:" com edu gov idv net org ",id:" ac co go mil net or sch web ",il:" ac co gov idf k12 muni net org ","in":" ac co edu ernet firm gen gov i ind mil net nic org res ",iq:" com edu gov i mil net org ",
15
- ir:" ac co dnssec gov i id net org sch ",it:" edu gov ",je:" co net org ",jo:" com edu gov mil name net org sch ",jp:" ac ad co ed go gr lg ne or ",ke:" ac co go info me mobi ne or sc ",kh:" com edu gov mil net org per ",ki:" biz com de edu gov info mob net org tel ",km:" asso com coop edu gouv k medecin mil nom notaires pharmaciens presse tm veterinaire ",kn:" edu gov net org ",kr:" ac busan chungbuk chungnam co daegu daejeon es gangwon go gwangju gyeongbuk gyeonggi gyeongnam hs incheon jeju jeonbuk jeonnam k kg mil ms ne or pe re sc seoul ulsan ",
16
- kw:" com edu gov net org ",ky:" com edu gov net org ",kz:" com edu gov mil net org ",lb:" com edu gov net org ",lk:" assn com edu gov grp hotel int ltd net ngo org sch soc web ",lr:" com edu gov net org ",lv:" asn com conf edu gov id mil net org ",ly:" com edu gov id med net org plc sch ",ma:" ac co gov m net org press ",mc:" asso tm ",me:" ac co edu gov its net org priv ",mg:" com edu gov mil nom org prd tm ",mk:" com edu gov inf name net org pro ",ml:" com edu gov net org presse ",mn:" edu gov org ",
17
- mo:" com edu gov net org ",mt:" com edu gov net org ",mv:" aero biz com coop edu gov info int mil museum name net org pro ",mw:" ac co com coop edu gov int museum net org ",mx:" com edu gob net org ",my:" com edu gov mil name net org sch ",nf:" arts com firm info net other per rec store web ",ng:" biz com edu gov mil mobi name net org sch ",ni:" ac co com edu gob mil net nom org ",np:" com edu gov mil net org ",nr:" biz com edu gov info net org ",om:" ac biz co com edu gov med mil museum net org pro sch ",
18
- pe:" com edu gob mil net nom org sld ",ph:" com edu gov i mil net ngo org ",pk:" biz com edu fam gob gok gon gop gos gov net org web ",pl:" art bialystok biz com edu gda gdansk gorzow gov info katowice krakow lodz lublin mil net ngo olsztyn org poznan pwr radom slupsk szczecin torun warszawa waw wroc wroclaw zgora ",pr:" ac biz com edu est gov info isla name net org pro prof ",ps:" com edu gov net org plo sec ",pw:" belau co ed go ne or ",ro:" arts com firm info nom nt org rec store tm www ",rs:" ac co edu gov in org ",
19
- sb:" com edu gov net org ",sc:" com edu gov net org ",sh:" co com edu gov net nom org ",sl:" com edu gov net org ",st:" co com consulado edu embaixada gov mil net org principe saotome store ",sv:" com edu gob org red ",sz:" ac co org ",tr:" av bbs bel biz com dr edu gen gov info k12 name net org pol tel tsk tv web ",tt:" aero biz cat co com coop edu gov info int jobs mil mobi museum name net org pro tel travel ",tw:" club com ebiz edu game gov idv mil net org ",mu:" ac co com gov net or org ",mz:" ac co edu gov org ",
20
- na:" co com ",nz:" ac co cri geek gen govt health iwi maori mil net org parliament school ",pa:" abo ac com edu gob ing med net nom org sld ",pt:" com edu gov int net nome org publ ",py:" com edu gov mil net org ",qa:" com edu gov mil net org ",re:" asso com nom ",ru:" ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ",
21
- rw:" ac co com edu gouv gov int mil net ",sa:" com edu gov med net org pub sch ",sd:" com edu gov info med net org tv ",se:" a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ",sg:" com edu gov idn net org per ",sn:" art com edu gouv org perso univ ",sy:" com edu gov mil net news org ",th:" ac co go in mi net or ",tj:" ac biz co com edu go gov info int mil name net nic org test web ",tn:" agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ",
22
- tz:" ac co go ne or ",ua:" biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ",ug:" ac co go ne or org sc ",uk:" ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ",
23
- us:" dni fed isa kids nsn ",uy:" com edu gub mil net org ",ve:" co com edu gob info mil net org web ",vi:" co com k12 net org ",vn:" ac biz com edu gov health info int name net org pro ",ye:" co com gov ltd me net org plc ",yu:" ac co edu gov org ",za:" ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ",zm:" ac co com edu gov net org sch "},has:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return!1;
24
- var g=f.lastIndexOf(".",b-1);if(0>=g||g>=b-1)return!1;var r=s.list[f.slice(b+1)];return r?0<=r.indexOf(" "+f.slice(g+1,b)+" "):!1},is:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1||0<=f.lastIndexOf(".",b-1))return!1;var g=s.list[f.slice(b+1)];return g?0<=g.indexOf(" "+f.slice(0,b)+" "):!1},get:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return null;var g=f.lastIndexOf(".",b-1);if(0>=g||g>=b-1)return null;var r=s.list[f.slice(b+1)];return!r||0>r.indexOf(" "+f.slice(g+
25
- 1,b)+" ")?null:f.slice(g+1)},noConflict:function(){f.SecondLevelDomains===this&&(f.SecondLevelDomains=g);return this}};return s});
26
- (function(f,g){"object"===typeof exports?module.exports=g(require("./punycode"),require("./IPv6"),require("./SecondLevelDomains")):"function"===typeof define&&define.amd?define(["./punycode","./IPv6","./SecondLevelDomains"],g):f.URI=g(f.punycode,f.IPv6,f.SecondLevelDomains,f)})(this,function(f,g,s,k){function b(a,c){if(!(this instanceof b))return new b(a,c);void 0===a&&(a="undefined"!==typeof location?location.href+"":"");this.href(a);return void 0!==c?this.absoluteTo(c):this}function u(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,
27
- "\\$1")}function r(a){return void 0===a?"Undefined":String(Object.prototype.toString.call(a)).slice(8,-1)}function q(a){return"Array"===r(a)}function h(a,c){var d,b;if(q(c)){d=0;for(b=c.length;d<b;d++)if(!h(a,c[d]))return!1;return!0}var e=r(c);d=0;for(b=a.length;d<b;d++)if("RegExp"===e){if("string"===typeof a[d]&&a[d].match(c))return!0}else if(a[d]===c)return!0;return!1}function w(a,c){if(!q(a)||!q(c)||a.length!==c.length)return!1;a.sort();c.sort();for(var d=0,b=a.length;d<b;d++)if(a[d]!==c[d])return!1;
28
- return!0}function A(a){return escape(a)}function m(a){return encodeURIComponent(a).replace(/[!'()*]/g,A).replace(/\*/g,"%2A")}var n=k&&k.URI;b.version="1.13.1";var e=b.prototype,t=Object.prototype.hasOwnProperty;b._parts=function(){return{protocol:null,username:null,password:null,hostname:null,urn:null,port:null,path:null,query:null,fragment:null,duplicateQueryParameters:b.duplicateQueryParameters,escapeQuerySpace:b.escapeQuerySpace}};b.duplicateQueryParameters=!1;b.escapeQuerySpace=!0;b.protocol_expression=
29
- /^[a-z][a-z0-9.+-]*$/i;b.idn_expression=/[^a-z0-9\.-]/i;b.punycode_expression=/(xn--)/i;b.ip4_expression=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;b.ip6_expression=/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
30
- b.find_uri_expression=/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;b.findUri={start:/\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,end:/[\s\r\n]|$/,trim:/[`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u201e\u2018\u2019]+$/};b.defaultPorts={http:"80",https:"443",ftp:"21",gopher:"70",ws:"80",wss:"443"};b.invalid_hostname_characters=
31
- /[^a-zA-Z0-9\.-]/;b.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src"};b.getDomAttribute=function(a){if(a&&a.nodeName){var c=a.nodeName.toLowerCase();return"input"===c&&"image"!==a.type?void 0:b.domAttributes[c]}};b.encode=m;b.decode=decodeURIComponent;b.iso8859=function(){b.encode=escape;b.decode=unescape};b.unicode=function(){b.encode=m;b.decode=decodeURIComponent};b.characters=
32
- {pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@","%21":"!","%24":"$","%26":"&","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}}};b.encodeQuery=
33
- function(a,c){var d=b.encode(a+"");void 0===c&&(c=b.escapeQuerySpace);return c?d.replace(/%20/g,"+"):d};b.decodeQuery=function(a,c){a+="";void 0===c&&(c=b.escapeQuerySpace);try{return b.decode(c?a.replace(/\+/g,"%20"):a)}catch(d){return a}};b.recodePath=function(a){a=(a+"").split("/");for(var c=0,d=a.length;c<d;c++)a[c]=b.encodePathSegment(b.decode(a[c]));return a.join("/")};b.decodePath=function(a){a=(a+"").split("/");for(var c=0,d=a.length;c<d;c++)a[c]=b.decodePathSegment(a[c]);return a.join("/")};
34
- var l={encode:"encode",decode:"decode"},x,y=function(a,c){return function(d){return b[c](d+"").replace(b.characters[a][c].expression,function(d){return b.characters[a][c].map[d]})}};for(x in l)b[x+"PathSegment"]=y("pathname",l[x]);b.encodeReserved=y("reserved","encode");b.parse=function(a,c){var d;c||(c={});d=a.indexOf("#");-1<d&&(c.fragment=a.substring(d+1)||null,a=a.substring(0,d));d=a.indexOf("?");-1<d&&(c.query=a.substring(d+1)||null,a=a.substring(0,d));"//"===a.substring(0,2)?(c.protocol=null,
35
- a=a.substring(2),a=b.parseAuthority(a,c)):(d=a.indexOf(":"),-1<d&&(c.protocol=a.substring(0,d)||null,c.protocol&&!c.protocol.match(b.protocol_expression)?c.protocol=void 0:"file"===c.protocol?a=a.substring(d+3):"//"===a.substring(d+1,d+3)?(a=a.substring(d+3),a=b.parseAuthority(a,c)):(a=a.substring(d+1),c.urn=!0)));c.path=a;return c};b.parseHost=function(a,c){var d=a.indexOf("/"),b;-1===d&&(d=a.length);"["===a.charAt(0)?(b=a.indexOf("]"),c.hostname=a.substring(1,b)||null,c.port=a.substring(b+2,d)||
36
- null,"/"===c.port&&(c.port=null)):a.indexOf(":")!==a.lastIndexOf(":")?(c.hostname=a.substring(0,d)||null,c.port=null):(b=a.substring(0,d).split(":"),c.hostname=b[0]||null,c.port=b[1]||null);c.hostname&&"/"!==a.substring(d).charAt(0)&&(d++,a="/"+a);return a.substring(d)||"/"};b.parseAuthority=function(a,c){a=b.parseUserinfo(a,c);return b.parseHost(a,c)};b.parseUserinfo=function(a,c){var d=a.indexOf("/"),p=-1<d?a.lastIndexOf("@",d):a.indexOf("@");-1<p&&(-1===d||p<d)?(d=a.substring(0,p).split(":"),c.username=
37
- d[0]?b.decode(d[0]):null,d.shift(),c.password=d[0]?b.decode(d.join(":")):null,a=a.substring(p+1)):(c.username=null,c.password=null);return a};b.parseQuery=function(a,c){if(!a)return{};a=a.replace(/&+/g,"&").replace(/^\?*&*|&+$/g,"");if(!a)return{};for(var d={},p=a.split("&"),e=p.length,f,h,m=0;m<e;m++)f=p[m].split("="),h=b.decodeQuery(f.shift(),c),f=f.length?b.decodeQuery(f.join("="),c):null,d[h]?("string"===typeof d[h]&&(d[h]=[d[h]]),d[h].push(f)):d[h]=f;return d};b.build=function(a){var c="";a.protocol&&
38
- (c+=a.protocol+":");a.urn||!c&&!a.hostname||(c+="//");c+=b.buildAuthority(a)||"";"string"===typeof a.path&&("/"!==a.path.charAt(0)&&"string"===typeof a.hostname&&(c+="/"),c+=a.path);"string"===typeof a.query&&a.query&&(c+="?"+a.query);"string"===typeof a.fragment&&a.fragment&&(c+="#"+a.fragment);return c};b.buildHost=function(a){var c="";if(a.hostname)c=b.ip6_expression.test(a.hostname)?c+("["+a.hostname+"]"):c+a.hostname;else return"";a.port&&(c+=":"+a.port);return c};b.buildAuthority=function(a){return b.buildUserinfo(a)+
39
- b.buildHost(a)};b.buildUserinfo=function(a){var c="";a.username&&(c+=b.encode(a.username),a.password&&(c+=":"+b.encode(a.password)),c+="@");return c};b.buildQuery=function(a,c,d){var p="",f,e,h,m;for(e in a)if(t.call(a,e)&&e)if(q(a[e]))for(f={},h=0,m=a[e].length;h<m;h++)void 0!==a[e][h]&&void 0===f[a[e][h]+""]&&(p+="&"+b.buildQueryParameter(e,a[e][h],d),!0!==c&&(f[a[e][h]+""]=!0));else void 0!==a[e]&&(p+="&"+b.buildQueryParameter(e,a[e],d));return p.substring(1)};b.buildQueryParameter=function(a,
40
- c,d){return b.encodeQuery(a,d)+(null!==c?"="+b.encodeQuery(c,d):"")};b.addQuery=function(a,c,d){if("object"===typeof c)for(var p in c)t.call(c,p)&&b.addQuery(a,p,c[p]);else if("string"===typeof c)void 0===a[c]?a[c]=d:("string"===typeof a[c]&&(a[c]=[a[c]]),q(d)||(d=[d]),a[c]=a[c].concat(d));else throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");};b.removeQuery=function(a,c,d){var p;if(q(c))for(d=0,p=c.length;d<p;d++)a[c[d]]=void 0;else if("object"===typeof c)for(p in c)t.call(c,
41
- p)&&b.removeQuery(a,p,c[p]);else if("string"===typeof c)if(void 0!==d)if(a[c]===d)a[c]=void 0;else{if(q(a[c])){p=a[c];var e={},f,h;if(q(d))for(f=0,h=d.length;f<h;f++)e[d[f]]=!0;else e[d]=!0;f=0;for(h=p.length;f<h;f++)void 0!==e[p[f]]&&(p.splice(f,1),h--,f--);a[c]=p}}else a[c]=void 0;else throw new TypeError("URI.addQuery() accepts an object, string as the first parameter");};b.hasQuery=function(a,c,d,e){if("object"===typeof c){for(var f in c)if(t.call(c,f)&&!b.hasQuery(a,f,c[f]))return!1;return!0}if("string"!==
42
- typeof c)throw new TypeError("URI.hasQuery() accepts an object, string as the name parameter");switch(r(d)){case "Undefined":return c in a;case "Boolean":return a=Boolean(q(a[c])?a[c].length:a[c]),d===a;case "Function":return!!d(a[c],c,a);case "Array":return q(a[c])?(e?h:w)(a[c],d):!1;case "RegExp":return q(a[c])?e?h(a[c],d):!1:Boolean(a[c]&&a[c].match(d));case "Number":d=String(d);case "String":return q(a[c])?e?h(a[c],d):!1:a[c]===d;default:throw new TypeError("URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter");
43
- }};b.commonPath=function(a,c){var d=Math.min(a.length,c.length),b;for(b=0;b<d;b++)if(a.charAt(b)!==c.charAt(b)){b--;break}if(1>b)return a.charAt(0)===c.charAt(0)&&"/"===a.charAt(0)?"/":"";if("/"!==a.charAt(b)||"/"!==c.charAt(b))b=a.substring(0,b).lastIndexOf("/");return a.substring(0,b+1)};b.withinString=function(a,c,d){d||(d={});var e=d.start||b.findUri.start,f=d.end||b.findUri.end,h=d.trim||b.findUri.trim,m=/[a-z0-9-]=["']?$/i;for(e.lastIndex=0;;){var g=e.exec(a);if(!g)break;g=g.index;if(d.ignoreHtml){var n=
44
- a.slice(Math.max(g-3,0),g);if(n&&m.test(n))continue}var n=g+a.slice(g).search(f),l=a.slice(g,n).replace(h,"");d.ignore&&d.ignore.test(l)||(n=g+l.length,l=c(l,g,n,a),a=a.slice(0,g)+l+a.slice(n),e.lastIndex=g+l.length)}e.lastIndex=0;return a};b.ensureValidHostname=function(a){if(a.match(b.invalid_hostname_characters)){if(!f)throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-] and Punycode.js is not available');if(f.toASCII(a).match(b.invalid_hostname_characters))throw new TypeError('Hostname "'+
45
- a+'" contains characters other than [A-Z0-9.-]');}};b.noConflict=function(a){if(a)return a={URI:this.noConflict()},k.URITemplate&&"function"===typeof k.URITemplate.noConflict&&(a.URITemplate=k.URITemplate.noConflict()),k.IPv6&&"function"===typeof k.IPv6.noConflict&&(a.IPv6=k.IPv6.noConflict()),k.SecondLevelDomains&&"function"===typeof k.SecondLevelDomains.noConflict&&(a.SecondLevelDomains=k.SecondLevelDomains.noConflict()),a;k.URI===this&&(k.URI=n);return this};e.build=function(a){if(!0===a)this._deferred_build=
46
- !0;else if(void 0===a||this._deferred_build)this._string=b.build(this._parts),this._deferred_build=!1;return this};e.clone=function(){return new b(this)};e.valueOf=e.toString=function(){return this.build(!1)._string};l={protocol:"protocol",username:"username",password:"password",hostname:"hostname",port:"port"};y=function(a){return function(c,d){if(void 0===c)return this._parts[a]||"";this._parts[a]=c||null;this.build(!d);return this}};for(x in l)e[x]=y(l[x]);l={query:"?",fragment:"#"};y=function(a,
47
- c){return function(d,b){if(void 0===d)return this._parts[a]||"";null!==d&&(d+="",d.charAt(0)===c&&(d=d.substring(1)));this._parts[a]=d;this.build(!b);return this}};for(x in l)e[x]=y(x,l[x]);l={search:["?","query"],hash:["#","fragment"]};y=function(a,c){return function(d,b){var e=this[a](d,b);return"string"===typeof e&&e.length?c+e:e}};for(x in l)e[x]=y(l[x][1],l[x][0]);e.pathname=function(a,c){if(void 0===a||!0===a){var d=this._parts.path||(this._parts.hostname?"/":"");return a?b.decodePath(d):d}this._parts.path=
48
- a?b.recodePath(a):"/";this.build(!c);return this};e.path=e.pathname;e.href=function(a,c){var d;if(void 0===a)return this.toString();this._string="";this._parts=b._parts();var e=a instanceof b,f="object"===typeof a&&(a.hostname||a.path||a.pathname);a.nodeName&&(f=b.getDomAttribute(a),a=a[f]||"",f=!1);!e&&f&&void 0!==a.pathname&&(a=a.toString());if("string"===typeof a)this._parts=b.parse(a,this._parts);else if(e||f)for(d in e=e?a._parts:a,e)t.call(this._parts,d)&&(this._parts[d]=e[d]);else throw new TypeError("invalid input");
49
- this.build(!c);return this};e.is=function(a){var c=!1,d=!1,e=!1,f=!1,h=!1,m=!1,g=!1,n=!this._parts.urn;this._parts.hostname&&(n=!1,d=b.ip4_expression.test(this._parts.hostname),e=b.ip6_expression.test(this._parts.hostname),c=d||e,h=(f=!c)&&s&&s.has(this._parts.hostname),m=f&&b.idn_expression.test(this._parts.hostname),g=f&&b.punycode_expression.test(this._parts.hostname));switch(a.toLowerCase()){case "relative":return n;case "absolute":return!n;case "domain":case "name":return f;case "sld":return h;
50
- case "ip":return c;case "ip4":case "ipv4":case "inet4":return d;case "ip6":case "ipv6":case "inet6":return e;case "idn":return m;case "url":return!this._parts.urn;case "urn":return!!this._parts.urn;case "punycode":return g}return null};var E=e.protocol,H=e.port,C=e.hostname;e.protocol=function(a,c){if(void 0!==a&&a&&(a=a.replace(/:(\/\/)?$/,""),!a.match(b.protocol_expression)))throw new TypeError('Protocol "'+a+"\" contains characters other than [A-Z0-9.+-] or doesn't start with [A-Z]");return E.call(this,
51
- a,c)};e.scheme=e.protocol;e.port=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a&&(0===a&&(a=null),a&&(a+="",":"===a.charAt(0)&&(a=a.substring(1)),a.match(/[^0-9]/))))throw new TypeError('Port "'+a+'" contains characters other than [0-9]');return H.call(this,a,c)};e.hostname=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a){var d={};b.parseHost(a,d);a=d.hostname}return C.call(this,a,c)};e.host=function(a,c){if(this._parts.urn)return void 0===a?"":this;
52
- if(void 0===a)return this._parts.hostname?b.buildHost(this._parts):"";b.parseHost(a,this._parts);this.build(!c);return this};e.authority=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?b.buildAuthority(this._parts):"";b.parseAuthority(a,this._parts);this.build(!c);return this};e.userinfo=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){if(!this._parts.username)return"";var d=b.buildUserinfo(this._parts);return d.substring(0,
53
- d.length-1)}"@"!==a[a.length-1]&&(a+="@");b.parseUserinfo(a,this._parts);this.build(!c);return this};e.resource=function(a,c){var d;if(void 0===a)return this.path()+this.search()+this.hash();d=b.parse(a);this._parts.path=d.path;this._parts.query=d.query;this._parts.fragment=d.fragment;this.build(!c);return this};e.subdomain=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.length-this.domain().length-
54
- 1;return this._parts.hostname.substring(0,d)||""}d=this._parts.hostname.length-this.domain().length;d=this._parts.hostname.substring(0,d);d=new RegExp("^"+u(d));a&&"."!==a.charAt(a.length-1)&&(a+=".");a&&b.ensureValidHostname(a);this._parts.hostname=this._parts.hostname.replace(d,a);this.build(!c);return this};e.domain=function(a,c){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(c=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.match(/\./g);
55
- if(d&&2>d.length)return this._parts.hostname;d=this._parts.hostname.length-this.tld(c).length-1;d=this._parts.hostname.lastIndexOf(".",d-1)+1;return this._parts.hostname.substring(d)||""}if(!a)throw new TypeError("cannot set domain empty");b.ensureValidHostname(a);!this._parts.hostname||this.is("IP")?this._parts.hostname=a:(d=new RegExp(u(this.domain())+"$"),this._parts.hostname=this._parts.hostname.replace(d,a));this.build(!c);return this};e.tld=function(a,c){if(this._parts.urn)return void 0===a?
56
- "":this;"boolean"===typeof a&&(c=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.lastIndexOf("."),d=this._parts.hostname.substring(d+1);return!0!==c&&s&&s.list[d.toLowerCase()]?s.get(this._parts.hostname)||d:d}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(s&&s.is(a))d=new RegExp(u(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(d,a);else throw new TypeError('TLD "'+a+'" contains characters other than [A-Z0-9]');else{if(!this._parts.hostname||
57
- this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");d=new RegExp(u(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(d,a)}else throw new TypeError("cannot set TLD empty");this.build(!c);return this};e.directory=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path&&!this._parts.hostname)return"";if("/"===this._parts.path)return"/";var d=this._parts.path.length-this.filename().length-1,d=this._parts.path.substring(0,
58
- d)||(this._parts.hostname?"/":"");return a?b.decodePath(d):d}d=this._parts.path.length-this.filename().length;d=this._parts.path.substring(0,d);d=new RegExp("^"+u(d));this.is("relative")||(a||(a="/"),"/"!==a.charAt(0)&&(a="/"+a));a&&"/"!==a.charAt(a.length-1)&&(a+="/");a=b.recodePath(a);this._parts.path=this._parts.path.replace(d,a);this.build(!c);return this};e.filename=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";
59
- var d=this._parts.path.lastIndexOf("/"),d=this._parts.path.substring(d+1);return a?b.decodePathSegment(d):d}d=!1;"/"===a.charAt(0)&&(a=a.substring(1));a.match(/\.?\//)&&(d=!0);var e=new RegExp(u(this.filename())+"$");a=b.recodePath(a);this._parts.path=this._parts.path.replace(e,a);d?this.normalizePath(c):this.build(!c);return this};e.suffix=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var d=this.filename(),
60
- e=d.lastIndexOf(".");if(-1===e)return"";d=d.substring(e+1);d=/^[a-z0-9%]+$/i.test(d)?d:"";return a?b.decodePathSegment(d):d}"."===a.charAt(0)&&(a=a.substring(1));if(d=this.suffix())e=a?new RegExp(u(d)+"$"):new RegExp(u("."+d)+"$");else{if(!a)return this;this._parts.path+="."+b.recodePath(a)}e&&(a=b.recodePath(a),this._parts.path=this._parts.path.replace(e,a));this.build(!c);return this};e.segment=function(a,c,d){var b=this._parts.urn?":":"/",e=this.path(),f="/"===e.substring(0,1),e=e.split(b);void 0!==
61
- a&&"number"!==typeof a&&(d=c,c=a,a=void 0);if(void 0!==a&&"number"!==typeof a)throw Error('Bad segment "'+a+'", must be 0-based integer');f&&e.shift();0>a&&(a=Math.max(e.length+a,0));if(void 0===c)return void 0===a?e:e[a];if(null===a||void 0===e[a])if(q(c)){e=[];a=0;for(var h=c.length;a<h;a++)if(c[a].length||e.length&&e[e.length-1].length)e.length&&!e[e.length-1].length&&e.pop(),e.push(c[a])}else{if(c||"string"===typeof c)""===e[e.length-1]?e[e.length-1]=c:e.push(c)}else c||"string"===typeof c&&c.length?
62
- e[a]=c:e.splice(a,1);f&&e.unshift("");return this.path(e.join(b),d)};e.segmentCoded=function(a,c,d){var e,f;"number"!==typeof a&&(d=c,c=a,a=void 0);if(void 0===c){a=this.segment(a,c,d);if(q(a))for(e=0,f=a.length;e<f;e++)a[e]=b.decode(a[e]);else a=void 0!==a?b.decode(a):void 0;return a}if(q(c))for(e=0,f=c.length;e<f;e++)c[e]=b.decode(c[e]);else c="string"===typeof c?b.encode(c):c;return this.segment(a,c,d)};var F=e.query;e.query=function(a,c){if(!0===a)return b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);
63
- if("function"===typeof a){var d=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace),e=a.call(this,d);this._parts.query=b.buildQuery(e||d,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);this.build(!c);return this}return void 0!==a&&"string"!==typeof a?(this._parts.query=b.buildQuery(a,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),this.build(!c),this):F.call(this,a,c)};e.setQuery=function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);
64
- if("object"===typeof a)for(var f in a)t.call(a,f)&&(e[f]=a[f]);else if("string"===typeof a)e[a]=void 0!==c?c:null;else throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");this._parts.query=b.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==typeof a&&(d=c);this.build(!d);return this};e.addQuery=function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);b.addQuery(e,a,void 0===c?null:c);this._parts.query=
65
- b.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==typeof a&&(d=c);this.build(!d);return this};e.removeQuery=function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);b.removeQuery(e,a,c);this._parts.query=b.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==typeof a&&(d=c);this.build(!d);return this};e.hasQuery=function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);
66
- return b.hasQuery(e,a,c,d)};e.setSearch=e.setQuery;e.addSearch=e.addQuery;e.removeSearch=e.removeQuery;e.hasSearch=e.hasQuery;e.normalize=function(){return this._parts.urn?this.normalizeProtocol(!1).normalizeQuery(!1).normalizeFragment(!1).build():this.normalizeProtocol(!1).normalizeHostname(!1).normalizePort(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build()};e.normalizeProtocol=function(a){"string"===typeof this._parts.protocol&&(this._parts.protocol=this._parts.protocol.toLowerCase(),
67
- this.build(!a));return this};e.normalizeHostname=function(a){this._parts.hostname&&(this.is("IDN")&&f?this._parts.hostname=f.toASCII(this._parts.hostname):this.is("IPv6")&&g&&(this._parts.hostname=g.best(this._parts.hostname)),this._parts.hostname=this._parts.hostname.toLowerCase(),this.build(!a));return this};e.normalizePort=function(a){"string"===typeof this._parts.protocol&&this._parts.port===b.defaultPorts[this._parts.protocol]&&(this._parts.port=null,this.build(!a));return this};e.normalizePath=
68
- function(a){if(this._parts.urn||!this._parts.path||"/"===this._parts.path)return this;var c,d=this._parts.path,e="",f,h;"/"!==d.charAt(0)&&(c=!0,d="/"+d);d=d.replace(/(\/(\.\/)+)|(\/\.$)/g,"/").replace(/\/{2,}/g,"/");c&&(e=d.substring(1).match(/^(\.\.\/)+/)||"")&&(e=e[0]);for(;;){f=d.indexOf("/..");if(-1===f)break;else if(0===f){d=d.substring(3);continue}h=d.substring(0,f).lastIndexOf("/");-1===h&&(h=f);d=d.substring(0,h)+d.substring(f+3)}c&&this.is("relative")&&(d=e+d.substring(1));d=b.recodePath(d);
69
- this._parts.path=d;this.build(!a);return this};e.normalizePathname=e.normalizePath;e.normalizeQuery=function(a){"string"===typeof this._parts.query&&(this._parts.query.length?this.query(b.parseQuery(this._parts.query,this._parts.escapeQuerySpace)):this._parts.query=null,this.build(!a));return this};e.normalizeFragment=function(a){this._parts.fragment||(this._parts.fragment=null,this.build(!a));return this};e.normalizeSearch=e.normalizeQuery;e.normalizeHash=e.normalizeFragment;e.iso8859=function(){var a=
70
- b.encode,c=b.decode;b.encode=escape;b.decode=decodeURIComponent;this.normalize();b.encode=a;b.decode=c;return this};e.unicode=function(){var a=b.encode,c=b.decode;b.encode=m;b.decode=unescape;this.normalize();b.encode=a;b.decode=c;return this};e.readable=function(){var a=this.clone();a.username("").password("").normalize();var c="";a._parts.protocol&&(c+=a._parts.protocol+"://");a._parts.hostname&&(a.is("punycode")&&f?(c+=f.toUnicode(a._parts.hostname),a._parts.port&&(c+=":"+a._parts.port)):c+=a.host());
71
- a._parts.hostname&&a._parts.path&&"/"!==a._parts.path.charAt(0)&&(c+="/");c+=a.path(!0);if(a._parts.query){for(var d="",e=0,h=a._parts.query.split("&"),m=h.length;e<m;e++){var g=(h[e]||"").split("="),d=d+("&"+b.decodeQuery(g[0],this._parts.escapeQuerySpace).replace(/&/g,"%26"));void 0!==g[1]&&(d+="="+b.decodeQuery(g[1],this._parts.escapeQuerySpace).replace(/&/g,"%26"))}c+="?"+d.substring(1)}return c+=b.decodeQuery(a.hash(),!0)};e.absoluteTo=function(a){var c=this.clone(),d=["protocol","username",
72
- "password","hostname","port"],e,f;if(this._parts.urn)throw Error("URNs do not have any generally defined hierarchical components");a instanceof b||(a=new b(a));c._parts.protocol||(c._parts.protocol=a._parts.protocol);if(this._parts.hostname)return c;for(e=0;f=d[e];e++)c._parts[f]=a._parts[f];c._parts.path?".."===c._parts.path.substring(-2)&&(c._parts.path+="/"):(c._parts.path=a._parts.path,c._parts.query||(c._parts.query=a._parts.query));"/"!==c.path().charAt(0)&&(a=a.directory(),c._parts.path=(a?
73
- a+"/":"")+c._parts.path,c.normalizePath());c.build();return c};e.relativeTo=function(a){var c=this.clone().normalize(),d,e,f,h;if(c._parts.urn)throw Error("URNs do not have any generally defined hierarchical components");a=(new b(a)).normalize();d=c._parts;e=a._parts;f=c.path();h=a.path();if("/"!==f.charAt(0))throw Error("URI is already relative");if("/"!==h.charAt(0))throw Error("Cannot calculate a URI relative to another relative URI");d.protocol===e.protocol&&(d.protocol=null);if(d.username===
74
- e.username&&d.password===e.password&&null===d.protocol&&null===d.username&&null===d.password&&d.hostname===e.hostname&&d.port===e.port)d.hostname=null,d.port=null;else return c.build();if(f===h)return d.path="",c.build();a=b.commonPath(c.path(),a.path());if(!a)return c.build();e=e.path.substring(a.length).replace(/[^\/]*$/,"").replace(/.*?\//g,"../");d.path=e+d.path.substring(a.length);return c.build()};e.equals=function(a){var c=this.clone();a=new b(a);var d={},e={},f={},h;c.normalize();a.normalize();
75
- if(c.toString()===a.toString())return!0;d=c.query();e=a.query();c.query("");a.query("");if(c.toString()!==a.toString()||d.length!==e.length)return!1;d=b.parseQuery(d,this._parts.escapeQuerySpace);e=b.parseQuery(e,this._parts.escapeQuerySpace);for(h in d)if(t.call(d,h)){if(!q(d[h])){if(d[h]!==e[h])return!1}else if(!w(d[h],e[h]))return!1;f[h]=!0}for(h in e)if(t.call(e,h)&&!f[h])return!1;return!0};e.duplicateQueryParameters=function(a){this._parts.duplicateQueryParameters=!!a;return this};e.escapeQuerySpace=
76
- function(a){this._parts.escapeQuerySpace=!!a;return this};return b});
77
- (function(f,g){"object"===typeof exports?module.exports=g(require("./URI")):"function"===typeof define&&define.amd?define(["./URI"],g):f.URITemplate=g(f.URI,f)})(this,function(f,g){function s(b){if(s._cache[b])return s._cache[b];if(!(this instanceof s))return new s(b);this.expression=b;s._cache[b]=this;return this}function k(b){this.data=b;this.cache={}}var b=g&&g.URITemplate,u=Object.prototype.hasOwnProperty,r=s.prototype,q={"":{prefix:"",separator:",",named:!1,empty_name_separator:!1,encode:"encode"},
78
- "+":{prefix:"",separator:",",named:!1,empty_name_separator:!1,encode:"encodeReserved"},"#":{prefix:"#",separator:",",named:!1,empty_name_separator:!1,encode:"encodeReserved"},".":{prefix:".",separator:".",named:!1,empty_name_separator:!1,encode:"encode"},"/":{prefix:"/",separator:"/",named:!1,empty_name_separator:!1,encode:"encode"},";":{prefix:";",separator:";",named:!0,empty_name_separator:!1,encode:"encode"},"?":{prefix:"?",separator:"&",named:!0,empty_name_separator:!0,encode:"encode"},"&":{prefix:"&",
79
- separator:"&",named:!0,empty_name_separator:!0,encode:"encode"}};s._cache={};s.EXPRESSION_PATTERN=/\{([^a-zA-Z0-9%_]?)([^\}]+)(\}|$)/g;s.VARIABLE_PATTERN=/^([^*:]+)((\*)|:(\d+))?$/;s.VARIABLE_NAME_PATTERN=/[^a-zA-Z0-9%_]/;s.expand=function(b,f){var g=q[b.operator],m=g.named?"Named":"Unnamed",n=b.variables,e=[],t,l,k;for(k=0;l=n[k];k++)t=f.get(l.name),t.val.length?e.push(s["expand"+m](t,g,l.explode,l.explode&&g.separator||",",l.maxlength,l.name)):t.type&&e.push("");return e.length?g.prefix+e.join(g.separator):
80
- ""};s.expandNamed=function(b,g,k,m,n,e){var t="",l=g.encode;g=g.empty_name_separator;var s=!b[l].length,q=2===b.type?"":f[l](e),r,u,C;u=0;for(C=b.val.length;u<C;u++)n?(r=f[l](b.val[u][1].substring(0,n)),2===b.type&&(q=f[l](b.val[u][0].substring(0,n)))):s?(r=f[l](b.val[u][1]),2===b.type?(q=f[l](b.val[u][0]),b[l].push([q,r])):b[l].push([void 0,r])):(r=b[l][u][1],2===b.type&&(q=b[l][u][0])),t&&(t+=m),k?t+=q+(g||r?"=":"")+r:(u||(t+=f[l](e)+(g||r?"=":"")),2===b.type&&(t+=q+","),t+=r);return t};s.expandUnnamed=
81
- function(b,g,k,m,n){var e="",t=g.encode;g=g.empty_name_separator;var l=!b[t].length,s,q,r,u;r=0;for(u=b.val.length;r<u;r++)n?q=f[t](b.val[r][1].substring(0,n)):l?(q=f[t](b.val[r][1]),b[t].push([2===b.type?f[t](b.val[r][0]):void 0,q])):q=b[t][r][1],e&&(e+=m),2===b.type&&(s=n?f[t](b.val[r][0].substring(0,n)):b[t][r][0],e+=s,e=k?e+(g||q?"=":""):e+","),e+=q;return e};s.noConflict=function(){g.URITemplate===s&&(g.URITemplate=b);return s};r.expand=function(b){var f="";this.parts&&this.parts.length||this.parse();
82
- b instanceof k||(b=new k(b));for(var g=0,m=this.parts.length;g<m;g++)f+="string"===typeof this.parts[g]?this.parts[g]:s.expand(this.parts[g],b);return f};r.parse=function(){var b=this.expression,f=s.EXPRESSION_PATTERN,g=s.VARIABLE_PATTERN,m=s.VARIABLE_NAME_PATTERN,n=[],e=0,k,l,r;for(f.lastIndex=0;;){l=f.exec(b);if(null===l){n.push(b.substring(e));break}else n.push(b.substring(e,l.index)),e=l.index+l[0].length;if(!q[l[1]])throw Error('Unknown Operator "'+l[1]+'" in "'+l[0]+'"');if(!l[3])throw Error('Unclosed Expression "'+
83
- l[0]+'"');k=l[2].split(",");for(var u=0,E=k.length;u<E;u++){r=k[u].match(g);if(null===r)throw Error('Invalid Variable "'+k[u]+'" in "'+l[0]+'"');if(r[1].match(m))throw Error('Invalid Variable Name "'+r[1]+'" in "'+l[0]+'"');k[u]={name:r[1],explode:!!r[3],maxlength:r[4]&&parseInt(r[4],10)}}if(!k.length)throw Error('Expression Missing Variable(s) "'+l[0]+'"');n.push({expression:l[0],operator:l[1],variables:k})}n.length||n.push(b);this.parts=n;return this};k.prototype.get=function(b){var f=this.data,
84
- g={type:0,val:[],encode:[],encodeReserved:[]},m;if(void 0!==this.cache[b])return this.cache[b];this.cache[b]=g;f="[object Function]"===String(Object.prototype.toString.call(f))?f(b):"[object Function]"===String(Object.prototype.toString.call(f[b]))?f[b](b):f[b];if(void 0!==f&&null!==f)if("[object Array]"===String(Object.prototype.toString.call(f))){m=0;for(b=f.length;m<b;m++)void 0!==f[m]&&null!==f[m]&&g.val.push([void 0,String(f[m])]);g.val.length&&(g.type=3)}else if("[object Object]"===String(Object.prototype.toString.call(f))){for(m in f)u.call(f,
85
- m)&&void 0!==f[m]&&null!==f[m]&&g.val.push([m,String(f[m])]);g.val.length&&(g.type=2)}else g.type=1,g.val.push([void 0,String(f)]);return g};f.expand=function(b,g){var k=(new s(b)).expand(g);return new f(k)};return s});
86
- (function(f,g){"object"===typeof exports?module.exports=g(require("jquery","./URI")):"function"===typeof define&&define.amd?define(["jquery","./URI"],g):g(f.jQuery,f.URI)})(this,function(f,g){function s(b){return b.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function k(b){var f=b.nodeName.toLowerCase();return"input"===f&&"image"!==b.type?void 0:g.domAttributes[f]}function b(b){return{get:function(g){return f(g).uri()[b]()},set:function(g,e){f(g).uri()[b](e);return e}}}function u(b,g){var e,h,l;if(!k(b)||
87
- !g)return!1;e=g.match(A);if(!e||!e[5]&&":"!==e[2]&&!q[e[2]])return!1;l=f(b).uri();if(e[5])return l.is(e[5]);if(":"===e[2])return h=e[1].toLowerCase()+":",q[h]?q[h](l,e[4]):!1;h=e[1].toLowerCase();return r[h]?q[e[2]](l[h](),e[4],h):!1}var r={},q={"=":function(b,f){return b===f},"^=":function(b,f){return!!(b+"").match(new RegExp("^"+s(f),"i"))},"$=":function(b,f){return!!(b+"").match(new RegExp(s(f)+"$","i"))},"*=":function(b,f,e){"directory"===e&&(b+="/");return!!(b+"").match(new RegExp(s(f),"i"))},
88
- "equals:":function(b,f){return b.equals(f)},"is:":function(b,f){return b.is(f)}};f.each("authority directory domain filename fragment hash host hostname href password path pathname port protocol query resource scheme search subdomain suffix tld username".split(" "),function(g,h){r[h]=!0;f.attrHooks["uri:"+h]=b(h)});var h=function(b,g){return f(b).uri().href(g).toString()};f.each(["src","href","action","uri","cite"],function(b,g){f.attrHooks[g]={set:h}});f.attrHooks.uri.get=function(b){return f(b).uri()};
89
- f.fn.uri=function(b){var f=this.first(),e=f.get(0),h=k(e);if(!h)throw Error('Element "'+e.nodeName+'" does not have either property: href, src, action, cite');if(void 0!==b){var l=f.data("uri");if(l)return l.href(b);b instanceof g||(b=g(b||""))}else{if(b=f.data("uri"))return b;b=g(f.attr(h)||"")}b._dom_element=e;b._dom_attribute=h;b.normalize();f.data("uri",b);return b};g.prototype.build=function(b){if(this._dom_element)this._string=g.build(this._parts),this._deferred_build=!1,this._dom_element.setAttribute(this._dom_attribute,
90
- this._string),this._dom_element[this._dom_attribute]=this._string;else if(!0===b)this._deferred_build=!0;else if(void 0===b||this._deferred_build)this._string=g.build(this._parts),this._deferred_build=!1;return this};var w,A=/^([a-zA-Z]+)\s*([\^\$*]?=|:)\s*(['"]?)(.+)\3|^\s*([a-zA-Z0-9]+)\s*$/;w=f.expr.createPseudo?f.expr.createPseudo(function(b){return function(f){return u(f,b)}}):function(b,f,e){return u(b,e[3])};f.expr[":"].uri=w;return f});
91
- (function(f,g){"object"===typeof exports?module.exports=g(require("./URI")):"function"===typeof define&&define.amd?define(["./URI"],g):g(f.URI)})(this,function(f){var g=f.prototype,s=g.fragment;f.fragmentPrefix="?";var k=f._parts;f._parts=function(){var b=k();b.fragmentPrefix=f.fragmentPrefix;return b};g.fragmentPrefix=function(b){this._parts.fragmentPrefix=b;return this};g.fragment=function(b,g){var k=this._parts.fragmentPrefix,q=this._parts.fragment||"";return!0===b?q.substring(0,k.length)!==k?
92
- {}:f.parseQuery(q.substring(k.length)):void 0!==b&&"string"!==typeof b?(this._parts.fragment=k+f.buildQuery(b),this.build(!g),this):s.call(this,b,g)};g.addFragment=function(b,g,k){var q=this._parts.fragmentPrefix,h=f.parseQuery((this._parts.fragment||"").substring(q.length));f.addQuery(h,b,g);this._parts.fragment=q+f.buildQuery(h);"string"!==typeof b&&(k=g);this.build(!k);return this};g.removeFragment=function(b,g,k){var q=this._parts.fragmentPrefix,h=f.parseQuery((this._parts.fragment||"").substring(q.length));
93
- f.removeQuery(h,b,g);this._parts.fragment=q+f.buildQuery(h);"string"!==typeof b&&(k=g);this.build(!k);return this};g.addHash=g.addFragment;g.removeHash=g.removeFragment;return f});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/{class-itsec-lib.php → lib.php} RENAMED
@@ -260,29 +260,26 @@ final class ITSEC_Lib {
260
  /**
261
  * Returns the root of the WordPress install.
262
  *
263
- * Get's the URI path to the WordPress installation.
264
  *
265
  * @since 4.0.6
266
  *
267
  * @return string the root folder
268
  */
269
  public static function get_home_root() {
 
 
 
270
 
271
- //homeroot from wp_rewrite
272
- $home_root = parse_url( site_url() );
273
-
274
- if ( isset( $home_root['path'] ) ) {
275
-
276
- $home_root = trailingslashit( $home_root['path'] );
277
 
 
 
278
  } else {
279
-
280
- $home_root = '/';
281
-
282
  }
283
 
284
- return $home_root;
285
-
286
  }
287
 
288
  /**
@@ -496,13 +493,15 @@ final class ITSEC_Lib {
496
  /**
497
  * Determines whether a given IP address is whiteliste
498
  *
499
- * @param string $ip_to_check ip to check (can be in CIDR notation)
500
  * @param array $whitelisted_ips ip list to compare to if not yet saved to options
501
  * @param boolean $current whether to whitelist the current ip or not (due to saving, etc)
502
  *
503
  * @return boolean true if whitelisted or false
504
  */
505
  public static function is_ip_whitelisted( $ip, $whitelisted_ips = null, $current = false ) {
 
 
506
  global $itsec_lockout;
507
 
508
  $ip = sanitize_text_field( $ip );
@@ -869,6 +868,14 @@ final class ITSEC_Lib {
869
  echo "<div class=\"error inline\"><p><strong>$message</strong></p></div>\n";
870
  }
871
 
 
 
 
 
 
 
 
 
872
  public static function get_user( $user = false ) {
873
  if ( $user instanceof WP_User ) {
874
  return $user;
@@ -880,6 +887,8 @@ final class ITSEC_Lib {
880
  $user = get_user_by( 'id', $user );
881
  } else if ( is_string( $user ) ) {
882
  $user = get_user_by( 'login', $user );
 
 
883
  } else {
884
  if ( is_object( $user ) ) {
885
  $type = 'object(' . get_class( $user ) . ')';
@@ -899,6 +908,14 @@ final class ITSEC_Lib {
899
  return false;
900
  }
901
 
 
 
 
 
 
 
 
 
902
  public static function get_password_strength_results( $password, $penalty_strings = array() ) {
903
  if ( ! isset( $GLOBALS['itsec_zxcvbn'] ) ) {
904
  require_once( ITSEC_Core::get_core_dir() . '/lib/itsec-zxcvbn-php/zxcvbn.php' );
@@ -908,6 +925,13 @@ final class ITSEC_Lib {
908
  return $GLOBALS['itsec_zxcvbn']->test_password( $password, $penalty_strings );
909
  }
910
 
 
 
 
 
 
 
 
911
  public static function get_trace_ip_link( $ip = false ) {
912
  if ( empty( $ip ) ) {
913
  return 'http://www.traceip.net/';
@@ -916,6 +940,11 @@ final class ITSEC_Lib {
916
  }
917
  }
918
 
 
 
 
 
 
919
  public static function handle_wp_login_failed( $username ) {
920
  $authentication_types = array();
921
 
@@ -957,4 +986,43 @@ final class ITSEC_Lib {
957
 
958
  do_action( 'itsec-handle-failed-login', $username, $details );
959
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960
  }
260
  /**
261
  * Returns the root of the WordPress install.
262
  *
263
+ * Gets the URI path to the WordPress installation.
264
  *
265
  * @since 4.0.6
266
  *
267
  * @return string the root folder
268
  */
269
  public static function get_home_root() {
270
+ if ( isset( $GLOBALS['__itsec_lib_get_home_root'] ) ) {
271
+ return $GLOBALS['__itsec_lib_get_home_root'];
272
+ }
273
 
274
+ $url_parts = parse_url( site_url() );
 
 
 
 
 
275
 
276
+ if ( isset( $url_parts['path'] ) ) {
277
+ $GLOBALS['__itsec_lib_get_home_root'] = trailingslashit( $url_parts['path'] );
278
  } else {
279
+ $GLOBALS['__itsec_lib_get_home_root'] = '/';
 
 
280
  }
281
 
282
+ return $GLOBALS['__itsec_lib_get_home_root'];
 
283
  }
284
 
285
  /**
493
  /**
494
  * Determines whether a given IP address is whiteliste
495
  *
496
+ * @param string $ip ip to check (can be in CIDR notation)
497
  * @param array $whitelisted_ips ip list to compare to if not yet saved to options
498
  * @param boolean $current whether to whitelist the current ip or not (due to saving, etc)
499
  *
500
  * @return boolean true if whitelisted or false
501
  */
502
  public static function is_ip_whitelisted( $ip, $whitelisted_ips = null, $current = false ) {
503
+
504
+ /** @var ITSEC_Lockout $itsec_lockout */
505
  global $itsec_lockout;
506
 
507
  $ip = sanitize_text_field( $ip );
868
  echo "<div class=\"error inline\"><p><strong>$message</strong></p></div>\n";
869
  }
870
 
871
+ /**
872
+ * Get a WordPress user object.
873
+ *
874
+ * @param int|string|WP_User|bool $user Either the user ID ( must be an int ), the username, a WP_User object,
875
+ * or false to retrieve the currently logged-in user.
876
+ *
877
+ * @return WP_User|false
878
+ */
879
  public static function get_user( $user = false ) {
880
  if ( $user instanceof WP_User ) {
881
  return $user;
887
  $user = get_user_by( 'id', $user );
888
  } else if ( is_string( $user ) ) {
889
  $user = get_user_by( 'login', $user );
890
+ } else if ( is_object( $user ) && isset( $user->ID ) ) {
891
+ $user = get_user_by( 'id', $user->ID );
892
  } else {
893
  if ( is_object( $user ) ) {
894
  $type = 'object(' . get_class( $user ) . ')';
908
  return false;
909
  }
910
 
911
+ /**
912
+ * Evaluate a password's strength.
913
+ *
914
+ * @param string $password
915
+ * @param array $penalty_strings Additional strings that if found within the password, will decrease the strength.
916
+ *
917
+ * @return ITSEC_Zxcvbn_Results
918
+ */
919
  public static function get_password_strength_results( $password, $penalty_strings = array() ) {
920
  if ( ! isset( $GLOBALS['itsec_zxcvbn'] ) ) {
921
  require_once( ITSEC_Core::get_core_dir() . '/lib/itsec-zxcvbn-php/zxcvbn.php' );
925
  return $GLOBALS['itsec_zxcvbn']->test_password( $password, $penalty_strings );
926
  }
927
 
928
+ /**
929
+ * Retrieve the URL to a website to lookup the location of an IP address.
930
+ *
931
+ * @param string|bool $ip IP address to lookup, or false to return a URL to their home page.
932
+ *
933
+ * @return string
934
+ */
935
  public static function get_trace_ip_link( $ip = false ) {
936
  if ( empty( $ip ) ) {
937
  return 'http://www.traceip.net/';
940
  }
941
  }
942
 
943
+ /**
944
+ * Whenever a login fails, collect details of the attempt, and forward them to modules.
945
+ *
946
+ * @param string $username
947
+ */
948
  public static function handle_wp_login_failed( $username ) {
949
  $authentication_types = array();
950
 
986
 
987
  do_action( 'itsec-handle-failed-login', $username, $details );
988
  }
989
+
990
+ /**
991
+ * Reliably provides the URL path.
992
+ *
993
+ * It optionally takes a prefix that will be stripped from the path, if present. This is useful for use to get site
994
+ * URL paths without the site's subdirectory.
995
+ *
996
+ * Trailing slashes are not preserved.
997
+ *
998
+ * @param string $url The URL to pull the path from.
999
+ * @param string $prefix [optional] A string prefix to be removed from the path.
1000
+ *
1001
+ * @return string The URL path.
1002
+ */
1003
+ public static function get_url_path( $url, $prefix = '' ) {
1004
+ $path = (string) parse_url( $url, PHP_URL_PATH );
1005
+ $path = untrailingslashit( $path );
1006
+
1007
+ if ( ! empty( $prefix ) && 0 === strpos( $path, $prefix ) ) {
1008
+ return substr( $path, strlen( $prefix ) );
1009
+ }
1010
+
1011
+ return '';
1012
+ }
1013
+
1014
+ /**
1015
+ * Returns the current request path without the protocol, domain, site subdirectories, or query args.
1016
+ *
1017
+ * This function returns "wp-login.php" when requesting http://example.com/site-path/wp-login.php?action=register.
1018
+ *
1019
+ * @return string The requested site path.
1020
+ */
1021
+ public static function get_request_path() {
1022
+ if ( ! isset( $GLOBALS['__itsec_lib_get_request_path'] ) ) {
1023
+ $GLOBALS['__itsec_lib_get_request_path'] = self::get_url_path( $_SERVER['REQUEST_URI'], self::get_home_root() );
1024
+ }
1025
+
1026
+ return $GLOBALS['__itsec_lib_get_request_path'];
1027
+ }
1028
  }
core/lib/class-itsec-lib-canonical-roles.php CHANGED
@@ -1,6 +1,15 @@
1
  <?php
2
 
3
  final class ITSEC_Lib_Canonical_Roles {
 
 
 
 
 
 
 
 
 
4
  public static function is_canonical_role_at_least( $min_role, $role ) {
5
  $roles = array(
6
  'super-admin' => 6,
@@ -23,6 +32,14 @@ final class ITSEC_Lib_Canonical_Roles {
23
  return false;
24
  }
25
 
 
 
 
 
 
 
 
 
26
  public static function is_user_at_least( $role, $user = false ) {
27
  $roles = array(
28
  'super-admin' => 6,
@@ -47,6 +64,15 @@ final class ITSEC_Lib_Canonical_Roles {
47
  return false;
48
  }
49
 
 
 
 
 
 
 
 
 
 
50
  public static function get_role_from_caps( $caps ) {
51
  if ( is_string( $caps ) ) {
52
  $caps = array( $caps );
@@ -65,6 +91,13 @@ final class ITSEC_Lib_Canonical_Roles {
65
  return '';
66
  }
67
 
 
 
 
 
 
 
 
68
  public static function get_user_role( $user = false ) {
69
  $user = ITSEC_Lib::get_user( $user );
70
 
@@ -89,6 +122,11 @@ final class ITSEC_Lib_Canonical_Roles {
89
  return '';
90
  }
91
 
 
 
 
 
 
92
  public static function get_unique_capabilities() {
93
  return array(
94
  'administrator' => array(
@@ -165,6 +203,11 @@ final class ITSEC_Lib_Canonical_Roles {
165
  );
166
  }
167
 
 
 
 
 
 
168
  public static function get_capabilities() {
169
  return array(
170
  'administrator' => array(
1
  <?php
2
 
3
  final class ITSEC_Lib_Canonical_Roles {
4
+
5
+ /**
6
+ * Check if a given role is at least as or equally as powerful as a given role.
7
+ *
8
+ * @param string $min_role
9
+ * @param string $role
10
+ *
11
+ * @return bool
12
+ */
13
  public static function is_canonical_role_at_least( $min_role, $role ) {
14
  $roles = array(
15
  'super-admin' => 6,
32
  return false;
33
  }
34
 
35
+ /**
36
+ * Check if a user's role is at least as or equally as powerful as a given role.
37
+ *
38
+ * @param string $role
39
+ * @param int|string|WP_User|false $user
40
+ *
41
+ * @return bool
42
+ */
43
  public static function is_user_at_least( $role, $user = false ) {
44
  $roles = array(
45
  'super-admin' => 6,
64
  return false;
65
  }
66
 
67
+ /**
68
+ * Retrieve the default WordPress role that would be associated with the given capabilities list.
69
+ *
70
+ * This checks the caps list for containing at least one of the unique capabilities from each default role.
71
+ *
72
+ * @param string[] $caps
73
+ *
74
+ * @return int|string
75
+ */
76
  public static function get_role_from_caps( $caps ) {
77
  if ( is_string( $caps ) ) {
78
  $caps = array( $caps );
91
  return '';
92
  }
93
 
94
+ /**
95
+ * Retrieve a user's equivalent default WordPress role from their capabilities.
96
+ *
97
+ * @param bool $user
98
+ *
99
+ * @return int|string
100
+ */
101
  public static function get_user_role( $user = false ) {
102
  $user = ITSEC_Lib::get_user( $user );
103
 
122
  return '';
123
  }
124
 
125
+ /**
126
+ * Get a list of all of the capabilities that are unique to each role.
127
+ *
128
+ * @return array
129
+ */
130
  public static function get_unique_capabilities() {
131
  return array(
132
  'administrator' => array(
203
  );
204
  }
205
 
206
+ /**
207
+ * Get a list of all of the capabilities each default WordPress role has.
208
+ *
209
+ * @return array
210
+ */
211
  public static function get_capabilities() {
212
  return array(
213
  'administrator' => array(
core/lib/class-itsec-lib-password-requirements.php ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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( 'wp_login', array( $this, 'wp_login' ), 12, 2 );
27
+ add_filter( 'wp_login_errors', array( $this, 'token_expired_message' ) );
28
+ add_action( 'login_form_' . self::LOGIN_ACTION, array( $this, 'handle_update_password_form' ), 9 );
29
+ add_action( 'login_form_' . self::LOGIN_ACTION, array( $this, 'display_update_password_form' ) );
30
+ }
31
+
32
+ /**
33
+ * When a user's password is updated, or a new user created, verify that the new password is valid.
34
+ *
35
+ * @param WP_Error $errors
36
+ * @param bool $update
37
+ * @param WP_User|stdClass $user
38
+ */
39
+ public function forward_profile_pass_update( $errors, $update, $user ) {
40
+
41
+ if ( ! isset( $user->user_pass ) ) {
42
+ return;
43
+ }
44
+
45
+ if ( ! $update ) {
46
+ $context = 'admin-user-create';
47
+ } elseif ( isset( $user->ID ) && $user->ID === get_current_user_id() ) {
48
+ $context = 'profile-update';
49
+ } else {
50
+ $context = 'admin-profile-update';
51
+ }
52
+
53
+ $args = array(
54
+ 'error' => $errors,
55
+ 'context' => $context
56
+ );
57
+
58
+ if ( isset( $user->role ) ) {
59
+ $args['role'] = $user->role;
60
+ }
61
+
62
+ self::validate_password( $user, $user->user_pass, $args );
63
+ }
64
+
65
+ /**
66
+ * When a user attempts to reset their password, verify that the new password is valid.
67
+ *
68
+ * @param WP_Error $errors
69
+ * @param WP_User $user
70
+ */
71
+ public function forward_reset_pass( $errors, $user ) {
72
+
73
+ if ( ! isset( $_POST['pass1'] ) ) {
74
+ // The validate_password_reset action fires when first rendering the reset page and when handling the form
75
+ // submissions. Since the pass1 data is missing, this must be the initial page render. So, we don't need to
76
+ // do anything yet.
77
+ return;
78
+ }
79
+
80
+ self::validate_password( $user, $user->user_pass, array(
81
+ 'error' => $errors,
82
+ 'context' => 'reset-password',
83
+ ) );
84
+ }
85
+
86
+ /**
87
+ * Whenever a user object is updated, set when their password was last updated.
88
+ *
89
+ * @param int $user_id
90
+ * @param object $old_user_data
91
+ */
92
+ public function set_password_last_updated( $user_id, $old_user_data ) {
93
+
94
+ $user = get_userdata( $user_id );
95
+
96
+ if ( $user->user_pass === $old_user_data->user_pass ) {
97
+ return;
98
+ }
99
+
100
+ delete_user_meta( $user_id, 'itsec_password_change_required' );
101
+ update_user_meta( $user_id, 'itsec_last_password_change', ITSEC_Core::get_current_time_gmt() );
102
+ }
103
+
104
+ /**
105
+ * Whenever a user logs in, check if their password needs to be changed. If so, mark that the user must change
106
+ * their password.
107
+ *
108
+ * @since 1.8
109
+ *
110
+ * @param string $username the username attempted
111
+ * @param WP_User $user wp_user the user
112
+ *
113
+ * @return void
114
+ */
115
+ public function wp_login( $username, $user = null ) {
116
+
117
+ //Get a valid user or terminate the hook (all we care about is forcing the password change... Let brute force protection handle the rest
118
+ if ( null !== $user ) {
119
+ $current_user = $user;
120
+ } elseif ( is_user_logged_in() ) {
121
+ $current_user = wp_get_current_user();
122
+ } else {
123
+ return;
124
+ }
125
+
126
+ if ( ! self::password_change_required( $current_user ) ) {
127
+ return;
128
+ }
129
+
130
+ $token = $this->set_update_password_key( $current_user );
131
+ $this->destroy_session( $current_user );
132
+
133
+ $this->login_html( $current_user, $token );
134
+ exit;
135
+ }
136
+
137
+ /**
138
+ * Add a message that the update password token has expired and they must login again.
139
+ *
140
+ * @param WP_Error $errors
141
+ *
142
+ * @return WP_Error
143
+ */
144
+ public function token_expired_message( $errors ) {
145
+
146
+ if ( ! empty( $_GET['itsec_update_pass_expired'] ) ) {
147
+ $errors->add(
148
+ 'itsec_update_pass_expired',
149
+ esc_html__( 'Sorry, the update password request has expired. Please log in again.', 'better-wp-security' )
150
+ );
151
+ }
152
+
153
+ return $errors;
154
+ }
155
+
156
+ /**
157
+ * Handle the request to update the user's password.
158
+ */
159
+ public function handle_update_password_form() {
160
+
161
+ if ( empty( $_POST['itsec_update_password'] ) || empty( $_POST['itsec_update_password_user'] ) || empty( $_POST['pass1'] ) ) {
162
+ return;
163
+ }
164
+
165
+ $user = get_userdata( $_POST['itsec_update_password_user'] );
166
+
167
+ if ( ! $user || empty( $_POST['itsec_update_password_token'] ) || ! $this->verify_update_password_key( $user, $_POST['itsec_update_password_token'] ) ) {
168
+
169
+ $url = add_query_arg( 'itsec_update_pass_expired', 1, wp_login_url() );
170
+ wp_safe_redirect( set_url_scheme( $url, 'login_post' ) );
171
+ die();
172
+ }
173
+
174
+ $error = self::validate_password( $user, $_POST['pass1'] );
175
+
176
+ if ( $error->get_error_message() ) {
177
+ $this->error_message = $error->get_error_message();
178
+
179
+ return;
180
+ }
181
+
182
+ $user->user_pass = $_POST['pass1'];
183
+ $error = wp_update_user( $user );
184
+
185
+ if ( is_wp_error( $error ) ) {
186
+ $this->error_message = $error->get_error_message();
187
+
188
+ return;
189
+ }
190
+
191
+ $this->delete_update_password_key( $user );
192
+ wp_set_auth_cookie( $user->ID, ! empty( $_REQUEST['rememberme'] ) );
193
+
194
+ if ( ! empty( $_REQUEST['redirect_to'] ) ) {
195
+ $redirect_to = apply_filters( 'login_redirect', $_REQUEST['redirect_to'], $_REQUEST['redirect_to'], $user );
196
+ wp_safe_redirect( $redirect_to );
197
+ } else {
198
+ wp_safe_redirect( admin_url( 'index.php' ) );
199
+ }
200
+
201
+ exit;
202
+ }
203
+
204
+ /**
205
+ * When the login page is loaded with the 'itsec_update_password' action, maybe display the update password form,
206
+ * or redirect to a standard login page.
207
+ */
208
+ public function display_update_password_form() {
209
+
210
+ $user = null;
211
+ $token = '';
212
+
213
+ if ( is_user_logged_in() ) {
214
+ $user = wp_get_current_user();
215
+ $token = $this->set_update_password_key( $user );
216
+ $this->destroy_session( $user );
217
+ } elseif ( ! empty( $_POST['itsec_update_password_user'] ) ) {
218
+ $user = get_userdata( $_POST['itsec_update_password_user'] );
219
+ $token = empty( $_POST['itsec_update_password_token'] ) ? '' : $_POST['itsec_update_password_token'];
220
+ }
221
+
222
+ if ( ! $user ) {
223
+ wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
224
+ die();
225
+ }
226
+
227
+ if ( ! self::password_change_required( $user ) ) {
228
+ wp_safe_redirect( set_url_scheme( wp_login_url(), 'login_post' ) );
229
+ die();
230
+ }
231
+
232
+ $this->login_html( $user, $token );
233
+ exit;
234
+ }
235
+
236
+ /**
237
+ * Destroy the session for a user.
238
+ *
239
+ * @param WP_User $user
240
+ */
241
+ private function destroy_session( $user ) {
242
+ WP_Session_Tokens::get_instance( $user->ID )->destroy_all();
243
+ wp_clear_auth_cookie();
244
+ }
245
+
246
+ /**
247
+ * Verify that the update password key is valid.
248
+ *
249
+ * @param WP_User $user
250
+ * @param string $key
251
+ *
252
+ * @return bool
253
+ */
254
+ private function verify_update_password_key( $user, $key ) {
255
+ $expected = get_user_meta( $user->ID, self::META_KEY, true );
256
+
257
+ if ( ! $expected || ! is_array( $expected ) ) {
258
+ return false;
259
+ }
260
+
261
+ if ( empty( $expected['expires'] ) || $expected['expires'] < ITSEC_Core::get_current_time_gmt() ) {
262
+ return false;
263
+ }
264
+
265
+ return hash_equals( $expected['key'], $key );
266
+ }
267
+
268
+ /**
269
+ * Set the update password key for a user.
270
+ *
271
+ * @param WP_User $user
272
+ *
273
+ * @return string
274
+ */
275
+ private function set_update_password_key( $user ) {
276
+ $key = $this->generate_update_password_key();
277
+
278
+ update_user_meta( $user->ID, self::META_KEY, array(
279
+ 'key' => $key,
280
+ 'expires' => ITSEC_Core::get_current_time_gmt() + HOUR_IN_SECONDS
281
+ ) );
282
+
283
+ return $key;
284
+ }
285
+
286
+ /**
287
+ * Generate a token to be used to verify intent of updating password.
288
+ *
289
+ * We can't use nonces here because the WordPress Session Tokens won't be initialized yet.
290
+ *
291
+ * @return string
292
+ */
293
+ private function generate_update_password_key() {
294
+ return wp_generate_password( 32, true, false );
295
+ }
296
+
297
+ /**
298
+ * Delete the update password key for a user.
299
+ *
300
+ * @param WP_User $user
301
+ */
302
+ private function delete_update_password_key( $user ) {
303
+ delete_user_meta( $user->ID, self::META_KEY );
304
+ }
305
+
306
+ /**
307
+ * Display an interstitial form during the login process to force a user to update their password.
308
+ *
309
+ * @param WP_User $user
310
+ * @param string $token
311
+ */
312
+ protected function login_html( $user, $token ) {
313
+
314
+ $wp_login_url = set_url_scheme( wp_login_url(), 'login_post' );
315
+ $wp_login_url = add_query_arg( 'action', self::LOGIN_ACTION, $wp_login_url );
316
+
317
+ if ( isset( $_GET['wpe-login'] ) && ! preg_match( '/[&?]wpe-login=/', $wp_login_url ) ) {
318
+ $wp_login_url = add_query_arg( 'wpe-login', $_GET['wpe-login'], $wp_login_url );
319
+ }
320
+
321
+ $interim_login = isset( $_REQUEST['interim-login'] );
322
+ $redirect_to = '';
323
+
324
+ $rememberme = ! empty( $_REQUEST['rememberme'] );
325
+
326
+ wp_enqueue_script( 'user-profile' );
327
+
328
+ // Prevent JetPack from attempting to SSO the update password form.
329
+ add_filter( 'jetpack_sso_allowed_actions', '__return_empty_array' );
330
+
331
+ if ( ! function_exists( 'login_header' ) ) {
332
+ require_once( dirname( __FILE__ ) . '/includes/function.login-header.php' );
333
+ }
334
+
335
+ login_header();
336
+
337
+ $type = self::password_change_required( $user );
338
+ // Modules are responsible for providing escaped reason messages
339
+ $reason = $this->get_message_for_password_change_reason( $type );
340
+ ?>
341
+
342
+ <?php if ( $this->error_message ) : ?>
343
+ <div id="login-error" class="message" style="border-left-color: #dc3232;">
344
+ <?php echo $this->error_message; ?>
345
+ </div>
346
+ <?php else: ?>
347
+ <p class="message"><?php echo $reason; ?></p>
348
+ <?php endif; ?>
349
+
350
+ <form name="resetpassform" id="resetpassform" action="<?php echo esc_url( $wp_login_url ); ?>" method="post"
351
+ autocomplete="off">
352
+
353
+ <div class="user-pass1-wrap">
354
+ <p><label for="pass1"><?php _e( 'New Password', 'better-wp-security' ); ?></label></p>
355
+ </div>
356
+
357
+ <div class="wp-pwd">
358
+ <span class="password-input-wrapper">
359
+ <input type="password" data-reveal="1"
360
+ data-pw="<?php echo esc_attr( wp_generate_password( 16 ) ); ?>" name="pass1" id="pass1"
361
+ class="input" size="20" value="" autocomplete="off" aria-describedby="pass-strength-result"/>
362
+ </span>
363
+ <div id="pass-strength-result" class="hide-if-no-js" aria-live="polite"><?php _e( 'Strength indicator', 'better-wp-security' ); ?></div>
364
+ </div>
365
+
366
+ <p class="user-pass2-wrap">
367
+ <label for="pass2"><?php _e( 'Confirm new password' ) ?></label><br/>
368
+ <input type="password" name="pass2" id="pass2" class="input" size="20" value="" autocomplete="off"/>
369
+ </p>
370
+
371
+ <p class="description indicator-hint"><?php echo wp_get_password_hint(); ?></p>
372
+ <br class="clear"/>
373
+
374
+ <p class="submit">
375
+ <input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large"
376
+ value="<?php esc_attr_e( 'Update Password', 'better-wp-security' ); ?>"/>
377
+ </p>
378
+
379
+ <?php if ( $interim_login ) : ?>
380
+ <input type="hidden" name="interim-login" value="1"/>
381
+ <?php else : ?>
382
+ <input type="hidden" name="redirect_to" value="<?php echo esc_url( $redirect_to ); ?>"/>
383
+ <?php endif; ?>
384
+
385
+ <input type="hidden" name="rememberme" id="rememberme" value="<?php echo esc_attr( $rememberme ); ?>"/>
386
+ <input type="hidden" name="itsec_update_password" value="1">
387
+ <input type="hidden" name="itsec_update_password_token" value="<?php echo esc_attr( $token ); ?>">
388
+ <input type="hidden" name="itsec_update_password_user" value="<?php echo esc_attr( $user->ID ); ?>">
389
+ </form>
390
+
391
+ <p id="backtoblog">
392
+ <a href="<?php echo esc_url( home_url( '/' ) ); ?>" title="<?php esc_attr_e( 'Are you lost?', 'better-wp-security' ); ?>">
393
+ <?php echo esc_html( sprintf( __( '&larr; Back to %s', 'better-wp-security' ), get_bloginfo( 'title', 'display' ) ) ); ?>
394
+ </a>
395
+ </p>
396
+
397
+ </div>
398
+ <?php do_action( 'login_footer' ); ?>
399
+ <div class="clear"></div>
400
+ </body>
401
+ </html>
402
+ <?php
403
+ }
404
+
405
+ /**
406
+ * Get a message indicating to the user why a password change is required.
407
+ *
408
+ * @param string $reason
409
+ *
410
+ * @return string
411
+ */
412
+ protected function get_message_for_password_change_reason( $reason ) {
413
+
414
+ /**
415
+ * Retrieve a human readable description as to why a password change has been required for the current user.
416
+ *
417
+ * Modules MUST HTML escape their reason strings before returning them with this filter.
418
+ *
419
+ * @param string $message
420
+ */
421
+ $message = apply_filters( "itsec_password_change_requirement_description_for_{$reason}", '' );
422
+
423
+ if ( $message ) {
424
+ return $message;
425
+ }
426
+
427
+ return esc_html__( 'A password change is required for your account.', 'better-wp-security' );
428
+ }
429
+
430
+ /**
431
+ * Validate a user's password.
432
+ *
433
+ * @param WP_User|stdClass|int $user
434
+ * @param string $new_password
435
+ * @param array $args
436
+ *
437
+ * @return WP_Error Error object with new errors.
438
+ */
439
+ public static function validate_password( $user, $new_password, $args = array() ) {
440
+
441
+ $args = wp_parse_args( $args, array(
442
+ 'error' => new WP_Error(),
443
+ 'context' => ''
444
+ ) );
445
+
446
+ $error = isset( $args['error'] ) ? $args['error'] : new WP_Error();
447
+
448
+ $user = $user instanceof stdClass ? $user : ITSEC_Lib::get_user( $user );
449
+
450
+ if ( ! $user ) {
451
+ $error->add( 'invalid_user', esc_html__( 'Invalid User', 'better-wp-security' ) );
452
+
453
+ return $error;
454
+ }
455
+
456
+ /**
457
+ * Fires when modules should validate a password according to their rules.
458
+ *
459
+ * @since 3.9.0
460
+ *
461
+ * @param \WP_Error $error
462
+ * @param \WP_User|stdClass $user
463
+ * @param string $new_password
464
+ * @param array $args
465
+ */
466
+ do_action( 'itsec_validate_password', $error, $user, $new_password, $args );
467
+
468
+ return $error;
469
+ }
470
+
471
+ /**
472
+ * Flag that a password change is required for a user.
473
+ *
474
+ * @param WP_User|int $user
475
+ * @param string $reason
476
+ */
477
+ public static function flag_password_change_required( $user, $reason ) {
478
+ $user = ITSEC_Lib::get_user( $user );
479
+
480
+ if ( $user ) {
481
+ update_user_meta( $user->ID, 'itsec_password_change_required', $reason );
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Check if a password change is required for the given user.
487
+ *
488
+ * @param WP_User|int $user
489
+ *
490
+ * @return string|false Either the reason code a change is required, or false.
491
+ */
492
+ public static function password_change_required( $user ) {
493
+ $user = ITSEC_Lib::get_user( $user );
494
+
495
+ if ( ! $user ) {
496
+ return false;
497
+ }
498
+
499
+ $reason = get_user_meta( $user->ID, 'itsec_password_change_required', true );
500
+
501
+ if ( ! $reason ) {
502
+ return false;
503
+ }
504
+
505
+ return $reason;
506
+ }
507
+
508
+ /**
509
+ * Get the GMT time the user's password has last been changed.
510
+ *
511
+ * @param WP_User|int $user
512
+ *
513
+ * @return int
514
+ */
515
+ public static function password_last_changed( $user ) {
516
+
517
+ $user = ITSEC_Lib::get_user( $user );
518
+
519
+ if ( ! $user ) {
520
+ return 0;
521
+ }
522
+
523
+ $changed = (int) get_user_meta( $user->ID, 'itsec_last_password_change', true );
524
+ $deprecated = (int) get_user_meta( $user->ID, 'itsec-password-updated', true );
525
+
526
+ if ( $deprecated > $changed ) {
527
+ return $deprecated;
528
+ }
529
+
530
+ return $changed;
531
+ }
532
+ }
core/lib/includes/function.login-header.php ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Output the login page header.
4
+ *
5
+ * @param string $title Optional. WordPress login Page title to display in the `<title>` element.
6
+ * Default 'Log In'.
7
+ * @param string $message Optional. Message to display in header. Default empty.
8
+ * @param WP_Error $wp_error Optional. The error to pass. Default empty.
9
+ */
10
+ function login_header( $title = 'Log In', $message = '', $wp_error = '' ) {
11
+ global $error, $interim_login, $action;
12
+
13
+ // Don't index any of these forms
14
+ add_action( 'login_head', 'wp_no_robots' );
15
+
16
+ add_action( 'login_head', 'wp_login_viewport_meta' );
17
+
18
+ if ( empty($wp_error) )
19
+ $wp_error = new WP_Error();
20
+
21
+ // Shake it!
22
+ $shake_error_codes = array( 'empty_password', 'empty_email', 'invalid_email', 'invalidcombo', 'empty_username', 'invalid_username', 'incorrect_password' );
23
+ /**
24
+ * Filters the error codes array for shaking the login form.
25
+ *
26
+ * @since 3.0.0
27
+ *
28
+ * @param array $shake_error_codes Error codes that shake the login form.
29
+ */
30
+ $shake_error_codes = apply_filters( 'shake_error_codes', $shake_error_codes );
31
+
32
+ if ( $shake_error_codes && $wp_error->get_error_code() && in_array( $wp_error->get_error_code(), $shake_error_codes ) )
33
+ add_action( 'login_head', 'wp_shake_js', 12 );
34
+
35
+ $separator = is_rtl() ? ' &rsaquo; ' : ' &lsaquo; ';
36
+
37
+ ?><!DOCTYPE html>
38
+ <!--[if IE 8]>
39
+ <html xmlns="http://www.w3.org/1999/xhtml" class="ie8" <?php language_attributes(); ?>>
40
+ <![endif]-->
41
+ <!--[if !(IE 8) ]><!-->
42
+ <html xmlns="http://www.w3.org/1999/xhtml" <?php language_attributes(); ?>>
43
+ <!--<![endif]-->
44
+ <head>
45
+ <meta http-equiv="Content-Type" content="<?php bloginfo('html_type'); ?>; charset=<?php bloginfo('charset'); ?>" />
46
+ <title><?php echo get_bloginfo( 'name', 'display' ) . $separator . $title; ?></title>
47
+ <?php
48
+
49
+ wp_enqueue_style( 'login' );
50
+
51
+ /*
52
+ * Remove all stored post data on logging out.
53
+ * This could be added by add_action('login_head'...) like wp_shake_js(),
54
+ * but maybe better if it's not removable by plugins
55
+ */
56
+ if ( 'loggedout' == $wp_error->get_error_code() ) {
57
+ ?>
58
+ <script>if("sessionStorage" in window){try{for(var key in sessionStorage){if(key.indexOf("wp-autosave-")!=-1){sessionStorage.removeItem(key)}}}catch(e){}};</script>
59
+ <?php
60
+ }
61
+
62
+ /**
63
+ * Enqueue scripts and styles for the login page.
64
+ *
65
+ * @since 3.1.0
66
+ */
67
+ do_action( 'login_enqueue_scripts' );
68
+
69
+ /**
70
+ * Fires in the login page header after scripts are enqueued.
71
+ *
72
+ * @since 2.1.0
73
+ */
74
+ do_action( 'login_head' );
75
+
76
+ if ( is_multisite() ) {
77
+ $login_header_url = network_home_url();
78
+ $login_header_title = get_network()->site_name;
79
+ } else {
80
+ $login_header_url = __( 'https://wordpress.org/' );
81
+ $login_header_title = __( 'Powered by WordPress' );
82
+ }
83
+
84
+ /**
85
+ * Filters link URL of the header logo above login form.
86
+ *
87
+ * @since 2.1.0
88
+ *
89
+ * @param string $login_header_url Login header logo URL.
90
+ */
91
+ $login_header_url = apply_filters( 'login_headerurl', $login_header_url );
92
+
93
+ /**
94
+ * Filters the title attribute of the header logo above login form.
95
+ *
96
+ * @since 2.1.0
97
+ *
98
+ * @param string $login_header_title Login header logo title attribute.
99
+ */
100
+ $login_header_title = apply_filters( 'login_headertitle', $login_header_title );
101
+
102
+ $classes = array( 'login-action-' . $action, 'wp-core-ui' );
103
+ if ( is_rtl() )
104
+ $classes[] = 'rtl';
105
+ if ( $interim_login ) {
106
+ $classes[] = 'interim-login';
107
+ ?>
108
+ <style type="text/css">html{background-color: transparent;}</style>
109
+ <?php
110
+
111
+ if ( 'success' === $interim_login )
112
+ $classes[] = 'interim-login-success';
113
+ }
114
+ $classes[] =' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );
115
+
116
+ /**
117
+ * Filters the login page body classes.
118
+ *
119
+ * @since 3.5.0
120
+ *
121
+ * @param array $classes An array of body classes.
122
+ * @param string $action The action that brought the visitor to the login page.
123
+ */
124
+ $classes = apply_filters( 'login_body_class', $classes, $action );
125
+
126
+ ?>
127
+ </head>
128
+ <body class="login <?php echo esc_attr( implode( ' ', $classes ) ); ?>">
129
+ <?php
130
+ /**
131
+ * Fires in the login page header after the body tag is opened.
132
+ *
133
+ * @since 4.6.0
134
+ */
135
+ do_action( 'login_header' );
136
+ ?>
137
+ <div id="login">
138
+ <h1><a href="<?php echo esc_url( $login_header_url ); ?>" title="<?php echo esc_attr( $login_header_title ); ?>" tabindex="-1"><?php bloginfo( 'name' ); ?></a></h1>
139
+ <?php
140
+
141
+ unset( $login_header_url, $login_header_title );
142
+
143
+ /**
144
+ * Filters the message to display above the login form.
145
+ *
146
+ * @since 2.1.0
147
+ *
148
+ * @param string $message Login message text.
149
+ */
150
+ $message = apply_filters( 'login_message', $message );
151
+ if ( !empty( $message ) )
152
+ echo $message . "\n";
153
+
154
+ // In case a plugin uses $error rather than the $wp_errors object
155
+ if ( !empty( $error ) ) {
156
+ $wp_error->add('error', $error);
157
+ unset($error);
158
+ }
159
+
160
+ if ( $wp_error->get_error_code() ) {
161
+ $errors = '';
162
+ $messages = '';
163
+ foreach ( $wp_error->get_error_codes() as $code ) {
164
+ $severity = $wp_error->get_error_data( $code );
165
+ foreach ( $wp_error->get_error_messages( $code ) as $error_message ) {
166
+ if ( 'message' == $severity )
167
+ $messages .= ' ' . $error_message . "<br />\n";
168
+ else
169
+ $errors .= ' ' . $error_message . "<br />\n";
170
+ }
171
+ }
172
+ if ( ! empty( $errors ) ) {
173
+ /**
174
+ * Filters the error messages displayed above the login form.
175
+ *
176
+ * @since 2.1.0
177
+ *
178
+ * @param string $errors Login error message.
179
+ */
180
+ echo '<div id="login_error">' . apply_filters( 'login_errors', $errors ) . "</div>\n";
181
+ }
182
+ if ( ! empty( $messages ) ) {
183
+ /**
184
+ * Filters instructional messages displayed above the login form.
185
+ *
186
+ * @since 2.5.0
187
+ *
188
+ * @param string $messages Login messages.
189
+ */
190
+ echo '<p class="message">' . apply_filters( 'login_messages', $messages ) . "</p>\n";
191
+ }
192
+ }
193
+ } // End of login_header()
194
+
195
+ if ( ! function_exists( 'wp_login_viewport_meta' ) ) :
196
+ /**
197
+ * @since 3.7.0
198
+ */
199
+ function wp_login_viewport_meta() {
200
+ ?>
201
+ <meta name="viewport" content="width=device-width" />
202
+ <?php
203
+ }
204
+ endif;
core/lib/includes/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/{lock.php → lib/lock.php} RENAMED
File without changes
core/{class-itsec-lockout.php → lockout.php} RENAMED
@@ -1,18 +1,72 @@
1
  <?php
2
-
3
  /**
4
  * Handles lockouts for modules and core
5
  *
6
  * @package iThemes-Security
7
  * @since 4.0
8
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  final class ITSEC_Lockout {
10
 
11
- private
12
- $core,
13
- $lockout_modules;
14
 
15
- function __construct( $core ) {
 
 
 
 
 
 
 
16
 
17
  $this->core = $core;
18
  $this->lockout_modules = array(); //array to hold information on modules using this feature
@@ -56,6 +110,14 @@ final class ITSEC_Lockout {
56
  require_once( dirname( __FILE__ ) . '/sidebar-widget-active-lockouts.php' );
57
  }
58
 
 
 
 
 
 
 
 
 
59
  public function check_authenticate_lockout( $user ) {
60
  if ( ! ( $user instanceof WP_User ) ) {
61
  return $user;
@@ -130,16 +192,18 @@ final class ITSEC_Lockout {
130
  }
131
 
132
  /**
133
- * Executes lockout and logging for modules
 
 
134
  *
135
  * @since 4.0
136
  *
137
- * @param string $module string name of the calling module
138
- * @param string $user username of user
139
  *
140
  * @return void
141
  */
142
- public function do_lockout( $module, $user = null ) {
143
 
144
  global $wpdb, $itsec_globals;
145
 
@@ -184,9 +248,9 @@ final class ITSEC_Lockout {
184
 
185
  }
186
 
187
- if ( $user !== null && isset( $options['user'] ) && $options['user'] > 0 ) {
188
 
189
- $user_id = username_exists( sanitize_text_field( $user ) );
190
 
191
  if ( $user_id !== false ) {
192
 
@@ -197,7 +261,7 @@ final class ITSEC_Lockout {
197
  'temp_date' => date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ),
198
  'temp_date_gmt' => date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ),
199
  'temp_user' => intval( $user_id ),
200
- 'temp_username' => sanitize_text_field( $user ),
201
  )
202
  );
203
 
@@ -205,7 +269,7 @@ final class ITSEC_Lockout {
205
  $wpdb->prepare(
206
  "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND `temp_username`='%s' OR `temp_user`=%s;",
207
  date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( $options['period'] * 60 ) ),
208
- sanitize_text_field( $user ),
209
  intval( $user_id )
210
  )
211
  );
@@ -218,7 +282,7 @@ final class ITSEC_Lockout {
218
 
219
  } else {
220
 
221
- $user = sanitize_text_field( $user );
222
 
223
  $wpdb->insert(
224
  $wpdb->base_prefix . 'itsec_temp',
@@ -226,7 +290,7 @@ final class ITSEC_Lockout {
226
  'temp_type' => $options['type'],
227
  'temp_date' => date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ),
228
  'temp_date_gmt' => date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ),
229
- 'temp_username' => $user,
230
  )
231
  );
232
 
@@ -234,13 +298,13 @@ final class ITSEC_Lockout {
234
  $wpdb->prepare(
235
  "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND `temp_username`='%s';",
236
  date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( $options['period'] * 60 ) ),
237
- $user
238
  )
239
  );
240
 
241
  if ( $user_count >= $options['user'] ) {
242
 
243
- $lock_username = $user;
244
 
245
  }
246
 
@@ -271,7 +335,8 @@ final class ITSEC_Lockout {
271
  /**
272
  * Executes lockout (locks user out)
273
  *
274
- * @param boolean $user if we're locking out a user or not
 
275
  *
276
  * @return void
277
  */
@@ -350,7 +415,7 @@ final class ITSEC_Lockout {
350
 
351
  $description = '<h4>' . __( 'About Lockouts', 'better-wp-security' ) . '</h4>';
352
  $description .= '<p>';
353
- $description .= sprintf( __( 'Your lockout settings can be configured in <a href="%s">Global Settings</a>.', 'better-wp-security' ), esc_url( $global_settings_url ) );
354
  $description .= '<br />';
355
  $description .= __( 'Your current settings are configured as follows:', 'better-wp-security' );
356
  $description .= '<ul><li>';
@@ -439,6 +504,11 @@ final class ITSEC_Lockout {
439
 
440
  }
441
 
 
 
 
 
 
442
  public function get_temp_whitelist() {
443
  $whitelist = get_site_option( 'itsec_temp_whitelist_ip', false );
444
 
@@ -458,6 +528,9 @@ final class ITSEC_Lockout {
458
  return $whitelist;
459
  }
460
 
 
 
 
461
  public function update_temp_whitelist() {
462
  if ( ! ITSEC_Core::current_user_can_manage() ) {
463
  // Only add IP's of users that can manage Security settings.
@@ -468,6 +541,13 @@ final class ITSEC_Lockout {
468
  $this->add_to_temp_whitelist( $ip );
469
  }
470
 
 
 
 
 
 
 
 
471
  public function add_to_temp_whitelist( $ip ) {
472
  $whitelist = $this->get_temp_whitelist();
473
  $expiration = ITSEC_Core::get_current_time_gmt() + DAY_IN_SECONDS;
@@ -490,6 +570,11 @@ final class ITSEC_Lockout {
490
  update_site_option( 'itsec_temp_whitelist_ip', $whitelist );
491
  }
492
 
 
 
 
 
 
493
  public function remove_from_temp_whitelist( $ip ) {
494
  $whitelist = $this->get_temp_whitelist();
495
 
@@ -502,10 +587,18 @@ final class ITSEC_Lockout {
502
  update_site_option( 'itsec_temp_whitelist_ip', $whitelist );
503
  }
504
 
505
- public function clear_temp_whitelist( $ip ) {
 
 
 
506
  update_site_option( 'itsec_temp_whitelist_ip', array() );
507
  }
508
 
 
 
 
 
 
509
  public function is_visitor_temp_whitelisted() {
510
  global $itsec_globals;
511
 
@@ -520,7 +613,12 @@ final class ITSEC_Lockout {
520
  }
521
 
522
  /**
523
- * Locks out given user or host
 
 
 
 
 
524
  *
525
  * @since 4.0
526
  *
@@ -542,6 +640,7 @@ final class ITSEC_Lockout {
542
  $user_expiration = null;
543
  $username = sanitize_text_field( trim( $username ) );
544
 
 
545
  if ( $itsec_files->get_file_lock( 'lockout_' . $host . $user . $username ) ) {
546
 
547
  //Do we have a good host to lock out or not
@@ -769,9 +868,9 @@ final class ITSEC_Lockout {
769
  /**
770
  * Register 404 and file change detection for logger
771
  *
772
- * @param array $logger_modules array of logger modules
773
  *
774
- * @return array array of logger modules
775
  */
776
  public function register_logger( $logger_modules ) {
777
 
@@ -789,7 +888,7 @@ final class ITSEC_Lockout {
789
  *
790
  * @since 3.6.0
791
  *
792
- * @param Ithemes_Sync_API Sync API object.
793
  */
794
  public function register_sync_verbs( $api ) {
795
  $api->register( 'itsec-get-lockouts', 'Ithemes_Sync_Verb_ITSEC_Get_Lockouts', dirname( __FILE__ ) . '/sync-verbs/itsec-get-lockouts.php' );
@@ -803,7 +902,7 @@ final class ITSEC_Lockout {
803
  *
804
  * @since 3.6.0
805
  *
806
- * @param array Array of verbs.
807
  *
808
  * @return array Array of verbs.
809
  */
@@ -815,14 +914,24 @@ final class ITSEC_Lockout {
815
  }
816
 
817
  /**
818
- * Register modules that will use the lockout service
819
  *
820
  * @return void
821
  */
822
  public function register_modules() {
823
 
 
 
 
 
 
 
 
 
 
 
 
824
  $this->lockout_modules = apply_filters( 'itsec_lockout_modules', $this->lockout_modules );
825
-
826
  }
827
 
828
  /**
@@ -830,6 +939,8 @@ final class ITSEC_Lockout {
830
  *
831
  * @since 4.0
832
  *
 
 
833
  * @return bool true on success or false
834
  */
835
  public function release_lockout( $id = null ) {
1
  <?php
 
2
  /**
3
  * Handles lockouts for modules and core
4
  *
5
  * @package iThemes-Security
6
  * @since 4.0
7
  */
8
+
9
+ /**
10
+ * Class ITSEC_Lockout
11
+ *
12
+ * The ITSEC Lockout class is the centralized controller for detecting and blocking already locked-out users. Other
13
+ * iThemes Security modules instruct ITSEC_Lockout to save a lock out to storage, but ITSEC Lockout will never lock
14
+ * out a user itself.
15
+ *
16
+ * If a user attempts to login with valid credentials and their user ID is marked as locked out, they will be prevented
17
+ * from logging in and the lock will remain in effect until its expiration.
18
+ *
19
+ * There are three types of lockouts.
20
+ *
21
+ * - User ID
22
+ * - Username
23
+ * - Host
24
+ *
25
+ * = User ID =
26
+ * User ID lockouts are used whenever an attacker tries to repeatedly log in with a valid username, but incorrect password.
27
+ * By default, a host lockout will occur first ( assuming the attacker does not alter their IPs ). This is done because
28
+ * a user ID lockout can lock out a legitimate user from signing into their account.
29
+ *
30
+ * = Username =
31
+ * Username lockouts are used whenever an attacker tried to repeatedly log in with a non-existent username. Or, if
32
+ * enabled, uses the 'admin' username. This is separate from the User ID lock out type, however the lockout message
33
+ * is shared between the two.
34
+ *
35
+ * = Host =
36
+ * Host lockouts are used whenever an IP address is flagged as an attacker. This is done via repeated 404 errors or
37
+ * failed captcha validations. If an IP address is whitelisted, an event will be logged, but the user will not be
38
+ * locked out. By default, host lockouts have the lowest threshold before locking out the host. The Network Brute Force
39
+ * module does NOT create host lockouts, but utilizes ITSEC_Lockout::execute_lock() to prevent the attacker from
40
+ * accessing the site.
41
+ *
42
+ * ITSEC_Lockout will store a record whenever ITSEC Lockout is instructed to perform a lockout via ::do_lockout() in the
43
+ * itsec_temp database table. If the threshold for that lockout type has been met – the most recently added one counts –
44
+ * an actual lockout will be saved to the itsec_lockouts table. If enabled, and enough lockouts have occurred
45
+ * ( configurable via settings ), a host will be blacklisted instead of added to the itsec_lockouts table. Blacklisted
46
+ * IPs are blocked at the server level. This is handled by the ban-users module.
47
+ *
48
+ * After the lockout has been stored, the request will be immediately exited.
49
+ *
50
+ * iThemes Security supports two types of whitelists. Temporary and permanent whitelists. Permanent whitelists are
51
+ * configured in the Global Settings module and will permanently prevent a user with that IP from being locked out.
52
+ * The temporary whitelist is a global list of admin level user's IP addresses. Whenever an admin user is logged-in and
53
+ * using the site, their IP will be added to the whitelist for 24 hours.
54
+ *
55
+ * This controller also provides a number of methods to retrieve a list or clear both lockouts and temporary whitelists.
56
+ */
57
  final class ITSEC_Lockout {
58
 
59
+ /** @var ITSEC_Core */
60
+ private $core;
 
61
 
62
+ private $lockout_modules;
63
+
64
+ /**
65
+ * ITSEC_Lockout constructor.
66
+ *
67
+ * @param ITSEC_Core $core
68
+ */
69
+ public function __construct( $core ) {
70
 
71
  $this->core = $core;
72
  $this->lockout_modules = array(); //array to hold information on modules using this feature
110
  require_once( dirname( __FILE__ ) . '/sidebar-widget-active-lockouts.php' );
111
  }
112
 
113
+ /**
114
+ * Check if a user has successfully logged-in, and prevent them from accessing the site if they
115
+ * still have a lockout in effect.
116
+ *
117
+ * @param \WP_User|\WP_Error|null $user
118
+ *
119
+ * @return WP_User|WP_Error|null
120
+ */
121
  public function check_authenticate_lockout( $user ) {
122
  if ( ! ( $user instanceof WP_User ) ) {
123
  return $user;
192
  }
193
 
194
  /**
195
+ * This persists a lockout to storage or performs a permanent ban if appropriate.
196
+ *
197
+ * The user will be immediately locked out by this method if their IP is not whitelisted.
198
  *
199
  * @since 4.0
200
  *
201
+ * @param string $module string name of the calling module
202
+ * @param string $username username of user
203
  *
204
  * @return void
205
  */
206
+ public function do_lockout( $module, $username = null ) {
207
 
208
  global $wpdb, $itsec_globals;
209
 
248
 
249
  }
250
 
251
+ if ( $username !== null && isset( $options['user'] ) && $options['user'] > 0 ) {
252
 
253
+ $user_id = username_exists( sanitize_text_field( $username ) );
254
 
255
  if ( $user_id !== false ) {
256
 
261
  'temp_date' => date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ),
262
  'temp_date_gmt' => date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ),
263
  'temp_user' => intval( $user_id ),
264
+ 'temp_username' => sanitize_text_field( $username ),
265
  )
266
  );
267
 
269
  $wpdb->prepare(
270
  "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND `temp_username`='%s' OR `temp_user`=%s;",
271
  date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( $options['period'] * 60 ) ),
272
+ sanitize_text_field( $username ),
273
  intval( $user_id )
274
  )
275
  );
282
 
283
  } else {
284
 
285
+ $username = sanitize_text_field( $username );
286
 
287
  $wpdb->insert(
288
  $wpdb->base_prefix . 'itsec_temp',
290
  'temp_type' => $options['type'],
291
  'temp_date' => date( 'Y-m-d H:i:s', $itsec_globals['current_time'] ),
292
  'temp_date_gmt' => date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ),
293
+ 'temp_username' => $username,
294
  )
295
  );
296
 
298
  $wpdb->prepare(
299
  "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND `temp_username`='%s';",
300
  date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( $options['period'] * 60 ) ),
301
+ $username
302
  )
303
  );
304
 
305
  if ( $user_count >= $options['user'] ) {
306
 
307
+ $lock_username = $username;
308
 
309
  }
310
 
335
  /**
336
  * Executes lockout (locks user out)
337
  *
338
+ * @param bool $user Is a specific user being locked out.
339
+ * @param bool $network Was the host flagged by the Security Network.
340
  *
341
  * @return void
342
  */
415
 
416
  $description = '<h4>' . __( 'About Lockouts', 'better-wp-security' ) . '</h4>';
417
  $description .= '<p>';
418
+ $description .= sprintf( __( 'Your lockout settings can be configured in <a href="%s" data-module-link="global">Global Settings</a>.', 'better-wp-security' ), esc_url( $global_settings_url ) );
419
  $description .= '<br />';
420
  $description .= __( 'Your current settings are configured as follows:', 'better-wp-security' );
421
  $description .= '<ul><li>';
504
 
505
  }
506
 
507
+ /**
508
+ * Retrieve a list of the temporary whitelisted IP addresses.
509
+ *
510
+ * @return array A map of IP addresses to their expiration time.
511
+ */
512
  public function get_temp_whitelist() {
513
  $whitelist = get_site_option( 'itsec_temp_whitelist_ip', false );
514
 
528
  return $whitelist;
529
  }
530
 
531
+ /**
532
+ * If the current user has permission to manage ITSEC, add them to the temporary whitelist.
533
+ */
534
  public function update_temp_whitelist() {
535
  if ( ! ITSEC_Core::current_user_can_manage() ) {
536
  // Only add IP's of users that can manage Security settings.
541
  $this->add_to_temp_whitelist( $ip );
542
  }
543
 
544
+ /**
545
+ * Add an IP address to the temporary whitelist for 24 hours.
546
+ *
547
+ * This method will also remove any expired IPs from storage.
548
+ *
549
+ * @param string $ip
550
+ */
551
  public function add_to_temp_whitelist( $ip ) {
552
  $whitelist = $this->get_temp_whitelist();
553
  $expiration = ITSEC_Core::get_current_time_gmt() + DAY_IN_SECONDS;
570
  update_site_option( 'itsec_temp_whitelist_ip', $whitelist );
571
  }
572
 
573
+ /**
574
+ * Remove a given IP address from the temporary whitelist.
575
+ *
576
+ * @param string $ip
577
+ */
578
  public function remove_from_temp_whitelist( $ip ) {
579
  $whitelist = $this->get_temp_whitelist();
580
 
587
  update_site_option( 'itsec_temp_whitelist_ip', $whitelist );
588
  }
589
 
590
+ /**
591
+ * Completely clear the temporary whitelist of all IP addresses.
592
+ */
593
+ public function clear_temp_whitelist() {
594
  update_site_option( 'itsec_temp_whitelist_ip', array() );
595
  }
596
 
597
+ /**
598
+ * Check if the current user is temporarily whitelisted.
599
+ *
600
+ * @return bool
601
+ */
602
  public function is_visitor_temp_whitelisted() {
603
  global $itsec_globals;
604
 
613
  }
614
 
615
  /**
616
+ * Store a record of the locked out user/host or permanently ban the host.
617
+ *
618
+ * Permanently banned hosts will be forwarded to the ban-users module via the itsec-new-blacklisted-ip hook and
619
+ * not persisted to the database.
620
+ *
621
+ * If configured, notifies the configured email addresses of the lockout.
622
  *
623
  * @since 4.0
624
  *
640
  $user_expiration = null;
641
  $username = sanitize_text_field( trim( $username ) );
642
 
643
+ // Acquire a lock to prevent a lockout being created more than once by a particularly fast attacker.
644
  if ( $itsec_files->get_file_lock( 'lockout_' . $host . $user . $username ) ) {
645
 
646
  //Do we have a good host to lock out or not
868
  /**
869
  * Register 404 and file change detection for logger
870
  *
871
+ * @param array $logger_modules array of logger modules
872
  *
873
+ * @return array
874
  */
875
  public function register_logger( $logger_modules ) {
876
 
888
  *
889
  * @since 3.6.0
890
  *
891
+ * @param Ithemes_Sync_API $api API object.
892
  */
893
  public function register_sync_verbs( $api ) {
894
  $api->register( 'itsec-get-lockouts', 'Ithemes_Sync_Verb_ITSEC_Get_Lockouts', dirname( __FILE__ ) . '/sync-verbs/itsec-get-lockouts.php' );
902
  *
903
  * @since 3.6.0
904
  *
905
+ * @param array $verbs of verbs.
906
  *
907
  * @return array Array of verbs.
908
  */
914
  }
915
 
916
  /**
917
+ * Register modules that will use the lockout service.
918
  *
919
  * @return void
920
  */
921
  public function register_modules() {
922
 
923
+ /**
924
+ * Filter the available lockout modules.
925
+ *
926
+ * @param array $lockout_modules Each lockout module should be an array containing 'type', 'reason' and
927
+ * 'period' options. The type is a unique string referring to the type of lockout.
928
+ * 'reason' is a human readable label describing the reason for the lockout.
929
+ * 'period' is the number of days to check for lockouts to decide if the host
930
+ * should be permanently banned. Additionally, the 'user' and 'host' options instruct
931
+ * security to wait for that many temporary lockout events to occur before executing
932
+ * the lockout.
933
+ */
934
  $this->lockout_modules = apply_filters( 'itsec_lockout_modules', $this->lockout_modules );
 
935
  }
936
 
937
  /**
939
  *
940
  * @since 4.0
941
  *
942
+ * @param int $id
943
+ *
944
  * @return bool true on success or false
945
  */
946
  public function release_lockout( $id = null ) {
core/{class-itsec-logger-all-logs.php → logger-all-logs.php} RENAMED
File without changes
core/{class-itsec-logger.php → logger.php} RENAMED
@@ -19,7 +19,7 @@ final class ITSEC_Logger {
19
  *
20
  * @var array Events that need to be logged to a file but couldn't
21
  */
22
- private $_events_to_log_to_file = array();
23
 
24
  function __construct() {
25
 
@@ -157,11 +157,11 @@ final class ITSEC_Logger {
157
  $type = ITSEC_Modules::get_setting( 'global', 'log_type' );
158
 
159
  if ( 'database' === $type || 'both' === $type ) {
160
- $this->_log_event_to_db( $module, $priority, $data, $host, $username, $user, $url, $referrer );
161
  }
162
 
163
  if ( 'file' === $type || 'both' === $type ) {
164
- $this->_log_event_to_file( $module, $priority, $data, $host, $username, $user, $url, $referrer );
165
  }
166
 
167
  }
@@ -170,7 +170,7 @@ final class ITSEC_Logger {
170
 
171
  }
172
 
173
- private function _log_event_to_db( $module, $priority = 5, $data = array(), $host = '', $username = '', $user = '', $url = '', $referrer = '' ) {
174
  global $wpdb, $itsec_globals;
175
 
176
  $options = $this->logger_modules[ $module ];
@@ -210,12 +210,12 @@ final class ITSEC_Logger {
210
  $wpdb->show_errors( $cached_show_errors_setting );
211
  }
212
 
213
- private function _log_event_to_file( $module, $priority = 5, $data = array(), $host = '', $username = '', $user = '', $url = '', $referrer = '' ) {
214
  global $itsec_globals;
215
 
216
  // If the file can't be prepared, store the events up to write later (at plugins_loaded)
217
- if ( false === $this->_prepare_log_file() ) {
218
- $this->_events_to_log_to_file[] = compact( 'module', 'priority', 'data', 'host', 'username', 'user', 'url', 'referrer' );
219
  return;
220
  }
221
 
@@ -241,8 +241,8 @@ final class ITSEC_Logger {
241
  }
242
 
243
  public function write_pending_events_to_file() {
244
- foreach ( $this->_events_to_log_to_file as $event ) {
245
- call_user_func_array( array( $this, '_log_event_to_file' ), $event );
246
  }
247
  }
248
 
@@ -404,8 +404,9 @@ final class ITSEC_Logger {
404
  */
405
  private function rotate_log() {
406
  $log_file = $this->get_log_file();
 
407
 
408
- if ( ! @file_exists( $log_file ) || @filesize( $log_file ) < 10485760 ) { // 10485760 is 1 mebibyte
409
  return;
410
  }
411
 
@@ -445,7 +446,7 @@ final class ITSEC_Logger {
445
 
446
  }
447
 
448
- $this->_prepare_log_file();
449
 
450
  }
451
 
@@ -494,7 +495,6 @@ final class ITSEC_Logger {
494
  private function get_log_file() {
495
  if ( isset( $this->log_file ) ) {
496
  return $this->log_file;
497
- $this->rotate_log();
498
  }
499
 
500
  $log_location = ITSEC_Modules::get_setting( 'global', 'log_location' );
@@ -522,7 +522,7 @@ final class ITSEC_Logger {
522
  *
523
  * @return void
524
  */
525
- private function _prepare_log_file() {
526
  $log_file = $this->get_log_file();
527
 
528
  // We can't prepare a file if we can't get the file name
19
  *
20
  * @var array Events that need to be logged to a file but couldn't
21
  */
22
+ private $events_to_log_to_file = array();
23
 
24
  function __construct() {
25
 
157
  $type = ITSEC_Modules::get_setting( 'global', 'log_type' );
158
 
159
  if ( 'database' === $type || 'both' === $type ) {
160
+ $this->log_event_to_db( $module, $priority, $data, $host, $username, $user, $url, $referrer );
161
  }
162
 
163
  if ( 'file' === $type || 'both' === $type ) {
164
+ $this->log_event_to_file( $module, $priority, $data, $host, $username, $user, $url, $referrer );
165
  }
166
 
167
  }
170
 
171
  }
172
 
173
+ private function log_event_to_db( $module, $priority, $data, $host, $username, $user, $url, $referrer ) {
174
  global $wpdb, $itsec_globals;
175
 
176
  $options = $this->logger_modules[ $module ];
210
  $wpdb->show_errors( $cached_show_errors_setting );
211
  }
212
 
213
+ private function log_event_to_file( $module, $priority = 5, $data = array(), $host = '', $username = '', $user = '', $url = '', $referrer = '' ) {
214
  global $itsec_globals;
215
 
216
  // If the file can't be prepared, store the events up to write later (at plugins_loaded)
217
+ if ( false === $this->prepare_log_file() ) {
218
+ $this->events_to_log_to_file[] = compact( 'module', 'priority', 'data', 'host', 'username', 'user', 'url', 'referrer' );
219
  return;
220
  }
221
 
241
  }
242
 
243
  public function write_pending_events_to_file() {
244
+ foreach ( $this->events_to_log_to_file as $event ) {
245
+ call_user_func_array( array( $this, 'log_event_to_file' ), $event );
246
  }
247
  }
248
 
404
  */
405
  private function rotate_log() {
406
  $log_file = $this->get_log_file();
407
+ $max_size = 1024 * 1024 * 10; // 10MiB
408
 
409
+ if ( ! @file_exists( $log_file ) || @filesize( $log_file ) < $max_size ) {
410
  return;
411
  }
412
 
446
 
447
  }
448
 
449
+ $this->prepare_log_file();
450
 
451
  }
452
 
495
  private function get_log_file() {
496
  if ( isset( $this->log_file ) ) {
497
  return $this->log_file;
 
498
  }
499
 
500
  $log_location = ITSEC_Modules::get_setting( 'global', 'log_location' );
522
  *
523
  * @return void
524
  */
525
+ private function prepare_log_file() {
526
  $log_file = $this->get_log_file();
527
 
528
  // We can't prepare a file if we can't get the file name
core/{class-itsec-modules.php → modules.php} RENAMED
@@ -27,6 +27,8 @@ final class ITSEC_Modules {
27
 
28
  /**
29
  * Function to instantiate our class and make it a singleton
 
 
30
  */
31
  public static function get_instance() {
32
  if ( ! self::$instance ) {
@@ -52,7 +54,7 @@ final class ITSEC_Modules {
52
  $slug = sanitize_title_with_dashes( $slug );
53
 
54
  if ( ! is_dir( $path ) ) {
55
- trigger_error( sprintf( __( 'An attempt to register the %1$s module failed since the supplied path (%1$s) is invalid. This could indicate an invalid modification or incomplete installation of the iThemes Security plugin. Please reinstall the plugin and try again.', 'better-wp-security' ), $slug, $path ) );
56
  return false;
57
  }
58
 
@@ -94,6 +96,14 @@ final class ITSEC_Modules {
94
  return false;
95
  }
96
 
 
 
 
 
 
 
 
 
97
  public static function update_module_paths( $old_dir, $new_dir ) {
98
  $self = self::get_instance();
99
 
@@ -102,11 +112,25 @@ final class ITSEC_Modules {
102
  }
103
  }
104
 
 
 
 
 
 
105
  public static function register_settings( $settings ) {
106
  $self = self::get_instance();
107
  $self->_module_settings[ $settings->get_id() ] = $settings;
108
  }
109
 
 
 
 
 
 
 
 
 
 
110
  public static function get_settings_obj( $slug ) {
111
  $self = self::get_instance();
112
 
@@ -121,6 +145,13 @@ final class ITSEC_Modules {
121
  return $self->_module_settings[ $slug ];
122
  }
123
 
 
 
 
 
 
 
 
124
  public static function get_defaults( $slug ) {
125
  $self = self::get_instance();
126
 
@@ -133,6 +164,15 @@ final class ITSEC_Modules {
133
  return $settings_obj->get_defaults();
134
  }
135
 
 
 
 
 
 
 
 
 
 
136
  public static function get_default( $slug, $name, $default = null ) {
137
  $self = self::get_instance();
138
 
@@ -145,6 +185,13 @@ final class ITSEC_Modules {
145
  return $default;
146
  }
147
 
 
 
 
 
 
 
 
148
  public static function get_settings( $slug ) {
149
  $self = self::get_instance();
150
 
@@ -157,6 +204,16 @@ final class ITSEC_Modules {
157
  return $settings_obj->get_all();
158
  }
159
 
 
 
 
 
 
 
 
 
 
 
160
  public static function get_setting( $slug, $name, $default = null ) {
161
  $self = self::get_instance();
162
 
@@ -169,6 +226,16 @@ final class ITSEC_Modules {
169
  return $settings_obj->get( $name, $default );
170
  }
171
 
 
 
 
 
 
 
 
 
 
 
172
  public static function set_settings( $slug, $settings ) {
173
  $self = self::get_instance();
174
 
@@ -184,6 +251,17 @@ final class ITSEC_Modules {
184
  return $settings_obj->set_all( $settings );
185
  }
186
 
 
 
 
 
 
 
 
 
 
 
 
187
  public static function set_setting( $slug, $name, $value ) {
188
  $self = self::get_instance();
189
 
@@ -197,11 +275,27 @@ final class ITSEC_Modules {
197
  return $settings_obj->set( $name, $value );
198
  }
199
 
 
 
 
 
 
 
 
200
  public static function register_validator( $validator ) {
201
  $self = self::get_instance();
202
  $self->_module_validators[ $validator->get_id() ] = $validator;
203
  }
204
 
 
 
 
 
 
 
 
 
 
205
  public static function get_validator( $slug ) {
206
  $self = self::get_instance();
207
 
@@ -217,6 +311,13 @@ final class ITSEC_Modules {
217
  return $self->_module_validators[ $slug ];
218
  }
219
 
 
 
 
 
 
 
 
220
  public static function get_available_modules() {
221
  $self = self::get_instance();
222
 
@@ -233,6 +334,13 @@ final class ITSEC_Modules {
233
  return $self->_available_modules;
234
  }
235
 
 
 
 
 
 
 
 
236
  public static function get_active_modules() {
237
  $self = self::get_instance();
238
 
@@ -286,11 +394,23 @@ final class ITSEC_Modules {
286
  return $self->_active_modules_list;
287
  }
288
 
 
 
 
 
 
289
  public static function get_always_active_modules() {
290
  $self = self::get_instance();
291
  return array_keys( $self->_always_active_modules );
292
  }
293
 
 
 
 
 
 
 
 
294
  public static function is_always_active( $module_id ) {
295
  $self = self::get_instance();
296
 
@@ -301,6 +421,13 @@ final class ITSEC_Modules {
301
  return false;
302
  }
303
 
 
 
 
 
 
 
 
304
  public static function is_active( $module_id ) {
305
  $self = self::get_instance();
306
 
@@ -385,6 +512,15 @@ final class ITSEC_Modules {
385
  return $was_active;
386
  }
387
 
 
 
 
 
 
 
 
 
 
388
  public static function set_active_modules( $new_active_modules ) {
389
  $self = self::get_instance();
390
 
@@ -413,13 +549,29 @@ final class ITSEC_Modules {
413
  return true;
414
  }
415
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  public static function load_module_file( $file, $modules = ':all' ) {
417
  $self = self::get_instance();
418
 
419
  if ( ':all' === $modules ) {
420
  $modules = self::get_available_modules();
421
  } else if ( ':active' === $modules ) {
422
- $modules = self::get_active_modules();
 
 
 
 
423
 
424
  $modules = array_merge( $modules, array_keys( $self->_always_active_modules ) );
425
  $modules = array_unique( $modules );
@@ -438,39 +590,65 @@ final class ITSEC_Modules {
438
  return true;
439
  }
440
 
 
 
 
441
  public static function init_modules() {
442
  do_action( 'itsec-register-modules' );
443
  }
444
 
 
 
 
445
  public static function run_active_modules() {
446
  // The active.php file is for code that will only run when the module is active.
447
  self::load_module_file( 'active.php', ':active' );
448
  }
449
 
 
 
 
450
  public function run_activation() {
451
  self::load_module_file( 'setup.php' );
452
 
453
  do_action( 'itsec_modules_do_plugin_activation' );
454
  }
455
 
 
 
 
456
  public function run_deactivation() {
457
  self::load_module_file( 'setup.php' );
458
 
459
  do_action( 'itsec_modules_do_plugin_deactivation' );
460
  }
461
 
 
 
 
462
  public static function run_uninstall() {
463
  self::load_module_file( 'setup.php' );
464
 
465
  do_action( 'itsec_modules_do_plugin_uninstall' );
466
  }
467
 
 
 
 
 
 
 
468
  public function run_upgrade( $old_version, $new_version ) {
469
  self::load_module_file( 'setup.php' );
470
 
471
  do_action( 'itsec_modules_do_plugin_upgrade', $old_version, $new_version );
472
  }
473
 
 
 
 
 
 
474
  public function load_settings_page() {
475
  if ( $this->_settings_files_loaded ) {
476
  return;
27
 
28
  /**
29
  * Function to instantiate our class and make it a singleton
30
+ *
31
+ * @return ITSEC_Modules
32
  */
33
  public static function get_instance() {
34
  if ( ! self::$instance ) {
54
  $slug = sanitize_title_with_dashes( $slug );
55
 
56
  if ( ! is_dir( $path ) ) {
57
+ trigger_error( sprintf( __( 'An attempt to register the %1$s module failed since the supplied path (%2$s) is invalid. This could indicate an invalid modification or incomplete installation of the iThemes Security plugin. Please reinstall the plugin and try again.', 'better-wp-security' ), $slug, $path ) );
58
  return false;
59
  }
60
 
96
  return false;
97
  }
98
 
99
+ /**
100
+ * Update the stored paths for each module.
101
+ *
102
+ * This is predominantly used when changing the WordPress content directory.
103
+ *
104
+ * @param string $old_dir
105
+ * @param string $new_dir
106
+ */
107
  public static function update_module_paths( $old_dir, $new_dir ) {
108
  $self = self::get_instance();
109
 
112
  }
113
  }
114
 
115
+ /**
116
+ * Register a module's settings controller.
117
+ *
118
+ * @param ITSEC_Settings $settings
119
+ */
120
  public static function register_settings( $settings ) {
121
  $self = self::get_instance();
122
  $self->_module_settings[ $settings->get_id() ] = $settings;
123
  }
124
 
125
+ /**
126
+ * Retrieve a module's settings controller.
127
+ *
128
+ * This will load a module's settings file if it has not yet been loaded.
129
+ *
130
+ * @param string $slug The module slug.
131
+ *
132
+ * @return ITSEC_Settings|null
133
+ */
134
  public static function get_settings_obj( $slug ) {
135
  $self = self::get_instance();
136
 
145
  return $self->_module_settings[ $slug ];
146
  }
147
 
148
+ /**
149
+ * Get the default settings for a module.
150
+ *
151
+ * @param string $slug The module slug.
152
+ *
153
+ * @return array
154
+ */
155
  public static function get_defaults( $slug ) {
156
  $self = self::get_instance();
157
 
164
  return $settings_obj->get_defaults();
165
  }
166
 
167
+ /**
168
+ * Retrieve the default value of specific setting in a module.
169
+ *
170
+ * @param string $slug The module slug.
171
+ * @param string $name The name of the setting.
172
+ * @param mixed|null $default Optionally, specify a default value to be used if the module did not declare one.
173
+ *
174
+ * @return mixed
175
+ */
176
  public static function get_default( $slug, $name, $default = null ) {
177
  $self = self::get_instance();
178
 
185
  return $default;
186
  }
187
 
188
+ /**
189
+ * Retrieve all of the settings for a module.
190
+ *
191
+ * @param string $slug The module slug.
192
+ *
193
+ * @return array
194
+ */
195
  public static function get_settings( $slug ) {
196
  $self = self::get_instance();
197
 
204
  return $settings_obj->get_all();
205
  }
206
 
207
+ /**
208
+ * Retrieve the value of a specific setting in a module.
209
+ *
210
+ * @param string $slug The module slug.
211
+ * @param string $name The name of the setting.
212
+ * @param mixed|null $default Optionally, specify a default value to be used if the requested setting does not
213
+ * exist.
214
+ *
215
+ * @return mixed
216
+ */
217
  public static function get_setting( $slug, $name, $default = null ) {
218
  $self = self::get_instance();
219
 
226
  return $settings_obj->get( $name, $default );
227
  }
228
 
229
+ /**
230
+ * Update all of a module's settings at once.
231
+ *
232
+ * The values will be validated, updated in-memory, and persisted.
233
+ *
234
+ * @param string $slug The module slug.
235
+ * @param array $settings New settings values.
236
+ *
237
+ * @return array|WP_Error
238
+ */
239
  public static function set_settings( $slug, $settings ) {
240
  $self = self::get_instance();
241
 
251
  return $settings_obj->set_all( $settings );
252
  }
253
 
254
+ /**
255
+ * Update a single setting in a module.
256
+ *
257
+ * The new value will be validated, updated- in-memory, and persisted.
258
+ *
259
+ * @param string $slug The module slug.
260
+ * @param string $name The setting name to updated.
261
+ * @param mixed $value The settings' new value.
262
+ *
263
+ * @return array|false
264
+ */
265
  public static function set_setting( $slug, $name, $value ) {
266
  $self = self::get_instance();
267
 
275
  return $settings_obj->set( $name, $value );
276
  }
277
 
278
+ /**
279
+ * Register a module's validator controller.
280
+ *
281
+ * Only one validator per-module is supported.
282
+ *
283
+ * @param ITSEC_Validator $validator
284
+ */
285
  public static function register_validator( $validator ) {
286
  $self = self::get_instance();
287
  $self->_module_validators[ $validator->get_id() ] = $validator;
288
  }
289
 
290
+ /**
291
+ * Retrieve the validator for a given module.
292
+ *
293
+ * This will load a module's validator component if not yet loaded.
294
+ *
295
+ * @param string $slug The module slug.
296
+ *
297
+ * @return ITSEC_Validator|null
298
+ */
299
  public static function get_validator( $slug ) {
300
  $self = self::get_instance();
301
 
311
  return $self->_module_validators[ $slug ];
312
  }
313
 
314
+ /**
315
+ * Retrieve the slugs of all modules available to the plugin.
316
+ *
317
+ * This function is internally cached.
318
+ *
319
+ * @return string[]
320
+ */
321
  public static function get_available_modules() {
322
  $self = self::get_instance();
323
 
334
  return $self->_available_modules;
335
  }
336
 
337
+ /**
338
+ * Retrieve the slugs of all active modules.
339
+ *
340
+ * This includes user activated and default activated modules. The result is internally cached.
341
+ *
342
+ * @return string[]
343
+ */
344
  public static function get_active_modules() {
345
  $self = self::get_instance();
346
 
394
  return $self->_active_modules_list;
395
  }
396
 
397
+ /**
398
+ * Retrieve the slugs of all modules that are required to be active.
399
+ *
400
+ * @return string[]
401
+ */
402
  public static function get_always_active_modules() {
403
  $self = self::get_instance();
404
  return array_keys( $self->_always_active_modules );
405
  }
406
 
407
+ /**
408
+ * Check if a module is configured to be always active.
409
+ *
410
+ * @param string $module_id The module slug.
411
+ *
412
+ * @return bool
413
+ */
414
  public static function is_always_active( $module_id ) {
415
  $self = self::get_instance();
416
 
421
  return false;
422
  }
423
 
424
+ /**
425
+ * Check if a module is active.
426
+ *
427
+ * @param string $module_id The module slug.
428
+ *
429
+ * @return bool
430
+ */
431
  public static function is_active( $module_id ) {
432
  $self = self::get_instance();
433
 
512
  return $was_active;
513
  }
514
 
515
+ /**
516
+ * Change the active and deactivate modules in bulk.
517
+ *
518
+ * The deactivation routine for no-longer active modules will NOT be run.
519
+ *
520
+ * @param string[] $new_active_modules
521
+ *
522
+ * @return bool
523
+ */
524
  public static function set_active_modules( $new_active_modules ) {
525
  $self = self::get_instance();
526
 
549
  return true;
550
  }
551
 
552
+ /**
553
+ * Attempt to load a module(s)'s file.
554
+ *
555
+ * The file will only be loaded once and will not error if does not exist.
556
+ *
557
+ * @param string $file The file name to load, including extension.
558
+ * @param string|string[] $modules The modules to load the files from. Accepts either a module slug, an array of
559
+ * module slugs, ':all' to load the files from all modules, or ':active' to load the
560
+ * files from active modules.
561
+ *
562
+ * @return bool True if a module matching the $modules parameter is found, false otherwise.
563
+ */
564
  public static function load_module_file( $file, $modules = ':all' ) {
565
  $self = self::get_instance();
566
 
567
  if ( ':all' === $modules ) {
568
  $modules = self::get_available_modules();
569
  } else if ( ':active' === $modules ) {
570
+ if ( ITSEC_Core::is_temp_disable_modules_set() ) {
571
+ $modules = array();
572
+ } else {
573
+ $modules = self::get_active_modules();
574
+ }
575
 
576
  $modules = array_merge( $modules, array_keys( $self->_always_active_modules ) );
577
  $modules = array_unique( $modules );
590
  return true;
591
  }
592
 
593
+ /**
594
+ * Fires an action to begin the registration of modules.
595
+ */
596
  public static function init_modules() {
597
  do_action( 'itsec-register-modules' );
598
  }
599
 
600
+ /**
601
+ * Load and run all active modules.
602
+ */
603
  public static function run_active_modules() {
604
  // The active.php file is for code that will only run when the module is active.
605
  self::load_module_file( 'active.php', ':active' );
606
  }
607
 
608
+ /**
609
+ * Run the activation routine for all registered modules.
610
+ */
611
  public function run_activation() {
612
  self::load_module_file( 'setup.php' );
613
 
614
  do_action( 'itsec_modules_do_plugin_activation' );
615
  }
616
 
617
+ /**
618
+ * Run the deactivation routine for all registered modules.
619
+ */
620
  public function run_deactivation() {
621
  self::load_module_file( 'setup.php' );
622
 
623
  do_action( 'itsec_modules_do_plugin_deactivation' );
624
  }
625
 
626
+ /**
627
+ * Run the uninstall routine for all registered modules.
628
+ */
629
  public static function run_uninstall() {
630
  self::load_module_file( 'setup.php' );
631
 
632
  do_action( 'itsec_modules_do_plugin_uninstall' );
633
  }
634
 
635
+ /**
636
+ * Run the upgrade routine for all registered modules.
637
+ *
638
+ * @param int $old_version
639
+ * @param int $new_version
640
+ */
641
  public function run_upgrade( $old_version, $new_version ) {
642
  self::load_module_file( 'setup.php' );
643
 
644
  do_action( 'itsec_modules_do_plugin_upgrade', $old_version, $new_version );
645
  }
646
 
647
+ /**
648
+ * Load the settings controller for all registered modules.
649
+ *
650
+ * This function can only be run once per-request.
651
+ */
652
  public function load_settings_page() {
653
  if ( $this->_settings_files_loaded ) {
654
  return;
core/modules/404-detection/class-itsec-four-oh-four.php CHANGED
@@ -23,6 +23,7 @@ class ITSEC_Four_Oh_Four {
23
  */
24
  public function check_404() {
25
 
 
26
  global $itsec_logger, $itsec_lockout;
27
 
28
  if ( ! is_404() ) {
23
  */
24
  public function check_404() {
25
 
26
+ /** @var ITSEC_Lockout $itsec_lockout */
27
  global $itsec_logger, $itsec_lockout;
28
 
29
  if ( ! is_404() ) {
core/modules/admin-user/validator.php CHANGED
@@ -139,6 +139,15 @@ final class ITSEC_Admin_User_Validator extends ITSEC_Validator {
139
  $wpdb->query( "UPDATE `" . $wpdb->comments . "` SET user_id = '" . $new_user . "' WHERE user_id = 1;" );
140
  $wpdb->query( "UPDATE `" . $wpdb->links . "` SET link_owner = '" . $new_user . "' WHERE link_owner = 1;" );
141
 
 
 
 
 
 
 
 
 
 
142
  $itsec_files->release_file_lock( 'admin_user' );
143
 
144
  return true;
139
  $wpdb->query( "UPDATE `" . $wpdb->comments . "` SET user_id = '" . $new_user . "' WHERE user_id = 1;" );
140
  $wpdb->query( "UPDATE `" . $wpdb->links . "` SET link_owner = '" . $new_user . "' WHERE link_owner = 1;" );
141
 
142
+ /**
143
+ * Fires when the admin user with id of #1 has been changed.
144
+ *
145
+ * @since 6.3.0
146
+ *
147
+ * @param int $new_user The new user's ID.
148
+ */
149
+ do_action( 'itsec_change_admin_user_id', $new_user );
150
+
151
  $itsec_files->release_file_lock( 'admin_user' );
152
 
153
  return true;
core/modules/away-mode/class-itsec-away-mode.php CHANGED
@@ -51,6 +51,8 @@ final class ITSEC_Away_Mode {
51
  }
52
  }
53
 
 
 
54
  if ( ! $details['has_active_file'] ) {
55
  $details['active'] = false;
56
  $details['remaining'] = false;
@@ -126,7 +128,7 @@ final class ITSEC_Away_Mode {
126
  *
127
  * @since 3.6.0
128
  *
129
- * @param Ithemes_Sync_API Sync API object.
130
  */
131
  public function register_sync_verbs( $api ) {
132
  $api->register( 'itsec-get-away-mode', 'Ithemes_Sync_Verb_ITSEC_Get_Away_Mode', dirname( __FILE__ ) . '/sync-verbs/itsec-get-away-mode.php' );
51
  }
52
  }
53
 
54
+ // If the active file does not exist, completely disable the away mode feature to allow an administrator
55
+ // to regain access to their site.
56
  if ( ! $details['has_active_file'] ) {
57
  $details['active'] = false;
58
  $details['remaining'] = false;
128
  *
129
  * @since 3.6.0
130
  *
131
+ * @param Ithemes_Sync_API $api API object.
132
  */
133
  public function register_sync_verbs( $api ) {
134
  $api->register( 'itsec-get-away-mode', 'Ithemes_Sync_Verb_ITSEC_Get_Away_Mode', dirname( __FILE__ ) . '/sync-verbs/itsec-get-away-mode.php' );
core/modules/away-mode/utilities.php CHANGED
@@ -1,6 +1,12 @@
1
  <?php
2
 
3
  final class ITSEC_Away_Mode_Utilities {
 
 
 
 
 
 
4
  public static function has_active_file() {
5
  if ( @file_exists( self::get_active_file_name() ) ) {
6
  return true;
@@ -9,6 +15,11 @@ final class ITSEC_Away_Mode_Utilities {
9
  }
10
  }
11
 
 
 
 
 
 
12
  public static function create_active_file() {
13
  if ( self::has_active_file() ) {
14
  return true;
@@ -25,6 +36,11 @@ final class ITSEC_Away_Mode_Utilities {
25
  }
26
  }
27
 
 
 
 
 
 
28
  public static function delete_active_file() {
29
  if ( ! self::has_active_file() ) {
30
  return true;
@@ -35,12 +51,26 @@ final class ITSEC_Away_Mode_Utilities {
35
  return @unlink( $file );
36
  }
37
 
 
 
 
 
 
38
  public static function get_active_file_name() {
39
  $file_name = apply_filters( 'itsec_filer_away_mode_active_file', ITSEC_Core::get_storage_dir() . '/itsec_away.confg' );
40
 
41
  return $file_name;
42
  }
43
 
 
 
 
 
 
 
 
 
 
44
  public static function is_current_timestamp_active( $start, $end, $include_details = false ) {
45
  $now = ITSEC_Core::get_current_time_gmt();
46
 
@@ -80,6 +110,15 @@ final class ITSEC_Away_Mode_Utilities {
80
  return compact( 'active', 'remaining', 'next', 'length', 'error' );
81
  }
82
 
 
 
 
 
 
 
 
 
 
83
  public static function is_current_time_active( $start, $end, $include_details = false ) {
84
  $current_time = ITSEC_Core::get_current_time();
85
  $now = $current_time - strtotime( date( 'Y-m-d', $current_time ) );
1
  <?php
2
 
3
  final class ITSEC_Away_Mode_Utilities {
4
+
5
+ /**
6
+ * Check if the config file signaling away mode is active exists.
7
+ *
8
+ * @return bool
9
+ */
10
  public static function has_active_file() {
11
  if ( @file_exists( self::get_active_file_name() ) ) {
12
  return true;
15
  }
16
  }
17
 
18
+ /**
19
+ * Create a config file specifying that away mode is active.
20
+ *
21
+ * @return bool
22
+ */
23
  public static function create_active_file() {
24
  if ( self::has_active_file() ) {
25
  return true;
36
  }
37
  }
38
 
39
+ /**
40
+ * Delete the config file specifying that away mode is active.
41
+ *
42
+ * @return bool
43
+ */
44
  public static function delete_active_file() {
45
  if ( ! self::has_active_file() ) {
46
  return true;
51
  return @unlink( $file );
52
  }
53
 
54
+ /**
55
+ * Get the file name for the config file specifying that away mode is active,
56
+ *
57
+ * @return string
58
+ */
59
  public static function get_active_file_name() {
60
  $file_name = apply_filters( 'itsec_filer_away_mode_active_file', ITSEC_Core::get_storage_dir() . '/itsec_away.confg' );
61
 
62
  return $file_name;
63
  }
64
 
65
+ /**
66
+ * Check if the current UTC time falls between the two specified times, inclusive.
67
+ *
68
+ * @param int $start The UTC timestamp signalling the beginning of the active window.
69
+ * @param int $end The UTC timestamp signalling the end of the active window.
70
+ * @param bool $include_details Whether to include additional details about the active window.
71
+ *
72
+ * @return array|bool
73
+ */
74
  public static function is_current_timestamp_active( $start, $end, $include_details = false ) {
75
  $now = ITSEC_Core::get_current_time_gmt();
76
 
110
  return compact( 'active', 'remaining', 'next', 'length', 'error' );
111
  }
112
 
113
+ /**
114
+ * Check if the current local time falls between the two specified times, inclusive.
115
+ *
116
+ * @param int $start The local timestamp signalling the beginning of the active window.
117
+ * @param int $end The local timestamp signalling the end of the active window.
118
+ * @param bool $include_details Whether to include additional details about the active window.
119
+ *
120
+ * @return array|bool
121
+ */
122
  public static function is_current_time_active( $start, $end, $include_details = false ) {
123
  $current_time = ITSEC_Core::get_current_time();
124
  $now = $current_time - strtotime( date( 'Y-m-d', $current_time ) );
core/modules/backup/class-itsec-backup.php CHANGED
@@ -233,7 +233,7 @@ class ITSEC_Backup {
233
  if ( 1 === $this->settings['method'] ) {
234
  @unlink( $file );
235
  } else if ( $this->settings['retain'] > 0 ) {
236
- $files = scandir( $dir, SCANDIR_SORT_DESCENDING );
237
 
238
  if ( is_array( $files ) && count( $files ) > 0 ) {
239
  $count = 0;
233
  if ( 1 === $this->settings['method'] ) {
234
  @unlink( $file );
235
  } else if ( $this->settings['retain'] > 0 ) {
236
+ $files = scandir( $dir, 1 );
237
 
238
  if ( is_array( $files ) && count( $files ) > 0 ) {
239
  $count = 0;
core/modules/ban-users/lists/hackrepair-apache.inc CHANGED
@@ -125,7 +125,6 @@ RewriteCond %{HTTP_USER_AGENT} "^Xaldon WebSpider" [NC,OR]
125
  RewriteCond %{HTTP_USER_AGENT} "^Zeus" [NC,OR]
126
  RewriteCond %{HTTP_USER_AGENT} "^zmeu" [NC,OR]
127
  RewriteCond %{HTTP_USER_AGENT} "360Spider" [NC,OR]
128
- RewriteCond %{HTTP_USER_AGENT} "AhrefsBot" [NC,OR]
129
  RewriteCond %{HTTP_USER_AGENT} "CazoodleBot" [NC,OR]
130
  RewriteCond %{HTTP_USER_AGENT} "discobot" [NC,OR]
131
  RewriteCond %{HTTP_USER_AGENT} "EasouSpider" [NC,OR]
125
  RewriteCond %{HTTP_USER_AGENT} "^Zeus" [NC,OR]
126
  RewriteCond %{HTTP_USER_AGENT} "^zmeu" [NC,OR]
127
  RewriteCond %{HTTP_USER_AGENT} "360Spider" [NC,OR]
 
128
  RewriteCond %{HTTP_USER_AGENT} "CazoodleBot" [NC,OR]
129
  RewriteCond %{HTTP_USER_AGENT} "discobot" [NC,OR]
130
  RewriteCond %{HTTP_USER_AGENT} "EasouSpider" [NC,OR]
core/modules/ban-users/lists/hackrepair-litespeed.inc CHANGED
@@ -125,7 +125,6 @@ RewriteCond %{HTTP_USER_AGENT} "^Xaldon WebSpider" [NC,OR]
125
  RewriteCond %{HTTP_USER_AGENT} "^Zeus" [NC,OR]
126
  RewriteCond %{HTTP_USER_AGENT} "^zmeu" [NC,OR]
127
  RewriteCond %{HTTP_USER_AGENT} "360Spider" [NC,OR]
128
- RewriteCond %{HTTP_USER_AGENT} "AhrefsBot" [NC,OR]
129
  RewriteCond %{HTTP_USER_AGENT} "CazoodleBot" [NC,OR]
130
  RewriteCond %{HTTP_USER_AGENT} "discobot" [NC,OR]
131
  RewriteCond %{HTTP_USER_AGENT} "EasouSpider" [NC,OR]
125
  RewriteCond %{HTTP_USER_AGENT} "^Zeus" [NC,OR]
126
  RewriteCond %{HTTP_USER_AGENT} "^zmeu" [NC,OR]
127
  RewriteCond %{HTTP_USER_AGENT} "360Spider" [NC,OR]
 
128
  RewriteCond %{HTTP_USER_AGENT} "CazoodleBot" [NC,OR]
129
  RewriteCond %{HTTP_USER_AGENT} "discobot" [NC,OR]
130
  RewriteCond %{HTTP_USER_AGENT} "EasouSpider" [NC,OR]
core/modules/ban-users/lists/hackrepair-nginx.inc CHANGED
@@ -124,7 +124,6 @@ if ($http_user_agent ~* "^Xaldon WebSpider"){return 403;}
124
  if ($http_user_agent ~* "^Zeus"){return 403;}
125
  if ($http_user_agent ~* "^zmeu"){return 403;}
126
  if ($http_user_agent ~* "360Spider"){return 403;}
127
- if ($http_user_agent ~* "AhrefsBot"){return 403;}
128
  if ($http_user_agent ~* "CazoodleBot"){return 403;}
129
  if ($http_user_agent ~* "discobot"){return 403;}
130
  if ($http_user_agent ~* "EasouSpider"){return 403;}
124
  if ($http_user_agent ~* "^Zeus"){return 403;}
125
  if ($http_user_agent ~* "^zmeu"){return 403;}
126
  if ($http_user_agent ~* "360Spider"){return 403;}
 
127
  if ($http_user_agent ~* "CazoodleBot"){return 403;}
128
  if ($http_user_agent ~* "discobot"){return 403;}
129
  if ($http_user_agent ~* "EasouSpider"){return 403;}
core/modules/brute-force/class-itsec-brute-force.php CHANGED
@@ -35,6 +35,8 @@ class ITSEC_Brute_Force {
35
  */
36
  public function authenticate( $user, $username = '', $password = '' ) {
37
 
 
 
38
  global $itsec_lockout, $itsec_logger;
39
 
40
  //Look for the "admin" user name and ban it if it is set to auto-ban
@@ -151,6 +153,7 @@ class ITSEC_Brute_Force {
151
  */
152
  public function wp_login( $username, $user = null ) {
153
 
 
154
  global $itsec_lockout;
155
 
156
  if ( ! $user === null ) {
@@ -177,6 +180,8 @@ class ITSEC_Brute_Force {
177
  * @return void
178
  */
179
  public function handle_failed_login( $username, $details ) {
 
 
180
  global $itsec_lockout, $itsec_logger;
181
 
182
  $user_id = username_exists( $username );
35
  */
36
  public function authenticate( $user, $username = '', $password = '' ) {
37
 
38
+ /** @var ITSEC_Lockout $itsec_lockout */
39
+ /** @var ITSEC_Logger $itsec_logger */
40
  global $itsec_lockout, $itsec_logger;
41
 
42
  //Look for the "admin" user name and ban it if it is set to auto-ban
153
  */
154
  public function wp_login( $username, $user = null ) {
155
 
156
+ /** @var ITSEC_Lockout $itsec_lockout */
157
  global $itsec_lockout;
158
 
159
  if ( ! $user === null ) {
180
  * @return void
181
  */
182
  public function handle_failed_login( $username, $details ) {
183
+
184
+ /** @var ITSEC_Lockout $itsec_lockout */
185
  global $itsec_lockout, $itsec_logger;
186
 
187
  $user_id = username_exists( $username );
core/modules/content-directory/settings-page.php CHANGED
@@ -53,7 +53,7 @@ final class ITSEC_Content_Directory_Settings_Page extends ITSEC_Module_Settings_
53
  <?php if ( ITSEC_Content_Directory_Utility::is_custom_directory() || ITSEC_Content_Directory_Utility::is_modified_by_it_security() ) : ?>
54
  <?php $this->show_current_wp_content_dir(); ?>
55
 
56
- <div class="itsec-warning-message"><?php printf( __( '<span>IMPORTANT:</span> Ensure that you <a href="%s">create a database backup</a> before undoing the Content Directory change.', 'better-wp-security' ), ITSEC_Core::get_backup_creation_page_url() ); ?></div>
57
  <div class="itsec-warning-message"><?php _e( '<span>WARNING:</span> Undoing the Content Directory change when images and other content were added after the change <strong>will break your site</strong>. Only undo the Content Directory change if absolutely necessary.', 'better-wp-security' ); ?></div>
58
 
59
  <table class="form-table itsec-settings-section">
53
  <?php if ( ITSEC_Content_Directory_Utility::is_custom_directory() || ITSEC_Content_Directory_Utility::is_modified_by_it_security() ) : ?>
54
  <?php $this->show_current_wp_content_dir(); ?>
55
 
56
+ <div class="itsec-warning-message"><?php printf( __( '<span>IMPORTANT:</span> Ensure that you <a href="%s" data-module-link="backup">create a database backup</a> before undoing the Content Directory change.', 'better-wp-security' ), ITSEC_Core::get_backup_creation_page_url() ); ?></div>
57
  <div class="itsec-warning-message"><?php _e( '<span>WARNING:</span> Undoing the Content Directory change when images and other content were added after the change <strong>will break your site</strong>. Only undo the Content Directory change if absolutely necessary.', 'better-wp-security' ); ?></div>
58
 
59
  <table class="form-table itsec-settings-section">
core/modules/database-prefix/settings-page.php CHANGED
@@ -18,7 +18,7 @@ final class ITSEC_Database_Prefix_Settings_Page extends ITSEC_Module_Settings_Pa
18
  ?>
19
  <p><?php _e( 'By default, WordPress assigns the prefix <code>wp_</code> to all tables in the database where your content, users, and objects exist. For potential attackers, this means it is easier to write scripts that can target WordPress databases as all the important table names for 95% of sites are already known. Changing the <code>wp_</code> prefix makes it more difficult for tools that are trying to take advantage of vulnerabilities in other places to affect the database of your site. <strong>Before using this tool, we strongly recommend creating a backup of your database.</strong>', 'better-wp-security' ); ?></p>
20
  <p><?php _e( 'Note: The use of this tool requires quite a bit of system memory which may be more than some hosts can handle. If you back your database up you can\'t do any permanent damage but without a proper backup you risk breaking your site and having to perform a rather difficult fix.', 'better-wp-security' ); ?></p>
21
- <div class="itsec-warning-message"><?php printf( __( '<span>WARNING: </span><a href="%1$s">Backup your database</a> before using this tool.', 'better-wp-security' ), ITSEC_Core::get_backup_creation_page_url() ); ?></div>
22
  <?php
23
 
24
  }
18
  ?>
19
  <p><?php _e( 'By default, WordPress assigns the prefix <code>wp_</code> to all tables in the database where your content, users, and objects exist. For potential attackers, this means it is easier to write scripts that can target WordPress databases as all the important table names for 95% of sites are already known. Changing the <code>wp_</code> prefix makes it more difficult for tools that are trying to take advantage of vulnerabilities in other places to affect the database of your site. <strong>Before using this tool, we strongly recommend creating a backup of your database.</strong>', 'better-wp-security' ); ?></p>
20
  <p><?php _e( 'Note: The use of this tool requires quite a bit of system memory which may be more than some hosts can handle. If you back your database up you can\'t do any permanent damage but without a proper backup you risk breaking your site and having to perform a rather difficult fix.', 'better-wp-security' ); ?></p>
21
+ <div class="itsec-warning-message"><?php printf( __( '<span>WARNING: </span><a href="%1$s" data-module-link="backup">Backup your database</a> before using this tool.', 'better-wp-security' ), ITSEC_Core::get_backup_creation_page_url() ); ?></div>
22
  <?php
23
 
24
  }
core/modules/database-prefix/utility.php CHANGED
@@ -51,7 +51,7 @@ final class ITSEC_Database_Prefix_Utility {
51
 
52
  if ( is_wp_error( $config ) ) {
53
  /* translators: 1: Specific error details */
54
- $response['errors'][] = new WP_Error( $confix->get_error_code(), sprintf( __( 'Unable to read the <code>wp-config.php</code> file in order to update the Database Prefix. Error details as follows: %1$s', 'better-wp-security' ), $config->get_error_message() ) );
55
  return $response;
56
  }
57
 
@@ -63,7 +63,7 @@ final class ITSEC_Database_Prefix_Utility {
63
 
64
  if ( is_wp_error( $write_result ) ) {
65
  /* translators: 1: Specific error details */
66
- $response['errors'][] = new WP_Error( $confix->get_error_code(), sprintf( __( 'Unable to update the <code>wp-config.php</code> file in order to update the Database Prefix. Error details as follows: %1$s', 'better-wp-security' ), $config->get_error_message() ) );
67
  return $response;
68
  }
69
 
51
 
52
  if ( is_wp_error( $config ) ) {
53
  /* translators: 1: Specific error details */
54
+ $response['errors'][] = new WP_Error( $config->get_error_code(), sprintf( __( 'Unable to read the <code>wp-config.php</code> file in order to update the Database Prefix. Error details as follows: %1$s', 'better-wp-security' ), $config->get_error_message() ) );
55
  return $response;
56
  }
57
 
63
 
64
  if ( is_wp_error( $write_result ) ) {
65
  /* translators: 1: Specific error details */
66
+ $response['errors'][] = new WP_Error( $write_result->get_error_code(), sprintf( __( 'Unable to update the <code>wp-config.php</code> file in order to update the Database Prefix. Error details as follows: %1$s', 'better-wp-security' ), $config->get_error_message() ) );
67
  return $response;
68
  }
69
 
core/modules/file-change/class-itsec-file-change.php CHANGED
@@ -164,7 +164,7 @@ class ITSEC_File_Change {
164
  *
165
  * @since 3.6.0
166
  *
167
- * @param Ithemes_Sync_API Sync API object.
168
  */
169
  public function register_sync_verbs( $api ) {
170
  $api->register( 'itsec-perform-file-scan', 'Ithemes_Sync_Verb_ITSEC_Perform_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-perform-file-scan.php' );
164
  *
165
  * @since 3.6.0
166
  *
167
+ * @param Ithemes_Sync_API $api Sync API object.
168
  */
169
  public function register_sync_verbs( $api ) {
170
  $api->register( 'itsec-perform-file-scan', 'Ithemes_Sync_Verb_ITSEC_Perform_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-perform-file-scan.php' );
core/modules/global/active.php CHANGED
@@ -10,6 +10,11 @@ function itsec_global_add_notice() {
10
  if ( ITSEC_Modules::get_setting( 'global', 'show_new_dashboard_notice' ) && current_user_can( ITSEC_Core::get_required_cap() ) ) {
11
  ITSEC_Core::add_notice( 'itsec_global_show_new_dashboard_notice' );
12
  }
 
 
 
 
 
13
  }
14
  add_action( 'admin_init', 'itsec_global_add_notice', 0 );
15
 
@@ -54,3 +59,7 @@ function itsec_network_brute_force_dismiss_notice() {
54
  wp_send_json_error();
55
  }
56
  add_action( 'wp_ajax_itsec-dismiss-notice-brute_force_network', 'itsec_network_brute_force_dismiss_notice' );
 
 
 
 
10
  if ( ITSEC_Modules::get_setting( 'global', 'show_new_dashboard_notice' ) && current_user_can( ITSEC_Core::get_required_cap() ) ) {
11
  ITSEC_Core::add_notice( 'itsec_global_show_new_dashboard_notice' );
12
  }
13
+
14
+ if ( ITSEC_Core::is_temp_disable_modules_set() && ITSEC_Core::current_user_can_manage() ) {
15
+ ITSEC_Core::add_notice( 'itsec_show_temp_disable_modules_notice', true );
16
+ }
17
+
18
  }
19
  add_action( 'admin_init', 'itsec_global_add_notice', 0 );
20
 
59
  wp_send_json_error();
60
  }
61
  add_action( 'wp_ajax_itsec-dismiss-notice-brute_force_network', 'itsec_network_brute_force_dismiss_notice' );
62
+
63
+ function itsec_show_temp_disable_modules_notice() {
64
+ ITSEC_Lib::show_error_message( esc_html__( 'The ITSEC_DISABLE_MODULES define is set. All iThemes Security protections are disabled. Please make the necessary settings changes and remove the define as quickly as possible.', 'better-wp-security' ) );
65
+ }
core/modules/global/settings-page.php CHANGED
@@ -248,7 +248,7 @@ final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
248
  <td>
249
  <?php $form->add_checkbox( 'lock_file' ); ?>
250
  <label for="itsec-global-lock_file"><?php _e( 'Disable File Locking', 'better-wp-security' ); ?></label>
251
- <p class="description"><?php _e( 'Turning this option on will prevent errors related to file locking however might result in operations being executed twice. We do not recommend turning this off unless your host prevents the file locking feature from working correctly.', 'better-wp-security' ); ?></p>
252
  </td>
253
  </tr>
254
  <tr>
248
  <td>
249
  <?php $form->add_checkbox( 'lock_file' ); ?>
250
  <label for="itsec-global-lock_file"><?php _e( 'Disable File Locking', 'better-wp-security' ); ?></label>
251
+ <p class="description"><?php _e( 'iThemes Security uses file locking to prevent operations from being executed twice. We do not recommend disabling file locking unless your host prevents it from working correctly.', 'better-wp-security' ); ?></p>
252
  </td>
253
  </tr>
254
  <tr>
core/modules/hide-backend/class-itsec-hide-backend.php CHANGED
@@ -1,357 +1,353 @@
1
  <?php
2
 
3
  class ITSEC_Hide_Backend {
 
 
4
 
5
- private
6
- $settings,
7
- $auth_cookie_expired;
8
-
9
- function run() {
10
 
 
 
 
 
 
 
11
  $this->settings = ITSEC_Modules::get_settings( 'hide-backend' );
12
 
13
- add_filter( 'itsec_filter_apache_server_config_modification', array( $this, 'filter_apache_server_config_modification' ) );
14
- add_filter( 'itsec_filter_litespeed_server_config_modification', array( $this, 'filter_apache_server_config_modification' ) );
15
- add_filter( 'itsec_filter_nginx_server_config_modification', array( $this, 'filter_nginx_server_config_modification' ) );
16
-
17
  if ( ! $this->settings['enabled'] ) {
18
  return;
19
  }
20
 
21
 
22
- $jetpack_active_modules = get_option( 'jetpack_active_modules' );
23
-
24
- if ( is_multisite() && function_exists( 'is_plugin_active_for_network' ) ) { //see if Jetpack is active
25
 
26
- $is_jetpack_active = in_array( 'jetpack/jetpack.php', (array) get_option( 'active_plugins', array() ) ) || is_plugin_active_for_network( 'jetpack/jetpack.php' );
 
 
 
27
 
28
- } else {
29
-
30
- $is_jetpack_active = in_array( 'jetpack/jetpack.php', (array) get_option( 'active_plugins', array() ) );
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
- if (
35
- ! (
36
- $is_jetpack_active === true &&
37
- is_array( $jetpack_active_modules ) &&
38
- in_array( 'json-api', $jetpack_active_modules ) &&
39
- isset( $_GET['action'] ) &&
40
- $_GET['action'] == 'jetpack_json_api_authorization'
41
- )
42
- ) {
43
-
44
- $this->auth_cookie_expired = false;
45
-
46
- add_action( 'auth_cookie_expired', array( $this, 'auth_cookie_expired' ) );
47
- add_action( 'init', array( $this, 'execute_hide_backend' ), 1000 );
48
- add_action( 'login_init', array( $this, 'execute_hide_backend_login' ) );
49
- add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 11 );
50
-
51
- add_filter( 'body_class', array( $this, 'remove_admin_bar' ) );
52
- add_filter( 'loginout', array( $this, 'filter_loginout' ) );
53
- add_filter( 'wp_redirect', array( $this, 'filter_login_url' ), 10, 2 );
54
- add_filter( 'lostpassword_url', array( $this, 'filter_login_url' ), 10, 2 );
55
- add_filter( 'site_url', array( $this, 'filter_login_url' ), 10, 2 );
56
- add_filter( 'retrieve_password_message', array( $this, 'retrieve_password_message' ) );
57
- add_filter( 'comment_moderation_text', array( $this, 'comment_moderation_text' ) );
58
-
59
- remove_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 );
60
-
61
  }
62
 
63
- }
64
-
65
- public function filter_apache_server_config_modification( $modification ) {
66
- require_once( dirname( __FILE__ ) . '/config-generators.php' );
67
-
68
- return ITSEC_Hide_Backend_Config_Generators::filter_apache_server_config_modification( $modification );
69
- }
70
-
71
- public function filter_nginx_server_config_modification( $modification ) {
72
- require_once( dirname( __FILE__ ) . '/config-generators.php' );
73
-
74
- return ITSEC_Hide_Backend_Config_Generators::filter_nginx_server_config_modification( $modification );
75
  }
76
 
77
  /**
78
- * Lets the module know that this is a reauthorization
79
  *
80
- * @since 4.1
 
 
 
 
81
  *
82
  * @return void
83
  */
84
- public function auth_cookie_expired() {
 
 
 
85
 
86
- $this->auth_cookie_expired = true;
87
- wp_clear_auth_cookie();
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
 
91
  /**
92
- * @param $notify_message
93
  *
94
- * @since 4.5
95
- *
96
- * @param sting $notify_message Notification message
97
- *
98
- * @return string Notification message
99
  */
100
- public function comment_moderation_text( $notify_message ) {
101
-
102
- preg_match_all( "#(https?:\/\/((.*)wp-admin(.*)))#", $notify_message, $urls );
103
-
104
- if ( isset( $urls ) && is_array( $urls ) && isset( $urls[0] ) ) {
105
-
106
- foreach ( $urls[0] as $url ) {
107
-
108
- $notify_message = str_replace( trim( $url ), wp_login_url( trim( $url ) ), $notify_message );
109
-
110
- }
111
-
112
  }
113
 
114
- return $notify_message;
115
-
116
  }
117
 
118
  /**
119
- * Execute hide backend functionality
120
- *
121
- * @since 4.0
122
  *
123
  * @return void
124
  */
125
- public function execute_hide_backend() {
126
-
127
- if ( get_site_option( 'users_can_register' ) == 1 && isset( $_SERVER['REQUEST_URI'] ) && $_SERVER['REQUEST_URI'] == ITSEC_Lib::get_home_root() . $this->settings['register'] ) {
128
-
129
- wp_redirect( wp_login_url() . '?action=register' );
130
- exit;
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
- //redirect wp-admin and wp-register.php to 404 when not logged in
135
- if (
136
- (
137
- (
138
- get_site_option( 'users_can_register' ) == false &&
139
- (
140
- isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], 'wp-register.php' ) ||
141
- isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], 'wp-signup.php' )
142
- )
143
- ) ||
144
- (
145
- isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) && is_user_logged_in() !== true
146
- ) ||
147
- ( is_admin() && is_user_logged_in() !== true ) ||
148
- (
149
- $this->settings['register'] != 'wp-register.php' &&
150
- strpos( $_SERVER['REQUEST_URI'], 'wp-register.php' ) !== false ||
151
- strpos( $_SERVER['REQUEST_URI'], 'wp-signup.php' ) !== false ||
152
- (
153
- isset( $_REQUEST['redirect_to'] ) &&
154
- strpos( $_REQUEST['redirect_to'], 'wp-admin/customize.php' ) !== false
155
-
156
- )
157
- )
158
- ) &&
159
- strpos( $_SERVER['REQUEST_URI'], 'admin-ajax.php' ) === false
160
- && $this->auth_cookie_expired === false
161
- ) {
162
-
163
- global $itsec_is_old_admin;
164
-
165
- $itsec_is_old_admin = true;
166
-
167
- if ( isset( $this->settings['theme_compat'] ) && $this->settings['theme_compat'] === true ) { //theme compat (process theme and redirect to a 404)
168
-
169
- wp_redirect( ITSEC_Lib::get_home_root() . sanitize_title( isset( $this->settings['theme_compat_slug'] ) ? $this->settings['theme_compat_slug'] : 'not_found' ), 302 );
170
- exit;
171
 
 
 
 
 
 
 
 
 
 
172
  } else {
173
-
174
- // Throw a 403 forbidden
175
- wp_die( __( 'This has been disabled.', 'better-wp-security' ), 403 );
176
-
177
  }
178
-
179
  }
 
180
 
181
- $url_info = parse_url( $_SERVER['REQUEST_URI'] );
182
- $login_path = site_url( $this->settings['slug'], 'relative' );
183
- $login_path_trailing_slash = site_url( $this->settings['slug'] . '/', 'relative' );
184
-
185
- if ( $url_info['path'] === $login_path || $url_info['path'] === $login_path_trailing_slash ) {
186
-
187
- if ( ! is_user_logged_in() ) {
188
- //Add the login form
189
-
190
- if ( isset( $this->settings['post_logout_slug'] ) && strlen( trim( $this->settings['post_logout_slug'] ) ) > 0 && isset( $_GET['action'] ) && sanitize_text_field( $_GET['action'] ) == trim( $this->settings['post_logout_slug'] ) ) {
191
- do_action( 'itsec_custom_login_slug' ); //add hook here for custom users
192
- }
193
-
194
- //suppress error messages due to timing
195
- error_reporting( 0 );
196
- @ini_set( 'display_errors', 0 );
197
-
198
- status_header( 200 );
199
-
200
- //don't allow domain mapping to redirect
201
- if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING == 1 ) {
202
- remove_action( 'login_head', 'redirect_login_to_orig' );
203
- }
204
-
205
- if ( ! function_exists( 'login_header' ) ) {
206
-
207
- include( ABSPATH . 'wp-login.php' );
208
- exit;
209
-
210
- }
211
-
212
- } elseif ( ! isset( $_GET['action'] ) || ( sanitize_text_field( $_GET['action'] ) != 'logout' && sanitize_text_field( $_GET['action'] ) != 'postpass' && ( isset( $this->settings['post_logout_slug'] ) && strlen( trim( $this->settings['post_logout_slug'] ) ) > 0 && sanitize_text_field( $_GET['action'] ) != trim( $this->settings['post_logout_slug'] ) ) ) ) {
213
- //Just redirect them to the dashboard (for logged in users)
214
-
215
- if ( $this->auth_cookie_expired === false ) {
216
-
217
- wp_redirect( get_admin_url() );
218
- exit();
219
-
220
- }
221
-
222
- } elseif ( isset( $_GET['action'] ) && ( sanitize_text_field( $_GET['action'] ) == 'postpass' || ( isset( $this->settings['post_logout_slug'] ) && strlen( trim( $this->settings['post_logout_slug'] ) ) > 0 && sanitize_text_field( $_GET['action'] ) == trim( $this->settings['post_logout_slug'] ) ) ) ) {
223
- //handle private posts for
224
-
225
- if ( isset( $this->settings['post_logout_slug'] ) && strlen( trim( $this->settings['post_logout_slug'] ) ) > 0 && sanitize_text_field( $_GET['action'] ) == trim( $this->settings['post_logout_slug'] ) ) {
226
- do_action( 'itsec_custom_login_slug' ); //add hook here for custom users
227
- }
228
-
229
- //suppress error messages due to timing
230
- error_reporting( 0 );
231
- @ini_set( 'display_errors', 0 );
232
-
233
- status_header( 200 ); //its a good login page. make sure we say so
234
-
235
- //include the login page where we need it
236
- if ( ! function_exists( 'login_header' ) ) {
237
- include( ABSPATH . '/wp-login.php' );
238
- exit;
239
- }
240
-
241
- //Take them back to the page if we need to
242
- if ( isset( $_SERVER['HTTP_REFERRER'] ) ) {
243
- wp_redirect( sanitize_text_field( $_SERVER['HTTP_REFERRER'] ) );
244
- exit();
245
- }
246
 
247
- }
 
 
 
 
 
 
248
 
 
 
 
249
  }
250
 
 
251
  }
252
 
253
  /**
254
- * Filter the old login page out
 
 
255
  *
256
  * @return void
257
  */
258
- public function execute_hide_backend_login() {
 
 
 
259
 
260
- if ( strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) ) { //are we on the login page
 
 
 
 
 
 
 
 
261
 
262
- global $itsec_is_old_admin;
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
264
- $itsec_is_old_admin = true;
 
 
 
265
 
266
- ITSEC_Lib::set_404();
 
267
 
 
 
 
 
268
  }
269
 
 
 
270
  }
271
 
272
  /**
273
- * Filters redirects for correct login URL
274
- *
275
- * @since 4.0
276
  *
277
- * @param string $url URL redirecting to
 
278
  *
279
- * @return string Correct redirect URL
280
  */
281
- public function filter_login_url( $url ) {
 
 
 
 
 
282
 
283
- return str_replace( 'wp-login.php', $this->settings['slug'], $url );
 
 
 
 
 
 
 
 
 
 
 
284
 
 
285
  }
286
 
287
  /**
288
- * Filter meta link
289
  *
290
- * @since 4.2
291
  *
292
- * @param string $link the link
293
- *
294
- * @return string the link
295
  */
296
- public function filter_loginout( $link ) {
297
-
298
- return str_replace( 'wp-login.php', $this->settings['slug'], $link );
299
-
300
  }
301
 
302
  /**
303
- * Actions for plugins loaded.
304
  *
305
- * Makes certain logout is processed on NGINX.
 
306
  *
307
- * @return void
308
  */
309
- public function plugins_loaded() {
 
310
 
311
- if ( is_user_logged_in() && isset( $_GET['action'] ) && sanitize_text_field( $_GET['action'] ) == 'logout' ) {
 
312
 
313
- check_admin_referer( 'log-out' );
314
- wp_logout();
315
-
316
- $redirect_to = ! empty( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : 'wp-login.php?loggedout=true';
317
- wp_safe_redirect( $redirect_to );
318
- exit();
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  }
321
-
322
  }
323
 
324
  /**
325
- * Removes the admin bar class from the body tag
326
  *
327
- * @param array $classes body tag classes
 
328
  *
329
- * @return array body tag classes
330
  */
331
- public function remove_admin_bar( $classes ) {
332
-
333
- if ( is_admin() && is_user_logged_in() !== true ) {
334
-
335
- foreach ( $classes as $key => $value ) {
336
-
337
- if ( $value == 'admin-bar' ) {
338
- unset( $classes[ $key ] );
339
- }
340
-
341
- }
342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  }
344
 
345
- return $classes;
346
-
347
  }
348
 
349
- public function retrieve_password_message( $message ) {
350
-
351
- return str_replace( 'wp-login.php', $this->settings['slug'], $message );
352
-
353
- return $message;
 
 
 
 
 
 
354
 
 
355
  }
356
-
357
  }
1
  <?php
2
 
3
  class ITSEC_Hide_Backend {
4
+ private $disable_filters = false;
5
+ private $token_var = 'itsec-hb-token';
6
 
7
+ private $settings;
 
 
 
 
8
 
9
+ /**
10
+ * Bootstrap Hide Backend functionality if the module is active.
11
+ *
12
+ * @return void
13
+ */
14
+ public function run() {
15
  $this->settings = ITSEC_Modules::get_settings( 'hide-backend' );
16
 
 
 
 
 
17
  if ( ! $this->settings['enabled'] ) {
18
  return;
19
  }
20
 
21
 
22
+ add_action( 'init', array( $this, 'handle_specific_page_requests' ), 1000 );
23
+ add_action( 'signup_hidden_fields', array( $this, 'add_token_to_registration_form' ) );
 
24
 
25
+ add_filter( 'site_url', array( $this, 'filter_generated_url' ), 100, 2 );
26
+ add_filter( 'network_site_url', array( $this, 'filter_generated_url' ), 100, 2 );
27
+ add_filter( 'wp_redirect', array( $this, 'filter_redirect' ) );
28
+ add_filter( 'comment_moderation_text', array( $this, 'filter_comment_moderation_text' ) );
29
 
30
+ remove_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 );
31
+ }
 
32
 
33
+ /**
34
+ * Filters emailed comment moderation links to use modified login links with redirection.
35
+ *
36
+ * Comment moderation links link directly to wp-admin pages. Since direct requests to wp-admin are blocked by Hide
37
+ * Backend, these links are updated to link to the login page with a redirect to the wp-admin page.
38
+ *
39
+ * @since 4.5
40
+ *
41
+ * @param string $text Comment moderation email text.
42
+ *
43
+ * @return string Comment moderation email text.
44
+ */
45
+ public function filter_comment_moderation_text( $text ) {
46
+ if ( $this->disable_filters ) {
47
+ return $location;
48
  }
49
 
50
+ // The email is plain text and the links are at the end of lines, so a lazy match can be used.
51
+ if ( preg_match_all( '|(https?:\/\/((.*)wp-admin(.*)))|', $text, $urls ) ) {
52
+ foreach ( $urls[0] as $url ) {
53
+ $url = trim( $url );
54
+ $text = str_replace( $url, wp_login_url( $url ), $text );
55
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
+ return $text;
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
  /**
62
+ * Ensure that login and registration pages and their aliases are handled properly.
63
  *
64
+ * This function is responsible for identifying if the current page request is for wp-login.php, wp-signup.php, a
65
+ * canonical alias for one of those pages, a wp-admin request, or one of Hide Backend's replacements pages. If a
66
+ * matching page page is found, the appropriate function is called to handle the rest of the processing.
67
+ *
68
+ * @since 4.0
69
  *
70
  * @return void
71
  */
72
+ public function handle_specific_page_requests() {
73
+ if ( ITSEC_Core::is_api_request() ) {
74
+ return;
75
+ }
76
 
77
+ $request_path = ITSEC_Lib::get_request_path();
 
78
 
79
+ if ( $request_path === $this->settings['slug'] ) {
80
+ $this->handle_login_alias();
81
+ } else if ( in_array( $request_path, array( 'wp-login', 'wp-login.php' ) ) ) {
82
+ $this->handle_canonical_login_page();
83
+ } else if ( 'wp-admin' === $request_path || 'wp-admin/' === substr( $request_path, 0, 9 ) ) {
84
+ $this->handle_wp_admin_page();
85
+ } else if ( 'wp-signup.php' === $this->settings['register'] ) {
86
+ // Only "hide" the signup page if a different slug was chosen for it.
87
+ return;
88
+ } else if ( $request_path === $this->settings['register'] ) {
89
+ $this->handle_registration_alias();
90
+ } else if ( 'wp-signup.php' === $request_path ) {
91
+ $this->handle_canonical_signup_page();
92
+ }
93
  }
94
 
95
  /**
96
+ * Handle a request for the Hide Backend replacement login page slug.
97
  *
98
+ * @return void
 
 
 
 
99
  */
100
+ private function handle_login_alias() {
101
+ if ( isset( $_GET['action'] ) && $_GET['action'] === trim( $this->settings['post_logout_slug'] ) ) {
102
+ // I'm not sure if this feature is still needed or if anyone still uses it. - Chris
103
+ do_action( 'itsec_custom_login_slug' );
 
 
 
 
 
 
 
 
104
  }
105
 
106
+ $this->do_redirect_with_token( 'login', 'wp-login.php' );
 
107
  }
108
 
109
  /**
110
+ * Handle a request for wp-login.php or a canonical alias for it.
 
 
111
  *
112
  * @return void
113
  */
114
+ private function handle_canonical_login_page() {
115
+ $action = isset( $_GET['action'] ) ? $_GET['action'] : '';
 
 
 
 
116
 
117
+ if ( 'postpass' === $action ) {
118
+ return;
119
+ } else if ( 'register' === $action ) {
120
+ $this->block_access( 'register' );
121
+ return;
122
+ } else if ( 'jetpack_json_api_authorization' === $action && has_filter( 'login_form_jetpack_json_api_authorization' ) ) {
123
+ // Jetpack handles authentication for this action. Processing is left to it.
124
+ return;
125
+ } else if ( 'jetpack-sso' === $action && has_filter( 'login_form_jetpack-sso' ) ) {
126
+ // Jetpack's SSO redirects from wordpress.com to wp-login.php on the site. Only allow this process to
127
+ // continue if they successfully log in, which should happen by login_init in Jetpack which happens just
128
+ // before this action fires.
129
+ add_action( 'login_form_jetpack-sso', array( $this, 'block_access' ) );
130
+ return;
131
  }
132
 
133
+ $this->block_access( 'login' );
134
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
+ /**
137
+ * Handle a request for the Hide Backend replacement register page slug.
138
+ *
139
+ * @return void
140
+ */
141
+ private function handle_registration_alias() {
142
+ if ( get_option( 'users_can_register' ) ) {
143
+ if ( is_multisite() ) {
144
+ $this->do_redirect_with_token( 'register', 'wp-signup.php' );
145
  } else {
146
+ $this->do_redirect_with_token( 'register', 'wp-login.php?action=register' );
 
 
 
147
  }
 
148
  }
149
+ }
150
 
151
+ /**
152
+ * Handle a request for wp-signup.php.
153
+ *
154
+ * @return void
155
+ */
156
+ private function handle_canonical_signup_page() {
157
+ $this->block_access( 'register' );
158
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ /**
161
+ * Handle a request for any wp-admin directory request.
162
+ *
163
+ * @return void
164
+ */
165
+ private function handle_wp_admin_page() {
166
+ $request_path = ITSEC_Lib::get_request_path();
167
 
168
+ if ( 'wp-admin/maint/repair.php' === $request_path && defined( 'WP_ALLOW_REPAIR' ) ) {
169
+ // Make sure to only allow access if the page would function.
170
+ return;
171
  }
172
 
173
+ $this->block_access( 'login' );
174
  }
175
 
176
  /**
177
+ * Block access to the page if the visitor is not a logged in user and the request fails validation.
178
+ *
179
+ * @param string $type The type of request to be validated.
180
  *
181
  * @return void
182
  */
183
+ public function block_access( $type = 'login' ) {
184
+ if ( is_user_logged_in() || $this->is_validated( $type ) ) {
185
+ return;
186
+ }
187
 
188
+ if ( $this->settings['theme_compat'] ) {
189
+ // The "Enable Redirection" setting is enabled. Redirect to the "Redirection Slug" setting.
190
+ wp_redirect( ITSEC_Lib::get_home_root() . $this->settings['theme_compat_slug'], 302 );
191
+ exit;
192
+ } else {
193
+ // The "Enable Redirection" setting is disabled. Return a 403 error.
194
+ wp_die( __( 'This has been disabled.', 'better-wp-security' ), 403 );
195
+ }
196
+ }
197
 
198
+ /**
199
+ * Redirect to requested path with the token query arg added to ensure that the redirected request is validated.
200
+ *
201
+ * This function will also set an appropriate cookie when doing the redirect. The presence of the cookie and query
202
+ * arg should ensure that the redirect request validates properly.
203
+ *
204
+ * @param string $type The type of request to add an access token for.
205
+ * @param string $path The path to redirect to.
206
+ *
207
+ * @return void
208
+ */
209
+ private function do_redirect_with_token( $type, $path ) {
210
+ // Set the cookie so that access via unknown integrations works more smoothly.
211
+ $this->set_cookie( $type );
212
 
213
+ // Preserve existing query vars and add access token query arg.
214
+ $query_vars = $_GET;
215
+ $query_vars[$this->token_var] = $this->get_access_token( $type );
216
+ $query = http_build_query( $query_vars, null, '&' );
217
 
218
+ // Disable the Hide Backend URL filters to prevent infinite loops when calling site_url().
219
+ $this->disable_filters = true;
220
 
221
+ if ( false === strpos( $path, '?' ) ) {
222
+ $url = site_url( "$path?$query" );
223
+ } else {
224
+ $url = site_url( "$path&$query" );
225
  }
226
 
227
+ wp_redirect( $url );
228
+ exit;
229
  }
230
 
231
  /**
232
+ * Filter generated login and signup URLs to include the access token query arg.
 
 
233
  *
234
+ * @param string $url The complete URL to be filtered.
235
+ * @param string $path The path submitted by the originating function call.
236
  *
237
+ * @return string The complete URL with conditionally added access token query arg.
238
  */
239
+ public function filter_generated_url( $url, $path ) {
240
+ if ( $this->disable_filters ) {
241
+ return $url;
242
+ }
243
+
244
+ list( $clean_path ) = explode( '?', $path );
245
 
246
+ if ( 'wp-login.php' === $clean_path && 'wp-login.php' !== $this->settings['slug'] ) {
247
+ if ( false !== strpos( $path, 'action=postpass' ) ) {
248
+ // No special handling is needed for a password-protected post.
249
+ return $url;
250
+ } else if ( false !== strpos( $path, 'action=register' ) ) {
251
+ $url = $this->add_token_to_url( $url, 'register' );
252
+ } else {
253
+ $url = $this->add_token_to_url( $url, 'login' );
254
+ }
255
+ } else if ( 'wp-signup.php' === $clean_path && 'wp-signup.php' !== $this->settings['register'] ) {
256
+ $url = $this->add_token_to_url( $url, 'register' );
257
+ }
258
 
259
+ return $url;
260
  }
261
 
262
  /**
263
+ * Filter redirection URLs to login and signup pages to include the access token query arg.
264
  *
265
+ * @param string $location The relative path to redirect to.
266
  *
267
+ * @return string The location with conditionally added access token query arg.
 
 
268
  */
269
+ public function filter_redirect( $location ) {
270
+ return $this->filter_generated_url( $location, $location );
 
 
271
  }
272
 
273
  /**
274
+ * Add the access token query arg to the URL.
275
  *
276
+ * @param string $url The URL to modify.
277
+ * @param string $type The type of request to add an access token for.
278
  *
279
+ * @return string The URL with the added access token query arg.
280
  */
281
+ private function add_token_to_url( $url, $type ) {
282
+ $token = $this->get_access_token( $type );
283
 
284
+ $url .= ( false === strpos( $url, '?' ) ) ? '?' : '&';
285
+ $url .= $this->token_var . '=' . urlencode( $token );
286
 
287
+ return $url;
288
+ }
 
 
 
 
289
 
290
+ /**
291
+ * Add a hidden input containing the appropriate access token name and value.
292
+ *
293
+ * This function is only used on multisite user signup pages. It is needed since the code that generates the form on
294
+ * that page does not use site_url() or network_site_url() to generate a full URL for form's action URL.
295
+ *
296
+ * @param string $context The type of signup form being rendered.
297
+ *
298
+ * @return null
299
+ */
300
+ public function add_token_to_registration_form( $context ) {
301
+ if ( 'validate-user' === $context ) {
302
+ echo '<input type="hidden" name="' . esc_attr( $this->token_var ) . '" value="' . esc_attr( $this->get_access_token( 'register' ) ) . '" />' . "\n";
303
  }
 
304
  }
305
 
306
  /**
307
+ * Creates a cookie to validate future requests.
308
  *
309
+ * @param string $type The type of request to add an access token for.
310
+ * @param int $duration Number of seconds that the key will be valid.
311
  *
312
+ * @return null
313
  */
314
+ private function set_cookie( $type, $duration = 3600 /* 1 hour */ ) {
315
+ $expires = time() + $duration;
316
+ setcookie( "itsec-hb-$type-" . COOKIEHASH, $this->get_access_token( $type ), $expires, ITSEC_Lib::get_home_root(), COOKIE_DOMAIN, is_ssl(), true );
317
+ }
 
 
 
 
 
 
 
318
 
319
+ /**
320
+ * Checks to see if a cookie or query arg value validates the current request for the type being checked.
321
+ *
322
+ * @param string $type The type of request to add an access token to validate.
323
+ *
324
+ * @return bool true if the request is validated, false otherwise.
325
+ */
326
+ private function is_validated( $type ) {
327
+ $token = $this->get_access_token( $type );
328
+
329
+ if ( isset( $_REQUEST[$this->token_var] ) && $_REQUEST[$this->token_var] === $token ) {
330
+ $this->set_cookie( $type );
331
+ return true;
332
+ } else if ( isset( $_COOKIE["itsec-hb-$type-" . COOKIEHASH] ) && $_COOKIE["itsec-hb-$type-" . COOKIEHASH] === $token ) {
333
+ return true;
334
  }
335
 
336
+ return false;
 
337
  }
338
 
339
+ /**
340
+ * The access token to use for the specific request.
341
+ *
342
+ * @param string $type The type of request to create an access token for.
343
+ *
344
+ * @return string The access token.
345
+ */
346
+ private function get_access_token( $type ) {
347
+ if ( isset( $this->settings[$type] ) ) {
348
+ return $this->settings[$type];
349
+ }
350
 
351
+ return $this->settings['slug'];
352
  }
 
353
  }
core/modules/hide-backend/config-generators.php DELETED
@@ -1,43 +0,0 @@
1
- <?php
2
-
3
- final class ITSEC_Hide_Backend_Config_Generators {
4
- public static function filter_apache_server_config_modification( $modification ) {
5
- $settings = ITSEC_Modules::get_settings( 'hide-backend' );
6
-
7
- if ( ! $settings['enabled'] ) {
8
- return $modification;
9
- }
10
-
11
- $home_root = ITSEC_Lib::get_home_root();
12
-
13
- $modification .= "\n";
14
- $modification .= "\t# " . __( 'Enable the hide backend feature - Security > Settings > Hide Login Area > Hide Backend', 'better-wp-security' ) . "\n";
15
- $modification .= "\tRewriteRule ^($home_root)?{$settings['slug']}/?$ {$home_root}wp-login.php [QSA,L]\n";
16
-
17
- if ( 'wp-register.php' != $settings['register'] ) {
18
- $modification .= "\tRewriteRule ^($home_root)?{$settings['register']}/?$ /wplogin?action=register [QSA,L]\n";
19
- }
20
-
21
- return $modification;
22
- }
23
-
24
- public static function filter_nginx_server_config_modification( $modification ) {
25
- $settings = ITSEC_Modules::get_settings( 'hide-backend' );
26
-
27
- if ( ! $settings['enabled'] ) {
28
- return $modification;
29
- }
30
-
31
- $home_root = ITSEC_Lib::get_home_root();
32
-
33
- $modification .= "\n";
34
- $modification .= "\t# " . __( 'Enable the hide backend feature - Security > Settings > Hide Login Area > Hide Backend', 'better-wp-security' ) . "\n";
35
- $modification .= "\trewrite ^($home_root)?{$settings['slug']}/?$ {$home_root}wp-login.php?\$query_string break;\n";
36
-
37
- if ( 'wp-register.php' != $settings['register'] ) {
38
- $modification .= "\trewrite ^($home_root)?{$settings['register']}/?$ {$home_root}{$settings['slug']}?action=register break;\n";
39
- }
40
-
41
- return $modification;
42
- }
43
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/modules/hide-backend/settings-page.php CHANGED
@@ -84,13 +84,13 @@ final class ITSEC_Hide_Backend_Settings_Page extends ITSEC_Module_Settings_Page
84
  <p class="description"><em><?php _e( 'Note: The output is limited to alphanumeric characters, underscore (_) and dash (-). Special characters such as "." and "/" are not allowed and will be converted in the same manner as a post title. Please review your selection before logging out.', 'better-wp-security' ); ?></em></p>
85
  </td>
86
  </tr>
87
- <?php if ( get_site_option( 'users_can_register' ) ) : ?>
88
  <tr>
89
  <th scope="row"><label for="itsec-hide-backend-register"><?php _e( 'Register Slug', 'better-wp-security' ); ?></label></th>
90
  <td>
91
  <?php $form->add_text( 'register', array( 'class' => 'text code' ) ); ?>
92
  <br />
93
- <label for="itsec-hide-backend-register"><?php printf( __( 'Registration URL: %s', 'better-wp-security' ), trailingslashit( get_option( 'siteurl' ) ) . '<span style="color: #4AA02C">' . sanitize_title( $settings['register'] ) . '</span>' ); ?></label>
94
  </td>
95
  </tr>
96
  <?php endif; ?>
84
  <p class="description"><em><?php _e( 'Note: The output is limited to alphanumeric characters, underscore (_) and dash (-). Special characters such as "." and "/" are not allowed and will be converted in the same manner as a post title. Please review your selection before logging out.', 'better-wp-security' ); ?></em></p>
85
  </td>
86
  </tr>
87
+ <?php if ( get_option( 'users_can_register' ) ) : ?>
88
  <tr>
89
  <th scope="row"><label for="itsec-hide-backend-register"><?php _e( 'Register Slug', 'better-wp-security' ); ?></label></th>
90
  <td>
91
  <?php $form->add_text( 'register', array( 'class' => 'text code' ) ); ?>
92
  <br />
93
+ <label for="itsec-hide-backend-register"><?php printf( __( 'Registration URL: %s', 'better-wp-security' ), trailingslashit( get_option( 'siteurl' ) ) . '<span style="color: #4AA02C">' . esc_html( $settings['register'] ) . '</span>' ); ?></label>
94
  </td>
95
  </tr>
96
  <?php endif; ?>
core/modules/hide-backend/settings.php CHANGED
@@ -4,12 +4,12 @@ final class ITSEC_Hide_Backend_Settings extends ITSEC_Settings {
4
  public function get_id() {
5
  return 'hide-backend';
6
  }
7
-
8
  public function get_defaults() {
9
  return array(
10
  'enabled' => false,
11
  'slug' => 'wplogin',
12
- 'register' => 'wp-register.php',
13
  'theme_compat' => true,
14
  'theme_compat_slug' => 'not_found',
15
  'post_logout_slug' => '',
4
  public function get_id() {
5
  return 'hide-backend';
6
  }
7
+
8
  public function get_defaults() {
9
  return array(
10
  'enabled' => false,
11
  'slug' => 'wplogin',
12
+ 'register' => 'wp-signup.php',
13
  'theme_compat' => true,
14
  'theme_compat_slug' => 'not_found',
15
  'post_logout_slug' => '',
core/modules/hide-backend/setup.php CHANGED
@@ -64,7 +64,7 @@ if ( ! class_exists( 'ITSEC_Hide_Backend_Setup' ) ) {
64
  if ( false !== $current_options ) {
65
 
66
  $current_options['enabled'] = isset( $itsec_bwps_options['hb_enabled'] ) && $itsec_bwps_options['hb_enabled'] == 1 ? true : false;
67
- $current_options['register'] = isset( $itsec_bwps_options['hb_register'] ) ? sanitize_text_field( $itsec_bwps_options['hb_register'] ) : 'wp-register.php';
68
 
69
  if ( $current_options['enabled'] === true ) {
70
 
@@ -124,6 +124,10 @@ if ( ! class_exists( 'ITSEC_Hide_Backend_Setup' ) ) {
124
  if ( $itsec_old_version < 4070 ) {
125
  delete_site_option( 'itsec_hide_backend' );
126
  }
 
 
 
 
127
  }
128
 
129
  /**
64
  if ( false !== $current_options ) {
65
 
66
  $current_options['enabled'] = isset( $itsec_bwps_options['hb_enabled'] ) && $itsec_bwps_options['hb_enabled'] == 1 ? true : false;
67
+ $current_options['register'] = isset( $itsec_bwps_options['hb_register'] ) ? sanitize_text_field( $itsec_bwps_options['hb_register'] ) : 'wp-signup.php';
68
 
69
  if ( $current_options['enabled'] === true ) {
70
 
124
  if ( $itsec_old_version < 4070 ) {
125
  delete_site_option( 'itsec_hide_backend' );
126
  }
127
+
128
+ if ( $itsec_old_version < 4072 ) {
129
+ ITSEC_Response::regenerate_server_config();
130
+ }
131
  }
132
 
133
  /**
core/modules/hide-backend/validator.php CHANGED
@@ -21,10 +21,11 @@ final class ITSEC_Hide_Backend_Validator extends ITSEC_Validator {
21
 
22
  if ( ! isset( $this->settings['register'] ) ) {
23
  $this->settings['register'] = $this->previous_settings['register'];
 
 
24
  }
25
 
26
  $this->sanitize_setting( 'non-empty-title', 'slug', __( 'Login Slug', 'better-wp-security' ) );
27
- $this->sanitize_setting( 'non-empty-title', 'register', __( 'Register Slug', 'better-wp-security' ) );
28
  $this->sanitize_setting( 'bool', 'theme_compat', __( 'Enable Redirection', 'better-wp-security' ) );
29
  $this->sanitize_setting( 'non-empty-title', 'theme_compat_slug', __( 'Redirection Slug', 'better-wp-security' ) );
30
  $this->sanitize_setting( 'title', 'post_logout_slug', __( 'Custom Login Action', 'better-wp-security' ) );
@@ -61,14 +62,6 @@ final class ITSEC_Hide_Backend_Validator extends ITSEC_Validator {
61
  ITSEC_Response::prevent_modal_close();
62
  }
63
 
64
- if (
65
- $this->settings['enabled'] !== $this->previous_settings['enabled'] ||
66
- $this->settings['slug'] !== $this->previous_settings['slug'] ||
67
- $this->settings['register'] !== $this->previous_settings['register']
68
- ) {
69
- ITSEC_Response::regenerate_server_config();
70
- }
71
-
72
 
73
  ITSEC_Response::reload_module( $this->get_id() );
74
  }
21
 
22
  if ( ! isset( $this->settings['register'] ) ) {
23
  $this->settings['register'] = $this->previous_settings['register'];
24
+ } else if ( 'wp-signup.php' !== $this->settings['register'] ) {
25
+ $this->sanitize_setting( 'non-empty-title', 'register', __( 'Register Slug', 'better-wp-security' ) );
26
  }
27
 
28
  $this->sanitize_setting( 'non-empty-title', 'slug', __( 'Login Slug', 'better-wp-security' ) );
 
29
  $this->sanitize_setting( 'bool', 'theme_compat', __( 'Enable Redirection', 'better-wp-security' ) );
30
  $this->sanitize_setting( 'non-empty-title', 'theme_compat_slug', __( 'Redirection Slug', 'better-wp-security' ) );
31
  $this->sanitize_setting( 'title', 'post_logout_slug', __( 'Custom Login Action', 'better-wp-security' ) );
62
  ITSEC_Response::prevent_modal_close();
63
  }
64
 
 
 
 
 
 
 
 
 
65
 
66
  ITSEC_Response::reload_module( $this->get_id() );
67
  }
core/modules/ipcheck/class-itsec-ipcheck.php CHANGED
@@ -226,6 +226,8 @@ class ITSEC_IPCheck {
226
  * @return void
227
  */
228
  public function wp_login() {
 
 
229
  global $itsec_logger, $itsec_lockout;
230
 
231
  $this->load_settings();
@@ -244,6 +246,8 @@ class ITSEC_IPCheck {
244
  * @return void
245
  */
246
  public function handle_failed_login( $username, $details ) {
 
 
247
  global $itsec_logger, $itsec_lockout;
248
 
249
  $this->load_settings();
226
  * @return void
227
  */
228
  public function wp_login() {
229
+
230
+ /** @var ITSEC_Lockout $itsec_lockout */
231
  global $itsec_logger, $itsec_lockout;
232
 
233
  $this->load_settings();
246
  * @return void
247
  */
248
  public function handle_failed_login( $username, $details ) {
249
+
250
+ /** @var ITSEC_Lockout $itsec_lockout */
251
  global $itsec_logger, $itsec_lockout;
252
 
253
  $this->load_settings();
core/modules/malware/class-itsec-malware-scanner.php CHANGED
@@ -63,7 +63,8 @@ final class ITSEC_Malware_Scanner {
63
  $scan_url = "$scanner_url?" . http_build_query( $query_args, '', '&' );
64
 
65
  $req_args = array(
66
- 'timeout' => 300,
 
67
  );
68
 
69
  if ( defined( 'ITSEC_TEST_MALWARE_SCAN_DISABLE_SSLVERIFY' ) && ITSEC_TEST_MALWARE_SCAN_DISABLE_SSLVERIFY ) {
63
  $scan_url = "$scanner_url?" . http_build_query( $query_args, '', '&' );
64
 
65
  $req_args = array(
66
+ 'connect_timeout' => 30, // Placeholder for when WordPress implements support.
67
+ 'timeout' => 300,
68
  );
69
 
70
  if ( defined( 'ITSEC_TEST_MALWARE_SCAN_DISABLE_SSLVERIFY' ) && ITSEC_TEST_MALWARE_SCAN_DISABLE_SSLVERIFY ) {
core/modules/malware/class-itsec-malware.php CHANGED
@@ -16,7 +16,7 @@ class ITSEC_Malware {
16
  *
17
  * @since 3.6.0
18
  *
19
- * @param Ithemes_Sync_API Sync API object.
20
  */
21
  public function register_sync_verbs( $api ) {
22
  $api->register( 'itsec-do-malware-scan', 'Ithemes_Sync_Verb_ITSEC_Malware_Do_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-do-malware-scan.php' );
16
  *
17
  * @since 3.6.0
18
  *
19
+ * @param Ithemes_Sync_API $api Sync API object.
20
  */
21
  public function register_sync_verbs( $api ) {
22
  $api->register( 'itsec-do-malware-scan', 'Ithemes_Sync_Verb_ITSEC_Malware_Do_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-do-malware-scan.php' );
core/modules/security-check/active.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function itsec_security_check_register_sync_verbs( $api ) {
4
+ $api->register( 'itsec-do-security-check', 'Ithemes_Sync_Verb_ITSEC_Do_Security_Check', dirname( __FILE__ ) . '/sync-verbs/itsec-do-security-check.php' );
5
+ $api->register( 'itsec-get-security-check-feedback-response', 'Ithemes_Sync_Verb_ITSEC_Get_Security_Check_Feedback_Response', dirname( __FILE__ ) . '/sync-verbs/itsec-get-security-check-feedback-response.php' );
6
+ $api->register( 'itsec-get-security-check-modules', 'Ithemes_Sync_Verb_ITSEC_Get_Security_Check_Modules', dirname( __FILE__ ) . '/sync-verbs/itsec-get-security-check-modules.php' );
7
+ }
8
+ add_action( 'ithemes_sync_register_verbs', 'itsec_security_check_register_sync_verbs' );
core/modules/security-check/feedback-renderer.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ final class ITSEC_Security_Check_Feedback_Renderer {
4
+ public static function render( $data ) {
5
+ $section_groups = array();
6
+
7
+ foreach ( $data['sections'] as $name => $args ) {
8
+ $section_groups[$args['status']][$name] = $args;
9
+ }
10
+
11
+ if ( isset( $section_groups['call-to-action'] ) ) {
12
+ self::render_sections( 'call-to-action', $section_groups['call-to-action'] );
13
+ }
14
+ if ( isset( $section_groups['action-taken'] ) ) {
15
+ self::render_sections( 'action-taken', $section_groups['action-taken'] );
16
+ }
17
+ if ( isset( $section_groups['confirmation'] ) ) {
18
+ self::render_sections( 'confirmation', $section_groups['confirmation'] );
19
+ }
20
+ }
21
+
22
+ private static function render_sections( $status, $sections ) {
23
+ foreach ( $sections as $name => $args ) {
24
+ $classes = array( 'itsec-security-check-container', "itsec-security-check-container-$status" );
25
+
26
+ if ( $args['interactive'] ) {
27
+ $classes[] = 'itsec-security-check-container-is-interactive';
28
+ }
29
+
30
+ echo '<div class="' . self::esc_attr( implode( ' ', $classes ) ) . '"';
31
+
32
+ if ( ! empty( $id ) ) {
33
+ echo " id=\"$id\"";
34
+ }
35
+
36
+ echo ">\n";
37
+
38
+ if ( $args['interactive'] ) {
39
+ echo '<div class="itsec-security-check-feedback"></div>';
40
+ }
41
+
42
+ foreach ( $args['entries'] as $entry ) {
43
+ self::render_entry( $entry );
44
+ }
45
+
46
+ echo "</div>\n";
47
+ }
48
+ }
49
+
50
+ private static function render_entry( $entry ) {
51
+ if ( empty( $entry['type'] ) ) {
52
+ return;
53
+ }
54
+
55
+ if ( 'text' === $entry['type'] ) {
56
+ if ( isset( $entry['value'] ) ) {
57
+ echo "<p>{$entry['value']}</p>\n";
58
+ }
59
+ } else if ( 'input' === $entry['type'] ) {
60
+ if ( empty( $entry['input'] ) ) {
61
+ return;
62
+ }
63
+
64
+ $defaults = array(
65
+ 'format' => '%1$s',
66
+ 'value' => '',
67
+ 'style_class' => '',
68
+ 'data' => array(),
69
+ );
70
+ $entry = array_merge( $defaults, $entry );
71
+
72
+ if ( ! empty( $entry['value_alias'] ) ) {
73
+ $entry['value'] = self::get_alias_value( $entry['value_alias'] );
74
+ }
75
+
76
+ $data_attrs = array();
77
+
78
+ foreach ( (array) $entry['data'] as $key => $val ) {
79
+ $key = preg_replace( '/[^a-zA-Z0-9\-_]+/', '', $key );
80
+ $val = self::esc_attr( $val );
81
+
82
+ $data_attrs[] = " data-$key=\"$val\"";
83
+ }
84
+
85
+
86
+ if ( 'select' === $entry['input'] ) {
87
+ if ( empty( $entry['name'] ) || empty( $entry['options'] ) ) {
88
+ return;
89
+ }
90
+
91
+ $options = "\n";
92
+
93
+ foreach ( $entry['options'] as $value => $description ) {
94
+ $option = '<option value="' . self::esc_attr( $value ) . '"';
95
+
96
+ if ( $value === $entry['value'] ) {
97
+ $option .= ' selected="selected"';
98
+ }
99
+
100
+ $option .= '>' . self::esc_html( $description ) . "</option>\n";
101
+
102
+ $options .= $option;
103
+ }
104
+
105
+ $input_format = '<select name="%1$s" class="%2$s"%3$s>%4$s</select>';
106
+ $input = sprintf( $input_format, self::esc_attr( $entry['name'] ), self::esc_attr( $entry['style_class'] ), implode( '', $data_attrs ), $options );
107
+ } else if ( 'textarea' === $entry['input'] ) {
108
+ if ( empty( $entry['name'] ) ) {
109
+ return;
110
+ }
111
+
112
+ $input_format = '<textarea name="%1$s" class="%2$s"%3$s>%4$s</textarea>';
113
+ $input = sprintf( $input_format, self::esc_attr( $entry['name'] ), self::esc_attr( $entry['style_class'] ), implode( '', $data_attrs ), self::esc_html( $entry['value'] ) );
114
+ } else {
115
+ if ( empty( $entry['name'] ) ) {
116
+ return;
117
+ }
118
+
119
+ $input_format = '<input type="%1$s" name="%2$s" value="%3$s" class="%4$s"%5$s />';
120
+ $input = sprintf( $input_format, self::esc_attr( $entry['input'] ), self::esc_attr( $entry['name'] ), self::esc_attr( $entry['value'] ), self::esc_attr( $entry['style_class'] ), implode( '', $data_attrs ) );
121
+ }
122
+
123
+ echo '<p><label>';
124
+ printf( $entry['format'], $input );
125
+ echo "</label></p>\n";
126
+ }
127
+ }
128
+
129
+ private static function esc_attr( $attr ) {
130
+ return esc_attr( $attr );
131
+ }
132
+
133
+ private static function esc_html( $html ) {
134
+ return esc_html( $html );
135
+ }
136
+
137
+ private static function get_alias_value( $alias ) {
138
+ if ( 'email' === $alias ) {
139
+ return get_option( 'admin_email' );
140
+ }
141
+ }
142
+ }
core/modules/security-check/feedback.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ final class ITSEC_Security_Check_Feedback {
4
+ private $sections = array();
5
+ private $current_section = '';
6
+
7
+ public function __construct( $raw_data = false ) {
8
+ if ( is_array( $raw_data ) && isset( $raw_data['sections'] ) && is_array( $raw_data['sections'] ) ) {
9
+ $this->sections = $raw_data['sections'];
10
+ }
11
+ }
12
+
13
+ public function add_section( $name, $args = array() ) {
14
+ if ( ! isset( $this->sections[$name] ) ) {
15
+ $default_args = array(
16
+ 'interactive' => false,
17
+ 'status' => 'confirmation',
18
+ 'entries' => array(),
19
+ );
20
+
21
+ $args = array_merge( $default_args, $args );
22
+
23
+ $this->sections[$name] = $args;
24
+ }
25
+
26
+ $this->current_section = $name;
27
+ }
28
+
29
+ public function switch_section( $name ) {
30
+ if ( isset( $this->sections[$name] ) ) {
31
+ $this->current_section = $name;
32
+ return true;
33
+ } else {
34
+ $this->current_section = '';
35
+ return false;
36
+ }
37
+ }
38
+
39
+ public function set_section_arg( $arg, $value, $name = false ) {
40
+ if ( false === $name ) {
41
+ $name = $this->current_section;
42
+ }
43
+
44
+ if ( ! isset( $this->sections[$name] ) ) {
45
+ return false;
46
+ }
47
+
48
+ $this->sections[$name][$arg] = $value;
49
+ return true;
50
+ }
51
+
52
+ public function add_entry( $entry ) {
53
+ if ( empty( $this->current_section ) ) {
54
+ return false;
55
+ }
56
+
57
+ $this->sections[$this->current_section]['entries'][] = $entry;
58
+ return true;
59
+ }
60
+
61
+ public function add_text( $text ) {
62
+ $entry = array(
63
+ 'type' => 'text',
64
+ 'value' => $text,
65
+ );
66
+
67
+ $this->add_entry( $entry );
68
+ }
69
+
70
+ public function add_input( $input, $name, $args = array() ) {
71
+ $entry = array(
72
+ 'type' => 'input',
73
+ 'input' => $input,
74
+ 'name' => $name,
75
+ 'format' => '%1$s',
76
+ 'value' => '',
77
+ 'style_class' => '',
78
+ );
79
+
80
+ if ( 'select' === $input ) {
81
+ $entry['options'] = array();
82
+ }
83
+
84
+ $entry = array_merge( $entry, $args );
85
+
86
+ $this->add_entry( $entry );
87
+ }
88
+
89
+ public function get_raw_data() {
90
+ return array(
91
+ 'sections' => $this->sections,
92
+ );
93
+ }
94
+ }
core/modules/security-check/js/settings-page.js CHANGED
@@ -1,5 +1,5 @@
1
  jQuery( document ).ready( function ( $ ) {
2
- var $container = $( '#itsec-module-card-security-check' )
3
 
4
  $container.on( 'click', '#itsec-security-check-secure_site', function( e ) {
5
  e.preventDefault();
@@ -27,47 +27,77 @@ jQuery( document ).ready( function ( $ ) {
27
  } );
28
  } );
29
 
30
- $container.on( 'click', '#itsec-security-check-enable_network_brute_force', function( e ) {
31
  e.preventDefault();
32
 
33
- var original_button_name = $( '#itsec-security-check-enable_network_brute_force' ).attr( 'value' );
 
 
 
34
 
35
- $( '#itsec-security-check-enable_network_brute_force' )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  .removeClass( 'button-primary' )
37
  .addClass( 'button-secondary' )
38
- .attr( 'value', itsec_security_check_settings.activating_network_brute_force )
39
  .prop( 'disabled', true );
40
 
41
- var data = {
42
- 'method': 'activate-network-brute-force',
43
- 'email': $( '#itsec-security-check-email' ).attr( 'value' ),
44
- 'updates_optin': $( '#itsec-security-check-updates_optin option:selected' ).text()
45
- };
46
 
47
- itsecSettingsPage.sendModuleAJAXRequest( 'security-check', data, function( results ) {
48
- $( '#itsec-security-check-enable_network_brute_force' )
49
- .addClass( 'button-primary' )
 
 
 
 
 
50
  .removeClass( 'button-secondary' )
51
- .attr( 'value', original_button_name )
52
  .prop( 'disabled', false );
53
 
54
- $( '#itsec-security-check-network-brute-force-errors' ).html( '' );
55
- var $container = $( '#itsec-module-card-security-check #itsec-security-check-network-brute-force-container' );
 
 
 
 
 
 
56
 
57
  if ( results.errors && results.errors.length > 0 ) {
58
  $container
59
- .removeClass( 'itsec-security-check-container-incomplete' )
60
- .removeClass( 'itsec-security-check-container-complete' )
61
  .addClass( 'itsec-security-check-container-error' );
62
 
63
  $.each( results.errors, function( index, error ) {
64
- $( '#itsec-security-check-network-brute-force-errors' ).append( '<div class="error inline"><p><strong>' + error + '</strong></p></div>' );
65
  } );
66
  } else {
67
  $container
68
- .removeClass( 'itsec-security-check-container-incomplete' )
69
  .removeClass( 'itsec-security-check-container-error' )
70
- .addClass( 'itsec-security-check-container-complete' );
71
 
72
  $container.html( results.response );
73
  $( '#itsec-notice-network-brute-force' ).hide();
@@ -75,3 +105,10 @@ jQuery( document ).ready( function ( $ ) {
75
  } );
76
  } );
77
  } );
 
 
 
 
 
 
 
1
  jQuery( document ).ready( function ( $ ) {
2
+ var $container = $( '#itsec-module-card-security-check' );
3
 
4
  $container.on( 'click', '#itsec-security-check-secure_site', function( e ) {
5
  e.preventDefault();
27
  } );
28
  } );
29
 
30
+ $container.on( 'click', '.itsec-security-check-container-is-interactive :submit', function( e ) {
31
  e.preventDefault();
32
 
33
+ var $button = $( this );
34
+ var $container = $( this ).parents( '.itsec-security-check-container-is-interactive' );
35
+ var inputs = $container.find( ':input' ).serializeArray();
36
+ var data = {};
37
 
38
+ for ( var i = 0; i < inputs.length; i++ ) {
39
+ var input = inputs[i];
40
+
41
+ if ( '[]' === input.name.substr( -2 ) ) {
42
+ var name = input.name.substr( 0, input.name.length - 2 );
43
+
44
+ if ( data[name] ) {
45
+ data[name].push( input.value );
46
+ } else {
47
+ data[name] = [input.value];
48
+ }
49
+ } else {
50
+ data[input.name] = input.value;
51
+ }
52
+ };
53
+
54
+
55
+ $button
56
  .removeClass( 'button-primary' )
57
  .addClass( 'button-secondary' )
 
58
  .prop( 'disabled', true );
59
 
60
+ if ( $button.data( 'clicked-value' ) ) {
61
+ $button
62
+ .data( 'original-value', $( this ).val() )
63
+ .attr( 'value', $( this ).data( 'clicked-value' ) )
64
+ }
65
 
66
+ var ajaxFunction = itsecSettingsPage.sendModuleAJAXRequest;
67
+
68
+ if ( 'undefined' !== typeof itsecSecurityCheckAJAXRequest ) {
69
+ ajaxFunction = itsecSecurityCheckAJAXRequest;
70
+ }
71
+
72
+ ajaxFunction( 'security-check', data, function( results ) {
73
+ $button
74
  .removeClass( 'button-secondary' )
75
+ .addClass( 'button-primary' )
76
  .prop( 'disabled', false );
77
 
78
+ if ( $button.data( 'original-value' ) ) {
79
+ $button
80
+ .attr( 'value', $( this ).data( 'original-value' ) )
81
+ }
82
+
83
+
84
+ var $feedback = $container.find( '.itsec-security-check-feedback' );
85
+ $feedback.html( '' );
86
 
87
  if ( results.errors && results.errors.length > 0 ) {
88
  $container
89
+ .removeClass( 'itsec-security-check-container-call-to-action' )
90
+ .removeClass( 'itsec-security-check-container-confirmation' )
91
  .addClass( 'itsec-security-check-container-error' );
92
 
93
  $.each( results.errors, function( index, error ) {
94
+ $feedback.append( '<div class="error inline"><p><strong>' + error + '</strong></p></div>' );
95
  } );
96
  } else {
97
  $container
98
+ .removeClass( 'itsec-security-check-container-call-to-action' )
99
  .removeClass( 'itsec-security-check-container-error' )
100
+ .addClass( 'itsec-security-check-container-confirmation' );
101
 
102
  $container.html( results.response );
103
  $( '#itsec-notice-network-brute-force' ).hide();
105
  } );
106
  } );
107
  } );
108
+
109
+ /*
110
+ function itsecSecurityCheckAJAXRequest( type, data, callback ) {
111
+ console.log( 'Override called' );
112
+ itsecSettingsPage.sendModuleAJAXRequest( type, data, callback );
113
+ }
114
+ */
core/modules/security-check/scanner.php CHANGED
@@ -2,12 +2,44 @@
2
 
3
  final class ITSEC_Security_Check_Scanner {
4
  private static $available_modules;
5
- private static $calls_to_action = array();
6
- private static $actions_taken = array();
7
- private static $confirmations = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
 
9
 
10
- public static function run() {
11
  self::$available_modules = ITSEC_Modules::get_available_modules();
12
 
13
  self::enforce_activation( 'ban-users', __( 'Banned Users', 'better-wp-security' ) );
@@ -34,15 +66,6 @@ final class ITSEC_Security_Check_Scanner {
34
  self::enforce_setting( 'wordpress-tweaks', 'rest_api', 'restrict-access', __( 'Changed the REST API setting in WordPress Tweaks to "Restricted Access".', 'better-wp-security' ) );
35
 
36
  self::enforce_setting( 'global', 'write_files', true, __( 'Enabled the Write to Files setting in Global Settings.', 'better-wp-security' ) );
37
-
38
-
39
- ob_start();
40
-
41
- echo implode( "\n", self::$calls_to_action );
42
- echo implode( "\n", self::$actions_taken );
43
- echo implode( "\n", self::$confirmations );
44
-
45
- ITSEC_Response::set_response( ob_get_clean() );
46
  }
47
 
48
  private static function add_network_brute_force_signup() {
@@ -59,37 +82,28 @@ final class ITSEC_Security_Check_Scanner {
59
  }
60
 
61
 
62
- require_once( ITSEC_Core::get_core_dir() . '/lib/form.php' );
63
- $form = new ITSEC_Form();
64
- $form->add_input_group( 'security-check' );
65
-
66
- ob_start();
67
-
68
- self::open_container( 'incomplete', 'itsec-security-check-network-brute-force-container' );
69
-
70
- echo '<p>' . __( 'With Network Brute Force Protection, your site is protected against attackers found by other sites running iThemes Security. If your site identifies a new attacker, it automatically notifies the network so that other sites are protected as well. To join this site to the network and enable the protection, click the button below.', 'better-wp-security' ) . '</p>';
71
-
72
- ob_start();
73
- $form->add_text( 'email', array( 'class' => 'regular-text', 'value' => get_option( 'admin_email' ) ) );
74
- $email_input = ob_get_clean();
75
- /* translators: 1: email text input */
76
- echo '<p><label for="itsec-security-check-email">' . sprintf( __( 'Email Address: %1$s', 'better-wp-security' ), $email_input ) . '</p>';
77
-
78
- ob_start();
79
- $form->add_select( 'updates_optin', array( 'true' => __( 'Yes', 'better-wp-security' ), 'false' => __( 'No', 'better-wp-security' ) ) );
80
- $optin_input = ob_get_clean();
81
- /* translators: 1: opt-in input */
82
- echo '<p><label for="itsec-security-check-updates_optin">' . sprintf( __( 'Receive email updates about WordPress Security from iThemes: %1$s', 'better-wp-security' ), $optin_input ) . '</p>';
83
-
84
- ob_start();
85
- $form->add_button( 'enable_network_brute_force', array( 'class' => 'button-primary', 'value' => __( 'Activate Network Brute Force Protection', 'better-wp-security' ) ) );
86
- echo '<p>' . ob_get_clean() . '</p>';
87
-
88
- echo '<div id="itsec-security-check-network-brute-force-errors"></div>';
89
-
90
- echo '</div>';
91
-
92
- self::$calls_to_action[] = ob_get_clean();
93
  }
94
 
95
  private static function enforce_setting( $module, $setting_name, $setting_value, $description ) {
@@ -97,19 +111,17 @@ final class ITSEC_Security_Check_Scanner {
97
  return;
98
  }
99
 
100
- if ( ITSEC_Modules::get_setting( $module, $setting_name ) !== $setting_value ) {
101
- ITSEC_Modules::set_setting( $module, $setting_name, $setting_value );
 
102
 
103
- ob_start();
104
 
105
- self::open_container();
106
- echo "<p>$description</p>";
107
- echo '</div>';
108
 
109
- self::$actions_taken[] = ob_get_clean();
 
110
 
111
- ITSEC_Response::reload_module( $module );
112
- }
113
  }
114
 
115
  private static function enforce_activation( $module, $name ) {
@@ -117,37 +129,40 @@ final class ITSEC_Security_Check_Scanner {
117
  return;
118
  }
119
 
 
 
120
  if ( ITSEC_Modules::is_active( $module ) ) {
121
  /* Translators: 1: feature name */
122
  $text = __( '%1$s is enabled as recommended.', 'better-wp-security' );
123
- $took_action = false;
124
  } else {
125
  ITSEC_Modules::activate( $module );
126
  ITSEC_Response::add_js_function_call( 'setModuleToActive', $module );
127
 
128
  /* Translators: 1: feature name */
129
  $text = __( 'Enabled %1$s.', 'better-wp-security' );
130
- $took_action = true;
 
131
  }
132
 
133
- ob_start();
 
134
 
135
- self::open_container();
136
- echo '<p>' . sprintf( $text, $name ) . '</p>';
137
- echo '</div>';
 
 
138
 
139
- if ( $took_action ) {
140
- self::$actions_taken[] = ob_get_clean();
141
- } else {
142
- self::$confirmations[] = ob_get_clean();
143
  }
144
- }
145
 
146
- public static function activate_network_brute_force() {
147
  $settings = ITSEC_Modules::get_settings( 'network-brute-force' );
148
 
149
- $settings['email'] = $_POST['data']['email'];
150
- $settings['updates_optin'] = $_POST['data']['updates_optin'];
151
  $settings['api_nag'] = false;
152
 
153
  $results = ITSEC_Modules::set_settings( 'network-brute-force', $settings );
@@ -160,14 +175,4 @@ final class ITSEC_Security_Check_Scanner {
160
  ITSEC_Response::set_response( '<p>' . __( 'Your site is now using Network Brute Force Protection.', 'better-wp-security' ) . '</p>' );
161
  }
162
  }
163
-
164
- private static function open_container( $status = 'complete', $id = '' ) {
165
- echo '<div class="itsec-security-check-container itsec-security-check-container-' . $status . '"';
166
-
167
- if ( ! empty( $id ) ) {
168
- echo ' id="' . $id . '"';
169
- }
170
-
171
- echo '>';
172
- }
173
  }
2
 
3
  final class ITSEC_Security_Check_Scanner {
4
  private static $available_modules;
5
+ private static $feedback;
6
+
7
+
8
+ public static function get_supported_modules() {
9
+ $available_modules = ITSEC_Modules::get_available_modules();
10
+
11
+ $modules = array(
12
+ 'ban-users' => __( 'Banned Users', 'better-wp-security' ),
13
+ 'backup' => __( 'Database Backups', 'better-wp-security' ),
14
+ 'brute-force' => __( 'Local Brute Force Protection', 'better-wp-security' ),
15
+ 'malware-scheduling' => __( 'Malware Scan Scheduling', 'better-wp-security' ),
16
+ 'network-brute-force' => __( 'Network Brute Force Protection', 'better-wp-security' ),
17
+ 'strong-passwords' => __( 'Strong Passwords', 'better-wp-security' ),
18
+ 'two-factor' => __( 'Two-Factor Authentication', 'better-wp-security' ),
19
+ 'user-logging' => __( 'User Logging', 'better-wp-security' ),
20
+ 'wordpress-tweaks' => __( 'WordPress Tweaks', 'better-wp-security' ),
21
+ );
22
+
23
+ foreach ( $modules as $module => $val ) {
24
+ if ( ! in_array( $module, $available_modules ) ) {
25
+ unset( $modules[$module] );
26
+ }
27
+ }
28
+
29
+ return $modules;
30
+ }
31
+
32
+ public static function get_results() {
33
+ self::run_scan();
34
+
35
+ return self::$feedback->get_raw_data();
36
+ }
37
+
38
+ public static function run_scan() {
39
+ require_once( dirname( __FILE__ ) . '/feedback.php' );
40
 
41
+ self::$feedback = new ITSEC_Security_Check_Feedback();
42
 
 
43
  self::$available_modules = ITSEC_Modules::get_available_modules();
44
 
45
  self::enforce_activation( 'ban-users', __( 'Banned Users', 'better-wp-security' ) );
66
  self::enforce_setting( 'wordpress-tweaks', 'rest_api', 'restrict-access', __( 'Changed the REST API setting in WordPress Tweaks to "Restricted Access".', 'better-wp-security' ) );
67
 
68
  self::enforce_setting( 'global', 'write_files', true, __( 'Enabled the Write to Files setting in Global Settings.', 'better-wp-security' ) );
 
 
 
 
 
 
 
 
 
69
  }
70
 
71
  private static function add_network_brute_force_signup() {
82
  }
83
 
84
 
85
+ self::$feedback->add_section( 'network-brute-force-signup', array( 'interactive' => true, 'status' => 'call-to-action' ) );
86
+ self::$feedback->add_text( __( 'With Network Brute Force Protection, your site is protected against attackers found by other sites running iThemes Security. If your site identifies a new attacker, it automatically notifies the network so that other sites are protected as well. To join this site to the network and enable the protection, click the button below.', 'better-wp-security' ) );
87
+ self::$feedback->add_input( 'text', 'email', array(
88
+ 'format' => __( 'Email Address: %1$s', 'better-wp-security' ),
89
+ 'value_alias' => 'email',
90
+ 'style_class' => 'regular-text',
91
+ ) );
92
+ self::$feedback->add_input( 'select', 'updates_optin', array(
93
+ 'format' => __( 'Receive email updates about WordPress Security from iThemes: %1$s', 'better-wp-security' ),
94
+ 'options' => array( 'true' => __( 'Yes', 'better-wp-security' ), 'false' => __( 'No', 'better-wp-security' ) ),
95
+ 'value' => 'true',
96
+ ) );
97
+ self::$feedback->add_input( 'hidden', 'method', array(
98
+ 'value' => 'activate-network-brute-force',
99
+ ) );
100
+ self::$feedback->add_input( 'submit', 'enable_network_brute_force', array(
101
+ 'value' => __( 'Activate Network Brute Force Protection', 'better-wp-security' ),
102
+ 'style_class' => 'button-primary',
103
+ 'data' => array(
104
+ 'clicked-value' => __( 'Activating Network Brute Force Protection...', 'better-wp-security' ),
105
+ ),
106
+ ) );
 
 
 
 
 
 
 
 
 
107
  }
108
 
109
  private static function enforce_setting( $module, $setting_name, $setting_value, $description ) {
111
  return;
112
  }
113
 
114
+ if ( ITSEC_Modules::get_setting( $module, $setting_name ) === $setting_value ) {
115
+ return;
116
+ }
117
 
 
118
 
119
+ ITSEC_Modules::set_setting( $module, $setting_name, $setting_value );
 
 
120
 
121
+ self::$feedback->add_section( "enforce-setting-$module-$setting_name", array( 'status' => 'action-taken' ) );
122
+ self::$feedback->add_text( $description );
123
 
124
+ ITSEC_Response::reload_module( $module );
 
125
  }
126
 
127
  private static function enforce_activation( $module, $name ) {
129
  return;
130
  }
131
 
132
+ self::$feedback->add_section( "$module-activation" );
133
+
134
  if ( ITSEC_Modules::is_active( $module ) ) {
135
  /* Translators: 1: feature name */
136
  $text = __( '%1$s is enabled as recommended.', 'better-wp-security' );
 
137
  } else {
138
  ITSEC_Modules::activate( $module );
139
  ITSEC_Response::add_js_function_call( 'setModuleToActive', $module );
140
 
141
  /* Translators: 1: feature name */
142
  $text = __( 'Enabled %1$s.', 'better-wp-security' );
143
+
144
+ self::$feedback->set_section_arg( 'status', 'action-taken' );
145
  }
146
 
147
+ self::$feedback->add_text( sprintf( $text, $name ) );
148
+ }
149
 
150
+ public static function activate_network_brute_force( $data ) {
151
+ if ( ! isset( $data['email'] ) ) {
152
+ ITSEC_Response::add_error( new WP_Error( 'itsec-security-check-missing-email', __( 'The email value is missing.', 'better-wp-security' ) ) );
153
+ return;
154
+ }
155
 
156
+ if ( ! isset( $data['updates_optin'] ) ) {
157
+ ITSEC_Response::add_error( new WP_Error( 'itsec-security-check-missing-updates_optin', __( 'The updates_optin value is missing.', 'better-wp-security' ) ) );
158
+ return;
 
159
  }
 
160
 
161
+
162
  $settings = ITSEC_Modules::get_settings( 'network-brute-force' );
163
 
164
+ $settings['email'] = $data['email'];
165
+ $settings['updates_optin'] = $data['updates_optin'];
166
  $settings['api_nag'] = false;
167
 
168
  $results = ITSEC_Modules::set_settings( 'network-brute-force', $settings );
175
  ITSEC_Response::set_response( '<p>' . __( 'Your site is now using Network Brute Force Protection.', 'better-wp-security' ) . '</p>' );
176
  }
177
  }
 
 
 
 
 
 
 
 
 
 
178
  }
core/modules/security-check/settings-page.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
 
3
  final class ITSEC_Security_Check_Settings_Page extends ITSEC_Module_Settings_Page {
4
- private $script_version = 1;
5
 
6
 
7
  public function __construct() {
@@ -17,9 +17,8 @@ final class ITSEC_Security_Check_Settings_Page extends ITSEC_Module_Settings_Pag
17
 
18
  public function enqueue_scripts_and_styles() {
19
  $vars = array(
20
- 'securing_site' => __( 'Securing Site...', 'better-wp-security' ),
21
- 'rerun_secure_site' => __( 'Run Secure Site Again', 'better-wp-security' ),
22
- 'activating_network_brute_force' => __( 'Activating Network Brute Force Protection...', 'better-wp-security' ),
23
  );
24
 
25
  wp_enqueue_script( 'itsec-security-check-settings-script', plugins_url( 'js/settings-page.js', __FILE__ ), array( 'jquery' ), $this->script_version, true );
@@ -29,37 +28,26 @@ final class ITSEC_Security_Check_Settings_Page extends ITSEC_Module_Settings_Pag
29
  public function handle_ajax_request( $data ) {
30
  if ( 'secure-site' === $data['method'] ) {
31
  require_once( dirname( __FILE__ ) . '/scanner.php' );
 
32
 
33
- ITSEC_Security_Check_Scanner::run();
 
 
 
 
34
  } else if ( 'activate-network-brute-force' === $data['method'] ) {
35
  require_once( dirname( __FILE__ ) . '/scanner.php' );
36
 
37
- ITSEC_Security_Check_Scanner::activate_network_brute_force();
38
  }
39
  }
40
 
41
  protected function render_description( $form ) {}
42
 
43
  protected function render_settings( $form ) {
44
- $available_modules = ITSEC_Modules::get_available_modules();
45
-
46
- $modules_to_activate = array(
47
- 'ban-users' => __( 'Banned Users', 'better-wp-security' ),
48
- 'backup' => __( 'Database Backups', 'better-wp-security' ),
49
- 'brute-force' => __( 'Local Brute Force Protection', 'better-wp-security' ),
50
- 'malware-scheduling' => __( 'Malware Scan Scheduling', 'better-wp-security' ),
51
- 'network-brute-force' => __( 'Network Brute Force Protection', 'better-wp-security' ),
52
- 'strong-passwords' => __( 'Strong Passwords', 'better-wp-security' ),
53
- 'two-factor' => __( 'Two-Factor Authentication', 'better-wp-security' ),
54
- 'user-logging' => __( 'User Logging', 'better-wp-security' ),
55
- 'wordpress-tweaks' => __( 'WordPress Tweaks', 'better-wp-security' ),
56
- );
57
 
58
- foreach ( $modules_to_activate as $module => $val ) {
59
- if ( ! in_array( $module, $available_modules ) ) {
60
- unset( $modules_to_activate[$module] );
61
- }
62
- }
63
 
64
  ?>
65
  <div id="itsec-security-check-details-container">
1
  <?php
2
 
3
  final class ITSEC_Security_Check_Settings_Page extends ITSEC_Module_Settings_Page {
4
+ private $script_version = 2;
5
 
6
 
7
  public function __construct() {
17
 
18
  public function enqueue_scripts_and_styles() {
19
  $vars = array(
20
+ 'securing_site' => __( 'Securing Site...', 'better-wp-security' ),
21
+ 'rerun_secure_site' => __( 'Run Secure Site Again', 'better-wp-security' ),
 
22
  );
23
 
24
  wp_enqueue_script( 'itsec-security-check-settings-script', plugins_url( 'js/settings-page.js', __FILE__ ), array( 'jquery' ), $this->script_version, true );
28
  public function handle_ajax_request( $data ) {
29
  if ( 'secure-site' === $data['method'] ) {
30
  require_once( dirname( __FILE__ ) . '/scanner.php' );
31
+ require_once( dirname( __FILE__ ) . '/feedback-renderer.php' );
32
 
33
+ $results = ITSEC_Security_Check_Scanner::get_results();
34
+
35
+ ob_start();
36
+ ITSEC_Security_Check_Feedback_Renderer::render( $results );
37
+ ITSEC_Response::set_response( ob_get_clean() );
38
  } else if ( 'activate-network-brute-force' === $data['method'] ) {
39
  require_once( dirname( __FILE__ ) . '/scanner.php' );
40
 
41
+ ITSEC_Security_Check_Scanner::activate_network_brute_force( $_POST['data'] );
42
  }
43
  }
44
 
45
  protected function render_description( $form ) {}
46
 
47
  protected function render_settings( $form ) {
48
+ require_once( dirname( __FILE__ ) . '/scanner.php' );
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ $modules_to_activate = ITSEC_Security_Check_Scanner::get_supported_modules();
 
 
 
 
51
 
52
  ?>
53
  <div id="itsec-security-check-details-container">
core/modules/security-check/sync-verbs/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/security-check/sync-verbs/itsec-do-security-check.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Ithemes_Sync_Verb_ITSEC_Do_Security_Check extends Ithemes_Sync_Verb {
4
+ public static $name = 'itsec-do-security-check';
5
+ public static $description = '';
6
+
7
+ public function run( $arguments ) {
8
+ require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
9
+
10
+ return ITSEC_Security_Check_Scanner::get_results();
11
+ }
12
+ }
core/modules/security-check/sync-verbs/itsec-get-security-check-feedback-response.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Ithemes_Sync_Verb_ITSEC_Get_Security_Check_Feedback_Response extends Ithemes_Sync_Verb {
4
+ public static $name = 'itsec-get-security-check-feedback-response';
5
+ public static $description = '';
6
+
7
+ private $default_arguments = array(
8
+ 'data' => array(),
9
+ );
10
+
11
+ public function run( $arguments ) {
12
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
13
+
14
+ require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
15
+
16
+ ITSEC_Security_Check_Scanner::activate_network_brute_force( $arguments['data'] );
17
+
18
+ return ITSEC_Response::get_raw_data();
19
+ }
20
+ }
core/modules/security-check/sync-verbs/itsec-get-security-check-modules.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Ithemes_Sync_Verb_ITSEC_Get_Security_Check_Modules extends Ithemes_Sync_Verb {
4
+ public static $name = 'itsec-get-security-check-modules';
5
+ public static $description = '';
6
+
7
+ public function run( $arguments ) {
8
+ require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
9
+
10
+ return ITSEC_Security_Check_Scanner::get_supported_modules();
11
+ }
12
+ }
core/modules/ssl/class-itsec-ssl-admin.php CHANGED
@@ -47,7 +47,15 @@ class ITSEC_SSL_Admin {
47
 
48
  if ( isset( $_POST['itsec_admin_save_wp_nonce'] ) ) {
49
 
50
- if ( ! wp_verify_nonce( $_POST['itsec_admin_save_wp_nonce'], 'ITSEC_Admin_Save' ) || ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ( $_POST['post_type'] == 'page' && ! current_user_can( 'edit_page', $id ) ) || ( $_POST['post_type'] == 'post' && ! current_user_can( 'edit_post', $id ) ) ) {
 
 
 
 
 
 
 
 
51
  return $id;
52
  }
53
 
47
 
48
  if ( isset( $_POST['itsec_admin_save_wp_nonce'] ) ) {
49
 
50
+ if ( ! wp_verify_nonce( $_POST['itsec_admin_save_wp_nonce'], 'ITSEC_Admin_Save' ) ) {
51
+ return $id;
52
+ }
53
+
54
+ if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
55
+ return $id;
56
+ }
57
+
58
+ if ( ! current_user_can( 'edit_post', $id ) ) {
59
  return $id;
60
  }
61
 
core/modules/strong-passwords/class-itsec-strong-passwords.php CHANGED
@@ -2,8 +2,10 @@
2
 
3
  final class ITSEC_Strong_Passwords {
4
  public function __construct() {
 
 
5
  add_action( 'user_profile_update_errors', array( $this, 'filter_user_profile_update_errors' ), 0, 3 );
6
- add_action( 'validate_password_reset', array( $this, 'validate_password_reset' ), 10, 2 );
7
 
8
  add_action( 'admin_enqueue_scripts', array( $this, 'add_scripts' ) );
9
  add_action( 'login_enqueue_scripts', array( $this, 'add_scripts' ) );
@@ -20,6 +22,18 @@ final class ITSEC_Strong_Passwords {
20
  wp_enqueue_script( 'itsec_strong_passwords', $module_path . 'js/script.js', array( 'jquery' ), ITSEC_Core::get_plugin_build() );
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  /**
24
  * Handle submission of a form to create or edit a user.
25
  *
@@ -30,153 +44,201 @@ final class ITSEC_Strong_Passwords {
30
  * @return WP_Error
31
  */
32
  public function filter_user_profile_update_errors( $errors, $update, $user ) {
 
 
33
  if ( $errors->get_error_data( 'pass' ) ) {
34
- // An error regarding the password was already found.
35
  return $errors;
36
  }
37
 
38
- $password_strength = false;
 
 
39
 
40
- if ( ! isset( $user->user_pass ) && $update ) {
41
- // The password was not changed, but an update is occurring. Test to see if we need to prompt for a password change.
42
- // This also handles the case where a user's role is being changed to one that requires strong password enforcement.
43
 
44
- $password_strength = get_user_meta( $user->ID, 'itsec-password-strength', true );
45
 
46
- if ( false === $password_strength || '' === $password_strength || ! in_array( $password_strength, range( 0, 4 ) ) ) {
47
- // Not enough data to determine whether a change of password is required.
48
- return $errors;
49
- }
50
  }
51
 
52
- $wp_roles = wp_roles();
53
- $user_caps = array();
54
-
55
- if ( $update ) {
56
- // We're updating the user, make sure that we keep a list of additional caps that may have been added to the user.
57
- // Since the $user_obj->caps array contains both roles and caps added to the user, we have to check whether the cap is a role or cap.
 
58
 
59
- $user_obj = get_user_by( 'id', $user->ID );
 
 
60
 
61
- if ( isset( $user_obj->caps ) && is_array( $user_obj->caps ) ) {
62
- foreach ( array_keys( $user_obj->caps ) as $cap ) {
63
- if ( ! $wp_roles->is_role( $cap ) ) {
64
- $user_caps[] = $cap;
65
- }
66
- }
67
- }
68
  }
69
 
70
- if ( isset( $user->role ) ) {
71
- // A user other than the current user is being created or updated, $user->role contains the role selected on the form.
72
- $role = $user->role;
73
- } else if ( isset( $user_obj ) ) {
74
- // The current user is being updated, this makes the logic simple as we can simply use $user_obj->allcaps to get a complete list of the user's caps.
75
- $caps = array_keys( $user_obj->allcaps );
76
  } else {
77
- // Something strange is going on, as a last-ditch effort, use the default role as done by wp_insert_user() when the role isn't provided.
78
- $role = get_option( 'default_role' );
79
  }
80
 
81
- if ( ! isset( $caps ) ) {
82
- // A user other than the current user is being created or updated.
 
 
83
 
84
- $the_role = $wp_roles->get_role( $role );
 
 
 
 
 
 
 
 
85
 
86
- // Merge the role's list of caps with the caps added to the user.
87
- $caps = array_merge( $user_caps, array_keys( $the_role->capabilities ) );
 
 
 
88
 
89
- // Ensure that there aren't any duplicate caps.
90
- $caps = array_unique( $caps );
 
 
 
 
 
91
  }
92
 
93
- if ( $this->fails_enforcement( $user, $caps, $password_strength ) ) {
94
- if ( $update ) {
95
- $errors->add( 'pass', wp_kses( __( '<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. The user changes have not been saved.', 'better-wp-security' ), array( 'strong' => array() ) ) );
96
- } else {
97
- $errors->add( 'pass', wp_kses( __( '<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. The user has not been created.', 'better-wp-security' ), array( 'strong' => array() ) ) );
98
- }
99
  }
100
 
101
- return $errors;
 
 
 
 
 
 
102
  }
103
 
104
  /**
105
- * Handle password reset requests.
106
  *
107
- * @param WP_Error $errors WP_Error object.
108
- * @param WP_User $user WP_User object.
109
  *
110
- * @return WP_Error
111
  */
112
- public function validate_password_reset( $errors, $user ) {
113
- if ( ! isset( $_POST['pass1'] ) ) {
114
- // The validate_password_reset action fires when first rendering the reset page and when handling the form
115
- // submissions. Since the pass1 data is missing, this must be the initial page render. So, we don't need to
116
- // do anything yet.
117
 
118
- return;
 
 
 
 
 
 
 
119
  }
120
 
121
- $caps = array_keys( $user->allcaps );
122
 
123
- if ( $this->fails_enforcement( $user, $caps ) ) {
124
- $errors->add( 'pass', wp_kses( __( '<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. The password has not been updated.', 'better-wp-security' ), array( 'strong' => array() ) ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  }
127
 
128
  /**
129
  * Determine if the user requires enforcement and if it fails that enforcement.
130
  *
131
- * @param WP_User|object $user Requires either a valid WP_User object or an object that has the following members:
132
  * user_login, first_name, last_name, nickname, display_name, user_email, user_url, and
133
  * description. A member of user_pass is required if $password_strength is false.
 
134
  * @param int|boolean $password_strength [optional] An integer value representing the password strength, if known, or false.
135
  * Defaults to false.
136
  *
137
  * @return boolean True if the user requires enforcement and has a password weaker than strong. False otherwise.
138
  */
139
- private function fails_enforcement( $user, $caps, $password_strength = false ) {
140
- require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
141
- $role = ITSEC_Lib_Canonical_Roles::get_role_from_caps( $caps );
142
- $min_role = ITSEC_Modules::get_setting( 'strong-passwords', 'role' );
143
 
144
- if ( ! ITSEC_Lib_Canonical_Roles::is_canonical_role_at_least( $min_role, $role ) ) {
145
- return false;
146
  }
147
 
148
- if ( false === $password_strength ) {
149
- if ( ! empty( $_POST['password_strength'] ) && 'strong' !== $_POST['password_strength'] ) {
150
- // We want to validate the password strength if the form data says that the password is strong since we want
151
- // to protect against spoofing. If the form data says that the password isn't strong, believe it.
152
-
153
- $password_strength = 1;
154
- } else {
155
- // The form data does not indicate a password strength or the data claimed that the password is strong,
156
- // which is a claim that must be validated. Use the zxcvbn library to find the password strength score.
157
-
158
- $penalty_strings = array(
159
- $user->user_login,
160
- $user->first_name,
161
- $user->last_name,
162
- $user->nickname,
163
- $user->display_name,
164
- $user->user_email,
165
- $user->user_url,
166
- $user->description,
167
- get_site_option( 'admin_email' ),
168
- );
169
-
170
- $results = ITSEC_Lib::get_password_strength_results( $user->user_pass, $penalty_strings );
171
- $password_strength = $results->score;
172
  }
173
- }
174
 
175
- if ( $password_strength < 4 ) {
176
- return true;
177
  }
178
 
179
- return false;
180
  }
181
  }
182
 
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' ) );
22
  wp_enqueue_script( 'itsec_strong_passwords', $module_path . 'js/script.js', array( 'jquery' ), ITSEC_Core::get_plugin_build() );
23
  }
24
 
25
+ /**
26
+ * Get the reason description for why a password change was set to 'strength'.
27
+ *
28
+ * @return string
29
+ */
30
+ public function strength_reason() {
31
+
32
+ $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' );
33
+
34
+ return wp_kses( $message, array( 'strong' => '' ) );
35
+ }
36
+
37
  /**
38
  * Handle submission of a form to create or edit a user.
39
  *
44
  * @return WP_Error
45
  */
46
  public function filter_user_profile_update_errors( $errors, $update, $user ) {
47
+
48
+ // An error regarding the password was already found.
49
  if ( $errors->get_error_data( 'pass' ) ) {
 
50
  return $errors;
51
  }
52
 
53
+ if ( isset( $user->user_pass ) || ! $update ) {
54
+ return $errors;
55
+ }
56
 
57
+ // The password was not changed, but an update is occurring. Test to see if we need to prompt for a password change.
58
+ // This also handles the case where a user's role is being changed to one that requires strong password enforcement.
 
59
 
60
+ $strength = get_user_meta( $user->ID, 'itsec-password-strength', true );
61
 
62
+ if ( ! is_numeric( $strength ) || $strength < 0 || $strength > 4 ) {
63
+ // Not enough data to determine whether a change of password is required.
64
+ return $errors;
 
65
  }
66
 
67
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
68
+
69
+ if ( isset( $user->role ) ) {
70
+ $role = $this->get_canonical_role_from_role_and_user( $user->role, $user );
71
+ } else {
72
+ $role = ITSEC_Lib_Canonical_Roles::get_user_role( $user );
73
+ }
74
 
75
+ if ( ! $this->role_requires_strong_password( $role ) ) {
76
+ return $errors;
77
+ }
78
 
79
+ if ( 4 === (int) $strength ) {
80
+ return $errors;
 
 
 
 
 
81
  }
82
 
83
+ if ( ! $update ) {
84
+ $context = 'admin-user-create';
85
+ } elseif ( $user->ID === get_current_user_id() ) {
86
+ $context = 'profile-update';
 
 
87
  } else {
88
+ $context = 'admin-profile-update';
 
89
  }
90
 
91
+ $errors->add( 'pass', $this->make_error_message( $context ) );
92
+
93
+ return $errors;
94
+ }
95
 
96
+ /**
97
+ * Validate a new password according to the configured strength rules.
98
+ *
99
+ * @param WP_Error $error
100
+ * @param WP_User $user
101
+ * @param string $new_password
102
+ * @param array $args
103
+ */
104
+ public function validate_password( $error, $user, $new_password, $args = array() ) {
105
 
106
+ if ( isset( $args['strength'] ) ) {
107
+ $reported_strength = $args['strength'];
108
+ } else {
109
+ $reported_strength = false;
110
+ }
111
 
112
+ if ( empty( $user->ID ) || ! is_numeric( $user->ID ) ) {
113
+ $role = isset( $args['role'] ) ? $args['role'] : get_option( 'default_role', 'subscriber' );
114
+ } elseif ( isset( $args['role'] ) ) {
115
+ $role = $this->get_canonical_role_from_role_and_user( $args['role'], $user );
116
+ } else {
117
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
118
+ $role = ITSEC_Lib_Canonical_Roles::get_user_role( $user );
119
  }
120
 
121
+ if ( ! $this->role_requires_strong_password( $role ) ) {
122
+ return;
 
 
 
 
123
  }
124
 
125
+ if ( ! $this->fails_enforcement( $user, $new_password, $reported_strength ) ) {
126
+ return;
127
+ }
128
+
129
+ $message = $this->make_error_message( $args['context'] );
130
+
131
+ $error->add( 'pass', $message );
132
  }
133
 
134
  /**
135
+ * Retrieve a canonical role for a user and a role.
136
  *
137
+ * @param string $role
138
+ * @param WP_User $user
139
  *
140
+ * @return string
141
  */
142
+ private function get_canonical_role_from_role_and_user( $role, $user ) {
143
+ $role_caps = array_keys( array_filter( wp_roles()->get_role( $role )->capabilities ) );
144
+ $user_caps = array();
 
 
145
 
146
+ if ( isset( $user->caps ) ) {
147
+ $wp_roles = wp_roles();
148
+
149
+ foreach ( $user->caps as $cap => $has ) {
150
+ if ( $has && ! $wp_roles->is_role( $cap ) ) {
151
+ $user_caps[] = $has;
152
+ }
153
+ }
154
  }
155
 
156
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
157
 
158
+ return ITSEC_Lib_Canonical_Roles::get_role_from_caps( array_merge( $role_caps, $user_caps ) );
159
+ }
160
+
161
+ /**
162
+ * Get the strong password error message according to the given context.
163
+ *
164
+ * @param string $context
165
+ *
166
+ * @return string
167
+ */
168
+ private function make_error_message( $context ) {
169
+ $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' );
170
+
171
+ if ( 'admin-user-create' === $context ) {
172
+ $message .= ' ' . __( 'The user has not been created.', 'better-wp-security' );
173
+ } elseif ( 'admin-profile-update' === $context ) {
174
+ $message .= ' ' . __( 'The user changes have not been saved.', 'better-wp-security' );
175
+ } elseif ( 'profile-update' === $context ) {
176
+ $message .= ' ' . __( 'Your profile has not been updated.', 'better-wp-security' );
177
+ } elseif ( 'reset-password' === $context ) {
178
+ $message .= ' ' . __( 'The password has not been updated.', 'better-wp-security' );
179
  }
180
+
181
+ return wp_kses( $message, array( 'strong' => array() ) );
182
+ }
183
+
184
+ /**
185
+ * Does the given role require a strong password.
186
+ *
187
+ * @param string $role The user's canonical role.
188
+ *
189
+ * @return bool
190
+ */
191
+ private function role_requires_strong_password( $role ) {
192
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
193
+
194
+ $min_role = ITSEC_Modules::get_setting( 'strong-passwords', 'role' );
195
+
196
+ return ITSEC_Lib_Canonical_Roles::is_canonical_role_at_least( $min_role, $role );
197
  }
198
 
199
  /**
200
  * Determine if the user requires enforcement and if it fails that enforcement.
201
  *
202
+ * @param WP_User|stdClass $user Requires either a valid WP_User object or an object that has the following members:
203
  * user_login, first_name, last_name, nickname, display_name, user_email, user_url, and
204
  * description. A member of user_pass is required if $password_strength is false.
205
+ * @param string $new_password The user's new password.
206
  * @param int|boolean $password_strength [optional] An integer value representing the password strength, if known, or false.
207
  * Defaults to false.
208
  *
209
  * @return boolean True if the user requires enforcement and has a password weaker than strong. False otherwise.
210
  */
211
+ private function fails_enforcement( $user, $new_password, $password_strength = false ) {
 
 
 
212
 
213
+ if ( false !== $password_strength ) {
214
+ return $password_strength < 4;
215
  }
216
 
217
+ if ( ! empty( $_POST['password_strength'] ) && 'strong' !== $_POST['password_strength'] ) {
218
+ // We want to validate the password strength if the form data says that the password is strong since we want
219
+ // to protect against spoofing. If the form data says that the password isn't strong, believe it.
220
+
221
+ $password_strength = 1;
222
+ } else {
223
+ // The form data does not indicate a password strength or the data claimed that the password is strong,
224
+ // which is a claim that must be validated. Use the zxcvbn library to find the password strength score.
225
+
226
+ $penalty_strings = array(
227
+ get_site_option( 'admin_email' )
228
+ );
229
+ $user_properties = array( 'user_login', 'first_name', 'last_name', 'nickname', 'display_name', 'user_email', 'user_url', 'description' );
230
+
231
+ foreach ( $user_properties as $user_property ) {
232
+ if ( isset( $user->$user_property ) ) {
233
+ $penalty_strings[] = $user->$user_property;
234
+ }
 
 
 
 
 
 
235
  }
 
236
 
237
+ $results = ITSEC_Lib::get_password_strength_results( $new_password, $penalty_strings );
238
+ $password_strength = $results->score;
239
  }
240
 
241
+ return $password_strength < 4;
242
  }
243
  }
244
 
core/modules/system-tweaks/class-itsec-system-tweaks.php CHANGED
@@ -76,6 +76,9 @@ final class ITSEC_System_Tweaks {
76
  return ITSEC_System_Tweaks_Config_Generators::filter_litespeed_server_config_modification( $modification );
77
  }
78
 
 
 
 
79
  public function block_long_urls() {
80
  if ( strlen( $_SERVER['REQUEST_URI'] ) <= 255 ) {
81
  return;
76
  return ITSEC_System_Tweaks_Config_Generators::filter_litespeed_server_config_modification( $modification );
77
  }
78
 
79
+ /**
80
+ * Block long URLs very early in the request cycle on the front-end.
81
+ */
82
  public function block_long_urls() {
83
  if ( strlen( $_SERVER['REQUEST_URI'] ) <= 255 ) {
84
  return;
core/modules/wordpress-tweaks/class-itsec-wordpress-tweaks.php CHANGED
@@ -91,7 +91,7 @@ final class ITSEC_WordPress_Tweaks {
91
  if ( 2 == $this->settings['disable_xmlrpc'] ) {
92
  add_filter( 'xmlrpc_enabled', '__return_null' );
93
  add_filter( 'bloginfo_url', array( $this, 'remove_pingback_url' ), 10, 2 );
94
- } else if ( 1 == $this->settings['disable_xmlrpc'] ) {
95
  add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
96
  }
97
 
@@ -122,7 +122,17 @@ final class ITSEC_WordPress_Tweaks {
122
  }
123
  }
124
 
125
- public function filter_rest_dispatch_request( $result, $request, $route_schema, $handler ) {
 
 
 
 
 
 
 
 
 
 
126
  if ( in_array( $this->settings['rest_api'], array( 'enable', 'default-access' ) ) ) {
127
  return $result;
128
  }
@@ -242,6 +252,15 @@ final class ITSEC_WordPress_Tweaks {
242
  wp_enqueue_script( 'itsec-wt-block-tabnapping', plugins_url( 'js/block-tabnapping.js', __FILE__ ), array( 'blankshield' ), ITSEC_Core::get_plugin_build(), true );
243
  }
244
 
 
 
 
 
 
 
 
 
 
245
  public function block_multiauth_attempts( $filter_val, $username, $password ) {
246
  if ( empty( $this->first_xmlrpc_credentials ) ) {
247
  $this->first_xmlrpc_credentials = array(
@@ -261,11 +280,16 @@ final class ITSEC_WordPress_Tweaks {
261
  die( __( 'XML-RPC services are disabled on this site.' ) );
262
  }
263
 
 
 
 
 
 
 
 
264
  public function current_jquery() {
265
 
266
- global $itsec_is_old_admin;
267
-
268
- if ( ! is_admin() && ! $itsec_is_old_admin ) {
269
 
270
  wp_deregister_script( 'jquery' );
271
  wp_deregister_script( 'jquery-core' );
@@ -300,10 +324,16 @@ final class ITSEC_WordPress_Tweaks {
300
  }
301
 
302
  /**
303
- * Requires a unique nicename on profile update or activate.
 
 
304
  *
305
  * @since 4.0
306
  *
 
 
 
 
307
  * @return void
308
  */
309
  public function force_unique_nicename( &$errors, $update, &$user ) {
91
  if ( 2 == $this->settings['disable_xmlrpc'] ) {
92
  add_filter( 'xmlrpc_enabled', '__return_null' );
93
  add_filter( 'bloginfo_url', array( $this, 'remove_pingback_url' ), 10, 2 );
94
+ } else if ( 1 == $this->settings['disable_xmlrpc'] ) { // Disable pingbacks
95
  add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
96
  }
97
 
122
  }
123
  }
124
 
125
+ /**
126
+ * Require capabilities for reading from WordPress object routes.
127
+ *
128
+ * @param null|WP_REST_Response|WP_Error $result
129
+ * @param WP_REST_Request $request
130
+ * @param string $route_regex
131
+ * @param array $handler
132
+ *
133
+ * @return WP_Error
134
+ */
135
+ public function filter_rest_dispatch_request( $result, $request, $route_regex, $handler ) {
136
  if ( in_array( $this->settings['rest_api'], array( 'enable', 'default-access' ) ) ) {
137
  return $result;
138
  }
252
  wp_enqueue_script( 'itsec-wt-block-tabnapping', plugins_url( 'js/block-tabnapping.js', __FILE__ ), array( 'blankshield' ), ITSEC_Core::get_plugin_build(), true );
253
  }
254
 
255
+ /**
256
+ * Prevent an attacker from trying multiple login credentials in a single XML-RPC request.
257
+ *
258
+ * @param WP_User|WP_Error|null $filter_val
259
+ * @param string $username
260
+ * @param string $password
261
+ *
262
+ * @return null|\WP_User|\WP_Error
263
+ */
264
  public function block_multiauth_attempts( $filter_val, $username, $password ) {
265
  if ( empty( $this->first_xmlrpc_credentials ) ) {
266
  $this->first_xmlrpc_credentials = array(
280
  die( __( 'XML-RPC services are disabled on this site.' ) );
281
  }
282
 
283
+ /**
284
+ * Attempt to force the core version of jQuery to be loaded.
285
+ *
286
+ * This will deregister the current version of jQuery and re-enqueue with the core version of the script.
287
+ *
288
+ * This could probably be refactored to use the 'script_loader_src' filter.
289
+ */
290
  public function current_jquery() {
291
 
292
+ if ( ! is_admin() ) {
 
 
293
 
294
  wp_deregister_script( 'jquery' );
295
  wp_deregister_script( 'jquery-core' );
324
  }
325
 
326
  /**
327
+ * Requires a user's nicename to be distinct from their username.
328
+ *
329
+ * This helps to prevent username leaking.
330
  *
331
  * @since 4.0
332
  *
333
+ * @param \WP_Error $errors
334
+ * @param bool $update
335
+ * @param \WP_User $user
336
+ *
337
  * @return void
338
  */
339
  public function force_unique_nicename( &$errors, $update, &$user ) {
core/{class-itsec-notify.php → notify.php} RENAMED
@@ -68,6 +68,8 @@ class ITSEC_Notify {
68
  * @return
69
  */
70
  public function send_daily_digest() {
 
 
71
  global $itsec_lockout;
72
 
73
 
68
  * @return
69
  */
70
  public function send_daily_digest() {
71
+
72
+ /** @var ITSEC_Lockout $itsec_lockout */
73
  global $itsec_lockout;
74
 
75
 
core/{class-itsec-response.php → response.php} RENAMED
@@ -226,7 +226,7 @@ final class ITSEC_Response {
226
  }
227
  }
228
 
229
- public static function send_json() {
230
  $self = self::get_instance();
231
 
232
  self::maybe_regenerate_wp_config();
@@ -250,6 +250,12 @@ final class ITSEC_Response {
250
  'closeModal' => $self->close_modal,
251
  );
252
 
 
 
 
 
 
 
253
  wp_send_json( $data );
254
  }
255
 
226
  }
227
  }
228
 
229
+ public static function get_raw_data() {
230
  $self = self::get_instance();
231
 
232
  self::maybe_regenerate_wp_config();
250
  'closeModal' => $self->close_modal,
251
  );
252
 
253
+ return $data;
254
+ }
255
+
256
+ public static function send_json() {
257
+ $data = self::get_raw_data();
258
+
259
  wp_send_json( $data );
260
  }
261
 
core/{class-itsec-setup.php → setup.php} RENAMED
@@ -76,7 +76,6 @@ final class ITSEC_Setup {
76
 
77
 
78
  // Ensure that the database tables are present and updated to the current schema.
79
- require_once( ITSEC_Core::get_core_dir() . '/class-itsec-lib.php' );
80
  ITSEC_Lib::create_database_tables();
81
 
82
  // Run activation routines for modules to ensure that they are properly set up.
@@ -144,7 +143,7 @@ final class ITSEC_Setup {
144
 
145
  }
146
 
147
- private function uninstall() {
148
 
149
  global $wpdb;
150
 
76
 
77
 
78
  // Ensure that the database tables are present and updated to the current schema.
 
79
  ITSEC_Lib::create_database_tables();
80
 
81
  // Run activation routines for modules to ensure that they are properly set up.
143
 
144
  }
145
 
146
+ private static function uninstall() {
147
 
148
  global $wpdb;
149
 
core/sidebar-widget-active-lockouts.php CHANGED
@@ -10,6 +10,8 @@ class ITSEC_Settings_Page_Sidebar_Widget_Active_Lockouts extends ITSEC_Settings_
10
  }
11
 
12
  public function render( $form ) {
 
 
13
  global $itsec_lockout;
14
 
15
  $lockouts = $itsec_lockout->get_lockouts( 'all', true );
@@ -78,6 +80,8 @@ class ITSEC_Settings_Page_Sidebar_Widget_Active_Lockouts extends ITSEC_Settings_
78
  }
79
 
80
  protected function save( $data ) {
 
 
81
  global $itsec_lockout;
82
 
83
  $count = 0;
10
  }
11
 
12
  public function render( $form ) {
13
+
14
+ /** @var ITSEC_Lockout $itsec_lockout */
15
  global $itsec_lockout;
16
 
17
  $lockouts = $itsec_lockout->get_lockouts( 'all', true );
80
  }
81
 
82
  protected function save( $data ) {
83
+
84
+ /** @var ITSEC_Lockout $itsec_lockout */
85
  global $itsec_lockout;
86
 
87
  $count = 0;
core/sidebar-widget-temp-whitelist.php CHANGED
@@ -9,6 +9,8 @@ class ITSEC_Settings_Page_Sidebar_Widget_Temp_Whitelist extends ITSEC_Settings_P
9
  }
10
 
11
  public function render( $form ) {
 
 
12
  global $itsec_lockout;
13
 
14
  $lockouts = $itsec_lockout->get_lockouts( 'all', true );
9
  }
10
 
11
  public function render( $form ) {
12
+
13
+ /** @var ITSEC_Lockout $itsec_lockout */
14
  global $itsec_lockout;
15
 
16
  $lockouts = $itsec_lockout->get_lockouts( 'all', true );
core/sync-verbs/itsec-get-temp-whitelist.php CHANGED
@@ -8,6 +8,7 @@ class Ithemes_Sync_Verb_ITSEC_Get_Temp_Whitelist extends Ithemes_Sync_Verb {
8
 
9
 
10
  public function run( $arguments ) {
 
11
  global $itsec_lockout;
12
 
13
  $response = array(
8
 
9
 
10
  public function run( $arguments ) {
11
+ /** @var ITSEC_Lockout $itsec_lockout */
12
  global $itsec_lockout;
13
 
14
  $response = array(
core/sync-verbs/itsec-release-lockout.php CHANGED
@@ -11,6 +11,7 @@ class Ithemes_Sync_Verb_ITSEC_Release_Lockout extends Ithemes_Sync_Verb {
11
 
12
  public function run( $arguments ) {
13
 
 
14
  global $itsec_lockout;
15
 
16
  $id = intval( $arguments['id'] );
11
 
12
  public function run( $arguments ) {
13
 
14
+ /** @var ITSEC_Lockout $itsec_lockout */
15
  global $itsec_lockout;
16
 
17
  $id = intval( $arguments['id'] );
core/sync-verbs/itsec-set-temp-whitelist.php CHANGED
@@ -11,6 +11,7 @@ class Ithemes_Sync_Verb_ITSEC_Set_Temp_Whitelist extends Ithemes_Sync_Verb {
11
 
12
 
13
  public function run( $arguments ) {
 
14
  global $itsec_lockout;
15
 
16
 
11
 
12
 
13
  public function run( $arguments ) {
14
+ /** @var ITSEC_Lockout $itsec_lockout */
15
  global $itsec_lockout;
16
 
17
 
history.txt CHANGED
@@ -627,3 +627,38 @@
627
  Bug Fix: When a requesting IP address cannot be found, default to 127.0.0.1. This fixes issues with some alternate cron setups.
628
  Bug Fix: Having more than one iThemes Security modification in a .htaccess, nginx.conf, or wp-config.php file will no longer result in having all the file content between each section removed when updating the file.
629
  Bug Fix: Modifications to the wp-config.php file added by W3 Total Cache now have their Windows-style newlines preserved when iThemes Security updates the file.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  Bug Fix: When a requesting IP address cannot be found, default to 127.0.0.1. This fixes issues with some alternate cron setups.
628
  Bug Fix: Having more than one iThemes Security modification in a .htaccess, nginx.conf, or wp-config.php file will no longer result in having all the file content between each section removed when updating the file.
629
  Bug Fix: Modifications to the wp-config.php file added by W3 Total Cache now have their Windows-style newlines preserved when iThemes Security updates the file.
630
+ 6.3.0 - 2017-07-06 - Chris Jean & Timothy Jacobs
631
+ Important: The way that Hide Backend functions changes in this release. Previously, if your Hide Backend Login Slug was wplogin, going to example.com/wplogin would result in the URL remaining example.com/wplogin. The new implementation of this feature results in a redirect to a URL that looks as follows: example.com/wp-login.php?itsec-hb-token=wplogin. While this may not be desireable for some users, this change was necessary to fix longstanding compatibility issues with other plugins. Once you access the login page using the Login Slug page, a cookie is set with an expiration time of one hour. As long as the cookie remains, you can access example.com/wp-login.php without having to access the Hide Backend Login Slug first. If you wish to confirm that Hide Backend is working properly on your site, opening up a private browsing window is a quick way to test without having to log out and clear cookies.
632
+
633
+ New Feature: Added support for iThemes Sync to run the Security Check feature from inside the Sync service.
634
+ New Feature: Added support for the ITSEC_DISABLE_MODULES define.
635
+ Bug Fix: Removed warning: "Non-static method ITSEC_Setup::uninstall() should not be called statically".
636
+ Bug Fix: Fixed the ability to manually enter a page number to navigate to on the Security > Logs page.
637
+ Bug Fix: Fixed source of warning that could appear when creating a backup while running a PHP version less than 5.4.
638
+ Bug Fix: Fixed source of notice that could appear when reseting a user's password when the Strong Passwords Enforcement feature is enabled.
639
+ Bug Fix: Fixed bugs that prevented reporting of specific error messages related to updating the wp-config.php file.
640
+ Bug Fix: Fixed an infinite loop that could occur when expiring a cookie and Hide Backend is enabled.
641
+ Bug Fix: Fixed compatibility issue with the Jetpack plugin when Hide Backend is enabled which could prevent Jetpack from redirecting users to the wordpress.com login page.
642
+ Bug Fix: Fixed issue where access to wp-admin/admin-post.php when Hide Backend is enabled.
643
+ Bug Fix: Fixed issue that could prevent "Register" and "Lost your password?" links from working properly on the login page when Hide Backend is enabled.
644
+ Bug Fix: Fix fatal error when updating a profile.
645
+ Bug Fix: Fix strong passwords not being recognized as strong on the profile page.
646
+ Bug Fix: Fix fatal error when registering a new user without specifying a role ( iThemes Exchange ).
647
+ Bug Fix: Compatability with JetPack SSO and Password Requirements.
648
+ Bug Fix: Ensure viewport meta is defined when loading the password requirements update password form.
649
+ Bug Fix: Hide Backend is now compatible with Jetpack Single Sign On.
650
+ Bug Fix: Hide Backend now hides registration pages on multisite sites.
651
+ Bug Fix: Fixed password-protected posts not properly handling the password when Hide Backend is enabled.
652
+ Enhancement: Removed AhrefsBot from the HackRepair blacklist as they are legitimate bot.
653
+ Enhancement: Improved efficiency of Hide Backend code, increasing site performance when the feature is enabled.
654
+ Enhancement: Enforce strong passwords during log-in. Can be disabled via the ITSEC_DISABLE_PASSWORD_REQUIREMENTS constant.
655
+ Enhancement: Use canonical roles library to determine if a new user or an updated role requires a strong password.
656
+ Enhancement: Introduce password requirements module to centralize handling of password updates.
657
+ Enhancement: The Hide Backend hidden login URL is no longer leaked by password-protected content.
658
+ Enhancement: Allow for searching through modules and settings.
659
+ Enhancement: Link to other module settings pages without forcing the page to refresh.
660
+ Enhancement: Fire an action, "itsec_change_admin_user_id", when the admin user id changes.
661
+ Enhancement: Changed default Hide Backend Register Slug from wp-register.php to wp-signup.php since WordPress switched from using wp-register.php to wp-signup.php for registrations. This will not affect existing sites.
662
+ Enhancement: Hide Backend functions purely in PHP code now rather than relying half on PHP code and half on .htaccess and nginx.conf modifications. This allows Hide Backend to function on web servers and server configurations that it was previously not compatible with.
663
+ Misc: Updated or added phpDoc to many functions.
664
+ Misc: Updated Disable File Locking description.
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === iThemes Security (formerly Better WP Security) ===
2
  Contributors: ithemes, chrisjean, gerroald, mattdanner
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.5
5
- Tested up to: 4.7.3
6
- Stable tag: 6.2.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -188,6 +188,41 @@ Free support may be available with the help of the community in the <a href="htt
188
 
189
  == Changelog ==
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  = 6.2.1 =
192
  * Bug Fix: When a requesting IP address cannot be found, default to 127.0.0.1. This fixes issues with some alternate cron setups.
193
  * Bug Fix: Having more than one iThemes Security modification in a .htaccess, nginx.conf, or wp-config.php file will no longer result in having all the file content between each section removed when updating the file.
@@ -1666,5 +1701,5 @@ This release is a complete rewrite from the ground up. Special thanks to Cory Mi
1666
 
1667
  == Upgrade Notice ==
1668
 
1669
- = 6.2.1 =
1670
- Version 6.2.1 contains important bug fixes. It is recommended for all users.
1
  === iThemes Security (formerly Better WP Security) ===
2
  Contributors: ithemes, chrisjean, gerroald, mattdanner
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.6
5
+ Tested up to: 4.8
6
+ Stable tag: 6.3.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
188
 
189
  == Changelog ==
190
 
191
+ = 6.3.0 =
192
+ * Important: The way that Hide Backend functions changes in this release. Previously, if your Hide Backend Login Slug was wplogin, going to example.com/wplogin would result in the URL remaining example.com/wplogin. The new implementation of this feature results in a redirect to a URL that looks as follows: example.com/wp-login.php?itsec-hb-token=wplogin. While this may not be desireable for some users, this change was necessary to fix longstanding compatibility issues with other plugins. Once you access the login page using the Login Slug page, a cookie is set with an expiration time of one hour. As long as the cookie remains, you can access example.com/wp-login.php without having to access the Hide Backend Login Slug first. If you wish to confirm that Hide Backend is working properly on your site, opening up a private browsing window is a quick way to test without having to log out and clear cookies.
193
+ * New Feature: Added support for iThemes Sync to run the Security Check feature from inside the Sync service.
194
+ * New Feature: Added support for the ITSEC_DISABLE_MODULES define.
195
+ * Bug Fix: Removed warning: "Non-static method ITSEC_Setup::uninstall() should not be called statically".
196
+ * Bug Fix: Fixed the ability to manually enter a page number to navigate to on the Security > Logs page.
197
+ * Bug Fix: Fixed source of warning that could appear when creating a backup while running a PHP version less than 5.4.
198
+ * Bug Fix: Fixed source of notice that could appear when reseting a user's password when the Strong Passwords Enforcement feature is enabled.
199
+ * Bug Fix: Fixed bugs that prevented reporting of specific error messages related to updating the wp-config.php file.
200
+ * Bug Fix: Fixed an infinite loop that could occur when expiring a cookie and Hide Backend is enabled.
201
+ * Bug Fix: Fixed compatibility issue with the Jetpack plugin when Hide Backend is enabled which could prevent Jetpack from redirecting users to the wordpress.com login page.
202
+ * Bug Fix: Fixed issue where access to wp-admin/admin-post.php when Hide Backend is enabled.
203
+ * Bug Fix: Fixed issue that could prevent "Register" and "Lost your password?" links from working properly on the login page when Hide Backend is enabled.
204
+ * Bug Fix: Fix fatal error when updating a profile.
205
+ * Bug Fix: Fix strong passwords not being recognized as strong on the profile page.
206
+ * Bug Fix: Fix fatal error when registering a new user without specifying a role ( iThemes Exchange ).
207
+ * Bug Fix: Compatability with JetPack SSO and Password Requirements.
208
+ * Bug Fix: Ensure viewport meta is defined when loading the password requirements update password form.
209
+ * Bug Fix: Hide Backend is now compatible with Jetpack Single Sign On.
210
+ * Bug Fix: Hide Backend now hides registration pages on multisite sites.
211
+ * Bug Fix: Fixed password-protected posts not properly handling the password when Hide Backend is enabled.
212
+ * Enhancement: Removed AhrefsBot from the HackRepair blacklist as they are legitimate bot.
213
+ * Enhancement: Improved efficiency of Hide Backend code, increasing site performance when the feature is enabled.
214
+ * Enhancement: Enforce strong passwords during log-in. Can be disabled via the ITSEC_DISABLE_PASSWORD_REQUIREMENTS constant.
215
+ * Enhancement: Use canonical roles library to determine if a new user or an updated role requires a strong password.
216
+ * Enhancement: Introduce password requirements module to centralize handling of password updates.
217
+ * Enhancement: The Hide Backend hidden login URL is no longer leaked by password-protected content.
218
+ * Enhancement: Allow for searching through modules and settings.
219
+ * Enhancement: Link to other module settings pages without forcing the page to refresh.
220
+ * Enhancement: Fire an action, "itsec_change_admin_user_id", when the admin user id changes.
221
+ * Enhancement: Changed default Hide Backend Register Slug from wp-register.php to wp-signup.php since WordPress switched from using wp-register.php to wp-signup.php for registrations. This will not affect existing sites.
222
+ * Enhancement: Hide Backend functions purely in PHP code now rather than relying half on PHP code and half on .htaccess and nginx.conf modifications. This allows Hide Backend to function on web servers and server configurations that it was previously not compatible with.
223
+ * Misc: Updated or added phpDoc to many functions.
224
+ * Misc: Updated Disable File Locking description.
225
+
226
  = 6.2.1 =
227
  * Bug Fix: When a requesting IP address cannot be found, default to 127.0.0.1. This fixes issues with some alternate cron setups.
228
  * Bug Fix: Having more than one iThemes Security modification in a .htaccess, nginx.conf, or wp-config.php file will no longer result in having all the file content between each section removed when updating the file.
1701
 
1702
  == Upgrade Notice ==
1703
 
1704
+ = 6.3.0 =
1705
+ Version 6.3.0 contains important bug and compatibility fixes. It is recommended for all users.