activeldap 0.9.0 → 0.10.0
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.
- data/CHANGES +61 -0
- data/README +8 -1
- data/Rakefile +4 -1
- data/benchmark/bench-al.rb +12 -2
- data/examples/al-admin/app/controllers/account_controller.rb +4 -3
- data/examples/al-admin/app/controllers/application.rb +5 -2
- data/examples/al-admin/app/controllers/directory_controller.rb +3 -1
- data/examples/al-admin/app/controllers/users_controller.rb +19 -4
- data/examples/al-admin/app/controllers/welcome_controller.rb +4 -2
- data/examples/al-admin/app/helpers/application_helper.rb +7 -1
- data/examples/al-admin/app/helpers/url_helper.rb +4 -0
- data/examples/al-admin/app/models/ldap_user.rb +4 -0
- data/examples/al-admin/app/views/_entry/{_attributes_information.rhtml → _attributes_information.html.erb} +0 -0
- data/examples/al-admin/app/views/_entry/{_entry.rhtml → _entry.html.erb} +0 -0
- data/examples/al-admin/app/views/_schema/{_aliases.rhtml → _aliases.html.erb} +0 -0
- data/examples/al-admin/app/views/_switcher/{_after.rhtml → _after.html.erb} +0 -0
- data/examples/al-admin/app/views/_switcher/{_before.rhtml → _before.html.erb} +0 -0
- data/examples/al-admin/app/views/account/{login.rhtml → login.html.erb} +0 -0
- data/examples/al-admin/app/views/account/{sign_up.rhtml → sign_up.html.erb} +0 -0
- data/examples/al-admin/app/views/attributes/{_attributes.rhtml → _attributes.html.erb} +0 -0
- data/examples/al-admin/app/views/attributes/{_detail.rhtml → _detail.html.erb} +0 -0
- data/examples/al-admin/app/views/attributes/{index.rhtml → index.html.erb} +0 -0
- data/examples/al-admin/app/views/attributes/{show.rhtml → show.html.erb} +0 -0
- data/examples/al-admin/app/views/directory/{_tree.rhtml → _tree.html.erb} +0 -0
- data/examples/al-admin/app/views/directory/{_tree_view_js.rhtml → _tree_view_js.html.erb} +4 -5
- data/examples/al-admin/app/views/directory/{index.rhtml → index.html.erb} +0 -0
- data/examples/al-admin/app/views/directory/{populate.rhtml → populate.html.erb} +0 -0
- data/examples/al-admin/app/views/layouts/{_footer.rhtml → _footer.html.erb} +0 -0
- data/examples/al-admin/app/views/layouts/{_header_menu.rhtml → _header_menu.html.erb} +0 -0
- data/examples/al-admin/app/views/layouts/{_main_menu.rhtml → _main_menu.html.erb} +0 -0
- data/examples/al-admin/app/views/layouts/{application.rhtml → application.html.erb} +3 -2
- data/examples/al-admin/app/views/object_classes/{_attributes.rhtml → _attributes.html.erb} +0 -0
- data/examples/al-admin/app/views/object_classes/{_object_classes.rhtml → _object_classes.html.erb} +0 -0
- data/examples/al-admin/app/views/object_classes/{index.rhtml → index.html.erb} +0 -0
- data/examples/al-admin/app/views/object_classes/{show.rhtml → show.html.erb} +0 -0
- data/examples/al-admin/app/views/syntaxes/{_detail.rhtml → _detail.html.erb} +0 -0
- data/examples/al-admin/app/views/syntaxes/{_syntaxes.rhtml → _syntaxes.html.erb} +0 -0
- data/examples/al-admin/app/views/syntaxes/{index.rhtml → index.html.erb} +0 -0
- data/examples/al-admin/app/views/syntaxes/{show.rhtml → show.html.erb} +0 -0
- data/examples/al-admin/app/views/users/{_attributes_update_form.rhtml → _attributes_update_form.html.erb} +0 -0
- data/examples/al-admin/app/views/users/{_form.rhtml → _form.html.erb} +0 -0
- data/examples/al-admin/app/views/users/{_object_classes_update_form.rhtml → _object_classes_update_form.html.erb} +7 -1
- data/examples/al-admin/app/views/users/{_password_change_form.rhtml → _password_change_form.html.erb} +0 -0
- data/examples/al-admin/app/views/users/{edit.rhtml → edit.html.erb} +0 -0
- data/examples/al-admin/app/views/users/{index.rhtml → index.html.erb} +0 -0
- data/examples/al-admin/app/views/users/{show.rhtml → show.html.erb} +0 -0
- data/examples/al-admin/app/views/welcome/{index.rhtml → index.html.erb} +0 -0
- data/examples/al-admin/config/boot.rb +96 -32
- data/examples/al-admin/config/environment.rb +30 -36
- data/examples/al-admin/config/environments/development.rb +2 -5
- data/examples/al-admin/config/environments/production.rb +1 -0
- data/examples/al-admin/config/environments/test.rb +4 -1
- data/examples/al-admin/config/initializers/exception_notifier.rb +2 -0
- data/examples/al-admin/config/initializers/gettext.rb +1 -0
- data/examples/al-admin/config/initializers/inflections.rb +10 -0
- data/examples/al-admin/config/initializers/mime_types.rb +5 -0
- data/examples/al-admin/config/initializers/ralative_url_support.rb +1 -0
- data/examples/al-admin/config/routes.rb +24 -12
- data/examples/al-admin/lib/authenticated_system.rb +1 -1
- data/examples/al-admin/lib/tasks/gettext.rake +1 -1
- data/examples/al-admin/po/en/al-admin.po +102 -100
- data/examples/al-admin/po/ja/al-admin.po +112 -110
- data/examples/al-admin/po/nl/al-admin.po +117 -110
- data/examples/al-admin/public/javascripts/controls.js +484 -354
- data/examples/al-admin/public/javascripts/dragdrop.js +88 -58
- data/examples/al-admin/public/javascripts/effects.js +396 -364
- data/examples/al-admin/public/javascripts/prototype.js +2817 -1107
- data/examples/al-admin/public/stylesheets/base.css +5 -0
- data/examples/al-admin/script/performance/request +3 -0
- data/lib/active_ldap.rb +13 -10
- data/lib/active_ldap/adapter/base.rb +159 -43
- data/lib/active_ldap/adapter/jndi.rb +175 -0
- data/lib/active_ldap/adapter/jndi_connection.rb +180 -0
- data/lib/active_ldap/adapter/ldap.rb +91 -46
- data/lib/active_ldap/adapter/ldap_ext.rb +19 -5
- data/lib/active_ldap/adapter/net_ldap.rb +52 -44
- data/lib/active_ldap/association/has_many_wrap.rb +1 -1
- data/lib/active_ldap/attributes.rb +20 -95
- data/lib/active_ldap/base.rb +195 -186
- data/lib/active_ldap/callbacks.rb +33 -0
- data/lib/active_ldap/command.rb +3 -3
- data/lib/active_ldap/connection.rb +21 -3
- data/lib/active_ldap/distinguished_name.rb +18 -11
- data/lib/active_ldap/entry_attribute.rb +78 -0
- data/lib/active_ldap/human_readable.rb +20 -0
- data/lib/active_ldap/ldif.rb +860 -10
- data/lib/active_ldap/object_class.rb +6 -4
- data/lib/active_ldap/operations.rb +129 -22
- data/lib/active_ldap/schema.rb +118 -9
- data/lib/active_ldap/schema/syntaxes.rb +33 -16
- data/lib/active_ldap/validations.rb +74 -65
- data/po/en/active-ldap.po +378 -768
- data/po/ja/active-ldap.po +935 -868
- data/rails/plugin/active_ldap/init.rb +40 -2
- data/test/al-test-utils.rb +78 -58
- data/test/command.rb +51 -1
- data/test/test-unit-ext/priority.rb +29 -6
- data/test/test_adapter.rb +21 -2
- data/test/test_attributes.rb +13 -0
- data/test/test_base.rb +51 -1
- data/test/test_connection.rb +2 -1
- data/test/test_connection_per_class.rb +55 -1
- data/test/test_connection_per_dn.rb +29 -1
- data/test/test_find.rb +73 -0
- data/test/test_ldif.rb +1829 -15
- data/test/test_load.rb +126 -0
- data/test/test_object_class.rb +23 -5
- data/test/test_schema.rb +28 -0
- data/test/test_syntax.rb +22 -11
- data/test/test_user.rb +16 -25
- data/test/test_useradd-binary.rb +1 -1
- data/test/test_usermod-binary-add-time.rb +1 -1
- data/test/test_usermod-binary-add.rb +1 -1
- data/test/test_validation.rb +100 -22
- metadata +77 -71
- data/data/locale/en/LC_MESSAGES/active-ldap.mo +0 -0
- data/data/locale/ja/LC_MESSAGES/active-ldap.mo +0 -0
- data/examples/al-admin/app/views/layouts/_flash_box.rhtml +0 -4
- data/examples/al-admin/public/stylesheets/common.css +0 -2
- data/examples/al-admin/script/breakpointer +0 -3
@@ -8,6 +8,14 @@ module ActiveLdap
|
|
8
8
|
base.class_eval do
|
9
9
|
include ActiveRecord::Callbacks
|
10
10
|
|
11
|
+
unless respond_to?(:instantiate_with_callbacks)
|
12
|
+
extend ClassMethods
|
13
|
+
class << self
|
14
|
+
alias_method_chain :instantiate, :callbacks
|
15
|
+
end
|
16
|
+
alias_method_chain :initialize, :callbacks
|
17
|
+
end
|
18
|
+
|
11
19
|
def callback(method)
|
12
20
|
super
|
13
21
|
rescue ActiveRecord::ActiveRecordError
|
@@ -15,5 +23,30 @@ module ActiveLdap
|
|
15
23
|
end
|
16
24
|
end
|
17
25
|
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def instantiate_with_callbacks(record)
|
29
|
+
object = instantiate_without_callbacks(record)
|
30
|
+
|
31
|
+
if object.respond_to_without_attributes?(:after_find)
|
32
|
+
object.send(:callback, :after_find)
|
33
|
+
end
|
34
|
+
|
35
|
+
if object.respond_to_without_attributes?(:after_initialize)
|
36
|
+
object.send(:callback, :after_initialize)
|
37
|
+
end
|
38
|
+
|
39
|
+
object
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize_with_callbacks(attributes = nil) #:nodoc:
|
44
|
+
initialize_without_callbacks(attributes)
|
45
|
+
result = yield self if block_given?
|
46
|
+
if respond_to_without_attributes?(:after_initialize)
|
47
|
+
callback(:after_initialize)
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
18
51
|
end
|
19
52
|
end
|
data/lib/active_ldap/command.rb
CHANGED
@@ -38,11 +38,11 @@ module ActiveLdap
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def read_password(prompt, input=$stdin, output=$stdout)
|
41
|
-
output.print
|
42
|
-
system
|
41
|
+
output.print(prompt)
|
42
|
+
system("/bin/stty -echo") if input.tty?
|
43
43
|
input.gets.chomp
|
44
44
|
ensure
|
45
|
-
system
|
45
|
+
system("/bin/stty echo") if input.tty?
|
46
46
|
output.puts
|
47
47
|
end
|
48
48
|
end
|
@@ -88,7 +88,7 @@ module ActiveLdap
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def instantiate_adapter(config)
|
91
|
-
adapter = (config[:adapter] ||
|
91
|
+
adapter = (config[:adapter] || default_adapter)
|
92
92
|
normalized_adapter = adapter.downcase.gsub(/-/, "_")
|
93
93
|
adapter_method = "#{normalized_adapter}_connection"
|
94
94
|
unless Adapter::Base.respond_to?(adapter_method)
|
@@ -104,6 +104,10 @@ module ActiveLdap
|
|
104
104
|
Adapter::Base.send(adapter_method, config)
|
105
105
|
end
|
106
106
|
|
107
|
+
def default_adapter
|
108
|
+
@@default_adapter ||= guess_available_adapter
|
109
|
+
end
|
110
|
+
|
107
111
|
def connected?
|
108
112
|
active_connections[active_connection_name] ? true : false
|
109
113
|
end
|
@@ -152,6 +156,12 @@ module ActiveLdap
|
|
152
156
|
connection.schema
|
153
157
|
end
|
154
158
|
|
159
|
+
def reset_runtime
|
160
|
+
active_connections.inject(0) do |result, (name, connection)|
|
161
|
+
result + connection.reset_runtime
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
155
165
|
private
|
156
166
|
def active_connection_key(k=self)
|
157
167
|
k.name.empty? ? k.object_id : k.name
|
@@ -179,6 +189,10 @@ module ActiveLdap
|
|
179
189
|
end
|
180
190
|
@@active_connections.clear
|
181
191
|
end
|
192
|
+
|
193
|
+
def guess_available_adapter
|
194
|
+
defined?(java) ? "jndi" : "ldap"
|
195
|
+
end
|
182
196
|
end
|
183
197
|
|
184
198
|
def establish_connection(config=nil)
|
@@ -197,8 +211,12 @@ module ActiveLdap
|
|
197
211
|
|
198
212
|
def connection
|
199
213
|
conn = @connection
|
200
|
-
if
|
201
|
-
|
214
|
+
return conn if conn
|
215
|
+
|
216
|
+
if @dn or
|
217
|
+
(attribute_name_resolvable_without_connection? and
|
218
|
+
get_attribute_before_type_cast(dn_attribute)[1])
|
219
|
+
conn = self.class.active_connections[dn] || retrieve_connection
|
202
220
|
end
|
203
221
|
conn || self.class.connection
|
204
222
|
end
|
@@ -153,13 +153,24 @@ module ActiveLdap
|
|
153
153
|
def parse(source)
|
154
154
|
Parser.new(source).parse
|
155
155
|
end
|
156
|
+
|
157
|
+
def escape_value(value)
|
158
|
+
if /(\A | \z)/.match(value)
|
159
|
+
'"' + value.gsub(/([\\\"])/, '\\\\\1') + '"'
|
160
|
+
else
|
161
|
+
value.gsub(/([,=\+<>#;\\\"])/, '\\\\\1')
|
162
|
+
end
|
163
|
+
end
|
156
164
|
end
|
157
165
|
|
158
166
|
attr_reader :rdns
|
159
167
|
def initialize(*rdns)
|
160
168
|
@rdns = rdns.collect do |rdn|
|
161
|
-
|
162
|
-
|
169
|
+
if rdn.is_a?(Array) and rdn.size == 2
|
170
|
+
{rdn[0] => rdn[1]}
|
171
|
+
else
|
172
|
+
rdn
|
173
|
+
end
|
163
174
|
end
|
164
175
|
end
|
165
176
|
|
@@ -212,11 +223,15 @@ module ActiveLdap
|
|
212
223
|
rdn.sort_by do |type, value|
|
213
224
|
type.upcase
|
214
225
|
end.collect do |type, value|
|
215
|
-
"#{type}=#{
|
226
|
+
"#{type}=#{self.class.escape_value(value)}"
|
216
227
|
end.join("+")
|
217
228
|
end.join(",")
|
218
229
|
end
|
219
230
|
|
231
|
+
def to_human_readable_format
|
232
|
+
to_s.inspect
|
233
|
+
end
|
234
|
+
|
220
235
|
private
|
221
236
|
def normalize(rdns)
|
222
237
|
rdns.collect do |rdn|
|
@@ -237,14 +252,6 @@ module ActiveLdap
|
|
237
252
|
[key, value]
|
238
253
|
end
|
239
254
|
end
|
240
|
-
|
241
|
-
def escape(value)
|
242
|
-
if /(\A | \z)/.match(value)
|
243
|
-
'"' + value.gsub(/([\\\"])/, '\\\\\1') + '"'
|
244
|
-
else
|
245
|
-
value.gsub(/([,=\+<>#;\\\"])/, '\\\\\1')
|
246
|
-
end
|
247
|
-
end
|
248
255
|
end
|
249
256
|
|
250
257
|
DN = DistinguishedName
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "active_ldap/attributes"
|
2
|
+
|
3
|
+
module ActiveLdap
|
4
|
+
class EntryAttribute
|
5
|
+
include Attributes::Normalizable
|
6
|
+
|
7
|
+
attr_reader :must, :may, :object_classes, :schemata
|
8
|
+
def initialize(schema, object_classes)
|
9
|
+
@schemata = {}
|
10
|
+
@names = {}
|
11
|
+
@normalized_names = {}
|
12
|
+
@aliases = {}
|
13
|
+
@must = []
|
14
|
+
@may = []
|
15
|
+
@object_classes = []
|
16
|
+
register(schema.attribute('objectClass'))
|
17
|
+
object_classes.each do |objc|
|
18
|
+
# get all attributes for the class
|
19
|
+
object_class = schema.object_class(objc)
|
20
|
+
@object_classes << object_class
|
21
|
+
@must.concat(object_class.must)
|
22
|
+
@may.concat(object_class.may)
|
23
|
+
end
|
24
|
+
@must.uniq!
|
25
|
+
@may.uniq!
|
26
|
+
(@must + @may).each do |attr|
|
27
|
+
# Update attr_method with appropriate
|
28
|
+
register(attr)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def names(normalize=false)
|
33
|
+
names = @names.keys
|
34
|
+
if normalize
|
35
|
+
names.collect do |name|
|
36
|
+
normalize(name)
|
37
|
+
end.uniq
|
38
|
+
else
|
39
|
+
names
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def normalize(name, allow_normalized_name=false)
|
44
|
+
return name if name.nil?
|
45
|
+
return nil if @names.empty? and @aliases.empty?
|
46
|
+
name = name.to_s
|
47
|
+
real_name = @names[name]
|
48
|
+
real_name ||= @aliases[Inflector.underscore(name)]
|
49
|
+
if real_name
|
50
|
+
real_name
|
51
|
+
elsif allow_normalized_name
|
52
|
+
return nil if @normalized_names.empty?
|
53
|
+
@normalized_names[normalize_attribute_name(name)]
|
54
|
+
else
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def all_names
|
60
|
+
@names.keys + @aliases.keys
|
61
|
+
end
|
62
|
+
|
63
|
+
# register
|
64
|
+
#
|
65
|
+
# Make a method entry for _every_ alias of a valid attribute and map it
|
66
|
+
# onto the first attribute passed in.
|
67
|
+
def register(attribute)
|
68
|
+
real_name = attribute.name
|
69
|
+
return if @schemata.has_key?(real_name)
|
70
|
+
@schemata[real_name] = attribute
|
71
|
+
([real_name] + attribute.aliases).each do |name|
|
72
|
+
@names[name] = real_name
|
73
|
+
@aliases[Inflector.underscore(name)] = real_name
|
74
|
+
@normalized_names[normalize_attribute_name(name)] = real_name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -107,6 +107,26 @@ module ActiveLdap
|
|
107
107
|
return nil if description.nil?
|
108
108
|
"LDAP|Description|Syntax|#{syntax.id}|#{description}"
|
109
109
|
end
|
110
|
+
|
111
|
+
def human_readable_format(object)
|
112
|
+
case object
|
113
|
+
when Array
|
114
|
+
"[#{object.collect {|value| human_readable_format(value)}.join(', ')}]"
|
115
|
+
when Hash
|
116
|
+
formatted_values = []
|
117
|
+
object.each do |key, value|
|
118
|
+
formatted_values << [human_readable_format(key),
|
119
|
+
human_readable_format(value)].join("=>")
|
120
|
+
end
|
121
|
+
"{#{formatted_values.join(', ')}}"
|
122
|
+
else
|
123
|
+
if object.respond_to?(:to_human_readable_format)
|
124
|
+
object.to_human_readable_format
|
125
|
+
else
|
126
|
+
object.inspect
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
110
130
|
end
|
111
131
|
end
|
112
132
|
end
|
data/lib/active_ldap/ldif.rb
CHANGED
@@ -1,11 +1,82 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require
|
1
|
+
require "strscan"
|
2
|
+
require "base64"
|
3
|
+
require "uri"
|
4
|
+
require "open-uri"
|
5
5
|
|
6
6
|
module ActiveLdap
|
7
7
|
class Ldif
|
8
|
-
|
8
|
+
module Attributes
|
9
|
+
module_function
|
10
|
+
def encode(attributes)
|
11
|
+
return "" if attributes.empty?
|
12
|
+
|
13
|
+
result = ""
|
14
|
+
normalize(attributes).sort_by {|name,| name}.each do |name, values|
|
15
|
+
values.each do |options, value|
|
16
|
+
result << Attribute.encode([name, *options].join(";"), value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def normalize(attributes)
|
23
|
+
result = {}
|
24
|
+
attributes.each do |name, values|
|
25
|
+
result[name] = Attribute.normalize_value(values).sort
|
26
|
+
end
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Attribute
|
32
|
+
SIZE = 75
|
33
|
+
|
34
|
+
module_function
|
35
|
+
def encode(name, value)
|
36
|
+
return "#{name}:\n" if value.blank?
|
37
|
+
result = "#{name}:"
|
38
|
+
|
39
|
+
if value[-1, 1] == ' ' or /\A#{Parser::SAFE_STRING}\z/u !~ value
|
40
|
+
result << ":"
|
41
|
+
value = [value].pack("m").gsub(/\n/u, '')
|
42
|
+
end
|
43
|
+
result << " "
|
44
|
+
|
45
|
+
first_line_value_size = SIZE - result.size
|
46
|
+
if value.size > first_line_value_size
|
47
|
+
first_line_value = value[0, first_line_value_size]
|
48
|
+
rest_value = value[first_line_value_size..-1]
|
49
|
+
else
|
50
|
+
first_line_value = value
|
51
|
+
rest_value = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
result << "#{first_line_value}\n"
|
55
|
+
return result if rest_value.nil?
|
56
|
+
|
57
|
+
rest_value.scan(/.{1,#{SIZE - 1}}/u).each do |line|
|
58
|
+
result << " #{line}\n"
|
59
|
+
end
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
def normalize_value(value, result=[])
|
64
|
+
case value
|
65
|
+
when Array
|
66
|
+
value.each {|val| normalize_value(val, result)}
|
67
|
+
when Hash
|
68
|
+
value.each do |option, val|
|
69
|
+
normalize_value(val).each do |options, v|
|
70
|
+
result << [[option] + options, v]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
result
|
74
|
+
else
|
75
|
+
result << [[], value]
|
76
|
+
end
|
77
|
+
result
|
78
|
+
end
|
79
|
+
end
|
9
80
|
|
10
81
|
class Parser
|
11
82
|
include GetTextSupport
|
@@ -17,28 +88,478 @@ module ActiveLdap
|
|
17
88
|
@source = source
|
18
89
|
end
|
19
90
|
|
91
|
+
ATTRIBUTE_TYPE_CHARS = /[a-zA-Z][a-zA-Z0-9\-]*/u
|
92
|
+
SAFE_CHAR = /[\x01-\x09\x0B-\x0C\x0E-\x7F]/u
|
93
|
+
SAFE_INIT_CHAR = /[\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x39\x3B\x3D-\x7F]/u
|
94
|
+
SAFE_STRING = /#{SAFE_INIT_CHAR}#{SAFE_CHAR}*/u
|
95
|
+
FILL = / */u
|
20
96
|
def parse
|
21
97
|
return @ldif if @ldif
|
22
98
|
|
23
|
-
scanner =
|
24
|
-
raise version_spec_is_missing unless scanner.scan(/version
|
99
|
+
@scanner = Scanner.new(@source)
|
100
|
+
raise version_spec_is_missing unless @scanner.scan(/version:/u)
|
101
|
+
@scanner.scan(FILL)
|
25
102
|
|
26
|
-
version =
|
27
|
-
raise
|
103
|
+
version = @scanner.scan(/\d+/u)
|
104
|
+
raise version_number_is_missing if version.nil?
|
105
|
+
|
106
|
+
version = Integer(version)
|
107
|
+
raise unsupported_version(version) if version != 1
|
108
|
+
|
109
|
+
raise separator_is_missing unless @scanner.scan_separators
|
110
|
+
|
111
|
+
records = parse_records
|
112
|
+
|
113
|
+
@ldif = LDIF.new(records)
|
28
114
|
end
|
29
115
|
|
30
116
|
private
|
117
|
+
def read_base64_value
|
118
|
+
value = @scanner.scan(/[a-zA-Z0-9\+\/=]+/u)
|
119
|
+
return nil if value.nil?
|
120
|
+
Base64.decode64(value).chomp
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_external_file
|
124
|
+
uri_string = @scanner.scan(URI::REGEXP::ABS_URI)
|
125
|
+
raise uri_is_missing if uri_string.nil?
|
126
|
+
uri = nil
|
127
|
+
begin
|
128
|
+
uri = URI.parse(uri_string)
|
129
|
+
rescue URI::Error
|
130
|
+
raise invalid_uri(uri_string, $!.message)
|
131
|
+
end
|
132
|
+
|
133
|
+
if uri.scheme == "file"
|
134
|
+
File.open(uri.path, "rb").read
|
135
|
+
else
|
136
|
+
uri.read
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse_dn(dn_string)
|
141
|
+
DN.parse(dn_string).to_s
|
142
|
+
rescue DistinguishedNameInvalid
|
143
|
+
raise invalid_dn(dn_string, $!.reason)
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_attributes(least=0, &block)
|
147
|
+
i = 0
|
148
|
+
attributes = {}
|
149
|
+
block ||= Proc.new {@scanner.check_separator}
|
150
|
+
loop do
|
151
|
+
i += 1
|
152
|
+
if i >= least
|
153
|
+
break if block.call or @scanner.eos?
|
154
|
+
end
|
155
|
+
type, options, value = parse_attribute
|
156
|
+
if @scanner.scan_separator.nil? and !@scanner.eos?
|
157
|
+
raise separator_is_missing
|
158
|
+
end
|
159
|
+
attributes[type] ||= []
|
160
|
+
container = attributes[type]
|
161
|
+
options.each do |option|
|
162
|
+
parent = container.find do |val|
|
163
|
+
val.is_a?(Hash) and val.has_key?(option)
|
164
|
+
end
|
165
|
+
if parent.nil?
|
166
|
+
parent = {option => []}
|
167
|
+
container << parent
|
168
|
+
end
|
169
|
+
container = parent[option]
|
170
|
+
end
|
171
|
+
container << value
|
172
|
+
end
|
173
|
+
raise attribute_spec_is_missing if attributes.size < least
|
174
|
+
attributes
|
175
|
+
end
|
176
|
+
|
177
|
+
def parse_attribute_description
|
178
|
+
type = @scanner.scan(ATTRIBUTE_TYPE_CHARS)
|
179
|
+
raise attribute_type_is_missing if type.nil?
|
180
|
+
options = parse_options
|
181
|
+
[type, options]
|
182
|
+
end
|
183
|
+
|
184
|
+
def parse_attribute
|
185
|
+
type, options = parse_attribute_description
|
186
|
+
value = parse_attribute_value
|
187
|
+
[type, options, value]
|
188
|
+
end
|
189
|
+
|
190
|
+
def parse_options
|
191
|
+
options = []
|
192
|
+
while @scanner.scan(/;/u)
|
193
|
+
option = @scanner.scan(ATTRIBUTE_TYPE_CHARS)
|
194
|
+
raise option_is_missing if option.nil?
|
195
|
+
options << option
|
196
|
+
end
|
197
|
+
options
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse_attribute_value(accept_external_file=true)
|
201
|
+
raise attribute_value_separator_is_missing if @scanner.scan(/:/u).nil?
|
202
|
+
if @scanner.scan(/:/u)
|
203
|
+
@scanner.scan(FILL)
|
204
|
+
read_base64_value
|
205
|
+
elsif accept_external_file and @scanner.scan(/</u)
|
206
|
+
@scanner.scan(FILL)
|
207
|
+
read_external_file
|
208
|
+
else
|
209
|
+
@scanner.scan(FILL)
|
210
|
+
@scanner.scan(SAFE_STRING)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def parse_control
|
215
|
+
return nil if @scanner.scan(/control:/u).nil?
|
216
|
+
@scanner.scan(FILL)
|
217
|
+
type = @scanner.scan(/\d+(?:\.\d+)*/u)
|
218
|
+
raise control_type_is_missing if type.nil?
|
219
|
+
criticality = nil
|
220
|
+
if @scanner.scan(/ +/u)
|
221
|
+
criticality = @scanner.scan(/true|false/u)
|
222
|
+
raise criticality_is_missing if criticality.nil?
|
223
|
+
end
|
224
|
+
value = parse_attribute_value if @scanner.check(/:/u)
|
225
|
+
raise separator_is_missing unless @scanner.scan_separator
|
226
|
+
ChangeRecord::Control.new(type, criticality, value)
|
227
|
+
end
|
228
|
+
|
229
|
+
def parse_controls
|
230
|
+
controls = []
|
231
|
+
loop do
|
232
|
+
control = parse_control
|
233
|
+
break if control.nil?
|
234
|
+
controls << control
|
235
|
+
end
|
236
|
+
controls
|
237
|
+
end
|
238
|
+
|
239
|
+
def parse_change_type
|
240
|
+
return nil unless @scanner.scan(/changetype:/u)
|
241
|
+
@scanner.scan(FILL)
|
242
|
+
type = @scanner.check(ATTRIBUTE_TYPE_CHARS)
|
243
|
+
raise change_type_value_is_missing if type.nil?
|
244
|
+
unless @scanner.scan(/add|delete|modrdn|moddn|modify/u)
|
245
|
+
raise unknown_change_type(type)
|
246
|
+
end
|
247
|
+
|
248
|
+
raise separator_is_missing unless @scanner.scan_separator
|
249
|
+
type
|
250
|
+
end
|
251
|
+
|
252
|
+
def parse_modify_name_record(klass, dn, controls)
|
253
|
+
raise new_rdn_mark_is_missing unless @scanner.scan(/newrdn\b/u)
|
254
|
+
new_rdn = parse_attribute_value(false)
|
255
|
+
raise new_rdn_value_is_missing if new_rdn.nil?
|
256
|
+
raise separator_is_missing unless @scanner.scan_separator
|
257
|
+
|
258
|
+
unless @scanner.scan(/deleteoldrdn:/u)
|
259
|
+
raise delete_old_rdn_mark_is_missing
|
260
|
+
end
|
261
|
+
@scanner.scan(FILL)
|
262
|
+
delete_old_rdn = @scanner.scan(/[01]/u)
|
263
|
+
raise delete_old_rdn_value_is_missing if delete_old_rdn.nil?
|
264
|
+
raise separator_is_missing unless @scanner.scan_separator
|
265
|
+
|
266
|
+
if @scanner.scan(/newsuperior\b/u)
|
267
|
+
@scanner.scan(FILL)
|
268
|
+
new_superior = parse_attribute_value(false)
|
269
|
+
raise new_superior_value_is_missing if new_superior.nil?
|
270
|
+
new_superior = parse_dn(new_superior)
|
271
|
+
raise separator_is_missing unless @scanner.scan_separator
|
272
|
+
end
|
273
|
+
klass.new(dn, controls, new_rdn, delete_old_rdn, new_superior)
|
274
|
+
end
|
275
|
+
|
276
|
+
def parse_modify_spec
|
277
|
+
return nil unless @scanner.check(/(#{ATTRIBUTE_TYPE_CHARS}):/u)
|
278
|
+
type = @scanner[1]
|
279
|
+
unless @scanner.scan(/(?:add|delete|replace):/u)
|
280
|
+
raise unknown_modify_type(type)
|
281
|
+
end
|
282
|
+
@scanner.scan(FILL)
|
283
|
+
attribute, options = parse_attribute_description
|
284
|
+
raise separator_is_missing unless @scanner.scan_separator
|
285
|
+
attributes = parse_attributes {@scanner.check(/-/u)}
|
286
|
+
raise modify_spec_separator_is_missing unless @scanner.scan(/-/u)
|
287
|
+
raise separator_is_missing unless @scanner.scan_separator
|
288
|
+
[type, attribute, options, attributes]
|
289
|
+
end
|
290
|
+
|
291
|
+
def parse_modify_record(dn, controls)
|
292
|
+
operations = []
|
293
|
+
loop do
|
294
|
+
spec = parse_modify_spec
|
295
|
+
break if spec.nil?
|
296
|
+
type, attribute, options, attributes = spec
|
297
|
+
case type
|
298
|
+
when "add"
|
299
|
+
klass = ModifyRecord::AddOperation
|
300
|
+
when "delete"
|
301
|
+
klass = ModifyRecord::DeleteOperation
|
302
|
+
when "replace"
|
303
|
+
klass = ModifyRecord::ReplaceOperation
|
304
|
+
else
|
305
|
+
unknown_modify_type(type)
|
306
|
+
end
|
307
|
+
operations << klass.new(attribute, options, attributes)
|
308
|
+
end
|
309
|
+
ModifyRecord.new(dn, controls, operations)
|
310
|
+
end
|
311
|
+
|
312
|
+
def parse_change_type_record(dn, controls, change_type)
|
313
|
+
case change_type
|
314
|
+
when "add"
|
315
|
+
attributes = parse_attributes(1)
|
316
|
+
AddRecord.new(dn, controls, attributes)
|
317
|
+
when "delete"
|
318
|
+
DeleteRecord.new(dn, controls)
|
319
|
+
when "moddn"
|
320
|
+
parse_modify_name_record(ModifyDNRecord, dn, controls)
|
321
|
+
when "modrdn"
|
322
|
+
parse_modify_name_record(ModifyRDNRecord, dn, controls)
|
323
|
+
when "modify"
|
324
|
+
parse_modify_record(dn, controls)
|
325
|
+
else
|
326
|
+
raise unknown_change_type(change_type)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def parse_record
|
331
|
+
raise dn_mark_is_missing unless @scanner.scan(/dn:/u)
|
332
|
+
if @scanner.scan(/:/u)
|
333
|
+
@scanner.scan(FILL)
|
334
|
+
dn = read_base64_value
|
335
|
+
raise dn_is_missing if dn.nil?
|
336
|
+
dn = parse_dn(dn)
|
337
|
+
else
|
338
|
+
@scanner.scan(FILL)
|
339
|
+
dn = @scanner.scan(/#{SAFE_STRING}$/u)
|
340
|
+
if dn.nil?
|
341
|
+
partial_dn = @scanner.scan(SAFE_STRING)
|
342
|
+
raise dn_has_invalid_character(@scanner.check(/./u)) if partial_dn
|
343
|
+
raise dn_is_missing
|
344
|
+
end
|
345
|
+
dn = parse_dn(dn)
|
346
|
+
end
|
347
|
+
|
348
|
+
raise separator_is_missing unless @scanner.scan_separator
|
349
|
+
|
350
|
+
controls = parse_controls
|
351
|
+
change_type = parse_change_type
|
352
|
+
raise change_type_is_missing if change_type.nil? and !controls.empty?
|
353
|
+
|
354
|
+
if change_type
|
355
|
+
parse_change_type_record(dn, controls, change_type)
|
356
|
+
else
|
357
|
+
attributes = parse_attributes(1)
|
358
|
+
ContentRecord.new(dn, attributes)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def parse_records
|
363
|
+
records = []
|
364
|
+
loop do
|
365
|
+
records << parse_record
|
366
|
+
break if @scanner.eos?
|
367
|
+
raise separator_is_missing if @scanner.scan_separator.nil?
|
368
|
+
end
|
369
|
+
records
|
370
|
+
end
|
371
|
+
|
31
372
|
def invalid_ldif(reason)
|
32
|
-
LdifInvalid.new(@source, reason)
|
373
|
+
LdifInvalid.new(@source, reason, @scanner.line, @scanner.column)
|
33
374
|
end
|
34
375
|
|
35
376
|
def version_spec_is_missing
|
36
377
|
invalid_ldif(_("version spec is missing"))
|
37
378
|
end
|
38
379
|
|
380
|
+
def version_number_is_missing
|
381
|
+
invalid_ldif(_("version number is missing"))
|
382
|
+
end
|
383
|
+
|
39
384
|
def unsupported_version(version)
|
40
385
|
invalid_ldif(_("unsupported version: %d") % version)
|
41
386
|
end
|
387
|
+
|
388
|
+
def separator_is_missing
|
389
|
+
invalid_ldif(_("separator is missing"))
|
390
|
+
end
|
391
|
+
|
392
|
+
def dn_mark_is_missing
|
393
|
+
invalid_ldif(_("'dn:' is missing"))
|
394
|
+
end
|
395
|
+
|
396
|
+
def dn_is_missing
|
397
|
+
invalid_ldif(_("DN is missing"))
|
398
|
+
end
|
399
|
+
|
400
|
+
def invalid_dn(dn_string, reason)
|
401
|
+
invalid_ldif(_("DN is invalid: %s: %s") % [dn_string, reason])
|
402
|
+
end
|
403
|
+
|
404
|
+
def dn_has_invalid_character(character)
|
405
|
+
invalid_ldif(_("DN has an invalid character: %s") % character)
|
406
|
+
end
|
407
|
+
|
408
|
+
def attribute_type_is_missing
|
409
|
+
invalid_ldif(_("attribute type is missing"))
|
410
|
+
end
|
411
|
+
|
412
|
+
def option_is_missing
|
413
|
+
invalid_ldif(_("option is missing"))
|
414
|
+
end
|
415
|
+
|
416
|
+
def attribute_value_separator_is_missing
|
417
|
+
invalid_ldif(_("':' is missing"))
|
418
|
+
end
|
419
|
+
|
420
|
+
def invalid_uri(uri_string, message)
|
421
|
+
invalid_ldif(_("URI is invalid: %s: %s") % [uri_string, message])
|
422
|
+
end
|
423
|
+
|
424
|
+
def modify_spec_separator_is_missing
|
425
|
+
invalid_ldif(_("'-' is missing"))
|
426
|
+
end
|
427
|
+
|
428
|
+
def unknown_change_type(change_type)
|
429
|
+
invalid_ldif(_("unknown change type: %s") % change_type)
|
430
|
+
end
|
431
|
+
|
432
|
+
def change_type_is_missing
|
433
|
+
invalid_ldif(_("change type is missing"))
|
434
|
+
end
|
435
|
+
|
436
|
+
def control_type_is_missing
|
437
|
+
invalid_ldif(_("control type is missing"))
|
438
|
+
end
|
439
|
+
|
440
|
+
def criticality_is_missing
|
441
|
+
invalid_ldif(_("criticality is missing"))
|
442
|
+
end
|
443
|
+
|
444
|
+
def change_type_value_is_missing
|
445
|
+
invalid_ldif(_("change type value is missing"))
|
446
|
+
end
|
447
|
+
|
448
|
+
def attribute_spec_is_missing
|
449
|
+
invalid_ldif(_("attribute spec is missing"))
|
450
|
+
end
|
451
|
+
|
452
|
+
def new_rdn_mark_is_missing
|
453
|
+
invalid_ldif(_("'newrdn:' is missing"))
|
454
|
+
end
|
455
|
+
|
456
|
+
def new_rdn_value_is_missing
|
457
|
+
invalid_ldif(_("new RDN value is missing"))
|
458
|
+
end
|
459
|
+
|
460
|
+
def delete_old_rdn_mark_is_missing
|
461
|
+
invalid_ldif(_("'deleteoldrdn:' is missing"))
|
462
|
+
end
|
463
|
+
|
464
|
+
def delete_old_rdn_value_is_missing
|
465
|
+
invalid_ldif(_("delete old RDN value is missing"))
|
466
|
+
end
|
467
|
+
|
468
|
+
def new_superior_value_is_missing
|
469
|
+
invalid_ldif(_("new superior value is missing"))
|
470
|
+
end
|
471
|
+
|
472
|
+
def unknown_modify_type(type)
|
473
|
+
invalid_ldif(_("unknown modify type: %s") % type)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
class Scanner
|
478
|
+
SEPARATOR = /(?:\r\n|\n)/u
|
479
|
+
|
480
|
+
def initialize(source)
|
481
|
+
@source = source
|
482
|
+
@scanner = StringScanner.new(@source)
|
483
|
+
@sub_scanner = next_segment || StringScanner.new("")
|
484
|
+
end
|
485
|
+
|
486
|
+
def scan(regexp)
|
487
|
+
@sub_scanner = next_segment if @sub_scanner.eos?
|
488
|
+
@sub_scanner.scan(regexp)
|
489
|
+
end
|
490
|
+
|
491
|
+
def check(regexp)
|
492
|
+
@sub_scanner = next_segment if @sub_scanner.eos?
|
493
|
+
@sub_scanner.check(regexp)
|
494
|
+
end
|
495
|
+
|
496
|
+
def scan_separator
|
497
|
+
return @scanner.scan(SEPARATOR) if @sub_scanner.eos?
|
498
|
+
|
499
|
+
scan(SEPARATOR)
|
500
|
+
end
|
501
|
+
|
502
|
+
def check_separator
|
503
|
+
return @scanner.check(SEPARATOR) if @sub_scanner.eos?
|
504
|
+
|
505
|
+
check(SEPARATOR)
|
506
|
+
end
|
507
|
+
|
508
|
+
def scan_separators
|
509
|
+
return @scanner.scan(/#{SEPARATOR}+/u) if @sub_scanner.eos?
|
510
|
+
|
511
|
+
sub_result = scan(/#{SEPARATOR}+/u)
|
512
|
+
return nil if sub_result.nil?
|
513
|
+
return sub_result unless @sub_scanner.eos?
|
514
|
+
|
515
|
+
result = @scanner.scan(/#{SEPARATOR}+/u)
|
516
|
+
return sub_result if result.nil?
|
517
|
+
|
518
|
+
sub_result + result
|
519
|
+
end
|
520
|
+
|
521
|
+
def [](*args)
|
522
|
+
@sub_scanner[*args]
|
523
|
+
end
|
524
|
+
|
525
|
+
def eos?
|
526
|
+
@sub_scanner = next_segment if @sub_scanner.eos?
|
527
|
+
@sub_scanner.eos? and @scanner.eos?
|
528
|
+
end
|
529
|
+
|
530
|
+
def line
|
531
|
+
_consumed_source = consumed_source
|
532
|
+
return 1 if _consumed_source.empty?
|
533
|
+
|
534
|
+
n = _consumed_source.to_a.size
|
535
|
+
n += 1 if _consumed_source[-1, 1] == "\n"
|
536
|
+
n
|
537
|
+
end
|
538
|
+
|
539
|
+
def column
|
540
|
+
_consumed_source = consumed_source
|
541
|
+
return 1 if _consumed_source.empty?
|
542
|
+
|
543
|
+
position - (_consumed_source.rindex("\n") || -1)
|
544
|
+
end
|
545
|
+
|
546
|
+
def position
|
547
|
+
@scanner.pos - (@sub_scanner.string.length - @sub_scanner.pos)
|
548
|
+
end
|
549
|
+
|
550
|
+
private
|
551
|
+
def next_segment
|
552
|
+
loop do
|
553
|
+
segment = @scanner.scan(/.+(?:#{SEPARATOR} .*)*#{SEPARATOR}?/u)
|
554
|
+
return @sub_scanner if segment.nil?
|
555
|
+
next if segment[0, 1] == "#"
|
556
|
+
return StringScanner.new(segment.gsub(/\r?\n /u, ''))
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
def consumed_source
|
561
|
+
@source[0, position]
|
562
|
+
end
|
42
563
|
end
|
43
564
|
|
44
565
|
class << self
|
@@ -46,6 +567,335 @@ module ActiveLdap
|
|
46
567
|
Parser.new(ldif).parse
|
47
568
|
end
|
48
569
|
end
|
570
|
+
|
571
|
+
include Enumerable
|
572
|
+
|
573
|
+
attr_reader :version, :records
|
574
|
+
def initialize(records=[])
|
575
|
+
@version = 1
|
576
|
+
@records = records
|
577
|
+
end
|
578
|
+
|
579
|
+
def <<(record)
|
580
|
+
@records << record
|
581
|
+
end
|
582
|
+
|
583
|
+
def each(&block)
|
584
|
+
@records.each(&block)
|
585
|
+
end
|
586
|
+
|
587
|
+
def to_s
|
588
|
+
result = "version: #{@version}\n"
|
589
|
+
result << @records.collect do |record|
|
590
|
+
record.to_s
|
591
|
+
end.join("\n")
|
592
|
+
result
|
593
|
+
end
|
594
|
+
|
595
|
+
def ==(other)
|
596
|
+
other.is_a?(self.class) and
|
597
|
+
@version == other.version and @records == other.records
|
598
|
+
end
|
599
|
+
|
600
|
+
class Record
|
601
|
+
include GetTextSupport
|
602
|
+
|
603
|
+
attr_reader :dn, :attributes
|
604
|
+
def initialize(dn, attributes)
|
605
|
+
@dn = dn
|
606
|
+
@attributes = attributes
|
607
|
+
end
|
608
|
+
|
609
|
+
def to_hash
|
610
|
+
attributes.merge({"dn" => dn})
|
611
|
+
end
|
612
|
+
|
613
|
+
def to_s
|
614
|
+
result = to_s_prelude
|
615
|
+
result << to_s_content
|
616
|
+
result
|
617
|
+
end
|
618
|
+
|
619
|
+
def ==(other)
|
620
|
+
other.is_a?(self.class) and
|
621
|
+
@dn == other.dn and
|
622
|
+
Attributes.normalize(@attributes) ==
|
623
|
+
Attributes.normalize(other.attributes)
|
624
|
+
end
|
625
|
+
|
626
|
+
private
|
627
|
+
def to_s_prelude
|
628
|
+
Attribute.encode("dn", dn)
|
629
|
+
end
|
630
|
+
|
631
|
+
def to_s_content
|
632
|
+
Attributes.encode(@attributes)
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
class ContentRecord < Record
|
637
|
+
end
|
638
|
+
|
639
|
+
class ChangeRecord < Record
|
640
|
+
attr_reader :controls, :change_type
|
641
|
+
def initialize(dn, attributes, controls, change_type)
|
642
|
+
super(dn, attributes)
|
643
|
+
@controls = controls
|
644
|
+
@change_type = change_type
|
645
|
+
end
|
646
|
+
|
647
|
+
def add?
|
648
|
+
@change_type == "add"
|
649
|
+
end
|
650
|
+
|
651
|
+
def delete?
|
652
|
+
@change_type == "delete"
|
653
|
+
end
|
654
|
+
|
655
|
+
def modify?
|
656
|
+
@change_type == "modify"
|
657
|
+
end
|
658
|
+
|
659
|
+
def modify_dn?
|
660
|
+
@change_type == "moddn"
|
661
|
+
end
|
662
|
+
|
663
|
+
def modify_rdn?
|
664
|
+
@change_type == "modrdn"
|
665
|
+
end
|
666
|
+
|
667
|
+
def ==(other)
|
668
|
+
super(other) and
|
669
|
+
@controls = other.controls and
|
670
|
+
@change_type == other.change_type
|
671
|
+
end
|
672
|
+
|
673
|
+
private
|
674
|
+
def to_s_prelude
|
675
|
+
result = super
|
676
|
+
@controls.each do |control|
|
677
|
+
result << control.to_s
|
678
|
+
end
|
679
|
+
result
|
680
|
+
end
|
681
|
+
|
682
|
+
def to_s_content
|
683
|
+
result = "changetype: #{@change_type}\n"
|
684
|
+
result << super
|
685
|
+
result
|
686
|
+
end
|
687
|
+
|
688
|
+
class Control
|
689
|
+
attr_reader :type, :value
|
690
|
+
def initialize(type, criticality, value)
|
691
|
+
@type = type
|
692
|
+
@criticality = normalize_criticality(criticality)
|
693
|
+
@value = value
|
694
|
+
end
|
695
|
+
|
696
|
+
def criticality?
|
697
|
+
@criticality
|
698
|
+
end
|
699
|
+
|
700
|
+
def to_a
|
701
|
+
[@type, @criticality, @value]
|
702
|
+
end
|
703
|
+
|
704
|
+
def to_hash
|
705
|
+
{
|
706
|
+
:type => @type,
|
707
|
+
:criticality => @criticality,
|
708
|
+
:value => @value,
|
709
|
+
}
|
710
|
+
end
|
711
|
+
|
712
|
+
def to_s
|
713
|
+
result = "control: #{@type}"
|
714
|
+
result << " #{@criticality}" unless @criticality.nil?
|
715
|
+
result << @value if @value
|
716
|
+
result << "\n"
|
717
|
+
result
|
718
|
+
end
|
719
|
+
|
720
|
+
def ==(other)
|
721
|
+
other.is_a?(self.class) and
|
722
|
+
@type == other.type and
|
723
|
+
@criticality = other.criticality and
|
724
|
+
@value == other.value
|
725
|
+
end
|
726
|
+
|
727
|
+
private
|
728
|
+
def normalize_criticality(criticality)
|
729
|
+
case criticality
|
730
|
+
when "true", true
|
731
|
+
true
|
732
|
+
when "false", false
|
733
|
+
false
|
734
|
+
when nil
|
735
|
+
nil
|
736
|
+
else
|
737
|
+
raise ArgumentError,
|
738
|
+
_("invalid criticality value: %s") % criticality.inspect
|
739
|
+
end
|
740
|
+
end
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
class AddRecord < ChangeRecord
|
745
|
+
def initialize(dn, controls=[], attributes={})
|
746
|
+
super(dn, attributes, controls, "add")
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
class DeleteRecord < ChangeRecord
|
751
|
+
def initialize(dn, controls=[])
|
752
|
+
super(dn, {}, controls, "delete")
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
class ModifyNameRecord < ChangeRecord
|
757
|
+
attr_reader :new_rdn, :new_superior
|
758
|
+
def initialize(dn, controls, change_type,
|
759
|
+
new_rdn, delete_old_rdn, new_superior)
|
760
|
+
super(dn, {}, controls, change_type)
|
761
|
+
@new_rdn = new_rdn
|
762
|
+
@delete_old_rdn = normalize_delete_old_rdn(delete_old_rdn)
|
763
|
+
@new_superior = new_superior
|
764
|
+
end
|
765
|
+
|
766
|
+
def delete_old_rdn?
|
767
|
+
@delete_old_rdn
|
768
|
+
end
|
769
|
+
|
770
|
+
private
|
771
|
+
def normalize_delete_old_rdn(delete_old_rdn)
|
772
|
+
case delete_old_rdn
|
773
|
+
when "1", true
|
774
|
+
true
|
775
|
+
when "0", false
|
776
|
+
false
|
777
|
+
when nil
|
778
|
+
nil
|
779
|
+
else
|
780
|
+
raise ArgumentError,
|
781
|
+
_("invalid deleteoldrdn value: %s") % delete_old_rdn.inspect
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
def to_s_content
|
786
|
+
result = super
|
787
|
+
result << "newrdn: #{@new_rdn}\n"
|
788
|
+
result << "deleteoldrdn: #{@delete_old_rdn ? 1 : 0}\n"
|
789
|
+
result << Attribute.encode("newsuperior", @new_superior) if @new_superior
|
790
|
+
result
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
794
|
+
class ModifyDNRecord < ModifyNameRecord
|
795
|
+
def initialize(dn, controls, new_rdn, delete_old_rdn, new_superior=nil)
|
796
|
+
super(dn, controls, "moddn", new_rdn, delete_old_rdn, new_superior)
|
797
|
+
end
|
798
|
+
end
|
799
|
+
|
800
|
+
class ModifyRDNRecord < ModifyNameRecord
|
801
|
+
def initialize(dn, controls, new_rdn, delete_old_rdn, new_superior=nil)
|
802
|
+
super(dn, controls, "modrdn", new_rdn, delete_old_rdn, new_superior)
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
class ModifyRecord < ChangeRecord
|
807
|
+
include Enumerable
|
808
|
+
|
809
|
+
attr_reader :operations
|
810
|
+
def initialize(dn, controls=[], operations=[])
|
811
|
+
super(dn, {}, controls, "modify")
|
812
|
+
@operations = operations
|
813
|
+
end
|
814
|
+
|
815
|
+
def each(&block)
|
816
|
+
@operations.each(&block)
|
817
|
+
end
|
818
|
+
|
819
|
+
def <<(operation)
|
820
|
+
@operations << operation
|
821
|
+
end
|
822
|
+
|
823
|
+
def add_operation(type, attribute, options, attributes)
|
824
|
+
klass = self.class.const_get("#{type.to_s.capitalize}Operation")
|
825
|
+
self << klass.new(attribute, options, attributes)
|
826
|
+
end
|
827
|
+
|
828
|
+
def ==(other)
|
829
|
+
super(other) and @operations == other.operations
|
830
|
+
end
|
831
|
+
|
832
|
+
private
|
833
|
+
def to_s_content
|
834
|
+
result = super
|
835
|
+
return result if @operations.empty?
|
836
|
+
@operations.collect do |operation|
|
837
|
+
result << "#{operation}-\n"
|
838
|
+
end
|
839
|
+
result
|
840
|
+
end
|
841
|
+
|
842
|
+
class Operation
|
843
|
+
attr_reader :type, :attribute, :options, :attributes
|
844
|
+
def initialize(type, attribute, options, attributes)
|
845
|
+
@type = type
|
846
|
+
@attribute = attribute
|
847
|
+
@options = options
|
848
|
+
@attributes = attributes
|
849
|
+
end
|
850
|
+
|
851
|
+
def full_attribute_name
|
852
|
+
[@attribute, *@options].join(";")
|
853
|
+
end
|
854
|
+
|
855
|
+
def add?
|
856
|
+
@type == "add"
|
857
|
+
end
|
858
|
+
|
859
|
+
def delete?
|
860
|
+
@type == "delete"
|
861
|
+
end
|
862
|
+
|
863
|
+
def replace?
|
864
|
+
@type == "replace"
|
865
|
+
end
|
866
|
+
|
867
|
+
def to_s
|
868
|
+
Attribute.encode(@type, full_attribute_name) +
|
869
|
+
Attributes.encode(@attributes)
|
870
|
+
end
|
871
|
+
|
872
|
+
def ==(other)
|
873
|
+
other.is_a?(self.class) and
|
874
|
+
@type == other.type and
|
875
|
+
full_attribute_name == other.full_attribute_name and
|
876
|
+
Attributes.normalize(@attributes) ==
|
877
|
+
Attributes.normalize(other.attributes)
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
class AddOperation < Operation
|
882
|
+
def initialize(attribute, options, attributes)
|
883
|
+
super("add", attribute, options, attributes)
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
class DeleteOperation < Operation
|
888
|
+
def initialize(attribute, options, attributes)
|
889
|
+
super("delete", attribute, options, attributes)
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
class ReplaceOperation < Operation
|
894
|
+
def initialize(attribute, options, attributes)
|
895
|
+
super("replace", attribute, options, attributes)
|
896
|
+
end
|
897
|
+
end
|
898
|
+
end
|
49
899
|
end
|
50
900
|
|
51
901
|
LDIF = Ldif
|