From 3ac41ea8f49c8de676f6be3fb661ccb41a40f23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Sepp=C3=A4l=C3=A4?= Date: Mon, 11 Aug 2014 15:58:05 +0300 Subject: [PATCH 1/3] 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 | 46 ++++++++- Koha/Exceptions/Password.pm | 14 ++- Koha/Patron.pm | 16 +++- 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 | 35 ++++++- .../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 +++++++++++++++- 21 files changed, 443 insertions(+), 28 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..5bca9b1a7d 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,7 +155,34 @@ 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 ) { @@ -162,7 +190,7 @@ sub is_password_valid { 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 25ceac1470..00c68ef7ea 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/admin/categories.pl b/admin/categories.pl index 749db2d0a0..ee77f11379 100755 --- a/admin/categories.pl +++ b/admin/categories.pl @@ -96,6 +96,7 @@ elsif ( $op eq 'add_validate' ) { my $exclude_from_local_holds_priority = $input->param('exclude_from_local_holds_priority'); 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; @@ -136,6 +137,7 @@ elsif ( $op eq 'add_validate' ) { $category->exclude_from_local_holds_priority($exclude_from_local_holds_priority); $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 ); @@ -167,6 +169,7 @@ elsif ( $op eq 'add_validate' ) { exclude_from_local_holds_priority => $exclude_from_local_holds_priority, 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..020d7b0c15 --- /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 exclude_from_local_holds_priority"); + + # 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 572a56310c..aafb6cc05e 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -330,6 +330,7 @@ CREATE TABLE `categories` ( -- this table shows information related to Koha patr `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 `exclude_from_local_holds_priority` tinyint(1) default NULL, -- Exclude patrons of this category from local holds priority + `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 1df6a1fb53..bb95f2df08 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -331,6 +331,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 0c836f418b..5bab349eed 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 %] +
  • + + + + Selecting a password policy for a category affects both automatically created suggested passwords and enfo$ + of rules. + +