Login Security Solution - Version 0.4.0

Version Description

  • Add multisite network support.
  • Keep unit tests from deleting settings. Note: removes the ability to run the unit tests without activating the plugin.
Download this release

Release Info

Developer convissor
Plugin Icon wp plugin Login Security Solution
Version 0.4.0
Comparing to
See all releases

Code changes from version 0.3.0 to 0.4.0

admin.inc CHANGED
@@ -6,7 +6,6 @@
6
  *
7
  * @package login-security-solution
8
  * @link http://wordpress.org/extend/plugins/login-security-solution/
9
- * @version 0.0.1
10
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
11
  * @author Daniel Convissor <danielc@analysisandsolutions.com>
12
  * @copyright The Analysis and Solutions Company, 2012
@@ -18,18 +17,29 @@
18
  *
19
  * @package login-security-solution
20
  * @link http://wordpress.org/extend/plugins/login-security-solution/
21
- * @version 0.0.1
22
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
23
  * @author Daniel Convissor <danielc@analysisandsolutions.com>
24
  * @copyright The Analysis and Solutions Company, 2012
25
  */
26
  class login_security_solution_admin extends login_security_solution {
 
 
 
 
 
 
27
  /**
28
  * Metadata and labels for each element of the plugin's options
29
  * @var array
30
  */
31
  protected $fields;
32
 
 
 
 
 
 
 
33
  /**
34
  * Key for the change password "don't remind me" checkbox
35
  * @var string
@@ -48,6 +58,12 @@ class login_security_solution_admin extends login_security_solution {
48
  */
49
  protected $option_pw_force_change_name;
50
 
 
 
 
 
 
 
51
  /**
52
  * Text for the plugin's password change page "don't remind me" button
53
  * @var string
@@ -90,6 +106,16 @@ class login_security_solution_admin extends login_security_solution {
90
  // Combine plugin's name with translation already in WP.
91
  $this->text_settings = self::NAME . ' ' . __('Settings');
92
 
 
 
 
 
 
 
 
 
 
 
93
  // NON-STANDARD: This is for the password change page.
94
  $this->option_pw_force_change_name = self::ID . '-pw-force-change-done';
95
  $this->text_pw_force_change = __('Change All Passwords', self::ID);
@@ -108,6 +134,10 @@ class login_security_solution_admin extends login_security_solution {
108
  public function activate() {
109
  global $wpdb;
110
 
 
 
 
 
111
  /*
112
  * Create or alter the plugin's tables as needed.
113
  */
@@ -137,8 +167,14 @@ class login_security_solution_admin extends login_security_solution {
137
  * Save this plugin's options to the database.
138
  */
139
 
 
 
 
140
  update_option($this->option_name, $this->options);
141
  add_option($this->option_pw_force_change_name, 0, '', 'no');
 
 
 
142
 
143
  /*
144
  * Store password hashes.
@@ -307,19 +343,18 @@ class login_security_solution_admin extends login_security_solution {
307
  * plugin being displayed on WordPress' Plugins admin page.
308
  *
309
  * @param array $links the links generated thus far
310
- * @param string $file the name of the plugin currently being rendered
311
  * @return array
312
  */
313
- public function plugin_action_links($links, $file) {
314
- if ($file == self::ID . '/' . self::ID . '.php') {
315
- $links[] = '<a href="options-general.php?page='
316
- . self::ID . '">' . __('Settings') . '</a>';
 
 
 
 
 
317
 
318
- // NON-STANDARD: This is for the password change page.
319
- $links[] = '<a href="options-general.php?page='
320
- . $this->option_pw_force_change_name . '">'
321
- . $this->text_pw_force_change . '</a>' ;
322
- }
323
  return $links;
324
  }
325
 
@@ -330,10 +365,11 @@ class login_security_solution_admin extends login_security_solution {
330
  * any admin page is rendered
331
  */
332
  public function admin_menu() {
333
- add_options_page(
 
334
  $this->text_settings,
335
  self::NAME,
336
- 'activate_plugins',
337
  self::ID,
338
  array(&$this, 'page_settings')
339
  );
@@ -390,7 +426,7 @@ class login_security_solution_admin extends login_security_solution {
390
  */
391
  public function page_settings() {
392
  echo '<h2>' . $this->hsc_utf8($this->text_settings) . '</h2>';
393
- echo '<form action="options.php" method="post">' . "\n";
394
  settings_fields($this->option_name);
395
  do_settings_sections(self::ID);
396
  submit_button();
@@ -410,19 +446,19 @@ class login_security_solution_admin extends login_security_solution {
410
  */
411
  public function section_login() {
412
  echo '<p>';
413
- _e("This plugin stores the IP address, username and password for each failed log in attempt.", self::ID);
414
  echo ' ';
415
- _e("The data from future login failures are compared against the historical data.", self::ID);
416
  echo ' ';
417
- _e("If any of the data points match, the plugin delays printing out the failure message.", self::ID);
418
  echo ' ';
419
- _e("The goal is for the responses to take so long that the attackers give up and go find an easier target.", self::ID);
420
  echo ' ';
421
- _e("The length of the delay is broken up into three tiers.", self::ID);
422
  echo ' ';
423
- _e("The amount of the delay increases in higher tiers.", self::ID);
424
  echo ' ';
425
- _e("The delay time within each tier is randomized to complicate profiling by attackers.", self::ID);
426
  echo '</p>';
427
  }
428
 
@@ -477,9 +513,9 @@ class login_security_solution_admin extends login_security_solution {
477
  . $this->hsc_utf8($this->option_name)
478
  . '[' . $this->hsc_utf8($name) . ']"'
479
  . ' value="' . $this->hsc_utf8($this->options[$name]) . '" /> ';
480
- echo $this->hsc_utf8($this->fields[$name]['text']);
481
- echo ' ' . __('Default:', self::ID) . ' '
482
- . $this->hsc_utf8($this->options_default[$name]) . '.';
483
  }
484
 
485
  /**
@@ -512,9 +548,9 @@ class login_security_solution_admin extends login_security_solution {
512
 
513
  if (!is_scalar($in[$name])) {
514
  // Not translating this since only hackers will see it.
515
- add_settings_error($this->option_name, $name, "'"
516
- . $this->hsc_utf8($field['label'])
517
- . "' was not a scalar, $default");
518
  continue;
519
  }
520
 
@@ -522,28 +558,27 @@ class login_security_solution_admin extends login_security_solution {
522
  case 'bool':
523
  if ($in[$name] != 0 && $in[$name] != 1) {
524
  // Not translating this since only hackers will see it.
525
- add_settings_error($this->option_name, $name, "'"
526
- . $this->hsc_utf8($field['label'])
527
- . "' must be '0' or '1', $default");
528
  continue 2;
529
  }
530
  break;
531
  case 'int':
532
  if (!ctype_digit($in[$name])) {
533
- add_settings_error($this->option_name, $name, "'"
534
- . $this->hsc_utf8($field['label'])
535
- . "' " . __("must be an integer,", self::ID)
536
- . ' ' . $default);
537
  continue 2;
538
  }
539
  if (array_key_exists('greater_than', $field)
540
  && $in[$name] < $field['greater_than'])
541
  {
542
- add_settings_error($this->option_name, $name, "'"
543
- . $this->hsc_utf8($field['label'])
544
- . "' " . sprintf($gt_format,
545
- $field['greater_than'])
546
- . ' ' . $default);
547
  continue 2;
548
  }
549
  break;
@@ -554,11 +589,10 @@ class login_security_solution_admin extends login_security_solution {
554
  // Special check to make sure Delay Tier 3 > Delay Tier 2.
555
  $name = 'login_fail_tier_3';
556
  if ($out[$name] <= $out['login_fail_tier_2']) {
557
- add_settings_error($this->option_name, $name, "'"
558
- . $this->hsc_utf8($this->fields[$name]['label'])
559
- . "' " . sprintf($gt_format,
560
- $this->hsc_utf8($this->fields['login_fail_tier_2']['label']))
561
- . ' ' . $default);
562
 
563
  $out[$name] = $out['login_fail_tier_2'] + 5;
564
  }
@@ -566,10 +600,10 @@ class login_security_solution_admin extends login_security_solution {
566
  // Speical check to ensure reuse count is set if aging is enabled.
567
  $name = 'pw_reuse_count';
568
  if ($out['pw_change_days'] && !$out[$name]) {
569
- add_settings_error($this->option_name, $name, "'"
570
- . $this->hsc_utf8($this->fields[$name]['label'])
571
- . "' " . sprintf($gt_format, 1)
572
- . ' ' . $default);
573
 
574
  $out[$name] = 5;
575
  }
@@ -588,10 +622,11 @@ class login_security_solution_admin extends login_security_solution {
588
  * any admin page is rendered
589
  */
590
  public function admin_menu_pw_force_change() {
591
- add_options_page(
 
592
  $this->text_pw_force_change,
593
  '',
594
- 'activate_plugins',
595
  $this->option_pw_force_change_name,
596
  array(&$this, 'page_pw_force_change')
597
  );
@@ -633,18 +668,18 @@ class login_security_solution_admin extends login_security_solution {
633
  echo '<h2>' . $this->hsc_utf8($this->text_pw_force_change) . '</h2>';
634
 
635
  echo '<p>';
636
- _e("There may be cases where everyone's password should be reset.", self::ID);
637
  echo ' ';
638
- printf(__("This page, provided by the %s plugin, offers that functionality.", self::ID), $this->hsc_utf8(self::NAME));
639
  echo '</p>';
640
 
641
  echo '<p>';
642
- _e("Submitting this form sets a flag that forces all users to utilize WordPress' built in password reset functionality.", self::ID);
643
  echo ' ';
644
- _e("Users who are presently logged in will be logged out the next time they view a page that requires authentication.", self::ID);
645
  echo '</p>';
646
 
647
- echo '<form action="options.php" method="post">' . "\n";
648
  settings_fields($this->option_pw_force_change_name);
649
 
650
  $this->echo_div();
@@ -653,13 +688,14 @@ class login_security_solution_admin extends login_security_solution {
653
  . $this->hsc_utf8($this->option_pw_force_change_name)
654
  . '[' . $this->hsc_utf8($this->key_checkbox_require)
655
  . ']" /> ';
656
- _e("Confirm that you want to force all users to change their passwords by checking this box, then click the button, below.", self::ID);
657
  echo '</strong></p>';
658
 
 
659
  submit_button(
660
  $this->text_button_require,
661
  'primary',
662
- $this->hsc_utf8($this->option_pw_force_change_name) . '[submit]'
663
  );
664
 
665
  echo "</div>\n";
@@ -671,13 +707,14 @@ class login_security_solution_admin extends login_security_solution {
671
  . $this->hsc_utf8($this->option_pw_force_change_name)
672
  . '[' . $this->hsc_utf8($this->key_checkbox_remind)
673
  . ']" /> ';
674
- _e("No thanks. I know what I'm doing. Please don't remind me about this.", self::ID);
675
  echo '</p>';
676
 
 
677
  submit_button(
678
  $this->text_button_remind,
679
  'secondary',
680
- $this->hsc_utf8($this->option_pw_force_change_name) . '[submit]'
681
  );
682
 
683
  echo "</div>\n";
@@ -702,40 +739,50 @@ class login_security_solution_admin extends login_security_solution {
702
  && !empty($in['submit'])
703
  && is_scalar($in['submit']))
704
  {
705
- $crossed = __("You have checked a box that does not correspond with the button you pressed. Please check and press buttons inside the same section.", self::ID);
 
 
706
 
707
  switch ($in['submit']) {
708
  case $this->text_button_remind:
709
  if (!empty($in[$this->key_checkbox_require])) {
710
  add_settings_error($this->option_pw_force_change_name,
711
- $this->option_pw_force_change_name, $crossed);
 
712
  } elseif (empty($in[$this->key_checkbox_remind])) {
713
- add_settings_error($this->option_pw_force_change_name, $this->option_pw_force_change_name, __("Please confirm that you really want to do this. Put a check in the 'No thanks' box before hitting the submit button.", self::ID));
 
 
714
  } else {
715
  // Translaton already in WP.
716
  add_settings_error($this->option_pw_force_change_name,
717
  $this->option_pw_force_change_name,
718
- __('Success!'), 'updated');
 
719
  $out = 1;
720
  }
721
  break;
722
  case $this->text_button_require:
723
  if (!empty($in[$this->key_checkbox_remind])) {
724
  add_settings_error($this->option_pw_force_change_name,
725
- $this->option_pw_force_change_name, $crossed);
 
726
  } elseif (empty($in[$this->key_checkbox_require])) {
727
- add_settings_error($this->option_pw_force_change_name, $this->option_pw_force_change_name, __("Please confirm that you really want to do this. Put a check in the 'Confirm' box before hitting the submit button.", self::ID));
 
 
728
  } else {
729
  $result = $this->force_change_for_all();
730
  if ($result === true) {
731
  // Translaton already in WP.
732
  add_settings_error($this->option_pw_force_change_name,
733
  $this->option_pw_force_change_name,
734
- __('Success!'), 'updated');
735
  $out = 1;
736
  } else {
737
  add_settings_error($this->option_pw_force_change_name,
738
- $this->option_pw_force_change_name, $result);
 
739
  }
740
  }
741
  break;
@@ -756,21 +803,24 @@ class login_security_solution_admin extends login_security_solution {
756
  * @return void
757
  */
758
  public function admin_notices() {
759
- if (!current_user_can('activate_plugins')) {
760
  return;
761
  }
762
 
763
  echo '<div class="error">';
764
 
765
  echo '<p><strong>';
766
- _e("You have not asked your users to change their passwords since the plugin was activated. Most users have weak passwords. This plugin's password policies protect your site from brute force attacks. Please improve <em>everyone's</em> security by making all users pick new, strong, passwords.", self::ID);
767
  echo '</strong></p>';
768
 
769
  echo '<p><strong>';
770
- _e("Speaking of which, do YOU have a strong password? Make sure by changing yours once you've submitted the Change All Passwords form.", self::ID);
771
  echo '</strong></p>';
772
 
773
- echo '<p><strong><a href="options-general.php?page=' . $this->hsc_utf8($this->option_pw_force_change_name) . '">' . $this->text_pw_force_change . "</a></strong></p>\n";
 
 
 
774
 
775
  echo "</div>\n";
776
  }
@@ -791,7 +841,7 @@ class login_security_solution_admin extends login_security_solution {
791
  protected function force_change_for_all() {
792
  global $user_ID, $wpdb;
793
 
794
- if (!current_user_can('activate_plugins')) {
795
  // Translaton already in WP.
796
  return __('You do not have sufficient permissions to access this page.');
797
  }
6
  *
7
  * @package login-security-solution
8
  * @link http://wordpress.org/extend/plugins/login-security-solution/
 
9
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
10
  * @author Daniel Convissor <danielc@analysisandsolutions.com>
11
  * @copyright The Analysis and Solutions Company, 2012
17
  *
18
  * @package login-security-solution
19
  * @link http://wordpress.org/extend/plugins/login-security-solution/
 
20
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
21
  * @author Daniel Convissor <danielc@analysisandsolutions.com>
22
  * @copyright The Analysis and Solutions Company, 2012
23
  */
24
  class login_security_solution_admin extends login_security_solution {
25
+ /**
26
+ * The WP privilege level required to use the admin interface
27
+ * @var string
28
+ */
29
+ protected $capability_required;
30
+
31
  /**
32
  * Metadata and labels for each element of the plugin's options
33
  * @var array
34
  */
35
  protected $fields;
36
 
37
+ /**
38
+ * URI for the forms' action attributes
39
+ * @var string
40
+ */
41
+ protected $form_action;
42
+
43
  /**
44
  * Key for the change password "don't remind me" checkbox
45
  * @var string
58
  */
59
  protected $option_pw_force_change_name;
60
 
61
+ /**
62
+ * Name of the page holding the options
63
+ * @var string
64
+ */
65
+ protected $page_options;
66
+
67
  /**
68
  * Text for the plugin's password change page "don't remind me" button
69
  * @var string
106
  // Combine plugin's name with translation already in WP.
107
  $this->text_settings = self::NAME . ' ' . __('Settings');
108
 
109
+ if (is_multisite()) {
110
+ $this->capability_required = 'manage_network_options';
111
+ $this->form_action = '../options.php';
112
+ $this->page_options = 'settings.php';
113
+ } else {
114
+ $this->capability_required = 'manage_options';
115
+ $this->form_action = 'options.php';
116
+ $this->page_options = 'options-general.php';
117
+ }
118
+
119
  // NON-STANDARD: This is for the password change page.
120
  $this->option_pw_force_change_name = self::ID . '-pw-force-change-done';
121
  $this->text_pw_force_change = __('Change All Passwords', self::ID);
134
  public function activate() {
135
  global $wpdb;
136
 
137
+ if (is_multisite() && !is_network_admin()) {
138
+ die($this->hsc_utf8(sprintf(__("%s must be activated via the Network Admin interface when WordPress is in multistie network mode."), self::NAME)));
139
+ }
140
+
141
  /*
142
  * Create or alter the plugin's tables as needed.
143
  */
167
  * Save this plugin's options to the database.
168
  */
169
 
170
+ if (is_multisite()) {
171
+ switch_to_blog(1);
172
+ }
173
  update_option($this->option_name, $this->options);
174
  add_option($this->option_pw_force_change_name, 0, '', 'no');
175
+ if (is_multisite()) {
176
+ restore_current_blog();
177
+ }
178
 
179
  /*
180
  * Store password hashes.
343
  * plugin being displayed on WordPress' Plugins admin page.
344
  *
345
  * @param array $links the links generated thus far
 
346
  * @return array
347
  */
348
+ public function plugin_action_links($links) {
349
+ $links[] = '<a href="' . $this->hsc_utf8($this->page_options)
350
+ . '?page=' . self::ID . '">'
351
+ . $this->hsc_utf8(__('Settings')) . '</a>';
352
+
353
+ // NON-STANDARD: This is for the password change page.
354
+ $links[] = '<a href="' . $this->hsc_utf8($this->page_options)
355
+ . '?page=' . $this->hsc_utf8($this->option_pw_force_change_name)
356
+ . '">' . $this->hsc_utf8($this->text_pw_force_change) . '</a>';
357
 
 
 
 
 
 
358
  return $links;
359
  }
360
 
365
  * any admin page is rendered
366
  */
367
  public function admin_menu() {
368
+ add_submenu_page(
369
+ $this->page_options,
370
  $this->text_settings,
371
  self::NAME,
372
+ $this->capability_required,
373
  self::ID,
374
  array(&$this, 'page_settings')
375
  );
426
  */
427
  public function page_settings() {
428
  echo '<h2>' . $this->hsc_utf8($this->text_settings) . '</h2>';
429
+ echo '<form action="' . $this->hsc_utf8($this->form_action) . '" method="post">' . "\n";
430
  settings_fields($this->option_name);
431
  do_settings_sections(self::ID);
432
  submit_button();
446
  */
447
  public function section_login() {
448
  echo '<p>';
449
+ echo $this->hsc_utf8(__("This plugin stores the IP address, username and password for each failed log in attempt.", self::ID));
450
  echo ' ';
451
+ echo $this->hsc_utf8(__("The data from future login failures are compared against the historical data.", self::ID));
452
  echo ' ';
453
+ echo $this->hsc_utf8(__("If any of the data points match, the plugin delays printing out the failure message.", self::ID));
454
  echo ' ';
455
+ echo $this->hsc_utf8(__("The goal is for the responses to take so long that the attackers give up and go find an easier target.", self::ID));
456
  echo ' ';
457
+ echo $this->hsc_utf8(__("The length of the delay is broken up into three tiers.", self::ID));
458
  echo ' ';
459
+ echo $this->hsc_utf8(__("The amount of the delay increases in higher tiers.", self::ID));
460
  echo ' ';
461
+ echo $this->hsc_utf8(__("The delay time within each tier is randomized to complicate profiling by attackers.", self::ID));
462
  echo '</p>';
463
  }
464
 
513
  . $this->hsc_utf8($this->option_name)
514
  . '[' . $this->hsc_utf8($name) . ']"'
515
  . ' value="' . $this->hsc_utf8($this->options[$name]) . '" /> ';
516
+ echo $this->hsc_utf8($this->fields[$name]['text']
517
+ . ' ' . __('Default:', self::ID) . ' '
518
+ . $this->options_default[$name] . '.');
519
  }
520
 
521
  /**
548
 
549
  if (!is_scalar($in[$name])) {
550
  // Not translating this since only hackers will see it.
551
+ add_settings_error($this->option_name, $name,
552
+ $this->hsc_utf8("'" . $field['label'])
553
+ . "' was not a scalar, $default");
554
  continue;
555
  }
556
 
558
  case 'bool':
559
  if ($in[$name] != 0 && $in[$name] != 1) {
560
  // Not translating this since only hackers will see it.
561
+ add_settings_error($this->option_name, $name,
562
+ $this->hsc_utf8("'" . $field['label']
563
+ . "' must be '0' or '1', $default"));
564
  continue 2;
565
  }
566
  break;
567
  case 'int':
568
  if (!ctype_digit($in[$name])) {
569
+ add_settings_error($this->option_name, $name,
570
+ $this->hsc_utf8("'" . $field['label'] . "' "
571
+ . __("must be an integer,", self::ID)
572
+ . ' ' . $default));
573
  continue 2;
574
  }
575
  if (array_key_exists('greater_than', $field)
576
  && $in[$name] < $field['greater_than'])
577
  {
578
+ add_settings_error($this->option_name, $name,
579
+ $this->hsc_utf8("'" . $field['label'] . "' "
580
+ . sprintf($gt_format, $field['greater_than'])
581
+ . ' ' . $default));
 
582
  continue 2;
583
  }
584
  break;
589
  // Special check to make sure Delay Tier 3 > Delay Tier 2.
590
  $name = 'login_fail_tier_3';
591
  if ($out[$name] <= $out['login_fail_tier_2']) {
592
+ add_settings_error($this->option_name, $name,
593
+ $this->hsc_utf8("'" . $this->fields[$name]['label'] . "' "
594
+ . sprintf($gt_format, $this->fields['login_fail_tier_2']['label'])
595
+ . ' ' . $default));
 
596
 
597
  $out[$name] = $out['login_fail_tier_2'] + 5;
598
  }
600
  // Speical check to ensure reuse count is set if aging is enabled.
601
  $name = 'pw_reuse_count';
602
  if ($out['pw_change_days'] && !$out[$name]) {
603
+ add_settings_error($this->option_name, $name,
604
+ $this->hsc_utf8("'" . $this->fields[$name]['label'] . "' "
605
+ . sprintf($gt_format, 1)
606
+ . ' ' . $default));
607
 
608
  $out[$name] = 5;
609
  }
622
  * any admin page is rendered
623
  */
624
  public function admin_menu_pw_force_change() {
625
+ add_submenu_page(
626
+ $this->page_options,
627
  $this->text_pw_force_change,
628
  '',
629
+ $this->capability_required,
630
  $this->option_pw_force_change_name,
631
  array(&$this, 'page_pw_force_change')
632
  );
668
  echo '<h2>' . $this->hsc_utf8($this->text_pw_force_change) . '</h2>';
669
 
670
  echo '<p>';
671
+ echo $this->hsc_utf8(__("There may be cases where everyone's password should be reset.", self::ID));
672
  echo ' ';
673
+ echo $this->hsc_utf8(sprintf(__("This page, provided by the %s plugin, offers that functionality.", self::ID), self::NAME));
674
  echo '</p>';
675
 
676
  echo '<p>';
677
+ echo $this->hsc_utf8(__("Submitting this form sets a flag that forces all users to utilize WordPress' built in password reset functionality.", self::ID));
678
  echo ' ';
679
+ echo $this->hsc_utf8(__("Users who are presently logged in will be logged out the next time they view a page that requires authentication.", self::ID));
680
  echo '</p>';
681
 
682
+ echo '<form action="' . $this->hsc_utf8($this->form_action) . '" method="post">' . "\n";
683
  settings_fields($this->option_pw_force_change_name);
684
 
685
  $this->echo_div();
688
  . $this->hsc_utf8($this->option_pw_force_change_name)
689
  . '[' . $this->hsc_utf8($this->key_checkbox_require)
690
  . ']" /> ';
691
+ echo $this->hsc_utf8(__("Confirm that you want to force all users to change their passwords by checking this box, then click the button, below.", self::ID));
692
  echo '</strong></p>';
693
 
694
+ // This function escapes output.
695
  submit_button(
696
  $this->text_button_require,
697
  'primary',
698
+ $this->option_pw_force_change_name . '[submit]'
699
  );
700
 
701
  echo "</div>\n";
707
  . $this->hsc_utf8($this->option_pw_force_change_name)
708
  . '[' . $this->hsc_utf8($this->key_checkbox_remind)
709
  . ']" /> ';
710
+ echo $this->hsc_utf8(__("No thanks. I know what I'm doing. Please don't remind me about this.", self::ID));
711
  echo '</p>';
712
 
713
+ // This function escapes output.
714
  submit_button(
715
  $this->text_button_remind,
716
  'secondary',
717
+ $this->option_pw_force_change_name . '[submit]'
718
  );
719
 
720
  echo "</div>\n";
739
  && !empty($in['submit'])
740
  && is_scalar($in['submit']))
741
  {
742
+ $crossed = $this->hsc_utf8(__("You have checked a box that does not correspond with the button you pressed. Please check and press buttons inside the same section.", self::ID));
743
+
744
+ $confirm = __("Please confirm that you really want to do this. Put a check in the '%s' box before hitting the submit button.", self::ID);
745
 
746
  switch ($in['submit']) {
747
  case $this->text_button_remind:
748
  if (!empty($in[$this->key_checkbox_require])) {
749
  add_settings_error($this->option_pw_force_change_name,
750
+ $this->option_pw_force_change_name,
751
+ $crossed);
752
  } elseif (empty($in[$this->key_checkbox_remind])) {
753
+ add_settings_error($this->option_pw_force_change_name,
754
+ $this->option_pw_force_change_name,
755
+ $this->hsc_utf8(sprintf($confirm, "No thanks")));
756
  } else {
757
  // Translaton already in WP.
758
  add_settings_error($this->option_pw_force_change_name,
759
  $this->option_pw_force_change_name,
760
+ $this->hsc_utf8(__("Success!")),
761
+ 'updated');
762
  $out = 1;
763
  }
764
  break;
765
  case $this->text_button_require:
766
  if (!empty($in[$this->key_checkbox_remind])) {
767
  add_settings_error($this->option_pw_force_change_name,
768
+ $this->option_pw_force_change_name,
769
+ $crossed);
770
  } elseif (empty($in[$this->key_checkbox_require])) {
771
+ add_settings_error($this->option_pw_force_change_name,
772
+ $this->option_pw_force_change_name,
773
+ $this->hsc_utf8(sprintf($confirm, "Confirm")));
774
  } else {
775
  $result = $this->force_change_for_all();
776
  if ($result === true) {
777
  // Translaton already in WP.
778
  add_settings_error($this->option_pw_force_change_name,
779
  $this->option_pw_force_change_name,
780
+ $this->hsc_utf8(__("Success!")), 'updated');
781
  $out = 1;
782
  } else {
783
  add_settings_error($this->option_pw_force_change_name,
784
+ $this->option_pw_force_change_name,
785
+ $this->hsc_utf8($result));
786
  }
787
  }
788
  break;
803
  * @return void
804
  */
805
  public function admin_notices() {
806
+ if (!current_user_can($this->capability_required)) {
807
  return;
808
  }
809
 
810
  echo '<div class="error">';
811
 
812
  echo '<p><strong>';
813
+ echo $this->hsc_utf8(__("You have not asked your users to change their passwords since the plugin was activated. Most users have weak passwords. This plugin's password policies protect your site from brute force attacks. Please improve security for everyone on the Internet by making all users pick new, strong, passwords.", self::ID));
814
  echo '</strong></p>';
815
 
816
  echo '<p><strong>';
817
+ echo $this->hsc_utf8(__("Speaking of which, do YOU have a strong password? Make sure by changing yours once you've submitted the Change All Passwords form.", self::ID));
818
  echo '</strong></p>';
819
 
820
+ echo '<p><strong><a href="' . $this->hsc_utf8($this->page_options)
821
+ . '?page=' . $this->hsc_utf8($this->option_pw_force_change_name)
822
+ . '">' . $this->hsc_utf8($this->text_pw_force_change)
823
+ . "</a></strong></p>\n";
824
 
825
  echo "</div>\n";
826
  }
841
  protected function force_change_for_all() {
842
  global $user_ID, $wpdb;
843
 
844
+ if (!current_user_can($this->capability_required)) {
845
  // Translaton already in WP.
846
  return __('You do not have sufficient permissions to access this page.');
847
  }
languages/login-security-solution.pot CHANGED
@@ -2,9 +2,9 @@
2
  # This file is distributed under the same license as the Login Security Solution package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Login Security Solution 0.0.1\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/login-security-solution\n"
7
- "POT-Creation-Date: 2012-03-19 19:35:46+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
@@ -12,170 +12,168 @@ msgstr ""
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
- #: login-security-solution.php:422
16
  msgid "Invalid username or password."
17
  msgstr ""
18
 
19
- #: login-security-solution.php:428 tests/LoginErrorsTest.php:110
20
- #: tests/LoginErrorsTest.php:122
21
  msgid "Password reset is not allowed for this user"
22
  msgstr ""
23
 
24
- #: login-security-solution.php:453 tests/LoginMessageTest.php:55
25
  msgid "It has been over %d minutes since your last action."
26
  msgstr ""
27
 
28
- #: login-security-solution.php:454 tests/LoginMessageTest.php:56
29
  msgid "Please log back in."
30
  msgstr ""
31
 
32
- #: login-security-solution.php:457 tests/LoginMessageTest.php:66
33
  msgid "The grace period for changing your password has expired."
34
  msgstr ""
35
 
36
- #: login-security-solution.php:458 tests/LoginMessageTest.php:67
37
  msgid "Please submit this form to reset your password."
38
  msgstr ""
39
 
40
- #: login-security-solution.php:461 tests/LoginMessageTest.php:77
41
  msgid "Your password must be reset."
42
  msgstr ""
43
 
44
- #: login-security-solution.php:462 tests/LoginMessageTest.php:78
45
  msgid "Please submit this form to reset it."
46
  msgstr ""
47
 
48
- #: login-security-solution.php:465 tests/LoginMessageTest.php:88
49
  msgid "Your password has expired. Please log and change it."
50
  msgstr ""
51
 
52
- #: login-security-solution.php:466 tests/LoginMessageTest.php:89
53
  msgid "We provide a %d minute grace period to do so."
54
  msgstr ""
55
 
56
- #: login-security-solution.php:472 tests/LoginMessageTest.php:103
57
- #: tests/LoginMessageTest.php:118
58
  msgid "The site is undergoing maintenance."
59
  msgstr ""
60
 
61
- #: login-security-solution.php:473 tests/LoginMessageTest.php:104
62
- #: tests/LoginMessageTest.php:119
63
  msgid "Please try again later."
64
  msgstr ""
65
 
66
- #: login-security-solution.php:532
67
- msgid "<strong>ERROR</strong>: Passwords can not be reused."
68
  msgstr ""
69
 
70
- #: login-security-solution.php:785
 
 
 
 
71
  msgid "Component Count Value from Current Attempt"
72
  msgstr ""
73
 
74
- #: login-security-solution.php:787
75
  msgid "Network IP %5d %s"
76
  msgstr ""
77
 
78
- #: login-security-solution.php:789
79
  msgid "Username %5d %s"
80
  msgstr ""
81
 
82
- #: login-security-solution.php:791
83
  msgid "Password MD5 %5d %s"
84
  msgstr ""
85
 
86
- #: login-security-solution.php:1498
87
  msgid "Your website, %s, may have been broken in to."
88
  msgstr ""
89
 
90
- #: login-security-solution.php:1501
91
  msgid ""
92
  "Someone just logged in using the following components. Prior to that, some "
93
  "combination of those components were a part of %d failed attempts to log in "
94
  "during the past %d minutes:"
95
  msgstr ""
96
 
97
- #: login-security-solution.php:1506
98
  msgid ""
99
  "The user has been logged out and will be required to confirm their identity "
100
  "via the password reset functionality."
101
  msgstr ""
102
 
103
- #: login-security-solution.php:1535
104
  msgid "Your website, %s, is undergoing a brute force attack."
105
  msgstr ""
106
 
107
- #: login-security-solution.php:1538
108
  msgid ""
109
  "There have been at least %d failed attempts to log in during the past %d "
110
  "minutes that used one or more of the following components:"
111
  msgstr ""
112
 
113
- #: login-security-solution.php:1543
114
  msgid ""
115
  "The %s plugin for WordPress is repelling the attack by making their login "
116
  "failures take a very long time."
117
  msgstr ""
118
 
119
- #: login-security-solution.php:1849 tests/PasswordValidationTest.php:436
120
- msgid "<strong>ERROR</strong>: Password not set."
121
  msgstr ""
122
 
123
- #: login-security-solution.php:1864 tests/PasswordValidationTest.php:447
124
- msgid "<strong>ERROR</strong>: Passwords must be strings."
125
  msgstr ""
126
 
127
- #: login-security-solution.php:1882 tests/PasswordValidationTest.php:460
128
- msgid "<strong>ERROR</strong>: Passwords must use ASCII characters."
129
  msgstr ""
130
 
131
- #: login-security-solution.php:1901 tests/PasswordValidationTest.php:477
132
- #: tests/PasswordValidationTest.php:491
133
- msgid "<strong>ERROR</strong>: Password is too short."
134
  msgstr ""
135
 
136
- #: login-security-solution.php:1910 tests/PasswordValidationTest.php:517
137
- msgid ""
138
- "<strong>ERROR</strong>: Passwords must either contain numbers or be %d "
139
- "characters long."
140
  msgstr ""
141
 
142
- #: login-security-solution.php:1919 tests/PasswordValidationTest.php:504
143
  msgid ""
144
- "<strong>ERROR</strong>: Passwords must either contain punctuation marks / "
145
- "symbols or be %d characters long."
146
  msgstr ""
147
 
148
- #: login-security-solution.php:1928 tests/PasswordValidationTest.php:530
149
  msgid ""
150
- "<strong>ERROR</strong>: Passwords must either contain upper-case and lower-"
151
- "case letters or be %d characters long."
152
  msgstr ""
153
 
154
- #: login-security-solution.php:1938 tests/PasswordValidationTest.php:543
155
- msgid "<strong>ERROR</strong>: Passwords can't be sequential keys."
156
  msgstr ""
157
 
158
- #: login-security-solution.php:1947 tests/PasswordValidationTest.php:556
159
- msgid ""
160
- "<strong>ERROR</strong>: Passwords can't have that many sequential characters."
161
  msgstr ""
162
 
163
- #: login-security-solution.php:1963 tests/PasswordValidationTest.php:569
164
- #: tests/PasswordValidationTest.php:582
165
- msgid "<strong>ERROR</strong>: Passwords can't contain user data."
166
  msgstr ""
167
 
168
- #: login-security-solution.php:1974 tests/PasswordValidationTest.php:595
169
- msgid "<strong>ERROR</strong>: Passwords can't contain site info."
170
  msgstr ""
171
 
172
- #: login-security-solution.php:1983 tests/PasswordValidationTest.php:608
173
- msgid "<strong>ERROR</strong>: Password is too common."
174
  msgstr ""
175
 
176
- #: login-security-solution.php:1992 tests/PasswordValidationTest.php:621
177
- msgid ""
178
- "<strong>ERROR</strong>: Passwords can't be variations of dictionary words."
179
  msgstr ""
180
 
181
  #. Plugin Name of the plugin/theme
2
  # This file is distributed under the same license as the Login Security Solution package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Login Security Solution 0.4.0\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/login-security-solution\n"
7
+ "POT-Creation-Date: 2012-04-17 21:46:55+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
+ #: login-security-solution.php:431
16
  msgid "Invalid username or password."
17
  msgstr ""
18
 
19
+ #: login-security-solution.php:437 tests/LoginErrorsTest.php:117
20
+ #: tests/LoginErrorsTest.php:129
21
  msgid "Password reset is not allowed for this user"
22
  msgstr ""
23
 
24
+ #: login-security-solution.php:462 tests/LoginMessageTest.php:61
25
  msgid "It has been over %d minutes since your last action."
26
  msgstr ""
27
 
28
+ #: login-security-solution.php:463 tests/LoginMessageTest.php:62
29
  msgid "Please log back in."
30
  msgstr ""
31
 
32
+ #: login-security-solution.php:466 tests/LoginMessageTest.php:72
33
  msgid "The grace period for changing your password has expired."
34
  msgstr ""
35
 
36
+ #: login-security-solution.php:467 tests/LoginMessageTest.php:73
37
  msgid "Please submit this form to reset your password."
38
  msgstr ""
39
 
40
+ #: login-security-solution.php:470 tests/LoginMessageTest.php:83
41
  msgid "Your password must be reset."
42
  msgstr ""
43
 
44
+ #: login-security-solution.php:471 tests/LoginMessageTest.php:84
45
  msgid "Please submit this form to reset it."
46
  msgstr ""
47
 
48
+ #: login-security-solution.php:474 tests/LoginMessageTest.php:94
49
  msgid "Your password has expired. Please log and change it."
50
  msgstr ""
51
 
52
+ #: login-security-solution.php:475 tests/LoginMessageTest.php:95
53
  msgid "We provide a %d minute grace period to do so."
54
  msgstr ""
55
 
56
+ #: login-security-solution.php:481 tests/LoginMessageTest.php:109
57
+ #: tests/LoginMessageTest.php:124
58
  msgid "The site is undergoing maintenance."
59
  msgstr ""
60
 
61
+ #: login-security-solution.php:482 tests/LoginMessageTest.php:110
62
+ #: tests/LoginMessageTest.php:125
63
  msgid "Please try again later."
64
  msgstr ""
65
 
66
+ #: login-security-solution.php:541
67
+ msgid "Passwords can not be reused."
68
  msgstr ""
69
 
70
+ #: login-security-solution.php:686
71
+ msgid "ERROR"
72
+ msgstr ""
73
+
74
+ #: login-security-solution.php:806
75
  msgid "Component Count Value from Current Attempt"
76
  msgstr ""
77
 
78
+ #: login-security-solution.php:808
79
  msgid "Network IP %5d %s"
80
  msgstr ""
81
 
82
+ #: login-security-solution.php:810
83
  msgid "Username %5d %s"
84
  msgstr ""
85
 
86
+ #: login-security-solution.php:812
87
  msgid "Password MD5 %5d %s"
88
  msgstr ""
89
 
90
+ #: login-security-solution.php:1593
91
  msgid "Your website, %s, may have been broken in to."
92
  msgstr ""
93
 
94
+ #: login-security-solution.php:1596
95
  msgid ""
96
  "Someone just logged in using the following components. Prior to that, some "
97
  "combination of those components were a part of %d failed attempts to log in "
98
  "during the past %d minutes:"
99
  msgstr ""
100
 
101
+ #: login-security-solution.php:1601
102
  msgid ""
103
  "The user has been logged out and will be required to confirm their identity "
104
  "via the password reset functionality."
105
  msgstr ""
106
 
107
+ #: login-security-solution.php:1630
108
  msgid "Your website, %s, is undergoing a brute force attack."
109
  msgstr ""
110
 
111
+ #: login-security-solution.php:1633
112
  msgid ""
113
  "There have been at least %d failed attempts to log in during the past %d "
114
  "minutes that used one or more of the following components:"
115
  msgstr ""
116
 
117
+ #: login-security-solution.php:1638
118
  msgid ""
119
  "The %s plugin for WordPress is repelling the attack by making their login "
120
  "failures take a very long time."
121
  msgstr ""
122
 
123
+ #: login-security-solution.php:1949
124
+ msgid "Password not set."
125
  msgstr ""
126
 
127
+ #: login-security-solution.php:1964
128
+ msgid "Passwords must be strings."
129
  msgstr ""
130
 
131
+ #: login-security-solution.php:1982
132
+ msgid "Passwords must use ASCII characters."
133
  msgstr ""
134
 
135
+ #: login-security-solution.php:2001
136
+ msgid "Password is too short."
 
137
  msgstr ""
138
 
139
+ #: login-security-solution.php:2010
140
+ msgid "Passwords must either contain numbers or be %d characters long."
 
 
141
  msgstr ""
142
 
143
+ #: login-security-solution.php:2019
144
  msgid ""
145
+ "Passwords must either contain punctuation marks / symbols or be %d "
146
+ "characters long."
147
  msgstr ""
148
 
149
+ #: login-security-solution.php:2028
150
  msgid ""
151
+ "Passwords must either contain upper-case and lower-case letters or be %d "
152
+ "characters long."
153
  msgstr ""
154
 
155
+ #: login-security-solution.php:2038
156
+ msgid "Passwords can't be sequential keys."
157
  msgstr ""
158
 
159
+ #: login-security-solution.php:2047
160
+ msgid "Passwords can't have that many sequential characters."
 
161
  msgstr ""
162
 
163
+ #: login-security-solution.php:2063
164
+ msgid "Passwords can't contain user data."
 
165
  msgstr ""
166
 
167
+ #: login-security-solution.php:2074
168
+ msgid "Passwords can't contain site info."
169
  msgstr ""
170
 
171
+ #: login-security-solution.php:2083
172
+ msgid "Password is too common."
173
  msgstr ""
174
 
175
+ #: login-security-solution.php:2092
176
+ msgid "Passwords can't be variations of dictionary words."
 
177
  msgstr ""
178
 
179
  #. Plugin Name of the plugin/theme
login-security-solution.php CHANGED
@@ -6,7 +6,7 @@
6
  * Description: Requires very strong passwords, repels brute force login attacks, prevents login information disclosures, expires idle sessions, notifies admins of attacks and breaches, permits administrators to disable logins for maintenance or emergency reasons and reset all passwords.
7
  *
8
  * Plugin URI: http://wordpress.org/extend/plugins/login-security-solution/
9
- * Version: 0.3.0
10
  * Author: Daniel Convissor
11
  * Author URI: http://www.analysisandsolutions.com/
12
  * License: GPLv2
@@ -23,7 +23,6 @@ $GLOBALS['login_security_solution'] = new login_security_solution;
23
  *
24
  * @package login-security-solution
25
  * @link http://wordpress.org/extend/plugins/login-security-solution/
26
- * @version 0.0.1
27
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
28
  * @author Daniel Convissor <danielc@analysisandsolutions.com>
29
  * @copyright The Analysis and Solutions Company, 2012
@@ -205,9 +204,19 @@ class login_security_solution {
205
  require_once dirname(__FILE__) . '/admin.inc';
206
  $admin = new login_security_solution_admin;
207
 
208
- add_action('admin_menu', array(&$admin, 'admin_menu'));
 
 
 
 
 
 
 
 
 
 
209
  add_action('admin_init', array(&$admin, 'admin_init'));
210
- add_filter('plugin_action_links', array(&$admin, 'plugin_action_links'), 10, 2);
211
 
212
  register_activation_hook(__FILE__, array(&$admin, 'activate'));
213
  if ($this->options['deactivate_deletes_data']) {
@@ -215,11 +224,11 @@ class login_security_solution {
215
  }
216
 
217
  // NON-STANDARD: This is for the password change page.
218
- add_action('admin_menu', array(&$admin, 'admin_menu_pw_force_change'));
219
- add_action('admin_init', array(&$admin, 'admin_init_pw_force_change'));
220
  if (!$admin->was_pw_force_change_done()) {
221
- add_action('admin_notices', array(&$admin, 'admin_notices'));
222
  }
 
223
  }
224
  }
225
 
@@ -419,13 +428,13 @@ class login_security_solution {
419
  $user_pass = empty($_POST['pwd']) ? '' : $_POST['pwd'];
420
  $this->process_login_fail($user_name, $user_pass);
421
  $this->load_plugin_textdomain();
422
- return __('Invalid username or password.', self::ID);
423
  }
424
 
425
  $codes_to_cloak = array('invalid_email', 'invalidcombo');
426
  if (array_intersect($error_codes, $codes_to_cloak)) {
427
  // This text is lifted directly from WordPress.
428
- return __('Password reset is not allowed for this user');
429
  }
430
 
431
  return $out;
@@ -529,7 +538,7 @@ class login_security_solution {
529
  if ($this->is_pw_reused($user->user_pass, $user->ID)) {
530
  $this->load_plugin_textdomain();
531
  $errors->add(self::ID,
532
- __("<strong>ERROR</strong>: Passwords can not be reused.", self::ID),
533
  array('form-field' => 'pass1')
534
  );
535
  return false;
@@ -667,6 +676,18 @@ class login_security_solution {
667
  return delete_user_meta($user_ID, $this->umk_grace_period);
668
  }
669
 
 
 
 
 
 
 
 
 
 
 
 
 
670
  /**
671
  * Obtains the IP address from $_SERVER['REMOTE_ADDR']
672
  *
@@ -1665,6 +1686,11 @@ class login_security_solution {
1665
 
1666
  if ($sleep) {
1667
  if (!$this->testing) {
 
 
 
 
 
1668
  // Keep login failures from becoming denial of service attacks.
1669
  mysql_close($wpdb->dbh);
1670
 
@@ -1920,7 +1946,7 @@ class login_security_solution {
1920
  if (empty($user->user_pass)) {
1921
  if ($errors !== null) {
1922
  $errors->add(self::ID,
1923
- __("<strong>ERROR</strong>: Password not set.", self::ID),
1924
  array('form-field' => 'pass1')
1925
  );
1926
  }
@@ -1935,7 +1961,7 @@ class login_security_solution {
1935
  if (!is_string($pw)) {
1936
  if ($errors !== null) {
1937
  $errors->add(self::ID,
1938
- __("<strong>ERROR</strong>: Passwords must be strings.", self::ID),
1939
  array('form-field' => 'pass1')
1940
  );
1941
  }
@@ -1953,7 +1979,7 @@ class login_security_solution {
1953
  {
1954
  if ($errors !== null) {
1955
  $errors->add(self::ID,
1956
- __("<strong>ERROR</strong>: Passwords must use ASCII characters.", self::ID),
1957
  array('form-field' => 'pass1')
1958
  );
1959
  }
@@ -1972,7 +1998,7 @@ class login_security_solution {
1972
  if ($length < $this->options['pw_length']) {
1973
  if ($errors !== null) {
1974
  $errors->add(self::ID,
1975
- __("<strong>ERROR</strong>: Password is too short.", self::ID),
1976
  array('form-field' => 'pass1')
1977
  );
1978
  }
@@ -1981,7 +2007,7 @@ class login_security_solution {
1981
  if ($enforce_complexity && $this->is_pw_missing_numeric($pw)) {
1982
  if ($errors !== null) {
1983
  $errors->add(self::ID,
1984
- sprintf(__("<strong>ERROR</strong>: Passwords must either contain numbers or be %d characters long.", self::ID), $this->options['pw_complexity_exemption_length']),
1985
  array('form-field' => 'pass1')
1986
  );
1987
  }
@@ -1990,7 +2016,7 @@ class login_security_solution {
1990
  if ($enforce_complexity && $this->is_pw_missing_punct_chars($pw)) {
1991
  if ($errors !== null) {
1992
  $errors->add(self::ID,
1993
- sprintf(__("<strong>ERROR</strong>: Passwords must either contain punctuation marks / symbols or be %d characters long.", self::ID), $this->options['pw_complexity_exemption_length']),
1994
  array('form-field' => 'pass1')
1995
  );
1996
  }
@@ -1999,7 +2025,7 @@ class login_security_solution {
1999
  if ($enforce_complexity && $this->is_pw_missing_upper_lower_chars($pw)) {
2000
  if ($errors !== null) {
2001
  $errors->add(self::ID,
2002
- sprintf(__("<strong>ERROR</strong>: Passwords must either contain upper-case and lower-case letters or be %d characters long.", self::ID), $this->options['pw_complexity_exemption_length']),
2003
  array('form-field' => 'pass1')
2004
  );
2005
  }
@@ -2009,7 +2035,7 @@ class login_security_solution {
2009
  if ($this->is_pw_sequential_file($pw)) {
2010
  if ($errors !== null) {
2011
  $errors->add(self::ID,
2012
- __("<strong>ERROR</strong>: Passwords can't be sequential keys.", self::ID),
2013
  array('form-field' => 'pass1')
2014
  );
2015
  }
@@ -2018,7 +2044,7 @@ class login_security_solution {
2018
  if ($this->is_pw_sequential_codepoints($pw)) {
2019
  if ($errors !== null) {
2020
  $errors->add(self::ID,
2021
- __("<strong>ERROR</strong>: Passwords can't have that many sequential characters.", self::ID),
2022
  array('form-field' => 'pass1')
2023
  );
2024
  }
@@ -2034,7 +2060,7 @@ class login_security_solution {
2034
  {
2035
  if ($errors !== null) {
2036
  $errors->add(self::ID,
2037
- __("<strong>ERROR</strong>: Passwords can't contain user data.", self::ID),
2038
  array('form-field' => 'pass1')
2039
  );
2040
  }
@@ -2045,7 +2071,7 @@ class login_security_solution {
2045
  {
2046
  if ($errors !== null) {
2047
  $errors->add(self::ID,
2048
- __("<strong>ERROR</strong>: Passwords can't contain site info.", self::ID),
2049
  array('form-field' => 'pass1')
2050
  );
2051
  }
@@ -2054,7 +2080,7 @@ class login_security_solution {
2054
  if ($all_tests && $this->is_pw_dictionary($pw)) {
2055
  if ($errors !== null) {
2056
  $errors->add(self::ID,
2057
- __("<strong>ERROR</strong>: Password is too common.", self::ID),
2058
  array('form-field' => 'pass1')
2059
  );
2060
  }
@@ -2063,7 +2089,7 @@ class login_security_solution {
2063
  if ($this->is_pw_dict_program($stripped)) {
2064
  if ($errors !== null) {
2065
  $errors->add(self::ID,
2066
- __("<strong>ERROR</strong>: Passwords can't be variations of dictionary words.", self::ID),
2067
  array('form-field' => 'pass1')
2068
  );
2069
  }
6
  * Description: Requires very strong passwords, repels brute force login attacks, prevents login information disclosures, expires idle sessions, notifies admins of attacks and breaches, permits administrators to disable logins for maintenance or emergency reasons and reset all passwords.
7
  *
8
  * Plugin URI: http://wordpress.org/extend/plugins/login-security-solution/
9
+ * Version: 0.4.0
10
  * Author: Daniel Convissor
11
  * Author URI: http://www.analysisandsolutions.com/
12
  * License: GPLv2
23
  *
24
  * @package login-security-solution
25
  * @link http://wordpress.org/extend/plugins/login-security-solution/
 
26
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
27
  * @author Daniel Convissor <danielc@analysisandsolutions.com>
28
  * @copyright The Analysis and Solutions Company, 2012
204
  require_once dirname(__FILE__) . '/admin.inc';
205
  $admin = new login_security_solution_admin;
206
 
207
+ if (is_multisite()) {
208
+ $admin_menu = 'network_admin_menu';
209
+ $admin_notices = 'network_admin_notices';
210
+ $plugin_action_links = 'network_admin_plugin_action_links_login-security-solution/login-security-solution.php';
211
+ } else {
212
+ $admin_menu = 'admin_menu';
213
+ $admin_notices = 'admin_notices';
214
+ $plugin_action_links = 'plugin_action_links_login-security-solution/login-security-solution.php';
215
+ }
216
+
217
+ add_action($admin_menu, array(&$admin, 'admin_menu'));
218
  add_action('admin_init', array(&$admin, 'admin_init'));
219
+ add_filter($plugin_action_links, array(&$admin, 'plugin_action_links'));
220
 
221
  register_activation_hook(__FILE__, array(&$admin, 'activate'));
222
  if ($this->options['deactivate_deletes_data']) {
224
  }
225
 
226
  // NON-STANDARD: This is for the password change page.
227
+ add_action($admin_menu, array(&$admin, 'admin_menu_pw_force_change'));
 
228
  if (!$admin->was_pw_force_change_done()) {
229
+ add_action($admin_notices, array(&$admin, 'admin_notices'));
230
  }
231
+ add_action('admin_init', array(&$admin, 'admin_init_pw_force_change'));
232
  }
233
  }
234
 
428
  $user_pass = empty($_POST['pwd']) ? '' : $_POST['pwd'];
429
  $this->process_login_fail($user_name, $user_pass);
430
  $this->load_plugin_textdomain();
431
+ return $this->hsc_utf8(__('Invalid username or password.', self::ID));
432
  }
433
 
434
  $codes_to_cloak = array('invalid_email', 'invalidcombo');
435
  if (array_intersect($error_codes, $codes_to_cloak)) {
436
  // This text is lifted directly from WordPress.
437
+ return $this->hsc_utf8(__('Password reset is not allowed for this user'));
438
  }
439
 
440
  return $out;
538
  if ($this->is_pw_reused($user->user_pass, $user->ID)) {
539
  $this->load_plugin_textdomain();
540
  $errors->add(self::ID,
541
+ $this->err(__("Passwords can not be reused.", self::ID)),
542
  array('form-field' => 'pass1')
543
  );
544
  return false;
676
  return delete_user_meta($user_ID, $this->umk_grace_period);
677
  }
678
 
679
+ /**
680
+ * Safely composes translated error messages
681
+ *
682
+ * @param string $message the error message
683
+ * @return string
684
+ */
685
+ protected function err($message) {
686
+ $error = $this->hsc_utf8(__("ERROR"));
687
+ $message = $this->hsc_utf8($message);
688
+ return "<strong>$error</strong>: $message";
689
+ }
690
+
691
  /**
692
  * Obtains the IP address from $_SERVER['REMOTE_ADDR']
693
  *
1686
 
1687
  if ($sleep) {
1688
  if (!$this->testing) {
1689
+ if (is_multisite()) {
1690
+ // Get this cached before disconnecting the database.
1691
+ get_option('users_can_register');
1692
+ }
1693
+
1694
  // Keep login failures from becoming denial of service attacks.
1695
  mysql_close($wpdb->dbh);
1696
 
1946
  if (empty($user->user_pass)) {
1947
  if ($errors !== null) {
1948
  $errors->add(self::ID,
1949
+ $this->err(__("Password not set.", self::ID)),
1950
  array('form-field' => 'pass1')
1951
  );
1952
  }
1961
  if (!is_string($pw)) {
1962
  if ($errors !== null) {
1963
  $errors->add(self::ID,
1964
+ $this->err(__("Passwords must be strings.", self::ID)),
1965
  array('form-field' => 'pass1')
1966
  );
1967
  }
1979
  {
1980
  if ($errors !== null) {
1981
  $errors->add(self::ID,
1982
+ $this->err(__("Passwords must use ASCII characters.", self::ID)),
1983
  array('form-field' => 'pass1')
1984
  );
1985
  }
1998
  if ($length < $this->options['pw_length']) {
1999
  if ($errors !== null) {
2000
  $errors->add(self::ID,
2001
+ $this->err(__("Password is too short.", self::ID)),
2002
  array('form-field' => 'pass1')
2003
  );
2004
  }
2007
  if ($enforce_complexity && $this->is_pw_missing_numeric($pw)) {
2008
  if ($errors !== null) {
2009
  $errors->add(self::ID,
2010
+ $this->err(sprintf(__("Passwords must either contain numbers or be %d characters long.", self::ID), $this->options['pw_complexity_exemption_length'])),
2011
  array('form-field' => 'pass1')
2012
  );
2013
  }
2016
  if ($enforce_complexity && $this->is_pw_missing_punct_chars($pw)) {
2017
  if ($errors !== null) {
2018
  $errors->add(self::ID,
2019
+ $this->err(sprintf(__("Passwords must either contain punctuation marks / symbols or be %d characters long.", self::ID), $this->options['pw_complexity_exemption_length'])),
2020
  array('form-field' => 'pass1')
2021
  );
2022
  }
2025
  if ($enforce_complexity && $this->is_pw_missing_upper_lower_chars($pw)) {
2026
  if ($errors !== null) {
2027
  $errors->add(self::ID,
2028
+ $this->err(sprintf(__("Passwords must either contain upper-case and lower-case letters or be %d characters long.", self::ID), $this->options['pw_complexity_exemption_length'])),
2029
  array('form-field' => 'pass1')
2030
  );
2031
  }
2035
  if ($this->is_pw_sequential_file($pw)) {
2036
  if ($errors !== null) {
2037
  $errors->add(self::ID,
2038
+ $this->err(__("Passwords can't be sequential keys.", self::ID)),
2039
  array('form-field' => 'pass1')
2040
  );
2041
  }
2044
  if ($this->is_pw_sequential_codepoints($pw)) {
2045
  if ($errors !== null) {
2046
  $errors->add(self::ID,
2047
+ $this->err(__("Passwords can't have that many sequential characters.", self::ID)),
2048
  array('form-field' => 'pass1')
2049
  );
2050
  }
2060
  {
2061
  if ($errors !== null) {
2062
  $errors->add(self::ID,
2063
+ $this->err(__("Passwords can't contain user data.", self::ID)),
2064
  array('form-field' => 'pass1')
2065
  );
2066
  }
2071
  {
2072
  if ($errors !== null) {
2073
  $errors->add(self::ID,
2074
+ $this->err(__("Passwords can't contain site info.", self::ID)),
2075
  array('form-field' => 'pass1')
2076
  );
2077
  }
2080
  if ($all_tests && $this->is_pw_dictionary($pw)) {
2081
  if ($errors !== null) {
2082
  $errors->add(self::ID,
2083
+ $this->err(__("Password is too common.", self::ID)),
2084
  array('form-field' => 'pass1')
2085
  );
2086
  }
2089
  if ($this->is_pw_dict_program($stripped)) {
2090
  if ($errors !== null) {
2091
  $errors->add(self::ID,
2092
+ $this->err(__("Passwords can't be variations of dictionary words.", self::ID)),
2093
  array('form-field' => 'pass1')
2094
  );
2095
  }
readme.txt CHANGED
@@ -63,6 +63,7 @@ The tests have caught every password dictionary entry I've tried.
63
 
64
  = Improvements Over Similar WordPress Plugins =
65
 
 
66
  * The plugin itself is secure against SQL, HTML, and header injections
67
  * Notice-free code means no information disclosures if `display_errors`
68
  is on and `error_reporting` includes `E_NOTICE`
@@ -159,11 +160,15 @@ clients and friends.
159
  1. Upload the `login-security-solution` directory to your
160
  server's `/wp-content/plugins/` directory
161
 
162
- 1. Activate the plugin through the "Plugins" menu in WordPress.
 
 
163
 
164
  1. Adjust the settings as desired. This plugin's settings page can be
165
  reached via a sub-menu entry under WordPress' "Settings" menu or this
166
- plugin's entry on WordPress' "Plugins" page.
 
 
167
 
168
  1. Run the "Change All Passwords" process. This is necessary to ensure
169
  all of your users have strong passwords. The user interface for
@@ -177,9 +182,7 @@ clients and friends.
177
 
178
  A thorough set of unit tests are found in the `tests` directory.
179
 
180
- The plugin needs to be placed in the `wp-contents/plugins` directory of
181
- a working WordPress installation. The plugin does not need to be
182
- activated for the tests to run.
183
 
184
  To execute the tests, `cd` into this plugin's directory and
185
  call `phpunit tests` .
@@ -204,11 +207,29 @@ are not using the `InnoDB` storage engine.
204
 
205
  == Frequently Asked Questions ==
206
 
207
- Ask and ye shall receive.
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
 
210
  == Changelog ==
211
 
 
 
 
 
 
212
  = 0.3.0 =
213
  * Use UTF-8 encoding for `htmlspecialchars()` instead of `DB_CHARSET`.
214
  * Tested under WordPress 3.3.1.
63
 
64
  = Improvements Over Similar WordPress Plugins =
65
 
66
+ * Multisite network support
67
  * The plugin itself is secure against SQL, HTML, and header injections
68
  * Notice-free code means no information disclosures if `display_errors`
69
  is on and `error_reporting` includes `E_NOTICE`
160
  1. Upload the `login-security-solution` directory to your
161
  server's `/wp-content/plugins/` directory
162
 
163
+ 1. Activate the plugin using WordPress' admin interface:
164
+ * Regular sites: Plugins
165
+ * Sites using multisite networks: My Sites | Network Admin | Plugins
166
 
167
  1. Adjust the settings as desired. This plugin's settings page can be
168
  reached via a sub-menu entry under WordPress' "Settings" menu or this
169
+ plugin's entry on WordPress' "Plugins" page. Sites using WordPress'
170
+ multisite network capabilitiy will find the "Settings" and "Plugin"
171
+ menus under "My Sites | Network Admin".
172
 
173
  1. Run the "Change All Passwords" process. This is necessary to ensure
174
  all of your users have strong passwords. The user interface for
182
 
183
  A thorough set of unit tests are found in the `tests` directory.
184
 
185
+ The plugin needs to be installed and activated before running the tests.
 
 
186
 
187
  To execute the tests, `cd` into this plugin's directory and
188
  call `phpunit tests` .
207
 
208
  == Frequently Asked Questions ==
209
 
210
+ = Where did the "Change All Passwords" interface go? =
211
+
212
+ A link to the page is found in this plugin's entry in the "Plugins" admin
213
+ interface:
214
+ * Regular sites: Plugins
215
+ * Sites using multisite networks: My Sites | Network Admin | Plugins
216
+
217
+ = How do I generate the POT translation file? =
218
+
219
+ Get the translation tools from `http://i18n.svn.wordpress.org/tools/trunk/`
220
+ then `cd` into that directory and run:
221
+
222
+ php makepot.php wp-plugin ../login-security-solution \
223
+ ../login-security-solution/languages/login-security-solution.pot
224
 
225
 
226
  == Changelog ==
227
 
228
+ = 0.4.0 =
229
+ * Add multisite network support.
230
+ * Keep unit tests from deleting settings. Note: removes the ability to
231
+ run the unit tests without activating the plugin.
232
+
233
  = 0.3.0 =
234
  * Use UTF-8 encoding for `htmlspecialchars()` instead of `DB_CHARSET`.
235
  * Tested under WordPress 3.3.1.
tests/Accessor.php CHANGED
@@ -32,12 +32,6 @@ unset($GLOBALS['login_security_solution']);
32
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
33
  */
34
  class Accessor extends login_security_solution_admin {
35
- /**
36
- * This plugin's table name prefix FOR TESTING
37
- * @var string
38
- */
39
- protected $prefix = 'login_security_solution__tests__';
40
-
41
  /**
42
  * Is this class being used by our unit tests?
43
  * @var bool
32
  * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
33
  */
34
  class Accessor extends login_security_solution_admin {
 
 
 
 
 
 
35
  /**
36
  * Is this class being used by our unit tests?
37
  * @var bool
tests/PasswordValidationTest.php CHANGED
@@ -56,6 +56,11 @@ class PasswordValidationTest extends TestCase {
56
  }
57
 
58
 
 
 
 
 
 
59
  public function test_is_pw_dictionary__grepavail() {
60
  if (!self::$dict_available) {
61
  $this->markTestSkipped('grep not available');
@@ -437,7 +442,7 @@ class PasswordValidationTest extends TestCase {
437
  $this->assertFalse($actual,
438
  "password not being set should have failed.");
439
  $this->assertEquals(
440
- __("<strong>ERROR</strong>: Password not set.", self::ID),
441
  $errors->get_error_message()
442
  );
443
  }
@@ -448,7 +453,7 @@ class PasswordValidationTest extends TestCase {
448
  $this->assertFalse($actual,
449
  "'array('abc')' should have failed.");
450
  $this->assertEquals(
451
- __("<strong>ERROR</strong>: Passwords must be strings.", self::ID),
452
  $errors->get_error_message()
453
  );
454
  }
@@ -461,7 +466,7 @@ class PasswordValidationTest extends TestCase {
461
  $this->assertFalse($actual,
462
  "'" . $this->user->user_pass . "' should have failed.");
463
  $this->assertEquals(
464
- __("<strong>ERROR</strong>: Passwords must use ASCII characters.", self::ID),
465
  $errors->get_error_message()
466
  );
467
  }
@@ -478,7 +483,7 @@ class PasswordValidationTest extends TestCase {
478
  $this->assertFalse($actual,
479
  "'" . $this->user->user_pass . "' should have failed.");
480
  $this->assertEquals(
481
- __("<strong>ERROR</strong>: Password is too short.", self::ID),
482
  $errors->get_error_message()
483
  );
484
  }
@@ -492,7 +497,7 @@ class PasswordValidationTest extends TestCase {
492
  $this->assertFalse($actual,
493
  "'" . $this->user->user_pass . "' should have failed.");
494
  $this->assertEquals(
495
- __("<strong>ERROR</strong>: Password is too short.", self::ID),
496
  $errors->get_error_message()
497
  );
498
  }
@@ -505,7 +510,7 @@ class PasswordValidationTest extends TestCase {
505
  $this->assertFalse($actual,
506
  "'" . $this->user->user_pass . "' should have failed.");
507
  $this->assertEquals(
508
- sprintf(__("<strong>ERROR</strong>: Passwords must either contain punctuation marks / symbols or be %d characters long.", self::ID), self::$lss->options['pw_complexity_exemption_length']),
509
  $errors->get_error_message()
510
  );
511
  }
@@ -518,7 +523,7 @@ class PasswordValidationTest extends TestCase {
518
  $this->assertFalse($actual,
519
  "'" . $this->user->user_pass . "' should have failed.");
520
  $this->assertEquals(
521
- sprintf(__("<strong>ERROR</strong>: Passwords must either contain numbers or be %d characters long.", self::ID), self::$lss->options['pw_complexity_exemption_length']),
522
  $errors->get_error_message()
523
  );
524
  }
@@ -531,7 +536,7 @@ class PasswordValidationTest extends TestCase {
531
  $this->assertFalse($actual,
532
  "'" . $this->user->user_pass . "' should have failed.");
533
  $this->assertEquals(
534
- sprintf(__("<strong>ERROR</strong>: Passwords must either contain upper-case and lower-case letters or be %d characters long.", self::ID), self::$lss->options['pw_complexity_exemption_length']),
535
  $errors->get_error_message()
536
  );
537
  }
@@ -544,7 +549,7 @@ class PasswordValidationTest extends TestCase {
544
  $this->assertFalse($actual,
545
  "'" . $this->user->user_pass . "' should have failed.");
546
  $this->assertEquals(
547
- __("<strong>ERROR</strong>: Passwords can't be sequential keys.", self::ID),
548
  $errors->get_error_message()
549
  );
550
  }
@@ -557,7 +562,7 @@ class PasswordValidationTest extends TestCase {
557
  $this->assertFalse($actual,
558
  "'" . $this->user->user_pass . "' should have failed.");
559
  $this->assertEquals(
560
- __("<strong>ERROR</strong>: Passwords can't have that many sequential characters.", self::ID),
561
  $errors->get_error_message()
562
  );
563
  }
@@ -570,7 +575,7 @@ class PasswordValidationTest extends TestCase {
570
  $this->assertFalse($actual,
571
  "'" . $this->user->user_pass . "' should have failed.");
572
  $this->assertEquals(
573
- __("<strong>ERROR</strong>: Passwords can't contain user data.", self::ID),
574
  $errors->get_error_message()
575
  );
576
  }
@@ -583,7 +588,7 @@ class PasswordValidationTest extends TestCase {
583
  $this->assertFalse($actual,
584
  "'" . $this->user->user_pass . "' should have failed.");
585
  $this->assertEquals(
586
- __("<strong>ERROR</strong>: Passwords can't contain user data.", self::ID),
587
  $errors->get_error_message()
588
  );
589
  }
@@ -596,7 +601,7 @@ class PasswordValidationTest extends TestCase {
596
  $this->assertFalse($actual,
597
  "'" . $this->user->user_pass . "' should have failed.");
598
  $this->assertEquals(
599
- __("<strong>ERROR</strong>: Passwords can't contain site info.", self::ID),
600
  $errors->get_error_message()
601
  );
602
  }
@@ -609,7 +614,7 @@ class PasswordValidationTest extends TestCase {
609
  $this->assertFalse($actual,
610
  "'" . $this->user->user_pass . "' should have failed.");
611
  $this->assertEquals(
612
- __("<strong>ERROR</strong>: Password is too common.", self::ID),
613
  $errors->get_error_message()
614
  );
615
  }
@@ -625,7 +630,7 @@ class PasswordValidationTest extends TestCase {
625
  $this->assertFalse($actual,
626
  "'" . $this->user->user_pass . "' should have failed.");
627
  $this->assertEquals(
628
- __("<strong>ERROR</strong>: Passwords can't be variations of dictionary words.", self::ID),
629
  $errors->get_error_message()
630
  );
631
  }
56
  }
57
 
58
 
59
+ protected function err($message) {
60
+ return self::$lss->err($message);
61
+ }
62
+
63
+
64
  public function test_is_pw_dictionary__grepavail() {
65
  if (!self::$dict_available) {
66
  $this->markTestSkipped('grep not available');
442
  $this->assertFalse($actual,
443
  "password not being set should have failed.");
444
  $this->assertEquals(
445
+ $this->err("Password not set."),
446
  $errors->get_error_message()
447
  );
448
  }
453
  $this->assertFalse($actual,
454
  "'array('abc')' should have failed.");
455
  $this->assertEquals(
456
+ $this->err("Passwords must be strings."),
457
  $errors->get_error_message()
458
  );
459
  }
466
  $this->assertFalse($actual,
467
  "'" . $this->user->user_pass . "' should have failed.");
468
  $this->assertEquals(
469
+ $this->err("Passwords must use ASCII characters."),
470
  $errors->get_error_message()
471
  );
472
  }
483
  $this->assertFalse($actual,
484
  "'" . $this->user->user_pass . "' should have failed.");
485
  $this->assertEquals(
486
+ $this->err("Password is too short."),
487
  $errors->get_error_message()
488
  );
489
  }
497
  $this->assertFalse($actual,
498
  "'" . $this->user->user_pass . "' should have failed.");
499
  $this->assertEquals(
500
+ $this->err("Password is too short."),
501
  $errors->get_error_message()
502
  );
503
  }
510
  $this->assertFalse($actual,
511
  "'" . $this->user->user_pass . "' should have failed.");
512
  $this->assertEquals(
513
+ $this->err(sprintf("Passwords must either contain punctuation marks / symbols or be %d characters long.", self::$lss->options['pw_complexity_exemption_length'])),
514
  $errors->get_error_message()
515
  );
516
  }
523
  $this->assertFalse($actual,
524
  "'" . $this->user->user_pass . "' should have failed.");
525
  $this->assertEquals(
526
+ $this->err(sprintf("Passwords must either contain numbers or be %d characters long.", self::$lss->options['pw_complexity_exemption_length'])),
527
  $errors->get_error_message()
528
  );
529
  }
536
  $this->assertFalse($actual,
537
  "'" . $this->user->user_pass . "' should have failed.");
538
  $this->assertEquals(
539
+ $this->err(sprintf("Passwords must either contain upper-case and lower-case letters or be %d characters long.", self::$lss->options['pw_complexity_exemption_length'])),
540
  $errors->get_error_message()
541
  );
542
  }
549
  $this->assertFalse($actual,
550
  "'" . $this->user->user_pass . "' should have failed.");
551
  $this->assertEquals(
552
+ $this->err("Passwords can't be sequential keys."),
553
  $errors->get_error_message()
554
  );
555
  }
562
  $this->assertFalse($actual,
563
  "'" . $this->user->user_pass . "' should have failed.");
564
  $this->assertEquals(
565
+ $this->err("Passwords can't have that many sequential characters."),
566
  $errors->get_error_message()
567
  );
568
  }
575
  $this->assertFalse($actual,
576
  "'" . $this->user->user_pass . "' should have failed.");
577
  $this->assertEquals(
578
+ $this->err("Passwords can't contain user data."),
579
  $errors->get_error_message()
580
  );
581
  }
588
  $this->assertFalse($actual,
589
  "'" . $this->user->user_pass . "' should have failed.");
590
  $this->assertEquals(
591
+ $this->err("Passwords can't contain user data."),
592
  $errors->get_error_message()
593
  );
594
  }
601
  $this->assertFalse($actual,
602
  "'" . $this->user->user_pass . "' should have failed.");
603
  $this->assertEquals(
604
+ $this->err("Passwords can't contain site info."),
605
  $errors->get_error_message()
606
  );
607
  }
614
  $this->assertFalse($actual,
615
  "'" . $this->user->user_pass . "' should have failed.");
616
  $this->assertEquals(
617
+ $this->err("Password is too common."),
618
  $errors->get_error_message()
619
  );
620
  }
630
  $this->assertFalse($actual,
631
  "'" . $this->user->user_pass . "' should have failed.");
632
  $this->assertEquals(
633
+ $this->err("Passwords can't be variations of dictionary words."),
634
  $errors->get_error_message()
635
  );
636
  }
tests/TestCase.php CHANGED
@@ -21,6 +21,21 @@
21
  */
22
  global $wp_rewrite;
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  /**
25
  * Overrides the wp_mail() function so we can ensure the messages are
26
  * composed when and how they should be
@@ -51,6 +66,13 @@ if (!is_readable($wp_load)) {
51
  }
52
  require_once $wp_load;
53
 
 
 
 
 
 
 
 
54
  /**
55
  * Get the class we will use for testing
56
  */
@@ -170,8 +192,8 @@ abstract class TestCase extends PHPUnit_Framework_TestCase {
170
 
171
  if (self::$db_needed && self::are_transactions_available()) {
172
  self::$db_has_transactions = true;
173
- self::$lss->activate();
174
  $wpdb->query('START TRANSACTION');
 
175
  } else {
176
  self::$db_has_transactions = false;
177
  }
@@ -185,7 +207,6 @@ abstract class TestCase extends PHPUnit_Framework_TestCase {
185
 
186
  if (self::$db_has_transactions) {
187
  $wpdb->query('ROLLBACK');
188
- self::$lss->deactivate();
189
  }
190
 
191
  self::$lss = null;
21
  */
22
  global $wp_rewrite;
23
 
24
+
25
+ /*
26
+ * Hacks to keep WordPress multisite network mode happy under PHPUnit.
27
+ */
28
+
29
+ // Undefined index: HTTP_HOST in wp-includes/ms-settings.php.
30
+ $_SERVER['HTTP_HOST'] = 'localhost';
31
+
32
+ // Undefined variable: wpdb in wp-includes/ms-settings.php.
33
+ global $wpdb;
34
+
35
+ // Trying to get property of non-object in wp-includes/functions.php.
36
+ global $current_site, $current_blog;
37
+
38
+
39
  /**
40
  * Overrides the wp_mail() function so we can ensure the messages are
41
  * composed when and how they should be
66
  }
67
  require_once $wp_load;
68
 
69
+
70
+ if (is_multisite()) {
71
+ // Workaround for the authentication check in my activate() method.
72
+ define('WP_NETWORK_ADMIN', true);
73
+ }
74
+
75
+
76
  /**
77
  * Get the class we will use for testing
78
  */
192
 
193
  if (self::$db_needed && self::are_transactions_available()) {
194
  self::$db_has_transactions = true;
 
195
  $wpdb->query('START TRANSACTION');
196
+ $wpdb->query('DELETE FROM `' . self::$lss->table_fail . '`');
197
  } else {
198
  self::$db_has_transactions = false;
199
  }
207
 
208
  if (self::$db_has_transactions) {
209
  $wpdb->query('ROLLBACK');
 
210
  }
211
 
212
  self::$lss = null;