[Rt-commit] rt branch, 4.6/password-complexity, created. rt-4.4.1-118-g8d7a9e6

Aaron Kondziela aaron at bestpractical.com
Mon Sep 26 22:02:38 EDT 2016


The branch, 4.6/password-complexity has been created
        at  8d7a9e693090bb6be580a1c4a961323fa413e133 (commit)

- Log -----------------------------------------------------------------
commit 8d7a9e693090bb6be580a1c4a961323fa413e133
Author: Aaron Kondziela <aaron at bestpractical.com>
Date:   Mon Sep 26 21:50:28 2016 -0400

    Improve password complexity configuration
    
    This expands the basic MinimumPasswordLength configuration option into a
    set of options under the new PasswordPolicy key. The new options allow
    checks for a minimum number of various classes of character.
    
    The web interface password fields are longer, to encourage use of a long
    passphrase. Some basic guidance for selecting a good password is displayed.
    The password requirements, as configured, are displayed to the user when
    they are entering a new password.
    
    Fixes: T#161950

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index bf9f01e..68cf655 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2454,14 +2454,43 @@ requirements.
 
 Set($WebHttpOnlyCookies, 1);
 
-=item C<$MinimumPasswordLength>
-
-C<$MinimumPasswordLength> defines the minimum length for user
-passwords. Setting it to 0 disables this check.
-
-=cut
-
-Set($MinimumPasswordLength, 5);
+=item C<%PasswordPolicy>
+
+C<%PasswordPolicy> sets the requirements for a user's password. The options
+and their default values are:
+
+    MinimumLength => 8,
+    Uppercase => 0,
+    Lowercase => 0,
+    Digits => 0,
+    Symbols => 0,
+    UppercaseRegex => qr/\p{XPosixUpper}/,
+    LowercaseRegex => qr/\p{XPosixLower}/,
+    DigitsRegex => qr/\p{XPosixDigit}/,
+    SymbolsRegex => qr/\p{XPosixPunct}/,
+
+For C<Uppercase>, C<Lowercase>, C<Digits>, and C<Symbols>, set to zero to
+disable that specific check. The value indicates the number of each class
+of characters that must be present in a valid password. If other than zero,
+the web interface will inform the user of each requirement in the web
+interface.
+
+The regex definitions are exposed as an aid to localisation. Don't change
+these values unless you have a specific need to do so.
+
+=cut
+
+Set(%PasswordPolicy, (
+    MinimumLength => 8,
+    Uppercase => 0,
+    Lowercase => 0,
+    Digits => 0,
+    Symbols => 0,
+    UppercaseRegex => qr/\p{XPosixUpper}/,
+    LowercaseRegex => qr/\p{XPosixLower}/,
+    DigitsRegex => qr/\p{XPosixDigit}/,
+    SymbolsRegex => qr/\p{XPosixPunct}/,
+));
 
 =back
 
diff --git a/lib/RT/Installer.pm b/lib/RT/Installer.pm
index 5b43ace..40e0f06 100644
--- a/lib/RT/Installer.pm
+++ b/lib/RT/Installer.pm
@@ -138,12 +138,6 @@ my %Meta = (
             Hints => 'RT will use this string to uniquely identify your installation and looks for it in the subject of emails to decide what ticket a message applies to.  We recommend that you set this to your internet domain. (ex: example.com)' #loc
         },
     },
-    MinimumPasswordLength => {
-        Widget          => '/Widgets/Form/Integer',
-        WidgetArguments => {
-            Description => 'Minimum password length',    #loc
-        },
-    },
     Password => {
         SkipWrite       => 1,
         Widget          => '/Widgets/Form/String',
diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index 29de4ae..c4e8c07 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -283,7 +283,8 @@ sub ValidateName {
 =head2 ValidatePassword STRING
 
 Returns either (0, "failure reason") or 1 depending on whether the given
-password is valid.
+password is valid. Tests for vailidity are configured by the C<%PasswordPolicy>
+settings.
 
 =cut
 
@@ -291,8 +292,23 @@ sub ValidatePassword {
     my $self = shift;
     my $password = shift;
 
-    if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
-        return ( 0, $self->loc("Password needs to be at least [quant,_1,character,characters] long", RT->Config->Get('MinimumPasswordLength')) );
+    if ( length($password) < (my $limit = RT->Config->Get('PasswordPolicy')->{'MinimumLength'}) ) {
+        return ( 0, $self->loc("Password needs to be at least [quant,_1,character,characters] long", $limit) );
+    }
+
+    my @checks = (
+        ['Uppercase', 'UppercaseRegex', 'Password must include at least [quant,_1,uppercase character,uppercase characters]'],
+        ['Lowercase', 'LowercaseRegex', 'Password must include at least [quant,_1,lowercase character,lowercase characters]'],
+        ['Digits', 'DigitsRegex', 'Password must include at least [quant,_1,digit,digits]'],
+        ['Symbols', 'SymbolsRegex', 'Password must include at least [quant,_1,punctuation mark or symbol,punctuation marks or symbols]'],
+    );
+
+    foreach my $check (@checks) {
+        if ( my $limit = RT->Config->Get('PasswordPolicy')->{@$check[0]} ) {
+            my $re = RT->Config->Get('PasswordPolicy')->{@$check[1]};
+            my $count = () = $password =~ m/$re/g;
+            return ( 0, $self->loc(@$check[2], $limit) ) if $count < $limit;
+        }
     }
 
     return 1;
@@ -849,9 +865,8 @@ sub SetRandomPassword {
         return ( 0, $self->loc("Permission Denied") );
     }
 
-
-    my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ?  RT->Config->Get('MinimumPasswordLength') : 6);
-    my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ?  RT->Config->Get('MinimumPasswordLength') : 8);
+    my $min = ( RT->Config->Get('PasswordPolicy')->{'MinimumLength'} > 6 ?  RT->Config->Get('PasswordPolicy')->{'MinimumLength'} : 6);
+    my $max = ( RT->Config->Get('PasswordPolicy')->{'MinimumLength'} > 8 ?  RT->Config->Get('PasswordPolicy')->{'MinimumLength'} : 8);
 
     my $pass = $self->GenerateRandomPassword( $min, $max) ;
 
diff --git a/sbin/rt-setup-database.in b/sbin/rt-setup-database.in
index 951d7f6..d32d3b5 100644
--- a/sbin/rt-setup-database.in
+++ b/sbin/rt-setup-database.in
@@ -114,7 +114,7 @@ if ( $args{'root-password-file'} ) {
       or die "Couldn't open 'args{'root-password-file'}' for reading: $!";
     $root_password = <$fh>;
     chomp $root_password;
-    my $min_length = RT->Config->Get('MinimumPasswordLength');
+    my $min_length = RT->Config->Get('PasswordPolicy')->{'MinimumLength'};
     if ($min_length) {
         die
 "password needs to be at least $min_length long, please check file '$args{'root-password-file'}'"
diff --git a/share/html/Admin/Tools/Configuration.html b/share/html/Admin/Tools/Configuration.html
index 4535827..f7b9731 100644
--- a/share/html/Admin/Tools/Configuration.html
+++ b/share/html/Admin/Tools/Configuration.html
@@ -87,7 +87,7 @@ foreach my $key ( RT->Config->Options( Overridable => undef, Sorted => 0 ) ) {
 <tr class="<% $index_conf%2 ? 'oddline' : 'evenline'%>">
 <td class="collection-as-table"><% $key %></td>
 <td class="collection-as-table">
-% if ( $key =~ /Password/i and $key !~ /MinimumPasswordLength|AllowLoginPasswordAutoComplete/ ) {
+% if ( $key =~ /Password/i and $key !~ /PasswordPolicy|AllowLoginPasswordAutoComplete/ ) {
 <em><% loc('Password not printed' ) %></em>\
 % } else {
 <% stringify($val) |n %>\
diff --git a/share/html/Elements/EditPassword b/share/html/Elements/EditPassword
index e074d7a..5b2d08f 100644
--- a/share/html/Elements/EditPassword
+++ b/share/html/Elements/EditPassword
@@ -53,18 +53,18 @@
 % if ( $cond{'RequireCurrent'} ) {
 <tr>
 <td class="label"><&|/l, $session{'CurrentUser'}->Name()&>[_1]'s current password</&>:</td>
-<td class="value"><input type="password" name="<% $Name[0] %>" size="16" autocomplete="off" /></td>
+<td class="value"><input type="password" name="<% $Name[0] %>" size="40" autocomplete="off" /></td>
 </tr>
 % }
 
 <tr>
 <td class="label"><&|/l&>New password</&>:</td>
-<td class="value"><input type="password" name="<% $Name[1] %>" size="16" autocomplete="off" /></td>
+<td class="value"><input type="password" name="<% $Name[1] %>" size="40" autocomplete="off" /></td>
 </tr>
 
 <tr>
 <td class="label"><&|/l&>Retype Password</&>:</td>
-<td class="value"><input type="password" name="<% $Name[2] %>" size="16" autocomplete="off" /></td>
+<td class="value"><input type="password" name="<% $Name[2] %>" size="40" autocomplete="off" /></td>
 </tr>
 
 </table>
diff --git a/share/html/Prefs/AboutMe.html b/share/html/Prefs/AboutMe.html
index d83cc39..749a281 100644
--- a/share/html/Prefs/AboutMe.html
+++ b/share/html/Prefs/AboutMe.html
@@ -117,6 +117,35 @@
 <td valign="top" class="boxcontainer">
 
 <&| /Widgets/TitleBox, title => loc('Password'), id => "user-prefs-password" &>
+
+<p><&|/l&>Tips to help you choose a more secure password:</&></p>
+<ul>
+<li><&|/l&><b>Use a full phrase.</b> Describe a vivid image or memory, so it's easy to recall.</&></li>
+<li><&|/l&><b>Avoid exact quotations</b> from movies, books, or elsewhere.</&></li>
+<li><&|/l&><b>Avoid personal information</b> like names and birthdays.</&></li>
+<li><&|/l&><b>Use each password on one site only.</b> Never re-use the same password on other sites!</&></li>
+<li><&|/l&>Poor example: <tt>Monkey123!</tt></&></li>
+<li><&|/l&>Good example: <tt>MamaLovedThe'57Chevy</tt></&></li>
+</ul>
+
+<p><&|/l&>Password requirements:</&></p>
+<ul>
+% {
+% my @checks = (
+%    ['MinimumLength', 'At least [quant,_1,character,characters] long'],
+%    ['Uppercase', 'Include at least [quant,_1,uppercase character,uppercase characters]'],
+%    ['Lowercase', 'Include at least [quant,_1,lowercase character,lowercase characters]'],
+%    ['Digits', 'Include at least [quant,_1,digit,digits]'],
+%    ['Symbols', 'Include at least [quant,_1,punctuation mark or symbol,punctuation marks or symbols]'],
+%    );
+% foreach my $check (@checks) {
+%    if ( my $limit = RT->Config->Get('PasswordPolicy')->{@$check[0]} ) {
+<li><%loc(@$check[1],$limit)%></li>
+%    }
+% }
+% }
+</ul>
+
 % if ( $UserObj->__Value('Password') ne '*NO-PASSWORD*' ) {
 <& /Elements/EditPassword,
     User => $UserObj,

-----------------------------------------------------------------------


More information about the rt-commit mailing list