##// END OF EJS Templates
Removed useless require 'iconv' (#12787)....
Jean-Philippe Lang -
r10946:e224d5040828
parent child
Show More
@@ -1,201 +1,200
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 require 'iconv'
19 18 require 'net/ldap'
20 19 require 'net/ldap/dn'
21 20 require 'timeout'
22 21
23 22 class AuthSourceLdap < AuthSource
24 23 validates_presence_of :host, :port, :attr_login
25 24 validates_length_of :name, :host, :maximum => 60, :allow_nil => true
26 25 validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true
27 26 validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
28 27 validates_numericality_of :port, :only_integer => true
29 28 validates_numericality_of :timeout, :only_integer => true, :allow_blank => true
30 29 validate :validate_filter
31 30
32 31 before_validation :strip_ldap_attributes
33 32
34 33 def initialize(attributes=nil, *args)
35 34 super
36 35 self.port = 389 if self.port == 0
37 36 end
38 37
39 38 def authenticate(login, password)
40 39 return nil if login.blank? || password.blank?
41 40
42 41 with_timeout do
43 42 attrs = get_user_dn(login, password)
44 43 if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
45 44 logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
46 45 return attrs.except(:dn)
47 46 end
48 47 end
49 48 rescue Net::LDAP::LdapError => e
50 49 raise AuthSourceException.new(e.message)
51 50 end
52 51
53 52 # test the connection to the LDAP
54 53 def test_connection
55 54 with_timeout do
56 55 ldap_con = initialize_ldap_con(self.account, self.account_password)
57 56 ldap_con.open { }
58 57 end
59 58 rescue Net::LDAP::LdapError => e
60 59 raise AuthSourceException.new(e.message)
61 60 end
62 61
63 62 def auth_method_name
64 63 "LDAP"
65 64 end
66 65
67 66 # Returns true if this source can be searched for users
68 67 def searchable?
69 68 !account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")}
70 69 end
71 70
72 71 # Searches the source for users and returns an array of results
73 72 def search(q)
74 73 q = q.to_s.strip
75 74 return [] unless searchable? && q.present?
76 75
77 76 results = []
78 77 search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q)
79 78 ldap_con = initialize_ldap_con(self.account, self.account_password)
80 79 ldap_con.search(:base => self.base_dn,
81 80 :filter => search_filter,
82 81 :attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail],
83 82 :size => 10) do |entry|
84 83 attrs = get_user_attributes_from_ldap_entry(entry)
85 84 attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login)
86 85 results << attrs
87 86 end
88 87 results
89 88 rescue Net::LDAP::LdapError => e
90 89 raise AuthSourceException.new(e.message)
91 90 end
92 91
93 92 private
94 93
95 94 def with_timeout(&block)
96 95 timeout = self.timeout
97 96 timeout = 20 unless timeout && timeout > 0
98 97 Timeout.timeout(timeout) do
99 98 return yield
100 99 end
101 100 rescue Timeout::Error => e
102 101 raise AuthSourceTimeoutException.new(e.message)
103 102 end
104 103
105 104 def ldap_filter
106 105 if filter.present?
107 106 Net::LDAP::Filter.construct(filter)
108 107 end
109 108 rescue Net::LDAP::LdapError
110 109 nil
111 110 end
112 111
113 112 def base_filter
114 113 filter = Net::LDAP::Filter.eq("objectClass", "*")
115 114 if f = ldap_filter
116 115 filter = filter & f
117 116 end
118 117 filter
119 118 end
120 119
121 120 def validate_filter
122 121 if filter.present? && ldap_filter.nil?
123 122 errors.add(:filter, :invalid)
124 123 end
125 124 end
126 125
127 126 def strip_ldap_attributes
128 127 [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
129 128 write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
130 129 end
131 130 end
132 131
133 132 def initialize_ldap_con(ldap_user, ldap_password)
134 133 options = { :host => self.host,
135 134 :port => self.port,
136 135 :encryption => (self.tls ? :simple_tls : nil)
137 136 }
138 137 options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank?
139 138 Net::LDAP.new options
140 139 end
141 140
142 141 def get_user_attributes_from_ldap_entry(entry)
143 142 {
144 143 :dn => entry.dn,
145 144 :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
146 145 :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
147 146 :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
148 147 :auth_source_id => self.id
149 148 }
150 149 end
151 150
152 151 # Return the attributes needed for the LDAP search. It will only
153 152 # include the user attributes if on-the-fly registration is enabled
154 153 def search_attributes
155 154 if onthefly_register?
156 155 ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]
157 156 else
158 157 ['dn']
159 158 end
160 159 end
161 160
162 161 # Check if a DN (user record) authenticates with the password
163 162 def authenticate_dn(dn, password)
164 163 if dn.present? && password.present?
165 164 initialize_ldap_con(dn, password).bind
166 165 end
167 166 end
168 167
169 168 # Get the user's dn and any attributes for them, given their login
170 169 def get_user_dn(login, password)
171 170 ldap_con = nil
172 171 if self.account && self.account.include?("$login")
173 172 ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password)
174 173 else
175 174 ldap_con = initialize_ldap_con(self.account, self.account_password)
176 175 end
177 176 attrs = {}
178 177 search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login)
179 178
180 179 ldap_con.search( :base => self.base_dn,
181 180 :filter => search_filter,
182 181 :attributes=> search_attributes) do |entry|
183 182
184 183 if onthefly_register?
185 184 attrs = get_user_attributes_from_ldap_entry(entry)
186 185 else
187 186 attrs = {:dn => entry.dn}
188 187 end
189 188
190 189 logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug?
191 190 end
192 191
193 192 attrs
194 193 end
195 194
196 195 def self.get_attr(entry, attr_name)
197 196 if !attr_name.blank?
198 197 entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
199 198 end
200 199 end
201 200 end
@@ -1,280 +1,278
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 require 'iconv'
19
20 18 class Changeset < ActiveRecord::Base
21 19 belongs_to :repository
22 20 belongs_to :user
23 21 has_many :filechanges, :class_name => 'Change', :dependent => :delete_all
24 22 has_and_belongs_to_many :issues
25 23 has_and_belongs_to_many :parents,
26 24 :class_name => "Changeset",
27 25 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
28 26 :association_foreign_key => 'parent_id', :foreign_key => 'changeset_id'
29 27 has_and_belongs_to_many :children,
30 28 :class_name => "Changeset",
31 29 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
32 30 :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
33 31
34 32 acts_as_event :title => Proc.new {|o| o.title},
35 33 :description => :long_comments,
36 34 :datetime => :committed_on,
37 35 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}}
38 36
39 37 acts_as_searchable :columns => 'comments',
40 38 :include => {:repository => :project},
41 39 :project_key => "#{Repository.table_name}.project_id",
42 40 :date_column => 'committed_on'
43 41
44 42 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
45 43 :author_key => :user_id,
46 44 :find_options => {:include => [:user, {:repository => :project}]}
47 45
48 46 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
49 47 validates_uniqueness_of :revision, :scope => :repository_id
50 48 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
51 49
52 50 scope :visible, lambda {|*args|
53 51 includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args))
54 52 }
55 53
56 54 after_create :scan_for_issues
57 55 before_create :before_create_cs
58 56
59 57 def revision=(r)
60 58 write_attribute :revision, (r.nil? ? nil : r.to_s)
61 59 end
62 60
63 61 # Returns the identifier of this changeset; depending on repository backends
64 62 def identifier
65 63 if repository.class.respond_to? :changeset_identifier
66 64 repository.class.changeset_identifier self
67 65 else
68 66 revision.to_s
69 67 end
70 68 end
71 69
72 70 def committed_on=(date)
73 71 self.commit_date = date
74 72 super
75 73 end
76 74
77 75 # Returns the readable identifier
78 76 def format_identifier
79 77 if repository.class.respond_to? :format_changeset_identifier
80 78 repository.class.format_changeset_identifier self
81 79 else
82 80 identifier
83 81 end
84 82 end
85 83
86 84 def project
87 85 repository.project
88 86 end
89 87
90 88 def author
91 89 user || committer.to_s.split('<').first
92 90 end
93 91
94 92 def before_create_cs
95 93 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
96 94 self.comments = self.class.normalize_comments(
97 95 self.comments, repository.repo_log_encoding)
98 96 self.user = repository.find_committer_user(self.committer)
99 97 end
100 98
101 99 def scan_for_issues
102 100 scan_comment_for_issue_ids
103 101 end
104 102
105 103 TIMELOG_RE = /
106 104 (
107 105 ((\d+)(h|hours?))((\d+)(m|min)?)?
108 106 |
109 107 ((\d+)(h|hours?|m|min))
110 108 |
111 109 (\d+):(\d+)
112 110 |
113 111 (\d+([\.,]\d+)?)h?
114 112 )
115 113 /x
116 114
117 115 def scan_comment_for_issue_ids
118 116 return if comments.blank?
119 117 # keywords used to reference issues
120 118 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
121 119 ref_keywords_any = ref_keywords.delete('*')
122 120 # keywords used to fix issues
123 121 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
124 122
125 123 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
126 124
127 125 referenced_issues = []
128 126
129 127 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
130 128 action, refs = match[2], match[3]
131 129 next unless action.present? || ref_keywords_any
132 130
133 131 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
134 132 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
135 133 if issue
136 134 referenced_issues << issue
137 135 fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
138 136 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
139 137 end
140 138 end
141 139 end
142 140
143 141 referenced_issues.uniq!
144 142 self.issues = referenced_issues unless referenced_issues.empty?
145 143 end
146 144
147 145 def short_comments
148 146 @short_comments || split_comments.first
149 147 end
150 148
151 149 def long_comments
152 150 @long_comments || split_comments.last
153 151 end
154 152
155 153 def text_tag(ref_project=nil)
156 154 tag = if scmid?
157 155 "commit:#{scmid}"
158 156 else
159 157 "r#{revision}"
160 158 end
161 159 if repository && repository.identifier.present?
162 160 tag = "#{repository.identifier}|#{tag}"
163 161 end
164 162 if ref_project && project && ref_project != project
165 163 tag = "#{project.identifier}:#{tag}"
166 164 end
167 165 tag
168 166 end
169 167
170 168 # Returns the title used for the changeset in the activity/search results
171 169 def title
172 170 repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
173 171 comm = short_comments.blank? ? '' : (': ' + short_comments)
174 172 "#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
175 173 end
176 174
177 175 # Returns the previous changeset
178 176 def previous
179 177 @previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first
180 178 end
181 179
182 180 # Returns the next changeset
183 181 def next
184 182 @next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first
185 183 end
186 184
187 185 # Creates a new Change from it's common parameters
188 186 def create_change(change)
189 187 Change.create(:changeset => self,
190 188 :action => change[:action],
191 189 :path => change[:path],
192 190 :from_path => change[:from_path],
193 191 :from_revision => change[:from_revision])
194 192 end
195 193
196 194 # Finds an issue that can be referenced by the commit message
197 195 def find_referenced_issue_by_id(id)
198 196 return nil if id.blank?
199 197 issue = Issue.find_by_id(id.to_i, :include => :project)
200 198 if Setting.commit_cross_project_ref?
201 199 # all issues can be referenced/fixed
202 200 elsif issue
203 201 # issue that belong to the repository project, a subproject or a parent project only
204 202 unless issue.project &&
205 203 (project == issue.project || project.is_ancestor_of?(issue.project) ||
206 204 project.is_descendant_of?(issue.project))
207 205 issue = nil
208 206 end
209 207 end
210 208 issue
211 209 end
212 210
213 211 private
214 212
215 213 def fix_issue(issue)
216 214 status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
217 215 if status.nil?
218 216 logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
219 217 return issue
220 218 end
221 219
222 220 # the issue may have been updated by the closure of another one (eg. duplicate)
223 221 issue.reload
224 222 # don't change the status is the issue is closed
225 223 return if issue.status && issue.status.is_closed?
226 224
227 225 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
228 226 issue.status = status
229 227 unless Setting.commit_fix_done_ratio.blank?
230 228 issue.done_ratio = Setting.commit_fix_done_ratio.to_i
231 229 end
232 230 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
233 231 { :changeset => self, :issue => issue })
234 232 unless issue.save
235 233 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
236 234 end
237 235 issue
238 236 end
239 237
240 238 def log_time(issue, hours)
241 239 time_entry = TimeEntry.new(
242 240 :user => user,
243 241 :hours => hours,
244 242 :issue => issue,
245 243 :spent_on => commit_date,
246 244 :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
247 245 :locale => Setting.default_language)
248 246 )
249 247 time_entry.activity = log_time_activity unless log_time_activity.nil?
250 248
251 249 unless time_entry.save
252 250 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
253 251 end
254 252 time_entry
255 253 end
256 254
257 255 def log_time_activity
258 256 if Setting.commit_logtime_activity_id.to_i > 0
259 257 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
260 258 end
261 259 end
262 260
263 261 def split_comments
264 262 comments =~ /\A(.+?)\r?\n(.*)$/m
265 263 @short_comments = $1 || comments
266 264 @long_comments = $2.to_s.strip
267 265 return @short_comments, @long_comments
268 266 end
269 267
270 268 public
271 269
272 270 # Strips and reencodes a commit log before insertion into the database
273 271 def self.normalize_comments(str, encoding)
274 272 Changeset.to_utf8(str.to_s.strip, encoding)
275 273 end
276 274
277 275 def self.to_utf8(str, encoding)
278 276 Redmine::CodesetUtil.to_utf8(str, encoding)
279 277 end
280 278 end
General Comments 0
You need to be logged in to leave comments. Login now