ruby-activeldap 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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