powerhome-activeldap 3.2.3

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 (145) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +6 -0
  3. data/COPYING +340 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +59 -0
  6. data/README.textile +140 -0
  7. data/TODO +32 -0
  8. data/benchmark/README.md +64 -0
  9. data/benchmark/bench-backend.rb +247 -0
  10. data/benchmark/bench-instantiate.rb +98 -0
  11. data/benchmark/config.yaml.sample +5 -0
  12. data/doc/text/development.textile +54 -0
  13. data/doc/text/news.textile +811 -0
  14. data/doc/text/rails.textile +144 -0
  15. data/doc/text/tutorial.textile +1010 -0
  16. data/examples/config.yaml.example +5 -0
  17. data/examples/example.der +0 -0
  18. data/examples/example.jpg +0 -0
  19. data/examples/groupadd +41 -0
  20. data/examples/groupdel +35 -0
  21. data/examples/groupls +49 -0
  22. data/examples/groupmod +42 -0
  23. data/examples/lpasswd +55 -0
  24. data/examples/objects/group.rb +13 -0
  25. data/examples/objects/ou.rb +4 -0
  26. data/examples/objects/user.rb +20 -0
  27. data/examples/ouadd +38 -0
  28. data/examples/useradd +45 -0
  29. data/examples/useradd-binary +53 -0
  30. data/examples/userdel +34 -0
  31. data/examples/userls +50 -0
  32. data/examples/usermod +42 -0
  33. data/examples/usermod-binary-add +50 -0
  34. data/examples/usermod-binary-add-time +54 -0
  35. data/examples/usermod-binary-del +48 -0
  36. data/examples/usermod-lang-add +43 -0
  37. data/lib/active_ldap.rb +85 -0
  38. data/lib/active_ldap/action_controller/ldap_benchmarking.rb +55 -0
  39. data/lib/active_ldap/acts/tree.rb +78 -0
  40. data/lib/active_ldap/adapter/base.rb +707 -0
  41. data/lib/active_ldap/adapter/jndi.rb +184 -0
  42. data/lib/active_ldap/adapter/jndi_connection.rb +185 -0
  43. data/lib/active_ldap/adapter/ldap.rb +290 -0
  44. data/lib/active_ldap/adapter/ldap_ext.rb +105 -0
  45. data/lib/active_ldap/adapter/net_ldap.rb +309 -0
  46. data/lib/active_ldap/adapter/net_ldap_ext.rb +23 -0
  47. data/lib/active_ldap/association/belongs_to.rb +47 -0
  48. data/lib/active_ldap/association/belongs_to_many.rb +58 -0
  49. data/lib/active_ldap/association/children.rb +21 -0
  50. data/lib/active_ldap/association/collection.rb +105 -0
  51. data/lib/active_ldap/association/has_many.rb +31 -0
  52. data/lib/active_ldap/association/has_many_utils.rb +44 -0
  53. data/lib/active_ldap/association/has_many_wrap.rb +75 -0
  54. data/lib/active_ldap/association/proxy.rb +107 -0
  55. data/lib/active_ldap/associations.rb +205 -0
  56. data/lib/active_ldap/attribute_methods.rb +23 -0
  57. data/lib/active_ldap/attribute_methods/before_type_cast.rb +24 -0
  58. data/lib/active_ldap/attribute_methods/dirty.rb +43 -0
  59. data/lib/active_ldap/attribute_methods/query.rb +31 -0
  60. data/lib/active_ldap/attribute_methods/read.rb +44 -0
  61. data/lib/active_ldap/attribute_methods/write.rb +38 -0
  62. data/lib/active_ldap/attributes.rb +176 -0
  63. data/lib/active_ldap/base.rb +1410 -0
  64. data/lib/active_ldap/callbacks.rb +71 -0
  65. data/lib/active_ldap/command.rb +49 -0
  66. data/lib/active_ldap/compatible.rb +44 -0
  67. data/lib/active_ldap/configuration.rb +147 -0
  68. data/lib/active_ldap/connection.rb +299 -0
  69. data/lib/active_ldap/distinguished_name.rb +291 -0
  70. data/lib/active_ldap/entry_attribute.rb +78 -0
  71. data/lib/active_ldap/escape.rb +12 -0
  72. data/lib/active_ldap/get_text.rb +20 -0
  73. data/lib/active_ldap/get_text/parser.rb +161 -0
  74. data/lib/active_ldap/helper.rb +92 -0
  75. data/lib/active_ldap/human_readable.rb +133 -0
  76. data/lib/active_ldap/ldap_error.rb +74 -0
  77. data/lib/active_ldap/ldif.rb +930 -0
  78. data/lib/active_ldap/log_subscriber.rb +50 -0
  79. data/lib/active_ldap/object_class.rb +95 -0
  80. data/lib/active_ldap/operations.rb +624 -0
  81. data/lib/active_ldap/persistence.rb +100 -0
  82. data/lib/active_ldap/populate.rb +53 -0
  83. data/lib/active_ldap/railtie.rb +43 -0
  84. data/lib/active_ldap/railties/controller_runtime.rb +48 -0
  85. data/lib/active_ldap/schema.rb +701 -0
  86. data/lib/active_ldap/schema/syntaxes.rb +422 -0
  87. data/lib/active_ldap/timeout.rb +75 -0
  88. data/lib/active_ldap/timeout_stub.rb +17 -0
  89. data/lib/active_ldap/user_password.rb +99 -0
  90. data/lib/active_ldap/validations.rb +200 -0
  91. data/lib/active_ldap/version.rb +3 -0
  92. data/lib/active_ldap/xml.rb +139 -0
  93. data/lib/rails/generators/active_ldap/model/USAGE +18 -0
  94. data/lib/rails/generators/active_ldap/model/model_generator.rb +47 -0
  95. data/lib/rails/generators/active_ldap/model/templates/model_active_ldap.rb +3 -0
  96. data/lib/rails/generators/active_ldap/scaffold/scaffold_generator.rb +14 -0
  97. data/lib/rails/generators/active_ldap/scaffold/templates/ldap.yml +19 -0
  98. data/po/en/active-ldap.po +4029 -0
  99. data/po/ja/active-ldap.po +4060 -0
  100. data/test/add-phonetic-attribute-options-to-slapd.ldif +10 -0
  101. data/test/al-test-utils.rb +428 -0
  102. data/test/command.rb +111 -0
  103. data/test/config.yaml.sample +6 -0
  104. data/test/fixtures/lower_case_object_class_schema.rb +802 -0
  105. data/test/run-test.rb +34 -0
  106. data/test/test_acts_as_tree.rb +60 -0
  107. data/test/test_adapter.rb +121 -0
  108. data/test/test_associations.rb +701 -0
  109. data/test/test_attributes.rb +117 -0
  110. data/test/test_base.rb +1214 -0
  111. data/test/test_base_per_instance.rb +61 -0
  112. data/test/test_bind.rb +62 -0
  113. data/test/test_callback.rb +31 -0
  114. data/test/test_configuration.rb +40 -0
  115. data/test/test_connection.rb +82 -0
  116. data/test/test_connection_per_class.rb +112 -0
  117. data/test/test_connection_per_dn.rb +112 -0
  118. data/test/test_dirty.rb +98 -0
  119. data/test/test_dn.rb +172 -0
  120. data/test/test_find.rb +176 -0
  121. data/test/test_groupadd.rb +50 -0
  122. data/test/test_groupdel.rb +46 -0
  123. data/test/test_groupls.rb +107 -0
  124. data/test/test_groupmod.rb +51 -0
  125. data/test/test_ldif.rb +1890 -0
  126. data/test/test_load.rb +133 -0
  127. data/test/test_lpasswd.rb +75 -0
  128. data/test/test_object_class.rb +74 -0
  129. data/test/test_persistence.rb +131 -0
  130. data/test/test_reflection.rb +175 -0
  131. data/test/test_schema.rb +559 -0
  132. data/test/test_syntax.rb +444 -0
  133. data/test/test_user.rb +217 -0
  134. data/test/test_user_password.rb +108 -0
  135. data/test/test_useradd-binary.rb +62 -0
  136. data/test/test_useradd.rb +57 -0
  137. data/test/test_userdel.rb +48 -0
  138. data/test/test_userls.rb +91 -0
  139. data/test/test_usermod-binary-add-time.rb +65 -0
  140. data/test/test_usermod-binary-add.rb +64 -0
  141. data/test/test_usermod-binary-del.rb +66 -0
  142. data/test/test_usermod-lang-add.rb +59 -0
  143. data/test/test_usermod.rb +58 -0
  144. data/test/test_validation.rb +274 -0
  145. metadata +379 -0
@@ -0,0 +1,100 @@
1
+ module ActiveLdap
2
+ module Persistence
3
+ # new_entry?
4
+ #
5
+ # Return whether the entry is new entry in LDAP or not
6
+ def new_entry?
7
+ @new_entry
8
+ end
9
+
10
+ # Return whether the entry is saved entry or not.
11
+ def persisted?
12
+ not new_entry?
13
+ end
14
+
15
+ # destroy
16
+ #
17
+ # Delete this entry from LDAP
18
+ def destroy
19
+ # TODO: support deleting relations
20
+ delete
21
+ end
22
+
23
+ def delete(options={})
24
+ if persisted?
25
+ default_options = {
26
+ :connection => connection,
27
+ }
28
+ self.class.delete_entry(dn, default_options.merge(options))
29
+ end
30
+ @new_entry = true
31
+ freeze
32
+ end
33
+
34
+ # save
35
+ #
36
+ # Save and validate this object into LDAP
37
+ # either adding or replacing attributes
38
+ # TODO: Relative DN support
39
+ def save(*)
40
+ create_or_update
41
+ end
42
+
43
+ def save!(*)
44
+ unless create_or_update
45
+ raise EntryNotSaved, _("entry %s can't be saved") % dn
46
+ end
47
+ end
48
+
49
+ def create_or_update
50
+ new_entry? ? create : update
51
+ end
52
+
53
+ def create
54
+ prepare_data_for_saving do |data, ldap_data|
55
+ attributes = collect_all_attributes(data)
56
+ add_entry(dn, attributes)
57
+ @new_entry = false
58
+ true
59
+ end
60
+ end
61
+
62
+ def update
63
+ prepare_data_for_saving do |data, ldap_data|
64
+ new_dn_value, attributes = collect_modified_attributes(ldap_data, data)
65
+ modify_entry(@original_dn, attributes)
66
+ if new_dn_value
67
+ old_dn_base = DN.parse(@original_dn).parent
68
+ new_dn_base = dn.clone.parent
69
+ if old_dn_base == new_dn_base
70
+ new_superior = nil
71
+ else
72
+ new_superior = new_dn_base
73
+ end
74
+ modify_rdn_entry(@original_dn,
75
+ "#{dn_attribute}=#{DN.escape_value(new_dn_value)}",
76
+ true,
77
+ new_superior)
78
+ end
79
+ true
80
+ end
81
+ end
82
+
83
+ def reload
84
+ clear_association_cache
85
+ _, attributes = search(:value => id).find do |_dn, _attributes|
86
+ dn == _dn
87
+ end
88
+ if attributes.nil?
89
+ raise EntryNotFound, _("Can't find DN '%s' to reload") % dn
90
+ end
91
+
92
+ @ldap_data.update(attributes)
93
+ classes, attributes = extract_object_class(attributes)
94
+ self.classes = classes
95
+ self.attributes = attributes
96
+ @new_entry = false
97
+ self
98
+ end
99
+ end # Persistence
100
+ end # ActiveLdap
@@ -0,0 +1,53 @@
1
+ module ActiveLdap
2
+ module Populate
3
+ module_function
4
+ def ensure_base(base_class=nil)
5
+ base_class ||= Base
6
+ return unless base_class.search(:scope => :base).empty?
7
+
8
+ base_dn = DN.parse(base_class.base)
9
+ suffixes = []
10
+
11
+ base_dn.rdns.reverse_each do |rdn|
12
+ name, value = rdn.to_a[0]
13
+ prefix = suffixes.join(",")
14
+ suffixes.unshift("#{name}=#{value}")
15
+ next unless name == "dc"
16
+ begin
17
+ ensure_dc(value, prefix, base_class)
18
+ rescue ActiveLdap::OperationNotPermitted
19
+ end
20
+ end
21
+ end
22
+
23
+ def ensure_ou(name, base_class=nil)
24
+ base_class ||= Base
25
+ name = name.to_s if name.is_a?(DN)
26
+ name = name.gsub(/\Aou\s*=\s*/i, '')
27
+
28
+ ou_class = Class.new(base_class)
29
+ ou_class.ldap_mapping(:dn_attribute => "ou",
30
+ :prefix => "",
31
+ :classes => ["top", "organizationalUnit"])
32
+ return if ou_class.exist?(name)
33
+ ou_class.new(name).save!
34
+ end
35
+
36
+ def ensure_dc(name, prefix, base_class=nil)
37
+ base_class ||= Base
38
+ name = name.to_s if name.is_a?(DN)
39
+ name = name.gsub(/\Adc\s*=\s*/i, '')
40
+
41
+ dc_class = Class.new(base_class)
42
+ dc_class.ldap_mapping(:dn_attribute => "dc",
43
+ :prefix => "",
44
+ :scope => :base,
45
+ :classes => ["top", "dcObject", "organization"])
46
+ dc_class.base = prefix
47
+ return if dc_class.exist?(name)
48
+ dc = dc_class.new(name)
49
+ dc.o = dc.dc
50
+ dc.save!
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,43 @@
1
+ require "locale"
2
+ require 'active_ldap'
3
+ require 'rails'
4
+
5
+ Locale.init(:driver => :cgi)
6
+
7
+ module ActiveLdap
8
+ class Railtie < Rails::Railtie
9
+ config.app_generators.orm :active_ldap
10
+
11
+ initializer "active_ldap.setup_connection" do
12
+ ldap_configuration_file = Rails.root.join('config', 'ldap.yml')
13
+ if File.exist?(ldap_configuration_file)
14
+ configurations = YAML::load(ERB.new(IO.read(ldap_configuration_file)).result)
15
+ ActiveLdap::Base.configurations = configurations
16
+ ActiveLdap::Base.setup_connection
17
+ else
18
+ ActiveLdap::Base.class_eval do
19
+ format =_("You should run 'rails generator active_ldap:scaffold' to make %s.")
20
+ logger.error(format % ldap_configuration_file)
21
+ end
22
+ end
23
+ end
24
+
25
+ initializer "active_ldap.logger", :before => "active_ldap.setup_connection" do
26
+ ActiveLdap::Base.logger ||= ::Rails.logger
27
+ end
28
+
29
+ initializer "active_ldap.action_view_helper" do
30
+ class ::ActionView::Base
31
+ include ActiveLdap::Helper
32
+ end
33
+ end
34
+
35
+ # Expose Ldap runtime to controller for logging.
36
+ initializer "active_ldap.log_runtime" do |app|
37
+ require "active_ldap/railties/controller_runtime"
38
+ ActiveSupport.on_load(:action_controller) do
39
+ include ActiveLdap::Railties::ControllerRuntime
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,48 @@
1
+ require 'active_support/core_ext/module/attr_internal'
2
+ require 'active_ldap/log_subscriber'
3
+
4
+ module ActiveLdap
5
+ module Railties
6
+ module ControllerRuntime #:nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ protected
10
+
11
+ attr_internal :ldap_runtime
12
+
13
+ def process_action(action, *args)
14
+ # We also need to reset the runtime before each action
15
+ # because of queries in middleware or in cases we are streaming
16
+ # and it won't be cleaned up by the method below.
17
+ ActiveLdap::LogSubscriber.reset_runtime
18
+ super
19
+ end
20
+
21
+ def cleanup_view_runtime
22
+ if ActiveLdap::Base.connected?
23
+ ldap_rt_before_render = ActiveLdap::LogSubscriber.reset_runtime
24
+ runtime = super
25
+ ldap_rt_after_render = ActiveLdap::LogSubscriber.reset_runtime
26
+ self.ldap_runtime = ldap_rt_before_render + ldap_rt_after_render
27
+ runtime - ldap_rt_after_render
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def append_info_to_payload(payload)
34
+ super
35
+ payload[:ldap_runtime] = ldap_runtime
36
+ end
37
+
38
+ module ClassMethods
39
+ def log_process_action(payload)
40
+ messages, ldap_runtime = super, payload[:ldap_runtime]
41
+ messages << ("ActiveLdap: %.1fms" % ldap_runtime.to_f) if ldap_runtime
42
+ messages
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,701 @@
1
+ module ActiveLdap
2
+ class Schema
3
+ include GetTextSupport
4
+
5
+ def initialize(entries)
6
+ @entries = normalize_entries(entries || {})
7
+ @schema_info = {}
8
+ @class_attributes_info = {}
9
+ @cache = {}
10
+ end
11
+
12
+ def ids(group)
13
+ ensure_parse(group)
14
+ info, ids, aliases = ensure_schema_info(group)
15
+ _ = info = aliases # for suppress a warning on Ruby 1.9.3
16
+ ids.keys
17
+ end
18
+
19
+ def names(group)
20
+ alias_map(group).keys
21
+ end
22
+
23
+ def exist_name?(group, name)
24
+ alias_map(group).has_key?(normalize_schema_name(name))
25
+ end
26
+
27
+ def resolve_name(group, name)
28
+ alias_map(group)[normalize_schema_name(name)]
29
+ end
30
+
31
+ # fetch
32
+ #
33
+ # This is just like LDAP::Schema#attribute except that it allows
34
+ # look up in any of the given keys.
35
+ # e.g.
36
+ # fetch('attributeTypes', 'cn', 'DESC')
37
+ # fetch('ldapSyntaxes', '1.3.6.1.4.1.1466.115.121.1.5', 'DESC')
38
+ def fetch(group, id_or_name, attribute_name)
39
+ return [] if attribute_name.empty?
40
+ attribute_name = normalize_attribute_name(attribute_name)
41
+ value = entry(group, id_or_name)[attribute_name]
42
+ value ? value.dup : []
43
+ end
44
+ alias_method :[], :fetch
45
+
46
+ NUMERIC_OID_RE = "\\d[\\d\\.]+"
47
+ DESCRIPTION_RE = "[a-zA-Z][a-zA-Z\\d\\-]*"
48
+ OID_RE = "(?:#{NUMERIC_OID_RE}|#{DESCRIPTION_RE}-oid)"
49
+ def entry(group, id_or_name)
50
+ return {} if group.empty? or id_or_name.empty?
51
+
52
+ unless @entries.has_key?(group)
53
+ raise ArgumentError, _("Unknown schema group: %s") % group
54
+ end
55
+
56
+ # Initialize anything that is required
57
+ info, ids, aliases = ensure_schema_info(group)
58
+ _ = info # for suppress a warning on Ruby 1.9.3
59
+ id, name = determine_id_or_name(id_or_name, aliases)
60
+
61
+ # Check already parsed options first
62
+ return ids[id] if ids.has_key?(id)
63
+
64
+ schemata = @entries[group] || []
65
+ while schema = schemata.shift
66
+ next unless /\A\s*\(\s*(#{OID_RE})\s*(.*)\s*\)\s*\z/ =~ schema
67
+ schema_id = $1
68
+ rest = $2
69
+
70
+ if ids.has_key?(schema_id)
71
+ attributes = ids[schema_id]
72
+ else
73
+ attributes = {}
74
+ ids[schema_id] = attributes
75
+ end
76
+
77
+ parse_attributes(rest, attributes)
78
+ (attributes["NAME"] || []).each do |v|
79
+ normalized_name = normalize_schema_name(v)
80
+ aliases[normalized_name] = schema_id
81
+ id = schema_id if id.nil? and name == normalized_name
82
+ end
83
+
84
+ break if id == schema_id
85
+ end
86
+
87
+ ids[id || aliases[name]] || {}
88
+ end
89
+
90
+ def attribute(name)
91
+ cache([:attribute, name]) do
92
+ Attribute.new(name, self)
93
+ end
94
+ end
95
+
96
+ def attributes
97
+ cache([:attributes]) do
98
+ names("attributeTypes").collect do |name|
99
+ attribute(name)
100
+ end
101
+ end
102
+ end
103
+
104
+ def attribute_type(name, attribute_name)
105
+ cache([:attribute_type, name, attribute_name]) do
106
+ fetch("attributeTypes", name, attribute_name)
107
+ end
108
+ end
109
+
110
+ def object_class(name)
111
+ cache([:object_class, name]) do
112
+ ObjectClass.new(name, self)
113
+ end
114
+ end
115
+
116
+ def object_classes
117
+ cache([:object_classes]) do
118
+ names("objectClasses").collect do |name|
119
+ object_class(name)
120
+ end
121
+ end
122
+ end
123
+
124
+ def object_class_attribute(name, attribute_name)
125
+ cache([:object_class_attribute, name, attribute_name]) do
126
+ fetch("objectClasses", name, attribute_name)
127
+ end
128
+ end
129
+
130
+ def dit_content_rule_attribute(name, attribute_name)
131
+ cache([:dit_content_rule_attribute, name, attribute_name]) do
132
+ fetch("dITContentRules", name, attribute_name)
133
+ end
134
+ end
135
+
136
+ def ldap_syntax(name)
137
+ cache([:ldap_syntax, name]) do
138
+ Syntax.new(name, self)
139
+ end
140
+ end
141
+
142
+ def ldap_syntaxes
143
+ cache([:ldap_syntaxes]) do
144
+ ids("ldapSyntaxes").collect do |id|
145
+ ldap_syntax(id)
146
+ end
147
+ end
148
+ end
149
+
150
+ def ldap_syntax_attribute(name, attribute_name)
151
+ cache([:ldap_syntax_attribute, name, attribute_name]) do
152
+ fetch("ldapSyntaxes", name, attribute_name)
153
+ end
154
+ end
155
+
156
+ def dump(output=nil)
157
+ require 'pp'
158
+ output ||= STDOUT
159
+ if output.respond_to?(:write)
160
+ PP.pp(@entries, output)
161
+ else
162
+ open(output, "w") {|out| PP.pp(@entries, out)}
163
+ end
164
+ nil
165
+ end
166
+
167
+ private
168
+ def cache(key)
169
+ (@cache[key] ||= [yield])[0]
170
+ end
171
+
172
+ def ensure_schema_info(group)
173
+ @schema_info[group] ||= {:ids => {}, :aliases => {}}
174
+ info = @schema_info[group]
175
+ [info, info[:ids], info[:aliases]]
176
+ end
177
+
178
+ def determine_id_or_name(id_or_name, aliases)
179
+ if /\A[\d\.]+\z/ =~ id_or_name
180
+ id = id_or_name
181
+ name = nil
182
+ else
183
+ name = normalize_schema_name(id_or_name)
184
+ id = aliases[name]
185
+ end
186
+ [id, name]
187
+ end
188
+
189
+ # from RFC 2252
190
+ attribute_type_description_reserved_names =
191
+ ["NAME", "DESC", "OBSOLETE", "SUP", "EQUALITY", "ORDERING", "SUBSTR",
192
+ "SYNTAX", "SINGLE-VALUE", "COLLECTIVE", "NO-USER-MODIFICATION", "USAGE"]
193
+ syntax_description_reserved_names = ["DESC"]
194
+ object_class_description_reserved_names =
195
+ ["NAME", "DESC", "OBSOLETE", "SUP", "ABSTRACT", "STRUCTURAL",
196
+ "AUXILIARY", "MUST", "MAY"]
197
+ matching_rule_description_reserved_names =
198
+ ["NAME", "DESC", "OBSOLETE", "SYNTAX"]
199
+ matching_rule_use_description_reserved_names =
200
+ ["NAME", "DESC", "OBSOLETE", "APPLIES"]
201
+ private_experiment_reserved_names = ["X-[A-Z\\-_]+"]
202
+ reserved_names =
203
+ (attribute_type_description_reserved_names +
204
+ syntax_description_reserved_names +
205
+ object_class_description_reserved_names +
206
+ matching_rule_description_reserved_names +
207
+ matching_rule_use_description_reserved_names +
208
+ private_experiment_reserved_names).uniq
209
+ RESERVED_NAMES_RE = /(?:#{reserved_names.join('|')})/
210
+
211
+ def parse_attributes(str, attributes)
212
+ str.scan(/([A-Z\-_]+)\s+
213
+ (?:\(\s*(\w[\w\-;]*(?:\s+\$\s+\w[\w\-;]*)*)\s*\)|
214
+ \(\s*([^\)]*)\s*\)|
215
+ '([^\']*)'|
216
+ ((?!#{RESERVED_NAMES_RE})[a-zA-Z][a-zA-Z\d\-;]*)|
217
+ (\d[\d\.\{\}]+)|
218
+ ()
219
+ )/x
220
+ ) do |name, multi_amp, multi, string, literal, syntax, no_value|
221
+ case
222
+ when multi_amp
223
+ values = multi_amp.rstrip.split(/\s*\$\s*/)
224
+ when multi
225
+ values = multi.scan(/\s*'([^\']*)'\s*/).collect {|value| value[0]}
226
+ when string
227
+ values = [string]
228
+ when literal
229
+ values = [literal]
230
+ when syntax
231
+ values = [syntax]
232
+ when no_value
233
+ values = ["TRUE"]
234
+ end
235
+ attributes[normalize_attribute_name(name)] ||= []
236
+ attributes[normalize_attribute_name(name)].concat(values)
237
+ end
238
+ end
239
+
240
+ def alias_map(group)
241
+ ensure_parse(group)
242
+ return {} if @schema_info[group].nil?
243
+ @schema_info[group][:aliases] || {}
244
+ end
245
+
246
+ def ensure_parse(group)
247
+ return if @entries[group].nil?
248
+ unless @entries[group].empty?
249
+ fetch(group, 'nonexistent', 'nonexistent')
250
+ end
251
+ end
252
+
253
+ def normalize_schema_name(name)
254
+ name.downcase.sub(/;.*$/, '')
255
+ end
256
+
257
+ def normalize_attribute_name(name)
258
+ name.upcase.gsub(/_/, "-")
259
+ end
260
+
261
+ def default_entries
262
+ {
263
+ "objectClasses" => [],
264
+ "attributeTypes" => [],
265
+ "ldapSyntaxes" => [],
266
+ "dITContentRules" => [],
267
+ "matchingRules" => [],
268
+ }
269
+ end
270
+
271
+ def normalize_entries(entries)
272
+ normalized_entries = default_entries
273
+ normalized_keys = normalized_entries.keys
274
+ entries.each do |name, values|
275
+ normalized_name = normalized_keys.find do |key|
276
+ key.downcase == name
277
+ end
278
+ normalized_entries[normalized_name || name] = values
279
+ end
280
+ normalized_entries
281
+ end
282
+
283
+ class Entry
284
+ include Comparable
285
+
286
+ attr_reader :id, :name, :aliases, :description
287
+ def initialize(name, schema, group)
288
+ @schema = schema
289
+ @name, *@aliases = attribute("NAME", name)
290
+ @name ||= name
291
+ @id = @schema.resolve_name(group, @name)
292
+ collect_info
293
+ @schema = nil
294
+ end
295
+
296
+ def eql?(other)
297
+ self.class == other.class and
298
+ (id == other.id or
299
+ (id.nil? and other.nil? and name == other.name))
300
+ end
301
+
302
+ def hash
303
+ id.nil? ? name.hash : id.hash
304
+ end
305
+
306
+ def <=>(other)
307
+ name <=> other.name
308
+ end
309
+
310
+ def to_param
311
+ name
312
+ end
313
+ end
314
+
315
+ class Syntax < Entry
316
+ attr_reader :length
317
+ def initialize(id, schema)
318
+ if /\{(\d+)\}\z/ =~ id
319
+ id = $PREMATCH
320
+ @length = Integer($1)
321
+ else
322
+ @length = nil
323
+ end
324
+ super(id, schema, "ldapSyntaxes")
325
+ @id = id
326
+ @name = nil if @name == @id
327
+ @validator = Syntaxes[@id]
328
+ end
329
+
330
+ def binary_transfer_required?
331
+ @binary_transfer_required
332
+ end
333
+
334
+ def human_readable?
335
+ @human_readable
336
+ end
337
+
338
+ def valid?(value)
339
+ validate(value).nil?
340
+ end
341
+
342
+ def validate(value)
343
+ if @validator
344
+ @validator.validate(value)
345
+ else
346
+ nil
347
+ end
348
+ end
349
+
350
+ def type_cast(value)
351
+ if @validator
352
+ @validator.type_cast(value)
353
+ else
354
+ value
355
+ end
356
+ end
357
+
358
+ def normalize_value(value)
359
+ if @validator
360
+ @validator.normalize_value(value)
361
+ else
362
+ value
363
+ end
364
+ end
365
+
366
+ def <=>(other)
367
+ id <=> other.id
368
+ end
369
+
370
+ def to_param
371
+ id
372
+ end
373
+
374
+ private
375
+ def attribute(attribute_name, name=@name)
376
+ @schema.ldap_syntax_attribute(name, attribute_name)
377
+ end
378
+
379
+ def collect_info
380
+ @description = attribute("DESC")[0]
381
+ @binary_transfer_required =
382
+ (attribute('X-BINARY-TRANSFER-REQUIRED')[0] == 'TRUE')
383
+ @human_readable = (attribute('X-NOT-HUMAN-READABLE')[0] != 'TRUE')
384
+ end
385
+ end
386
+
387
+ class Attribute < Entry
388
+ include GetTextSupport
389
+ include HumanReadable
390
+
391
+ attr_reader :super_attribute
392
+ def initialize(name, schema)
393
+ super(name, schema, "attributeTypes")
394
+ end
395
+
396
+ # read_only?
397
+ #
398
+ # Returns true if an attribute is read-only
399
+ # NO-USER-MODIFICATION
400
+ def read_only?
401
+ @read_only
402
+ end
403
+
404
+ # single_value?
405
+ #
406
+ # Returns true if an attribute can only have one
407
+ # value defined
408
+ # SINGLE-VALUE
409
+ def single_value?
410
+ @single_value
411
+ end
412
+
413
+ # binary?
414
+ #
415
+ # Returns true if the given attribute's syntax
416
+ # is X-NOT-HUMAN-READABLE or X-BINARY-TRANSFER-REQUIRED
417
+ def binary?
418
+ @binary
419
+ end
420
+
421
+ # binary_required?
422
+ #
423
+ # Returns true if the value MUST be transferred in binary
424
+ def binary_required?
425
+ @binary_required
426
+ end
427
+
428
+ # directory_operation?
429
+ #
430
+ # Returns true if an attribute is directory operation.
431
+ # It means that USAGE contains directoryOperation.
432
+ def directory_operation?
433
+ @directory_operation
434
+ end
435
+
436
+ def syntax
437
+ @derived_syntax
438
+ end
439
+
440
+ def valid?(value)
441
+ validate(value).nil?
442
+ end
443
+
444
+ def validate(value)
445
+ error_info = validate_each_value(value)
446
+ return error_info if error_info
447
+ begin
448
+ normalize_value(value)
449
+ nil
450
+ rescue AttributeValueInvalid
451
+ [$!.message]
452
+ end
453
+ end
454
+
455
+ def type_cast(value)
456
+ send_to_syntax(value, :type_cast, value)
457
+ end
458
+
459
+ def normalize_value(value)
460
+ normalize_value_internal(value, false)
461
+ end
462
+
463
+ def syntax_description
464
+ send_to_syntax(nil, :description)
465
+ end
466
+
467
+ def human_attribute_name
468
+ self.class.human_attribute_name(self)
469
+ end
470
+
471
+ def human_attribute_description
472
+ self.class.human_attribute_description(self)
473
+ end
474
+
475
+ def to_hash
476
+ {
477
+ :read_only => read_only?,
478
+ :single_value => single_value?,
479
+ :binary => binary?,
480
+ :binary_required => binary_required?,
481
+ :directory_operation => directory_operation?,
482
+ :syntax => syntax,
483
+ :syntax_description => syntax_description,
484
+ }
485
+ end
486
+
487
+ private
488
+ def attribute(attribute_name, name=@name)
489
+ @schema.attribute_type(name, attribute_name)
490
+ end
491
+
492
+ def collect_info
493
+ @description = attribute("DESC")[0]
494
+ @super_attribute = attribute("SUP")[0]
495
+ if @super_attribute
496
+ @super_attribute = @schema.attribute(@super_attribute)
497
+ @super_attribute = nil if @super_attribute.id.nil?
498
+ end
499
+ @read_only = attribute('NO-USER-MODIFICATION')[0] == 'TRUE'
500
+ @single_value = attribute('SINGLE-VALUE')[0] == 'TRUE'
501
+ @syntax = attribute("SYNTAX")[0]
502
+ @syntax = @schema.ldap_syntax(@syntax) if @syntax
503
+ if @syntax
504
+ @binary_required = @syntax.binary_transfer_required?
505
+ @binary = (@binary_required or !@syntax.human_readable?)
506
+ @derived_syntax = @syntax
507
+ else
508
+ @binary_required = false
509
+ @binary = false
510
+ @derived_syntax = nil
511
+ @derived_syntax = @super_attribute.syntax if @super_attribute
512
+ end
513
+ @directory_operation = attribute("USAGE").include?("directoryOperation")
514
+ end
515
+
516
+ def send_to_syntax(default_value, method_name, *args)
517
+ _syntax = syntax
518
+ if _syntax
519
+ _syntax.send(method_name, *args)
520
+ else
521
+ default_value
522
+ end
523
+ end
524
+
525
+ def validate_each_value(value, option=nil)
526
+ failed_reason = nil
527
+ case value
528
+ when Hash
529
+ original_option = option
530
+ value.each do |sub_option, val|
531
+ opt = [original_option, sub_option].compact.join(";")
532
+ failed_reason, option = validate_each_value(val, opt)
533
+ break if failed_reason
534
+ end
535
+ when Array
536
+ original_option = option
537
+ value.each do |val|
538
+ failed_reason, option = validate_each_value(val, original_option)
539
+ break if failed_reason
540
+ end
541
+ else
542
+ failed_reason = send_to_syntax(nil, :validate, value)
543
+ end
544
+ return nil if failed_reason.nil?
545
+ [failed_reason, option]
546
+ end
547
+
548
+ def normalize_value_internal(value, have_binary_mark)
549
+ case value
550
+ when Array
551
+ normalize_array_value(value, have_binary_mark)
552
+ when Hash
553
+ normalize_hash_value(value, have_binary_mark)
554
+ else
555
+ if value.blank?
556
+ value = []
557
+ else
558
+ value = send_to_syntax(value, :normalize_value, value)
559
+ end
560
+ if !have_binary_mark and binary_required?
561
+ [{'binary' => value}]
562
+ else
563
+ value.is_a?(Array) ? value : [value]
564
+ end
565
+ end
566
+ end
567
+
568
+ def normalize_array_value(value, have_binary_mark)
569
+ if single_value? and value.reject {|v| v.is_a?(Hash)}.size > 1
570
+ format = _("Attribute %s can only have a single value: %s")
571
+ message = format % [human_attribute_name, value.inspect]
572
+ raise AttributeValueInvalid.new(self, value, message)
573
+ end
574
+ if value.empty?
575
+ if !have_binary_mark and binary_required?
576
+ [{'binary' => value}]
577
+ else
578
+ value
579
+ end
580
+ else
581
+ value.collect do |entry|
582
+ normalize_value_internal(entry, have_binary_mark)[0]
583
+ end
584
+ end
585
+ end
586
+
587
+ def normalize_hash_value(value, have_binary_mark)
588
+ if value.size > 1
589
+ format = _("Attribute %s: Hash must have one key-value pair only: %s")
590
+ message = format % [human_attribute_name, value.inspect]
591
+ raise AttributeValueInvalid.new(self, value, message)
592
+ end
593
+
594
+ if !have_binary_mark and binary_required? and !have_binary_key?(value)
595
+ [append_binary_key(value)]
596
+ else
597
+ key = value.keys[0]
598
+ have_binary_mark ||= key == "binary"
599
+ [{key => normalize_value_internal(value.values[0], have_binary_mark)}]
600
+ end
601
+ end
602
+
603
+ def have_binary_key?(hash)
604
+ key, value = hash.to_a[0]
605
+ return true if key == "binary"
606
+ return have_binary_key?(value) if value.is_a?(Hash)
607
+ false
608
+ end
609
+
610
+ def append_binary_key(hash)
611
+ key, value = hash.to_a[0]
612
+ if value.is_a?(Hash)
613
+ append_binary_key(value)
614
+ else
615
+ hash.merge(key => {"binary" => value})
616
+ end
617
+ end
618
+ end
619
+
620
+ class ObjectClass < Entry
621
+ attr_reader :super_classes
622
+ def initialize(name, schema)
623
+ super(name, schema, "objectClasses")
624
+ end
625
+
626
+ def super_class?(object_class)
627
+ @super_classes.include?(object_class)
628
+ end
629
+
630
+ def must(include_super_class=true)
631
+ if include_super_class
632
+ @all_must
633
+ else
634
+ @must
635
+ end
636
+ end
637
+
638
+ def may(include_super_class=true)
639
+ if include_super_class
640
+ @all_may
641
+ else
642
+ @may
643
+ end
644
+ end
645
+
646
+ private
647
+ def collect_info
648
+ @description = attribute("DESC")[0]
649
+ @super_classes = collect_super_classes
650
+ @must, @may, @all_must, @all_may = collect_attributes
651
+ end
652
+
653
+ def collect_super_classes
654
+ super_classes = attribute('SUP')
655
+ loop do
656
+ start_size = super_classes.size
657
+ new_super_classes = []
658
+ super_classes.each do |super_class|
659
+ new_super_classes.concat(attribute('SUP', super_class))
660
+ end
661
+
662
+ super_classes.concat(new_super_classes)
663
+ super_classes.uniq!
664
+ break if super_classes.size == start_size
665
+ end
666
+ super_classes.collect do |name|
667
+ @schema.object_class(name)
668
+ end
669
+ end
670
+
671
+ UNWRITABLE_MUST_ATTRIBUTES = ["nTSecurityDescriptor"]
672
+ def collect_attributes
673
+ must = attribute('MUST').reject do |name|
674
+ UNWRITABLE_MUST_ATTRIBUTES.include?(name)
675
+ end.uniq
676
+ must = must.collect {|name| @schema.attribute(name)}
677
+ may = attribute('MAY').uniq.collect {|name| @schema.attribute(name)}
678
+
679
+ all_must = must.dup
680
+ all_may = may.dup
681
+ @super_classes.each do |super_class|
682
+ all_must.concat(super_class.must(false))
683
+ all_may.concat(super_class.may(false))
684
+ end
685
+
686
+ # Clean out the dupes.
687
+ all_must.uniq!
688
+ all_may.uniq!
689
+
690
+ [must, may, all_must, all_may]
691
+ end
692
+
693
+ def attribute(attribute_name, name=@name)
694
+ @schema.object_class_attribute(name, attribute_name) +
695
+ @schema.dit_content_rule_attribute(name, attribute_name)
696
+ end
697
+ end
698
+ end
699
+ end
700
+
701
+ require 'active_ldap/schema/syntaxes'