powerhome-activeldap 3.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.yardopts +6 -0
- data/COPYING +340 -0
- data/Gemfile +12 -0
- data/LICENSE +59 -0
- data/README.textile +140 -0
- data/TODO +32 -0
- data/benchmark/README.md +64 -0
- data/benchmark/bench-backend.rb +247 -0
- data/benchmark/bench-instantiate.rb +98 -0
- data/benchmark/config.yaml.sample +5 -0
- data/doc/text/development.textile +54 -0
- data/doc/text/news.textile +811 -0
- data/doc/text/rails.textile +144 -0
- data/doc/text/tutorial.textile +1010 -0
- data/examples/config.yaml.example +5 -0
- data/examples/example.der +0 -0
- data/examples/example.jpg +0 -0
- data/examples/groupadd +41 -0
- data/examples/groupdel +35 -0
- data/examples/groupls +49 -0
- data/examples/groupmod +42 -0
- data/examples/lpasswd +55 -0
- data/examples/objects/group.rb +13 -0
- data/examples/objects/ou.rb +4 -0
- data/examples/objects/user.rb +20 -0
- data/examples/ouadd +38 -0
- data/examples/useradd +45 -0
- data/examples/useradd-binary +53 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +50 -0
- data/examples/usermod-binary-add-time +54 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +85 -0
- data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
- data/lib/active_ldap/acts/tree.rb +78 -0
- data/lib/active_ldap/adapter/base.rb +707 -0
- data/lib/active_ldap/adapter/jndi.rb +184 -0
- data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
- data/lib/active_ldap/adapter/ldap.rb +290 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
- data/lib/active_ldap/adapter/net_ldap.rb +309 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
- data/lib/active_ldap/association/belongs_to.rb +47 -0
- data/lib/active_ldap/association/belongs_to_many.rb +58 -0
- data/lib/active_ldap/association/children.rb +21 -0
- data/lib/active_ldap/association/collection.rb +105 -0
- data/lib/active_ldap/association/has_many.rb +31 -0
- data/lib/active_ldap/association/has_many_utils.rb +44 -0
- data/lib/active_ldap/association/has_many_wrap.rb +75 -0
- data/lib/active_ldap/association/proxy.rb +107 -0
- data/lib/active_ldap/associations.rb +205 -0
- data/lib/active_ldap/attribute_methods.rb +23 -0
- data/lib/active_ldap/attribute_methods/before_type_cast.rb +24 -0
- data/lib/active_ldap/attribute_methods/dirty.rb +43 -0
- data/lib/active_ldap/attribute_methods/query.rb +31 -0
- data/lib/active_ldap/attribute_methods/read.rb +44 -0
- data/lib/active_ldap/attribute_methods/write.rb +38 -0
- data/lib/active_ldap/attributes.rb +176 -0
- data/lib/active_ldap/base.rb +1410 -0
- data/lib/active_ldap/callbacks.rb +71 -0
- data/lib/active_ldap/command.rb +49 -0
- data/lib/active_ldap/compatible.rb +44 -0
- data/lib/active_ldap/configuration.rb +147 -0
- data/lib/active_ldap/connection.rb +299 -0
- data/lib/active_ldap/distinguished_name.rb +291 -0
- data/lib/active_ldap/entry_attribute.rb +78 -0
- data/lib/active_ldap/escape.rb +12 -0
- data/lib/active_ldap/get_text.rb +20 -0
- data/lib/active_ldap/get_text/parser.rb +161 -0
- data/lib/active_ldap/helper.rb +92 -0
- data/lib/active_ldap/human_readable.rb +133 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/ldif.rb +930 -0
- data/lib/active_ldap/log_subscriber.rb +50 -0
- data/lib/active_ldap/object_class.rb +95 -0
- data/lib/active_ldap/operations.rb +624 -0
- data/lib/active_ldap/persistence.rb +100 -0
- data/lib/active_ldap/populate.rb +53 -0
- data/lib/active_ldap/railtie.rb +43 -0
- data/lib/active_ldap/railties/controller_runtime.rb +48 -0
- data/lib/active_ldap/schema.rb +701 -0
- data/lib/active_ldap/schema/syntaxes.rb +422 -0
- data/lib/active_ldap/timeout.rb +75 -0
- data/lib/active_ldap/timeout_stub.rb +17 -0
- data/lib/active_ldap/user_password.rb +99 -0
- data/lib/active_ldap/validations.rb +200 -0
- data/lib/active_ldap/version.rb +3 -0
- data/lib/active_ldap/xml.rb +139 -0
- data/lib/rails/generators/active_ldap/model/USAGE +18 -0
- data/lib/rails/generators/active_ldap/model/model_generator.rb +47 -0
- data/lib/rails/generators/active_ldap/model/templates/model_active_ldap.rb +3 -0
- data/lib/rails/generators/active_ldap/scaffold/scaffold_generator.rb +14 -0
- data/lib/rails/generators/active_ldap/scaffold/templates/ldap.yml +19 -0
- data/po/en/active-ldap.po +4029 -0
- data/po/ja/active-ldap.po +4060 -0
- data/test/add-phonetic-attribute-options-to-slapd.ldif +10 -0
- data/test/al-test-utils.rb +428 -0
- data/test/command.rb +111 -0
- data/test/config.yaml.sample +6 -0
- data/test/fixtures/lower_case_object_class_schema.rb +802 -0
- data/test/run-test.rb +34 -0
- data/test/test_acts_as_tree.rb +60 -0
- data/test/test_adapter.rb +121 -0
- data/test/test_associations.rb +701 -0
- data/test/test_attributes.rb +117 -0
- data/test/test_base.rb +1214 -0
- data/test/test_base_per_instance.rb +61 -0
- data/test/test_bind.rb +62 -0
- data/test/test_callback.rb +31 -0
- data/test/test_configuration.rb +40 -0
- data/test/test_connection.rb +82 -0
- data/test/test_connection_per_class.rb +112 -0
- data/test/test_connection_per_dn.rb +112 -0
- data/test/test_dirty.rb +98 -0
- data/test/test_dn.rb +172 -0
- data/test/test_find.rb +176 -0
- data/test/test_groupadd.rb +50 -0
- data/test/test_groupdel.rb +46 -0
- data/test/test_groupls.rb +107 -0
- data/test/test_groupmod.rb +51 -0
- data/test/test_ldif.rb +1890 -0
- data/test/test_load.rb +133 -0
- data/test/test_lpasswd.rb +75 -0
- data/test/test_object_class.rb +74 -0
- data/test/test_persistence.rb +131 -0
- data/test/test_reflection.rb +175 -0
- data/test/test_schema.rb +559 -0
- data/test/test_syntax.rb +444 -0
- data/test/test_user.rb +217 -0
- data/test/test_user_password.rb +108 -0
- data/test/test_useradd-binary.rb +62 -0
- data/test/test_useradd.rb +57 -0
- data/test/test_userdel.rb +48 -0
- data/test/test_userls.rb +91 -0
- data/test/test_usermod-binary-add-time.rb +65 -0
- data/test/test_usermod-binary-add.rb +64 -0
- data/test/test_usermod-binary-del.rb +66 -0
- data/test/test_usermod-lang-add.rb +59 -0
- data/test/test_usermod.rb +58 -0
- data/test/test_validation.rb +274 -0
- metadata +379 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module ActiveLdap
|
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
3
|
+
def self.runtime=(value)
|
|
4
|
+
Thread.current["active_ldap_runtime"] = value
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.runtime
|
|
8
|
+
Thread.current["active_ldap_runtime"] ||= 0
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.reset_runtime
|
|
12
|
+
rt, self.runtime = runtime, 0
|
|
13
|
+
rt
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
super
|
|
18
|
+
@odd_or_even = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def log_info(event)
|
|
22
|
+
self.class.runtime += event.duration
|
|
23
|
+
return unless logger.debug?
|
|
24
|
+
|
|
25
|
+
payload = event.payload
|
|
26
|
+
name = 'LDAP: %s (%.1fms)' % [payload[:name], event.duration]
|
|
27
|
+
info = payload[:info].inspect
|
|
28
|
+
|
|
29
|
+
if odd?
|
|
30
|
+
name_color, dump_color = "4;36;1", "0;1"
|
|
31
|
+
else
|
|
32
|
+
name_color, dump_color = "4;35;1", "0"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
debug " \e[#{name_color}m#{name}\e[0m: \e[#{dump_color}m#{info}\e[0m"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def odd?
|
|
39
|
+
@odd_or_even = !@odd_or_even
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def logger
|
|
43
|
+
ActiveLdap::Base.logger
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
ActiveLdap::LogSubscriber.attach_to :active_ldap
|
|
49
|
+
|
|
50
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module ActiveLdap
|
|
2
|
+
module ObjectClass
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.extend(ClassMethods)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def classes
|
|
9
|
+
required_classes.collect do |name|
|
|
10
|
+
schema.object_class(name)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_class(*target_classes)
|
|
16
|
+
replace_class(classes + target_classes)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ensure_recommended_classes
|
|
20
|
+
add_class(self.class.recommended_classes)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def remove_class(*target_classes)
|
|
24
|
+
replace_class(classes - target_classes)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def replace_class(*target_classes)
|
|
28
|
+
new_classes = target_classes.flatten.compact.uniq
|
|
29
|
+
assert_object_classes(new_classes)
|
|
30
|
+
if new_classes.sort != classes.sort
|
|
31
|
+
set_attribute('objectClass', new_classes)
|
|
32
|
+
clear_object_class_based_cache
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
alias_method(:classes=, :replace_class)
|
|
36
|
+
|
|
37
|
+
def classes
|
|
38
|
+
(get_attribute('objectClass', true) || []).dup
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def assert_object_classes(new_classes)
|
|
43
|
+
assert_valid_object_class_value_type(new_classes)
|
|
44
|
+
assert_valid_object_class_value(new_classes)
|
|
45
|
+
assert_have_all_required_classes(new_classes)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def assert_valid_object_class_value_type(new_classes)
|
|
49
|
+
invalid_classes = new_classes.reject do |new_class|
|
|
50
|
+
new_class.is_a?(String)
|
|
51
|
+
end
|
|
52
|
+
unless invalid_classes.empty?
|
|
53
|
+
format = _("Value in objectClass array is not a String: %s")
|
|
54
|
+
invalid_classes_info = invalid_classes.collect do |invalid_class|
|
|
55
|
+
"#{invalid_class.class}: #{invalid_class.inspect}"
|
|
56
|
+
end.join(", ")
|
|
57
|
+
raise TypeError, format % invalid_classes_info
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def assert_valid_object_class_value(new_classes)
|
|
62
|
+
_schema = schema
|
|
63
|
+
invalid_classes = new_classes.reject do |new_class|
|
|
64
|
+
!_schema.object_class(new_class).id.nil?
|
|
65
|
+
end
|
|
66
|
+
unless invalid_classes.empty?
|
|
67
|
+
format = _("unknown objectClass in LDAP server: %s")
|
|
68
|
+
message = format % invalid_classes.join(', ')
|
|
69
|
+
raise ObjectClassError, message
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def assert_have_all_required_classes(new_classes)
|
|
74
|
+
_schema = schema
|
|
75
|
+
normalized_new_classes = new_classes.collect(&:downcase)
|
|
76
|
+
required_classes = self.class.required_classes
|
|
77
|
+
required_classes = required_classes.reject do |required_class_name|
|
|
78
|
+
normalized_new_classes.include?(required_class_name.downcase) or
|
|
79
|
+
(normalized_new_classes.find do |new_class|
|
|
80
|
+
required_class = _schema.object_class(required_class_name)
|
|
81
|
+
_schema.object_class(new_class).super_class?(required_class)
|
|
82
|
+
end)
|
|
83
|
+
end
|
|
84
|
+
unless required_classes.empty?
|
|
85
|
+
format = _("Can't remove required objectClass: %s")
|
|
86
|
+
required_class_names = required_classes.collect do |required_class|
|
|
87
|
+
required_class = _schema.object_class(required_class)
|
|
88
|
+
self.class.human_object_class_name(required_class)
|
|
89
|
+
end
|
|
90
|
+
message = format % required_class_names.join(", ")
|
|
91
|
+
raise RequiredObjectClassMissed, message
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,624 @@
|
|
|
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(ClassOnlyDelete)
|
|
12
|
+
extend(Update)
|
|
13
|
+
extend(ClassOnlyUpdate)
|
|
14
|
+
|
|
15
|
+
include(Common)
|
|
16
|
+
include(Find)
|
|
17
|
+
include(LDIF)
|
|
18
|
+
include(Delete)
|
|
19
|
+
include(Update)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Common
|
|
25
|
+
VALID_SEARCH_OPTIONS = [:attribute, :value, :filter, :prefix,
|
|
26
|
+
:classes, :scope, :limit, :attributes,
|
|
27
|
+
:sort_by, :order, :connection, :base, :offset]
|
|
28
|
+
|
|
29
|
+
def search(options={}, &block)
|
|
30
|
+
validate_search_options(options)
|
|
31
|
+
attr = options[:attribute]
|
|
32
|
+
value = options[:value] || '*'
|
|
33
|
+
filter = options[:filter]
|
|
34
|
+
prefix = options[:prefix]
|
|
35
|
+
classes = options[:classes]
|
|
36
|
+
requested_attributes = options[:attributes]
|
|
37
|
+
|
|
38
|
+
value = value.first if value.is_a?(Array) and value.first.size == 1
|
|
39
|
+
|
|
40
|
+
_attr = nil
|
|
41
|
+
_prefix = nil
|
|
42
|
+
if attr.nil? or attr == dn_attribute
|
|
43
|
+
_attr, value, _prefix = split_search_value(value)
|
|
44
|
+
end
|
|
45
|
+
attr ||= _attr || ensure_search_attribute
|
|
46
|
+
prefix ||= _prefix
|
|
47
|
+
filter ||= [attr, value]
|
|
48
|
+
filter = [:and, filter, *object_class_filters(classes)]
|
|
49
|
+
_base = options[:base] ? [options[:base]] : [prefix, base]
|
|
50
|
+
_base = prepare_search_base(_base)
|
|
51
|
+
if options.has_key?(:ldap_scope)
|
|
52
|
+
message = _(":ldap_scope search option is deprecated. " \
|
|
53
|
+
"Use :scope instead.")
|
|
54
|
+
ActiveSupport::Deprecation.warn(message)
|
|
55
|
+
options[:scope] ||= options[:ldap_scope]
|
|
56
|
+
end
|
|
57
|
+
search_options = {
|
|
58
|
+
:base => _base,
|
|
59
|
+
:scope => options[:scope] || scope,
|
|
60
|
+
:filter => filter,
|
|
61
|
+
:limit => options[:limit],
|
|
62
|
+
:attributes => requested_attributes,
|
|
63
|
+
:sort_by => options[:sort_by] || sort_by,
|
|
64
|
+
:order => options[:order] || order,
|
|
65
|
+
}
|
|
66
|
+
options[:connection] ||= connection
|
|
67
|
+
values = []
|
|
68
|
+
requested_all_attributes_p =
|
|
69
|
+
(requested_attributes.nil? or requested_attributes.include?('*'))
|
|
70
|
+
options[:connection].search(search_options) do |dn, attrs|
|
|
71
|
+
attributes = {}
|
|
72
|
+
attrs.each do |key, _value|
|
|
73
|
+
if requested_all_attributes_p or requested_attributes.include?(key)
|
|
74
|
+
normalized_attribute, normalized_value =
|
|
75
|
+
normalize_attribute_options(key, _value)
|
|
76
|
+
attributes[normalized_attribute] ||= []
|
|
77
|
+
attributes[normalized_attribute].concat(normalized_value)
|
|
78
|
+
else
|
|
79
|
+
next
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
values << [dn, attributes]
|
|
83
|
+
end
|
|
84
|
+
values = values.collect {|_value| yield(_value)} if block_given?
|
|
85
|
+
values
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def exist?(dn, options={})
|
|
89
|
+
attr, value, prefix = split_search_value(dn)
|
|
90
|
+
|
|
91
|
+
options_for_leaf = {
|
|
92
|
+
:attribute => attr,
|
|
93
|
+
:value => value,
|
|
94
|
+
:prefix => prefix,
|
|
95
|
+
:limit => 1,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
attribute = attr || ensure_search_attribute
|
|
99
|
+
options_for_non_leaf = {
|
|
100
|
+
:attribute => attr,
|
|
101
|
+
:value => value,
|
|
102
|
+
:prefix => ["#{attribute}=#{value}", prefix].compact.join(","),
|
|
103
|
+
:limit => 1,
|
|
104
|
+
:scope => :base,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
!search(options_for_leaf.merge(options)).empty? or
|
|
108
|
+
!search(options_for_non_leaf.merge(options)).empty?
|
|
109
|
+
end
|
|
110
|
+
alias_method :exists?, :exist?
|
|
111
|
+
|
|
112
|
+
def count(options={})
|
|
113
|
+
search(options).size
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
def validate_search_options(options)
|
|
118
|
+
options.assert_valid_keys(VALID_SEARCH_OPTIONS)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def extract_options_from_args!(args)
|
|
122
|
+
args.last.is_a?(Hash) ? args.pop : {}
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def ensure_search_attribute(*candidates)
|
|
126
|
+
default_search_attribute || "objectClass"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def ensure_dn_attribute(target)
|
|
130
|
+
"#{dn_attribute}=" +
|
|
131
|
+
target.gsub(/^\s*#{Regexp.escape(dn_attribute)}\s*=\s*/i, '')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def ensure_base(target)
|
|
135
|
+
[truncate_base(target), base.to_s].reject do |component|
|
|
136
|
+
component.blank?
|
|
137
|
+
end.join(',')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def truncate_base(target)
|
|
141
|
+
return nil if target.blank?
|
|
142
|
+
return target if base.nil?
|
|
143
|
+
|
|
144
|
+
parsed_target = nil
|
|
145
|
+
if target.is_a?(DN)
|
|
146
|
+
parsed_target = target
|
|
147
|
+
elsif /,/ =~ target
|
|
148
|
+
begin
|
|
149
|
+
parsed_target = DN.parse(target)
|
|
150
|
+
rescue DistinguishedNameInvalid
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
return target if parsed_target.nil?
|
|
155
|
+
begin
|
|
156
|
+
(parsed_target - base).to_s
|
|
157
|
+
rescue ArgumentError
|
|
158
|
+
target
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def prepare_search_base(components)
|
|
163
|
+
components.compact.collect do |component|
|
|
164
|
+
case component
|
|
165
|
+
when String
|
|
166
|
+
component
|
|
167
|
+
when DN
|
|
168
|
+
component.to_s
|
|
169
|
+
else
|
|
170
|
+
DN.new(*component).to_s
|
|
171
|
+
end
|
|
172
|
+
end.reject{|x| x.empty?}.join(",")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def object_class_filters(classes=nil)
|
|
176
|
+
expected_classes = (classes || required_classes).collect do |name|
|
|
177
|
+
Escape.ldap_filter_escape(name)
|
|
178
|
+
end
|
|
179
|
+
unexpected_classes = excluded_classes.collect do |name|
|
|
180
|
+
Escape.ldap_filter_escape(name)
|
|
181
|
+
end
|
|
182
|
+
filters = []
|
|
183
|
+
unless expected_classes.empty?
|
|
184
|
+
filters << ["objectClass", "=", *expected_classes]
|
|
185
|
+
end
|
|
186
|
+
unless unexpected_classes.empty?
|
|
187
|
+
filters << [:not, [:or, ["objectClass", "=", *unexpected_classes]]]
|
|
188
|
+
end
|
|
189
|
+
filters
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def split_search_value(value)
|
|
193
|
+
attr = prefix = nil
|
|
194
|
+
|
|
195
|
+
begin
|
|
196
|
+
dn = DN.parse(value)
|
|
197
|
+
attr, value = dn.rdns.first.to_a.first
|
|
198
|
+
rest = dn.rdns[1..-1]
|
|
199
|
+
prefix = DN.new(*rest).to_s unless rest.empty?
|
|
200
|
+
rescue DistinguishedNameInputInvalid
|
|
201
|
+
return [attr, value, prefix]
|
|
202
|
+
rescue DistinguishedNameInvalid
|
|
203
|
+
begin
|
|
204
|
+
dn = DN.parse("DUMMY=#{value}")
|
|
205
|
+
_, value = dn.rdns.first.to_a.first
|
|
206
|
+
rest = dn.rdns[1..-1]
|
|
207
|
+
prefix = DN.new(*rest).to_s unless rest.empty?
|
|
208
|
+
rescue DistinguishedNameInvalid
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
prefix = nil if prefix == base
|
|
213
|
+
prefix = truncate_base(prefix) if prefix
|
|
214
|
+
[attr, value, prefix]
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
module Find
|
|
219
|
+
# find
|
|
220
|
+
#
|
|
221
|
+
# Finds the first match for value where |value| is the value of some
|
|
222
|
+
# |field|, or the wildcard match. This is only useful for derived classes.
|
|
223
|
+
# usage: Subclass.find(:all, :attribute => "cn", :value => "some*val")
|
|
224
|
+
# Subclass.find(:all, 'some*val')
|
|
225
|
+
def find(*args)
|
|
226
|
+
options = extract_options_from_args!(args)
|
|
227
|
+
args = [:first] if args.empty? and !options.empty?
|
|
228
|
+
case args.first
|
|
229
|
+
when :first
|
|
230
|
+
options[:value] ||= args[1]
|
|
231
|
+
find_initial(options)
|
|
232
|
+
when :last
|
|
233
|
+
options[:value] ||= args[1]
|
|
234
|
+
find_last(options)
|
|
235
|
+
when :all
|
|
236
|
+
options[:value] ||= args[1]
|
|
237
|
+
find_every(options)
|
|
238
|
+
else
|
|
239
|
+
find_from_dns(args, options)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# A convenience wrapper for <tt>find(:first,
|
|
244
|
+
# *args)</tt>. You can pass in all the same arguments
|
|
245
|
+
# to this method as you can to <tt>find(:first)</tt>.
|
|
246
|
+
def first(*args)
|
|
247
|
+
find(:first, *args)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# A convenience wrapper for <tt>find(:last,
|
|
251
|
+
# *args)</tt>. You can pass in all the same arguments
|
|
252
|
+
# to this method as you can to <tt>find(:last)</tt>.
|
|
253
|
+
def last(*args)
|
|
254
|
+
find(:last, *args)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# This is an alias for find(:all). You can pass in
|
|
258
|
+
# all the same arguments to this method as you can
|
|
259
|
+
# to find(:all)
|
|
260
|
+
def all(*args)
|
|
261
|
+
find(:all, *args)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
private
|
|
265
|
+
def find_initial(options)
|
|
266
|
+
find_every(options.merge(:limit => 1)).first
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def find_last(options)
|
|
270
|
+
order = options[:order] || self.order || 'ascend'
|
|
271
|
+
order = normalize_sort_order(order) == :ascend ? :descend : :ascend
|
|
272
|
+
find_initial(options.merge(:order => order))
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def normalize_sort_order(value)
|
|
276
|
+
case value.to_s
|
|
277
|
+
when /\Aasc(?:end)?\z/i
|
|
278
|
+
:ascend
|
|
279
|
+
when /\Adesc(?:end)?\z/i
|
|
280
|
+
:descend
|
|
281
|
+
else
|
|
282
|
+
raise ArgumentError, _("Invalid order: %s") % value.inspect
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def find_every(options)
|
|
287
|
+
options = options.dup
|
|
288
|
+
sort_by = options.delete(:sort_by) || self.sort_by
|
|
289
|
+
order = options.delete(:order) || self.order
|
|
290
|
+
limit = options.delete(:limit) if sort_by or order
|
|
291
|
+
offset = options.delete(:offset) || offset
|
|
292
|
+
options[:attributes] = options.delete(:attributes) || ['*']
|
|
293
|
+
options[:attributes] |= ['objectClass']
|
|
294
|
+
results = search(options).collect do |dn, attrs|
|
|
295
|
+
instantiate([dn, attrs, {:connection => options[:connection]}])
|
|
296
|
+
end
|
|
297
|
+
return results if sort_by.nil? and order.nil?
|
|
298
|
+
|
|
299
|
+
sort_by ||= "dn"
|
|
300
|
+
if sort_by.downcase == "dn"
|
|
301
|
+
results = results.sort_by {|result| DN.parse(result.dn)}
|
|
302
|
+
else
|
|
303
|
+
results = results.sort_by {|result| result.send(sort_by)}
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
results.reverse! if normalize_sort_order(order || "ascend") == :descend
|
|
307
|
+
results = results[offset, results.size] if offset
|
|
308
|
+
results = results[0, limit] if limit
|
|
309
|
+
results
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def find_from_dns(dns, options)
|
|
313
|
+
expects_array = dns.first.is_a?(Array)
|
|
314
|
+
return [] if expects_array and dns.first.empty?
|
|
315
|
+
|
|
316
|
+
dns = dns.flatten.compact.uniq
|
|
317
|
+
|
|
318
|
+
case dns.size
|
|
319
|
+
when 0
|
|
320
|
+
raise EntryNotFound, _("Couldn't find %s without a DN") % name
|
|
321
|
+
when 1
|
|
322
|
+
result = find_one(dns.first, options)
|
|
323
|
+
expects_array ? [result] : result
|
|
324
|
+
else
|
|
325
|
+
find_some(dns, options)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def find_one(dn, options)
|
|
330
|
+
attr, value, prefix = split_search_value(dn)
|
|
331
|
+
filter = [attr || ensure_search_attribute,
|
|
332
|
+
Escape.ldap_filter_escape(value)]
|
|
333
|
+
filter = [:and, filter, options[:filter]] if options[:filter]
|
|
334
|
+
options = {:prefix => prefix}.merge(options.merge(:filter => filter))
|
|
335
|
+
result = find_initial(options)
|
|
336
|
+
if result
|
|
337
|
+
result
|
|
338
|
+
else
|
|
339
|
+
args = [self.is_a?(Class) ? name : self.class.name,
|
|
340
|
+
dn]
|
|
341
|
+
if options[:filter]
|
|
342
|
+
format = _("Couldn't find %s: DN: %s: filter: %s")
|
|
343
|
+
args << options[:filter].inspect
|
|
344
|
+
else
|
|
345
|
+
format = _("Couldn't find %s: DN: %s")
|
|
346
|
+
end
|
|
347
|
+
raise EntryNotFound, format % args
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def find_some(dns, options)
|
|
352
|
+
dn_filters = dns.collect do |dn|
|
|
353
|
+
attr, value, prefix = split_search_value(dn)
|
|
354
|
+
attr ||= ensure_search_attribute
|
|
355
|
+
filter = [attr, value]
|
|
356
|
+
if prefix
|
|
357
|
+
filter = [:and,
|
|
358
|
+
filter,
|
|
359
|
+
[dn, "*,#{Escape.ldap_filter_escape(prefix)},#{base}"]]
|
|
360
|
+
end
|
|
361
|
+
filter
|
|
362
|
+
end
|
|
363
|
+
filter = [:or, *dn_filters]
|
|
364
|
+
filter = [:and, filter, options[:filter]] if options[:filter]
|
|
365
|
+
result = find_every(options.merge(:filter => filter))
|
|
366
|
+
if result.size == dns.size
|
|
367
|
+
result
|
|
368
|
+
else
|
|
369
|
+
args = [self.is_a?(Class) ? name : self.class.name,
|
|
370
|
+
dns.join(", ")]
|
|
371
|
+
if options[:filter]
|
|
372
|
+
format = _("Couldn't find all %s: DNs (%s): filter: %s")
|
|
373
|
+
args << options[:filter].inspect
|
|
374
|
+
else
|
|
375
|
+
format = _("Couldn't find all %s: DNs (%s)")
|
|
376
|
+
end
|
|
377
|
+
raise EntryNotFound, format % args
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def ensure_dn(target)
|
|
382
|
+
attr, value, prefix = split_search_value(target)
|
|
383
|
+
"#{attr || dn_attribute}=#{value},#{prefix || base}"
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
module LDIF
|
|
388
|
+
def dump(options={})
|
|
389
|
+
ldif = Ldif.new
|
|
390
|
+
options = {:base => base, :scope => scope}.merge(options)
|
|
391
|
+
options[:connection] ||= connection
|
|
392
|
+
options[:connection].search(options) do |dn, attributes|
|
|
393
|
+
ldif << Ldif::Record.new(dn, attributes)
|
|
394
|
+
end
|
|
395
|
+
return "" if ldif.records.empty?
|
|
396
|
+
ldif.to_s
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def to_ldif_record(dn, attributes)
|
|
400
|
+
Ldif::Record.new(dn, attributes)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def to_ldif(dn, attributes)
|
|
404
|
+
Ldif.new([to_ldif_record(dn, attributes)]).to_s
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def load(ldif, options={})
|
|
408
|
+
return if ldif.blank?
|
|
409
|
+
Ldif.parse(ldif).each do |record|
|
|
410
|
+
record.load(self, options)
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
module ContentRecordLoadable
|
|
415
|
+
def load(operator, options)
|
|
416
|
+
operator.add_entry(dn, attributes, options)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
Ldif::ContentRecord.send(:include, ContentRecordLoadable)
|
|
420
|
+
|
|
421
|
+
module AddRecordLoadable
|
|
422
|
+
def load(operator, options)
|
|
423
|
+
entries = attributes.collect do |key, value|
|
|
424
|
+
[:add, key, value]
|
|
425
|
+
end
|
|
426
|
+
options = {:controls => controls}.merge(options)
|
|
427
|
+
operator.modify_entry(dn, entries, options)
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
Ldif::AddRecord.send(:include, AddRecordLoadable)
|
|
431
|
+
|
|
432
|
+
module DeleteRecordLoadable
|
|
433
|
+
def load(operator, options)
|
|
434
|
+
operator.delete_entry(dn, {:controls => controls}.merge(options))
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
Ldif::DeleteRecord.send(:include, DeleteRecordLoadable)
|
|
438
|
+
|
|
439
|
+
module ModifyNameRecordLoadable
|
|
440
|
+
def load(operator, options)
|
|
441
|
+
operator.modify_rdn_entry(dn, new_rdn, delete_old_rdn?, new_superior,
|
|
442
|
+
{:controls => controls}.merge(options))
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
Ldif::ModifyNameRecord.send(:include, ModifyNameRecordLoadable)
|
|
446
|
+
|
|
447
|
+
module ModifyRecordLoadable
|
|
448
|
+
def load(operator, options)
|
|
449
|
+
modify_entries = operations.inject([]) do |result, operation|
|
|
450
|
+
result + operation.to_modify_entries
|
|
451
|
+
end
|
|
452
|
+
return if modify_entries.empty?
|
|
453
|
+
operator.modify_entry(dn, modify_entries,
|
|
454
|
+
{:controls => controls}.merge(options))
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
module AddOperationModifiable
|
|
458
|
+
def to_modify_entries
|
|
459
|
+
attributes.collect do |key, value|
|
|
460
|
+
[:add, key, value]
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
Ldif::ModifyRecord::AddOperation.send(:include, AddOperationModifiable)
|
|
465
|
+
|
|
466
|
+
module DeleteOperationModifiable
|
|
467
|
+
def to_modify_entries
|
|
468
|
+
return [[:delete, full_attribute_name, []]] if attributes.empty?
|
|
469
|
+
attributes.collect do |key, value|
|
|
470
|
+
[:delete, key, value]
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
Ldif::ModifyRecord::DeleteOperation.send(:include,
|
|
475
|
+
DeleteOperationModifiable)
|
|
476
|
+
|
|
477
|
+
module ReplaceOperationModifiable
|
|
478
|
+
def to_modify_entries
|
|
479
|
+
return [[:replace, full_attribute_name, []]] if attributes.empty?
|
|
480
|
+
attributes.collect do |key, value|
|
|
481
|
+
[:replace, key, value]
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
Ldif::ModifyRecord::ReplaceOperation.send(:include,
|
|
486
|
+
ReplaceOperationModifiable)
|
|
487
|
+
end
|
|
488
|
+
Ldif::ModifyRecord.send(:include, ModifyRecordLoadable)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
module Delete
|
|
492
|
+
def destroy_all(options_or_filter=nil, deprecated_options=nil)
|
|
493
|
+
if deprecated_options.nil?
|
|
494
|
+
if options_or_filter.is_a?(String)
|
|
495
|
+
options = {:filter => options_or_filter}
|
|
496
|
+
else
|
|
497
|
+
options = (options_or_filter || {}).dup
|
|
498
|
+
end
|
|
499
|
+
else
|
|
500
|
+
options = deprecated_options.merge(:filter => options_or_filter)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
find(:all, options).sort_by do |target|
|
|
504
|
+
target.dn
|
|
505
|
+
end.each do |target|
|
|
506
|
+
target.destroy
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def delete_all(options_or_filter=nil, deprecated_options=nil)
|
|
511
|
+
if deprecated_options.nil?
|
|
512
|
+
if options_or_filter.is_a?(String)
|
|
513
|
+
options = {:filter => options_or_filter}
|
|
514
|
+
else
|
|
515
|
+
options = (options_or_filter || {}).dup
|
|
516
|
+
end
|
|
517
|
+
else
|
|
518
|
+
options = deprecated_options.merge(:filter => options_or_filter)
|
|
519
|
+
end
|
|
520
|
+
targets = search(options).collect do |dn, attributes|
|
|
521
|
+
dn
|
|
522
|
+
end.sort_by do |dn|
|
|
523
|
+
dn.upcase.reverse
|
|
524
|
+
end.reverse
|
|
525
|
+
|
|
526
|
+
delete_entry(targets, options)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def delete_entry(dn, options={})
|
|
530
|
+
options[:connection] ||= connection
|
|
531
|
+
begin
|
|
532
|
+
options[:connection].delete(dn, options)
|
|
533
|
+
rescue Error
|
|
534
|
+
format = _("Failed to delete LDAP entry: <%s>: %s")
|
|
535
|
+
raise DeleteError.new(format % [dn.inspect, $!.message])
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
module ClassOnlyDelete
|
|
541
|
+
def destroy(targets, options={})
|
|
542
|
+
targets = [targets] unless targets.is_a?(Array)
|
|
543
|
+
targets.each do |target|
|
|
544
|
+
find(target, options).destroy
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def delete(targets, options={})
|
|
549
|
+
targets = [targets] unless targets.is_a?(Array)
|
|
550
|
+
targets = targets.collect do |target|
|
|
551
|
+
ensure_dn_attribute(ensure_base(target))
|
|
552
|
+
end
|
|
553
|
+
delete_entry(targets, options)
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
module Update
|
|
558
|
+
def add_entry(dn, attributes, options={})
|
|
559
|
+
unnormalized_attributes = attributes.collect do |key, value|
|
|
560
|
+
[:add, key, unnormalize_attribute(key, value)]
|
|
561
|
+
end
|
|
562
|
+
options[:connection] ||= connection
|
|
563
|
+
options[:connection].add(dn, unnormalized_attributes, options)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def modify_entry(dn, attributes, options={})
|
|
567
|
+
return if attributes.empty?
|
|
568
|
+
unnormalized_attributes = attributes.collect do |type, key, value|
|
|
569
|
+
[type, key, unnormalize_attribute(key, value)]
|
|
570
|
+
end
|
|
571
|
+
options[:connection] ||= connection
|
|
572
|
+
options[:connection].modify(dn, unnormalized_attributes, options)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def modify_rdn_entry(dn, new_rdn, delete_old_rdn, new_superior, options={})
|
|
576
|
+
options[:connection] ||= connection
|
|
577
|
+
options[:connection].modify_rdn(dn, new_rdn, delete_old_rdn,
|
|
578
|
+
new_superior, options)
|
|
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
|
+
|
|
607
|
+
module ClassOnlyUpdate
|
|
608
|
+
def update(dn, attributes, options={})
|
|
609
|
+
if dn.is_a?(Array)
|
|
610
|
+
i = -1
|
|
611
|
+
dns = dn
|
|
612
|
+
dns.collect do |_dn|
|
|
613
|
+
i += 1
|
|
614
|
+
update(_dn, attributes[i], options)
|
|
615
|
+
end
|
|
616
|
+
else
|
|
617
|
+
object = find(dn, options)
|
|
618
|
+
object.update_attributes(attributes)
|
|
619
|
+
object
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
end
|