Project

General

Profile

Patch #5690 » redmine_3_2_stableLDAP_password_recovery_and_change.patch

Oleksandr Melnyk, 2016-06-24 18:17

View differences:

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 %> &copy; 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
(5-5/7)