Bug 18298: Add server-side checks and refactor stuffs
authorJonathan Druart <jonathan.druart@bugs.koha-community.org>
Fri, 17 Mar 2017 02:03:20 +0000 (23:03 -0300)
committerJonathan Druart <jonathan.druart@bugs.koha-community.org>
Mon, 16 Oct 2017 12:44:32 +0000 (09:44 -0300)
Now that we have a check client-side, nothing prevents us from a smart guy to
bypass it and force an invalid password.
This patch adds two new subroutines to Koha::AuthUtils to check the
validity of passwords and generate a password server-side. It is used
only once (self-registration) but could be useful later.

Moreover the 3 different cases of password rejection (too leak, too
short, contains leading or trailing whitespaces) were not tested
everywhere. Now they are!

This patch makes things consistent everywhere and clean up some code.

Signed-off-by: Marc VĂ©ron <veron@veron.ch>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
12 files changed:
Koha/AuthUtils.pm
koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt
koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt
koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt
koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-password-recovery.tt
members/member-password.pl
members/memberentry.pl
opac/opac-memberentry.pl
opac/opac-passwd.pl
opac/opac-password-recovery.pl
t/AuthUtils.t

index a8b391d..a42705f 100644 (file)
@@ -22,6 +22,9 @@ use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
 use Encode qw( encode is_utf8 );
 use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt
 use List::MoreUtils qw/ any /;
+use String::Random qw( random_string );
+
+use C4::Context;
 
 use base 'Exporter';
 
@@ -134,6 +137,51 @@ sub generate_salt {
     return $string;
 }
 
+=head2 is_password_valid
+
+my ( $is_valid, $error ) = is_password_valid( $password );
+
+return $is_valid == 1 if the password match minPasswordLength and RequireStrongPassword conditions
+otherwise return $is_valid == 0 and $error will contain the error ('too_short' or 'too_weak')
+
+=cut
+
+sub is_password_valid {
+    my ($password) = @_;
+    my $minPasswordLength = C4::Context->preference('minPasswordLength');
+    $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3;
+    if ( length($password) < $minPasswordLength ) {
+        return ( 0, 'too_short' );
+    }
+    elsif ( C4::Context->preference('RequireStrongPassword') ) {
+        return ( 0, 'too_weak' )
+          if $password !~ m|(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{$minPasswordLength,}|;
+    }
+    return ( 0, 'has_whitespaces' ) if $password =~ m[^\s|\s$];
+    return ( 1, undef );
+}
+
+=head2 generate_password
+
+my password = generate_password();
+
+Generate a password according to the minPasswordLength and RequireStrongPassword.
+
+=cut
+
+sub generate_password {
+    my $minPasswordLength = C4::Context->preference('minPasswordLength');
+    $minPasswordLength = 8 if not $minPasswordLength or $minPasswordLength < 8;
+
+    my ( $password, $is_valid );
+    do {
+        $password = random_string('.' x $minPasswordLength );
+        ( $is_valid, undef ) = is_password_valid( $password );
+    } while not $is_valid;
+    return $password;
+}
+
+
 =head2 get_script_name
 
 This returns the correct script name, for use in redirecting back to the correct page after showing
index cf5e33d..18749ae 100644 (file)
                [% IF ( BADUSERID ) %]
         <li>You have entered a username that already exists. Please choose another one.</li>
                [% END %]
-               [% IF ( SHORTPASSWORD ) %]
-               <li><strong>The password entered is too short</strong>. Password must be at least [% minPasswordLength %] characters.</li>
-               [% END %]
+        [% IF ( ERROR_password_too_short ) %]
+            <li id="ERROR_short_password">Password must be at least [% minPasswordLength %] characters long.</li>
+        [% END %]
+        [% IF ( ERROR_password_too_weak ) %]
+            <li id="ERROR_weak_password">Password must contain at least one digit, one lowercase and one uppercase.</li>
+        [% END %]
+        [% IF ( ERROR_password_has_whitespaces ) %]
+            <li id="ERROR_weak_password">Password must not contain leading or trailing whitespaces.</li>
+        [% END %]
                [% IF ( NOPERMISSION ) %]
                <li>You do not have permission to edit this patron's login information.</li>
                [% END %]
index df84f9c..6d8ddc0 100644 (file)
@@ -196,9 +196,15 @@ $(document).ready(function() {
                        [% IF ( ERROR_dateexpiry ) %]
                                <li id="ERROR_dateexpiry">Date of expiration is invalid.</li>
                        [% END %]
-                       [% IF ( ERROR_short_password ) %]
-                               <li id="ERROR_short_password">Password must be at least [% minPasswordLength %] characters long.</li>
-                       [% END %]
+            [% IF ( ERROR_password_too_short ) %]
+                <li id="ERROR_short_password">Password must be at least [% minPasswordLength %] characters long.</li>
+            [% END %]
+            [% IF ( ERROR_password_too_weak ) %]
+                <li id="ERROR_weak_password">Password must contain at least one digit, one lowercase and one uppercase.</li>
+            [% END %]
+            [% IF ( ERROR_password_has_whitespaces ) %]
+                <li id="ERROR_weak_password">Password must not contain leading or trailing whitespaces.</li>
+            [% END %]
                        [% IF ( ERROR_password_mismatch ) %]
                                <li id="ERROR_password_mismatch">Passwords do not match.</li>
                        [% END %]
@@ -910,8 +916,11 @@ $(document).ready(function() {
                                [% END %]
                        [% END %]
                        [% END %]
-         [% IF ( mandatorypassword ) %]<span class="required">Required</span>[% END %][% IF ( ERROR_short_password ) %]<span class="required">Password is too short</span>[% END %]
-    <div class="hint">Minimum password length: [% minPasswordLength %]</div>
+            [% IF ( mandatorypassword ) %]<span class="required">Required</span>[% END %]
+            [% IF ( ERROR_password_too_short ) %]<span class="required">Password is too short</span>[% END %]
+            [% IF ( ERROR_password_too_weak ) %]<span class="required">Password is too weak</span>[% END %]
+            [% IF ( ERROR_password_has_whitespaces ) %]<span class="required">Password has leading or trailing whitespaces</span>[% END %]
+            <div class="hint">Minimum password length: [% minPasswordLength %]</div>
                </li>
                <li>
                        [% IF ( mandatorypassword ) %]
index 8730c47..9181a20 100644 (file)
                                 [% IF field == "emailpro" %]<li>Contact information: <a href="#borrower_emailpro">secondary email address</a></li>[% END %]
                                 [% IF field == "B_email" %]<li>Alternate address information: <a href="#borrower_B_email">email address</a></li>[% END %]
                                 [% IF field == "password_match" %]<li>Passwords do not match! <a href="#password">password</a></li>[% END %]
-                                [% IF field == "password_invalid" %]<li>Password does not meet minimum requirements! <a href="#password">password</a></li>[% END %]
-                                [% IF field == "password_spaces" %]<li>Password contains leading and/or trailing spaces! <a href="#password">password</a></li>[% END %]
+                                [% IF field == "password_too_short" %]
+                                    <li>Password must be at least [% minPasswordLength %] characters long.</li>
+                                [% END %]
+                                [% IF field == "password_too_weak" %]
+                                    <li>Password must contain at least one digit, one lowercase and one uppercase.</li>
+                                [% END %]
+                                [% IF field == "password_has_whitespaces" %]
+                                    <li>Password must not contain leading or trailing whitespaces.</li>
+                                [% END %]
                                 [% IF field == "duplicate_email" %]
                                     <li>This email address already exists in our database.</li>
                                 [% END %]
index 307f71d..cc04d35 100644 (file)
                         <div class="alert">
                             <h3>There was a problem with your submission</h3>
                             <p>
-                                [% IF ( PassMismatch ) %]
+                                [% IF ( passwords_mismatch ) %]
                                 Passwords do not match.  Please re-type your new password.
                                 [% END %]
-                                [% IF ( ShortPass ) %]
-                                Your new password must be at least [% minPasswordLength%] characters long.
+                                [% IF password_too_short %]
+                                    Password must be at least [% minPasswordLength %] characters long.
                                 [% END %]
+                                [% IF password_too_weak %]
+                                    Password must contain at least one digit, one lowercase and one uppercase.
+                                [% END %]
+                                [% IF password_has_whitespaces %]
+                                    Password must not contain leading or trailing whitespaces.
+                                [% END %]
+
                                 [% IF ( WrongPass ) %]
                                 Your current password was entered incorrectly.  If this problem persists, please ask a librarian to reset your password for you.
                                 [% END %]
-                                [% IF PasswordContainsTrailingSpaces %]
-                                    Your password contains leading and/or trailing spaces.
-                                [% END %]
                             </p>
                         </div>
                     [% END # /IF Error_messages %]
index 4f3f857..fd36ba4 100644 (file)
                         <br/>If you did not receive this email, you can request a new one: <a href="/cgi-bin/koha/opac-password-recovery.pl?resendEmail=true&email=[% email %]&username=[% username %]">Get new password recovery link</a>
                     [% ELSIF (errPassNotMatch) %]
                         The passwords do not match.
-                    [% ELSIF (errPassTooShort) %]
-                        Your chosen password is too short.
-                        <br/>The password must contain at least [% minPassLength %] characters.
+                    [% ELSIF password_too_short %]
+                        <li>Password must be at least [% minPasswordLength %] characters long.</li>
+                    [% ELSIF password_too_weak %]
+                        <li>Password must contain at least one digit, one lowercase and one uppercase.</li>
+                    [% ELSIF password_has_whitespaces %]
+                        <li>Password must not contain leading or trailing whitespaces.</li>
                     [% ELSIF (errLinkNotValid) %]
                         The link you clicked is either invalid, or expired.
                         <br/>Be sure you used the link from the email, or contact library staff for assistance.
@@ -95,7 +98,7 @@
                     <form action="/cgi-bin/koha/opac-password-recovery.pl" method="post" autocomplete="off">
                         <input type="hidden" name="koha_login_context" value="opac" />
                         <fieldset>
-                            <div class="alert alert-info">The password must contain at least [% minPassLength %] characters.</div>
+                            <div class="alert alert-info">The password must contain at least [% minPasswordLength %] characters.</div>
                             <label for="password">New password:</label>
                             <input type="password" id="password" size="40" name="password" />
                             <label for="repeatPassword">Confirm new password:</label>
index a3c36e5..bb82bbc 100755 (executable)
@@ -15,6 +15,7 @@ use C4::Members;
 use C4::Circulation;
 use CGI qw ( -utf8 );
 use C4::Members::Attributes qw(GetBorrowerAttributes);
+use Koha::AuthUtils;
 use Koha::Token;
 
 use Koha::Patrons;
@@ -66,11 +67,16 @@ if ( ( $member ne $loggedinuser ) && ( $category_type eq 'S' ) ) {
 
 push( @errors, 'NOMATCH' ) if ( ( $newpassword && $newpassword2 ) && ( $newpassword ne $newpassword2 ) );
 
-my $minpw = C4::Context->preference('minPasswordLength');
-$minpw = 3 if not $minpw or $minpw < 3;
-push( @errors, 'SHORTPASSWORD' ) if ( $newpassword && $minpw && ( length($newpassword) < $minpw ) );
+if ( $newpassword and not @errors ) {
+    my ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( $newpassword );
+    unless ( $is_valid ) {
+        push @errors, 'ERROR_password_too_short' if $error eq 'too_short';
+        push @errors, 'ERROR_password_too_weak' if $error eq 'too_weak';
+        push @errors, 'ERROR_password_has_whitespaces' if $error eq 'has_whitespaces';
+    }
+}
 
-if ( $newpassword && !scalar(@errors) ) {
+if ( $newpassword and not @errors) {
 
     die "Wrong CSRF token"
         unless Koha::Token->new->check_csrf({
index 8661a59..ed35e1b 100755 (executable)
@@ -37,6 +37,7 @@ use C4::Koha;
 use C4::Log;
 use C4::Letters;
 use C4::Form::MessagingPreferences;
+use Koha::AuthUtils;
 use Koha::AuthorisedValues;
 use Koha::Patron::Debarments;
 use Koha::Cities;
@@ -353,13 +354,19 @@ if ($op eq 'save' || $op eq 'insert'){
   unless (Check_Userid($userid,$borrowernumber)) {
     push @errors, "ERROR_login_exist";
   }
-  
+
   my $password = $input->param('password');
   my $password2 = $input->param('password2');
   push @errors, "ERROR_password_mismatch" if ( $password ne $password2 );
-  my $minpw = C4::Context->preference('minPasswordLength');
-  $minpw = 3 if not $minpw or $minpw < 3;
-  push @errors, "ERROR_short_password" if( $password && $minpw && $password ne '****' && (length($password) < $minpw) );
+
+  if ( $password and $password ne '****' ) {
+      my ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( $password );
+      unless ( $is_valid ) {
+          push @errors, 'ERROR_password_too_short' if $error eq 'too_short';
+          push @errors, 'ERROR_password_too_weak' if $error eq 'too_weak';
+          push @errors, 'ERROR_password_has_whitespaces' if $error eq 'has_whitespaces';
+      }
+  }
 
   # Validate emails
   my $emailprimary = $input->param('email');
index f7d08b6..33988c1 100755 (executable)
@@ -28,6 +28,10 @@ use C4::Output;
 use C4::Members;
 use C4::Members::Attributes qw( GetBorrowerAttributes );
 use C4::Form::MessagingPreferences;
+use Koha::AuthUtils;
+use Koha::Patrons;
+use Koha::Patron::Modification;
+use Koha::Patron::Modifications;
 use C4::Scrubber;
 use Email::Valid;
 use Koha::DateUtils;
@@ -165,7 +169,7 @@ if ( $action eq 'create' ) {
                 $verification_token = md5_hex( time().{}.rand().{}.$$ );
             }
 
-            $borrower{password}           = random_string("..........");
+            $borrower{password}           = Koha::AuthUtils::generate_password;
             $borrower{verification_token} = $verification_token;
 
             Koha::Patron::Modification->new( \%borrower )->store();
@@ -386,8 +390,6 @@ sub CheckMandatoryFields {
 }
 
 sub CheckForInvalidFields {
-    my $minpw = C4::Context->preference('minPasswordLength');
-    $minpw = 3 if not $minpw or $minpw < 3;
     my $borrower = shift;
     my @invalidFields;
     if ($borrower->{'email'}) {
@@ -421,11 +423,13 @@ sub CheckForInvalidFields {
     {
         push( @invalidFields, "password_match" );
     }
-    if ( $borrower->{'password'}  && $minpw && (length($borrower->{'password'}) < $minpw) ) {
-       push(@invalidFields, "password_invalid");
-    }
     if ( $borrower->{'password'} ) {
-       push(@invalidFields, "password_spaces") if ($borrower->{'password'} =~ /^\s/ or $borrower->{'password'} =~ /\s$/);
+        my ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( $borrower->{password} );
+          unless ( $is_valid ) {
+              push @invalidFields, 'password_too_short' if $error eq 'too_short';
+              push @invalidFields, 'password_too_weak' if $error eq 'too_weak';
+              push @invalidFields, 'password_has_whitespaces' if $error eq 'has_whitespaces';
+          }
     }
 
     return \@invalidFields;
index bef1f99..b5ae895 100755 (executable)
@@ -46,47 +46,46 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
 );
 
 my $patron = Koha::Patrons->find( $borrowernumber );
-my $minpasslen = C4::Context->preference("minPasswordLength");
-$minpasslen = 3 if not $minpasslen or $minpasslen < 3;
 if ( C4::Context->preference("OpacPasswordChange") ) {
     my $sth =  $dbh->prepare("UPDATE borrowers SET password = ? WHERE borrowernumber=?");
     if (   $query->param('Oldkey')
         && $query->param('Newkey')
         && $query->param('Confirm') )
     {
+        my $error;
+        my $new_password = $query->param('Newkey');
+        my $confirm_password = $query->param('Confirm');
         if ( goodkey( $dbh, $borrowernumber, $query->param('Oldkey') ) ) {
-            if ( $query->param('Newkey') =~ m|^\s+| or $query->param('Newkey') =~ m|\s+$| ) {
-                $template->param(
-                    Error_messages => 1,
-                    PasswordContainsTrailingSpaces => 1,
-                );
-            }
-            elsif ( $query->param('Newkey') eq $query->param('Confirm')
-                && length( $query->param('Confirm') ) >= $minpasslen )
-            {    # Record password
-                my $clave = hash_password( $query->param('Newkey') );
-                $sth->execute( $clave, $borrowernumber );
-                $template->param( 'password_updated' => '1' );
-                $template->param( 'borrowernumber'   => $borrowernumber );
-            }
-            elsif ( $query->param('Newkey') ne $query->param('Confirm') ) {
-                $template->param( 'Ask_data'       => '1' );
-                $template->param( 'Error_messages' => '1' );
-                $template->param( 'PassMismatch'   => '1' );
-            }
-            elsif ( length( $query->param('Confirm') ) < $minpasslen ) {
+
+            if ( $new_password ne $confirm_password ) {
                 $template->param( 'Ask_data'       => '1' );
                 $template->param( 'Error_messages' => '1' );
-                $template->param( 'ShortPass'      => '1' );
-            }
-            else {
-                $template->param( 'Error_messages' => '1' );
+                $template->param( 'passwords_mismatch'   => '1' );
+            } else {
+                my ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( $new_password );
+                unless ( $is_valid ) {
+                    $error = 'password_too_short' if $error eq 'too_short';
+                    $error = 'password_too_weak' if $error eq 'too_weak';
+                    $error = 'password_has_whitespaces' if $error eq 'has_whitespaces';
+                } else {
+                    # Password is valid and match
+                    my $clave = hash_password( $new_password );
+                    $sth->execute( $clave, $borrowernumber );
+                    $template->param( 'password_updated' => '1' );
+                    $template->param( 'borrowernumber'   => $borrowernumber );
+                }
             }
         }
         else {
-            $template->param( 'Ask_data'       => '1' );
-            $template->param( 'Error_messages' => '1' );
-            $template->param( 'WrongPass'      => '1' );
+            $error = 'WrongPass';
+        }
+        if ($error) {
+            $template->param(
+                Ask_data       => 1,
+                Error_messages => 1,
+                $error         => 1,
+            );
+
         }
     }
     else {
index 1a4abf7..d08d101 100755 (executable)
@@ -46,8 +46,6 @@ my $errBadEmail;
 
 #new password form error
 my $errLinkNotValid;
-my $errPassNotMatch;
-my $errPassTooShort;
 
 if ( $query->param('sendEmail') || $query->param('resendEmail') ) {
 
@@ -144,38 +142,33 @@ if ( $query->param('sendEmail') || $query->param('resendEmail') ) {
 elsif ( $query->param('passwordReset') ) {
     ( $borrower_number, $username ) = GetValidLinkInfo($uniqueKey);
 
-    my $minPassLength = C4::Context->preference('minPasswordLength');
-    $minPassLength = 3 if not $minPassLength or $minPassLength < 3;
-    #validate password length & match
-    if (   ($borrower_number)
-        && ( $password eq $repeatPassword )
-        && ( length($password) >= $minPassLength ) )
-    {    #apply changes
-        Koha::Patrons->find($borrower_number)->update_password( $username, hash_password($password) );
-        CompletePasswordRecovery($uniqueKey);
-        $template->param(
-            password_reset_done => 1,
-            username            => $username
-        );
-    }
-    else {    #errors
-        if ( !$borrower_number ) {    #parameters not valid
-            $errLinkNotValid = 1;
-        }
-        elsif ( $password ne $repeatPassword ) {    #passwords does not match
-            $errPassNotMatch = 1;
-        }
-        elsif ( length($password) < $minPassLength ) {    #password too short
-            $errPassTooShort = 1;
+    my $error;
+    if ( not $borrower_number ) {
+        $error = 'errLinkNotValid';
+    } elsif ( $password ne $repeatPassword ) {
+        $error = 'errPassNotMatch';
+    } else {
+        my ( $is_valid, $err) = Koha::AuthUtils::is_password_valid( $password );
+        unless ( $is_valid ) {
+            $error = 'password_too_short' if $err eq 'too_short';
+            $error = 'password_too_weak' if $err eq 'too_weak';
+            $error = 'password_has_whitespaces' if $err eq 'has_whitespaces';
+        } else {
+            Koha::Patrons->find($borrower_number)->update_password( $username, hash_password($password) );
+            CompletePasswordRecovery($uniqueKey);
+            $template->param(
+                password_reset_done => 1,
+                username            => $username
+            );
         }
+    }
+    if ( $error ) {
         $template->param(
-            new_password    => 1,
-            email           => $email,
-            uniqueKey       => $uniqueKey,
-            errLinkNotValid => $errLinkNotValid,
-            errPassNotMatch => $errPassNotMatch,
-            errPassTooShort => $errPassTooShort,
-            hasError        => 1
+            new_password => 1,
+            email        => $email,
+            uniqueKey    => $uniqueKey,
+            hasError     => 1,
+            $error       => 1,
         );
     }
 }
index f0fa81b..aa73e16 100644 (file)
@@ -1,6 +1,7 @@
 # This file is part of Koha.
 #
 # Copyright (C) 2013 Equinox Software, Inc.
+# Copyright 2017 Koha Development Team
 #
 # Koha is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
 # along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
-use Test::More tests => 1;
+use Test::More tests => 3;
 
+use t::lib::Mocks;
 use Koha::AuthUtils qw/hash_password/;
 
 my $hash1 = hash_password('password');
 my $hash2 = hash_password('password');
 
 ok($hash1 ne $hash2, 'random salts used when generating password hash');
+
+subtest 'is_password_valid' => sub {
+    plan tests => 12;
+
+    my ( $is_valid, $error );
+
+    t::lib::Mocks::mock_preference('RequireStrongPassword', 0);
+    t::lib::Mocks::mock_preference('minPasswordLength', 0);
+    ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '12' );
+    is( $is_valid, 0, 'min password size should be 3' );
+    is( $error, 'too_short', 'min password size should be 3' );
+    ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( ' 123' );
+    is( $is_valid, 0, 'password should not contain leading spaces' );
+    is( $error, 'has_whitespaces', 'password should not contain leading spaces' );
+    ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '123 ' );
+    is( $is_valid, 0, 'password should not contain trailing spaces' );
+    is( $error, 'has_whitespaces', 'password should not contain trailing spaces' );
+    ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '123' );
+    is( $is_valid, 1, 'min password size should be 3' );
+
+    t::lib::Mocks::mock_preference('RequireStrongPassword', 1);
+    t::lib::Mocks::mock_preference('minPasswordLength', 8);
+    ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '12345678' );
+    is( $is_valid, 0, 'password should be strong' );
+    is( $error, 'too_weak', 'password should be strong' );
+    ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'abcd1234' );
+    is( $is_valid, 0, 'strong password should contain uppercase' );
+    is( $error, 'too_weak', 'strong password should contain uppercase' );
+
+    ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'abcD1234' );
+    is( $is_valid, 1, 'strong password should contain uppercase' );
+};
+
+subtest 'generate_password' => sub {
+    plan tests => 1;
+    t::lib::Mocks::mock_preference('RequireStrongPassword', 1);
+    t::lib::Mocks::mock_preference('minPasswordLength', 8);
+    my $all_valid = 1;
+    for ( 1 .. 10 ) {
+        my $password = Koha::AuthUtils::generate_password;
+        my ( $is_valid, undef ) = Koha::AuthUtils::is_password_valid( $password );
+        $all_valid = 0 unless $is_valid;
+    }
+    is ( $all_valid, 1, 'generate_password should generate valid passwords' );
+};