OSDN Git Service

fc6b9b87ffa254e5ace4fb7e8178f7b041438f32
[redminele/redminele.git] / ruby / lib / ruby / gems / 1.8 / gems / activeldap-1.2.1 / lib / active_ldap / operations.rb
1 module ActiveLdap
2   module Operations
3     class << self
4       def included(base)
5         super
6         base.class_eval do
7           extend(Common)
8           extend(Find)
9           extend(LDIF)
10           extend(Delete)
11           extend(Update)
12
13           include(Common)
14           include(Find)
15           include(LDIF)
16           include(Delete)
17           include(Update)
18         end
19       end
20     end
21
22     module Common
23       VALID_SEARCH_OPTIONS = [:attribute, :value, :filter, :prefix,
24                               :classes, :scope, :limit, :attributes,
25                               :sort_by, :order, :connection, :base]
26
27       def search(options={}, &block)
28         validate_search_options(options)
29         attr = options[:attribute]
30         value = options[:value] || '*'
31         filter = options[:filter]
32         prefix = options[:prefix]
33         classes = options[:classes]
34
35         value = value.first if value.is_a?(Array) and value.first.size == 1
36
37         _attr = nil
38         _prefix = nil
39         if attr.nil? or attr == dn_attribute
40           _attr, value, _prefix = split_search_value(value)
41         end
42         attr ||= _attr || ensure_search_attribute
43         prefix ||= _prefix
44         filter ||= [attr, value]
45         filter = [:and, filter, *object_class_filters(classes)]
46         _base = options[:base] ? [options[:base]] : [prefix, base]
47         _base = prepare_search_base(_base)
48         if options.has_key?(:ldap_scope)
49           message = _(":ldap_scope search option is deprecated. " \
50                       "Use :scope instead.")
51           ActiveSupport::Deprecation.warn(message)
52           options[:scope] ||= options[:ldap_scope]
53         end
54         search_options = {
55           :base => _base,
56           :scope => options[:scope] || scope,
57           :filter => filter,
58           :limit => options[:limit],
59           :attributes => options[:attributes],
60           :sort_by => options[:sort_by] || sort_by,
61           :order => options[:order] || order,
62         }
63
64         options[:connection] ||= connection
65         values = options[:connection].search(search_options) do |dn, attrs|
66           attributes = {}
67           attrs.each do |key, _value|
68             normalized_attr, normalized_value =
69               normalize_attribute_options(key, _value)
70             attributes[normalized_attr] ||= []
71             attributes[normalized_attr].concat(normalized_value)
72           end
73           [dn, attributes]
74         end
75         values = values.collect {|_value| yield(_value)} if block_given?
76         values
77       end
78
79       def exist?(dn, options={})
80         attr, value, prefix = split_search_value(dn)
81
82         options_for_leaf = {
83           :attribute => attr,
84           :value => value,
85           :prefix => prefix,
86         }
87
88         attribute = attr || ensure_search_attribute
89         options_for_non_leaf = {
90           :attribute => attr,
91           :value => value,
92           :prefix => ["#{attribute}=#{value}", prefix].compact.join(","),
93           :scope => :base,
94         }
95
96         !search(options_for_leaf.merge(options)).empty? or
97           !search(options_for_non_leaf.merge(options)).empty?
98       end
99       alias_method :exists?, :exist?
100
101       def count(options={})
102         search(options).size
103       end
104
105       private
106       def validate_search_options(options)
107         options.assert_valid_keys(VALID_SEARCH_OPTIONS)
108       end
109
110       def extract_options_from_args!(args)
111         args.last.is_a?(Hash) ? args.pop : {}
112       end
113
114       def ensure_search_attribute(*candidates)
115         default_search_attribute || "objectClass"
116       end
117
118       def ensure_dn_attribute(target)
119         "#{dn_attribute}=" +
120           target.gsub(/^\s*#{Regexp.escape(dn_attribute)}\s*=\s*/i, '')
121       end
122
123       def ensure_base(target)
124         [truncate_base(target), base.to_s].reject do |component|
125           component.blank?
126         end.join(',')
127       end
128
129       def truncate_base(target)
130         return nil if target.blank?
131         return target if base.nil?
132
133         parsed_target = nil
134         if target.is_a?(DN)
135           parsed_target = target
136         elsif /,/ =~ target
137           begin
138             parsed_target = DN.parse(target)
139           rescue DistinguishedNameInvalid
140           end
141         end
142
143         return target if parsed_target.nil?
144         begin
145           (parsed_target - base).to_s
146         rescue ArgumentError
147           target
148         end
149       end
150
151       def prepare_search_base(components)
152         components.compact.collect do |component|
153           case component
154           when String
155             component
156           when DN
157             component.to_s
158           else
159             DN.new(*component).to_s
160           end
161         end.reject{|x| x.empty?}.join(",")
162       end
163
164       def object_class_filters(classes=nil)
165         expected_classes = (classes || required_classes).collect do |name|
166           Escape.ldap_filter_escape(name)
167         end
168         unexpected_classes = excluded_classes.collect do |name|
169           Escape.ldap_filter_escape(name)
170         end
171         filters = []
172         unless expected_classes.empty?
173           filters << ["objectClass", "=", *expected_classes]
174         end
175         unless unexpected_classes.empty?
176           filters << [:not, [:or, ["objectClass", "=", *unexpected_classes]]]
177         end
178         filters
179       end
180
181       def split_search_value(value)
182         attr = prefix = nil
183
184         begin
185           dn = DN.parse(value)
186           attr, value = dn.rdns.first.to_a.first
187           rest = dn.rdns[1..-1]
188           prefix = DN.new(*rest).to_s unless rest.empty?
189         rescue DistinguishedNameInputInvalid
190           return [attr, value, prefix]
191         rescue DistinguishedNameInvalid
192           begin
193             dn = DN.parse("DUMMY=#{value}")
194             _, value = dn.rdns.first.to_a.first
195             rest = dn.rdns[1..-1]
196             prefix = DN.new(*rest).to_s unless rest.empty?
197           rescue DistinguishedNameInvalid
198           end
199         end
200
201         prefix = nil if prefix == base
202         prefix = truncate_base(prefix) if prefix
203         [attr, value, prefix]
204       end
205     end
206
207     module Find
208       # find
209       #
210       # Finds the first match for value where |value| is the value of some
211       # |field|, or the wildcard match. This is only useful for derived classes.
212       # usage: Subclass.find(:all, :attribute => "cn", :value => "some*val")
213       #        Subclass.find(:all, 'some*val')
214       def find(*args)
215         options = extract_options_from_args!(args)
216         args = [:first] if args.empty? and !options.empty?
217         case args.first
218         when :first
219           options[:value] ||= args[1]
220           find_initial(options)
221         when :last
222           options[:value] ||= args[1]
223           find_last(options)
224         when :all
225           options[:value] ||= args[1]
226           find_every(options)
227         else
228           find_from_dns(args, options)
229         end
230       end
231
232       # A convenience wrapper for <tt>find(:first,
233       # *args)</tt>. You can pass in all the same arguments
234       # to this method as you can to <tt>find(:first)</tt>.
235       def first(*args)
236         find(:first, *args)
237       end
238
239       # A convenience wrapper for <tt>find(:last,
240       # *args)</tt>. You can pass in all the same arguments
241       # to this method as you can to <tt>find(:last)</tt>.
242       def last(*args)
243         find(:last, *args)
244       end
245
246       # This is an alias for find(:all).  You can pass in
247       # all the same arguments to this method as you can
248       # to find(:all)
249       def all(*args)
250         find(:all, *args)
251       end
252
253       private
254       def find_initial(options)
255         find_every(options.merge(:limit => 1)).first
256       end
257
258       def find_last(options)
259         order = options[:order] || self.order || 'ascend'
260         order = normalize_sort_order(order) == :ascend ? :descend : :ascend
261         find_initial(options.merge(:order => order))
262       end
263
264       def normalize_sort_order(value)
265         case value.to_s
266         when /\Aasc(?:end)?\z/i
267           :ascend
268         when /\Adesc(?:end)?\z/i
269           :descend
270         else
271           raise ArgumentError, _("Invalid order: %s") % value.inspect
272         end
273       end
274
275       def find_every(options)
276         options = options.dup
277         sort_by = options.delete(:sort_by) || self.sort_by
278         order = options.delete(:order) || self.order
279         limit = options.delete(:limit) if sort_by or order
280         options[:attributes] |= ["objectClass"] if options[:attributes]
281
282         results = search(options).collect do |dn, attrs|
283           instantiate([dn, attrs, {:connection => options[:connection]}])
284         end
285         return results if sort_by.nil? and order.nil?
286
287         sort_by ||= "dn"
288         if sort_by.downcase == "dn"
289           results = results.sort_by {|result| DN.parse(result.dn)}
290         else
291           results = results.sort_by {|result| result.send(sort_by)}
292         end
293
294         results.reverse! if normalize_sort_order(order || "ascend") == :descend
295         results = results[0, limit] if limit
296         results
297       end
298
299       def find_from_dns(dns, options)
300         expects_array = dns.first.is_a?(Array)
301         return [] if expects_array and dns.first.empty?
302
303         dns = dns.flatten.compact.uniq
304
305         case dns.size
306         when 0
307           raise EntryNotFound, _("Couldn't find %s without a DN") % name
308         when 1
309           result = find_one(dns.first, options)
310           expects_array ? [result] : result
311         else
312           find_some(dns, options)
313         end
314       end
315
316       def find_one(dn, options)
317         attr, value, prefix = split_search_value(dn)
318         filter = [attr || ensure_search_attribute,
319                   Escape.ldap_filter_escape(value)]
320         filter = [:and, filter, options[:filter]] if options[:filter]
321         options = {:prefix => prefix}.merge(options.merge(:filter => filter))
322         result = find_initial(options)
323         if result
324           result
325         else
326           args = [self.is_a?(Class) ? name : self.class.name,
327                   dn]
328           if options[:filter]
329             format = _("Couldn't find %s: DN: %s: filter: %s")
330             args << options[:filter].inspect
331           else
332             format = _("Couldn't find %s: DN: %s")
333           end
334           raise EntryNotFound, format % args
335         end
336       end
337
338       def find_some(dns, options)
339         dn_filters = dns.collect do |dn|
340           attr, value, prefix = split_search_value(dn)
341           attr ||= ensure_search_attribute
342           filter = [attr, value]
343           if prefix
344             filter = [:and,
345                       filter,
346                       [dn, "*,#{Escape.ldap_filter_escape(prefix)},#{base}"]]
347           end
348           filter
349         end
350         filter = [:or, *dn_filters]
351         filter = [:and, filter, options[:filter]] if options[:filter]
352         result = find_every(options.merge(:filter => filter))
353         if result.size == dns.size
354           result
355         else
356           args = [self.is_a?(Class) ? name : self.class.name,
357                   dns.join(", ")]
358           if options[:filter]
359             format = _("Couldn't find all %s: DNs (%s): filter: %s")
360             args << options[:filter].inspect
361           else
362             format = _("Couldn't find all %s: DNs (%s)")
363           end
364           raise EntryNotFound, format % args
365         end
366       end
367
368       def ensure_dn(target)
369         attr, value, prefix = split_search_value(target)
370         "#{attr || dn_attribute}=#{value},#{prefix || base}"
371       end
372     end
373
374     module LDIF
375       def dump(options={})
376         ldif = Ldif.new
377         options = {:base => base, :scope => scope}.merge(options)
378         options[:connection] ||= connection
379         options[:connection].search(options) do |dn, attributes|
380           ldif << Ldif::Record.new(dn, attributes)
381         end
382         return "" if ldif.records.empty?
383         ldif.to_s
384       end
385
386       def to_ldif_record(dn, attributes)
387         Ldif::Record.new(dn, attributes)
388       end
389
390       def to_ldif(dn, attributes)
391         Ldif.new([to_ldif_record(dn, attributes)]).to_s
392       end
393
394       def load(ldif, options={})
395         return if ldif.blank?
396         Ldif.parse(ldif).each do |record|
397           record.load(self, options)
398         end
399       end
400
401       module ContentRecordLoadable
402         def load(operator, options)
403           operator.add_entry(dn, attributes, options)
404         end
405       end
406       Ldif::ContentRecord.send(:include, ContentRecordLoadable)
407
408       module AddRecordLoadable
409         def load(operator, options)
410           entries = attributes.collect do |key, value|
411             [:add, key, value]
412           end
413           options = {:controls => controls}.merge(options)
414           operator.modify_entry(dn, entries, options)
415         end
416       end
417       Ldif::AddRecord.send(:include, AddRecordLoadable)
418
419       module DeleteRecordLoadable
420         def load(operator, options)
421           operator.delete_entry(dn, {:controls => controls}.merge(options))
422         end
423       end
424       Ldif::DeleteRecord.send(:include, DeleteRecordLoadable)
425
426       module ModifyNameRecordLoadable
427         def load(operator, options)
428           operator.modify_rdn_entry(dn, new_rdn, delete_old_rdn?, new_superior,
429                                     {:controls => controls}.merge(options))
430         end
431       end
432       Ldif::ModifyNameRecord.send(:include, ModifyNameRecordLoadable)
433
434       module ModifyRecordLoadable
435         def load(operator, options)
436           modify_entries = operations.inject([]) do |result, operation|
437             result + operation.to_modify_entries
438           end
439           return if modify_entries.empty?
440           operator.modify_entry(dn, modify_entries,
441                                 {:controls => controls}.merge(options))
442         end
443
444         module AddOperationModifiable
445           def to_modify_entries
446             attributes.collect do |key, value|
447               [:add, key, value]
448             end
449           end
450         end
451         Ldif::ModifyRecord::AddOperation.send(:include, AddOperationModifiable)
452
453         module DeleteOperationModifiable
454           def to_modify_entries
455             return [[:delete, full_attribute_name, []]] if attributes.empty?
456             attributes.collect do |key, value|
457               [:delete, key, value]
458             end
459           end
460         end
461         Ldif::ModifyRecord::DeleteOperation.send(:include,
462                                                  DeleteOperationModifiable)
463
464         module ReplaceOperationModifiable
465           def to_modify_entries
466             return [[:replace, full_attribute_name, []]] if attributes.empty?
467             attributes.collect do |key, value|
468               [:replace, key, value]
469             end
470           end
471         end
472         Ldif::ModifyRecord::ReplaceOperation.send(:include,
473                                                   ReplaceOperationModifiable)
474       end
475       Ldif::ModifyRecord.send(:include, ModifyRecordLoadable)
476     end
477
478     module Delete
479       def destroy(targets, options={})
480         targets = [targets] unless targets.is_a?(Array)
481         targets.each do |target|
482           find(target, options).destroy
483         end
484       end
485
486       def destroy_all(options_or_filter=nil, deprecated_options=nil)
487         if deprecated_options.nil?
488           if options_or_filter.is_a?(String)
489             options = {:filter => options_or_filter}
490           else
491             options = (options_or_filter || {}).dup
492           end
493         else
494           options = deprecated_options.merge(:filter => options_or_filter)
495         end
496
497         find(:all, options).sort_by do |target|
498           target.dn
499         end.each do |target|
500           target.destroy
501         end
502       end
503
504       def delete(targets, options={})
505         targets = [targets] unless targets.is_a?(Array)
506         targets = targets.collect do |target|
507           ensure_dn_attribute(ensure_base(target))
508         end
509         delete_entry(targets, options)
510       end
511
512       def delete_entry(dn, options={})
513         options[:connection] ||= connection
514         begin
515           options[:connection].delete(dn, options)
516         rescue Error
517           format = _("Failed to delete LDAP entry: <%s>: %s")
518           raise DeleteError.new(format % [dn.inspect, $!.message])
519         end
520       end
521
522       def delete_all(options_or_filter=nil, deprecated_options=nil)
523         if deprecated_options.nil?
524           if options_or_filter.is_a?(String)
525             options = {:filter => options_or_filter}
526           else
527             options = (options_or_filter || {}).dup
528           end
529         else
530           options = deprecated_options.merge(:filter => options_or_filter)
531         end
532         targets = search(options).collect do |dn, attributes|
533           dn
534         end.sort_by do |dn|
535           dn.upcase.reverse
536         end.reverse
537
538         delete_entry(targets, options)
539       end
540     end
541
542     module Update
543       def add_entry(dn, attributes, options={})
544         unnormalized_attributes = attributes.collect do |key, value|
545           [:add, key, unnormalize_attribute(key, value)]
546         end
547         options[:connection] ||= connection
548         options[:connection].add(dn, unnormalized_attributes, options)
549       end
550
551       def modify_entry(dn, attributes, options={})
552         return if attributes.empty?
553         unnormalized_attributes = attributes.collect do |type, key, value|
554           [type, key, unnormalize_attribute(key, value)]
555         end
556         options[:connection] ||= connection
557         options[:connection].modify(dn, unnormalized_attributes, options)
558       end
559
560       def modify_rdn_entry(dn, new_rdn, delete_old_rdn, new_superior, options={})
561         options[:connection] ||= connection
562         options[:connection].modify_rdn(dn, new_rdn, delete_old_rdn,
563                                         new_superior, options)
564       end
565
566       def update(dn, attributes, options={})
567         if dn.is_a?(Array)
568           i = -1
569           dns = dn
570           dns.collect do |_dn|
571             i += 1
572             update(_dn, attributes[i], options)
573           end
574         else
575           object = find(dn, options)
576           object.update_attributes(attributes)
577           object
578         end
579       end
580
581       def update_all(attributes, filter=nil, options={})
582         search_options = options.dup
583         if filter
584           if filter.is_a?(String) and /[=\(\)&\|]/ !~ filter
585             search_options = search_options.merge(:value => filter)
586           else
587             search_options = search_options.merge(:filter => filter)
588           end
589         end
590         targets = search(search_options).collect do |dn, attrs|
591           dn
592         end
593
594         unnormalized_attributes = attributes.collect do |name, value|
595           normalized_name, normalized_value = normalize_attribute(name, value)
596           [:replace, normalized_name,
597            unnormalize_attribute(normalized_name, normalized_value)]
598         end
599         options[:connection] ||= connection
600         conn = options[:connection]
601         targets.each do |dn|
602           conn.modify(dn, unnormalized_attributes, options)
603         end
604       end
605     end
606   end
607 end