ruby-activeldap 0.7.4 → 0.8.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.
Files changed (74) hide show
  1. data/CHANGES +375 -0
  2. data/COPYING +340 -0
  3. data/LICENSE +58 -0
  4. data/Manifest.txt +33 -0
  5. data/README +63 -0
  6. data/Rakefile +37 -0
  7. data/TODO +31 -0
  8. data/benchmark/bench-al.rb +152 -0
  9. data/lib/{activeldap.rb → active_ldap.rb} +280 -263
  10. data/lib/active_ldap/adaptor/base.rb +29 -0
  11. data/lib/active_ldap/adaptor/ldap.rb +466 -0
  12. data/lib/active_ldap/association/belongs_to.rb +38 -0
  13. data/lib/active_ldap/association/belongs_to_many.rb +40 -0
  14. data/lib/active_ldap/association/collection.rb +80 -0
  15. data/lib/active_ldap/association/has_many.rb +48 -0
  16. data/lib/active_ldap/association/has_many_wrap.rb +56 -0
  17. data/lib/active_ldap/association/proxy.rb +89 -0
  18. data/lib/active_ldap/associations.rb +162 -0
  19. data/lib/active_ldap/attributes.rb +199 -0
  20. data/lib/active_ldap/base.rb +1343 -0
  21. data/lib/active_ldap/callbacks.rb +19 -0
  22. data/lib/active_ldap/command.rb +46 -0
  23. data/lib/active_ldap/configuration.rb +96 -0
  24. data/lib/active_ldap/connection.rb +137 -0
  25. data/lib/{activeldap → active_ldap}/ldap.rb +1 -1
  26. data/lib/active_ldap/object_class.rb +70 -0
  27. data/lib/active_ldap/schema.rb +258 -0
  28. data/lib/{activeldap → active_ldap}/timeout.rb +0 -0
  29. data/lib/{activeldap → active_ldap}/timeout_stub.rb +0 -0
  30. data/lib/active_ldap/user_password.rb +92 -0
  31. data/lib/active_ldap/validations.rb +78 -0
  32. data/rails/plugin/active_ldap/README +54 -0
  33. data/rails/plugin/active_ldap/init.rb +6 -0
  34. data/test/TODO +2 -0
  35. data/test/al-test-utils.rb +337 -0
  36. data/test/command.rb +62 -0
  37. data/test/config.yaml +8 -0
  38. data/test/config.yaml.sample +6 -0
  39. data/test/run-test.rb +17 -0
  40. data/test/test-unit-ext.rb +2 -0
  41. data/test/test_associations.rb +334 -0
  42. data/test/test_attributes.rb +71 -0
  43. data/test/test_base.rb +345 -0
  44. data/test/test_base_per_instance.rb +32 -0
  45. data/test/test_bind.rb +53 -0
  46. data/test/test_callback.rb +35 -0
  47. data/test/test_connection.rb +38 -0
  48. data/test/test_connection_per_class.rb +50 -0
  49. data/test/test_find.rb +36 -0
  50. data/test/test_groupadd.rb +50 -0
  51. data/test/test_groupdel.rb +46 -0
  52. data/test/test_groupls.rb +107 -0
  53. data/test/test_groupmod.rb +51 -0
  54. data/test/test_lpasswd.rb +75 -0
  55. data/test/test_object_class.rb +32 -0
  56. data/test/test_reflection.rb +173 -0
  57. data/test/test_schema.rb +166 -0
  58. data/test/test_user.rb +209 -0
  59. data/test/test_user_password.rb +93 -0
  60. data/test/test_useradd-binary.rb +59 -0
  61. data/test/test_useradd.rb +55 -0
  62. data/test/test_userdel.rb +48 -0
  63. data/test/test_userls.rb +86 -0
  64. data/test/test_usermod-binary-add-time.rb +62 -0
  65. data/test/test_usermod-binary-add.rb +61 -0
  66. data/test/test_usermod-binary-del.rb +64 -0
  67. data/test/test_usermod-lang-add.rb +57 -0
  68. data/test/test_usermod.rb +56 -0
  69. data/test/test_validation.rb +38 -0
  70. metadata +94 -21
  71. data/lib/activeldap/associations.rb +0 -170
  72. data/lib/activeldap/base.rb +0 -1456
  73. data/lib/activeldap/configuration.rb +0 -59
  74. data/lib/activeldap/schema2.rb +0 -217
@@ -0,0 +1,19 @@
1
+ require 'active_record/callbacks'
2
+
3
+ module ActiveLdap
4
+ module Callbacks
5
+ def self.append_features(base)
6
+ super
7
+
8
+ base.class_eval do
9
+ include ActiveRecord::Callbacks
10
+
11
+ def callback(method)
12
+ super
13
+ rescue ActiveRecord::ActiveRecordError
14
+ raise Error, $!.message
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ module ActiveLdap
5
+ module Command
6
+ module_function
7
+ def parse_options(argv=nil, version=nil)
8
+ argv ||= ARGV.dup
9
+ options = OpenStruct.new
10
+ opts = OptionParser.new do |opts|
11
+ yield(opts, options)
12
+
13
+ opts.separator ""
14
+ opts.separator "Common options:"
15
+
16
+ opts.on_tail("--config=CONFIG",
17
+ "Specify configuration file written as YAML") do |file|
18
+ require 'yaml'
19
+ config = YAML.load(File.read(file)).symbolize_keys
20
+ Configuration::DEFAULT_CONFIG.update(config)
21
+ end
22
+
23
+ opts.on_tail("-h", "--help", "Show this message") do
24
+ puts opts
25
+ exit
26
+ end
27
+
28
+ opts.on_tail("--version", "Show version") do
29
+ puts(version || VERSION)
30
+ exit
31
+ end
32
+ end
33
+ opts.parse!(argv)
34
+ [argv, opts, options]
35
+ end
36
+
37
+ def read_password(prompt, input=$stdin, output=$stdout)
38
+ output.print prompt
39
+ system "/bin/stty -echo" if input.tty?
40
+ input.gets.chomp
41
+ ensure
42
+ system "/bin/stty echo" if input.tty?
43
+ output.puts
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,96 @@
1
+
2
+ module ActiveLdap
3
+ # Configuration
4
+ #
5
+ # Configuration provides the default settings required for
6
+ # ActiveLdap to work with your LDAP server. All of these
7
+ # settings can be passed in at initialization time.
8
+ module Configuration
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+
13
+ DEFAULT_CONFIG = {}
14
+ DEFAULT_CONFIG[:host] = '127.0.0.1'
15
+ DEFAULT_CONFIG[:port] = 389
16
+ DEFAULT_CONFIG[:method] = :plain # :ssl, :tls, :plain allowed
17
+
18
+ DEFAULT_CONFIG[:bind_dn] = "cn=admin,dc=localdomain"
19
+ DEFAULT_CONFIG[:password_block] = nil
20
+ DEFAULT_CONFIG[:password] = nil
21
+ DEFAULT_CONFIG[:store_password] = true
22
+ DEFAULT_CONFIG[:allow_anonymous] = true
23
+ DEFAULT_CONFIG[:sasl_quiet] = false
24
+ DEFAULT_CONFIG[:try_sasl] = false
25
+
26
+ DEFAULT_CONFIG[:retry_limit] = 3
27
+ DEFAULT_CONFIG[:retry_wait] = 3
28
+ DEFAULT_CONFIG[:timeout] = 0 # in seconds; 0 <= Never timeout
29
+ # Whether or not to retry on timeouts
30
+ DEFAULT_CONFIG[:retry_on_timeout] = true
31
+
32
+ DEFAULT_CONFIG[:logger] = nil
33
+
34
+ module ClassMethods
35
+ @@defined_configurations = {}
36
+
37
+ def default_configuration
38
+ DEFAULT_CONFIG.dup
39
+ end
40
+
41
+ def ensure_configuration(config=nil)
42
+ if config.nil?
43
+ if defined?(LDAP_ENV)
44
+ config = LDAP_ENV
45
+ elsif defined?(RAILS_ENV)
46
+ config = RAILS_ENV
47
+ else
48
+ config = {}
49
+ end
50
+ end
51
+
52
+ if config.is_a?(Symbol) or config.is_a?(String)
53
+ _config = configurations[config.to_s]
54
+ unless _config
55
+ raise ConnectionError, "#{config} connection is not configured"
56
+ end
57
+ config = _config
58
+ end
59
+
60
+ config
61
+ end
62
+
63
+ def configuration(key=nil)
64
+ @@defined_configurations[key || active_connection_name]
65
+ end
66
+
67
+ def define_configuration(key, config)
68
+ @@defined_configurations[key] = config
69
+ end
70
+
71
+ def defined_configurations
72
+ @@defined_configurations
73
+ end
74
+
75
+ def remove_configuration_by_configuration(config)
76
+ @@defined_configurations.delete_if {|key, value| value == config}
77
+ end
78
+
79
+ def merge_configuration(config)
80
+ configuration = default_configuration
81
+ config.symbolize_keys.each do |key, value|
82
+ case key
83
+ when :base
84
+ # Scrub before inserting
85
+ self.base = value.gsub(/['}{#]/, '')
86
+ when :ldap_scope
87
+ self.ldap_scope = value
88
+ else
89
+ configuration[key] = value
90
+ end
91
+ end
92
+ configuration
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,137 @@
1
+ module ActiveLdap
2
+ module Connection
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ @@active_connections = {}
9
+
10
+ def active_connections
11
+ @@active_connections[Thread.current.object_id] ||= {}
12
+ end
13
+
14
+ def active_connection_name
15
+ @active_connection_name ||= determine_active_connection_name
16
+ end
17
+
18
+ def clear_active_connections!
19
+ connections = active_connections
20
+ connections.each do |key, connection|
21
+ connection.disconnect!
22
+ end
23
+ connections.clear
24
+ end
25
+
26
+ def clear_active_connection_name
27
+ @active_connection_name = nil
28
+ ObjectSpace.each_object(Class) do |klass|
29
+ if klass < self and !klass.name.empty?
30
+ klass.instance_variable_set("@active_connection_name", nil)
31
+ end
32
+ end
33
+ end
34
+
35
+ def connection
36
+ conn = nil
37
+ @active_connection_name ||= nil
38
+ if @active_connection_name
39
+ conn = active_connections[@active_connection_name]
40
+ end
41
+ unless conn
42
+ conn = retrieve_connection
43
+ active_connections[@active_connection_name] = conn
44
+ end
45
+ conn
46
+ end
47
+
48
+ def connection=(adaptor)
49
+ if adaptor.is_a?(Adaptor::Base)
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?
57
+ raise ConnectionNotEstablished
58
+ else
59
+ establish_connection(adaptor)
60
+ end
61
+ end
62
+
63
+ def connected?
64
+ active_connections[active_connection_name] ? true : false
65
+ end
66
+
67
+ def retrieve_connection
68
+ conn = nil
69
+ name = active_connection_name
70
+ raise ConnectionNotEstablished unless name
71
+ conn = active_connections[name]
72
+ if conn.nil?
73
+ config = configuration(name)
74
+ raise ConnectionNotEstablished unless config
75
+ self.connection = config
76
+ conn = active_connections[name]
77
+ end
78
+ raise ConnectionNotEstablished if conn.nil?
79
+ conn
80
+ end
81
+
82
+ def remove_connection(klass=self)
83
+ key = active_connection_key(klass)
84
+ config = configuration(key)
85
+ conn = active_connections[key]
86
+ remove_configuration_by_configuration(config)
87
+ active_connections.delete_if {|key, value| value == conn}
88
+ conn.disconnect! if conn
89
+ config
90
+ end
91
+
92
+ def establish_connection(config=nil)
93
+ config = ensure_configuration(config)
94
+ remove_connection
95
+
96
+ clear_active_connection_name
97
+ key = active_connection_key
98
+ @active_connection_name = key
99
+ define_configuration(key, merge_configuration(config))
100
+ end
101
+
102
+ # Return the schema object
103
+ def schema
104
+ @schema ||= connection.schema
105
+ end
106
+
107
+ private
108
+ def active_connection_key(k=self)
109
+ k.name.empty? ? k.object_id : k.name
110
+ end
111
+
112
+ def determine_active_connection_name
113
+ key = active_connection_key
114
+ if active_connections[key] or configuration(key)
115
+ key
116
+ elsif self == ActiveLdap::Base
117
+ nil
118
+ else
119
+ superclass.active_connection_name
120
+ end
121
+ end
122
+ end
123
+
124
+ def connection
125
+ self.class.connection
126
+ end
127
+
128
+ # schema
129
+ #
130
+ # Returns the value of self.class.schema
131
+ # This is just syntactic sugar
132
+ def schema
133
+ logger.debug {"stub: called schema"}
134
+ self.class.schema
135
+ end
136
+ end
137
+ end
@@ -1,4 +1,4 @@
1
- # Extensions to Rubu/LDAP to make ActiveLDAP behave better
1
+ # Extensions to Rubu/LDAP to make ActiveLdap behave better
2
2
  #
3
3
  # Copyright 2006 Will Drewry <will@alum.bu.edu>
4
4
  # Some portions Copyright 2006 Google Inc
@@ -0,0 +1,70 @@
1
+ module ActiveLdap
2
+ module ObjectClass
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ end
9
+
10
+ def add_class(*target_classes)
11
+ replace_class((classes + target_classes.flatten).uniq)
12
+ end
13
+
14
+ 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)
18
+ end
19
+
20
+ def replace_class(*target_classes)
21
+ new_classes = target_classes.flatten.uniq
22
+ assert_object_classes(new_classes)
23
+ set_attribute('objectClass', new_classes)
24
+ end
25
+
26
+ def classes
27
+ (get_attribute('objectClass', true) || []).dup
28
+ end
29
+
30
+ private
31
+ def assert_object_classes(new_classes)
32
+ assert_valid_object_class_value_type(new_classes)
33
+ assert_valid_object_class_value(new_classes)
34
+ assert_have_all_required_classes(new_classes)
35
+ end
36
+
37
+ def assert_valid_object_class_value_type(new_classes)
38
+ invalid_classes = new_classes.reject do |new_class|
39
+ new_class.is_a?(String)
40
+ end
41
+ unless invalid_classes.empty?
42
+ message = "Value in objectClass array is not a String"
43
+ invalid_classes_info = invalid_classes.collect do |invalid_class|
44
+ "#{invalid_class.class}:#{invalid_class.inspect}"
45
+ end.join(", ")
46
+ raise TypeError, "#{message}: #{invalid_classes_info}"
47
+ end
48
+ end
49
+
50
+ def assert_valid_object_class_value(new_classes)
51
+ invalid_classes = new_classes.reject do |new_class|
52
+ schema.exist_name?("objectClasses", new_class)
53
+ end
54
+ unless invalid_classes.empty?
55
+ message = "unknown objectClass to LDAP server"
56
+ message = "#{message}: #{invalid_classes.join(', ')}"
57
+ raise ObjectClassError, message
58
+ end
59
+ end
60
+
61
+ def assert_have_all_required_classes(new_classes)
62
+ required_classes = self.class.required_classes - new_classes
63
+ unless required_classes.empty?
64
+ raise RequiredObjectClassMissed,
65
+ "Can't remove required objectClass: " +
66
+ required_classes.join(", ")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,258 @@
1
+ module ActiveLdap
2
+ class Schema
3
+ def initialize(entries)
4
+ @entries = entries
5
+ @schema_info = {}
6
+ @class_attributes_info = {}
7
+ @cache = {}
8
+ end
9
+
10
+ def names(group)
11
+ alias_map(group).keys
12
+ end
13
+
14
+ def exist_name?(group, name)
15
+ alias_map(group).has_key?(normalize_schema_name(name))
16
+ end
17
+
18
+ # attribute
19
+ #
20
+ # This is just like LDAP::Schema#attribute except that it allows
21
+ # look up in any of the given keys.
22
+ # e.g.
23
+ # attribute('attributeTypes', 'cn', 'DESC')
24
+ # attribute('ldapSyntaxes', '1.3.6.1.4.1.1466.115.121.1.5', 'DESC')
25
+ def attribute(group, id_or_name, attribute_name)
26
+ return [] if attribute_name.empty?
27
+ attribute_name = normalize_attribute_name(attribute_name)
28
+ value = attributes(group, id_or_name)[attribute_name]
29
+ value ? value.dup : []
30
+ end
31
+ alias_method :[], :attribute
32
+ alias_method :attr, :attribute
33
+
34
+ def attributes(group, id_or_name)
35
+ return {} if group.empty? or id_or_name.empty?
36
+
37
+ # Initialize anything that is required
38
+ info, ids, aliases = ensure_schema_info(group)
39
+ id, name = determine_id_or_name(id_or_name, aliases)
40
+
41
+ # Check already parsed options first
42
+ return ids[id] if ids.has_key?(id)
43
+
44
+ while schema = @entries[group].shift
45
+ next unless /\A\s*\(\s*([\d\.]+)\s*(.*)\s*\)\s*\z/ =~ schema
46
+ schema_id = $1
47
+ rest = $2
48
+ next if ids.has_key?(schema_id)
49
+
50
+ attributes = {}
51
+ ids[schema_id] = attributes
52
+
53
+ parse_attributes(rest, attributes)
54
+ (attributes["NAME"] || []).each do |v|
55
+ normalized_name = normalize_schema_name(v)
56
+ aliases[normalized_name] = schema_id
57
+ id = schema_id if id.nil? and name == normalized_name
58
+ end
59
+
60
+ break if id == schema_id
61
+ end
62
+
63
+ ids[id || aliases[name]] || {}
64
+ end
65
+
66
+ # attribute_aliases
67
+ #
68
+ # Returns all names from the LDAP schema for the
69
+ # attribute given.
70
+ def attribute_aliases(name)
71
+ cache([:attribute_aliases, name]) do
72
+ attribute_type(name, 'NAME')
73
+ end
74
+ end
75
+
76
+ # read_only?
77
+ #
78
+ # Returns true if an attribute is read-only
79
+ # NO-USER-MODIFICATION
80
+ def read_only?(name)
81
+ cache([:read_only?, name]) do
82
+ attribute_type(name, 'NO-USER-MODIFICATION')[0] == 'TRUE'
83
+ end
84
+ end
85
+
86
+ # single_value?
87
+ #
88
+ # Returns true if an attribute can only have one
89
+ # value defined
90
+ # SINGLE-VALUE
91
+ def single_value?(name)
92
+ cache([:single_value?, name]) do
93
+ attribute_type(name, 'SINGLE-VALUE')[0] == 'TRUE'
94
+ end
95
+ end
96
+
97
+ # binary?
98
+ #
99
+ # Returns true if the given attribute's syntax
100
+ # is X-NOT-HUMAN-READABLE or X-BINARY-TRANSFER-REQUIRED
101
+ def binary?(name)
102
+ cache([:binary?, name]) do
103
+ # Get syntax OID
104
+ syntax = attribute_type(name, 'SYNTAX')[0]
105
+ !syntax.nil? and
106
+ (ldap_syntax(syntax, 'X-NOT-HUMAN-READABLE') == ["TRUE"] or
107
+ ldap_syntax(syntax, 'X-BINARY-TRANSFER-REQUIRED') == ["TRUE"])
108
+ end
109
+ end
110
+
111
+ # binary_required?
112
+ #
113
+ # Returns true if the value MUST be transferred in binary
114
+ def binary_required?(name)
115
+ cache([:binary_required?, name]) do
116
+ # Get syntax OID
117
+ syntax = attribute_type(name, 'SYNTAX')[0]
118
+ !syntax.nil? and
119
+ ldap_syntax(syntax, 'X-BINARY-TRANSFER-REQUIRED') == ["TRUE"]
120
+ end
121
+ end
122
+
123
+ # class_attributes
124
+ #
125
+ # Returns an Array of all the valid attributes (but not with full aliases)
126
+ # for the given objectClass
127
+ def class_attributes(objc)
128
+ cache([:class_attributes, objc]) do
129
+ # First get all the current level attributes
130
+ must = object_class(objc, 'MUST')
131
+ may = object_class(objc, 'MAY')
132
+
133
+ # Now add all attributes from the parent object (SUPerclasses)
134
+ # Hopefully an iterative approach will be pretty speedy
135
+ # 1. build complete list of SUPs
136
+ # 2. Add attributes from each
137
+ sups = object_class(objc, 'SUP')
138
+ loop do
139
+ start_size = sups.size
140
+ new_sups = []
141
+ sups.each do |sup|
142
+ new_sups.concat(object_class(sup, 'SUP'))
143
+ end
144
+
145
+ sups.concat(new_sups)
146
+ sups.uniq!
147
+ break if sups.size == start_size
148
+ end
149
+ sups.each do |sup|
150
+ must.concat(object_class(sup, 'MUST'))
151
+ may.concat(object_class(sup, 'MAY'))
152
+ end
153
+
154
+ # Clean out the dupes.
155
+ must.uniq!
156
+ may.uniq!
157
+ if objc == "inetOrgPerson"
158
+ may.collect! do |name|
159
+ if name == "x500uniqueIdentifier"
160
+ "x500UniqueIdentifier"
161
+ else
162
+ name
163
+ end
164
+ end
165
+ end
166
+
167
+ {:must => must, :may => may}
168
+ end
169
+ end
170
+
171
+ private
172
+ def cache(key)
173
+ (@cache[key] ||= [yield])[0]
174
+ end
175
+
176
+ def ensure_schema_info(group)
177
+ @schema_info[group] ||= {:ids => {}, :aliases => {}}
178
+ info = @schema_info[group]
179
+ [info, info[:ids], info[:aliases]]
180
+ end
181
+
182
+ def determine_id_or_name(id_or_name, aliases)
183
+ if /\A[\d\.]+\z/ =~ id_or_name
184
+ id = id_or_name
185
+ name = nil
186
+ else
187
+ name = normalize_schema_name(id_or_name)
188
+ id = aliases[name]
189
+ end
190
+ [id, name]
191
+ end
192
+
193
+ def parse_attributes(str, attributes)
194
+ str.scan(/([A-Z\-]+)\s+
195
+ (?:\(\s*([\w\-]+(?:\s+\$\s+[\w\-]+)+)\s*\)|
196
+ \(\s*([^\)]*)\s*\)|
197
+ '([^\']*)'|
198
+ ([a-z][\w\-]*)|
199
+ (\d[\d\.\{\}]+)|
200
+ ()
201
+ )/x
202
+ ) do |name, multi_amp, multi, string, literal, syntax, no_value|
203
+ case
204
+ when multi_amp
205
+ values = multi_amp.rstrip.split(/\s*\$\s*/)
206
+ when multi
207
+ values = multi.scan(/\s*'([^\']*)'\s*/).collect {|value| value[0]}
208
+ when string
209
+ values = [string]
210
+ when literal
211
+ values = [literal]
212
+ when syntax
213
+ values = [syntax]
214
+ when no_value
215
+ values = ["TRUE"]
216
+ end
217
+ attributes[name] = values
218
+ end
219
+ end
220
+
221
+ def attribute_type(name, attribute_name)
222
+ cache([:attribute_type, name, attribute_name]) do
223
+ attribute("attributeTypes", name, attribute_name)
224
+ end
225
+ end
226
+
227
+ def ldap_syntax(name, attribute_name)
228
+ cache([:ldap_syntax, name, attribute_name]) do
229
+ attribute("ldapSyntaxes", name, attribute_name)
230
+ end
231
+ end
232
+
233
+ def object_class(name, attribute_name)
234
+ cache([:object_class, name, attribute_name]) do
235
+ attribute("objectClasses", name, attribute_name)
236
+ end
237
+ end
238
+
239
+ def alias_map(group)
240
+ ensure_parse(group)
241
+ @schema_info[group][:aliases]
242
+ end
243
+
244
+ def ensure_parse(group)
245
+ unless @entries[group].empty?
246
+ attribute(group, 'nonexistent', 'nonexistent')
247
+ end
248
+ end
249
+
250
+ def normalize_schema_name(name)
251
+ name.downcase.sub(/;.*$/, '')
252
+ end
253
+
254
+ def normalize_attribute_name(name)
255
+ name.upcase
256
+ end
257
+ end # Schema
258
+ end