Patch #5690 » redmine_3_2_stableLDAP_password_recovery_and_change.patch
| app/controllers/account_controller.rb (revision ) | ||
|---|---|---|
| 70 | 70 |
return |
| 71 | 71 |
end |
| 72 | 72 |
if request.post? |
| 73 |
if @user.isExternal? |
|
| 74 |
if @user.newExternalPassword(params[:new_password], params[:new_password_confirmation]) |
|
| 75 |
@token.destroy |
|
| 76 |
flash[:notice] = l(:notice_account_password_updated) |
|
| 77 |
redirect_to signin_path |
|
| 78 |
return |
|
| 79 |
else |
|
| 80 |
flash[:error] = l(:notice_external_password_error) |
|
| 81 |
end |
|
| 82 |
else |
|
| 73 |
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] |
|
| 74 |
if @user.save |
|
| 75 |
@token.destroy |
|
| 76 |
flash[:notice] = l(:notice_account_password_updated) |
|
| 77 |
redirect_to signin_path |
|
| 78 |
return |
|
| 83 |
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] |
|
| 84 |
if @user.save |
|
| 85 |
@token.destroy |
|
| 86 |
flash[:notice] = l(:notice_account_password_updated) |
|
| 87 |
redirect_to signin_path |
|
| 88 |
return |
|
| 89 |
end |
|
| 79 | 90 |
end |
| 80 | 91 |
end |
| 81 | 92 |
render :template => "account/password_recovery" |
| app/controllers/my_controller.rb (revision ) | ||
|---|---|---|
| 100 | 100 |
elsif params[:password] == params[:new_password] |
| 101 | 101 |
flash.now[:error] = l(:notice_new_password_must_be_different) |
| 102 | 102 |
else |
| 103 | ||
| 104 |
if @user.isExternal? |
|
| 105 |
if @user.changeExternalPassword(params[:password],params[:new_password], params[:new_password_confirmation]) |
|
| 106 |
session[:ctime] = Time.now.change(:usec => 0).utc.to_i |
|
| 107 |
flash[:notice] = l(:notice_account_password_updated) |
|
| 108 |
redirect_to my_account_path |
|
| 109 |
else |
|
| 110 |
flash[:error] = l(:notice_external_password_error) |
|
| 111 |
end |
|
| 112 |
else |
|
| 103 |
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] |
|
| 104 |
@user.must_change_passwd = false |
|
| 105 |
if @user.save |
|
| 113 |
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] |
|
| 114 |
@user.must_change_passwd = false |
|
| 115 |
if @user.save |
|
| 106 |
# The session token was destroyed by the password change, generate a new one |
|
| 107 |
session[:tk] = @user.generate_session_token |
|
| 116 |
# Reset the session creation time to not log out this session on next |
|
| 117 |
# request due to ApplicationController#force_logout_if_password_changed |
|
| 118 |
session[:ctime] = User.current.passwd_changed_on.utc.to_i |
|
| 108 |
flash[:notice] = l(:notice_account_password_updated) |
|
| 109 |
redirect_to my_account_path |
|
| 119 |
flash[:notice] = l(:notice_account_password_updated) |
|
| 120 |
redirect_to my_account_path |
|
| 121 |
end |
|
| 110 | 122 |
end |
| 111 | 123 |
end |
| 112 | 124 |
end |
| app/helpers/auth_sources_helper.rb (revision ) | ||
|---|---|---|
| 21 | 21 |
def auth_source_partial_name(auth_source) |
| 22 | 22 |
"form_#{auth_source.class.name.underscore}"
|
| 23 | 23 |
end |
| 24 | ||
| 25 |
module Encryption |
|
| 26 |
# Return an array of password encryptions |
|
| 27 |
def self.encryptiontypes |
|
| 28 |
["MD5","SSHA","CLEAR"] |
|
| 29 |
end |
|
| 30 |
end |
|
| 24 | 31 |
end |
| config/locales/en.yml (revision ) | ||
|---|---|---|
| 1169 | 1169 |
description_date_from: Enter start date |
| 1170 | 1170 |
description_date_to: Enter end date |
| 1171 | 1171 |
text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.' |
| 1172 |
notice_external_password_error: Error changing external password. |
|
| 1173 |
field_password_encryption: Encryption |
|
| 1174 |
field_enabled_passwd: Enabled password changing |
|
| app/views/auth_sources/_form_auth_source_ldap.html.erb (revision ) | ||
|---|---|---|
| 14 | 14 |
<p><%= f.text_area :filter, :size => 60, :label => :field_auth_source_ldap_filter %></p> |
| 15 | 15 |
<p><%= f.text_field :timeout, :size => 4 %></p> |
| 16 | 16 |
<p><%= f.check_box :onthefly_register, :label => :field_onthefly %></p> |
| 17 |
<p><%= f.check_box :enabled_passwd, :label => :field_enabled_passwd %></p> |
|
| 17 | 18 |
</div> |
| 18 | 19 | |
| 19 | 20 |
<fieldset class="box tabular"><legend><%=l(:label_attribute_plural)%></legend> |
| ... | ... | |
| 21 | 22 |
<p><%= f.text_field :attr_firstname, :size => 20 %></p> |
| 22 | 23 |
<p><%= f.text_field :attr_lastname, :size => 20 %></p> |
| 23 | 24 |
<p><%= f.text_field :attr_mail, :size => 20 %></p> |
| 25 |
<p><%= f.select :password_encryption, AuthSourcesHelper::Encryption.encryptiontypes %></p> |
|
| 24 | 26 |
</fieldset> |
| config/environments/production.rb (revision ) | ||
|---|---|---|
| 22 | 22 | |
| 23 | 23 |
# Print deprecation notices to the Rails logger. |
| 24 | 24 |
config.active_support.deprecation = :log |
| 25 | ||
| 26 |
# config.log_level = :info |
|
| 25 | 27 |
end |
| app/models/auth_source_ldap.rb (revision ) | ||
|---|---|---|
| 18 | 18 |
require 'net/ldap' |
| 19 | 19 |
require 'net/ldap/dn' |
| 20 | 20 |
require 'timeout' |
| 21 |
require 'digest' |
|
| 22 |
require 'base64' |
|
| 21 | 23 | |
| 22 | 24 |
class AuthSourceLdap < AuthSource |
| 23 | 25 |
NETWORK_EXCEPTIONS = [ |
| ... | ... | |
| 68 | 70 | |
| 69 | 71 |
def auth_method_name |
| 70 | 72 |
"LDAP" |
| 73 |
end |
|
| 74 | ||
| 75 |
def allow_password_changes? |
|
| 76 |
return self.enabled_passwd |
|
| 77 |
end |
|
| 78 | ||
| 79 |
def encode_password(clear_password) |
|
| 80 |
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
|
| 81 |
salt = '' |
|
| 82 |
10.times { |i| salt << chars[rand(chars.size-1)] }
|
|
| 83 | ||
| 84 |
if self.password_encryption == "MD5" |
|
| 85 |
logger.debug "Encode as md5" |
|
| 86 |
return "{MD5}"+Base64.encode64(Digest::MD5.digest(clear_password)).chomp!
|
|
| 87 |
end |
|
| 88 |
if self.password_encryption == "SSHA" |
|
| 89 |
logger.debug "Encode as ssha" |
|
| 90 |
return "{SSHA}"+Base64.encode64(Digest::SHA1.digest(clear_password+salt)+salt).chomp!
|
|
| 91 |
end |
|
| 92 | ||
| 93 |
if self.password_encryption == "CLEAR" |
|
| 94 |
logger.debug "Encode as cleartype" |
|
| 95 |
return clear_password |
|
| 96 |
end |
|
| 97 |
# |
|
| 98 |
end |
|
| 99 | ||
| 100 |
# change password |
|
| 101 |
def change_password(login,password,newPassword) |
|
| 102 |
begin |
|
| 103 |
attrs = get_user_dn(login, password) |
|
| 104 |
if attrs |
|
| 105 |
if self.account.blank? || self.account_password.blank? |
|
| 106 |
logger.debug "Binding with user account" |
|
| 107 |
ldap_con = initialize_ldap_con(attrs[:dn], password) |
|
| 108 |
else |
|
| 109 |
logger.debug "Binding with administrator account" |
|
| 110 |
ldap_con = initialize_ldap_con(self.account, self.account_password) |
|
| 111 |
end |
|
| 112 | ||
| 113 |
ops = [ |
|
| 114 |
[:delete, :userPassword, password], |
|
| 115 |
[:add, :userPassword, newPassword] |
|
| 116 |
] |
|
| 117 |
#return ldap_con.modify :dn => attrs[:dn], :operations => ops |
|
| 118 |
# This is another password change method, probably more common |
|
| 119 |
newPassword = encode_password(newPassword) |
|
| 120 |
# logger.info("NEW PASSWORD #{newPassword}")
|
|
| 121 |
if newPassword.blank? |
|
| 122 |
logger.debug "Invaild password" |
|
| 123 |
return false |
|
| 124 |
else |
|
| 125 |
logger.debug "Try to change password" |
|
| 126 |
return ldap_con.replace_attribute attrs[:dn], :userPassword, newPassword |
|
| 127 |
end |
|
| 128 |
end |
|
| 129 |
rescue |
|
| 130 |
return false |
|
| 131 |
end |
|
| 132 |
return false |
|
| 133 |
end |
|
| 134 | ||
| 135 |
def lost_password(login,newPassword) |
|
| 136 |
begin |
|
| 137 |
attrs = get_user_dn_nopass(login) |
|
| 138 |
if attrs |
|
| 139 |
ldap_con = initialize_ldap_con(self.account, self.account_password) |
|
| 140 |
return ldap_con.replace_attribute attrs[:dn], :userPassword, encode_password(newPassword) |
|
| 141 |
end |
|
| 142 |
rescue |
|
| 143 |
return false |
|
| 144 |
end |
|
| 145 |
return false |
|
| 146 |
end |
|
| 147 | ||
| 148 |
def get_user_dn_nopass(login) |
|
| 149 |
ldap_con = nil |
|
| 150 |
ldap_con = initialize_ldap_con(self.account, self.account_password) |
|
| 151 |
attrs = {}
|
|
| 152 |
search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login) |
|
| 153 |
ldap_con.search( :base => self.base_dn, |
|
| 154 |
:filter => search_filter, |
|
| 155 |
:attributes=> search_attributes) do |entry| |
|
| 156 |
if onthefly_register? |
|
| 157 |
attrs = get_user_attributes_from_ldap_entry(entry) |
|
| 158 |
else |
|
| 159 |
attrs = {:dn => entry.dn}
|
|
| 160 |
end |
|
| 161 |
logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug?
|
|
| 162 |
end |
|
| 163 |
attrs |
|
| 71 |
end |
|
| 164 |
end |
|
| 72 | 165 | |
| 73 | 166 |
# Returns true if this source can be searched for users |
| 74 | 167 |
def searchable? |
| app/views/layouts/base.html.erb (revision ) | ||
|---|---|---|
| 112 | 112 |
<div id="ajax-modal" style="display:none;"></div> |
| 113 | 113 | |
| 114 | 114 |
<div id="footer"> |
| 115 |
<div class="bgl"><div class="bgr"> |
|
| 115 |
<div class="bgl"><div class="bgr" style="display: none">
|
|
| 116 | 116 |
Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> © 2006-2016 Jean-Philippe Lang |
| 117 | 117 |
</div></div> |
| 118 | 118 |
</div> |
| app/models/user.rb (revision ) | ||
|---|---|---|
| 756 | 756 |
end |
| 757 | 757 |
end |
| 758 | 758 | |
| 759 |
def isExternal? |
|
| 760 |
return auth_source_id.present? |
|
| 761 |
end |
|
| 762 | ||
| 763 |
def changeExternalPassword(password,newPassword,newPasswordConfirm) |
|
| 764 |
return false if newPassword == "" || newPassword.length < Setting.password_min_length.to_i |
|
| 765 |
return false if newPassword != newPasswordConfirm |
|
| 766 |
if (self.isExternal?) |
|
| 767 |
return self.auth_source.change_password(self.login,password,newPassword) |
|
| 768 |
end |
|
| 769 |
return false |
|
| 770 |
end |
|
| 771 | ||
| 772 |
def newExternalPassword(newPassword,newPasswordConfirm) |
|
| 773 |
return false if newPassword == "" || newPassword.length < 4 |
|
| 774 |
return false if newPassword != newPasswordConfirm |
|
| 775 |
if (self.isExternal?) |
|
| 776 |
return self.auth_source.lost_password(self.login,newPassword) |
|
| 777 |
end |
|
| 778 |
return false |
|
| 779 |
end |
|
| 780 | ||
| 759 | 781 |
protected |
| 760 | 782 | |
| 761 | 783 |
def validate_password_length |