Bugzilla – Attachment 109388 Details for
Bug 12617
Koha should let admins to configure automatically generated password complexity/difficulty
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 12617: Koha should let admins to configure automatically generated password complexity/difficulty
0001-Bug-12617-Koha-should-let-admins-to-configure-automa.patch (text/plain), 59.06 KB, created by
Emmi Takkinen
on 2020-09-01 07:41:29 UTC
(
hide
)
Description:
Bug 12617: Koha should let admins to configure automatically generated password complexity/difficulty
Filename:
MIME Type:
Creator:
Emmi Takkinen
Created:
2020-09-01 07:41:29 UTC
Size:
59.06 KB
patch
obsolete
>From f938fb0212df5405c6cce8cc25feaf0ea02d0185 Mon Sep 17 00:00:00 2001 >From: =?UTF-8?q?Juhani=20Sepp=C3=A4l=C3=A4?= <jseppal@student.uef.fi> >Date: Mon, 11 Aug 2014 15:58:05 +0300 >Subject: [PATCH 1/2] Bug 12617: Koha should let admins to configure > automatically generated password complexity/difficulty > >Adds simple password policy(with regards to complexity) management into categories: >- Per category password policy: admins can configure what kind of passwords get generated >in member-passwords. User-created passwords are also checked against the policy if it is >defined and complexity is enforced for every user based on their set category. >- Predefined policies: > - simplenumeric: the digits 0-9 allowed only > - alphanumeric: passwords must contain only the digits 0-9 and lowercase and uppercase characters. > Special characters are not allowed. > - complex: patrons are required to use complex passwords containing numbers, uppercase and lowercase > characters and special characters. >Old passwords for excisting patrons are not affected. > >To test: >1. Apply this patch and update database. >2. Navigate to categories.pl and note there is new column 'Password policies' has been added. >3. Edit some categories and set password policy for them. >4. Set some values to sysprefs 'minPasswordLength', 'minAlnumPasswordLength' and >'minComplexPasswordLength'. > >Staff interface: >1. Create new patron. >2. Set their password against their categorys policy and save. >3. Error message is displayed (with content depending on password policy). >4. Set acceptable password and save succesfully. >5. Repeat steps 2-3-4 on patron edit page. >6. Repeat steps 2-3-4 on 'Change password' page. > >OPAC: >1. Enable 'OpacPasswordChange' and 'OpacResetPassword'. >2. On OPAC, repeat what you did on staff interface (on create, edit and 'Change your password'. >3. Confirm errors are displayed correctly and saving works. >4. Log out and go to 'Forgotten password recovery' page. >5. Send and receive email for password recovery. >6. Set unacceptable password and save, confirm correct error is displayed. >7. Set acceptable password and save succesfully. > >REST API: >1. With your preferred REST client (curl e.g) sent POST request to /api/v1/patrons/{patron_id}/password >with 'password' and 'password_2' parameters. >2. Confirm correct error message is displayed when sending password against password policy. >3. Confirm password is changed when acceptable password is send. > >Also prove t/AuthUtils.t and t/db_dependent/api/v1/patrons_password.t > >Sponsored-by: Koha-Suomi Oy >--- > Koha/AuthUtils.pm | 48 ++++++++-- > Koha/Exceptions/Password.pm | 14 ++- > Koha/Patron.pm | 16 +++- > Koha/Schema/Result/Category.pm | 7 ++ > admin/categories.pl | 3 + > ...gure-automatically-generated-password.perl | 8 ++ > installer/data/mysql/kohastructure.sql | 1 + > installer/data/mysql/sysprefs.sql | 2 + > .../prog/en/modules/admin/categories.tt | 15 +++ > .../en/modules/admin/preferences/patrons.pref | 12 ++- > .../en/modules/members/member-password.tt | 29 +++++- > .../prog/en/modules/members/memberentrygen.tt | 13 ++- > .../bootstrap/en/modules/opac-memberentry.tt | 37 +++++++- > .../bootstrap/en/modules/opac-passwd.tt | 16 +++- > .../en/modules/opac-password-recovery.tt | 12 +++ > members/member-password.pl | 15 ++- > members/memberentry.pl | 9 ++ > opac/opac-memberentry.pl | 14 +++ > opac/opac-passwd.pl | 13 +++ > opac/opac-password-recovery.pl | 22 ++++- > t/db_dependent/AuthUtils.t | 95 +++++++++++++++++-- > t/db_dependent/api/v1/patrons_password.t | 81 +++++++++++++++- > 22 files changed, 452 insertions(+), 30 deletions(-) > create mode 100644 installer/data/mysql/atomicupdate/Bug-12617-Koha-should-let-admins-to-configure-automatically-generated-password.perl > >diff --git a/Koha/AuthUtils.pm b/Koha/AuthUtils.pm >index 3bf28ef73a..f1c0666883 100644 >--- a/Koha/AuthUtils.pm >+++ b/Koha/AuthUtils.pm >@@ -22,10 +22,11 @@ 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 String::Random qw( random_string random_regex ); > use Koha::Exceptions::Password; > > use C4::Context; >+use Koha::Patron::Categories; > > use base 'Exporter'; > >@@ -154,15 +155,42 @@ sub is_password_valid { > } > my $minPasswordLength = $category->effective_min_password_length; > $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3; >- if ( length($password) < $minPasswordLength ) { >+ my $passwordpolicy = $category->passwordpolicy; >+ >+ if ($passwordpolicy) { >+ if ($passwordpolicy eq "complex") { >+ unless ($password =~ /[0-9]/ >+ && $password =~ /[a-zåäö]/ >+ && $password =~ /[A-ZÃÃÃ]/ >+ && $password =~ /[\|\[\]\{\}!@#\$%\^&\*\(\)_\-\+\?]/ >+ && length($password) >= $minPasswordLength ) { >+ return (0, "complex_policy_mismatch"); >+ } >+ } >+ elsif ($passwordpolicy eq "alphanumeric") { >+ unless ($password =~ /[0-9]/ >+ && $password =~ /[a-zA-ZöäåÃÃÃ]/ >+ && $password !~ /\W/ >+ && $password !~ /[_-]/ >+ && length($password) >= $minPasswordLength ) { >+ return (0, "alpha_policy_mismatch"); >+ } >+ } >+ else { >+ if ($password !~ /^[0-9]+$/ || length($password) < $minPasswordLength) { >+ return (0, "simple_policy_mismatch"); >+ } >+ } >+ } >+ elsif ( length($password) < $minPasswordLength ) { > return ( 0, 'too_short' ); > } > elsif ( $category->effective_require_strong_password ) { > return ( 0, 'too_weak' ) >- if $password !~ m|(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{$minPasswordLength,}|; >+ if $password !~ m|(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{$minPasswordLength,}|; > } > return ( 0, 'has_whitespaces' ) if $password =~ m[^\s|\s$]; >- return ( 1, undef ); >+ return 1; > } > > =head2 generate_password >@@ -180,16 +208,24 @@ sub generate_password { > } > my $minPasswordLength = $category->effective_min_password_length; > $minPasswordLength = 8 if not $minPasswordLength or $minPasswordLength < 8; >+ my $passwordpolicy = $category->passwordpolicy; > > my ( $password, $is_valid ); > do { >- $password = random_string('.' x $minPasswordLength ); >+ if (!$passwordpolicy || $passwordpolicy eq "complex") { >+ $password = random_string('.' x $minPasswordLength); >+ } else { >+ if ($passwordpolicy eq "alphanumeric") { >+ $password = random_regex('[a-zA-Z0-9]' x $minPasswordLength); >+ } else { >+ $password = random_regex('[0-9]' x $minPasswordLength); >+ } >+ } > ( $is_valid, undef ) = is_password_valid( $password, $category ); > } 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 >diff --git a/Koha/Exceptions/Password.pm b/Koha/Exceptions/Password.pm >index 549e3b9519..693b033d05 100644 >--- a/Koha/Exceptions/Password.pm >+++ b/Koha/Exceptions/Password.pm >@@ -46,7 +46,19 @@ use Exception::Class ( > 'Koha::Exceptions::Password::NoCategoryProvided' => { > isa => 'Koha::Exceptions::Password', > description => 'You must provide a patron\'s category to validate password\'s strength and length' >- } >+ }, >+ 'Koha::Exceptions::Password::SimplePolicy' => { >+ isa => 'Koha::Exceptions::Password', >+ description => 'Password does not match simplenumeric passwordpolicy', >+ }, >+ 'Koha::Exceptions::Password::AlphaPolicy' => { >+ isa => 'Koha::Exceptions::Password', >+ description => 'Password does not match alphanumeric passwordpolicy', >+ }, >+ 'Koha::Exceptions::Password::ComplexPolicy' => { >+ isa => 'Koha::Exceptions::Password', >+ description => 'Password does not match complex passwordpolicy', >+ }, > ); > > sub full_message { >diff --git a/Koha/Patron.pm b/Koha/Patron.pm >index 12b8bb24da..a255f77086 100644 >--- a/Koha/Patron.pm >+++ b/Koha/Patron.pm >@@ -734,6 +734,12 @@ Exceptions are thrown if the password is not good enough. > > =item Koha::Exceptions::Password::Plugin (if a "check password" plugin is enabled) > >+=item Koha::Exceptions::Password::SimplePolicy >+ >+=item Koha::Exceptions::Password::AlphaPolicy >+ >+=item Koha::Exceptions::Password::ComplexPolicy >+ > =back > > =cut >@@ -761,6 +767,15 @@ sub set_password { > elsif ( $error eq 'too_weak' ) { > Koha::Exceptions::Password::TooWeak->throw(); > } >+ elsif ( $error eq 'simple_policy_mismatch' ) { >+ Koha::Exceptions::Password::SimplePolicy->throw(); >+ } >+ elsif ( $error eq 'alpha_policy_mismatch' ) { >+ Koha::Exceptions::Password::AlphaPolicy->throw(); >+ } >+ elsif ( $error eq 'complex_policy_mismatch' ) { >+ Koha::Exceptions::Password::ComplexPolicy->throw(); >+ } > } > } > >@@ -801,7 +816,6 @@ sub set_password { > return $self; > } > >- > =head3 renew_account > > my $new_expiry_date = $patron->renew_account >diff --git a/Koha/Schema/Result/Category.pm b/Koha/Schema/Result/Category.pm >index 27cc1e2e5e..f32a41cfb4 100644 >--- a/Koha/Schema/Result/Category.pm >+++ b/Koha/Schema/Result/Category.pm >@@ -142,6 +142,11 @@ __PACKAGE__->table("categories"); > > data_type: 'tinyint' > is_nullable: 1 >+=head2 passwordpolicy >+ >+ data_type: 'varchar' >+ is_nullable: 1 >+ size: 40 > > =cut > >@@ -203,6 +208,8 @@ __PACKAGE__->add_columns( > { data_type => "smallint", is_nullable => 1 }, > "require_strong_password", > { data_type => "tinyint", is_nullable => 1 }, >+ "passwordpolicy", >+ { data_type => "varchar", is_nullable => 1, size => 40 }, > ); > > =head1 PRIMARY KEY >diff --git a/admin/categories.pl b/admin/categories.pl >index d0b2d058f0..5851289d53 100755 >--- a/admin/categories.pl >+++ b/admin/categories.pl >@@ -96,6 +96,7 @@ elsif ( $op eq 'add_validate' ) { > my $change_password = $input->param('change_password'); > my $min_password_length = $input->param('min_password_length'); > my $require_strong_password = $input->param('require_strong_password'); >+ my $selectedpasswordpolicy = $input->param('passwordpolicy'); > my @branches = grep { $_ ne q{} } $input->multi_param('branches'); > > $reset_password = undef if $reset_password eq -1; >@@ -135,6 +136,7 @@ elsif ( $op eq 'add_validate' ) { > $category->change_password($change_password); > $category->min_password_length($min_password_length); > $category->require_strong_password($require_strong_password); >+ $category->passwordpolicy($selectedpasswordpolicy); > eval { > $category->store; > $category->replace_branch_limitations( \@branches ); >@@ -165,6 +167,7 @@ elsif ( $op eq 'add_validate' ) { > change_password => $change_password, > min_password_length => $min_password_length, > require_strong_password => $require_strong_password, >+ passwordpolicy => $selectedpasswordpolicy, > }); > eval { > $category->store; >diff --git a/installer/data/mysql/atomicupdate/Bug-12617-Koha-should-let-admins-to-configure-automatically-generated-password.perl b/installer/data/mysql/atomicupdate/Bug-12617-Koha-should-let-admins-to-configure-automatically-generated-password.perl >new file mode 100644 >index 0000000000..0c03f3b310 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/Bug-12617-Koha-should-let-admins-to-configure-automatically-generated-password.perl >@@ -0,0 +1,8 @@ >+$DBversion = 'XXX'; # will be replaced by the RM >+if( CheckVersion( $DBversion ) ) { >+ $dbh->do("ALTER TABLE categories ADD COLUMN passwordpolicy VARCHAR(40) DEFAULT NULL AFTER require_strong_password"); >+ >+ # Always end with this (adjust the bug info) >+ SetVersion( $DBversion ); >+ print "Upgrade to $DBversion done (Bug 12617 - Koha should let admins to configure automatically generated password complexity/difficulty)\n"; >+} >\ No newline at end of file >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index b3062bdfff..190468122c 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -329,6 +329,7 @@ CREATE TABLE `categories` ( -- this table shows information related to Koha patr > `change_password` TINYINT(1) NULL DEFAULT NULL, -- if patrons of this category can change their passwords in the OAPC > `min_password_length` smallint(6) NULL DEFAULT NULL, -- set minimum password length for patrons in this category > `require_strong_password` TINYINT(1) NULL DEFAULT NULL, -- set required password strength for patrons in this category >+ `passwordpolicy` varchar(40) default NULL, -- which password policy patron category uses > PRIMARY KEY (`categorycode`), > UNIQUE KEY `categorycode` (`categorycode`) > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; >diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql >index cfb5ba6837..fbb969a53f 100644 >--- a/installer/data/mysql/sysprefs.sql >+++ b/installer/data/mysql/sysprefs.sql >@@ -329,6 +329,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('MaxTotalSuggestions','',NULL,'Number of total suggestions used for time limit with NumberOfSuggestionDays','Free'), > ('MembershipExpiryDaysNotice','',NULL,'Send an account expiration notice that a patron\'s card is about to expire after','Integer'), > ('MergeReportFields','',NULL,'Displayed fields for deleted MARC records after merge','Free'), >+('minAlnumPasswordLength', '10', null, 'Specify the minimum length for alphanumeric passwords', 'free') >+('minComplexPasswordLength', '10', null, 'Specify the minimum length for complex passwords', 'free') > ('minPasswordLength','8',NULL,'Specify the minimum length of a patron/staff password','free'), > ('NewItemsDefaultLocation','','','If set, all new items will have a location of the given Location Code ( Authorized Value type LOC )',''), > ('NewsAuthorDisplay','none','none|opac|staff|both','Display the author name for news items.','Choice'), >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt >index 5b3cf34b20..7ef075c4f6 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt >@@ -266,6 +266,19 @@ > [% END %] > </select> > </li> >+ <li> >+ <label for="password-policy">Category password policy:</label> >+ <select name="passwordpolicy" id="password-policy"> >+ [% UNLESS category %]<option value="" selected="selected"></option>[% ELSE %]<option value=""></option>[% END %] >+ [% IF category and category.passwordpolicy == 'complex' %]<option value="complex" selected="selected">Complex</option>[% ELSE %]<option value="complex">Complex</option>[% END %] >+ [% IF category and category.passwordpolicy == 'alphanumeric' %]<option value="alphanumeric" selected="selected">Alphanumeric</option>[% ELSE %]<option value="alphanumeric">Alphanumeric</option>[% END %] >+ [% IF category and category.passwordpolicy == 'simplenumeric' %]<option value="simplenumeric" selected="selected">Numbers only</option>[% ELSE %]<option value="simplenumeric">Numbers only</option>[% END %] >+ </select> >+ <span> >+ Selecting a password policy for a category affects both automatically created suggested passwords and enfo$ >+ of rules. >+ </span> >+ </li> > <li><label for="block_expired">Block expired patrons:</label> > <select name="BlockExpiredPatronOpacActions" id="block_expired"> > [% IF not category or category.BlockExpiredPatronOpacActions == -1%] >@@ -456,6 +469,7 @@ > <th scope="col">Messaging</th> > [% END %] > <th scope="col">Library limitations</th> >+ <th scope="col">Password policy</th> > [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %] > <th scope="col">Check previous checkout?</th> > [% END %] >@@ -551,6 +565,7 @@ > No limitation > [% END %] > </td> >+ <td>[% category.passwordpolicy %]</td> > [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %] > <td> > [% SWITCH category.checkprevcheckout %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref >index eb49161489..bbbed3a9e2 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref >@@ -313,10 +313,20 @@ Patrons: > - "days.<br>IMPORTANT: No action is performed when these delays are empty (no text). But a zero value ('0') is interpreted as no delay (do it now)! The actions are performed by the cleanup database cron job." > Security: > - >- - Login passwords for staff and patrons must be at least >+ - Login passwords for simplenumeric policy must be at least > - pref: minPasswordLength > class: integer > - characters long. >+ - >+ - Login passwords for alphanumeric policy must be at least >+ - pref: minAlnumPasswordLength >+ class: integer >+ - characters long. >+ - >+ - Login passwords for complex policy must be at least >+ - pref: minComplexPasswordLength >+ class: integer >+ - characters long. > - > - pref: RequireStrongPassword > choices: >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt >index a0a1b2ea79..970f213243 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt >@@ -51,9 +51,18 @@ > [% IF ( NOPERMISSION ) %] > <li>You do not have permission to edit this patron's login information.</li> > [% END %] >- [% IF ( NOMATCH ) %] >- <li><strong>The passwords entered do not match</strong>. Please re-enter the new password.</li> >+ [% IF ( ERROR_password_mismatch )%] >+ <li id="ERROR_password_mismatch"><strong>The passwords entered do not match</strong>. Please re-enter the new password.</li> > [% END %] >+ [% IF ( ERROR_complex_policy_mismatch ) %] >+ <li id="ERROR_policy_mismatch"><strong>Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</strong> Please re-enter the new password.</li> >+ [% END %] >+ [% IF ( ERROR_alpha_policy_mismatch ) %] >+ <li id="ERROR_policy_mismatch"><strong>Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</strong> Please re-enter the new password.</li> >+ [% END %] >+ [% IF ( ERROR_simple_policy_mismatch ) %] >+ <li id="ERROR_policy_mismatch"><strong>Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</strong> Please re-enter the new password.</li> >+ [% END %] > </ul> > </div> > [% END %] >@@ -102,9 +111,14 @@ > [% INCLUDE 'str/members-menu.inc' %] > [% Asset.js("js/members-menu.js") | $raw %] > <script> >- function generate_password() { >- // Always generate a strong password >+ function generate_password(password_policy) { >+ // Follow password policy when generating password > var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; >+ if ( password_policy == 'complex' ){ >+ chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ|[]{}!@#$%^&*()_-+?'; >+ } else if ( password_policy == 'simplenumeric'){ >+ chars = '0123456789'; >+ } > var length = [% patron.category.effective_min_password_length | html %]; > if ( length < 8 ) length = 8; > var password=''; >@@ -117,9 +131,14 @@ > $("body").on('click', "#fillrandom",function(e) { > e.preventDefault(); > var password = ''; >+ var password_policy = '[% password_policy | html %]'; >+ >+ // Change password pattern to match password policy > var pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{[% patron.category.effective_min_password_length | html %],}/; >+ if (password_policy == 'simplenumeric') pattern_regex = /(?=.*\d).{[% patron.category.effective_min_password_length | html %],}/; >+ if (password_policy == 'complex') pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[|[]{}!@#$%^&*()_-+?]).{[% patron.category.effective_min_password_length | html %],}/; > while ( ! pattern_regex.test( password ) ) { >- password = generate_password(); >+ password = generate_password(password_policy); > } > $("#newpassword").val(password); > $("#newpassword").attr('type', 'text'); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt >index fc75acf954..10fbb58a5b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt >@@ -172,6 +172,15 @@ legend:hover { > [% IF ERROR_bad_email_alternative %] > <li id="ERROR_bad_email_alternative">The alternative email is invalid.</li> > [% END %] >+ [% IF ( ERROR_complex_policy_mismatch ) %] >+ <li id="ERROR_policy_mismatch">Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% END %] >+ [% IF ( ERROR_alpha_policy_mismatch ) %] >+ <li id="ERROR_policy_mismatch">Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% END %] >+ [% IF ( ERROR_simple_policy_mismatch ) %] >+ <li id="ERROR_policy_mismatch">Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% END %] > </ul> > </div> > [% END %] >@@ -830,9 +839,9 @@ legend:hover { > [% IF ( typeloo.typename_X ) %]<optgroup label="Statistical">[% END %] > [% END %] > [% IF ( categoryloo.categorycodeselected ) %] >- <option value="[% categoryloo.categorycode | html %]" selected="selected" data-pwd-length="[% categoryloo.effective_min_password_length | html %]" data-pwd-strong="[% categoryloo.effective_require_strong_password | html %]" data-typename="[% typeloo.typename | html %]">[% categoryloo.categoryname | html %]</option> >+ <option value="[% categoryloo.categorycode | html %]" selected="selected" data-pwd-length="[% categoryloo.effective_min_password_length | html %]" data-pwd-strong="[% categoryloo.effective_require_strong_password | html %]" data-pwd-policy="[% categoryloo.passwordpolicy | html %]" data-typename="[% typeloo.typename | html %]">[% categoryloo.categoryname | html %]</option> > [% ELSE %] >- <option value="[% categoryloo.categorycode | html %]" data-pwd-length="[% categoryloo.effective_min_password_length | html %]" data-pwd-strong="[% categoryloo.effective_require_strong_password | html %]" data-typename="[% typeloo.typename | html %]">[% categoryloo.categoryname | html %]</option> >+ <option value="[% categoryloo.categorycode | html %]" data-pwd-length="[% categoryloo.effective_min_password_length | html %]" data-pwd-strong="[% categoryloo.effective_require_strong_password | html %]" data-pwd-policy="[% categoryloo.passwordpolicy | html %]" data-typename="[% typeloo.typename | html %]">[% categoryloo.categoryname | html %]</option> > [% END %] > [% IF ( loop.last ) %] > </optgroup> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt >index 34453ed139..06ab0b1544 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt >@@ -90,6 +90,15 @@ > [% IF field == "password_has_whitespaces" %] > <li>Password must not contain leading or trailing whitespaces.</li> > [% END %] >+ [% IF field == "complex_policy_mismatch" %] >+ <li>Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% END %] >+ [% IF field == "alpha_policy_mismatch" %] >+ <li>Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% END %] >+ [% IF field == "simple_policy_mismatch" %] >+ <li>Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% END %] > [% IF field == "duplicate_email" %] > <li>This email address already exists in our database.</li> > [% END %] >@@ -252,9 +261,9 @@ > <select id="borrower_categorycode" name="borrower_categorycode"> > [% FOREACH c IN Categories.all() %] > [% IF c.categorycode == Koha.Preference('PatronSelfRegistrationDefaultCategory') %] >- <option value="[% c.categorycode | html %]" data-pwd-length="[% c.effective_min_password_length | html %]" data-pwd-strong="[% c.effective_require_strong_password | html %]" selected="selected">[% c.description | html %]</option> >+ <option value="[% c.categorycode | html %]" data-pwd-length="[% c.effective_min_password_length | html %]" data-pwd-strong="[% c.effective_require_strong_password | html %]" data-pwd-policy="[% c.passwordpolicy | html %]" selected="selected">[% c.description | html %]</option> > [% ELSE %] >- <option value="[% c.categorycode | html %]" data-pwd-length="[% c.effective_min_password_length | html %]" data-pwd-strong="[% c.effective_require_strong_password | html %]">[% c.description | html %]</option> >+ <option value="[% c.categorycode | html %]" data-pwd-length="[% c.effective_min_password_length | html %]" data-pwd-strong="[% c.effective_require_strong_password | html %]" data-pwd-policy="[% c.passwordpolicy | html %]">[% c.description | html %]</option> > [% END %] > [% END %] > </select> >@@ -869,6 +878,12 @@ > [% IF patron %] > [% IF ( patron.category.effective_require_strong_password ) %] > <p>Your password must contain at least [% patron.category.effective_min_password_length | html %] characters, including UPPERCASE, lowercase and numbers.</p> >+ [% ELSIF ( passwordpolicy == 'complex') %] >+ <p>Your password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</p> >+ [% ELSIF ( passwordpolicy == 'alphanumeric') %] >+ <p>Your password must contain both numbers and non-special characters and must be at least [% patron.category.effective_min_password_length | html %] characters long.</p> >+ [% ELSIF ( passwordpolicy == 'simplenumeric') %] >+ <p>Your password can only contain digits 0-9 and must be at least [% patron.category.effective_min_password_length | html %] characters long.</p> > [% ELSE %] > <p>Your password must be at least [% patron.category.effective_min_password_length | html %] characters long.</p> > [% END %] >@@ -1185,11 +1200,25 @@ > [% UNLESS patron %] > var PWD_STRONG_MSG = _("Password must contain at least %s characters, including UPPERCASE, lowercase and numbers"); > var PWD_WEAK_MSG = _("Password must contain at least %s characters"); >- $(document).ready(function() { >+ var PWD_COMPLEX_MSG = _("Password must contain numbers, lower and uppercase characters and special characters and must be at least %s characters long."); >+ var PWD_ALPHA_MSG = _("Password must contain both numbers and non-special characters and must be at least %s characters long."); >+ var PWD_SIMPLE_MSG = _("Password can only contain digits 0-9 and must be at least %s characters long."); >+ $(document).ready(function() { > var setPwdMessage = function() { > var require_strong = $('select#borrower_categorycode option:selected').data('pwdStrong'); > var min_lenght = $('select#borrower_categorycode option:selected').data('pwdLength'); >- $('#password_alert').html((require_strong?PWD_STRONG_MSG:PWD_WEAK_MSG).format(min_lenght)); >+ var passwordpolicy = $('select#borrower_categorycode option:selected').data('pwdPolicy'); >+ if(passwordpolicy){ >+ if(passwordpolicy == 'complex'){ >+ $('#password_alert').html((PWD_COMPLEX_MSG).format(min_lenght)); >+ }else if(passwordpolicy == 'alphanumeric'){ >+ $('#password_alert').html((PWD_ALPHA_MSG).format(min_lenght)); >+ }else{ >+ $('#password_alert').html((PWD_SIMPLE_MSG).format(min_lenght)); >+ } >+ }else{ >+ $('#password_alert').html((require_strong?PWD_STRONG_MSG:PWD_WEAK_MSG).format(min_lenght)); >+ } > }; > setPwdMessage(); > $('select#borrower_categorycode').change(setPwdMessage); >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt >index 73579f1a57..a2812f3e32 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt >@@ -43,7 +43,15 @@ > [% IF password_has_whitespaces %] > Password must not contain leading or trailing whitespaces. > [% END %] >- >+ [% IF ( complex_policy_mismatch ) %] >+ Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long. >+ [% END %] >+ [% IF ( alpha_policy_mismatch ) %] >+ Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long. >+ [% END %] >+ [% IF ( simple_policy_mismatch ) %] >+ Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long. >+ [% END %] > [% IF ( WrongPass ) %] > Your current password was entered incorrectly. If this problem persists, please ask a librarian to reset your password for you. > [% END %] >@@ -59,6 +67,12 @@ > <fieldset> > [% IF ( logged_in_user.category.effective_require_strong_password ) %] > <div class="alert alert-info">Your password must contain at least [% logged_in_user.category.effective_min_password_length | html %] characters, including UPPERCASE, lowercase and numbers.</div> >+ [% ELSIF ( password_policy == 'complex') %] >+ <div class="alert alert-info">Your password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</div> >+ [% ELSIF ( password_policy == 'alphanumeric') %] >+ <div class="alert alert-info">Your password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</div> >+ [% ELSIF ( password_policy == 'simplenumeric') %] >+ <div class="alert alert-info">Your password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</div> > [% ELSE %] > <div class="alert alert-info">Your password must be at least [% logged_in_user.category.effective_min_password_length | html %] characters long.</div> > [% END %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-password-recovery.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-password-recovery.tt >index 3ca70e7881..82559a868a 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-password-recovery.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-password-recovery.tt >@@ -80,6 +80,12 @@ > [% 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. >+ [% ELSIF ( complex_policy_mismatch ) %] >+ <li>Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% ELSIF ( alpha_policy_mismatch ) %] >+ <li>Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</li> >+ [% ELSIF ( simple_policy_mismatch ) %] >+ <li>Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</li> > [% END %] > </p> > <p>Please contact the library if you need further assistance.</p> >@@ -109,6 +115,12 @@ > <fieldset> > [% IF ( RequireStrongPassword ) %] > <div class="alert alert-info">Your password must contain at least [% minPasswordLength | html %] characters, including UPPERCASE, lowercase and numbers.</div> >+ [% ELSIF ( password_policy == 'complex') %] >+ <div class="alert alert-info">Your password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</div> >+ [% ELSIF ( password_policy == 'alphanumeric') %] >+ <div class="alert alert-info">Your password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</div> >+ [% ELSIF ( password_policy == 'simplenumeric') %] >+ <div class="alert alert-info">Your password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</div> > [% ELSE %] > <div class="alert alert-info">Your password must be at least [% minPasswordLength | html %] characters long.</div> > [% END %] >diff --git a/members/member-password.pl b/members/member-password.pl >index e9d5f54d9f..f71f6831cf 100755 >--- a/members/member-password.pl >+++ b/members/member-password.pl >@@ -52,10 +52,12 @@ output_and_exit_if_error( $input, $cookie, $template, { module => 'members', log > > my $category_type = $patron->category->category_type; > >+my $passwordpolicy = $patron->category->passwordpolicy; >+my $minPasswordLength = $patron->category->effective_min_password_length; >+ > if ( ( $patron_id ne $loggedinuser ) && ( $category_type eq 'S' ) ) { > push( @errors, 'NOPERMISSION' ) > unless ( $staffflags->{'superlibrarian'} || $staffflags->{'staffaccess'} ); >- > # need superlibrarian for koha-conf.xml fakeuser. > } > >@@ -94,6 +96,15 @@ if ( $newpassword and not @errors) { > elsif ( $_->isa('Koha::Exceptions::Password::Plugin') ) { > push @errors, 'ERROR_from_plugin'; > } >+ elsif ( $_->isa('Koha::Exceptions::Password::SimplePolicy') ) { >+ push @errors, 'ERROR_simple_policy_mismatch'; >+ } >+ elsif ( $_->isa('Koha::Exceptions::Password::AlphaPolicy') ) { >+ push @errors, 'ERROR_alpha_policy_mismatch'; >+ } >+ elsif ( $_->isa('Koha::Exceptions::Password::ComplexPolicy') ) { >+ push @errors, 'ERROR_complex_policy_mismatch'; >+ } > else { > push( @errors, 'BADUSERID' ); > } >@@ -104,6 +115,8 @@ $template->param( > patron => $patron, > destination => $destination, > csrf_token => Koha::Token->new->generate_csrf({ session_id => scalar $input->cookie('CGISESSID'), }), >+ password_policy => $passwordpolicy, >+ minPasswordLength => $minPasswordLength, > ); > > if ( scalar(@errors) ) { >diff --git a/members/memberentry.pl b/members/memberentry.pl >index ba5b5d1e9e..f5594e0775 100755 >--- a/members/memberentry.pl >+++ b/members/memberentry.pl >@@ -184,6 +184,12 @@ unless ($category_type or !($categorycode)){ > } > $category_type="A" unless $category_type; # FIXME we should display a error message instead of a 500 error ! > >+my $passwordpolicy = ( $patron ) ? $patron->category->passwordpolicy : Koha::Patron::Categories->find($categorycode)->passwordpolicy; >+$template->param(password_policy => $passwordpolicy); >+ >+my $minPasswordLength = Koha::Patron::Categories->find($categorycode)->effective_min_password_length; >+$template->param("minPasswordLength" => $minPasswordLength); >+ > # if a add or modify is requested => check validity of data. > %data = %$borrower_data if ($borrower_data); > >@@ -383,6 +389,9 @@ if ($op eq 'save' || $op eq 'insert'){ > 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'; >+ push @errors, 'ERROR_complex_policy_mismatch' if $error eq 'complex_policy_mismatch'; >+ push @errors, 'ERROR_alpha_policy_mismatch' if $error eq 'alpha_policy_mismatch'; >+ push @errors, 'ERROR_simple_policy_mismatch' if $error eq 'simple_policy_mismatch'; > } > } > >diff --git a/opac/opac-memberentry.pl b/opac/opac-memberentry.pl >index 37ade6703f..daa95afd1d 100755 >--- a/opac/opac-memberentry.pl >+++ b/opac/opac-memberentry.pl >@@ -38,6 +38,7 @@ use Koha::DateUtils; > use Koha::Libraries; > use Koha::Patron::Attribute::Types; > use Koha::Patron::Attributes; >+use Koha::Patron::Categories; > use Koha::Patron::Images; > use Koha::Patron::Modification; > use Koha::Patron::Modifications; >@@ -117,6 +118,15 @@ foreach my $attr (@$attributes) { > } > } > >+my $categorycode = C4::Context->preference('PatronSelfRegistrationDefaultCategory'); >+my $category = Koha::Patron::Categories->find($categorycode); >+my $passwordpolicy = $category->passwordpolicy; >+my $minPasswordLength = $category->effective_min_password_length; >+$template->param( >+ password_policy => $passwordpolicy, >+ minPasswordLength => $minPasswordLength >+); >+ > if ( $action eq 'create' ) { > > my %borrower = ParseCgiForBorrower($cgi); >@@ -125,6 +135,7 @@ if ( $action eq 'create' ) { > > my @empty_mandatory_fields = (CheckMandatoryFields( \%borrower, $action ), CheckMandatoryAttributes( \%borrower, $attributes ) ); > my $invalidformfields = CheckForInvalidFields(\%borrower); >+ > delete $borrower{'password2'}; > my $cardnumber_error_code; > if ( !grep { $_ eq 'cardnumber' } @empty_mandatory_fields ) { >@@ -472,6 +483,9 @@ sub CheckForInvalidFields { > 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'; >+ push @invalidFields, 'complex_policy_mismatch' if $error eq 'complex_policy_mismatch'; >+ push @invalidFields, 'alpha_policy_mismatch' if $error eq 'alpha_policy_mismatch'; >+ push @invalidFields, 'simple_policy_mismatch' if $error eq 'simple_policy_mismatch'; > } > } > >diff --git a/opac/opac-passwd.pl b/opac/opac-passwd.pl >index bff0214a9f..538ba349ec 100755 >--- a/opac/opac-passwd.pl >+++ b/opac/opac-passwd.pl >@@ -27,6 +27,7 @@ use C4::Context; > use C4::Circulation; > use C4::Members; > use C4::Output; >+use Koha::AuthUtils; > use Koha::Patrons; > > use Try::Tiny; >@@ -44,6 +45,9 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( > ); > > my $patron = Koha::Patrons->find( $borrowernumber ); >+my $categorycode = $patron->category->categorycode; >+my $passwordpolicy = $patron->category->passwordpolicy; >+my $minPasswordLength = $patron->category->effective_min_password_length; > if ( $patron->category->effective_change_password ) { > if ( $query->param('Oldkey') > && $query->param('Newkey') >@@ -60,6 +64,7 @@ if ( $patron->category->effective_change_password ) { > $template->param( 'passwords_mismatch' => '1' ); > } else { > try { >+ Koha::AuthUtils::is_password_valid( $new_password, $categorycode ); > $patron->set_password({ password => $new_password }); > $template->param( 'password_updated' => '1' ); > $template->param( 'borrowernumber' => $borrowernumber ); >@@ -71,6 +76,12 @@ if ( $patron->category->effective_change_password ) { > if $_->isa('Koha::Exceptions::Password::TooWeak'); > $error = 'password_has_whitespaces' > if $_->isa('Koha::Exceptions::Password::WhitespaceCharacters'); >+ $error = 'simple_policy_mismatch' >+ if $_->isa('Koha::Exceptions::Password::SimplePolicy'); >+ $error = 'alpha_policy_mismatch' >+ if $_->isa('Koha::Exceptions::Password::AlphaPolicy'); >+ $error = 'complex_policy_mismatch' >+ if $_->isa('Koha::Exceptions::Password::ComplexPolicy'); > }; > } > } >@@ -106,6 +117,8 @@ $template->param( > firstname => $patron->firstname, > surname => $patron->surname, > passwdview => 1, >+ password_policy => $passwordpolicy, >+ minPasswordLength => $minPasswordLength, > ); > > >diff --git a/opac/opac-password-recovery.pl b/opac/opac-password-recovery.pl >index d688e365a9..e3e0973af7 100755 >--- a/opac/opac-password-recovery.pl >+++ b/opac/opac-password-recovery.pl >@@ -7,6 +7,7 @@ use C4::Auth; > use C4::Koha; > use C4::Output; > use C4::Context; >+use Koha::AuthUtils; > use Koha::Patron::Password::Recovery > qw(SendPasswordRecoveryEmail ValidateBorrowernumber GetValidLinkInfo CompletePasswordRecovery DeleteExpiredPasswordRecovery); > use Koha::Patrons; >@@ -153,6 +154,15 @@ if ( $query->param('sendEmail') || $query->param('resendEmail') ) { > elsif ( $query->param('passwordReset') ) { > ( $borrower_number, $username ) = GetValidLinkInfo($uniqueKey); > >+ my $patron = Koha::Patrons->find($borrower_number); >+ my $passwordpolicy = $patron->category->passwordpolicy; >+ my $minPasswordLength = $patron->category->effective_min_password_length; >+ >+ $template->param( >+ password_policy => $passwordpolicy, >+ minPasswordLength => $minPasswordLength, >+ ); >+ > my $error; > my $min_password_length = C4::Context->preference('minPasswordPreference'); > my $require_strong_password = C4::Context->preference('RequireStrongPassword'); >@@ -183,6 +193,15 @@ elsif ( $query->param('passwordReset') ) { > elsif ( $_->isa('Koha::Exceptions::Password::TooWeak') ) { > $error = 'password_too_weak'; > } >+ elsif ( $_->isa('Koha::Exceptions::Password::SimplePolicy') ) { >+ $error = 'simple_policy_mismatch'; >+ } >+ elsif ( $_->isa('Koha::Exceptions::Password::AlphaPolicy') ) { >+ $error = 'alpha_policy_mismatch'; >+ } >+ elsif ( $_->isa('Koha::Exceptions::Password::ComplexPolicy') ) { >+ $error = 'complex_policy_mismatch'; >+ } > }; > } > if ( $error ) { >@@ -215,7 +234,8 @@ elsif ($uniqueKey) { #reset password form > errLinkNotValid => $errLinkNotValid, > hasError => ( $errLinkNotValid ? 1 : 0 ), > minPasswordLength => $borrower->category->effective_min_password_length, >- RequireStrongPassword => $borrower->category->effective_require_strong_password >+ RequireStrongPassword => $borrower->category->effective_require_strong_password, >+ password_policy => $borrower->category->passwordpolicy, > ); > } > else { #password recovery form (to send email) >diff --git a/t/db_dependent/AuthUtils.t b/t/db_dependent/AuthUtils.t >index fa5bc76038..843e42cf73 100644 >--- a/t/db_dependent/AuthUtils.t >+++ b/t/db_dependent/AuthUtils.t >@@ -32,26 +32,26 @@ $schema->storage->txn_begin; > my $category1 = $builder->build_object( > { > class => 'Koha::Patron::Categories', >- value => { min_password_length => 15, require_strong_password => 1 } >+ value => { min_password_length => 15, require_strong_password => 1, passwordpolicy => '' } > } > ); > my $category2 = $builder->build_object( > { > class => 'Koha::Patron::Categories', >- value => { min_password_length => 5, require_strong_password => undef } >+ value => { min_password_length => 5, require_strong_password => undef, passwordpolicy => '' } > } > ); > my $category3 = $builder->build_object( > { > class => 'Koha::Patron::Categories', >- value => { min_password_length => undef, require_strong_password => 1 } >+ value => { min_password_length => undef, require_strong_password => 1, passwordpolicy => '' } > } > ); > my $category4 = $builder->build_object( > { > class => 'Koha::Patron::Categories', > value => >- { min_password_length => undef, require_strong_password => undef } >+ { min_password_length => undef, require_strong_password => undef, passwordpolicy => '' } > } > ); > >@@ -63,8 +63,26 @@ my $p_15l_weak = '0123456789abcdf'; > my $p_5l_strong = 'Abc12'; > my $p_15l_strong = '0123456789AbCdF'; > >+# Password policy simplenumeric >+my $category_simple = $builder->build_object({ >+ class => 'Koha::Patron::Categories', >+ value => { min_password_length => 4, passwordpolicy => 'simplenumeric' }, >+}); >+ >+# Password policy alphanumeric >+my $category_alpha = $builder->build_object({ >+ class => 'Koha::Patron::Categories', >+ value => { min_password_length => 5, passwordpolicy => 'alphanumeric' }, >+}); >+ >+# Password policy complex >+my $category_complex = $builder->build_object({ >+ class => 'Koha::Patron::Categories', >+ value => { min_password_length => 6, passwordpolicy => 'complex' }, >+}); >+ > subtest 'is_password_valid for category' => sub { >- plan tests => 15; >+ plan tests => 16; > > my ( $is_valid, $error ); > >@@ -121,10 +139,44 @@ subtest 'is_password_valid for category' => sub { > 'Koha::Exceptions::Password::NoCategoryProvided', > 'Category should always be provided'; > >+ subtest 'password policies' => sub { >+ >+ t::lib::Mocks::mock_preference('RequireStrongPassword', 0); >+ t::lib::Mocks::mock_preference('minPasswordLength', 4); >+ >+ #test simplenumeric password policy >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '1234', $category_simple ); >+ is ( $is_valid, 1, 'simplenumeric password should contain only numbers' ); >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A123', $category_simple ); >+ is ( $is_valid, 0, 'simplenumeric password should not contain alphabets' ); >+ is($error, 'simple_policy_mismatch', 'error "simple_policy_mismatch" raised'); >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '!234', $category_simple ); >+ is ( $is_valid, 0, 'simplenumeric password should not contain non-special characters' ); >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '123', $category_simple ); >+ is ( $is_valid, 0, 'simplenumeric password follows "min_password_length" value' ); >+ >+ #test alphanumeric >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A1234', $category_alpha ); >+ is ( $is_valid, 1, 'alphanumeric password should contain both numbers and non-special characters' ); >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '12345', $category_alpha ); >+ is ( $is_valid, 0, 'alphanumeric password must contain at least one uppercase character' ); >+ is($error, 'alpha_policy_mismatch', 'error "alpha_policy_mismatch" raised'); >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A123', $category_alpha ); >+ is ( $is_valid, 0, 'alphanumeric password follows "min_password_length" value'); >+ >+ #test complex >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'As!123', $category_complex ); >+ is ( $is_valid, 1, 'complex password should contain numbers, lower and uppercase characters and special characters' ); >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A12345', $category_complex ); >+ is ( $is_valid, 0, 'complex password must contain numbers, lower and uppercase characters and special characters' ); >+ is($error, 'complex_policy_mismatch', 'error "complex_policy_mismatch" raised'); >+ ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'As!12', $category_complex ); >+ is ( $is_valid, 0, 'complex password follows "min_password_length" value' ); >+ } > }; > > subtest 'generate_password for category' => sub { >- plan tests => 5; >+ plan tests => 6; > > my ( $is_valid, $error ); > >@@ -159,6 +211,37 @@ subtest 'generate_password for category' => sub { > 'Koha::Exceptions::Password::NoCategoryProvided', > 'Category should always be provided'; > >+ subtest 'generate_password with password policies' => sub { >+ >+ t::lib::Mocks::mock_preference('RequireStrongPassword', 0); >+ t::lib::Mocks::mock_preference('minPasswordLength', 4); >+ >+ my $all_valid = 1; >+ >+ #simplenumeric >+ for ( 1 .. 10 ) { >+ my $password = Koha::AuthUtils::generate_password( $category_simple );; >+ my ( $is_valid, undef ) = Koha::AuthUtils::is_password_valid( $password, $category_simple ); >+ $all_valid = 0 unless $is_valid; >+ } >+ is ( $all_valid, 1, 'generate_password should generate valid passwords with simplenumeric policy' ); >+ >+ #alphanumeric >+ for ( 1 .. 10 ) { >+ my $password = Koha::AuthUtils::generate_password( $category_alpha ); >+ my ( $is_valid, undef ) = Koha::AuthUtils::is_password_valid( $password, $category_alpha ); >+ $all_valid = 0 unless $is_valid; >+ } >+ is ( $all_valid, 1, 'generate_password should generate valid passwords with alphanumeric policy' ); >+ >+ #complex >+ for ( 1 .. 10 ) { >+ my $password = Koha::AuthUtils::generate_password( $category_complex ); >+ my ( $is_valid, undef ) = Koha::AuthUtils::is_password_valid( $password, $category_complex ); >+ $all_valid = 0 unless $is_valid; >+ } >+ is ( $all_valid, 1, 'generate_password should generate valid passwords with complex policy' ); >+ } > }; > > $schema->storage->txn_rollback; >diff --git a/t/db_dependent/api/v1/patrons_password.t b/t/db_dependent/api/v1/patrons_password.t >index 2a57516319..8d06ccbdf9 100644 >--- a/t/db_dependent/api/v1/patrons_password.t >+++ b/t/db_dependent/api/v1/patrons_password.t >@@ -39,11 +39,12 @@ my $t = Test::Mojo->new('Koha::REST::V1'); > > subtest 'set() (authorized user tests)' => sub { > >- plan tests => 21; >+ plan tests => 22; > > $schema->storage->txn_begin; > > my ( $patron, $session ) = create_user_and_session({ authorized => 1 }); >+ $patron->category->update({ passwordpolicy => ''}); > > t::lib::Mocks::mock_preference( 'minPasswordLength', 3 ); > t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 ); >@@ -118,6 +119,82 @@ subtest 'set() (authorized user tests)' => sub { > $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); > $t->request_ok($tx)->status_is(200)->json_is(''); > >+ subtest 'password policies' => sub { >+ >+ plan tests => 18; >+ >+ t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0); >+ t::lib::Mocks::mock_preference( 'minPasswordLength', 4 ); >+ >+ # simple policy >+ $patron->category->update({ passwordpolicy => 'simple'}); >+ $new_password = '1234'; >+ $tx >+ = $t->ua->build_tx( POST => "/api/v1/patrons/" >+ . $patron->id >+ . "/password" => json => { password => $new_password, password_2 => $new_password } ); >+ >+ $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); >+ $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); >+ $t->request_ok($tx)->status_is(200)->json_is(''); >+ >+ $new_password = '123A'; >+ $tx >+ = $t->ua->build_tx( POST => "/api/v1/patrons/" >+ . $patron->id >+ . "/password" => json => { password => $new_password, password_2 => $new_password } ); >+ >+ $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); >+ $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); >+ $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password does not match simplenumeric passwordpolicy]' }); >+ >+ # alphanumeric policy >+ $patron->category->update({ passwordpolicy => 'alphanumeric'}); >+ $new_password = '123A5'; >+ $tx >+ = $t->ua->build_tx( POST => "/api/v1/patrons/" >+ . $patron->id >+ . "/password" => json => { password => $new_password, password_2 => $new_password } ); >+ >+ $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); >+ $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); >+ $t->request_ok($tx)->status_is(200)->json_is(''); >+ >+ $new_password = '12345'; >+ $tx >+ = $t->ua->build_tx( POST => "/api/v1/patrons/" >+ . $patron->id >+ . "/password" => json => { password => $new_password, password_2 => $new_password } ); >+ >+ $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); >+ $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); >+ $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password does not match alphanumeric passwordpolicy]' }); >+ >+ # complex policy >+ $patron->category->update({ passwordpolicy => 'complex'}); >+ $new_password = 'As!123'; >+ $tx >+ = $t->ua->build_tx( POST => "/api/v1/patrons/" >+ . $patron->id >+ . "/password" => json => { password => $new_password, password_2 => $new_password } ); >+ >+ $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); >+ $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); >+ $t->request_ok($tx)->status_is(200)->json_is(''); >+ >+ $new_password = '123A5'; >+ $tx >+ = $t->ua->build_tx( POST => "/api/v1/patrons/" >+ . $patron->id >+ . "/password" => json => { password => $new_password, password_2 => $new_password } ); >+ >+ $tx->req->cookies( { name => 'CGISESSID', value => $session->id } ); >+ $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } ); >+ $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password does not match complex passwordpolicy]' }); >+ >+ >+ }; >+ > $schema->storage->txn_rollback; > }; > >@@ -128,6 +205,8 @@ subtest 'set_public() (unprivileged user tests)' => sub { > $schema->storage->txn_begin; > > my ( $patron, $session ) = create_user_and_session({ authorized => 0 }); >+ $patron->category->update({ passwordpolicy => ''}); >+ > my $other_patron = $builder->build_object({ class => 'Koha::Patrons' }); > > # Enable the public API >-- >2.17.1 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 12617
:
37740
|
107382
|
107383
|
107384
|
109388
|
109389
|
110526
|
110527
|
110528
|
111297
|
111298
|
111299