Shield Security for WordPress - Version 16.1.3

Version Description

Download this release

Release Info

Developer paultgoodchild
Plugin Icon 128x128 Shield Security for WordPress
Version 16.1.3
Comparing to
See all releases

Code changes from version 16.1.2 to 16.1.3

Files changed (36) hide show
  1. cl.json +22 -0
  2. config/deprecated/data.php +1 -1
  3. config/deprecated/login_protect.php +18 -11
  4. config/login_protect.json +18 -11
  5. icwp-wpsf.php +2 -2
  6. plugin-spec.php +5 -5
  7. plugin.json +5 -5
  8. readme.txt +2 -2
  9. resources/css/plugin.css +1 -1
  10. resources/js/shield/login2fa.js +2 -4
  11. src/lib/src/Modules/Base/Strings.php +8 -2
  12. src/lib/src/Modules/Base/UI.php +1 -0
  13. src/lib/src/Modules/IPs/DB/IpRules/MergeAutoBlockRules.php +0 -6
  14. src/lib/src/Modules/IPs/Lib/CrowdSec/Api/DecisionsDownload.php +0 -5
  15. src/lib/src/Modules/IPs/UI.php +1 -1
  16. src/lib/src/Modules/LoginGuard/AjaxHandler.php +9 -10
  17. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/{NoLoginIntentForUserException.php → InvalidLoginIntentException.php} +1 -1
  18. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php +33 -34
  19. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestValidate.php +11 -18
  20. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginRequestCapture.php +6 -6
  21. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/MfaController.php +26 -3
  22. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/BackupCodes.php +1 -1
  23. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/BaseProvider.php +9 -3
  24. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Email.php +44 -31
  25. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/GoogleAuth.php +2 -2
  26. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Sms.php +1 -1
  27. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/U2F.php +1 -1
  28. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Yubikey.php +1 -1
  29. src/lib/src/Modules/LoginGuard/Strings.php +17 -11
  30. src/lib/vendor/composer/autoload_classmap.php +1 -1
  31. src/lib/vendor/composer/autoload_static.php +1 -1
  32. src/lib/vendor/composer/installed.json +6 -6
  33. src/lib/vendor/composer/installed.php +9 -9
  34. src/lib/vendor/fernleafsystems/wordpress-services/src/Utilities/IpUtils.php +5 -3
  35. templates/twig/wpadmin_pages/base.twig +1 -2
  36. templates/twig/wpadmin_pages/components/page/nav_sidebar.twig +10 -1
cl.json CHANGED
@@ -127,6 +127,28 @@
127
  }
128
  ],
129
  "patches": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  {
131
  "version": "2",
132
  "released_at": 1662985000,
127
  }
128
  ],
129
  "patches": [
130
+ {
131
+ "version": "3",
132
+ "released_at": 1663061215,
133
+ "items": [
134
+ {
135
+ "title": "Security fix for reported 2FA vulnerability. More info will be released after allowing time for client upgrades.",
136
+ "type": "fixed",
137
+ "description": [
138
+ "Note: sites are only vulnerable to this particular exploit IF it has an SQL-injection vulnerability caused by another plugin/theme.",
139
+ "As we always say, please ensure you keep ALL your plugins, themes and WordPress core up-to-date, particularly if they have known vulnerabilities!"
140
+ ]
141
+ },
142
+ {
143
+ "title": "Reverted minimum WP version to 3.7 to allow for security patching.",
144
+ "type": "changes"
145
+ },
146
+ {
147
+ "title": "Bug: an error was generated when assessing some IP addresses.",
148
+ "type": "fixed"
149
+ }
150
+ ]
151
+ },
152
  {
153
  "version": "2",
154
  "released_at": 1662985000,
config/deprecated/data.php CHANGED
@@ -54,7 +54,7 @@
54
  "has_deleted_at": false,
55
  "cols_custom": {
56
  "req_id": {
57
- "macro_type": "scenario",
58
  "length": 10,
59
  "attr": [
60
  "UNIQUE"
54
  "has_deleted_at": false,
55
  "cols_custom": {
56
  "req_id": {
57
+ "macro_type": "varchar",
58
  "length": 10,
59
  "attr": [
60
  "UNIQUE"
config/deprecated/login_protect.php CHANGED
@@ -536,14 +536,14 @@
536
  "login_intent_timeout": 5,
537
  "login_intent_max_attempts": 5,
538
  "events": {
539
- "2fa_verify_success": {
540
  "audit_params": [
541
  "user_login",
542
  "method"
543
  ],
544
  "level": "notice"
545
  },
546
- "2fa_verify_fail": {
547
  "audit_params": [
548
  "user_login",
549
  "method"
@@ -551,18 +551,25 @@
551
  "level": "warning",
552
  "offense": true
553
  },
554
- "cooldown_fail": {
 
 
 
 
 
 
 
555
  "level": "warning",
556
  "offense": true
557
  },
558
- "honeypot_fail": {
559
  "audit_params": [
560
  "user_login",
561
  "action"
562
  ],
563
  "level": "warning"
564
  },
565
- "botbox_fail": {
566
  "audit_params": [
567
  "user_login",
568
  "action"
@@ -570,22 +577,22 @@
570
  "level": "warning",
571
  "offense": true
572
  },
573
- "login_block": {
574
  "level": "warning"
575
  },
576
- "block_register": {
577
  "level": "warning"
578
  },
579
- "block_lostpassword": {
580
  "level": "warning"
581
  },
582
- "block_checkout": {
583
  "level": "warning"
584
  },
585
- "hide_login_url": {
586
  "level": "notice"
587
  },
588
- "2fa_success": {
589
  "level": "info",
590
  "recent": true
591
  }
536
  "login_intent_timeout": 5,
537
  "login_intent_max_attempts": 5,
538
  "events": {
539
+ "2fa_verify_success": {
540
  "audit_params": [
541
  "user_login",
542
  "method"
543
  ],
544
  "level": "notice"
545
  },
546
+ "2fa_verify_fail": {
547
  "audit_params": [
548
  "user_login",
549
  "method"
551
  "level": "warning",
552
  "offense": true
553
  },
554
+ "2fa_nonce_verify_fail": {
555
+ "audit_params": [
556
+ "user_login"
557
+ ],
558
+ "level": "alert",
559
+ "offense": true
560
+ },
561
+ "cooldown_fail": {
562
  "level": "warning",
563
  "offense": true
564
  },
565
+ "honeypot_fail": {
566
  "audit_params": [
567
  "user_login",
568
  "action"
569
  ],
570
  "level": "warning"
571
  },
572
+ "botbox_fail": {
573
  "audit_params": [
574
  "user_login",
575
  "action"
577
  "level": "warning",
578
  "offense": true
579
  },
580
+ "login_block": {
581
  "level": "warning"
582
  },
583
+ "block_register": {
584
  "level": "warning"
585
  },
586
+ "block_lostpassword": {
587
  "level": "warning"
588
  },
589
+ "block_checkout": {
590
  "level": "warning"
591
  },
592
+ "hide_login_url": {
593
  "level": "notice"
594
  },
595
+ "2fa_success": {
596
  "level": "info",
597
  "recent": true
598
  }
config/login_protect.json CHANGED
@@ -536,14 +536,14 @@
536
  "login_intent_timeout": 5,
537
  "login_intent_max_attempts": 5,
538
  "events": {
539
- "2fa_verify_success": {
540
  "audit_params": [
541
  "user_login",
542
  "method"
543
  ],
544
  "level": "notice"
545
  },
546
- "2fa_verify_fail": {
547
  "audit_params": [
548
  "user_login",
549
  "method"
@@ -551,18 +551,25 @@
551
  "level": "warning",
552
  "offense": true
553
  },
554
- "cooldown_fail": {
 
 
 
 
 
 
 
555
  "level": "warning",
556
  "offense": true
557
  },
558
- "honeypot_fail": {
559
  "audit_params": [
560
  "user_login",
561
  "action"
562
  ],
563
  "level": "warning"
564
  },
565
- "botbox_fail": {
566
  "audit_params": [
567
  "user_login",
568
  "action"
@@ -570,22 +577,22 @@
570
  "level": "warning",
571
  "offense": true
572
  },
573
- "login_block": {
574
  "level": "warning"
575
  },
576
- "block_register": {
577
  "level": "warning"
578
  },
579
- "block_lostpassword": {
580
  "level": "warning"
581
  },
582
- "block_checkout": {
583
  "level": "warning"
584
  },
585
- "hide_login_url": {
586
  "level": "notice"
587
  },
588
- "2fa_success": {
589
  "level": "info",
590
  "recent": true
591
  }
536
  "login_intent_timeout": 5,
537
  "login_intent_max_attempts": 5,
538
  "events": {
539
+ "2fa_verify_success": {
540
  "audit_params": [
541
  "user_login",
542
  "method"
543
  ],
544
  "level": "notice"
545
  },
546
+ "2fa_verify_fail": {
547
  "audit_params": [
548
  "user_login",
549
  "method"
551
  "level": "warning",
552
  "offense": true
553
  },
554
+ "2fa_nonce_verify_fail": {
555
+ "audit_params": [
556
+ "user_login"
557
+ ],
558
+ "level": "alert",
559
+ "offense": true
560
+ },
561
+ "cooldown_fail": {
562
  "level": "warning",
563
  "offense": true
564
  },
565
+ "honeypot_fail": {
566
  "audit_params": [
567
  "user_login",
568
  "action"
569
  ],
570
  "level": "warning"
571
  },
572
+ "botbox_fail": {
573
  "audit_params": [
574
  "user_login",
575
  "action"
577
  "level": "warning",
578
  "offense": true
579
  },
580
+ "login_block": {
581
  "level": "warning"
582
  },
583
+ "block_register": {
584
  "level": "warning"
585
  },
586
+ "block_lostpassword": {
587
  "level": "warning"
588
  },
589
+ "block_checkout": {
590
  "level": "warning"
591
  },
592
+ "hide_login_url": {
593
  "level": "notice"
594
  },
595
+ "2fa_success": {
596
  "level": "info",
597
  "recent": true
598
  }
icwp-wpsf.php CHANGED
@@ -3,12 +3,12 @@
3
  * Plugin Name: Shield Security
4
  * Plugin URI: https://shsec.io/2f
5
  * Description: Powerful, Easy-To-Use #1 Rated WordPress Security System
6
- * Version: 16.1.2
7
  * Text Domain: wp-simple-firewall
8
  * Domain Path: /languages
9
  * Author: Shield Security
10
  * Author URI: https://shsec.io/bv
11
- * Requires at least: 4.7
12
  * Requires PHP: 7.0
13
  */
14
 
3
  * Plugin Name: Shield Security
4
  * Plugin URI: https://shsec.io/2f
5
  * Description: Powerful, Easy-To-Use #1 Rated WordPress Security System
6
+ * Version: 16.1.3
7
  * Text Domain: wp-simple-firewall
8
  * Domain Path: /languages
9
  * Author: Shield Security
10
  * Author URI: https://shsec.io/bv
11
+ * Requires at least: 3.7
12
  * Requires PHP: 7.0
13
  */
14
 
plugin-spec.php CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "properties": {
3
- "version": "16.1.2",
4
- "release_timestamp": 1662985001,
5
- "build": "202209.1202",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "text_domain": "wp-simple-firewall",
@@ -18,7 +18,7 @@
18
  },
19
  "requirements": {
20
  "php": "7.0",
21
- "wordpress": "4.7",
22
  "mysql": "5.6"
23
  },
24
  "reqs_rest": {
@@ -36,7 +36,7 @@
36
  "wp": "3.7",
37
  "mysql": "5.6"
38
  },
39
- "16.0": {
40
  "php": "7.0",
41
  "wp": "4.7",
42
  "mysql": "5.6"
1
  {
2
  "properties": {
3
+ "version": "16.1.3",
4
+ "release_timestamp": 1663061215,
5
+ "build": "202209.1301",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "text_domain": "wp-simple-firewall",
18
  },
19
  "requirements": {
20
  "php": "7.0",
21
+ "wordpress": "3.7",
22
  "mysql": "5.6"
23
  },
24
  "reqs_rest": {
36
  "wp": "3.7",
37
  "mysql": "5.6"
38
  },
39
+ "16.2": {
40
  "php": "7.0",
41
  "wp": "4.7",
42
  "mysql": "5.6"
plugin.json CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "properties": {
3
- "version": "16.1.2",
4
- "release_timestamp": 1662985001,
5
- "build": "202209.1202",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "text_domain": "wp-simple-firewall",
@@ -18,7 +18,7 @@
18
  },
19
  "requirements": {
20
  "php": "7.0",
21
- "wordpress": "4.7",
22
  "mysql": "5.6"
23
  },
24
  "reqs_rest": {
@@ -36,7 +36,7 @@
36
  "wp": "3.7",
37
  "mysql": "5.6"
38
  },
39
- "16.0": {
40
  "php": "7.0",
41
  "wp": "4.7",
42
  "mysql": "5.6"
1
  {
2
  "properties": {
3
+ "version": "16.1.3",
4
+ "release_timestamp": 1663061215,
5
+ "build": "202209.1301",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "text_domain": "wp-simple-firewall",
18
  },
19
  "requirements": {
20
  "php": "7.0",
21
+ "wordpress": "3.7",
22
  "mysql": "5.6"
23
  },
24
  "reqs_rest": {
36
  "wp": "3.7",
37
  "mysql": "5.6"
38
  },
39
+ "16.2": {
40
  "php": "7.0",
41
  "wp": "4.7",
42
  "mysql": "5.6"
readme.txt CHANGED
@@ -4,11 +4,11 @@ Donate link: https://shsec.io/bw
4
  License: GPLv3
5
  License URI: http://www.gnu.org/licenses/gpl.html
6
  Tags: limit login, malware scan, firewall, two factor authentication, login protection
7
- Requires at least: 4.7
8
  Requires PHP: 7.0
9
  Recommended PHP: 7.4
10
  Tested up to: 6.0
11
- Stable tag: 16.1.2
12
 
13
  Bad Bots Are Your #1 Security Risk. Stop playing whack-a-mole with malware and vulnerabilities. Discover the advantage of putting real security before marketing.
14
 
4
  License: GPLv3
5
  License URI: http://www.gnu.org/licenses/gpl.html
6
  Tags: limit login, malware scan, firewall, two factor authentication, login protection
7
+ Requires at least: 3.7
8
  Requires PHP: 7.0
9
  Recommended PHP: 7.4
10
  Tested up to: 6.0
11
+ Stable tag: 16.1.3
12
 
13
  Bad Bots Are Your #1 Security Risk. Stop playing whack-a-mole with malware and vulnerabilities. Discover the advantage of putting real security before marketing.
14
 
resources/css/plugin.css CHANGED
@@ -547,7 +547,7 @@ a:focus .gravatar, a:focus, a:focus .media-icon img {
547
  .table-success, .table-success > td, .table-success > th {
548
  background-color: rgba(0, 128, 0, 0.20);
549
  }
550
- a[target="_blank"]:not(.option_link_info):not(.card-link):not(.table-link):not(.meter-analysis-link):not(.page-header-item)::after {
551
  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
552
  margin: 0 3px;
553
  }
547
  .table-success, .table-success > td, .table-success > th {
548
  background-color: rgba(0, 128, 0, 0.20);
549
  }
550
+ a[target="_blank"]:not(.shield-footer-version):not(.option_link_info):not(.card-link):not(.table-link):not(.meter-analysis-link):not(.page-header-item)::after {
551
  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
552
  margin: 0 3px;
553
  }
resources/js/shield/login2fa.js CHANGED
@@ -2,8 +2,6 @@ jQuery( document ).ready( function () {
2
 
3
  let $body = jQuery( 'body' );
4
  let $theForm = jQuery( 'form#loginform' );
5
- let userID = jQuery( 'input[type=hidden]#wp_user_id' ).val();
6
- let loginNonce = jQuery( 'input[type=hidden]#login_nonce' ).val();
7
  let $u2fStart = jQuery( 'input#btn_u2f_start' );
8
 
9
  jQuery( 'input[type=text]:first', $theForm ).focus();
@@ -57,8 +55,8 @@ jQuery( document ).ready( function () {
57
  $this.attr( 'disabled', true );
58
 
59
  let reqParams = $emailInput.data( 'ajax_intent_email_send' );
60
- reqParams.wp_user_id = userID;
61
- reqParams.login_nonce = loginNonce;
62
  $body.addClass( 'shield-busy' );
63
  jQuery.post( reqParams.ajaxurl, reqParams, function ( response ) {
64
  let msg = 'Communications error with site.';
2
 
3
  let $body = jQuery( 'body' );
4
  let $theForm = jQuery( 'form#loginform' );
 
 
5
  let $u2fStart = jQuery( 'input#btn_u2f_start' );
6
 
7
  jQuery( 'input[type=text]:first', $theForm ).focus();
55
  $this.attr( 'disabled', true );
56
 
57
  let reqParams = $emailInput.data( 'ajax_intent_email_send' );
58
+ reqParams.wp_user_id = jQuery( 'input[type=hidden]#wp_user_id' ).val();
59
+ reqParams.login_nonce = jQuery( 'input[type=hidden]#login_nonce' ).val();
60
  $body.addClass( 'shield-busy' );
61
  jQuery.post( reqParams.ajaxurl, reqParams, function ( response ) {
62
  let msg = 'Communications error with site.';
src/lib/src/Modules/Base/Strings.php CHANGED
@@ -126,9 +126,15 @@ class Strings {
126
  'wphashes_token' => 'ShieldPRO API Token',
127
  'is_opt_importexport' => __( 'Is this option included with import/export?', 'wp-simple-firewall' ),
128
 
129
- 'search_select' => [
130
  'title' => ucwords( __( 'Search for a plugin option', 'wp-simple-firewall' ) ),
131
- ]
 
 
 
 
 
 
132
  ],
133
  $this->getAdditionalDisplayStrings()
134
  );
126
  'wphashes_token' => 'ShieldPRO API Token',
127
  'is_opt_importexport' => __( 'Is this option included with import/export?', 'wp-simple-firewall' ),
128
 
129
+ 'search_select' => [
130
  'title' => ucwords( __( 'Search for a plugin option', 'wp-simple-firewall' ) ),
131
+ ],
132
+ 'running_version' => sprintf( '%s %s', $con->getHumanName(),
133
+ Services::WpPlugins()->isUpdateAvailable( $con->base_file ) ?
134
+ sprintf( '<a href="%s" target="_blank" class="text-danger shield-footer-version">%s</a>',
135
+ Services::WpGeneral()->getAdminUrl_Updates(), $con->getVersion() )
136
+ : $con->getVersion()
137
+ ),
138
  ],
139
  $this->getAdditionalDisplayStrings()
140
  );
src/lib/src/Modules/Base/UI.php CHANGED
@@ -84,6 +84,7 @@ class UI {
84
  'vars' => [
85
  'mod_slug' => $mod->getModSlug(),
86
  'unique_render_id' => uniqid(),
 
87
  ],
88
  ];
89
  }
84
  'vars' => [
85
  'mod_slug' => $mod->getModSlug(),
86
  'unique_render_id' => uniqid(),
87
+ 'plugin_version' => $con->getVersion(),
88
  ],
89
  ];
90
  }
src/lib/src/Modules/IPs/DB/IpRules/MergeAutoBlockRules.php CHANGED
@@ -66,11 +66,6 @@ class MergeAutoBlockRules extends ExecOnceModConsumer {
66
  }
67
 
68
  if ( !empty( $toKeep ) && !empty( $idsToDelete ) ) {
69
- error_log( var_export( $toKeep->ip, true ) );
70
- error_log( var_export( $workingIP, true ) );
71
- error_log( var_export( $idsToDelete, true ) );
72
- error_log( var_export( $extraOffenses, true ) );
73
-
74
  $mod->getDbH_IPRules()
75
  ->getQueryDeleter()
76
  ->addWhereIn( 'id', $idsToDelete )
@@ -88,7 +83,6 @@ class MergeAutoBlockRules extends ExecOnceModConsumer {
88
  $updater->updateRecord( $toKeep, $updateData );
89
 
90
  $toKeep = $mod->getDbH_IPRules()->getQuerySelector()->byId( $toKeep->id );
91
- error_log( var_export( $toKeep, true ) );
92
  }
93
 
94
  return $toKeep;
66
  }
67
 
68
  if ( !empty( $toKeep ) && !empty( $idsToDelete ) ) {
 
 
 
 
 
69
  $mod->getDbH_IPRules()
70
  ->getQueryDeleter()
71
  ->addWhereIn( 'id', $idsToDelete )
83
  $updater->updateRecord( $toKeep, $updateData );
84
 
85
  $toKeep = $mod->getDbH_IPRules()->getQuerySelector()->byId( $toKeep->id );
 
86
  }
87
 
88
  return $toKeep;
src/lib/src/Modules/IPs/Lib/CrowdSec/Api/DecisionsDownload.php CHANGED
@@ -15,11 +15,6 @@ class DecisionsDownload extends BaseAuth {
15
  $this->request_method = 'get';
16
  $decisions = $this->sendReq();
17
  if ( !is_array( $decisions ) || !isset( $decisions[ 'new' ] ) || !isset( $decisions[ 'deleted' ] ) ) {
18
- error_log( var_export( $this->getApiRequestUrl(), true ) );
19
- error_log( var_export( $this->getRequestParams(), true ) );
20
- error_log( var_export( $this->params_body, true ) );
21
- error_log( var_export( $this->params_query, true ) );
22
- error_log( var_export( $this->headers, true ) );
23
  throw new DownloadDecisionsStreamFailedException( sprintf( 'Failed to download decisions: %s',
24
  var_export( $this->last_http_req->lastResponse->body, true ) ) );
25
  }
15
  $this->request_method = 'get';
16
  $decisions = $this->sendReq();
17
  if ( !is_array( $decisions ) || !isset( $decisions[ 'new' ] ) || !isset( $decisions[ 'deleted' ] ) ) {
 
 
 
 
 
18
  throw new DownloadDecisionsStreamFailedException( sprintf( 'Failed to download decisions: %s',
19
  var_export( $this->last_http_req->lastResponse->body, true ) ) );
20
  }
src/lib/src/Modules/IPs/UI.php CHANGED
@@ -41,7 +41,7 @@ class UI extends BaseShield\UI {
41
  'label_help' => __( 'A helpful label to describe this IP rule.', 'wp-simple-firewall' ),
42
  'label_help_max' => sprintf( '%s: %s', __( '255 characters max', 'wp-simple-firewall' ), 'a-z,0-9' ),
43
  'ip_address' => __( 'IP Address or IP Range', 'wp-simple-firewall' ),
44
- 'ip_address_help' => __( 'IPv4 or Ipv6; CIDR ranges only.', 'wp-simple-firewall' ),
45
  'add_rule' => __( 'Add New IP Rule', 'wp-simple-firewall' ),
46
  'confirm' => __( "I fully understand the significance of this action", 'wp-simple-firewall' ),
47
  ],
41
  'label_help' => __( 'A helpful label to describe this IP rule.', 'wp-simple-firewall' ),
42
  'label_help_max' => sprintf( '%s: %s', __( '255 characters max', 'wp-simple-firewall' ), 'a-z,0-9' ),
43
  'ip_address' => __( 'IP Address or IP Range', 'wp-simple-firewall' ),
44
+ 'ip_address_help' => __( 'IPv4 or IPv6; Single Address or CIDR Range', 'wp-simple-firewall' ),
45
  'add_rule' => __( 'Add New IP Rule', 'wp-simple-firewall' ),
46
  'confirm' => __( "I fully understand the significance of this action", 'wp-simple-firewall' ),
47
  ],
src/lib/src/Modules/LoginGuard/AjaxHandler.php CHANGED
@@ -232,19 +232,18 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
232
  public function ajaxExec_IntentEmailSend() :array {
233
  /** @var ModCon $mod */
234
  $mod = $this->getMod();
235
- $mfaCon = $mod->getMfaController();
236
 
237
  $success = false;
238
- $userID = Services::Request()->post( 'wp_user_id' );
239
- $loginNonce = Services::Request()->post( 'login_nonce' );
240
- if ( !empty( $userID ) && !empty( $loginNonce ) ) {
241
  $user = Services::WpUsers()->getUserById( $userID );
242
- $nonces = array_keys( $mfaCon->getActiveLoginIntents( $user ) );
243
- if ( $user instanceof \WP_User && in_array( $loginNonce, $nonces ) ) {
244
- /** @var TwoFactor\Provider\Email $provider */
245
- $provider = $mod->getMfaController()
246
- ->getProvidersForUser( $user, true )[ TwoFactor\Provider\Email::SLUG ] ?? null;
247
- $success = !empty( $provider ) && $provider->sendEmailTwoFactorVerify( $loginNonce );
248
  }
249
  }
250
 
232
  public function ajaxExec_IntentEmailSend() :array {
233
  /** @var ModCon $mod */
234
  $mod = $this->getMod();
235
+ $req = Services::Request();
236
 
237
  $success = false;
238
+ $userID = $req->post( 'wp_user_id' );
239
+ $plainNonce = $req->post( 'login_nonce' );
240
+ if ( !empty( $userID ) && !empty( $plainNonce ) ) {
241
  $user = Services::WpUsers()->getUserById( $userID );
242
+ if ( $user instanceof \WP_User ) {
243
+ /** @var TwoFactor\Provider\Email $p */
244
+ $p = $mod->getMfaController()
245
+ ->getProvidersForUser( $user, true )[ TwoFactor\Provider\Email::SLUG ] ?? null;
246
+ $success = !empty( $p ) && $p->sendEmailTwoFactorVerify( $plainNonce );
 
247
  }
248
  }
249
 
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/{NoLoginIntentForUserException.php → InvalidLoginIntentException.php} RENAMED
@@ -2,6 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Exceptions;
4
 
5
- class NoLoginIntentForUserException extends \Exception {
6
 
7
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Exceptions;
4
 
5
+ class InvalidLoginIntentException extends \Exception {
6
 
7
  }
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php CHANGED
@@ -8,7 +8,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Exc
8
  CouldNotValidate2FA,
9
  LoginCancelException,
10
  NoActiveProvidersForUserException,
11
- NoLoginIntentForUserException,
12
  NotValidUserException,
13
  TooManyAttemptsException
14
  };
@@ -16,6 +16,11 @@ use FernleafSystems\Wordpress\Services\Services;
16
 
17
  class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModConsumer {
18
 
 
 
 
 
 
19
  protected function canRun() :bool {
20
  return Services::Request()->isPost()
21
  && $this->getCon()->getShieldAction() === 'wp_login_2fa_verify'
@@ -32,34 +37,39 @@ class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModCo
32
  $mfaCon = $mod->getMfaController();
33
  $req = Services::Request();
34
 
35
- $user = $req->post( 'wp_user_id' ) ? Services::WpUsers()->getUserById( $req->post( 'wp_user_id' ) ) : null;
36
-
37
  try {
 
 
 
 
 
38
  $this->capture();
39
  }
40
- catch ( LoginCancelException $e ) {
41
- $redirect = $req->post( 'cancel_href' );
42
- empty( $redirect ) ? Services::Response()->redirectToLogin() : Services::Response()->redirect( $redirect );
43
- }
44
  catch ( NotValidUserException $e ) {
45
- // put error about no login intent
46
  Services::Response()->redirectToLogin( [
47
  'shield_msg' => 'no_user_login_intent'
48
  ] );
49
  }
50
- catch ( NoLoginIntentForUserException $e ) {
51
- // put error about no login intent
52
  Services::Response()->redirectToLogin( [
53
  'shield_msg' => 'no_user_login_intent'
54
  ] );
55
  }
 
 
 
 
 
 
56
  catch ( TooManyAttemptsException $e ) {
57
- // put error about no login intent
58
  Services::Response()->redirectToLogin( [
59
  'shield_msg' => 'too_many_attempts'
60
  ] );
61
  }
62
  catch ( NoActiveProvidersForUserException $e ) {
 
63
  Services::Response()->redirectToLogin( [
64
  'shield_msg' => 'no_providers'
65
  ] );
@@ -68,7 +78,7 @@ class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModCo
68
  // Allow a further attempt to 2FA
69
  $pageRender = $mfaCon->useLoginIntentPage() ? new Render\RenderLoginIntentPage() : new Render\RenderWpLoginReplica();
70
  $pageRender->setMod( $mod )
71
- ->setWpUser( $user );
72
  $pageRender->login_nonce = $req->request( 'login_nonce', false, '' );
73
  $pageRender->redirect_to = $req->request( 'redirect_to', false, '' );
74
  $pageRender->rememberme = $req->request( 'rememberme' );
@@ -82,8 +92,7 @@ class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModCo
82
  * @throws Exceptions\TooManyAttemptsException
83
  * @throws LoginCancelException
84
  * @throws NoActiveProvidersForUserException
85
- * @throws NoLoginIntentForUserException
86
- * @throws NotValidUserException
87
  */
88
  private function capture() {
89
  $con = $this->getCon();
@@ -93,32 +102,22 @@ class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModCo
93
  $opts = $this->getOptions();
94
  $req = Services::Request();
95
 
 
 
 
 
 
96
  if ( $req->post( 'cancel' ) ) {
97
  throw new LoginCancelException();
98
  }
99
 
100
- $user = Services::WpUsers()->getUserById( $req->post( 'wp_user_id' ) );
101
- if ( empty( $user ) ) {
102
- throw new NotValidUserException();
103
- }
104
-
105
- $nonce = (string)$req->post( 'login_nonce' );
106
- if ( !preg_match( '#^[\da-z]{10}$#i', $nonce ) ) {
107
- throw new NoLoginIntentForUserException();
108
- }
109
-
110
- $valid = ( new LoginIntentRequestValidate() )
111
- ->setMod( $mod )
112
- ->setWpUser( $user )
113
- ->run( $nonce );
114
-
115
  if ( $valid ) {
116
- wp_set_auth_cookie( $user->ID, (bool)$req->post( 'rememberme' ) );
117
 
118
  if ( $req->post( 'skip_mfa' ) === 'Y' ) {
119
  ( new MfaSkip() )
120
  ->setMod( $this->getMod() )
121
- ->addMfaSkip( $user );
122
  }
123
 
124
  $con->fireEvent( '2fa_success' );
@@ -131,7 +130,7 @@ class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModCo
131
  }, 100, 0 );
132
  $renderer = ( new Render\RenderWpLoginReplica() )
133
  ->setMod( $mod )
134
- ->setWpUser( $user );
135
  $renderer->interim_message = __( '2FA authentication verified successfully.', 'wp-simple-firewall' );
136
  $renderer->include_body = false;
137
  $renderer->render();
@@ -141,12 +140,12 @@ class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModCo
141
  if ( $opts->isEnabledBackupCodes() ) {
142
  $flash .= ' '.__( 'If you used your Backup Code, you will need to reset it.', 'wp-simple-firewall' );
143
  }
144
- $this->getMod()->setFlashAdminNotice( $flash, $user );
145
  }
146
 
147
  $redirect = $req->request( 'redirect_to', false, $req->getPath() );
148
  Services::Response()->redirect(
149
- apply_filters( 'login_redirect', $redirect, $redirect, $user ),
150
  [], true, false
151
  );
152
  }
8
  CouldNotValidate2FA,
9
  LoginCancelException,
10
  NoActiveProvidersForUserException,
11
+ InvalidLoginIntentException,
12
  NotValidUserException,
13
  TooManyAttemptsException
14
  };
16
 
17
  class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModConsumer {
18
 
19
+ /**
20
+ * @var \WP_User
21
+ */
22
+ private $user;
23
+
24
  protected function canRun() :bool {
25
  return Services::Request()->isPost()
26
  && $this->getCon()->getShieldAction() === 'wp_login_2fa_verify'
37
  $mfaCon = $mod->getMfaController();
38
  $req = Services::Request();
39
 
 
 
40
  try {
41
+ $user = $req->post( 'wp_user_id' ) ? Services::WpUsers()->getUserById( $req->post( 'wp_user_id' ) ) : null;
42
+ if ( empty( $user ) ) {
43
+ throw new NotValidUserException();
44
+ }
45
+ $this->user = $user;
46
  $this->capture();
47
  }
 
 
 
 
48
  catch ( NotValidUserException $e ) {
49
+ // output error about no login intent so there's no way to discern externally whether such a user exists
50
  Services::Response()->redirectToLogin( [
51
  'shield_msg' => 'no_user_login_intent'
52
  ] );
53
  }
54
+ catch ( InvalidLoginIntentException $e ) {
 
55
  Services::Response()->redirectToLogin( [
56
  'shield_msg' => 'no_user_login_intent'
57
  ] );
58
  }
59
+ catch ( LoginCancelException $e ) {
60
+ // This should always be a user since we can only throw this exception after loading the user
61
+ $this->getCon()->getUserMeta( $this->user )->login_intents = [];
62
+ $redirect = $req->post( 'cancel_href' );
63
+ empty( $redirect ) ? Services::Response()->redirectToLogin() : Services::Response()->redirect( $redirect );
64
+ }
65
  catch ( TooManyAttemptsException $e ) {
66
+ $this->getCon()->getUserMeta( $this->user )->login_intents = [];
67
  Services::Response()->redirectToLogin( [
68
  'shield_msg' => 'too_many_attempts'
69
  ] );
70
  }
71
  catch ( NoActiveProvidersForUserException $e ) {
72
+ $this->getCon()->getUserMeta( $this->user )->login_intents = [];
73
  Services::Response()->redirectToLogin( [
74
  'shield_msg' => 'no_providers'
75
  ] );
78
  // Allow a further attempt to 2FA
79
  $pageRender = $mfaCon->useLoginIntentPage() ? new Render\RenderLoginIntentPage() : new Render\RenderWpLoginReplica();
80
  $pageRender->setMod( $mod )
81
+ ->setWpUser( $this->user );
82
  $pageRender->login_nonce = $req->request( 'login_nonce', false, '' );
83
  $pageRender->redirect_to = $req->request( 'redirect_to', false, '' );
84
  $pageRender->rememberme = $req->request( 'rememberme' );
92
  * @throws Exceptions\TooManyAttemptsException
93
  * @throws LoginCancelException
94
  * @throws NoActiveProvidersForUserException
95
+ * @throws InvalidLoginIntentException
 
96
  */
97
  private function capture() {
98
  $con = $this->getCon();
102
  $opts = $this->getOptions();
103
  $req = Services::Request();
104
 
105
+ $valid = ( new LoginIntentRequestValidate() )
106
+ ->setMod( $mod )
107
+ ->setWpUser( $this->user )
108
+ ->run( (string)$req->post( 'login_nonce' ) );
109
+
110
  if ( $req->post( 'cancel' ) ) {
111
  throw new LoginCancelException();
112
  }
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  if ( $valid ) {
115
+ wp_set_auth_cookie( $this->user->ID, (bool)$req->post( 'rememberme' ) );
116
 
117
  if ( $req->post( 'skip_mfa' ) === 'Y' ) {
118
  ( new MfaSkip() )
119
  ->setMod( $this->getMod() )
120
+ ->addMfaSkip( $this->user );
121
  }
122
 
123
  $con->fireEvent( '2fa_success' );
130
  }, 100, 0 );
131
  $renderer = ( new Render\RenderWpLoginReplica() )
132
  ->setMod( $mod )
133
+ ->setWpUser( $this->user );
134
  $renderer->interim_message = __( '2FA authentication verified successfully.', 'wp-simple-firewall' );
135
  $renderer->include_body = false;
136
  $renderer->render();
140
  if ( $opts->isEnabledBackupCodes() ) {
141
  $flash .= ' '.__( 'If you used your Backup Code, you will need to reset it.', 'wp-simple-firewall' );
142
  }
143
+ $this->getMod()->setFlashAdminNotice( $flash, $this->user );
144
  }
145
 
146
  $redirect = $req->request( 'redirect_to', false, $req->getPath() );
147
  Services::Response()->redirect(
148
+ apply_filters( 'login_redirect', $redirect, $redirect, $this->user ),
149
  [], true, false
150
  );
151
  }
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestValidate.php CHANGED
@@ -6,7 +6,7 @@ use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Exceptions\{
7
  CouldNotValidate2FA,
8
  NoActiveProvidersForUserException,
9
- NoLoginIntentForUserException,
10
  TooManyAttemptsException
11
  };
12
 
@@ -18,17 +18,17 @@ class LoginIntentRequestValidate {
18
  /**
19
  * @throws CouldNotValidate2FA
20
  * @throws NoActiveProvidersForUserException
21
- * @throws NoLoginIntentForUserException
22
  * @throws TooManyAttemptsException
23
  */
24
- public function run( string $loginNonce ) :bool {
25
  /** @var Shield\Modules\LoginGuard\ModCon $mod */
26
  $mod = $this->getMod();
27
  $mfaCon = $mod->getMfaController();
28
  $user = $this->getWpUser();
29
 
30
- if ( empty( $mfaCon->getActiveLoginIntents( $user )[ $loginNonce ] ) ) {
31
- throw new NoLoginIntentForUserException();
32
  }
33
 
34
  $providers = $mfaCon->getProvidersForUser( $user, true );
@@ -39,30 +39,23 @@ class LoginIntentRequestValidate {
39
  $validated = false;
40
  foreach ( $providers as $provider ) {
41
  $provider->setUser( $user );
42
- if ( $provider->validateLoginIntent( $loginNonce ) ) {
43
  $provider->postSuccessActions();
44
  $validated = true;
45
  break;
46
  }
47
  }
48
 
49
- // Always remove intent after success, otherwise increment attempts.
50
- $intents = $mfaCon->getActiveLoginIntents( $user );
51
- if ( $validated ) {
52
- unset( $intents[ $loginNonce ] );
53
- }
54
- else {
55
- $intents[ $loginNonce ][ 'attempts' ]++;
56
- }
57
- $this->getCon()->getUserMeta( $user )->login_intents = $intents;
58
-
59
  if ( !$validated ) {
60
- if ( empty( $mfaCon->getActiveLoginIntents( $user )[ $loginNonce ] ) ) {
 
61
  throw new TooManyAttemptsException();
62
  }
63
- throw new CouldNotValidate2FA();
64
  }
65
 
 
 
 
66
  return true;
67
  }
68
  }
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Exceptions\{
7
  CouldNotValidate2FA,
8
  NoActiveProvidersForUserException,
9
+ InvalidLoginIntentException,
10
  TooManyAttemptsException
11
  };
12
 
18
  /**
19
  * @throws CouldNotValidate2FA
20
  * @throws NoActiveProvidersForUserException
21
+ * @throws InvalidLoginIntentException
22
  * @throws TooManyAttemptsException
23
  */
24
+ public function run( string $plainNonce ) :bool {
25
  /** @var Shield\Modules\LoginGuard\ModCon $mod */
26
  $mod = $this->getMod();
27
  $mfaCon = $mod->getMfaController();
28
  $user = $this->getWpUser();
29
 
30
+ if ( !$mfaCon->verifyLoginNonce( $user, $plainNonce ) ) {
31
+ throw new InvalidLoginIntentException();
32
  }
33
 
34
  $providers = $mfaCon->getProvidersForUser( $user, true );
39
  $validated = false;
40
  foreach ( $providers as $provider ) {
41
  $provider->setUser( $user );
42
+ if ( $provider->validateLoginIntent( $mfaCon->findHashedNonce( $user, $plainNonce ) ) ) {
43
  $provider->postSuccessActions();
44
  $validated = true;
45
  break;
46
  }
47
  }
48
 
 
 
 
 
 
 
 
 
 
 
49
  if ( !$validated ) {
50
+ throw new CouldNotValidate2FA();
51
+ if ( empty( $mfaCon->getActiveLoginIntents( $user )[ $plainNonce ] ) ) {
52
  throw new TooManyAttemptsException();
53
  }
 
54
  }
55
 
56
+ // Always remove intents after success.
57
+ $this->getCon()->getUserMeta( $user )->login_intents = [];
58
+
59
  return true;
60
  }
61
  }
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginRequestCapture.php CHANGED
@@ -27,17 +27,17 @@ class LoginRequestCapture extends Shield\Modules\Base\Common\ExecOnceModConsumer
27
  ->captureLoginAttempt();
28
  }
29
 
30
- // login nonce
31
- $randStart = rand( 0, 10 );
32
- $loginNonce = substr( hash( 'sha256', uniqid( '', true ) ), $randStart, 10 );
33
 
34
- $meta = $this->getCon()->getUserMeta( $user );
35
  $intents = $mfaCon->getActiveLoginIntents( $user );
36
- $intents[ $loginNonce ] = [
 
37
  'start' => Services::Request()->ts(),
38
  'attempts' => 0,
39
  ];
40
- $meta->login_intents = $intents;
 
41
 
42
  $loggedInCookie = $this->getLoggedInCookie();
43
  if ( !empty( $loggedInCookie ) ) {
27
  ->captureLoginAttempt();
28
  }
29
 
30
+ $loginNonce = bin2hex( random_bytes( 32 ) );
31
+ $loginNonceHashed = wp_hash_password( $loginNonce.$user->ID );
 
32
 
 
33
  $intents = $mfaCon->getActiveLoginIntents( $user );
34
+ $intents[ $loginNonceHashed ] = [
35
+ 'hash' => $loginNonceHashed,
36
  'start' => Services::Request()->ts(),
37
  'attempts' => 0,
38
  ];
39
+
40
+ $this->getCon()->getUserMeta( $user )->login_intents = $intents;
41
 
42
  $loggedInCookie = $this->getLoggedInCookie();
43
  if ( !empty( $loggedInCookie ) ) {
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/MfaController.php CHANGED
@@ -60,9 +60,9 @@ class MfaController extends Shield\Modules\Base\Common\ExecOnceModConsumer {
60
  /** @var Provider\Email|null $emailProvider */
61
  $emailProvider = $providers[ Provider\Email::SLUG ] ?? null;
62
  if ( count( $providers ) === 1 && !empty( $emailProvider ) ) {
63
- $nonces = array_keys( $this->getActiveLoginIntents( $user ) );
64
- $latestNonce = (string)array_pop( $nonces );
65
- $auto = !$emailProvider->hasOtpForNonce( $latestNonce );
66
  }
67
  return $auto;
68
  }
@@ -207,4 +207,27 @@ class MfaController extends Shield\Modules\Base\Common\ExecOnceModConsumer {
207
  }
208
  );
209
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  }
60
  /** @var Provider\Email|null $emailProvider */
61
  $emailProvider = $providers[ Provider\Email::SLUG ] ?? null;
62
  if ( count( $providers ) === 1 && !empty( $emailProvider ) ) {
63
+ $intents = $this->getActiveLoginIntents( $user );
64
+ $latest = array_pop( $intents );
65
+ $auto = !empty( $latest ) && empty( $latest[ 'auto_email_sent' ] );
66
  }
67
  return $auto;
68
  }
207
  }
208
  );
209
  }
210
+
211
+ public function findHashedNonce( \WP_User $user, string $plainNonce ) :string {
212
+ $hashedNonce = '';
213
+ foreach ( array_keys( $this->getActiveLoginIntents( $user ) ) as $maybeHash ) {
214
+ if ( wp_check_password( $plainNonce.$user->ID, $maybeHash ) ) {
215
+ $hashedNonce = $maybeHash;
216
+ break;
217
+ }
218
+ }
219
+ return $hashedNonce;
220
+ }
221
+
222
+ public function verifyLoginNonce( \WP_User $user, string $plainNonce ) :bool {
223
+ $valid = !empty( $this->findHashedNonce( $user, $plainNonce ) );
224
+ if ( !$valid ) {
225
+ $this->getCon()->fireEvent( '2fa_nonce_verify_fail', [
226
+ 'audit_params' => [
227
+ 'user_login' => $user->user_login,
228
+ ]
229
+ ] );
230
+ }
231
+ return $valid;
232
+ }
233
  }
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/BackupCodes.php CHANGED
@@ -73,7 +73,7 @@ class BackupCodes extends BaseProvider {
73
  return $this;
74
  }
75
 
76
- protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
77
  return (bool)wp_check_password( str_replace( '-', '', $otp ), $this->getSecret() );
78
  }
79
 
73
  return $this;
74
  }
75
 
76
+ protected function processOtp( string $otp ) :bool {
77
  return (bool)wp_check_password( str_replace( '-', '', $otp ), $this->getSecret() );
78
  }
79
 
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/BaseProvider.php CHANGED
@@ -22,6 +22,11 @@ abstract class BaseProvider {
22
  */
23
  private $user;
24
 
 
 
 
 
 
25
  public function __construct() {
26
  }
27
 
@@ -34,11 +39,12 @@ abstract class BaseProvider {
34
  /**
35
  * Assumes this is only called on active profiles
36
  */
37
- public function validateLoginIntent( string $loginNonce ) :bool {
38
  $otpSuccess = false;
39
  $otp = $this->fetchCodeFromRequest();
40
  if ( !empty( $otp ) ) {
41
- $otpSuccess = $this->processOtp( $otp, $loginNonce );
 
42
  $this->auditLogin( $otpSuccess );
43
  }
44
  return $otpSuccess;
@@ -115,7 +121,7 @@ abstract class BaseProvider {
115
  return '';
116
  }
117
 
118
- abstract protected function processOtp( string $otp, string $loginNonce = '' ) :bool;
119
 
120
  /**
121
  * Only to be fired if and when Login has been completely verified.
22
  */
23
  private $user;
24
 
25
+ /**
26
+ * @var string
27
+ */
28
+ protected $workingHashedLoginNonce;
29
+
30
  public function __construct() {
31
  }
32
 
39
  /**
40
  * Assumes this is only called on active profiles
41
  */
42
+ public function validateLoginIntent( string $hashedNonce ) :bool {
43
  $otpSuccess = false;
44
  $otp = $this->fetchCodeFromRequest();
45
  if ( !empty( $otp ) ) {
46
+ $this->workingHashedLoginNonce = $hashedNonce;
47
+ $otpSuccess = $this->processOtp( $otp );
48
  $this->auditLogin( $otpSuccess );
49
  }
50
  return $otpSuccess;
121
  return '';
122
  }
123
 
124
+ abstract protected function processOtp( string $otp ) :bool;
125
 
126
  /**
127
  * Only to be fired if and when Login has been completely verified.
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Email.php CHANGED
@@ -22,10 +22,17 @@ class Email extends BaseProvider {
22
  * If login nonce is provided, the OTP check is stricter and must be the same as that assigned to the nonce.
23
  * Otherwise, we just check whether the OTP exists.
24
  */
25
- protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
26
- return empty( $loginNonce ) ?
27
- in_array( $otp, $this->getAllCodes() )
28
- : ( $this->getAllCodes()[ $loginNonce ] ?? '' ) === $otp;
 
 
 
 
 
 
 
29
  }
30
 
31
  public function getFormField() :array {
@@ -71,16 +78,30 @@ class Email extends BaseProvider {
71
  return true;
72
  }
73
 
74
- public function sendEmailTwoFactorVerify( string $loginNonce ) :bool {
 
 
 
 
75
  $user = $this->getUser();
76
- $sureCon = $this->getCon()->getModule_Comms()->getSureSendController();
 
77
  $useSureSend = $sureCon->isEnabled2Fa() && $sureCon->canUserSend( $user );
78
 
79
  $success = false;
80
  try {
81
- $code = $this->get2faCode( $loginNonce );
 
 
 
 
 
 
 
82
 
83
- $success = ( $useSureSend && $this->send2faEmailSureSend( $code ) )
 
 
84
  || $this->getMod()
85
  ->getEmailProcessor()
86
  ->sendEmailWithTemplate(
@@ -92,7 +113,7 @@ class Email extends BaseProvider {
92
  'show_login_link' => !$this->getCon()->isRelabelled()
93
  ],
94
  'vars' => [
95
- 'code' => $code
96
  ],
97
  'hrefs' => [
98
  'login_link' => 'https://shsec.io/96',
@@ -149,32 +170,24 @@ class Email extends BaseProvider {
149
  && ( $this->isEnforced() || $opts->isEnabledEmailAuthAnyUserSet() );
150
  }
151
 
152
- private function get2faCode( string $loginNonce ) :string {
153
- $secrets = $this->getAllCodes();
154
- if ( !isset( $secrets[ $loginNonce ] ) ) {
155
- $secrets[ $loginNonce ] = $this->generateSimpleOTP();
156
- $this->storeCodes( $secrets );
157
- }
158
- return $secrets[ $loginNonce ];
159
- }
160
-
161
- public function hasOtpForNonce( string $loginNonce ) :bool {
162
- return isset( $this->getAllCodes()[ $loginNonce ] );
163
- }
164
-
165
- private function getAllCodes() :array {
166
  /** @var LoginGuard\ModCon $mod */
167
  $mod = $this->getMod();
168
- $mfaCon = $mod->getMfaController();
169
  $secrets = $this->getSecret();
170
- return array_intersect_key(
171
- is_array( $secrets ) ? $secrets : [],
172
- $mfaCon->getActiveLoginIntents( $this->getUser() )
173
- );
174
- }
 
175
 
176
- private function storeCodes( array $codes ) {
177
- $this->setSecret( $codes );
 
 
 
 
178
  }
179
 
180
  public function getProviderName() :string {
22
  * If login nonce is provided, the OTP check is stricter and must be the same as that assigned to the nonce.
23
  * Otherwise, we just check whether the OTP exists.
24
  */
25
+ protected function processOtp( string $otp ) :bool {
26
+ $secret = $this->getSecret()[ $this->workingHashedLoginNonce ] ?? '';
27
+ return !empty( $secret ) && wp_check_password( $otp, $secret );
28
+ }
29
+
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ public function postSuccessActions() {
34
+ parent::postSuccessActions();
35
+ return $this->setSecret( [] );
36
  }
37
 
38
  public function getFormField() :array {
78
  return true;
79
  }
80
 
81
+ public function sendEmailTwoFactorVerify( string $plainNonce ) :bool {
82
+ $con = $this->getCon();
83
+ /** @var LoginGuard\ModCon $mod */
84
+ $mod = $this->getMod();
85
+ $mfaCon = $mod->getMfaController();
86
  $user = $this->getUser();
87
+ $userMeta = $con->getUserMeta( $user );
88
+ $sureCon = $con->getModule_Comms()->getSureSendController();
89
  $useSureSend = $sureCon->isEnabled2Fa() && $sureCon->canUserSend( $user );
90
 
91
  $success = false;
92
  try {
93
+ if ( !$mfaCon->verifyLoginNonce( $user, $plainNonce ) ) {
94
+ throw new \Exception( 'No such login intent' );
95
+ }
96
+
97
+ $hashedNonce = $mfaCon->findHashedNonce( $user, $plainNonce );
98
+ $intents = $mfaCon->getActiveLoginIntents( $user );
99
+ $intents[ $hashedNonce ][ 'auto_email_sent' ] = true;
100
+ $userMeta->login_intents = $intents;
101
 
102
+ $otp = $this->generate2faCode( $hashedNonce );
103
+
104
+ $success = ( $useSureSend && $this->send2faEmailSureSend( $otp ) )
105
  || $this->getMod()
106
  ->getEmailProcessor()
107
  ->sendEmailWithTemplate(
113
  'show_login_link' => !$this->getCon()->isRelabelled()
114
  ],
115
  'vars' => [
116
+ 'code' => $otp
117
  ],
118
  'hrefs' => [
119
  'login_link' => 'https://shsec.io/96',
170
  && ( $this->isEnforced() || $opts->isEnabledEmailAuthAnyUserSet() );
171
  }
172
 
173
+ private function generate2faCode( string $hashedLoginNonce ) :string {
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  /** @var LoginGuard\ModCon $mod */
175
  $mod = $this->getMod();
176
+
177
  $secrets = $this->getSecret();
178
+ if ( !is_array( $secrets ) ) {
179
+ $secrets = [];
180
+ }
181
+
182
+ $otp = $this->generateSimpleOTP();
183
+ $secrets[ $hashedLoginNonce ] = wp_hash_password( $otp );
184
 
185
+ // Clean old secrets linked to expired login intents
186
+ $this->setSecret( array_intersect_key(
187
+ $secrets,
188
+ $mod->getMfaController()->getActiveLoginIntents( $this->getUser() )
189
+ ) );
190
+ return $otp;
191
  }
192
 
193
  public function getProviderName() :string {
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/GoogleAuth.php CHANGED
@@ -115,7 +115,7 @@ class GoogleAuth extends BaseProvider {
115
 
116
  public function activateGA( string $otp ) :StdResponse {
117
  $r = new StdResponse();
118
- $r->success = $this->processOtp( $otp, '' );
119
  if ( $r->success ) {
120
  $this->setProfileValidated( true );
121
  $r->msg_text = sprintf(
@@ -147,7 +147,7 @@ class GoogleAuth extends BaseProvider {
147
  ];
148
  }
149
 
150
- protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
151
  $valid = false;
152
  try {
153
  $valid = preg_match( '#^\d{6}$#', $otp )
115
 
116
  public function activateGA( string $otp ) :StdResponse {
117
  $r = new StdResponse();
118
+ $r->success = $this->processOtp( $otp );
119
  if ( $r->success ) {
120
  $this->setProfileValidated( true );
121
  $r->msg_text = sprintf(
147
  ];
148
  }
149
 
150
+ protected function processOtp( string $otp ) :bool {
151
  $valid = false;
152
  try {
153
  $valid = preg_match( '#^\d{6}$#', $otp )
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Sms.php CHANGED
@@ -116,7 +116,7 @@ class Sms extends BaseProvider {
116
  return $this;
117
  }
118
 
119
- protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
120
  $meta = $this->getCon()->getUserMeta( $this->getUser() );
121
  return !empty( $meta->sms_registration[ 'code' ] )
122
  && $meta->sms_registration[ 'code' ] === strtoupper( $otp );
116
  return $this;
117
  }
118
 
119
+ protected function processOtp( string $otp ) :bool {
120
  $meta = $this->getCon()->getUserMeta( $this->getUser() );
121
  return !empty( $meta->sms_registration[ 'code' ] )
122
  && $meta->sms_registration[ 'code' ] === strtoupper( $otp );
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/U2F.php CHANGED
@@ -221,7 +221,7 @@ class U2F extends BaseProvider {
221
  return $response;
222
  }
223
 
224
- protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
225
  try {
226
  $registration = ( new \u2flib_server\U2F( $this->getU2fAppID() ) )
227
  ->doAuthenticate(
221
  return $response;
222
  }
223
 
224
+ protected function processOtp( string $otp ) :bool {
225
  try {
226
  $registration = ( new \u2flib_server\U2F( $this->getU2fAppID() ) )
227
  ->doAuthenticate(
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Yubikey.php CHANGED
@@ -55,7 +55,7 @@ class Yubikey extends BaseProvider {
55
  return count( $this->getYubiIds() ) > 0;
56
  }
57
 
58
- protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
59
  $valid = false;
60
 
61
  foreach ( $this->getYubiIds() as $key ) {
55
  return count( $this->getYubiIds() ) > 0;
56
  }
57
 
58
+ protected function processOtp( string $otp ) :bool {
59
  $valid = false;
60
 
61
  foreach ( $this->getYubiIds() as $key ) {
src/lib/src/Modules/LoginGuard/Strings.php CHANGED
@@ -12,68 +12,74 @@ class Strings extends Base\Strings {
12
  */
13
  public function getEventStrings() :array {
14
  return [
15
- 'botbox_fail' => [
16
  'name' => __( 'BotBox Fail', 'wp-simple-firewall' ),
17
  'audit' => [
18
  __( 'User "{{user_login}}" attempted "{{action}}" but Bot checkbox was not found.', 'wp-simple-firewall' ),
19
  ],
20
  ],
21
- 'cooldown_fail' => [
22
  'name' => __( 'Cooldown Fail', 'wp-simple-firewall' ),
23
  'audit' => [
24
  __( 'Login/Register request triggered cooldown and was blocked.', 'wp-simple-firewall' )
25
  ],
26
  ],
27
- 'honeypot_fail' => [
28
  'name' => __( 'Honeypot Fail', 'wp-simple-firewall' ),
29
  'audit' => [
30
  __( 'User "{{user_login}}" attempted {{action}} but they were caught by the honeypot.', 'wp-simple-firewall' )
31
  ],
32
  ],
33
- '2fa_success' => [
34
  'name' => __( '2FA Login Success', 'wp-simple-firewall' ),
35
  'audit' => [
36
  __( 'Successful 2FA Login Verification', 'wp-simple-firewall' ),
37
  ],
38
  ],
39
- '2fa_verify_success' => [
40
  'name' => __( '2FA Verify Success', 'wp-simple-firewall' ),
41
  'audit' => [
42
  __( 'User "{{user_login}}" verified their identity using "{{method}}".', 'wp-simple-firewall' )
43
  ],
44
  ],
45
- '2fa_verify_fail' => [
46
  'name' => __( '2FA Verify Fail', 'wp-simple-firewall' ),
47
  'audit' => [
48
  __( 'User "{{user_login}}" failed to verify their identity using "{{method}}".', 'wp-simple-firewall' )
49
  ],
50
  ],
 
 
 
 
 
 
51
  // todo rename to block_login
52
- 'login_block' => [
53
  'name' => __( 'Login Blocked', 'wp-simple-firewall' ),
54
  'audit' => [
55
  __( 'User login request blocked.', 'wp-simple-firewall' ),
56
  ],
57
  ],
58
- 'block_register' => [
59
  'name' => __( 'Registration Blocked', 'wp-simple-firewall' ),
60
  'audit' => [
61
  __( 'User registration request blocked.', 'wp-simple-firewall' ),
62
  ],
63
  ],
64
- 'block_lostpassword' => [
65
  'name' => __( 'Lost Password Blocked', 'wp-simple-firewall' ),
66
  'audit' => [
67
  __( 'User lost password request blocked.', 'wp-simple-firewall' ),
68
  ],
69
  ],
70
- 'block_checkout' => [
71
  'name' => __( 'Checkout Blocked', 'wp-simple-firewall' ),
72
  'audit' => [
73
  __( 'User attempting checkout request blocked.', 'wp-simple-firewall' ),
74
  ],
75
  ],
76
- 'hide_login_url' => [
77
  'name' => __( 'Hidden Login URL Fail', 'wp-simple-firewall' ),
78
  'audit' => [
79
  __( 'Redirecting wp-login due to hidden login URL', 'wp-simple-firewall' ),
12
  */
13
  public function getEventStrings() :array {
14
  return [
15
+ 'botbox_fail' => [
16
  'name' => __( 'BotBox Fail', 'wp-simple-firewall' ),
17
  'audit' => [
18
  __( 'User "{{user_login}}" attempted "{{action}}" but Bot checkbox was not found.', 'wp-simple-firewall' ),
19
  ],
20
  ],
21
+ 'cooldown_fail' => [
22
  'name' => __( 'Cooldown Fail', 'wp-simple-firewall' ),
23
  'audit' => [
24
  __( 'Login/Register request triggered cooldown and was blocked.', 'wp-simple-firewall' )
25
  ],
26
  ],
27
+ 'honeypot_fail' => [
28
  'name' => __( 'Honeypot Fail', 'wp-simple-firewall' ),
29
  'audit' => [
30
  __( 'User "{{user_login}}" attempted {{action}} but they were caught by the honeypot.', 'wp-simple-firewall' )
31
  ],
32
  ],
33
+ '2fa_success' => [
34
  'name' => __( '2FA Login Success', 'wp-simple-firewall' ),
35
  'audit' => [
36
  __( 'Successful 2FA Login Verification', 'wp-simple-firewall' ),
37
  ],
38
  ],
39
+ '2fa_verify_success' => [
40
  'name' => __( '2FA Verify Success', 'wp-simple-firewall' ),
41
  'audit' => [
42
  __( 'User "{{user_login}}" verified their identity using "{{method}}".', 'wp-simple-firewall' )
43
  ],
44
  ],
45
+ '2fa_verify_fail' => [
46
  'name' => __( '2FA Verify Fail', 'wp-simple-firewall' ),
47
  'audit' => [
48
  __( 'User "{{user_login}}" failed to verify their identity using "{{method}}".', 'wp-simple-firewall' )
49
  ],
50
  ],
51
+ '2fa_nonce_verify_fail' => [
52
+ 'name' => __( '2FA Nonce Verify Fail', 'wp-simple-firewall' ),
53
+ 'audit' => [
54
+ __( 'An attempt was made to verify a 2FA login "{{user_login}}" using an invalid nonce.', 'wp-simple-firewall' )
55
+ ],
56
+ ],
57
  // todo rename to block_login
58
+ 'login_block' => [
59
  'name' => __( 'Login Blocked', 'wp-simple-firewall' ),
60
  'audit' => [
61
  __( 'User login request blocked.', 'wp-simple-firewall' ),
62
  ],
63
  ],
64
+ 'block_register' => [
65
  'name' => __( 'Registration Blocked', 'wp-simple-firewall' ),
66
  'audit' => [
67
  __( 'User registration request blocked.', 'wp-simple-firewall' ),
68
  ],
69
  ],
70
+ 'block_lostpassword' => [
71
  'name' => __( 'Lost Password Blocked', 'wp-simple-firewall' ),
72
  'audit' => [
73
  __( 'User lost password request blocked.', 'wp-simple-firewall' ),
74
  ],
75
  ],
76
+ 'block_checkout' => [
77
  'name' => __( 'Checkout Blocked', 'wp-simple-firewall' ),
78
  'audit' => [
79
  __( 'User attempting checkout request blocked.', 'wp-simple-firewall' ),
80
  ],
81
  ],
82
+ 'hide_login_url' => [
83
  'name' => __( 'Hidden Login URL Fail', 'wp-simple-firewall' ),
84
  'audit' => [
85
  __( 'Redirecting wp-login due to hidden login URL', 'wp-simple-firewall' ),
src/lib/vendor/composer/autoload_classmap.php CHANGED
@@ -826,9 +826,9 @@ return array(
826
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\CooldownFlagFile' => $baseDir . '/src/Modules/LoginGuard/Lib/CooldownFlagFile.php',
827
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\Rename\\RenameLogin' => $baseDir . '/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php',
828
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\CouldNotValidate2FA' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/CouldNotValidate2FA.php',
 
829
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\LoginCancelException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/LoginCancelException.php',
830
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NoActiveProvidersForUserException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NoActiveProvidersForUserException.php',
831
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NoLoginIntentForUserException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NoLoginIntentForUserException.php',
832
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NotValidUserException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NotValidUserException.php',
833
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\TooManyAttemptsException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/TooManyAttemptsException.php',
834
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\LoginIntentRequestCapture' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php',
826
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\CooldownFlagFile' => $baseDir . '/src/Modules/LoginGuard/Lib/CooldownFlagFile.php',
827
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\Rename\\RenameLogin' => $baseDir . '/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php',
828
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\CouldNotValidate2FA' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/CouldNotValidate2FA.php',
829
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\InvalidLoginIntentException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/InvalidLoginIntentException.php',
830
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\LoginCancelException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/LoginCancelException.php',
831
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NoActiveProvidersForUserException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NoActiveProvidersForUserException.php',
 
832
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NotValidUserException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NotValidUserException.php',
833
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\TooManyAttemptsException' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/TooManyAttemptsException.php',
834
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\LoginIntentRequestCapture' => $baseDir . '/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php',
src/lib/vendor/composer/autoload_static.php CHANGED
@@ -1021,9 +1021,9 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
1021
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\CooldownFlagFile' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/CooldownFlagFile.php',
1022
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\Rename\\RenameLogin' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php',
1023
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\CouldNotValidate2FA' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/CouldNotValidate2FA.php',
 
1024
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\LoginCancelException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/LoginCancelException.php',
1025
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NoActiveProvidersForUserException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NoActiveProvidersForUserException.php',
1026
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NoLoginIntentForUserException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NoLoginIntentForUserException.php',
1027
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NotValidUserException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NotValidUserException.php',
1028
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\TooManyAttemptsException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/TooManyAttemptsException.php',
1029
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\LoginIntentRequestCapture' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php',
1021
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\CooldownFlagFile' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/CooldownFlagFile.php',
1022
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\Rename\\RenameLogin' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php',
1023
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\CouldNotValidate2FA' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/CouldNotValidate2FA.php',
1024
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\InvalidLoginIntentException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/InvalidLoginIntentException.php',
1025
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\LoginCancelException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/LoginCancelException.php',
1026
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NoActiveProvidersForUserException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NoActiveProvidersForUserException.php',
 
1027
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\NotValidUserException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/NotValidUserException.php',
1028
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Exceptions\\TooManyAttemptsException' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/Exceptions/TooManyAttemptsException.php',
1029
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\LoginIntentRequestCapture' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php',
src/lib/vendor/composer/installed.json CHANGED
@@ -288,17 +288,17 @@
288
  },
289
  {
290
  "name": "fernleafsystems/wordpress-services",
291
- "version": "2.30.2",
292
- "version_normalized": "2.30.2.0",
293
  "source": {
294
  "type": "git",
295
  "url": "git@gitlab.com:fernleafsystems/wordpress/wordpress-services.git",
296
- "reference": "3e7945ba6a32bc3f358d6e7550374d6b21da1613"
297
  },
298
  "dist": {
299
  "type": "zip",
300
- "url": "https://gitlab.com/api/v4/projects/fernleafsystems%2Fwordpress%2Fwordpress-services/repository/archive.zip?sha=3e7945ba6a32bc3f358d6e7550374d6b21da1613",
301
- "reference": "3e7945ba6a32bc3f358d6e7550374d6b21da1613",
302
  "shasum": ""
303
  },
304
  "require": {
@@ -317,7 +317,7 @@
317
  "symfony/yaml": "~3.0",
318
  "twig/twig": "^1.42"
319
  },
320
- "time": "2022-09-07T08:44:10+00:00",
321
  "type": "library",
322
  "installation-source": "dist",
323
  "autoload": {
288
  },
289
  {
290
  "name": "fernleafsystems/wordpress-services",
291
+ "version": "2.30.3",
292
+ "version_normalized": "2.30.3.0",
293
  "source": {
294
  "type": "git",
295
  "url": "git@gitlab.com:fernleafsystems/wordpress/wordpress-services.git",
296
+ "reference": "34e9e70321792811571efa0dea1ccf422484f3e9"
297
  },
298
  "dist": {
299
  "type": "zip",
300
+ "url": "https://gitlab.com/api/v4/projects/fernleafsystems%2Fwordpress%2Fwordpress-services/repository/archive.zip?sha=34e9e70321792811571efa0dea1ccf422484f3e9",
301
+ "reference": "34e9e70321792811571efa0dea1ccf422484f3e9",
302
  "shasum": ""
303
  },
304
  "require": {
317
  "symfony/yaml": "~3.0",
318
  "twig/twig": "^1.42"
319
  },
320
+ "time": "2022-09-13T09:24:49+00:00",
321
  "type": "library",
322
  "installation-source": "dist",
323
  "autoload": {
src/lib/vendor/composer/installed.php CHANGED
@@ -1,11 +1,11 @@
1
  <?php return array(
2
  'root' => array(
3
- 'pretty_version' => 'dev-develop',
4
- 'version' => 'dev-develop',
5
  'type' => 'library',
6
  'install_path' => __DIR__ . '/../../',
7
  'aliases' => array(),
8
- 'reference' => '5f4642f2c038b77e969af37613c3842d43387017',
9
  'name' => 'apto-shield/requirements',
10
  'dev' => true,
11
  ),
@@ -20,12 +20,12 @@
20
  'dev_requirement' => false,
21
  ),
22
  'apto-shield/requirements' => array(
23
- 'pretty_version' => 'dev-develop',
24
- 'version' => 'dev-develop',
25
  'type' => 'library',
26
  'install_path' => __DIR__ . '/../../',
27
  'aliases' => array(),
28
- 'reference' => '5f4642f2c038b77e969af37613c3842d43387017',
29
  'dev_requirement' => false,
30
  ),
31
  'christian-riesen/base32' => array(
@@ -74,12 +74,12 @@
74
  'dev_requirement' => false,
75
  ),
76
  'fernleafsystems/wordpress-services' => array(
77
- 'pretty_version' => '2.30.2',
78
- 'version' => '2.30.2.0',
79
  'type' => 'library',
80
  'install_path' => __DIR__ . '/../fernleafsystems/wordpress-services',
81
  'aliases' => array(),
82
- 'reference' => '3e7945ba6a32bc3f358d6e7550374d6b21da1613',
83
  'dev_requirement' => false,
84
  ),
85
  'fernleafsystems/zxcvbn-php' => array(
1
  <?php return array(
2
  'root' => array(
3
+ 'pretty_version' => 'dev-master',
4
+ 'version' => 'dev-master',
5
  'type' => 'library',
6
  'install_path' => __DIR__ . '/../../',
7
  'aliases' => array(),
8
+ 'reference' => '2ec6ed3be8e4285ee1e10f6afafd8f4b3e4d2402',
9
  'name' => 'apto-shield/requirements',
10
  'dev' => true,
11
  ),
20
  'dev_requirement' => false,
21
  ),
22
  'apto-shield/requirements' => array(
23
+ 'pretty_version' => 'dev-master',
24
+ 'version' => 'dev-master',
25
  'type' => 'library',
26
  'install_path' => __DIR__ . '/../../',
27
  'aliases' => array(),
28
+ 'reference' => '2ec6ed3be8e4285ee1e10f6afafd8f4b3e4d2402',
29
  'dev_requirement' => false,
30
  ),
31
  'christian-riesen/base32' => array(
74
  'dev_requirement' => false,
75
  ),
76
  'fernleafsystems/wordpress-services' => array(
77
+ 'pretty_version' => '2.30.3',
78
+ 'version' => '2.30.3.0',
79
  'type' => 'library',
80
  'install_path' => __DIR__ . '/../fernleafsystems/wordpress-services',
81
  'aliases' => array(),
82
+ 'reference' => '34e9e70321792811571efa0dea1ccf422484f3e9',
83
  'dev_requirement' => false,
84
  ),
85
  'fernleafsystems/zxcvbn-php' => array(
src/lib/vendor/fernleafsystems/wordpress-services/src/Utilities/IpUtils.php CHANGED
@@ -37,10 +37,12 @@ class IpUtils {
37
  else {
38
  foreach ( $ipsOrRanges as $ipOrRangeHaystack ) {
39
  $range = Factory::parseRangeString( $ipOrRangeHaystack );
40
- if ( empty( $range ) && $throwException ) {
41
- throw new NotAnIpAddressOrRangeException( $ipOrRangeHaystack );
 
 
42
  }
43
- if ( !empty( $ipOrRangeHaystack ) && $range->containsRange( $IP ) ) {
44
  $in = true;
45
  break;
46
  }
37
  else {
38
  foreach ( $ipsOrRanges as $ipOrRangeHaystack ) {
39
  $range = Factory::parseRangeString( $ipOrRangeHaystack );
40
+ if ( empty( $range ) ) {
41
+ if ( $throwException ) {
42
+ throw new NotAnIpAddressOrRangeException( $ipOrRangeHaystack );
43
+ }
44
  }
45
+ elseif ( $range->containsRange( $IP ) ) {
46
  $in = true;
47
  break;
48
  }
templates/twig/wpadmin_pages/base.twig CHANGED
@@ -18,8 +18,7 @@
18
  <div class="row">
19
 
20
  {% if flags.show_sidebar_nav|default(true) %}
21
- <div id="apto-PageMainSide"
22
- class="col-2 col-lg-1">
23
  {% block page_main_side %}
24
  {% include '/wpadmin_pages/components/page/nav_sidebar.twig' %}
25
  {% endblock %}
18
  <div class="row">
19
 
20
  {% if flags.show_sidebar_nav|default(true) %}
21
+ <div id="apto-PageMainSide" class="col-2 col-lg-1">
 
22
  {% block page_main_side %}
23
  {% include '/wpadmin_pages/components/page/nav_sidebar.twig' %}
24
  {% endblock %}
templates/twig/wpadmin_pages/components/page/nav_sidebar.twig CHANGED
@@ -1,6 +1,7 @@
1
  <div id="NavSideBar">
2
 
3
  <ul class="nav top-level-nav flex-column accordion" id="ShieldCollapseNav">
 
4
  {% for mitem in vars.navbar_menu %}
5
 
6
  <li class="nav-item pb-2{% if mitem.sub_items|default([]) is not empty %} with-submenu{% endif %}"
@@ -42,7 +43,8 @@
42
  </a>
43
 
44
  {% if mitem.sub_items|default([]) is not empty %}
45
- <div class="subnava-menu accordion-collapse {{ mitem.active|default(false) ? 'show':'collapse' }}" id="subnav-{{ mitem.slug }}"
 
46
  data-bs-parent="#ShieldCollapseNav"
47
  >
48
  <ul class="nav flex-column pt-0 primary_side_sub_menu px-0 py-2">
@@ -64,4 +66,11 @@
64
 
65
  {% endfor %}
66
  </ul>
 
 
 
 
 
 
 
67
  </div>
1
  <div id="NavSideBar">
2
 
3
  <ul class="nav top-level-nav flex-column accordion" id="ShieldCollapseNav">
4
+
5
  {% for mitem in vars.navbar_menu %}
6
 
7
  <li class="nav-item pb-2{% if mitem.sub_items|default([]) is not empty %} with-submenu{% endif %}"
43
  </a>
44
 
45
  {% if mitem.sub_items|default([]) is not empty %}
46
+ <div class="subnava-menu accordion-collapse {{ mitem.active|default(false) ? 'show':'collapse' }}"
47
+ id="subnav-{{ mitem.slug }}"
48
  data-bs-parent="#ShieldCollapseNav"
49
  >
50
  <ul class="nav flex-column pt-0 primary_side_sub_menu px-0 py-2">
66
 
67
  {% endfor %}
68
  </ul>
69
+
70
+ {% if not flags.is_whitelabelled %}
71
+ <div class="text-muted py-1 px-2"
72
+ style="position:fixed; bottom:10px;background: #e9e9e9;">
73
+ {{ strings.running_version|raw }}
74
+ </div>
75
+ {% endif %}
76
  </div>