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.
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