ruby-activeldap 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGES +5 -0
  2. data/Manifest.txt +91 -25
  3. data/README +22 -0
  4. data/Rakefile +41 -8
  5. data/TODO +1 -6
  6. data/examples/config.yaml.example +5 -0
  7. data/examples/example.der +0 -0
  8. data/examples/example.jpg +0 -0
  9. data/examples/groupadd +41 -0
  10. data/examples/groupdel +35 -0
  11. data/examples/groupls +49 -0
  12. data/examples/groupmod +42 -0
  13. data/examples/lpasswd +55 -0
  14. data/examples/objects/group.rb +13 -0
  15. data/examples/objects/ou.rb +4 -0
  16. data/examples/objects/user.rb +20 -0
  17. data/examples/ouadd +38 -0
  18. data/examples/useradd +45 -0
  19. data/examples/useradd-binary +50 -0
  20. data/examples/userdel +34 -0
  21. data/examples/userls +50 -0
  22. data/examples/usermod +42 -0
  23. data/examples/usermod-binary-add +47 -0
  24. data/examples/usermod-binary-add-time +51 -0
  25. data/examples/usermod-binary-del +48 -0
  26. data/examples/usermod-lang-add +43 -0
  27. data/lib/active_ldap.rb +213 -214
  28. data/lib/active_ldap/adapter/base.rb +461 -0
  29. data/lib/active_ldap/adapter/ldap.rb +232 -0
  30. data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
  31. data/lib/active_ldap/adapter/net_ldap.rb +288 -0
  32. data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
  33. data/lib/active_ldap/association/belongs_to.rb +3 -1
  34. data/lib/active_ldap/association/belongs_to_many.rb +5 -6
  35. data/lib/active_ldap/association/has_many.rb +9 -17
  36. data/lib/active_ldap/association/has_many_wrap.rb +4 -5
  37. data/lib/active_ldap/attributes.rb +4 -0
  38. data/lib/active_ldap/base.rb +201 -56
  39. data/lib/active_ldap/configuration.rb +11 -1
  40. data/lib/active_ldap/connection.rb +15 -9
  41. data/lib/active_ldap/distinguished_name.rb +246 -0
  42. data/lib/active_ldap/ldap_error.rb +74 -0
  43. data/lib/active_ldap/object_class.rb +9 -5
  44. data/lib/active_ldap/schema.rb +50 -9
  45. data/lib/active_ldap/validations.rb +11 -13
  46. data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
  47. data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
  48. data/rails/plugin/active_ldap/init.rb +10 -4
  49. data/test/al-test-utils.rb +46 -3
  50. data/test/run-test.rb +16 -4
  51. data/test/test-unit-ext/always-show-result.rb +28 -0
  52. data/test/test-unit-ext/priority.rb +163 -0
  53. data/test/test_adapter.rb +81 -0
  54. data/test/test_attributes.rb +8 -1
  55. data/test/test_base.rb +132 -3
  56. data/test/test_base_per_instance.rb +14 -3
  57. data/test/test_connection.rb +19 -0
  58. data/test/test_dn.rb +161 -0
  59. data/test/test_find.rb +24 -0
  60. data/test/test_object_class.rb +15 -2
  61. data/test/test_schema.rb +108 -1
  62. metadata +111 -41
  63. data/lib/active_ldap/adaptor/base.rb +0 -29
  64. data/lib/active_ldap/adaptor/ldap.rb +0 -466
  65. 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] = "cn=admin,dc=localdomain"
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=(adaptor)
49
- if adaptor.is_a?(Adaptor::Base)
48
+ def connection=(adapter)
49
+ if adapter.is_a?(Adapter::Base)
50
50
  @schema = nil
51
- active_connections[active_connection_name] = adaptor
52
- elsif adaptor.is_a?(Hash)
53
- config = adaptor
54
- adaptor = Inflector.camelize(config[:adaptor] || "ldap")
55
- self.connection = Adaptor.const_get(adaptor).new(config)
56
- elsif adaptor.nil?
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(adaptor)
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
- new_classes = (classes - target_classes.flatten).uniq
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
- set_attribute('objectClass', new_classes)
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 to LDAP server"
59
+ message = "unknown objectClass in LDAP server"
56
60
  message = "#{message}: #{invalid_classes.join(', ')}"
57
61
  raise ObjectClassError, message
58
62
  end
@@ -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
- while schema = @entries[group].shift
45
- next unless /\A\s*\(\s*([\d\.]+)\s*(.*)\s*\)\s*\z/ =~ schema
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-z][\w\-]*)|
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][:aliases]
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