View | Details | Raw Unified | Return to bug 12617
Collapse All | Expand All

(-)a/Koha/AuthUtils.pm (-5 / +41 lines)
Lines 22-31 use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); Link Here
22
use Encode qw( encode is_utf8 );
22
use Encode qw( encode is_utf8 );
23
use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt
23
use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt
24
use List::MoreUtils qw/ any /;
24
use List::MoreUtils qw/ any /;
25
use String::Random qw( random_string );
25
use String::Random qw( random_string random_regex );
26
use Koha::Exceptions::Password;
26
use Koha::Exceptions::Password;
27
27
28
use C4::Context;
28
use C4::Context;
29
use Koha::Patron::Categories;
29
30
30
use base 'Exporter';
31
use base 'Exporter';
31
32
Lines 154-160 sub is_password_valid { Link Here
154
    }
155
    }
155
    my $minPasswordLength = $category->effective_min_password_length;
156
    my $minPasswordLength = $category->effective_min_password_length;
156
    $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3;
157
    $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3;
157
    if ( length($password) < $minPasswordLength ) {
158
    my $passwordpolicy = $category->passwordpolicy;
159
160
    if ($passwordpolicy) {
161
        if ($passwordpolicy eq "complex") {
162
            unless ($password =~ /[0-9]/
163
                && $password =~ /[a-z]/
164
                && $password =~ /[A-Z]/
165
                && $password =~ /[\|\[\]\{\}!@#\$%\^&\*\(\)_\-\+\?]/
166
                && length($password) >= $minPasswordLength ) {
167
                return (0, "complex_policy_mismatch");
168
            }
169
        }
170
        elsif ($passwordpolicy eq "alphanumeric") {
171
            unless ($password =~ /[0-9]/
172
                && $password =~ /[a-zA-Z]/
173
                && $password !~ /\W/
174
                && $password !~ /[_-]/
175
                && length($password) >= $minPasswordLength ) {
176
                return (0, "alpha_policy_mismatch");
177
            }
178
        }
179
        else {
180
            if ($password !~ /^[0-9]+$/ || length($password) < $minPasswordLength) {
181
                return (0, "simple_policy_mismatch");
182
            }
183
        }
184
    }
185
    elsif ( length($password) < $minPasswordLength ) {
158
        return ( 0, 'too_short' );
186
        return ( 0, 'too_short' );
159
    }
187
    }
160
    elsif ( $category->effective_require_strong_password ) {
188
    elsif ( $category->effective_require_strong_password ) {
Lines 162-168 sub is_password_valid { Link Here
162
          if $password !~ m|(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{$minPasswordLength,}|;
190
          if $password !~ m|(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{$minPasswordLength,}|;
163
    }
191
    }
164
    return ( 0, 'has_whitespaces' ) if $password =~ m[^\s|\s$];
192
    return ( 0, 'has_whitespaces' ) if $password =~ m[^\s|\s$];
165
    return ( 1, undef );
193
    return 1;
166
}
194
}
167
195
168
=head2 generate_password
196
=head2 generate_password
Lines 180-195 sub generate_password { Link Here
180
    }
208
    }
181
    my $minPasswordLength = $category->effective_min_password_length;
209
    my $minPasswordLength = $category->effective_min_password_length;
182
    $minPasswordLength = 8 if not $minPasswordLength or $minPasswordLength < 8;
210
    $minPasswordLength = 8 if not $minPasswordLength or $minPasswordLength < 8;
211
    my $passwordpolicy = $category->passwordpolicy;
183
212
184
    my ( $password, $is_valid );
213
    my ( $password, $is_valid );
185
    do {
214
    do {
186
        $password = random_string('.' x $minPasswordLength );
215
        if (!$passwordpolicy || $passwordpolicy eq "complex") {
216
            $password = random_string('.' x $minPasswordLength);
217
        } else {
218
            if ($passwordpolicy eq "alphanumeric") {
219
                $password = random_regex('[a-zA-Z0-9]' x $minPasswordLength);
220
            } else {
221
                $password = random_regex('[0-9]' x $minPasswordLength);
222
            }
223
        }
187
        ( $is_valid, undef ) = is_password_valid( $password, $category );
224
        ( $is_valid, undef ) = is_password_valid( $password, $category );
188
    } while not $is_valid;
225
    } while not $is_valid;
189
    return $password;
226
    return $password;
190
}
227
}
191
228
192
193
=head2 get_script_name
229
=head2 get_script_name
194
230
195
This returns the correct script name, for use in redirecting back to the correct page after showing
231
This returns the correct script name, for use in redirecting back to the correct page after showing
(-)a/Koha/Exceptions/Password.pm (-1 / +13 lines)
Lines 46-52 use Exception::Class ( Link Here
46
    'Koha::Exceptions::Password::NoCategoryProvided' => {
46
    'Koha::Exceptions::Password::NoCategoryProvided' => {
47
        isa => 'Koha::Exceptions::Password',
47
        isa => 'Koha::Exceptions::Password',
48
        description => 'You must provide a patron\'s category to validate password\'s strength and length'
48
        description => 'You must provide a patron\'s category to validate password\'s strength and length'
49
    }
49
    },
50
    'Koha::Exceptions::Password::SimplePolicy' => {
51
        isa => 'Koha::Exceptions::Password',
52
        description => 'Password does not match simplenumeric passwordpolicy',
53
    },
54
    'Koha::Exceptions::Password::AlphaPolicy' => {
55
        isa => 'Koha::Exceptions::Password',
56
        description => 'Password does not match alphanumeric passwordpolicy',
57
    },
58
    'Koha::Exceptions::Password::ComplexPolicy' => {
59
        isa => 'Koha::Exceptions::Password',
60
        description => 'Password does not match complex passwordpolicy',
61
    },
50
);
62
);
51
63
52
sub full_message {
64
sub full_message {
(-)a/Koha/Patron.pm (-1 / +15 lines)
Lines 734-739 Exceptions are thrown if the password is not good enough. Link Here
734
734
735
=item Koha::Exceptions::Password::Plugin (if a "check password" plugin is enabled)
735
=item Koha::Exceptions::Password::Plugin (if a "check password" plugin is enabled)
736
736
737
=item Koha::Exceptions::Password::SimplePolicy
738
739
=item Koha::Exceptions::Password::AlphaPolicy
740
741
=item Koha::Exceptions::Password::ComplexPolicy
742
737
=back
743
=back
738
744
739
=cut
745
=cut
Lines 761-766 sub set_password { Link Here
761
            elsif ( $error eq 'too_weak' ) {
767
            elsif ( $error eq 'too_weak' ) {
762
                Koha::Exceptions::Password::TooWeak->throw();
768
                Koha::Exceptions::Password::TooWeak->throw();
763
            }
769
            }
770
            elsif ( $error eq 'simple_policy_mismatch' ) {
771
                Koha::Exceptions::Password::SimplePolicy->throw();
772
            }
773
            elsif ( $error eq 'alpha_policy_mismatch' ) {
774
                Koha::Exceptions::Password::AlphaPolicy->throw();
775
            }
776
            elsif ( $error eq 'complex_policy_mismatch' ) {
777
                Koha::Exceptions::Password::ComplexPolicy->throw();
778
            }
764
        }
779
        }
765
    }
780
    }
766
781
Lines 801-807 sub set_password { Link Here
801
    return $self;
816
    return $self;
802
}
817
}
803
818
804
805
=head3 renew_account
819
=head3 renew_account
806
820
807
my $new_expiry_date = $patron->renew_account
821
my $new_expiry_date = $patron->renew_account
(-)a/admin/categories.pl (+3 lines)
Lines 96-101 elsif ( $op eq 'add_validate' ) { Link Here
96
    my $exclude_from_local_holds_priority = $input->param('exclude_from_local_holds_priority');
96
    my $exclude_from_local_holds_priority = $input->param('exclude_from_local_holds_priority');
97
    my $min_password_length = $input->param('min_password_length');
97
    my $min_password_length = $input->param('min_password_length');
98
    my $require_strong_password = $input->param('require_strong_password');
98
    my $require_strong_password = $input->param('require_strong_password');
99
    my $selectedpasswordpolicy  = $input->param('passwordpolicy');
99
    my @branches = grep { $_ ne q{} } $input->multi_param('branches');
100
    my @branches = grep { $_ ne q{} } $input->multi_param('branches');
100
101
101
    $reset_password = undef if $reset_password eq -1;
102
    $reset_password = undef if $reset_password eq -1;
Lines 136-141 elsif ( $op eq 'add_validate' ) { Link Here
136
        $category->exclude_from_local_holds_priority($exclude_from_local_holds_priority);
137
        $category->exclude_from_local_holds_priority($exclude_from_local_holds_priority);
137
        $category->min_password_length($min_password_length);
138
        $category->min_password_length($min_password_length);
138
        $category->require_strong_password($require_strong_password);
139
        $category->require_strong_password($require_strong_password);
140
        $category->passwordpolicy($selectedpasswordpolicy);
139
        eval {
141
        eval {
140
            $category->store;
142
            $category->store;
141
            $category->replace_branch_limitations( \@branches );
143
            $category->replace_branch_limitations( \@branches );
Lines 167-172 elsif ( $op eq 'add_validate' ) { Link Here
167
            exclude_from_local_holds_priority => $exclude_from_local_holds_priority,
169
            exclude_from_local_holds_priority => $exclude_from_local_holds_priority,
168
            min_password_length => $min_password_length,
170
            min_password_length => $min_password_length,
169
            require_strong_password => $require_strong_password,
171
            require_strong_password => $require_strong_password,
172
            passwordpolicy => $selectedpasswordpolicy,
170
        });
173
        });
171
        eval {
174
        eval {
172
            $category->store;
175
            $category->store;
(-)a/installer/data/mysql/atomicupdate/Bug-12617-Koha-should-let-admins-to-configure-automatically-generated-password.perl (+8 lines)
Line 0 Link Here
1
$DBversion = 'XXX';  # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
    $dbh->do("ALTER TABLE categories ADD COLUMN passwordpolicy VARCHAR(40) DEFAULT NULL AFTER exclude_from_local_holds_priority");
4
5
    # Always end with this (adjust the bug info)
6
    SetVersion( $DBversion );
7
    print "Upgrade to $DBversion done (Bug 12617 - Koha should let admins to configure automatically generated password complexity/difficulty)\n";
8
}
(-)a/installer/data/mysql/kohastructure.sql (+1 lines)
Lines 330-335 CREATE TABLE `categories` ( -- this table shows information related to Koha patr Link Here
330
  `min_password_length` smallint(6) NULL DEFAULT NULL, -- set minimum password length for patrons in this category
330
  `min_password_length` smallint(6) NULL DEFAULT NULL, -- set minimum password length for patrons in this category
331
  `require_strong_password` TINYINT(1) NULL DEFAULT NULL, -- set required password strength for patrons in this category
331
  `require_strong_password` TINYINT(1) NULL DEFAULT NULL, -- set required password strength for patrons in this category
332
  `exclude_from_local_holds_priority` tinyint(1) default NULL, -- Exclude patrons of this category from local holds priority
332
  `exclude_from_local_holds_priority` tinyint(1) default NULL, -- Exclude patrons of this category from local holds priority
333
  `passwordpolicy` varchar(40) default NULL, -- which password policy patron category uses
333
  PRIMARY KEY  (`categorycode`),
334
  PRIMARY KEY  (`categorycode`),
334
  UNIQUE KEY `categorycode` (`categorycode`)
335
  UNIQUE KEY `categorycode` (`categorycode`)
335
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
336
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
(-)a/installer/data/mysql/sysprefs.sql (+2 lines)
Lines 332-337 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
332
('MaxTotalSuggestions','',NULL,'Number of total suggestions used for time limit with NumberOfSuggestionDays','Free'),
332
('MaxTotalSuggestions','',NULL,'Number of total suggestions used for time limit with NumberOfSuggestionDays','Free'),
333
('MembershipExpiryDaysNotice','',NULL,'Send an account expiration notice that a patron\'s card is about to expire after','Integer'),
333
('MembershipExpiryDaysNotice','',NULL,'Send an account expiration notice that a patron\'s card is about to expire after','Integer'),
334
('MergeReportFields','',NULL,'Displayed fields for deleted MARC records after merge','Free'),
334
('MergeReportFields','',NULL,'Displayed fields for deleted MARC records after merge','Free'),
335
('minAlnumPasswordLength', '10', null, 'Specify the minimum length for alphanumeric passwords', 'free')
336
('minComplexPasswordLength', '10', null, 'Specify the minimum length for complex passwords', 'free')
335
('minPasswordLength','8',NULL,'Specify the minimum length of a patron/staff password','free'),
337
('minPasswordLength','8',NULL,'Specify the minimum length of a patron/staff password','free'),
336
('NewItemsDefaultLocation','','','If set, all new items will have a location of the given Location Code ( Authorized Value type LOC )',''),
338
('NewItemsDefaultLocation','','','If set, all new items will have a location of the given Location Code ( Authorized Value type LOC )',''),
337
('NewsAuthorDisplay','none','none|opac|staff|both','Display the author name for news items.','Choice'),
339
('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 (+15 lines)
Lines 266-271 Link Here
266
                      [% END %]
266
                      [% END %]
267
                    </select>
267
                    </select>
268
                </li>
268
                </li>
269
                <li>
270
                    <label for="password-policy">Category password policy:</label>
271
                    <select name="passwordpolicy" id="password-policy">
272
                        [% UNLESS category %]<option value="" selected="selected"></option>[% ELSE %]<option value=""></option>[% END %]
273
                        [% IF category and category.passwordpolicy == 'complex' %]<option value="complex" selected="selected">Complex</option>[% ELSE %]<option value="complex">Complex</option>[% END %]
274
                        [% IF category and category.passwordpolicy == 'alphanumeric' %]<option value="alphanumeric" selected="selected">Alphanumeric</option>[% ELSE %]<option value="alphanumeric">Alphanumeric</option>[% END %]
275
                        [% IF category and category.passwordpolicy == 'simplenumeric' %]<option value="simplenumeric" selected="selected">Numbers only</option>[% ELSE %]<option value="simplenumeric">Numbers only</option>[% END %]
276
                    </select>
277
                    <span>
278
                        Selecting a password policy for a category affects both automatically created suggested passwords and enfo$
279
                        of rules.
280
                    </span>
281
                </li>
269
                <li><label for="block_expired">Block expired patrons:</label>
282
                <li><label for="block_expired">Block expired patrons:</label>
270
                    <select name="BlockExpiredPatronOpacActions" id="block_expired">
283
                    <select name="BlockExpiredPatronOpacActions" id="block_expired">
271
                        [% IF not category or category.BlockExpiredPatronOpacActions == -1%]
284
                        [% IF not category or category.BlockExpiredPatronOpacActions == -1%]
Lines 469-474 Link Here
469
                    <th scope="col">Messaging</th>
482
                    <th scope="col">Messaging</th>
470
                    [% END %]
483
                    [% END %]
471
                    <th scope="col">Library limitations</th>
484
                    <th scope="col">Library limitations</th>
485
                    <th scope="col">Password policy</th>
472
                    [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
486
                    [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
473
                    <th scope="col">Check previous checkout?</th>
487
                    <th scope="col">Check previous checkout?</th>
474
                    [% END %]
488
                    [% END %]
Lines 565-570 Link Here
565
                                No limitation
579
                                No limitation
566
                            [% END %]
580
                            [% END %]
567
                        </td>
581
                        </td>
582
                        <td>[% category.passwordpolicy %]</td>
568
                        [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
583
                        [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
569
                          <td>
584
                          <td>
570
                              [% SWITCH category.checkprevcheckout %]
585
                              [% SWITCH category.checkprevcheckout %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref (-1 / +11 lines)
Lines 321-330 Patrons: Link Here
321
         - "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."
321
         - "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."
322
    Security:
322
    Security:
323
     -
323
     -
324
         - Login passwords for staff and patrons must be at least
324
         - Login passwords for simplenumeric policy must be at least
325
         - pref: minPasswordLength
325
         - pref: minPasswordLength
326
           class: integer
326
           class: integer
327
         - characters long.
327
         - characters long.
328
     -
329
         - Login passwords for alphanumeric policy must be at least
330
         - pref: minAlnumPasswordLength
331
           class: integer
332
         - characters long.
333
     -
334
         - Login passwords for complex policy must be at least
335
         - pref: minComplexPasswordLength
336
           class: integer
337
         - characters long.
328
     -
338
     -
329
         - pref: RequireStrongPassword
339
         - pref: RequireStrongPassword
330
           choices:
340
           choices:
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt (-5 / +24 lines)
Lines 51-59 Link Here
51
		[% IF ( NOPERMISSION ) %]
51
		[% IF ( NOPERMISSION ) %]
52
		<li>You do not have permission to edit this patron's login information.</li>
52
		<li>You do not have permission to edit this patron's login information.</li>
53
		[% END %]
53
		[% END %]
54
		[% IF ( NOMATCH ) %]
54
		[% IF ( ERROR_password_mismatch )%]
55
		<li><strong>The passwords entered do not match</strong>. Please re-enter the new password.</li>
55
		<li id="ERROR_password_mismatch"><strong>The passwords entered do not match</strong>. Please re-enter the new password.</li>
56
		[% END %]
56
		[% END %]
57
        [% IF ( ERROR_complex_policy_mismatch ) %]
58
        <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>
59
        [% END %]
60
        [% IF ( ERROR_alpha_policy_mismatch ) %]
61
        <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>
62
        [% END %]
63
        [% IF ( ERROR_simple_policy_mismatch ) %]
64
        <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>
65
        [% END %]
57
		</ul>
66
		</ul>
58
		</div>
67
		</div>
59
	[% END %]
68
	[% END %]
Lines 102-110 Link Here
102
    [% INCLUDE 'str/members-menu.inc' %]
111
    [% INCLUDE 'str/members-menu.inc' %]
103
    [% Asset.js("js/members-menu.js") | $raw %]
112
    [% Asset.js("js/members-menu.js") | $raw %]
104
    <script>
113
    <script>
105
        function generate_password() {
114
        function generate_password(password_policy) {
106
            // Always generate a strong password
115
            // Follow password policy when generating password
107
            var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
116
            var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
117
            if ( password_policy == 'complex' ){
118
                chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ|[]{}!@#$%^&*()_-+?';
119
            } else if ( password_policy == 'simplenumeric'){
120
                chars = '0123456789';
121
            }
108
            var length = [% patron.category.effective_min_password_length | html %];
122
            var length = [% patron.category.effective_min_password_length | html %];
109
            if ( length < 8 ) length = 8;
123
            if ( length < 8 ) length = 8;
110
            var password='';
124
            var password='';
Lines 117-125 Link Here
117
            $("body").on('click', "#fillrandom",function(e) {
131
            $("body").on('click', "#fillrandom",function(e) {
118
                e.preventDefault();
132
                e.preventDefault();
119
                var password = '';
133
                var password = '';
134
                var password_policy = '[% password_policy | html %]';
135
136
                // Change password pattern to match password policy
120
                var pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{[% patron.category.effective_min_password_length | html %],}/;
137
                var pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{[% patron.category.effective_min_password_length | html %],}/;
138
                if (password_policy == 'simplenumeric') pattern_regex = /(?=.*\d).{[% patron.category.effective_min_password_length | html %],}/;
139
                if (password_policy == 'complex') pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!?@#$%^&*()_+-]).{[% patron.category.effective_min_password_length | html %],}/;
121
                while ( ! pattern_regex.test( password ) ) {
140
                while ( ! pattern_regex.test( password ) ) {
122
                    password = generate_password();
141
                    password = generate_password(password_policy);
123
                }
142
                }
124
                $("#newpassword").val(password);
143
                $("#newpassword").val(password);
125
                $("#newpassword").attr('type', 'text');
144
                $("#newpassword").attr('type', 'text');
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt (-2 / +11 lines)
Lines 172-177 legend:hover { Link Here
172
                                    [% IF ERROR_bad_email_alternative %]
172
                                    [% IF ERROR_bad_email_alternative %]
173
                                        <li id="ERROR_bad_email_alternative">The alternative email is invalid.</li>
173
                                        <li id="ERROR_bad_email_alternative">The alternative email is invalid.</li>
174
                                    [% END %]
174
                                    [% END %]
175
                                    [% IF ( ERROR_complex_policy_mismatch ) %]
176
                                    <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>
177
                                    [% END %]
178
                                    [% IF ( ERROR_alpha_policy_mismatch ) %]
179
                                    <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>
180
                                    [% END %]
181
                                    [% IF ( ERROR_simple_policy_mismatch ) %]
182
                                    <li id="ERROR_policy_mismatch">Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</li>
183
                                    [% END %]
175
                                </ul>
184
                                </ul>
176
                            </div>
185
                            </div>
177
                        [% END %]
186
                        [% END %]
Lines 838-846 legend:hover { Link Here
838
                                                            [% IF ( typeloo.typename_X ) %]<optgroup label="Statistical">[% END %]
847
                                                            [% IF ( typeloo.typename_X ) %]<optgroup label="Statistical">[% END %]
839
                                                        [% END %]
848
                                                        [% END %]
840
                                                        [% IF ( categoryloo.categorycodeselected ) %]
849
                                                        [% IF ( categoryloo.categorycodeselected ) %]
841
                                                            <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>
850
                                                            <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>
842
                                                        [% ELSE %]
851
                                                        [% ELSE %]
843
                                                            <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>
852
                                                            <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>
844
                                                        [% END %]
853
                                                        [% END %]
845
                                                        [% IF ( loop.last ) %]
854
                                                        [% IF ( loop.last ) %]
846
                                                            </optgroup>
855
                                                            </optgroup>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt (-3 / +32 lines)
Lines 100-105 Link Here
100
                                [% IF field == "password_has_whitespaces" %]
100
                                [% IF field == "password_has_whitespaces" %]
101
                                    <li>Password must not contain leading or trailing whitespaces.</li>
101
                                    <li>Password must not contain leading or trailing whitespaces.</li>
102
                                [% END %]
102
                                [% END %]
103
                                [% IF field == "complex_policy_mismatch" %]
104
                                    <li>Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</li>
105
                                [% END %]
106
                                [% IF field == "alpha_policy_mismatch" %]
107
                                    <li>Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</li>
108
                                [% END %]
109
                                [% IF field == "simple_policy_mismatch" %]
110
                                    <li>Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</li>
111
                                [% END %]
103
                                [% IF field == "duplicate_email" %]
112
                                [% IF field == "duplicate_email" %]
104
                                    <li>This email address already exists in our database.</li>
113
                                    <li>This email address already exists in our database.</li>
105
                                [% END %]
114
                                [% END %]
Lines 269-277 Link Here
269
                                                        <select id="borrower_categorycode" name="borrower_categorycode">
278
                                                        <select id="borrower_categorycode" name="borrower_categorycode">
270
                                                            [% FOREACH c IN Categories.all() %]
279
                                                            [% FOREACH c IN Categories.all() %]
271
                                                                [% IF c.categorycode == Koha.Preference('PatronSelfRegistrationDefaultCategory') %]
280
                                                                [% IF c.categorycode == Koha.Preference('PatronSelfRegistrationDefaultCategory') %]
272
                                                                    <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>
281
                                                                    <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>
273
                                                                [% ELSE %]
282
                                                                [% ELSE %]
274
                                                                    <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>
283
                                                                    <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>
275
                                                                [% END %]
284
                                                                [% END %]
276
                                                            [% END %]
285
                                                            [% END %]
277
                                                        </select>
286
                                                        </select>
Lines 899-904 Link Here
899
                                        [% IF patron %]
908
                                        [% IF patron %]
900
                                            [% IF ( patron.category.effective_require_strong_password ) %]
909
                                            [% IF ( patron.category.effective_require_strong_password ) %]
901
                                                <p>Your password must contain at least [% patron.category.effective_min_password_length | html %] characters, including UPPERCASE, lowercase and numbers.</p>
910
                                                <p>Your password must contain at least [% patron.category.effective_min_password_length | html %] characters, including UPPERCASE, lowercase and numbers.</p>
911
                                            [% ELSIF ( passwordpolicy == 'complex') %]
912
                                                <p>Your password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</p>
913
                                            [% ELSIF ( passwordpolicy == 'alphanumeric') %]
914
                                                <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>
915
                                            [% ELSIF ( passwordpolicy == 'simplenumeric') %]
916
                                                <p>Your password can only contain digits 0-9 and must be at least [% patron.category.effective_min_password_length | html %] characters long.</p>
902
                                            [% ELSE %]
917
                                            [% ELSE %]
903
                                                <p>Your password must be at least [% patron.category.effective_min_password_length | html %] characters long.</p>
918
                                                <p>Your password must be at least [% patron.category.effective_min_password_length | html %] characters long.</p>
904
                                            [% END %]
919
                                            [% END %]
Lines 1234-1244 Link Here
1234
    [% UNLESS patron %]
1249
    [% UNLESS patron %]
1235
        var PWD_STRONG_MSG = _("Password must contain at least %s characters, including UPPERCASE, lowercase and numbers");
1250
        var PWD_STRONG_MSG = _("Password must contain at least %s characters, including UPPERCASE, lowercase and numbers");
1236
        var PWD_WEAK_MSG = _("Password must contain at least %s characters");
1251
        var PWD_WEAK_MSG = _("Password must contain at least %s characters");
1252
        var PWD_COMPLEX_MSG = _("Password must contain numbers, lower and uppercase characters and special characters and must be at least %s characters long.");
1253
        var PWD_ALPHA_MSG = _("Password must contain both numbers and non-special characters and must be at least %s characters long.");
1254
        var PWD_SIMPLE_MSG = _("Password can only contain digits 0-9 and must be at least %s characters long.");
1237
        $(document).ready(function() {
1255
        $(document).ready(function() {
1238
            var setPwdMessage = function() {
1256
            var setPwdMessage = function() {
1239
                var require_strong = $('select#borrower_categorycode option:selected').data('pwdStrong');
1257
                var require_strong = $('select#borrower_categorycode option:selected').data('pwdStrong');
1240
                var min_lenght = $('select#borrower_categorycode option:selected').data('pwdLength');
1258
                var min_lenght = $('select#borrower_categorycode option:selected').data('pwdLength');
1241
                $('#password_alert').html((require_strong?PWD_STRONG_MSG:PWD_WEAK_MSG).format(min_lenght));
1259
                var passwordpolicy = $('select#borrower_categorycode option:selected').data('pwdPolicy');
1260
                if(passwordpolicy){
1261
                    if(passwordpolicy == 'complex'){
1262
                        $('#password_alert').html((PWD_COMPLEX_MSG).format(min_lenght));
1263
                    }else if(passwordpolicy == 'alphanumeric'){
1264
                        $('#password_alert').html((PWD_ALPHA_MSG).format(min_lenght));
1265
                    }else{
1266
                        $('#password_alert').html((PWD_SIMPLE_MSG).format(min_lenght));
1267
                    }
1268
                }else{
1269
                    $('#password_alert').html((require_strong?PWD_STRONG_MSG:PWD_WEAK_MSG).format(min_lenght));
1270
                }
1242
            };
1271
            };
1243
            setPwdMessage();
1272
            setPwdMessage();
1244
            $('select#borrower_categorycode').change(setPwdMessage);
1273
            $('select#borrower_categorycode').change(setPwdMessage);
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt (-1 / +15 lines)
Lines 51-57 Link Here
51
                                [% IF password_has_whitespaces %]
51
                                [% IF password_has_whitespaces %]
52
                                    Password must not contain leading or trailing whitespaces.
52
                                    Password must not contain leading or trailing whitespaces.
53
                                [% END %]
53
                                [% END %]
54
54
                                [% IF ( complex_policy_mismatch ) %]
55
                                    Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.
56
                                [% END %]
57
                                [% IF ( alpha_policy_mismatch ) %]
58
                                    Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.
59
                                [% END %]
60
                                [% IF ( simple_policy_mismatch ) %]
61
                                    Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.
62
                                [% END %]
55
                                [% IF ( WrongPass ) %]
63
                                [% IF ( WrongPass ) %]
56
                                Your current password was entered incorrectly.  If this problem persists, please ask a librarian to reset your password for you.
64
                                Your current password was entered incorrectly.  If this problem persists, please ask a librarian to reset your password for you.
57
                                [% END %]
65
                                [% END %]
Lines 65-70 Link Here
65
                                <fieldset>
73
                                <fieldset>
66
                                    [% IF ( logged_in_user.category.effective_require_strong_password ) %]
74
                                    [% IF ( logged_in_user.category.effective_require_strong_password ) %]
67
                                        <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>
75
                                        <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>
76
                                    [% ELSIF ( password_policy == 'complex') %]
77
                                        <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>
78
                                    [% ELSIF ( password_policy == 'alphanumeric') %]
79
                                       <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>
80
                                    [% ELSIF ( password_policy == 'simplenumeric') %]
81
                                        <div class="alert alert-info">Your password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</div>
68
                                    [% ELSE %]
82
                                    [% ELSE %]
69
                                        <div class="alert alert-info">Your password must be at least [% logged_in_user.category.effective_min_password_length | html %] characters long.</div>
83
                                        <div class="alert alert-info">Your password must be at least [% logged_in_user.category.effective_min_password_length | html %] characters long.</div>
70
                                    [% END %]
84
                                    [% END %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-password-recovery.tt (+12 lines)
Lines 104-109 Link Here
104
                        [% ELSIF (errLinkNotValid) %]
104
                        [% ELSIF (errLinkNotValid) %]
105
                            The link you clicked is either invalid, or expired.
105
                            The link you clicked is either invalid, or expired.
106
                            <br/>Be sure you used the link from the email, or contact library staff for assistance.
106
                            <br/>Be sure you used the link from the email, or contact library staff for assistance.
107
                        [% ELSIF ( complex_policy_mismatch ) %]
108
                            <li>Password policy: password must contain numbers, lower and uppercase characters and special characters and must be at least [% minPasswordLength | html %] characters long.</li>
109
                        [% ELSIF ( alpha_policy_mismatch ) %]
110
                            <li>Password policy: password must contain both numbers and non-special characters and must be at least [% minPasswordLength | html %] characters long.</li>
111
                        [% ELSIF ( simple_policy_mismatch ) %]
112
                            <li>Password policy: password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</li>
107
                        [% END %]
113
                        [% END %]
108
                        </p>
114
                        </p>
109
                        <p>Please contact the library if you need further assistance.</p>
115
                        <p>Please contact the library if you need further assistance.</p>
Lines 140-145 Link Here
140
                                <fieldset class="brief">
146
                                <fieldset class="brief">
141
                                    [% IF ( RequireStrongPassword ) %]
147
                                    [% IF ( RequireStrongPassword ) %]
142
                                        <div class="alert alert-info">Your password must contain at least [% minPasswordLength | html %] characters, including UPPERCASE, lowercase and numbers.</div>
148
                                        <div class="alert alert-info">Your password must contain at least [% minPasswordLength | html %] characters, including UPPERCASE, lowercase and numbers.</div>
149
                                    [% ELSIF ( password_policy == 'complex') %]
150
                                        <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>
151
                                    [% ELSIF ( password_policy == 'alphanumeric') %]
152
                                        <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>
153
                                    [% ELSIF ( password_policy == 'simplenumeric') %]
154
                                        <div class="alert alert-info">Your password can only contain digits 0-9 and must be at least [% minPasswordLength | html %] characters long.</div>
143
                                    [% ELSE %]
155
                                    [% ELSE %]
144
                                        <div class="alert alert-info">Your password must be at least [% minPasswordLength | html %] characters long.</div>
156
                                        <div class="alert alert-info">Your password must be at least [% minPasswordLength | html %] characters long.</div>
145
                                    [% END %]
157
                                    [% END %]
(-)a/members/member-password.pl (-1 / +14 lines)
Lines 51-60 output_and_exit_if_error( $input, $cookie, $template, { module => 'members', log Link Here
51
51
52
my $category_type = $patron->category->category_type;
52
my $category_type = $patron->category->category_type;
53
53
54
my $passwordpolicy = $patron->category->passwordpolicy;
55
my $minPasswordLength = $patron->category->effective_min_password_length;
56
54
if ( ( $patron_id ne $loggedinuser ) && ( $category_type eq 'S' ) ) {
57
if ( ( $patron_id ne $loggedinuser ) && ( $category_type eq 'S' ) ) {
55
    push( @errors, 'NOPERMISSION' )
58
    push( @errors, 'NOPERMISSION' )
56
      unless ( $staffflags->{'superlibrarian'} || $staffflags->{'staffaccess'} );
59
      unless ( $staffflags->{'superlibrarian'} || $staffflags->{'staffaccess'} );
57
58
    # need superlibrarian for koha-conf.xml fakeuser.
60
    # need superlibrarian for koha-conf.xml fakeuser.
59
}
61
}
60
62
Lines 93-98 if ( $newpassword and not @errors) { Link Here
93
        elsif ( $_->isa('Koha::Exceptions::Password::Plugin') ) {
95
        elsif ( $_->isa('Koha::Exceptions::Password::Plugin') ) {
94
            push @errors, 'ERROR_from_plugin';
96
            push @errors, 'ERROR_from_plugin';
95
        }
97
        }
98
        elsif ( $_->isa('Koha::Exceptions::Password::SimplePolicy') ) {
99
            push @errors, 'ERROR_simple_policy_mismatch';
100
        }
101
        elsif ( $_->isa('Koha::Exceptions::Password::AlphaPolicy') ) {
102
            push @errors, 'ERROR_alpha_policy_mismatch';
103
        }
104
        elsif ( $_->isa('Koha::Exceptions::Password::ComplexPolicy') ) {
105
            push @errors, 'ERROR_complex_policy_mismatch';
106
        }
96
        else {
107
        else {
97
            push( @errors, 'BADUSERID' );
108
            push( @errors, 'BADUSERID' );
98
        }
109
        }
Lines 103-108 $template->param( Link Here
103
    patron      => $patron,
114
    patron      => $patron,
104
    destination => $destination,
115
    destination => $destination,
105
    csrf_token  => Koha::Token->new->generate_csrf({ session_id => scalar $input->cookie('CGISESSID'), }),
116
    csrf_token  => Koha::Token->new->generate_csrf({ session_id => scalar $input->cookie('CGISESSID'), }),
117
    password_policy => $passwordpolicy,
118
    minPasswordLength => $minPasswordLength,
106
);
119
);
107
120
108
if ( scalar(@errors) ) {
121
if ( scalar(@errors) ) {
(-)a/members/memberentry.pl (+9 lines)
Lines 189-194 unless ($category_type or !($categorycode)){ Link Here
189
}
189
}
190
$category_type="A" unless $category_type; # FIXME we should display a error message instead of a 500 error !
190
$category_type="A" unless $category_type; # FIXME we should display a error message instead of a 500 error !
191
191
192
my $passwordpolicy = ( $patron ) ? $patron->category->passwordpolicy : Koha::Patron::Categories->find($categorycode)->passwordpolicy;
193
$template->param(password_policy => $passwordpolicy);
194
195
my $minPasswordLength = Koha::Patron::Categories->find($categorycode)->effective_min_password_length;
196
$template->param("minPasswordLength" => $minPasswordLength);
197
192
# if a add or modify is requested => check validity of data.
198
# if a add or modify is requested => check validity of data.
193
%data = %$borrower_data if ($borrower_data);
199
%data = %$borrower_data if ($borrower_data);
194
200
Lines 389-394 if ($op eq 'save' || $op eq 'insert'){ Link Here
389
          push @errors, 'ERROR_password_too_short' if $error eq 'too_short';
395
          push @errors, 'ERROR_password_too_short' if $error eq 'too_short';
390
          push @errors, 'ERROR_password_too_weak' if $error eq 'too_weak';
396
          push @errors, 'ERROR_password_too_weak' if $error eq 'too_weak';
391
          push @errors, 'ERROR_password_has_whitespaces' if $error eq 'has_whitespaces';
397
          push @errors, 'ERROR_password_has_whitespaces' if $error eq 'has_whitespaces';
398
          push @errors, 'ERROR_complex_policy_mismatch' if $error eq 'complex_policy_mismatch';
399
          push @errors, 'ERROR_alpha_policy_mismatch' if $error eq 'alpha_policy_mismatch';
400
          push @errors, 'ERROR_simple_policy_mismatch' if $error eq 'simple_policy_mismatch';
392
      }
401
      }
393
  }
402
  }
394
403
(-)a/opac/opac-memberentry.pl (+14 lines)
Lines 39-44 use Koha::DateUtils; Link Here
39
use Koha::Libraries;
39
use Koha::Libraries;
40
use Koha::Patron::Attribute::Types;
40
use Koha::Patron::Attribute::Types;
41
use Koha::Patron::Attributes;
41
use Koha::Patron::Attributes;
42
use Koha::Patron::Categories;
42
use Koha::Patron::Images;
43
use Koha::Patron::Images;
43
use Koha::Patron::Modification;
44
use Koha::Patron::Modification;
44
use Koha::Patron::Modifications;
45
use Koha::Patron::Modifications;
Lines 118-123 foreach my $attr (@$attributes) { Link Here
118
    }
119
    }
119
}
120
}
120
121
122
my $categorycode = C4::Context->preference('PatronSelfRegistrationDefaultCategory');
123
my $category = Koha::Patron::Categories->find($categorycode);
124
my $passwordpolicy = $category->passwordpolicy;
125
my $minPasswordLength = $category->effective_min_password_length;
126
$template->param(
127
    password_policy => $passwordpolicy,
128
    minPasswordLength => $minPasswordLength
129
);
130
121
if ( $action eq 'create' ) {
131
if ( $action eq 'create' ) {
122
132
123
    my %borrower = ParseCgiForBorrower($cgi);
133
    my %borrower = ParseCgiForBorrower($cgi);
Lines 126-131 if ( $action eq 'create' ) { Link Here
126
136
127
    my @empty_mandatory_fields = (CheckMandatoryFields( \%borrower, $action ), CheckMandatoryAttributes( \%borrower, $attributes ) );
137
    my @empty_mandatory_fields = (CheckMandatoryFields( \%borrower, $action ), CheckMandatoryAttributes( \%borrower, $attributes ) );
128
    my $invalidformfields = CheckForInvalidFields(\%borrower);
138
    my $invalidformfields = CheckForInvalidFields(\%borrower);
139
129
    delete $borrower{'password2'};
140
    delete $borrower{'password2'};
130
    my $cardnumber_error_code;
141
    my $cardnumber_error_code;
131
    if ( !grep { $_ eq 'cardnumber' } @empty_mandatory_fields ) {
142
    if ( !grep { $_ eq 'cardnumber' } @empty_mandatory_fields ) {
Lines 481-486 sub CheckForInvalidFields { Link Here
481
              push @invalidFields, 'password_too_short' if $error eq 'too_short';
492
              push @invalidFields, 'password_too_short' if $error eq 'too_short';
482
              push @invalidFields, 'password_too_weak' if $error eq 'too_weak';
493
              push @invalidFields, 'password_too_weak' if $error eq 'too_weak';
483
              push @invalidFields, 'password_has_whitespaces' if $error eq 'has_whitespaces';
494
              push @invalidFields, 'password_has_whitespaces' if $error eq 'has_whitespaces';
495
              push @invalidFields, 'complex_policy_mismatch' if $error eq 'complex_policy_mismatch';
496
              push @invalidFields, 'alpha_policy_mismatch' if $error eq 'alpha_policy_mismatch';
497
              push @invalidFields, 'simple_policy_mismatch' if $error eq 'simple_policy_mismatch';
484
          }
498
          }
485
    }
499
    }
486
500
(-)a/opac/opac-passwd.pl (+13 lines)
Lines 27-32 use C4::Context; Link Here
27
use C4::Circulation;
27
use C4::Circulation;
28
use C4::Members;
28
use C4::Members;
29
use C4::Output;
29
use C4::Output;
30
use Koha::AuthUtils;
30
use Koha::Patrons;
31
use Koha::Patrons;
31
32
32
use Try::Tiny;
33
use Try::Tiny;
Lines 43-48 my ( $template, $borrowernumber, $cookie ) = get_template_and_user( Link Here
43
);
44
);
44
45
45
my $patron = Koha::Patrons->find( $borrowernumber );
46
my $patron = Koha::Patrons->find( $borrowernumber );
47
my $categorycode = $patron->category->categorycode;
48
my $passwordpolicy = $patron->category->passwordpolicy;
49
my $minPasswordLength = $patron->category->effective_min_password_length;
46
if ( $patron->category->effective_change_password ) {
50
if ( $patron->category->effective_change_password ) {
47
    if (   $query->param('Oldkey')
51
    if (   $query->param('Oldkey')
48
        && $query->param('Newkey')
52
        && $query->param('Newkey')
Lines 59-64 if ( $patron->category->effective_change_password ) { Link Here
59
                $template->param( 'passwords_mismatch'   => '1' );
63
                $template->param( 'passwords_mismatch'   => '1' );
60
            } else {
64
            } else {
61
                try {
65
                try {
66
                    Koha::AuthUtils::is_password_valid( $new_password, $categorycode );
62
                    $patron->set_password({ password => $new_password });
67
                    $patron->set_password({ password => $new_password });
63
                    $template->param( 'password_updated' => '1' );
68
                    $template->param( 'password_updated' => '1' );
64
                    $template->param( 'borrowernumber'   => $borrowernumber );
69
                    $template->param( 'borrowernumber'   => $borrowernumber );
Lines 70-75 if ( $patron->category->effective_change_password ) { Link Here
70
                        if $_->isa('Koha::Exceptions::Password::TooWeak');
75
                        if $_->isa('Koha::Exceptions::Password::TooWeak');
71
                    $error = 'password_has_whitespaces'
76
                    $error = 'password_has_whitespaces'
72
                        if $_->isa('Koha::Exceptions::Password::WhitespaceCharacters');
77
                        if $_->isa('Koha::Exceptions::Password::WhitespaceCharacters');
78
                    $error = 'simple_policy_mismatch'
79
                        if $_->isa('Koha::Exceptions::Password::SimplePolicy');
80
                    $error = 'alpha_policy_mismatch'
81
                        if $_->isa('Koha::Exceptions::Password::AlphaPolicy');
82
                    $error = 'complex_policy_mismatch'
83
                        if $_->isa('Koha::Exceptions::Password::ComplexPolicy');
73
                };
84
                };
74
            }
85
            }
75
        }
86
        }
Lines 105-110 $template->param( Link Here
105
    firstname  => $patron->firstname,
116
    firstname  => $patron->firstname,
106
    surname    => $patron->surname,
117
    surname    => $patron->surname,
107
    passwdview => 1,
118
    passwdview => 1,
119
    password_policy => $passwordpolicy,
120
    minPasswordLength => $minPasswordLength,
108
);
121
);
109
122
110
123
(-)a/opac/opac-password-recovery.pl (-1 / +21 lines)
Lines 7-12 use C4::Auth; Link Here
7
use C4::Koha;
7
use C4::Koha;
8
use C4::Output;
8
use C4::Output;
9
use C4::Context;
9
use C4::Context;
10
use Koha::AuthUtils;
10
use Koha::Patron::Password::Recovery
11
use Koha::Patron::Password::Recovery
11
  qw(SendPasswordRecoveryEmail ValidateBorrowernumber GetValidLinkInfo CompletePasswordRecovery DeleteExpiredPasswordRecovery);
12
  qw(SendPasswordRecoveryEmail ValidateBorrowernumber GetValidLinkInfo CompletePasswordRecovery DeleteExpiredPasswordRecovery);
12
use Koha::Patrons;
13
use Koha::Patrons;
Lines 153-158 if ( $query->param('sendEmail') || $query->param('resendEmail') ) { Link Here
153
elsif ( $query->param('passwordReset') ) {
154
elsif ( $query->param('passwordReset') ) {
154
    ( $borrower_number, $username ) = GetValidLinkInfo($uniqueKey);
155
    ( $borrower_number, $username ) = GetValidLinkInfo($uniqueKey);
155
156
157
    my $patron = Koha::Patrons->find($borrower_number);
158
    my $passwordpolicy = $patron->category->passwordpolicy;
159
    my $minPasswordLength = $patron->category->effective_min_password_length;
160
161
    $template->param(
162
        password_policy => $passwordpolicy,
163
        minPasswordLength => $minPasswordLength,
164
    );
165
156
    my $error;
166
    my $error;
157
    my $min_password_length = C4::Context->preference('minPasswordPreference');
167
    my $min_password_length = C4::Context->preference('minPasswordPreference');
158
    my $require_strong_password = C4::Context->preference('RequireStrongPassword');
168
    my $require_strong_password = C4::Context->preference('RequireStrongPassword');
Lines 183-188 elsif ( $query->param('passwordReset') ) { Link Here
183
            elsif ( $_->isa('Koha::Exceptions::Password::TooWeak') ) {
193
            elsif ( $_->isa('Koha::Exceptions::Password::TooWeak') ) {
184
                $error = 'password_too_weak';
194
                $error = 'password_too_weak';
185
            }
195
            }
196
            elsif ( $_->isa('Koha::Exceptions::Password::SimplePolicy') ) {
197
                $error = 'simple_policy_mismatch';
198
            }
199
            elsif ( $_->isa('Koha::Exceptions::Password::AlphaPolicy') ) {
200
                $error = 'alpha_policy_mismatch';
201
            }
202
            elsif ( $_->isa('Koha::Exceptions::Password::ComplexPolicy') ) {
203
                $error = 'complex_policy_mismatch';
204
            }
186
        };
205
        };
187
    }
206
    }
188
    if ( $error ) {
207
    if ( $error ) {
Lines 215-221 elsif ($uniqueKey) { #reset password form Link Here
215
        errLinkNotValid => $errLinkNotValid,
234
        errLinkNotValid => $errLinkNotValid,
216
        hasError        => ( $errLinkNotValid ? 1 : 0 ),
235
        hasError        => ( $errLinkNotValid ? 1 : 0 ),
217
        minPasswordLength => $borrower->category->effective_min_password_length,
236
        minPasswordLength => $borrower->category->effective_min_password_length,
218
        RequireStrongPassword => $borrower->category->effective_require_strong_password
237
        RequireStrongPassword => $borrower->category->effective_require_strong_password,
238
        password_policy => $borrower->category->passwordpolicy,
219
    );
239
    );
220
}
240
}
221
else {    #password recovery form (to send email)
241
else {    #password recovery form (to send email)
(-)a/t/db_dependent/AuthUtils.t (-6 / +89 lines)
Lines 32-57 $schema->storage->txn_begin; Link Here
32
my $category1 = $builder->build_object(
32
my $category1 = $builder->build_object(
33
    {
33
    {
34
        class => 'Koha::Patron::Categories',
34
        class => 'Koha::Patron::Categories',
35
        value => { min_password_length => 15, require_strong_password => 1 }
35
        value => { min_password_length => 15, require_strong_password => 1, passwordpolicy => '' }
36
    }
36
    }
37
);
37
);
38
my $category2 = $builder->build_object(
38
my $category2 = $builder->build_object(
39
    {
39
    {
40
        class => 'Koha::Patron::Categories',
40
        class => 'Koha::Patron::Categories',
41
        value => { min_password_length => 5, require_strong_password => undef }
41
        value => { min_password_length => 5, require_strong_password => undef, passwordpolicy => '' }
42
    }
42
    }
43
);
43
);
44
my $category3 = $builder->build_object(
44
my $category3 = $builder->build_object(
45
    {
45
    {
46
        class => 'Koha::Patron::Categories',
46
        class => 'Koha::Patron::Categories',
47
        value => { min_password_length => undef, require_strong_password => 1 }
47
        value => { min_password_length => undef, require_strong_password => 1, passwordpolicy => '' }
48
    }
48
    }
49
);
49
);
50
my $category4 = $builder->build_object(
50
my $category4 = $builder->build_object(
51
    {
51
    {
52
        class => 'Koha::Patron::Categories',
52
        class => 'Koha::Patron::Categories',
53
        value =>
53
        value =>
54
          { min_password_length => undef, require_strong_password => undef }
54
          { min_password_length => undef, require_strong_password => undef, passwordpolicy => '' }
55
    }
55
    }
56
);
56
);
57
57
Lines 63-70 my $p_15l_weak = '0123456789abcdf'; Link Here
63
my $p_5l_strong  = 'Abc12';
63
my $p_5l_strong  = 'Abc12';
64
my $p_15l_strong = '0123456789AbCdF';
64
my $p_15l_strong = '0123456789AbCdF';
65
65
66
# Password policy simplenumeric
67
my $category_simple = $builder->build_object({
68
    class => 'Koha::Patron::Categories',
69
    value  => { min_password_length => 4, passwordpolicy => 'simplenumeric' },
70
});
71
72
# Password policy alphanumeric
73
my $category_alpha = $builder->build_object({
74
    class => 'Koha::Patron::Categories',
75
    value  => { min_password_length => 5, passwordpolicy => 'alphanumeric' },
76
});
77
78
# Password policy complex
79
my $category_complex = $builder->build_object({
80
    class => 'Koha::Patron::Categories',
81
    value  => { min_password_length => 6, passwordpolicy => 'complex' },
82
});
83
66
subtest 'is_password_valid for category' => sub {
84
subtest 'is_password_valid for category' => sub {
67
    plan tests => 15;
85
    plan tests => 16;
68
86
69
    my ( $is_valid, $error );
87
    my ( $is_valid, $error );
70
88
Lines 121-130 subtest 'is_password_valid for category' => sub { Link Here
121
    'Koha::Exceptions::Password::NoCategoryProvided',
139
    'Koha::Exceptions::Password::NoCategoryProvided',
122
      'Category should always be provided';
140
      'Category should always be provided';
123
141
142
    subtest 'password policies' => sub {
143
144
        t::lib::Mocks::mock_preference('RequireStrongPassword', 0);
145
        t::lib::Mocks::mock_preference('minPasswordLength', 4);
146
147
        #test simplenumeric password policy
148
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '1234', $category_simple );
149
        is ( $is_valid, 1, 'simplenumeric password should contain only numbers' );
150
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A123', $category_simple );
151
        is ( $is_valid, 0, 'simplenumeric password should not contain alphabets' );
152
        is($error, 'simple_policy_mismatch', 'error "simple_policy_mismatch" raised');
153
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '!234', $category_simple );
154
        is ( $is_valid, 0, 'simplenumeric password should not contain non-special characters' );
155
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '123', $category_simple );
156
        is ( $is_valid, 0, 'simplenumeric password follows "min_password_length" value' );
157
158
        #test alphanumeric
159
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A1234', $category_alpha );
160
        is ( $is_valid, 1, 'alphanumeric password should contain both numbers and non-special characters' );
161
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( '12345', $category_alpha );
162
        is ( $is_valid, 0, 'alphanumeric password must contain at least one uppercase character' );
163
        is($error, 'alpha_policy_mismatch', 'error "alpha_policy_mismatch" raised');
164
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A123', $category_alpha );
165
        is ( $is_valid, 0, 'alphanumeric password follows "min_password_length" value');
166
167
        #test complex
168
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'As!123', $category_complex );
169
        is ( $is_valid, 1, 'complex password should contain numbers, lower and uppercase characters and special characters' );
170
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'A12345', $category_complex );
171
        is ( $is_valid, 0, 'complex password must contain numbers, lower and uppercase characters and special characters' );
172
        is($error, 'complex_policy_mismatch', 'error "complex_policy_mismatch" raised');
173
        ( $is_valid, $error ) = Koha::AuthUtils::is_password_valid( 'As!12', $category_complex );
174
        is ( $is_valid, 0, 'complex password follows "min_password_length" value' );
175
    }
124
};
176
};
125
177
126
subtest 'generate_password for category' => sub {
178
subtest 'generate_password for category' => sub {
127
    plan tests => 5;
179
    plan tests => 6;
128
180
129
    my ( $is_valid, $error );
181
    my ( $is_valid, $error );
130
182
Lines 159-164 subtest 'generate_password for category' => sub { Link Here
159
    'Koha::Exceptions::Password::NoCategoryProvided',
211
    'Koha::Exceptions::Password::NoCategoryProvided',
160
      'Category should always be provided';
212
      'Category should always be provided';
161
213
214
    subtest 'generate_password with password policies' => sub {
215
216
        t::lib::Mocks::mock_preference('RequireStrongPassword', 0);
217
        t::lib::Mocks::mock_preference('minPasswordLength', 4);
218
219
        my $all_valid = 1;
220
221
        #simplenumeric
222
        for ( 1 .. 10 ) {
223
            my $password = Koha::AuthUtils::generate_password( $category_simple );;
224
            my ( $is_valid, undef ) = Koha::AuthUtils::is_password_valid( $password, $category_simple );
225
            $all_valid = 0 unless $is_valid;
226
        }
227
        is ( $all_valid, 1, 'generate_password should generate valid passwords with simplenumeric policy' );
228
229
        #alphanumeric
230
        for ( 1 .. 10 ) {
231
            my $password = Koha::AuthUtils::generate_password( $category_alpha );
232
            my ( $is_valid, undef ) = Koha::AuthUtils::is_password_valid( $password, $category_alpha );
233
            $all_valid = 0 unless $is_valid;
234
        }
235
        is ( $all_valid, 1, 'generate_password should generate valid passwords with alphanumeric policy' );
236
237
        #complex
238
        for ( 1 .. 10 ) {
239
            my $password = Koha::AuthUtils::generate_password( $category_complex );
240
            my ( $is_valid, undef ) = Koha::AuthUtils::is_password_valid( $password, $category_complex );
241
            $all_valid = 0 unless $is_valid;
242
        }
243
        is ( $all_valid, 1, 'generate_password should generate valid passwords with complex policy' );
244
    }
162
};
245
};
163
246
164
$schema->storage->txn_rollback;
247
$schema->storage->txn_rollback;
(-)a/t/db_dependent/api/v1/patrons_password.t (-2 / +80 lines)
Lines 39-49 my $t = Test::Mojo->new('Koha::REST::V1'); Link Here
39
39
40
subtest 'set() (authorized user tests)' => sub {
40
subtest 'set() (authorized user tests)' => sub {
41
41
42
    plan tests => 21;
42
    plan tests => 22;
43
43
44
    $schema->storage->txn_begin;
44
    $schema->storage->txn_begin;
45
45
46
    my ( $patron, $session ) = create_user_and_session({ authorized => 1 });
46
    my ( $patron, $session ) = create_user_and_session({ authorized => 1 });
47
    $patron->category->update({ passwordpolicy => ''});
47
48
48
    t::lib::Mocks::mock_preference( 'minPasswordLength',     3 );
49
    t::lib::Mocks::mock_preference( 'minPasswordLength',     3 );
49
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
50
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
Lines 118-123 subtest 'set() (authorized user tests)' => sub { Link Here
118
    $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
119
    $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
119
    $t->request_ok($tx)->status_is(200)->json_is('');
120
    $t->request_ok($tx)->status_is(200)->json_is('');
120
121
122
    subtest 'password policies' => sub {
123
124
      plan tests => 18;
125
126
      t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0);
127
      t::lib::Mocks::mock_preference( 'minPasswordLength', 4 );
128
129
      # simple policy
130
      $patron->category->update({ passwordpolicy => 'simple'});
131
      $new_password = '1234';
132
      $tx
133
            = $t->ua->build_tx( POST => "/api/v1/patrons/"
134
              . $patron->id
135
              . "/password" => json => { password => $new_password, password_2 => $new_password } );
136
137
      $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
138
      $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
139
      $t->request_ok($tx)->status_is(200)->json_is('');
140
141
      $new_password = '123A';
142
      $tx
143
            = $t->ua->build_tx( POST => "/api/v1/patrons/"
144
              . $patron->id
145
              . "/password" => json => { password => $new_password, password_2 => $new_password } );
146
147
      $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
148
      $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
149
      $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password does not match simplenumeric passwordpolicy]' });
150
151
      # alphanumeric policy
152
      $patron->category->update({ passwordpolicy => 'alphanumeric'});
153
      $new_password = '123A5';
154
      $tx
155
            = $t->ua->build_tx( POST => "/api/v1/patrons/"
156
              . $patron->id
157
              . "/password" => json => { password => $new_password, password_2 => $new_password } );
158
159
      $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
160
      $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
161
      $t->request_ok($tx)->status_is(200)->json_is('');
162
163
      $new_password = '12345';
164
      $tx
165
            = $t->ua->build_tx( POST => "/api/v1/patrons/"
166
              . $patron->id
167
              . "/password" => json => { password => $new_password, password_2 => $new_password } );
168
169
      $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
170
      $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
171
      $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password does not match alphanumeric passwordpolicy]' });
172
173
      # complex policy
174
      $patron->category->update({ passwordpolicy => 'complex'});
175
      $new_password = 'As!123';
176
      $tx
177
            = $t->ua->build_tx( POST => "/api/v1/patrons/"
178
              . $patron->id
179
              . "/password" => json => { password => $new_password, password_2 => $new_password } );
180
181
      $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
182
      $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
183
      $t->request_ok($tx)->status_is(200)->json_is('');
184
185
      $new_password = '123A5';
186
      $tx
187
            = $t->ua->build_tx( POST => "/api/v1/patrons/"
188
              . $patron->id
189
              . "/password" => json => { password => $new_password, password_2 => $new_password } );
190
191
      $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
192
      $tx->req->env( { REMOTE_ADDR => '127.0.0.1' } );
193
      $t->request_ok($tx)->status_is(400)->json_is({ error => '[Password does not match complex passwordpolicy]' });
194
195
196
    };
197
121
    $schema->storage->txn_rollback;
198
    $schema->storage->txn_rollback;
122
};
199
};
123
200
Lines 128-133 subtest 'set_public() (unprivileged user tests)' => sub { Link Here
128
    $schema->storage->txn_begin;
205
    $schema->storage->txn_begin;
129
206
130
    my ( $patron, $session ) = create_user_and_session({ authorized => 0 });
207
    my ( $patron, $session ) = create_user_and_session({ authorized => 0 });
208
    $patron->category->update({ passwordpolicy => ''});
209
131
    my $other_patron = $builder->build_object({ class => 'Koha::Patrons' });
210
    my $other_patron = $builder->build_object({ class => 'Koha::Patrons' });
132
211
133
    # Enable the public API
212
    # Enable the public API
134
- 

Return to bug 12617