From c6a8428aa6ee29e7d84adddf6d59c0c380da75d9 Mon Sep 17 00:00:00 2001
From: Emmi Takkinen <emmi.takkinen@outlook.com>
Date: Mon, 20 Jul 2020 10:48:01 +0300
Subject: [PATCH 3/3] Bug 12617: Add new regex patterns to password_check.inc

This patch adds new regex patterns to follow into password_check.inc
files.

To test:
1. Create or find existing patron.
2. Set or edit invalid password for patron in OPAC and staff interfaces:
	a) create and edit patron pages
	b) 'Change password' pages
3. Confirm alert text next to input field is displayed and it follows
set passwordpolicy.

Sponsored-by: Koha-Suomi Oy
---
 .../prog/en/includes/password_check.inc       | 42 ++++++++++++++++--
 .../en/modules/members/member-password.tt     |  2 +-
 .../prog/en/modules/members/memberentrygen.tt |  2 +-
 .../bootstrap/en/includes/password_check.inc  | 43 +++++++++++++++++--
 .../bootstrap/en/modules/opac-memberentry.tt  |  2 +-
 .../bootstrap/en/modules/opac-passwd.tt       |  2 +-
 members/memberentry.pl                        |  1 +
 7 files changed, 83 insertions(+), 11 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/password_check.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/password_check.inc
index 2960da66ee..1a702106d5 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/password_check.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/password_check.inc
@@ -7,26 +7,60 @@
     var category_selector = "[% category_selector | html %]";
     var STRONG_MSG = _("Password must contain at least %s characters, including UPPERCASE, lowercase and numbers");
     var WEAK_MSG = _("Password must contain at least %s characters");
-
+    var COMPLEX_MSG = _("Password must contain numbers, lower and uppercase characters and special characters and must be at least %s characters long.");
+    var ALPHA_MSG = _("Password must contain both numbers and non-special characters and must be at least %s characters long.");
+    var SIMPLE_MSG = _("Password can only contain digits 0-9 and must be at least %s characters long.");
     if(category_selector && $('select'+category_selector).length) {
         jQuery.validator.addMethod("password_strong", function(value, element){
             var require_strong = $('select'+category_selector+' option:selected').data('pwdStrong');
             var min_lenght = $('select'+category_selector+' option:selected').data('pwdLength');
-            var regex_text = require_strong?"(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{"+min_lenght+",}":".{"+min_lenght+",}";
+            var passwordpolicy = $('select'+category_selector+' option:selected').data('pwdPolicy');
+            var regex_text = '';
+            if(passwordpolicy){
+                if(passwordpolicy == 'complex'){
+                    regex_text = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\^&*(){}[\\]<>?/|\-]).{"+min_lenght+",}";
+                }else if(passwordpolicy == 'alphanumeric'){
+                    regex_text = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{"+min_lenght+",}";
+                }else{
+                    regex_text = "(?=.*\\d)(?=.*[0-9]).{"+min_lenght+",}";
+                }
+            }else{
+                regex_text = require_strong?"(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{"+min_lenght+",}":".{"+min_lenght+",}";
+            }
             var pattern_regex = new RegExp(regex_text);
             return this.optional(element) || value == '****' || pattern_regex.test(value);
         }, function(unused, element) {
             var require_strong = $('select'+category_selector+' option:selected').data('pwdStrong');
             var min_lenght = $('select'+category_selector+' option:selected').data('pwdLength');
-            return (require_strong?STRONG_MSG:WEAK_MSG).format(min_lenght)
+            var passwordpolicy = $('select'+category_selector+' option:selected').data('pwdPolicy');
+            if(passwordpolicy){
+                if(passwordpolicy == 'complex'){
+                    return (COMPLEX_MSG).format(min_lenght);
+                }else if(passwordpolicy == 'alphanumeric'){
+                    return (ALPHA_MSG).format(min_lenght);
+                }else{
+                    return (SIMPLE_MSG).format(min_lenght);
+                }
+            }else{
+                return (require_strong?STRONG_MSG:WEAK_MSG).format(min_lenght)
+            }
         });
     } else {
         [% IF RequireStrongPassword %]
             pwd_title = STRONG_MSG.format([% minPasswordLength | html %]);
             pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{[% minPasswordLength | html %],}/;
-        [% ELSIF minPasswordLength %]
+        [% ELSIF minPasswordLength && !password_policy %]
             pwd_title = WEAK_MSG.format([% minPasswordLength | html %]);
             pattern_regex = /.{[% minPasswordLength | html %],}/;
+         [% ELSIF password_policy == 'simplenumeric' %]
+            pwd_title = SIMPLE_MSG.format([% minPasswordLength | html %]);
+            pattern_regex = /(?=.*\d)([0-9]){[% minPasswordLength | html %],}/;
+        [% ELSIF password_policy == 'alphanumeric' %]
+            pwd_title = ALPHA_MSG.format([% minPasswordLength | html %]);;
+            pattern_regex = /(?=.*\d)(?=.*[a-zA-Z])([a-zA-Z0-9]){[% minPasswordLength | html %],}/;
+        [% ELSIF password_policy == 'complex' %]
+            pwd_title = COMPLEX_MSG.format([% minPasswordLength | html %]);
+            pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\^&*(){}[\\]<>?/|\-])([a-zA-Z0-9!@#$%\^&*(){}[\\]<>?/|\-]){[% minPasswordLength | html %],}/;
         [% END %]
         jQuery.validator.addMethod("password_strong", function(value, element){
             return this.optional(element) || value == '****' || pattern_regex.test(value);
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt
index d9072608bc..b35aa8bad6 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-password.tt
@@ -175,7 +175,7 @@
             });
         });
     </script>
-    [% PROCESS 'password_check.inc' new_password => 'newpassword', minPasswordLength => patron.category.effective_min_password_length, RequireStrongPassword => patron.category.effective_require_strong_password %]
+    [% PROCESS 'password_check.inc' new_password => 'newpassword', minPasswordLength => patron.category.effective_min_password_length, RequireStrongPassword => patron.category.effective_require_strong_password, password_policy => patron.category.passwordpolicy %]
 [% END %]
 
 [% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
index 1114dcad43..fa3517647b 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
@@ -1747,7 +1747,7 @@ legend:hover {
     </script>
     [% Asset.js("js/members.js") | $raw %]
     [% Asset.js("js/messaging-preference-form.js") | $raw %]
-    [% PROCESS 'password_check.inc' new_password => 'password', category_selector => '#categorycode_entry', minPasswordLength => patron.category.effective_min_password_length, RequireStrongPassword => patron.category.effective_require_strong_password %]
+    [% PROCESS 'password_check.inc' new_password => 'password', category_selector => '#categorycode_entry', minPasswordLength => patron.category.effective_min_password_length, RequireStrongPassword => patron.category.effective_require_strong_password, password_policy => patron.category.passwordpolicy %]
 [% END %]
 
 [% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/password_check.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/password_check.inc
index 338d07bbbb..95d07cd28d 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/password_check.inc
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/password_check.inc
@@ -4,33 +4,70 @@
     var pattern_title = "";
     var new_password_node_name = "[% new_password | html %]";
     var category_selector = "[% category_selector | html %]";
+    var password_policy = "[% password_policy | html %]";
     var STRONG_MSG = _("Password must contain at least %s characters, including UPPERCASE, lowercase and numbers");
     var WEAK_MSG = _("Password must contain at least %s characters");
+    var COMPLEX_MSG = _("Password must contain numbers, lower and uppercase characters and special characters and must be at least %s characters long.");
+    var ALPHA_MSG = _("Password must contain both numbers and non-special characters and must be at least %s characters long.");
+    var SIMPLE_MSG = _("Password can only contain digits 0-9 and must be at least %s characters long.");
 
     if(category_selector && $('select'+category_selector).length) {
         jQuery.validator.addMethod("password_strong", function(value, element){
             var require_strong = $('select'+category_selector+' option:selected').data('pwdStrong');
             var min_lenght = $('select'+category_selector+' option:selected').data('pwdLength');
-            var regex_text = require_strong?"(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{"+min_lenght+",}":".{"+min_lenght+",}";
+            var passwordpolicy = $('select'+category_selector+' option:selected').data('pwdPolicy');
+            var regex_text = '';
+            if(passwordpolicy){
+                if(passwordpolicy == 'complex'){
+                    regex_text = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\^&*(){}[\\]<>?/|\-]).{"+min_lenght+",}";
+                }else if(passwordpolicy == 'alphanumeric'){
+                    regex_text = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{"+min_lenght+",}";
+                }else{
+                    regex_text = "(?=.*\\d)(?=.*[0-9]).{"+min_lenght+",}";
+                }
+            }else{
+                regex_text = require_strong?"(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{"+min_lenght+",}":".{"+min_lenght+",}";
+            }
             var pattern_regex = new RegExp(regex_text);
             return this.optional(element) || pattern_regex.test(value);
         }, function(unused, element) {
             var require_strong = $('select'+category_selector+' option:selected').data('pwdStrong');
             var min_lenght = $('select'+category_selector+' option:selected').data('pwdLength');
-            return (require_strong?STRONG_MSG:WEAK_MSG).format(min_lenght)
+            var passwordpolicy = $('select'+category_selector+' option:selected').data('pwdPolicy');
+            if(passwordpolicy){
+                if(passwordpolicy == 'complex'){
+                    return (COMPLEX_MSG).format(min_lenght);
+                }else if(passwordpolicy == 'alphanumeric'){
+                    return (ALPHA_MSG).format(min_lenght);
+                }else{
+                    return (SIMPLE_MSG).format(min_lenght);
+                }
+            }else{
+                return (require_strong?STRONG_MSG:WEAK_MSG).format(min_lenght)
+            }
         });
     } else {
         [% IF RequireStrongPassword %]
             pwd_title = STRONG_MSG.format([% minPasswordLength | html %]);
             pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{[% minPasswordLength | html %],}/;
-        [% ELSIF minPasswordLength %]
+        [% ELSIF minPasswordLength && !password_policy %]
             pwd_title = WEAK_MSG.format([% minPasswordLength | html %]);
             pattern_regex = /.{[% minPasswordLength | html %],}/;
+        [% ELSIF password_policy == 'simplenumeric' %]
+            pwd_title = SIMPLE_MSG.format([% minPasswordLength | html %]);
+            pattern_regex = /(?=.*\d)([0-9]){[% minPasswordLength | html %],}/;
+        [% ELSIF password_policy == 'alphanumeric' %]
+            pwd_title = ALPHA_MSG.format([% minPasswordLength | html %]);;
+            pattern_regex = /(?=.*\d)(?=.*[a-zA-Z])([a-zA-Z0-9]){[% minPasswordLength | html %],}/;
+        [% ELSIF password_policy == 'complex' %]
+            pwd_title = COMPLEX_MSG.format([% minPasswordLength | html %]);
+            pattern_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\^&*(){}[\]<>?/|\-])([a-zA-Z0-9!@#$%\^&*(){}[\]<>?/|\-]){[% minPasswordLength | html %],}/;
         [% END %]
         jQuery.validator.addMethod("password_strong", function(value, element){
             return this.optional(element) || value == '****' || pattern_regex.test(value);
         }, pwd_title);
     }
+
     jQuery.validator.addMethod("password_no_spaces", function(value, element){
         return ( this.optional(element) || !value.match(/^\s/) && !value.match(/\s$/) );
     }, _("Password contains leading and/or trailing spaces"));
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt
index 7db4fcc6d1..7f2043600e 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt
@@ -1275,6 +1275,6 @@
     [% END %]
     //]]>
     </script>
-    [% PROCESS 'password_check.inc' new_password => 'borrower_password', category_selector => '#borrower_categorycode', RequireStrongPassword => patron.category.effective_require_strong_password, minPasswordLength => patron.category.effective_min_password_length %]
+    [% PROCESS 'password_check.inc' new_password => 'borrower_password', category_selector => '#borrower_categorycode', RequireStrongPassword => patron.category.effective_require_strong_password, minPasswordLength => patron.category.effective_min_password_length, password_policy => patron.category.passwordpolicy %]
 
 [% END %]
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt
index 4c05a7e433..4e43a23c2c 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-passwd.tt
@@ -126,7 +126,7 @@
 [% INCLUDE 'opac-bottom.inc' %]
 [% BLOCK jsinclude %]
     [% Asset.js("lib/jquery/plugins/jquery.validate.min.js") | $raw %]
-    [% PROCESS 'password_check.inc' new_password => 'Newkey', minPasswordLength => logged_in_user.category.effective_min_password_length, RequireStrongPassword => logged_in_user.category.effective_require_strong_password %]
+    [% PROCESS 'password_check.inc' new_password => 'Newkey', minPasswordLength => logged_in_user.category.effective_min_password_length, RequireStrongPassword => logged_in_user.category.effective_require_strong_password, password_policy => logged_in_user.category.passwordpolicy %]
     <script>
         $(document).ready(function() {
             $("#mainform").validate({
diff --git a/members/memberentry.pl b/members/memberentry.pl
index 6cfe3eb36c..db170f8e1b 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -693,6 +693,7 @@ foreach my $category_type (qw(C A S P I X)) {
             'categoryname' => $patron_category->description,
             'effective_min_password_length' => $patron_category->effective_min_password_length,
             'effective_require_strong_password' => $patron_category->effective_require_strong_password,
+            'passwordpolicy' => $patron_category->passwordpolicy,
             'categorycodeselected' =>
               ( defined($categorycode) && $patron_category->categorycode eq $categorycode ),
           };
-- 
2.17.1