@@ -, +, @@ automatically generated password complexity/difficulty - Per category password policy: admins can configure what kind of passwords get generated - Predefined policies: --- 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 --- a/Koha/AuthUtils.pm +++ a/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 --- a/Koha/Exceptions/Password.pm +++ a/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 { --- a/Koha/Patron.pm +++ a/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 --- a/admin/categories.pl +++ a/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; --- a/installer/data/mysql/atomicupdate/Bug-12617-Koha-should-let-admins-to-configure-automatically-generated-password.perl +++ a/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"; +} --- a/installer/data/mysql/kohastructure.sql +++ a/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; --- a/installer/data/mysql/sysprefs.sql +++ a/installer/data/mysql/sysprefs.sql @@ -332,6 +332,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'), --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt +++ a/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. + +