ruby-activeldap 0.8.1 → 0.8.2
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 +5 -0
- data/Manifest.txt +91 -25
- data/README +22 -0
- data/Rakefile +41 -8
- data/TODO +1 -6
- 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 +50 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +47 -0
- data/examples/usermod-binary-add-time +51 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +213 -214
- data/lib/active_ldap/adapter/base.rb +461 -0
- data/lib/active_ldap/adapter/ldap.rb +232 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
- data/lib/active_ldap/adapter/net_ldap.rb +288 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
- data/lib/active_ldap/association/belongs_to.rb +3 -1
- data/lib/active_ldap/association/belongs_to_many.rb +5 -6
- data/lib/active_ldap/association/has_many.rb +9 -17
- data/lib/active_ldap/association/has_many_wrap.rb +4 -5
- data/lib/active_ldap/attributes.rb +4 -0
- data/lib/active_ldap/base.rb +201 -56
- data/lib/active_ldap/configuration.rb +11 -1
- data/lib/active_ldap/connection.rb +15 -9
- data/lib/active_ldap/distinguished_name.rb +246 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/object_class.rb +9 -5
- data/lib/active_ldap/schema.rb +50 -9
- data/lib/active_ldap/validations.rb +11 -13
- data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
- data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
- data/rails/plugin/active_ldap/init.rb +10 -4
- data/test/al-test-utils.rb +46 -3
- data/test/run-test.rb +16 -4
- data/test/test-unit-ext/always-show-result.rb +28 -0
- data/test/test-unit-ext/priority.rb +163 -0
- data/test/test_adapter.rb +81 -0
- data/test/test_attributes.rb +8 -1
- data/test/test_base.rb +132 -3
- data/test/test_base_per_instance.rb +14 -3
- data/test/test_connection.rb +19 -0
- data/test/test_dn.rb +161 -0
- data/test/test_find.rb +24 -0
- data/test/test_object_class.rb +15 -2
- data/test/test_schema.rb +108 -1
- metadata +111 -41
- data/lib/active_ldap/adaptor/base.rb +0 -29
- data/lib/active_ldap/adaptor/ldap.rb +0 -466
- data/lib/active_ldap/ldap.rb +0 -113
@@ -15,13 +15,16 @@ module ActiveLdap
|
|
15
15
|
DEFAULT_CONFIG[:port] = 389
|
16
16
|
DEFAULT_CONFIG[:method] = :plain # :ssl, :tls, :plain allowed
|
17
17
|
|
18
|
-
DEFAULT_CONFIG[:bind_dn] =
|
18
|
+
DEFAULT_CONFIG[:bind_dn] = nil
|
19
19
|
DEFAULT_CONFIG[:password_block] = nil
|
20
20
|
DEFAULT_CONFIG[:password] = nil
|
21
21
|
DEFAULT_CONFIG[:store_password] = true
|
22
22
|
DEFAULT_CONFIG[:allow_anonymous] = true
|
23
23
|
DEFAULT_CONFIG[:sasl_quiet] = false
|
24
24
|
DEFAULT_CONFIG[:try_sasl] = false
|
25
|
+
# See http://www.iana.org/assignments/sasl-mechanisms
|
26
|
+
DEFAULT_CONFIG[:sasl_mechanisms] = ["GSSAPI", "DIGEST-MD5",
|
27
|
+
"CRAM-MD5", "EXTERNAL"]
|
25
28
|
|
26
29
|
DEFAULT_CONFIG[:retry_limit] = 3
|
27
30
|
DEFAULT_CONFIG[:retry_wait] = 3
|
@@ -76,6 +79,13 @@ module ActiveLdap
|
|
76
79
|
@@defined_configurations.delete_if {|key, value| value == config}
|
77
80
|
end
|
78
81
|
|
82
|
+
CONNECTION_CONFIGURATION_KEYS = [:base, :ldap_scope, :adapter]
|
83
|
+
def remove_connection_related_configuration(config)
|
84
|
+
config.reject do |key, value|
|
85
|
+
CONNECTION_CONFIGURATION_KEYS.include?(key)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
79
89
|
def merge_configuration(config)
|
80
90
|
configuration = default_configuration
|
81
91
|
config.symbolize_keys.each do |key, value|
|
@@ -45,18 +45,24 @@ module ActiveLdap
|
|
45
45
|
conn
|
46
46
|
end
|
47
47
|
|
48
|
-
def connection=(
|
49
|
-
if
|
48
|
+
def connection=(adapter)
|
49
|
+
if adapter.is_a?(Adapter::Base)
|
50
50
|
@schema = nil
|
51
|
-
active_connections[active_connection_name] =
|
52
|
-
elsif
|
53
|
-
config =
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
active_connections[active_connection_name] = adapter
|
52
|
+
elsif adapter.is_a?(Hash)
|
53
|
+
config = adapter
|
54
|
+
adapter = (config[:adapter] || "ldap")
|
55
|
+
normalized_adapter = adapter.downcase.gsub(/-/, "_")
|
56
|
+
adapter_method = "#{normalized_adapter}_connection"
|
57
|
+
unless Adapter::Base.respond_to?(adapter_method)
|
58
|
+
raise AdapterNotFound.new(adapter)
|
59
|
+
end
|
60
|
+
config = remove_connection_related_configuration(config)
|
61
|
+
self.connection = Adapter::Base.send(adapter_method, config)
|
62
|
+
elsif adapter.nil?
|
57
63
|
raise ConnectionNotEstablished
|
58
64
|
else
|
59
|
-
establish_connection(
|
65
|
+
establish_connection(adapter)
|
60
66
|
end
|
61
67
|
end
|
62
68
|
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module ActiveLdap
|
4
|
+
class DistinguishedName
|
5
|
+
class Parser
|
6
|
+
attr_reader :dn
|
7
|
+
def initialize(source)
|
8
|
+
@dn = nil
|
9
|
+
@source = source
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse
|
13
|
+
return @dn if @dn
|
14
|
+
|
15
|
+
rdns = []
|
16
|
+
scanner = StringScanner.new(@source)
|
17
|
+
|
18
|
+
scanner.scan(/\s*/)
|
19
|
+
raise rdn_is_missing if scanner.scan(/\s*\+\s*/)
|
20
|
+
raise name_component_is_missing if scanner.scan(/\s*,\s*/)
|
21
|
+
|
22
|
+
rdn = {}
|
23
|
+
until scanner.eos?
|
24
|
+
type = scan_attribute_type(scanner)
|
25
|
+
skip_attribute_type_and_value_separator(scanner)
|
26
|
+
value = scan_attribute_value(scanner)
|
27
|
+
rdn[type] = value
|
28
|
+
if scanner.scan(/\s*\+\s*/)
|
29
|
+
raise rdn_is_missing if scanner.eos?
|
30
|
+
elsif scanner.scan(/\s*\,\s*/)
|
31
|
+
rdns << rdn
|
32
|
+
rdn = {}
|
33
|
+
raise name_component_is_missing if scanner.eos?
|
34
|
+
else
|
35
|
+
scanner.scan(/\s*/)
|
36
|
+
rdns << rdn if scanner.eos?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@dn = DN.new(*rdns)
|
41
|
+
@dn
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
ATTRIBUTE_TYPE_RE = /\s*([a-zA-Z][a-zA-Z\d\-]*|\d+(?:\.\d+)*)\s*/
|
46
|
+
def scan_attribute_type(scanner)
|
47
|
+
raise attribute_type_is_missing unless scanner.scan(ATTRIBUTE_TYPE_RE)
|
48
|
+
scanner[1]
|
49
|
+
end
|
50
|
+
|
51
|
+
def skip_attribute_type_and_value_separator(scanner)
|
52
|
+
raise attribute_value_is_missing unless scanner.scan(/\s*=\s*/)
|
53
|
+
end
|
54
|
+
|
55
|
+
HEX_PAIR = "(?:[\\da-fA-F]{2})"
|
56
|
+
STRING_CHARS_RE = /[^,=\+<>\#;\\\"]*/ #
|
57
|
+
PAIR_RE = /\\([,=\+<>\#;]|\\|\"|(#{HEX_PAIR}))/ #
|
58
|
+
HEX_STRING_RE = /\#(#{HEX_PAIR}+)/ #
|
59
|
+
def scan_attribute_value(scanner)
|
60
|
+
if scanner.scan(HEX_STRING_RE)
|
61
|
+
value = scanner[1].scan(/../).collect do |hex_pair|
|
62
|
+
hex_pair.hex
|
63
|
+
end.pack("C*")
|
64
|
+
elsif scanner.scan(/\"/)
|
65
|
+
value = scan_quoted_attribute_value(scanner)
|
66
|
+
else
|
67
|
+
value = scan_not_quoted_attribute_value(scanner)
|
68
|
+
end
|
69
|
+
raise attribute_value_is_missing if value.blank?
|
70
|
+
|
71
|
+
value
|
72
|
+
end
|
73
|
+
|
74
|
+
def scan_quoted_attribute_value(scanner)
|
75
|
+
result = ""
|
76
|
+
until scanner.scan(/\"/)
|
77
|
+
scanner.scan(/([^\\\"]*)/)
|
78
|
+
quoted_strings = scanner[1]
|
79
|
+
pairs = collect_pairs(scanner)
|
80
|
+
|
81
|
+
if scanner.eos? or (quoted_strings.empty? and pairs.empty?)
|
82
|
+
raise found_unmatched_quotation
|
83
|
+
end
|
84
|
+
|
85
|
+
result << quoted_strings
|
86
|
+
result << pairs
|
87
|
+
end
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
def scan_not_quoted_attribute_value(scanner)
|
92
|
+
result = ""
|
93
|
+
until scanner.eos?
|
94
|
+
prev_size = result.size
|
95
|
+
pairs = collect_pairs(scanner)
|
96
|
+
strings = scanner.scan(STRING_CHARS_RE)
|
97
|
+
result << pairs if !pairs.nil? and !pairs.empty?
|
98
|
+
unless strings.nil?
|
99
|
+
if scanner.peek(1) == ","
|
100
|
+
result << strings.rstrip
|
101
|
+
else
|
102
|
+
result << strings
|
103
|
+
end
|
104
|
+
end
|
105
|
+
break if prev_size == result.size
|
106
|
+
end
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
def collect_pairs(scanner)
|
111
|
+
result = ""
|
112
|
+
while scanner.scan(PAIR_RE)
|
113
|
+
if scanner[2]
|
114
|
+
result << [scanner[2].hex].pack("C*")
|
115
|
+
else
|
116
|
+
result << scanner[1]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
result
|
120
|
+
end
|
121
|
+
|
122
|
+
def invalid_dn(reason)
|
123
|
+
DistinguishedNameInvalid.new(@source, reason)
|
124
|
+
end
|
125
|
+
|
126
|
+
def name_component_is_missing
|
127
|
+
invalid_dn("name component is missing")
|
128
|
+
end
|
129
|
+
|
130
|
+
def rdn_is_missing
|
131
|
+
invalid_dn("relative distinguished name (RDN) is missing")
|
132
|
+
end
|
133
|
+
|
134
|
+
def attribute_type_is_missing
|
135
|
+
invalid_dn("attribute type is missing")
|
136
|
+
end
|
137
|
+
|
138
|
+
def attribute_value_is_missing
|
139
|
+
invalid_dn("attribute value is missing")
|
140
|
+
end
|
141
|
+
|
142
|
+
def found_unmatched_quotation
|
143
|
+
invalid_dn("found unmatched quotation")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class << self
|
148
|
+
def parse(source)
|
149
|
+
Parser.new(source).parse
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
attr_reader :rdns
|
154
|
+
def initialize(*rdns)
|
155
|
+
@rdns = rdns.collect do |rdn|
|
156
|
+
rdn = {rdn[0] => rdn[1]} if rdn.is_a?(Array) and rdn.size == 2
|
157
|
+
rdn
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def -(other)
|
162
|
+
rdns = @rdns.dup
|
163
|
+
normalized_rdns = normalize(@rdns)
|
164
|
+
normalize(other.rdns).reverse_each do |rdn|
|
165
|
+
if rdn == normalized_rdns.pop
|
166
|
+
rdns.pop
|
167
|
+
else
|
168
|
+
raise ArgumentError, "#{other} isn't sub DN of #{self}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
self.class.new(*rdns)
|
172
|
+
end
|
173
|
+
|
174
|
+
def <<(rdn)
|
175
|
+
@rdns << rdn
|
176
|
+
end
|
177
|
+
|
178
|
+
def unshift(rdn)
|
179
|
+
@rdns.unshift(rdn)
|
180
|
+
end
|
181
|
+
|
182
|
+
def <=>(other)
|
183
|
+
normalize_for_comparing(@rdns) <=>
|
184
|
+
normalize_for_comparing(other.rdns)
|
185
|
+
end
|
186
|
+
|
187
|
+
def ==(other)
|
188
|
+
other.is_a?(self.class) and
|
189
|
+
normalize(@rdns) == normalize(other.rdns)
|
190
|
+
end
|
191
|
+
|
192
|
+
def eql?(other)
|
193
|
+
other.is_a?(self.class) and
|
194
|
+
normalize(@rdns).to_s.eql?(normalize(other.rdns).to_s)
|
195
|
+
end
|
196
|
+
|
197
|
+
def hash
|
198
|
+
normalize(@rdns).to_s.hash
|
199
|
+
end
|
200
|
+
|
201
|
+
def inspect
|
202
|
+
super
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_s
|
206
|
+
@rdns.collect do |rdn|
|
207
|
+
rdn.sort_by do |type, value|
|
208
|
+
type.upcase
|
209
|
+
end.collect do |type, value|
|
210
|
+
"#{type}=#{escape(value)}"
|
211
|
+
end.join("+")
|
212
|
+
end.join(",")
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
def normalize(rdns)
|
217
|
+
rdns.collect do |rdn|
|
218
|
+
normalized_rdn = {}
|
219
|
+
rdn.each do |key, value|
|
220
|
+
normalized_rdn[key.upcase] = value.upcase
|
221
|
+
end
|
222
|
+
normalized_rdn
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def normalize_for_comparing(rdns)
|
227
|
+
normalize(rdns).collect do |rdn|
|
228
|
+
rdn.sort_by do |key, value|
|
229
|
+
key
|
230
|
+
end
|
231
|
+
end.collect do |key, value|
|
232
|
+
[key, value]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def escape(value)
|
237
|
+
if /(\A | \z)/.match(value)
|
238
|
+
'"' + value.gsub(/([\\\"])/, '\\\\\1') + '"'
|
239
|
+
else
|
240
|
+
value.gsub(/([,=\+<>#;\\\"])/, '\\\\\1')
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
DN = DistinguishedName
|
246
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module ActiveLdap
|
2
|
+
class LdapError < Error
|
3
|
+
class << self
|
4
|
+
def define(code, name, target)
|
5
|
+
klass_name = name.downcase.camelize
|
6
|
+
target.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
|
7
|
+
class #{klass_name} < #{self}
|
8
|
+
CODE = #{code}
|
9
|
+
def code
|
10
|
+
CODE
|
11
|
+
end
|
12
|
+
end
|
13
|
+
EOC
|
14
|
+
target.const_get(klass_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
ERRORS = {}
|
19
|
+
{
|
20
|
+
0x00 => "SUCCESS",
|
21
|
+
0x01 => "OPERATIONS_ERROR",
|
22
|
+
0x02 => "PROTOCOL_ERROR",
|
23
|
+
0x03 => "TIMELIMIT_EXCEEDED",
|
24
|
+
0x04 => "SIZELIMIT_EXCEEDED",
|
25
|
+
0x05 => "COMPARE_FALSE",
|
26
|
+
0x06 => "COMPARE_TRUE",
|
27
|
+
0x07 => "AUTH_METHOD_NOT_SUPPORTED",
|
28
|
+
0x08 => "STRONG_AUTH_REQUIRED",
|
29
|
+
0x09 => "PARTIAL_RESULTS", # LDAPv2+ (not LDAPv3)
|
30
|
+
|
31
|
+
0x0a => "REFERRAL",
|
32
|
+
0x0b => "ADMINLIMIT_EXCEEDED",
|
33
|
+
0x0c => "UNAVAILABLE_CRITICAL_EXTENSION",
|
34
|
+
0x0d => "CONFIDENTIALITY_REQUIRED",
|
35
|
+
0x0e => "LDAP_SASL_BIND_IN_PROGRESS",
|
36
|
+
|
37
|
+
0x10 => "NO_SUCH_ATTRIBUTE",
|
38
|
+
0x11 => "UNDEFINED_TYPE",
|
39
|
+
0x12 => "INAPPROPRIATE_MATCHING",
|
40
|
+
0x13 => "CONSTRAINT_VIOLATION",
|
41
|
+
0x14 => "TYPE_OR_VALUE_EXISTS",
|
42
|
+
0x15 => "INVALID_SYNTAX",
|
43
|
+
|
44
|
+
0x20 => "NO_SUCH_OBJECT",
|
45
|
+
0x21 => "ALIAS_PROBLEM",
|
46
|
+
0x22 => "INVALID_DN_SYNTAX",
|
47
|
+
0x23 => "IS_LEAF",
|
48
|
+
0x24 => "ALIAS_DEREF_PROBLEM",
|
49
|
+
|
50
|
+
0x2F => "PROXY_AUTHZ_FAILURE",
|
51
|
+
0x30 => "INAPPROPRIATE_AUTH",
|
52
|
+
0x31 => "INVALID_CREDENTIALS",
|
53
|
+
0x32 => "INSUFFICIENT_ACCESS",
|
54
|
+
|
55
|
+
0x33 => "BUSY",
|
56
|
+
0x34 => "UNAVAILABLE",
|
57
|
+
0x35 => "UNWILLING_TO_PERFORM",
|
58
|
+
0x36 => "LOOP_DETECT",
|
59
|
+
|
60
|
+
0x40 => "NAMING_VIOLATION",
|
61
|
+
0x41 => "OBJECT_CLASS_VIOLATION",
|
62
|
+
0x42 => "NOT_ALLOWED_ON_NONLEAF",
|
63
|
+
0x43 => "NOT_ALLOWED_ON_RDN",
|
64
|
+
0x44 => "ALREADY_EXISTS",
|
65
|
+
0x45 => "NO_OBJECT_CLASS_MODS",
|
66
|
+
0x46 => "RESULTS_TOO_LARGE",
|
67
|
+
0x47 => "AFFECTS_MULTIPLE_DSAS",
|
68
|
+
|
69
|
+
0x50 => "OTHER",
|
70
|
+
}.each do |code, name|
|
71
|
+
ERRORS[code] = LdapError.define(code, name, self)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -11,16 +11,20 @@ module ActiveLdap
|
|
11
11
|
replace_class((classes + target_classes.flatten).uniq)
|
12
12
|
end
|
13
13
|
|
14
|
+
def ensure_recommended_classes
|
15
|
+
add_class(self.class.recommended_classes)
|
16
|
+
end
|
17
|
+
|
14
18
|
def remove_class(*target_classes)
|
15
|
-
|
16
|
-
assert_object_classes(new_classes)
|
17
|
-
set_attribute('objectClass', new_classes)
|
19
|
+
replace_class((classes - target_classes.flatten).uniq)
|
18
20
|
end
|
19
21
|
|
20
22
|
def replace_class(*target_classes)
|
21
23
|
new_classes = target_classes.flatten.uniq
|
22
24
|
assert_object_classes(new_classes)
|
23
|
-
|
25
|
+
if new_classes.sort != classes.sort
|
26
|
+
set_attribute('objectClass', new_classes)
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def classes
|
@@ -52,7 +56,7 @@ module ActiveLdap
|
|
52
56
|
schema.exist_name?("objectClasses", new_class)
|
53
57
|
end
|
54
58
|
unless invalid_classes.empty?
|
55
|
-
message = "unknown objectClass
|
59
|
+
message = "unknown objectClass in LDAP server"
|
56
60
|
message = "#{message}: #{invalid_classes.join(', ')}"
|
57
61
|
raise ObjectClassError, message
|
58
62
|
end
|
data/lib/active_ldap/schema.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveLdap
|
2
2
|
class Schema
|
3
3
|
def initialize(entries)
|
4
|
-
@entries = entries
|
4
|
+
@entries = default_entries.merge(entries || {})
|
5
5
|
@schema_info = {}
|
6
6
|
@class_attributes_info = {}
|
7
7
|
@cache = {}
|
@@ -16,7 +16,7 @@ module ActiveLdap
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# attribute
|
19
|
-
#
|
19
|
+
#
|
20
20
|
# This is just like LDAP::Schema#attribute except that it allows
|
21
21
|
# look up in any of the given keys.
|
22
22
|
# e.g.
|
@@ -31,9 +31,16 @@ module ActiveLdap
|
|
31
31
|
alias_method :[], :attribute
|
32
32
|
alias_method :attr, :attribute
|
33
33
|
|
34
|
+
NUMERIC_OID_RE = "\\d[\\d\\.]+"
|
35
|
+
DESCRIPTION_RE = "[a-zA-Z][a-zA-Z\\d\\-]*"
|
36
|
+
OID_RE = "(?:#{NUMERIC_OID_RE}|#{DESCRIPTION_RE}-oid)"
|
34
37
|
def attributes(group, id_or_name)
|
35
38
|
return {} if group.empty? or id_or_name.empty?
|
36
39
|
|
40
|
+
unless @entries.has_key?(group)
|
41
|
+
raise ArgumentError, "Unknown schema group: #{group}"
|
42
|
+
end
|
43
|
+
|
37
44
|
# Initialize anything that is required
|
38
45
|
info, ids, aliases = ensure_schema_info(group)
|
39
46
|
id, name = determine_id_or_name(id_or_name, aliases)
|
@@ -41,8 +48,9 @@ module ActiveLdap
|
|
41
48
|
# Check already parsed options first
|
42
49
|
return ids[id] if ids.has_key?(id)
|
43
50
|
|
44
|
-
|
45
|
-
|
51
|
+
schemata = @entries[group] || []
|
52
|
+
while schema = schemata.shift
|
53
|
+
next unless /\A\s*\(\s*(#{OID_RE})\s*(.*)\s*\)\s*\z/ =~ schema
|
46
54
|
schema_id = $1
|
47
55
|
rest = $2
|
48
56
|
next if ids.has_key?(schema_id)
|
@@ -190,12 +198,34 @@ module ActiveLdap
|
|
190
198
|
[id, name]
|
191
199
|
end
|
192
200
|
|
201
|
+
# from RFC 2252
|
202
|
+
attribute_type_description_reserved_names =
|
203
|
+
["NAME", "DESC", "OBSOLETE", "SUP", "EQUALITY", "ORDERING", "SUBSTR",
|
204
|
+
"SYNTAX", "SINGLE-VALUE", "COLLECTIVE", "NO-USER-MODIFICATION", "USAGE"]
|
205
|
+
syntax_description_reserved_names = ["DESC"]
|
206
|
+
object_class_description_reserved_names =
|
207
|
+
["NAME", "DESC", "OBSOLETE", "SUP", "ABSTRACT", "STRUCTURAL",
|
208
|
+
"AUXILIARY", "MUST", "MAY"]
|
209
|
+
matching_rule_description_reserved_names =
|
210
|
+
["NAME", "DESC", "OBSOLETE", "SYNTAX"]
|
211
|
+
matching_rule_use_description_reserved_names =
|
212
|
+
["NAME", "DESC", "OBSOLETE", "APPLIES"]
|
213
|
+
private_experiment_reserved_names = ["X-[A-Z\\-_]+"]
|
214
|
+
reserved_names =
|
215
|
+
(attribute_type_description_reserved_names +
|
216
|
+
syntax_description_reserved_names +
|
217
|
+
object_class_description_reserved_names +
|
218
|
+
matching_rule_description_reserved_names +
|
219
|
+
matching_rule_use_description_reserved_names +
|
220
|
+
private_experiment_reserved_names).uniq
|
221
|
+
RESERVED_NAMES_RE = /(?:#{reserved_names.join('|')})/
|
222
|
+
|
193
223
|
def parse_attributes(str, attributes)
|
194
|
-
str.scan(/([A-Z\-]+)\s+
|
224
|
+
str.scan(/([A-Z\-_]+)\s+
|
195
225
|
(?:\(\s*([\w\-]+(?:\s+\$\s+[\w\-]+)+)\s*\)|
|
196
226
|
\(\s*([^\)]*)\s*\)|
|
197
227
|
'([^\']*)'|
|
198
|
-
([a-
|
228
|
+
((?!#{RESERVED_NAMES_RE})[a-zA-Z][a-zA-Z\d\-;]*)|
|
199
229
|
(\d[\d\.\{\}]+)|
|
200
230
|
()
|
201
231
|
)/x
|
@@ -214,7 +244,7 @@ module ActiveLdap
|
|
214
244
|
when no_value
|
215
245
|
values = ["TRUE"]
|
216
246
|
end
|
217
|
-
attributes[name] = values
|
247
|
+
attributes[normalize_attribute_name(name)] = values
|
218
248
|
end
|
219
249
|
end
|
220
250
|
|
@@ -225,6 +255,7 @@ module ActiveLdap
|
|
225
255
|
end
|
226
256
|
|
227
257
|
def ldap_syntax(name, attribute_name)
|
258
|
+
return [] unless @entries.has_key?("ldapSyntaxes")
|
228
259
|
cache([:ldap_syntax, name, attribute_name]) do
|
229
260
|
attribute("ldapSyntaxes", name, attribute_name)
|
230
261
|
end
|
@@ -238,10 +269,12 @@ module ActiveLdap
|
|
238
269
|
|
239
270
|
def alias_map(group)
|
240
271
|
ensure_parse(group)
|
241
|
-
@schema_info[group]
|
272
|
+
return {} if @schema_info[group].nil?
|
273
|
+
@schema_info[group][:aliases] || {}
|
242
274
|
end
|
243
275
|
|
244
276
|
def ensure_parse(group)
|
277
|
+
return if @entries[group].nil?
|
245
278
|
unless @entries[group].empty?
|
246
279
|
attribute(group, 'nonexistent', 'nonexistent')
|
247
280
|
end
|
@@ -252,7 +285,15 @@ module ActiveLdap
|
|
252
285
|
end
|
253
286
|
|
254
287
|
def normalize_attribute_name(name)
|
255
|
-
name.upcase
|
288
|
+
name.upcase.gsub(/_/, "-")
|
289
|
+
end
|
290
|
+
|
291
|
+
def default_entries
|
292
|
+
{
|
293
|
+
"objectClasses" => [],
|
294
|
+
"attributeTypes" => [],
|
295
|
+
"ldapSyntaxes" => [],
|
296
|
+
}
|
256
297
|
end
|
257
298
|
end # Schema
|
258
299
|
end
|