summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorGo MAEDA <maeda@farend.jp>2019-08-29 01:35:09 +0000
committerGo MAEDA <maeda@farend.jp>2019-08-29 01:35:09 +0000
commit35e6a532f56d1a3e54476d2adb36375169e9bef9 (patch)
tree6d225402666ad8aa85b2fe6f9d40c427669368fc /app
parent0d9f7ee64a43ce75dd87c8d035a9d335b1077c16 (diff)
downloadredmine-35e6a532f56d1a3e54476d2adb36375169e9bef9.tar.gz
redmine-35e6a532f56d1a3e54476d2adb36375169e9bef9.zip
Force passwords to contain specified character classes (#4221).
Patch by Takenori TAKAKI. git-svn-id: http://svn.redmine.org/redmine/trunk@18411 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app')
-rw-r--r--app/models/setting.rb7
-rw-r--r--app/models/user.rb21
-rw-r--r--app/views/account/password_recovery.html.erb3
-rw-r--r--app/views/account/register.html.erb7
-rw-r--r--app/views/my/password.html.erb6
-rw-r--r--app/views/settings/_authentication.html.erb2
-rw-r--r--app/views/users/_form.html.erb9
7 files changed, 47 insertions, 8 deletions
diff --git a/app/models/setting.rb b/app/models/setting.rb
index b5c8ca2e5..384b6e866 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -19,6 +19,13 @@
class Setting < ActiveRecord::Base
+ PASSWORD_CHAR_CLASSES = {
+ 'uppercase' => /[A-Z]/,
+ 'lowercase' => /[a-z]/,
+ 'digits' => /[0-9]/,
+ 'special_chars' => /[[:ascii:]&&[:graph:]&&[:^alnum:]]/
+ }
+
DATE_FORMATS = [
'%Y-%m-%d',
'%d/%m/%Y',
diff --git a/app/models/user.rb b/app/models/user.rb
index 9003b253f..a8a80678a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -112,6 +112,9 @@ class User < Principal
validates_length_of :firstname, :lastname, :maximum => 30
validates_length_of :identity_url, maximum: 255
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
+ Setting::PASSWORD_CHAR_CLASSES.each do |k, v|
+ validates_format_of :password, :with => v, :message => :"must_contain_#{k}", :allow_blank => true, :if => Proc.new {Setting.password_required_char_classes.include?(k)}
+ end
validate :validate_password_length
validate do
if password_confirmation && password != password_confirmation
@@ -366,10 +369,22 @@ class User < Principal
# Generate and set a random password on given length
def random_password(length=40)
- chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
- chars -= %w(0 O 1 l)
+ chars_list = [('A'..'Z').to_a, ('a'..'z').to_a, ('0'..'9').to_a]
+ # auto-generated passwords contain special characters only when admins
+ # require users to use passwords which contains special characters
+ if Setting.password_required_char_classes.include?('special_chars')
+ chars_list << ("\x20".."\x7e").to_a.select {|c| c =~ Setting::PASSWORD_CHAR_CLASSES['special_chars']}
+ end
+ chars_list.each {|v| v.reject! {|c| %(0O1l|'"`*).include?(c)}}
+
password = +''
- length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
+ chars_list.each do |chars|
+ password << chars[SecureRandom.random_number(chars.size)]
+ length -= 1
+ end
+ chars = chars_list.flatten
+ length.times { password << chars[SecureRandom.random_number(chars.size)] }
+ password = password.split('').shuffle(random: SecureRandom).join
self.password = password
self.password_confirmation = password
self
diff --git a/app/views/account/password_recovery.html.erb b/app/views/account/password_recovery.html.erb
index 24da8223b..0c275b476 100644
--- a/app/views/account/password_recovery.html.erb
+++ b/app/views/account/password_recovery.html.erb
@@ -9,6 +9,9 @@
<label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label>
<%= password_field_tag 'new_password', nil, :size => 25 %>
<em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em>
+ <% if Setting.password_required_char_classes.any? %>
+ <em class="info"><%= l(:text_characters_must_contain, :character_classes => Setting.password_required_char_classes.collect{|c| l("label_password_char_class_#{c}")}.join(", ")) %></em>
+ <% end %>
</p>
<p>
diff --git a/app/views/account/register.html.erb b/app/views/account/register.html.erb
index ade00adfc..f35e0e0cc 100644
--- a/app/views/account/register.html.erb
+++ b/app/views/account/register.html.erb
@@ -8,8 +8,11 @@
<p><%= f.text_field :login, :size => 25, :required => true %></p>
<p><%= f.password_field :password, :size => 25, :required => true %>
- <em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
-
+ <em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em>
+ <% if Setting.password_required_char_classes.any? %>
+ <em class="info"><%= l(:text_characters_must_contain, :character_classes => Setting.password_required_char_classes.collect{|c| l("label_password_char_class_#{c}")}.join(", ")) %></em>
+ <% end %>
+ </p>
<p><%= f.password_field :password_confirmation, :size => 25, :required => true %></p>
<% end %>
diff --git a/app/views/my/password.html.erb b/app/views/my/password.html.erb
index 7a411e51a..4e123db48 100644
--- a/app/views/my/password.html.erb
+++ b/app/views/my/password.html.erb
@@ -9,7 +9,11 @@
<p><label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label>
<%= password_field_tag 'new_password', nil, :size => 25 %>
-<em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
+ <em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em>
+ <% if Setting.password_required_char_classes.any? %>
+ <em class="info"><%= l(:text_characters_must_contain, :character_classes => Setting.password_required_char_classes.collect{|c| l("label_password_char_class_#{c}")}.join(", ")) %></em>
+ <% end %>
+</p>
<p><label for="new_password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label>
<%= password_field_tag 'new_password_confirmation', nil, :size => 25 %></p>
diff --git a/app/views/settings/_authentication.html.erb b/app/views/settings/_authentication.html.erb
index 4bf890d3f..9a39497b8 100644
--- a/app/views/settings/_authentication.html.erb
+++ b/app/views/settings/_authentication.html.erb
@@ -20,6 +20,8 @@
<p><%= setting_text_field :password_min_length, :size => 6 %></p>
+<p><%= setting_multiselect :password_required_char_classes, Setting::PASSWORD_CHAR_CLASSES.keys.collect {|c| [l("label_password_char_class_#{c}"), c]} , :inline => true %></p>
+
<p>
<%= setting_select :password_max_age, [[l(:label_disabled), 0]] + [7, 30, 60, 90, 180, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]} %>
</p>
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb
index ce5b1f6c7..bb20a4f9d 100644
--- a/app/views/users/_form.html.erb
+++ b/app/views/users/_form.html.erb
@@ -31,8 +31,13 @@
<p><%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {$('#password_fields').show();} else {$('#password_fields').hide();}" %></p>
<% end %>
<div id="password_fields" style="<%= 'display:none;' if @user.auth_source %>">
- <p><%= f.password_field :password, :required => true, :size => 25 %>
- <em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
+ <p>
+ <%= f.password_field :password, :required => true, :size => 25 %>
+ <em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em>
+ <% if Setting.password_required_char_classes.any? %>
+ <em class="info"><%= l(:text_characters_must_contain, :character_classes => Setting.password_required_char_classes.collect{|c| l("label_password_char_class_#{c}")}.join(", ")) %></em>
+ <% end %>
+ </p>
<p><%= f.password_field :password_confirmation, :required => true, :size => 25 %></p>
<p><%= f.check_box :generate_password %></p>
<p><%= f.check_box :must_change_passwd %></p>