ruby-activeldap 0.8.3 → 0.8.3.1
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.
- data/CHANGES +431 -0
- data/COPYING +340 -0
- data/LICENSE +58 -0
- data/README +104 -0
- data/Rakefile +165 -0
- data/TODO +22 -0
- data/benchmark/bench-al.rb +202 -0
- data/benchmark/config.yaml.sample +5 -0
- data/data/locale/en/LC_MESSAGES/active-ldap.mo +0 -0
- data/data/locale/ja/LC_MESSAGES/active-ldap.mo +0 -0
- data/examples/al-admin/README +182 -0
- data/examples/al-admin/Rakefile +10 -0
- data/examples/al-admin/app/controllers/account_controller.rb +50 -0
- data/examples/al-admin/app/controllers/application.rb +15 -0
- data/examples/al-admin/app/controllers/directory_controller.rb +22 -0
- data/examples/al-admin/app/controllers/users_controller.rb +38 -0
- data/examples/al-admin/app/controllers/welcome_controller.rb +4 -0
- data/examples/al-admin/app/helpers/account_helper.rb +2 -0
- data/examples/al-admin/app/helpers/application_helper.rb +6 -0
- data/examples/al-admin/app/helpers/directory_helper.rb +2 -0
- data/examples/al-admin/app/helpers/users_helper.rb +13 -0
- data/examples/al-admin/app/helpers/welcome_helper.rb +2 -0
- data/examples/al-admin/app/models/entry.rb +19 -0
- data/examples/al-admin/app/models/ldap_user.rb +49 -0
- data/examples/al-admin/app/models/user.rb +91 -0
- data/examples/al-admin/app/views/account/login.rhtml +12 -0
- data/examples/al-admin/app/views/account/sign_up.rhtml +22 -0
- data/examples/al-admin/app/views/directory/index.rhtml +5 -0
- data/examples/al-admin/app/views/directory/populate.rhtml +2 -0
- data/examples/al-admin/app/views/layouts/application.rhtml +41 -0
- data/examples/al-admin/app/views/users/_attribute_information.rhtml +22 -0
- data/examples/al-admin/app/views/users/_entry.rhtml +12 -0
- data/examples/al-admin/app/views/users/_form.rhtml +29 -0
- data/examples/al-admin/app/views/users/_object_class_information.rhtml +23 -0
- data/examples/al-admin/app/views/users/edit.rhtml +10 -0
- data/examples/al-admin/app/views/users/index.rhtml +9 -0
- data/examples/al-admin/app/views/users/show.rhtml +3 -0
- data/examples/al-admin/app/views/welcome/index.rhtml +16 -0
- data/examples/al-admin/config/boot.rb +45 -0
- data/examples/al-admin/config/database.yml.example +19 -0
- data/examples/al-admin/config/environment.rb +68 -0
- data/examples/al-admin/config/environments/development.rb +21 -0
- data/examples/al-admin/config/environments/production.rb +18 -0
- data/examples/al-admin/config/environments/test.rb +19 -0
- data/examples/al-admin/config/ldap.yml.example +21 -0
- data/examples/al-admin/config/routes.rb +26 -0
- data/examples/al-admin/db/migrate/001_create_users.rb +16 -0
- data/examples/al-admin/lib/accept_http_rails_relative_url_root.rb +9 -0
- data/examples/al-admin/lib/authenticated_system.rb +131 -0
- data/examples/al-admin/lib/authenticated_test_helper.rb +113 -0
- data/examples/al-admin/lib/tasks/gettext.rake +35 -0
- data/examples/al-admin/po/en/al-admin.po +190 -0
- data/examples/al-admin/po/ja/al-admin.po +190 -0
- data/examples/al-admin/po/nl/al-admin.po +202 -0
- data/examples/al-admin/public/.htaccess +40 -0
- data/examples/al-admin/public/404.html +30 -0
- data/examples/al-admin/public/500.html +30 -0
- data/examples/al-admin/public/dispatch.cgi +10 -0
- data/examples/al-admin/public/dispatch.fcgi +24 -0
- data/examples/al-admin/public/dispatch.rb +10 -0
- data/examples/al-admin/public/favicon.ico +0 -0
- data/examples/al-admin/public/images/rails.png +0 -0
- data/examples/al-admin/public/javascripts/application.js +2 -0
- data/examples/al-admin/public/javascripts/controls.js +833 -0
- data/examples/al-admin/public/javascripts/dragdrop.js +942 -0
- data/examples/al-admin/public/javascripts/effects.js +1088 -0
- data/examples/al-admin/public/javascripts/prototype.js +2515 -0
- data/examples/al-admin/public/robots.txt +1 -0
- data/examples/al-admin/public/stylesheets/rails.css +35 -0
- data/examples/al-admin/public/stylesheets/screen.css +52 -0
- data/examples/al-admin/script/about +3 -0
- data/examples/al-admin/script/breakpointer +3 -0
- data/examples/al-admin/script/console +3 -0
- data/examples/al-admin/script/destroy +3 -0
- data/examples/al-admin/script/generate +3 -0
- data/examples/al-admin/script/performance/benchmarker +3 -0
- data/examples/al-admin/script/performance/profiler +3 -0
- data/examples/al-admin/script/plugin +3 -0
- data/examples/al-admin/script/process/inspector +3 -0
- data/examples/al-admin/script/process/reaper +3 -0
- data/examples/al-admin/script/process/spawner +3 -0
- data/examples/al-admin/script/runner +3 -0
- data/examples/al-admin/script/server +3 -0
- data/examples/al-admin/test/fixtures/users.yml +9 -0
- data/examples/al-admin/test/functional/account_controller_test.rb +24 -0
- data/examples/al-admin/test/functional/directory_controller_test.rb +18 -0
- data/examples/al-admin/test/functional/users_controller_test.rb +18 -0
- data/examples/al-admin/test/functional/welcome_controller_test.rb +18 -0
- data/examples/al-admin/test/run-test.sh +3 -0
- data/examples/al-admin/test/test_helper.rb +28 -0
- data/examples/al-admin/test/unit/user_test.rb +13 -0
- data/examples/al-admin/vendor/plugins/exception_notification/README +111 -0
- data/examples/al-admin/vendor/plugins/exception_notification/init.rb +1 -0
- data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifiable.rb +99 -0
- data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier.rb +67 -0
- data/examples/al-admin/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb +77 -0
- data/examples/al-admin/vendor/plugins/exception_notification/test/exception_notifier_helper_test.rb +61 -0
- data/examples/al-admin/vendor/plugins/exception_notification/test/test_helper.rb +7 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml +1 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml +7 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml +16 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml +3 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml +2 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml +3 -0
- data/examples/al-admin/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml +6 -0
- data/examples/config.yaml.example +5 -0
- data/examples/example.der +0 -0
- data/examples/example.jpg +0 -0
- data/examples/groupadd +41 -0
- data/examples/groupdel +35 -0
- data/examples/groupls +49 -0
- data/examples/groupmod +42 -0
- data/examples/lpasswd +55 -0
- data/examples/objects/group.rb +13 -0
- data/examples/objects/ou.rb +4 -0
- data/examples/objects/user.rb +20 -0
- data/examples/ouadd +38 -0
- data/examples/useradd +45 -0
- data/examples/useradd-binary +50 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +47 -0
- data/examples/usermod-binary-add-time +51 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +978 -0
- data/lib/active_ldap/adapter/base.rb +512 -0
- data/lib/active_ldap/adapter/ldap.rb +233 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
- data/lib/active_ldap/adapter/net_ldap.rb +290 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
- data/lib/active_ldap/association/belongs_to.rb +47 -0
- data/lib/active_ldap/association/belongs_to_many.rb +42 -0
- data/lib/active_ldap/association/collection.rb +83 -0
- data/lib/active_ldap/association/has_many.rb +31 -0
- data/lib/active_ldap/association/has_many_utils.rb +35 -0
- data/lib/active_ldap/association/has_many_wrap.rb +46 -0
- data/lib/active_ldap/association/proxy.rb +102 -0
- data/lib/active_ldap/associations.rb +172 -0
- data/lib/active_ldap/attributes.rb +211 -0
- data/lib/active_ldap/base.rb +1256 -0
- data/lib/active_ldap/callbacks.rb +19 -0
- data/lib/active_ldap/command.rb +48 -0
- data/lib/active_ldap/configuration.rb +114 -0
- data/lib/active_ldap/connection.rb +234 -0
- data/lib/active_ldap/distinguished_name.rb +250 -0
- data/lib/active_ldap/escape.rb +12 -0
- data/lib/active_ldap/get_text/parser.rb +142 -0
- data/lib/active_ldap/get_text_fallback.rb +53 -0
- data/lib/active_ldap/get_text_support.rb +12 -0
- data/lib/active_ldap/helper.rb +23 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/object_class.rb +93 -0
- data/lib/active_ldap/operations.rb +419 -0
- data/lib/active_ldap/populate.rb +44 -0
- data/lib/active_ldap/schema.rb +427 -0
- data/lib/active_ldap/timeout.rb +75 -0
- data/lib/active_ldap/timeout_stub.rb +17 -0
- data/lib/active_ldap/user_password.rb +93 -0
- data/lib/active_ldap/validations.rb +112 -0
- data/po/en/active-ldap.po +3011 -0
- data/po/ja/active-ldap.po +3044 -0
- data/rails/plugin/active_ldap/README +54 -0
- data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
- data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
- data/rails/plugin/active_ldap/init.rb +19 -0
- data/test/al-test-utils.rb +362 -0
- data/test/command.rb +62 -0
- data/test/config.yaml.sample +6 -0
- data/test/run-test.rb +31 -0
- data/test/test-unit-ext.rb +4 -0
- data/test/test-unit-ext/always-show-result.rb +28 -0
- data/test/test-unit-ext/backtrace-filter.rb +17 -0
- data/test/test-unit-ext/long-display-for-emacs.rb +25 -0
- data/test/test-unit-ext/priority.rb +163 -0
- metadata +211 -4
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
module ActiveLdap
|
|
2
|
+
module Attributes
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
extend(ClassMethods)
|
|
6
|
+
extend(Normalize)
|
|
7
|
+
include(Normalize)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def attr_protected(*attributes)
|
|
13
|
+
targets = attributes.collect {|attr| attr.to_s} - protected_attributes
|
|
14
|
+
instance_variable_set("@attr_protected", targets)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def protected_attributes
|
|
18
|
+
ancestors[0..(ancestors.index(Base))].inject([]) do |result, ancestor|
|
|
19
|
+
result + ancestor.instance_eval {@attr_protected ||= []}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Normalize
|
|
25
|
+
def normalize_attribute_name(name)
|
|
26
|
+
name.to_s.downcase
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Enforce typing:
|
|
30
|
+
# Hashes are for subtypes
|
|
31
|
+
# Arrays are for multiple entries
|
|
32
|
+
def normalize_attribute(name, value)
|
|
33
|
+
if name.nil?
|
|
34
|
+
raise RuntimeError, _('The first argument, name, must not be nil. ' \
|
|
35
|
+
'Please report this as a bug!')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
name = normalize_attribute_name(name)
|
|
39
|
+
rubyish_class_name = Inflector.underscore(value.class.name)
|
|
40
|
+
handler = "normalize_attribute_value_of_#{rubyish_class_name}"
|
|
41
|
+
if respond_to?(handler, true)
|
|
42
|
+
[name, send(handler, name, value)]
|
|
43
|
+
else
|
|
44
|
+
[name, [value.to_s]]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def unnormalize_attributes(attributes)
|
|
49
|
+
result = {}
|
|
50
|
+
attributes.each do |name, values|
|
|
51
|
+
unnormalize_attribute(name, values, result)
|
|
52
|
+
end
|
|
53
|
+
result
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def unnormalize_attribute(name, values, result={})
|
|
57
|
+
if values.empty?
|
|
58
|
+
result[name] = []
|
|
59
|
+
else
|
|
60
|
+
values.each do |value|
|
|
61
|
+
if value.is_a?(Hash)
|
|
62
|
+
suffix, real_value = extract_attribute_options(value)
|
|
63
|
+
new_name = name + suffix
|
|
64
|
+
result[new_name] ||= []
|
|
65
|
+
result[new_name].concat(real_value)
|
|
66
|
+
else
|
|
67
|
+
result[name] ||= []
|
|
68
|
+
result[name] << value.dup
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
def normalize_attribute_value_of_array(name, value)
|
|
77
|
+
attribute = schema.attribute(name)
|
|
78
|
+
if value.size > 1 and attribute.single_value?
|
|
79
|
+
format = _("Attribute %s can only have a single value")
|
|
80
|
+
message = format % self.class.human_attribute_name(attribute)
|
|
81
|
+
raise TypeError, message
|
|
82
|
+
end
|
|
83
|
+
if value.empty?
|
|
84
|
+
if schema.attribute(name).binary_required?
|
|
85
|
+
[{'binary' => value}]
|
|
86
|
+
else
|
|
87
|
+
value
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
value.collect do |entry|
|
|
91
|
+
normalize_attribute(name, entry)[1][0]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def normalize_attribute_value_of_hash(name, value)
|
|
97
|
+
if value.keys.size > 1
|
|
98
|
+
format = _("Hashes must have one key-value pair only: %s")
|
|
99
|
+
raise TypeError, format % value.inspect
|
|
100
|
+
end
|
|
101
|
+
unless value.keys[0].match(/^(lang-[a-z][a-z]*)|(binary)$/)
|
|
102
|
+
logger.warn do
|
|
103
|
+
format = _("unknown option did not match lang-* or binary: %s")
|
|
104
|
+
format % value.keys[0]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
# Contents MUST be a String or an Array
|
|
108
|
+
if !value.has_key?('binary') and schema.attribute(name).binary_required?
|
|
109
|
+
suffix, real_value = extract_attribute_options(value)
|
|
110
|
+
name, values =
|
|
111
|
+
normalize_attribute_options("#{name}#{suffix};binary", real_value)
|
|
112
|
+
values
|
|
113
|
+
else
|
|
114
|
+
[value]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def normalize_attribute_value_of_nil_class(name, value)
|
|
119
|
+
if schema.attribute(name).binary_required?
|
|
120
|
+
[{'binary' => []}]
|
|
121
|
+
else
|
|
122
|
+
[]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def normalize_attribute_value_of_string(name, value)
|
|
127
|
+
if schema.attribute(name).binary_required?
|
|
128
|
+
[{'binary' => [value]}]
|
|
129
|
+
else
|
|
130
|
+
[value]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def normalize_attribute_value_of_date(name, value)
|
|
135
|
+
new_value = sprintf('%.04d%.02d%.02d%.02d%.02d%.02d%s',
|
|
136
|
+
value.year, value.month, value.mday, 0, 0, 0,
|
|
137
|
+
'+0000')
|
|
138
|
+
normalize_attribute_value_of_string(name, new_value)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def normalize_attribute_value_of_time(name, value)
|
|
142
|
+
new_value = sprintf('%.04d%.02d%.02d%.02d%.02d%.02d%s',
|
|
143
|
+
0, 0, 0, value.hour, value.min, value.sec,
|
|
144
|
+
value.zone)
|
|
145
|
+
normalize_attribute_value_of_string(name, new_value)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def normalize_attribute_value_of_date_time(name, value)
|
|
149
|
+
new_value = sprintf('%.04d%.02d%.02d%.02d%.02d%.02d%s',
|
|
150
|
+
value.year, value.month, value.mday, value.hour,
|
|
151
|
+
value.min, value.sec, value.zone)
|
|
152
|
+
normalize_attribute_value_of_string(name, new_value)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# normalize_attribute_options
|
|
156
|
+
#
|
|
157
|
+
# Makes the Hashized value from the full attribute name
|
|
158
|
+
# e.g. userCertificate;binary => "some_bin"
|
|
159
|
+
# becomes userCertificate => {"binary" => "some_bin"}
|
|
160
|
+
def normalize_attribute_options(attr, value)
|
|
161
|
+
return [attr, value] unless attr.match(/;/)
|
|
162
|
+
|
|
163
|
+
ret_attr, *options = attr.split(/;/)
|
|
164
|
+
[ret_attr,
|
|
165
|
+
[options.reverse.inject(value) {|result, option| {option => result}}]]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# extract_attribute_options
|
|
169
|
+
#
|
|
170
|
+
# Extracts all of the subtypes from a given set of nested hashes
|
|
171
|
+
# and returns the attribute suffix and the final true value
|
|
172
|
+
def extract_attribute_options(value)
|
|
173
|
+
options = ''
|
|
174
|
+
ret_val = value
|
|
175
|
+
if value.class == Hash
|
|
176
|
+
options = ';' + value.keys[0]
|
|
177
|
+
ret_val = value[value.keys[0]]
|
|
178
|
+
if ret_val.class == Hash
|
|
179
|
+
sub_options, ret_val = extract_attribute_options(ret_val)
|
|
180
|
+
options += sub_options
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
ret_val = [ret_val] unless ret_val.class == Array
|
|
184
|
+
[options, ret_val]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
private
|
|
189
|
+
def remove_attributes_protected_from_mass_assignment(targets)
|
|
190
|
+
needless_attributes = {}
|
|
191
|
+
(attributes_protected_by_default +
|
|
192
|
+
(self.class.protected_attributes || [])).each do |name|
|
|
193
|
+
needless_attributes[to_real_attribute_name(name)] = true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
targets.collect do |key, value|
|
|
197
|
+
[to_real_attribute_name(key) || key, value]
|
|
198
|
+
end.reject do |key, value|
|
|
199
|
+
needless_attributes[key]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def attributes_protected_by_default
|
|
204
|
+
[dn_attribute, 'objectClass']
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def normalize_attribute_name(name)
|
|
208
|
+
self.class.normalize_attribute_name(name)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,1256 @@
|
|
|
1
|
+
# === activeldap - an OO-interface to LDAP objects inspired by ActiveRecord
|
|
2
|
+
# Author: Will Drewry <will@alum.bu.edu>
|
|
3
|
+
# License: See LICENSE and COPYING.txt
|
|
4
|
+
# Copyright 2004-2006 Will Drewry <will@alum.bu.edu>
|
|
5
|
+
# Some portions Copyright 2006 Google Inc
|
|
6
|
+
#
|
|
7
|
+
# == Summary
|
|
8
|
+
# ActiveLdap lets you read and update LDAP entries in a completely object
|
|
9
|
+
# oriented fashion, even handling attributes with multiple names seamlessly.
|
|
10
|
+
# It was inspired by ActiveRecord so extending it to deal with custom
|
|
11
|
+
# LDAP schemas is as effortless as knowing the 'ou' of the objects, and the
|
|
12
|
+
# primary key. (fix this up some)
|
|
13
|
+
#
|
|
14
|
+
# == Example
|
|
15
|
+
# irb> require 'active_ldap'
|
|
16
|
+
# > true
|
|
17
|
+
# irb> user = ActiveLdap::User.new("drewry")
|
|
18
|
+
# > #<ActiveLdap::User:0x402e...
|
|
19
|
+
# irb> user.cn
|
|
20
|
+
# > "foo"
|
|
21
|
+
# irb> user.common_name
|
|
22
|
+
# > "foo"
|
|
23
|
+
# irb> user.cn = "Will Drewry"
|
|
24
|
+
# > "Will Drewry"
|
|
25
|
+
# irb> user.cn
|
|
26
|
+
# > "Will Drewry"
|
|
27
|
+
# irb> user.save
|
|
28
|
+
#
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
require 'English'
|
|
32
|
+
require 'thread'
|
|
33
|
+
|
|
34
|
+
module ActiveLdap
|
|
35
|
+
# OO-interface to LDAP assuming pam/nss_ldap-style organization with
|
|
36
|
+
# Active specifics
|
|
37
|
+
# Each subclass does a ldapsearch for the matching entry.
|
|
38
|
+
# If no exact match, raise an error.
|
|
39
|
+
# If match, change all LDAP attributes in accessor attributes on the object.
|
|
40
|
+
# -- these are ACTUALLY populated from schema - see active_ldap/schema.rb
|
|
41
|
+
# example
|
|
42
|
+
# -- extract objectClasses from match and populate
|
|
43
|
+
# Multiple entries become lists.
|
|
44
|
+
# If this isn't read-only then lists become multiple entries, etc.
|
|
45
|
+
|
|
46
|
+
class Error < StandardError
|
|
47
|
+
include GetTextSupport
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# ConfigurationError
|
|
51
|
+
#
|
|
52
|
+
# An exception raised when there is a problem with Base.connect arguments
|
|
53
|
+
class ConfigurationError < Error
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# DeleteError
|
|
57
|
+
#
|
|
58
|
+
# An exception raised when an ActiveLdap delete action fails
|
|
59
|
+
class DeleteError < Error
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# SaveError
|
|
63
|
+
#
|
|
64
|
+
# An exception raised when an ActiveLdap save action failes
|
|
65
|
+
class SaveError < Error
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# AuthenticationError
|
|
69
|
+
#
|
|
70
|
+
# An exception raised when user authentication fails
|
|
71
|
+
class AuthenticationError < Error
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# ConnectionError
|
|
75
|
+
#
|
|
76
|
+
# An exception raised when the LDAP conenction fails
|
|
77
|
+
class ConnectionError < Error
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# ObjectClassError
|
|
81
|
+
#
|
|
82
|
+
# An exception raised when an objectClass is not defined in the schema
|
|
83
|
+
class ObjectClassError < Error
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# AttributeAssignmentError
|
|
87
|
+
#
|
|
88
|
+
# An exception raised when there is an issue assigning a value to
|
|
89
|
+
# an attribute
|
|
90
|
+
class AttributeAssignmentError < Error
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# TimeoutError
|
|
94
|
+
#
|
|
95
|
+
# An exception raised when a connection action fails due to a timeout
|
|
96
|
+
class TimeoutError < Error
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class EntryNotFound < Error
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class EntryAlreadyExist < Error
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class StrongAuthenticationRequired < Error
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class DistinguishedNameInvalid < Error
|
|
109
|
+
attr_reader :dn, :reason
|
|
110
|
+
def initialize(dn, reason=nil)
|
|
111
|
+
@dn = dn
|
|
112
|
+
@reason = reason
|
|
113
|
+
if @reason
|
|
114
|
+
message = _("%s is invalid distinguished name (DN): %s") % [@dn, @reason]
|
|
115
|
+
else
|
|
116
|
+
message = _("%s is invalid distinguished name (DN)") % @dn
|
|
117
|
+
end
|
|
118
|
+
super(message)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class DistinguishedNameNotSetError < Error
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class EntryNotSaved < Error
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class RequiredObjectClassMissed < Error
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class RequiredAttributeMissed < Error
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class EntryInvalid < Error
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class OperationNotPermitted < Error
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class ConnectionNotEstablished < Error
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class AdapterNotSpecified < Error
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
class AdapterNotFound < Error
|
|
147
|
+
attr_reader :adapter
|
|
148
|
+
def initialize(adapter)
|
|
149
|
+
@adapter = adapter
|
|
150
|
+
super(_("LDAP configuration specifies nonexistent %s adapter") % adapter)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class UnknownAttribute < Error
|
|
155
|
+
attr_reader :name
|
|
156
|
+
def initialize(name)
|
|
157
|
+
@name = name
|
|
158
|
+
super(_("%s is unknown attribute") % @name)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Base
|
|
163
|
+
#
|
|
164
|
+
# Base is the primary class which contains all of the core
|
|
165
|
+
# ActiveLdap functionality. It is meant to only ever be subclassed
|
|
166
|
+
# by extension classes.
|
|
167
|
+
class Base
|
|
168
|
+
include GetTextSupport
|
|
169
|
+
public :gettext
|
|
170
|
+
|
|
171
|
+
if Reloadable.const_defined?(:Deprecated)
|
|
172
|
+
include Reloadable::Deprecated
|
|
173
|
+
else
|
|
174
|
+
include Reloadable::Subclasses
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
VALID_LDAP_MAPPING_OPTIONS = [:dn_attribute, :prefix, :scope,
|
|
178
|
+
:classes, :recommended_classes]
|
|
179
|
+
|
|
180
|
+
cattr_accessor :logger
|
|
181
|
+
cattr_accessor :configurations
|
|
182
|
+
@@configurations = {}
|
|
183
|
+
|
|
184
|
+
def self.class_local_attr_accessor(search_ancestors, *syms)
|
|
185
|
+
syms.flatten.each do |sym|
|
|
186
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
|
187
|
+
def self.#{sym}(search_superclasses=#{search_ancestors})
|
|
188
|
+
@#{sym} ||= nil
|
|
189
|
+
return @#{sym} if @#{sym}
|
|
190
|
+
if search_superclasses
|
|
191
|
+
target = superclass
|
|
192
|
+
value = nil
|
|
193
|
+
loop do
|
|
194
|
+
break nil unless target.respond_to?(:#{sym})
|
|
195
|
+
value = target.#{sym}
|
|
196
|
+
break if value
|
|
197
|
+
target = target.superclass
|
|
198
|
+
end
|
|
199
|
+
value
|
|
200
|
+
else
|
|
201
|
+
nil
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
def #{sym}; self.class.#{sym}; end
|
|
205
|
+
def self.#{sym}=(value); @#{sym} = value; end
|
|
206
|
+
def #{sym}=(value); self.class.#{sym} = value; end
|
|
207
|
+
EOS
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
class_local_attr_accessor false, :prefix, :base, :dn_attribute
|
|
212
|
+
class_local_attr_accessor true, :scope
|
|
213
|
+
class_local_attr_accessor true, :required_classes, :recommended_classes
|
|
214
|
+
|
|
215
|
+
class << self
|
|
216
|
+
# Hide new in Base
|
|
217
|
+
private :new
|
|
218
|
+
|
|
219
|
+
# Connect and bind to LDAP creating a class variable for use by
|
|
220
|
+
# all ActiveLdap objects.
|
|
221
|
+
#
|
|
222
|
+
# == +config+
|
|
223
|
+
# +config+ must be a hash that may contain any of the following fields:
|
|
224
|
+
# :password_block, :logger, :host, :port, :base, :bind_dn,
|
|
225
|
+
# :try_sasl, :allow_anonymous
|
|
226
|
+
# :bind_dn specifies the DN to bind with.
|
|
227
|
+
# :password_block specifies a Proc object that will yield a String to
|
|
228
|
+
# be used as the password when called.
|
|
229
|
+
# :logger specifies a preconfigured Log4r::Logger to be used for all
|
|
230
|
+
# logging
|
|
231
|
+
# :host sets the LDAP server hostname
|
|
232
|
+
# :port sets the LDAP server port
|
|
233
|
+
# :base overwrites Base.base - this affects EVERYTHING
|
|
234
|
+
# :try_sasl indicates that a SASL bind should be attempted when binding
|
|
235
|
+
# to the server (default: false)
|
|
236
|
+
# :sasl_mechanisms is an array of SASL mechanism to try
|
|
237
|
+
# (default: ["GSSAPI", "CRAM-MD5", "EXTERNAL"])
|
|
238
|
+
# :allow_anonymous indicates that a true anonymous bind is allowed when
|
|
239
|
+
# trying to bind to the server (default: true)
|
|
240
|
+
# :retries - indicates the number of attempts to reconnect that will be
|
|
241
|
+
# undertaken when a stale connection occurs. -1 means infinite.
|
|
242
|
+
# :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection
|
|
243
|
+
# :method - whether to use :ssl, :tls, or :plain (unencrypted)
|
|
244
|
+
# :retry_wait - seconds to wait before retrying a connection
|
|
245
|
+
# :scope - dictates how to find objects. ONELEVEL by default to
|
|
246
|
+
# avoid dn_attr collisions across OUs. Think before changing.
|
|
247
|
+
# :timeout - time in seconds - defaults to disabled. This CAN interrupt
|
|
248
|
+
# search() requests. Be warned.
|
|
249
|
+
# :retry_on_timeout - whether to reconnect when timeouts occur. Defaults
|
|
250
|
+
# to true
|
|
251
|
+
# See lib/configuration.rb for defaults for each option
|
|
252
|
+
def establish_connection(config=nil)
|
|
253
|
+
super
|
|
254
|
+
ensure_logger
|
|
255
|
+
connection.connect
|
|
256
|
+
# Make irb users happy with a 'true'
|
|
257
|
+
true
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def create(attributes=nil, &block)
|
|
261
|
+
if attributes.is_a?(Array)
|
|
262
|
+
attributes.collect {|attrs| create(attrs, &block)}
|
|
263
|
+
else
|
|
264
|
+
object = new(attributes, &block)
|
|
265
|
+
object.save
|
|
266
|
+
object
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# This class function is used to setup all mappings between the subclass
|
|
271
|
+
# and ldap for use in activeldap
|
|
272
|
+
#
|
|
273
|
+
# Example:
|
|
274
|
+
# ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People',
|
|
275
|
+
# :classes => ['top', 'posixAccount'],
|
|
276
|
+
# :scope => :sub
|
|
277
|
+
def ldap_mapping(options={})
|
|
278
|
+
validate_ldap_mapping_options(options)
|
|
279
|
+
dn_attribute = options[:dn_attribute] || default_dn_attribute
|
|
280
|
+
prefix = options[:prefix] || default_prefix
|
|
281
|
+
classes = options[:classes]
|
|
282
|
+
recommended_classes = options[:recommended_classes]
|
|
283
|
+
scope = options[:scope]
|
|
284
|
+
|
|
285
|
+
self.dn_attribute = dn_attribute
|
|
286
|
+
self.prefix = prefix
|
|
287
|
+
self.scope = scope
|
|
288
|
+
self.required_classes = classes
|
|
289
|
+
self.recommended_classes = recommended_classes
|
|
290
|
+
|
|
291
|
+
public_class_method :new
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
alias_method :base_inheritable, :base
|
|
295
|
+
# Base.base
|
|
296
|
+
#
|
|
297
|
+
# This method when included into Base provides
|
|
298
|
+
# an inheritable, overwritable configuration setting
|
|
299
|
+
#
|
|
300
|
+
# This should be a string with the base of the
|
|
301
|
+
# ldap server such as 'dc=example,dc=com', and
|
|
302
|
+
# it should be overwritten by including
|
|
303
|
+
# configuration.rb into this class.
|
|
304
|
+
# When subclassing, the specified prefix will be concatenated.
|
|
305
|
+
def base
|
|
306
|
+
_base = base_inheritable
|
|
307
|
+
_base = configuration[:base] if _base.nil? and configuration
|
|
308
|
+
_base ||= base_inheritable(true)
|
|
309
|
+
[prefix, _base].find_all do |component|
|
|
310
|
+
component and !component.empty?
|
|
311
|
+
end.join(",")
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
alias_method :scope_without_validation=, :scope=
|
|
315
|
+
def scope=(scope)
|
|
316
|
+
validate_scope(scope)
|
|
317
|
+
self.scope_without_validation = scope
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def validate_scope(scope)
|
|
321
|
+
scope = scope.to_sym if scope.is_a?(String)
|
|
322
|
+
return if scope.nil? or scope.is_a?(Symbol)
|
|
323
|
+
raise ConfigurationError,
|
|
324
|
+
_("scope '%s' must be a Symbol") % scope.inspect
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def base_class
|
|
328
|
+
if self == Base or superclass == Base
|
|
329
|
+
self
|
|
330
|
+
else
|
|
331
|
+
superclass.base_class
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def human_attribute_name(attribute_or_name)
|
|
336
|
+
msgid = human_attribute_name_msgid(attribute_or_name)
|
|
337
|
+
msgid ||= human_attribute_name_with_gettext(attribute_or_name)
|
|
338
|
+
s_(msgid)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def human_attribute_name_msgid(attribute_or_name)
|
|
342
|
+
if attribute_or_name.is_a?(Schema::Attribute)
|
|
343
|
+
name = attribute_or_name.name
|
|
344
|
+
else
|
|
345
|
+
attribute = schema.attribute(attribute_or_name)
|
|
346
|
+
return nil if attribute.id.nil?
|
|
347
|
+
if attribute.name == attribute_or_name or
|
|
348
|
+
attribute.aliases.include?(attribute_or_name)
|
|
349
|
+
name = attribute_or_name
|
|
350
|
+
else
|
|
351
|
+
return nil
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
"LDAP|Attribute|#{name}"
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def human_attribute_description(attribute_or_name)
|
|
358
|
+
msgid = human_attribute_description_msgid(attribute_or_name)
|
|
359
|
+
return nil if msgid.nil?
|
|
360
|
+
s_(msgid)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def human_attribute_description_msgid(attribute_or_name)
|
|
364
|
+
if attribute_or_name.is_a?(Schema::Attribute)
|
|
365
|
+
attribute = attribute_or_name
|
|
366
|
+
else
|
|
367
|
+
attribute = schema.attribute(attribute_or_name)
|
|
368
|
+
return nil if attribute.nil?
|
|
369
|
+
end
|
|
370
|
+
description = attribute.description
|
|
371
|
+
return nil if description.nil?
|
|
372
|
+
"LDAP|Description|Attribute|#{attribute.name}|#{description}"
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def human_object_class_name(object_class_or_name)
|
|
376
|
+
s_(human_object_class_name_msgid(object_class_or_name))
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def human_object_class_name_msgid(object_class_or_name)
|
|
380
|
+
if object_class_or_name.is_a?(Schema::ObjectClass)
|
|
381
|
+
name = object_class_or_name.name
|
|
382
|
+
else
|
|
383
|
+
name = object_class_or_name
|
|
384
|
+
end
|
|
385
|
+
"LDAP|ObjectClass|#{name}"
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def human_object_class_description(object_class_or_name)
|
|
389
|
+
msgid = human_object_class_description_msgid(object_class_or_name)
|
|
390
|
+
return nil if msgid.nil?
|
|
391
|
+
s_(msgid)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def human_object_class_description_msgid(object_class_or_name)
|
|
395
|
+
if object_class_or_name.is_a?(Schema::ObjectClass)
|
|
396
|
+
object_class = object_class_or_name
|
|
397
|
+
else
|
|
398
|
+
object_class = schema.object_class(object_class_or_name)
|
|
399
|
+
return nil if object_class.nil?
|
|
400
|
+
end
|
|
401
|
+
description = object_class.description
|
|
402
|
+
return nil if description.nil?
|
|
403
|
+
"LDAP|Description|ObjectClass|#{object_class.name}|#{description}"
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
private
|
|
407
|
+
def validate_ldap_mapping_options(options)
|
|
408
|
+
options.assert_valid_keys(VALID_LDAP_MAPPING_OPTIONS)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def ensure_logger
|
|
412
|
+
@@logger ||= configuration[:logger]
|
|
413
|
+
# Setup default logger to console
|
|
414
|
+
if @@logger.nil?
|
|
415
|
+
require 'log4r'
|
|
416
|
+
@@logger = Log4r::Logger.new('activeldap')
|
|
417
|
+
@@logger.level = Log4r::OFF
|
|
418
|
+
Log4r::StderrOutputter.new 'console'
|
|
419
|
+
@@logger.add('console')
|
|
420
|
+
end
|
|
421
|
+
configuration[:logger] ||= @@logger
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def instantiate(args)
|
|
425
|
+
dn, attributes, options = args
|
|
426
|
+
options ||= {}
|
|
427
|
+
if self.class == Class
|
|
428
|
+
klass = self.ancestors[0].to_s.split(':').last
|
|
429
|
+
real_klass = self.ancestors[0]
|
|
430
|
+
else
|
|
431
|
+
klass = self.class.to_s.split(':').last
|
|
432
|
+
real_klass = self.class
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
obj = real_klass.allocate
|
|
436
|
+
conn = options[:connection] || connection
|
|
437
|
+
obj.connection = conn if conn != connection
|
|
438
|
+
obj.instance_eval do
|
|
439
|
+
initialize_by_ldap_data(dn, attributes)
|
|
440
|
+
end
|
|
441
|
+
obj
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def default_dn_attribute
|
|
445
|
+
if name.empty?
|
|
446
|
+
dn_attribute = nil
|
|
447
|
+
parent_class = ancestors[1]
|
|
448
|
+
if parent_class.respond_to?(:dn_attribute)
|
|
449
|
+
dn_attribute = parent_class.dn_attribute
|
|
450
|
+
end
|
|
451
|
+
dn_attribute || "cn"
|
|
452
|
+
else
|
|
453
|
+
Inflector.underscore(Inflector.demodulize(name))
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def default_prefix
|
|
458
|
+
if name.empty?
|
|
459
|
+
nil
|
|
460
|
+
else
|
|
461
|
+
"ou=#{Inflector.pluralize(Inflector.demodulize(name))}"
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
self.scope = :sub
|
|
467
|
+
self.required_classes = ['top']
|
|
468
|
+
self.recommended_classes = []
|
|
469
|
+
|
|
470
|
+
include Enumerable
|
|
471
|
+
|
|
472
|
+
### All instance methods, etc
|
|
473
|
+
|
|
474
|
+
# new
|
|
475
|
+
#
|
|
476
|
+
# Creates a new instance of Base initializing all class and all
|
|
477
|
+
# initialization. Defines local defaults. See examples If multiple values
|
|
478
|
+
# exist for dn_attribute, the first one put here will be authoritative
|
|
479
|
+
def initialize(attributes=nil)
|
|
480
|
+
init_base
|
|
481
|
+
@new_entry = true
|
|
482
|
+
initial_classes = required_classes | recommended_classes
|
|
483
|
+
if attributes.nil?
|
|
484
|
+
apply_object_class(initial_classes)
|
|
485
|
+
elsif attributes.is_a?(String) or attributes.is_a?(Array)
|
|
486
|
+
apply_object_class(initial_classes)
|
|
487
|
+
self.dn = attributes
|
|
488
|
+
elsif attributes.is_a?(Hash)
|
|
489
|
+
classes, attributes = extract_object_class(attributes)
|
|
490
|
+
apply_object_class(classes | initial_classes)
|
|
491
|
+
normalized_attributes = {}
|
|
492
|
+
attributes.each do |key, value|
|
|
493
|
+
real_key = to_real_attribute_name(key) || key
|
|
494
|
+
normalized_attributes[real_key] = value
|
|
495
|
+
end
|
|
496
|
+
self.dn = normalized_attributes[dn_attribute]
|
|
497
|
+
self.attributes = normalized_attributes
|
|
498
|
+
else
|
|
499
|
+
message = _("'%s' must be either nil, DN value as String or Array " \
|
|
500
|
+
"or attributes as Hash") % attributes.inspect
|
|
501
|
+
raise ArgumentError, message
|
|
502
|
+
end
|
|
503
|
+
yield self if block_given?
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Returns true if the +comparison_object+ is the same object, or is of
|
|
507
|
+
# the same type and has the same dn.
|
|
508
|
+
def ==(comparison_object)
|
|
509
|
+
comparison_object.equal?(self) or
|
|
510
|
+
(comparison_object.instance_of?(self.class) and
|
|
511
|
+
comparison_object.dn == dn and
|
|
512
|
+
!comparison_object.new_entry?)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Delegates to ==
|
|
516
|
+
def eql?(comparison_object)
|
|
517
|
+
self == (comparison_object)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# Delegates to id in order to allow two records of the same type and id
|
|
521
|
+
# to work with something like:
|
|
522
|
+
# [ User.find("a"), User.find("b"), User.find("c") ] &
|
|
523
|
+
# [ User.find("a"), User.find("d") ] # => [ User.find("a") ]
|
|
524
|
+
def hash
|
|
525
|
+
dn.hash
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def may
|
|
529
|
+
ensure_apply_object_class
|
|
530
|
+
@may
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def must
|
|
534
|
+
ensure_apply_object_class
|
|
535
|
+
@must
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# attributes
|
|
539
|
+
#
|
|
540
|
+
# Return attribute methods so that a program can determine available
|
|
541
|
+
# attributes dynamically without schema awareness
|
|
542
|
+
def attribute_names(normalize=false)
|
|
543
|
+
ensure_apply_object_class
|
|
544
|
+
names = @attr_methods.keys
|
|
545
|
+
if normalize
|
|
546
|
+
names.collect do |name|
|
|
547
|
+
to_real_attribute_name(name)
|
|
548
|
+
end.uniq
|
|
549
|
+
else
|
|
550
|
+
names
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def attribute_present?(name)
|
|
555
|
+
values = get_attribute(name, true)
|
|
556
|
+
!values.empty? or values.any? {|x| not (x and x.empty?)}
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# exist?
|
|
560
|
+
#
|
|
561
|
+
# Return whether the entry exists in LDAP or not
|
|
562
|
+
def exist?
|
|
563
|
+
self.class.exists?(dn)
|
|
564
|
+
end
|
|
565
|
+
alias_method(:exists?, :exist?)
|
|
566
|
+
|
|
567
|
+
# new_entry?
|
|
568
|
+
#
|
|
569
|
+
# Return whether the entry is new entry in LDAP or not
|
|
570
|
+
def new_entry?
|
|
571
|
+
@new_entry
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# dn
|
|
575
|
+
#
|
|
576
|
+
# Return the authoritative dn
|
|
577
|
+
def dn
|
|
578
|
+
dn_value = id
|
|
579
|
+
if dn_value.nil?
|
|
580
|
+
raise DistinguishedNameNotSetError.new,
|
|
581
|
+
_("%s's DN attribute (%s) isn't set") % [self, dn_attribute]
|
|
582
|
+
end
|
|
583
|
+
_base = base
|
|
584
|
+
_base = nil if _base.empty?
|
|
585
|
+
["#{dn_attribute}=#{dn_value}", _base].compact.join(",")
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def id
|
|
589
|
+
get_attribute(dn_attribute)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def to_param
|
|
593
|
+
id
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def dn=(value)
|
|
597
|
+
set_attribute(dn_attribute, value)
|
|
598
|
+
end
|
|
599
|
+
alias_method(:id=, :dn=)
|
|
600
|
+
|
|
601
|
+
alias_method(:dn_attribute_of_class, :dn_attribute)
|
|
602
|
+
def dn_attribute
|
|
603
|
+
@dn_attribute || dn_attribute_of_class
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# destroy
|
|
607
|
+
#
|
|
608
|
+
# Delete this entry from LDAP
|
|
609
|
+
def destroy
|
|
610
|
+
begin
|
|
611
|
+
self.class.delete(dn)
|
|
612
|
+
@new_entry = true
|
|
613
|
+
rescue Error
|
|
614
|
+
raise DeleteError.new(_("Failed to delete LDAP entry: %s") % dn)
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def delete(options={})
|
|
619
|
+
super(dn, options)
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# save
|
|
623
|
+
#
|
|
624
|
+
# Save and validate this object into LDAP
|
|
625
|
+
# either adding or replacing attributes
|
|
626
|
+
# TODO: Relative DN support
|
|
627
|
+
def save
|
|
628
|
+
create_or_update
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def save!
|
|
632
|
+
unless create_or_update
|
|
633
|
+
raise EntryNotSaved, _("entry %s can't be saved") % dn
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# method_missing
|
|
638
|
+
#
|
|
639
|
+
# If a given method matches an attribute or an attribute alias
|
|
640
|
+
# then call the appropriate method.
|
|
641
|
+
# TODO: Determine if it would be better to define each allowed method
|
|
642
|
+
# using class_eval instead of using method_missing. This would
|
|
643
|
+
# give tab completion in irb.
|
|
644
|
+
def method_missing(name, *args, &block)
|
|
645
|
+
ensure_apply_object_class
|
|
646
|
+
|
|
647
|
+
key = name.to_s
|
|
648
|
+
case key
|
|
649
|
+
when /=$/
|
|
650
|
+
real_key = $PREMATCH
|
|
651
|
+
if have_attribute?(real_key, ['objectClass'])
|
|
652
|
+
if args.size != 1
|
|
653
|
+
raise ArgumentError,
|
|
654
|
+
_("wrong number of arguments (%d for 1)") % args.size
|
|
655
|
+
end
|
|
656
|
+
return set_attribute(real_key, *args, &block)
|
|
657
|
+
end
|
|
658
|
+
when /(?:(_before_type_cast)|(\?))?$/
|
|
659
|
+
real_key = $PREMATCH
|
|
660
|
+
before_type_cast = !$1.nil?
|
|
661
|
+
query = !$2.nil?
|
|
662
|
+
if have_attribute?(real_key, ['objectClass'])
|
|
663
|
+
if args.size > 1
|
|
664
|
+
raise ArgumentError,
|
|
665
|
+
_("wrong number of arguments (%d for 1)") % args.size
|
|
666
|
+
end
|
|
667
|
+
if before_type_cast
|
|
668
|
+
return get_attribute_before_type_cast(real_key, *args)
|
|
669
|
+
elsif query
|
|
670
|
+
return get_attribute_as_query(real_key, *args)
|
|
671
|
+
else
|
|
672
|
+
return get_attribute(real_key, *args)
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
super
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Add available attributes to the methods
|
|
680
|
+
def methods(inherited_too=true)
|
|
681
|
+
ensure_apply_object_class
|
|
682
|
+
target_names = @attr_methods.keys + @attr_aliases.keys
|
|
683
|
+
target_names -= ['objectClass', Inflector.underscore('objectClass')]
|
|
684
|
+
super + target_names.uniq.collect do |x|
|
|
685
|
+
[x, "#{x}=", "#{x}?", "#{x}_before_type_cast"]
|
|
686
|
+
end.flatten
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
alias_method :respond_to_without_attributes?, :respond_to?
|
|
690
|
+
def respond_to?(name, include_priv=false)
|
|
691
|
+
have_attribute?(name.to_s) or
|
|
692
|
+
(/(?:=|\?|_before_type_cast)$/ =~ name.to_s and
|
|
693
|
+
have_attribute?($PREMATCH)) or
|
|
694
|
+
super
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# Updates a given attribute and saves immediately
|
|
698
|
+
def update_attribute(name, value)
|
|
699
|
+
send("#{name}=", value)
|
|
700
|
+
save
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# This performs a bulk update of attributes and immediately
|
|
704
|
+
# calls #save.
|
|
705
|
+
def update_attributes(attrs)
|
|
706
|
+
self.attributes = attrs
|
|
707
|
+
save
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
def update_attributes!(attrs)
|
|
711
|
+
self.attributes = attrs
|
|
712
|
+
save!
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
# This returns the key value pairs in @data with all values
|
|
716
|
+
# cloned
|
|
717
|
+
def attributes
|
|
718
|
+
Marshal.load(Marshal.dump(@data))
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# This allows a bulk update to the attributes of a record
|
|
722
|
+
# without forcing an immediate save or validation.
|
|
723
|
+
#
|
|
724
|
+
# It is unwise to attempt objectClass updates this way.
|
|
725
|
+
# Also be sure to only pass in key-value pairs of your choosing.
|
|
726
|
+
# Do not let URL/form hackers supply the keys.
|
|
727
|
+
def attributes=(hash_or_assoc)
|
|
728
|
+
_schema = nil
|
|
729
|
+
targets = remove_attributes_protected_from_mass_assignment(hash_or_assoc)
|
|
730
|
+
targets.each do |key, value|
|
|
731
|
+
setter = "#{key}="
|
|
732
|
+
unless respond_to?(setter)
|
|
733
|
+
_schema ||= schema
|
|
734
|
+
attribute = _schema.attribute(key)
|
|
735
|
+
next if attribute.id.nil?
|
|
736
|
+
define_attribute_methods(attribute)
|
|
737
|
+
end
|
|
738
|
+
send(setter, value)
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def to_ldif
|
|
743
|
+
super(dn, normalize_data(@data))
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def to_xml(options={})
|
|
747
|
+
root = options[:root] || Inflector.underscore(self.class.name)
|
|
748
|
+
result = "<#{root}>\n"
|
|
749
|
+
result << " <dn>#{dn}</dn>\n"
|
|
750
|
+
normalize_data(@data).sort_by {|key, values| key}.each do |key, values|
|
|
751
|
+
targets = []
|
|
752
|
+
values.each do |value|
|
|
753
|
+
if value.is_a?(Hash)
|
|
754
|
+
value.each do |option, real_value|
|
|
755
|
+
targets << [real_value, " #{option}=\"true\""]
|
|
756
|
+
end
|
|
757
|
+
else
|
|
758
|
+
targets << [value]
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
targets.sort_by {|value, attr| value}.each do |value, attr|
|
|
762
|
+
result << " <#{key}#{attr}>#{value}</#{key}>\n"
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
result << "</#{root}>\n"
|
|
766
|
+
result
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def have_attribute?(name, except=[])
|
|
770
|
+
real_name = to_real_attribute_name(name)
|
|
771
|
+
real_name and !except.include?(real_name)
|
|
772
|
+
end
|
|
773
|
+
alias_method :has_attribute?, :have_attribute?
|
|
774
|
+
|
|
775
|
+
def reload
|
|
776
|
+
clear_association_cache
|
|
777
|
+
_, attributes = search(:value => id).find do |_dn, _attributes|
|
|
778
|
+
dn == _dn
|
|
779
|
+
end
|
|
780
|
+
if attributes.nil?
|
|
781
|
+
raise EntryNotFound, _("Can't find DN '%s' to reload") % dn
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
@ldap_data.update(attributes)
|
|
785
|
+
classes, attributes = extract_object_class(attributes)
|
|
786
|
+
apply_object_class(classes)
|
|
787
|
+
self.attributes = attributes
|
|
788
|
+
@new_entry = false
|
|
789
|
+
self
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
def [](name, force_array=false)
|
|
793
|
+
if name == "dn"
|
|
794
|
+
array_of(dn, force_array)
|
|
795
|
+
else
|
|
796
|
+
get_attribute(name, force_array)
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def []=(name, value)
|
|
801
|
+
set_attribute(name, value)
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def each
|
|
805
|
+
@data.each do |key, values|
|
|
806
|
+
yield(key.dup, values.dup)
|
|
807
|
+
end
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
def establish_connection(config={})
|
|
811
|
+
if config.is_a?(Hash)
|
|
812
|
+
config = {:bind_dn => dn, :allow_anonymous => false}.merge(config)
|
|
813
|
+
end
|
|
814
|
+
super(config)
|
|
815
|
+
before_connection = @connection
|
|
816
|
+
begin
|
|
817
|
+
@connection = nil
|
|
818
|
+
connection.connect
|
|
819
|
+
@connection = connection
|
|
820
|
+
@schema = nil
|
|
821
|
+
clear_association_cache
|
|
822
|
+
rescue ActiveLdap::Error
|
|
823
|
+
remove_connection
|
|
824
|
+
@connection = before_connection
|
|
825
|
+
raise
|
|
826
|
+
end
|
|
827
|
+
true
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def schema
|
|
831
|
+
@schema ||= super
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
def inspect
|
|
835
|
+
schema, @schema = @schema, nil
|
|
836
|
+
must, may = @must, @may
|
|
837
|
+
object_classes = @object_classes
|
|
838
|
+
@must, @may = @must.collect(&:name), @may.collect(&:name)
|
|
839
|
+
@object_classes = @object_classes.collect(&:name)
|
|
840
|
+
super
|
|
841
|
+
ensure
|
|
842
|
+
@schema = schema
|
|
843
|
+
@must = must
|
|
844
|
+
@may = may
|
|
845
|
+
@object_classes = object_classes
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
private
|
|
849
|
+
def extract_object_class(attributes)
|
|
850
|
+
classes = []
|
|
851
|
+
attrs = attributes.stringify_keys.reject do |key, value|
|
|
852
|
+
if key == 'objectClass' or
|
|
853
|
+
key.underscore == 'object_class' or
|
|
854
|
+
key.downcase == 'objectclass'
|
|
855
|
+
classes |= [value].flatten
|
|
856
|
+
true
|
|
857
|
+
else
|
|
858
|
+
false
|
|
859
|
+
end
|
|
860
|
+
end
|
|
861
|
+
[classes, attrs]
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
def init_base
|
|
865
|
+
check_configuration
|
|
866
|
+
init_instance_variables
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
def initialize_by_ldap_data(dn, attributes)
|
|
870
|
+
init_base
|
|
871
|
+
@new_entry = false
|
|
872
|
+
@ldap_data = attributes
|
|
873
|
+
classes, attributes = extract_object_class(attributes)
|
|
874
|
+
apply_object_class(classes)
|
|
875
|
+
self.dn = dn
|
|
876
|
+
self.attributes = attributes
|
|
877
|
+
yield self if block_given?
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
def instantiate(args)
|
|
881
|
+
dn, attributes, options = args
|
|
882
|
+
options ||= {}
|
|
883
|
+
|
|
884
|
+
obj = self.class.allocate
|
|
885
|
+
obj.connection = options[:connection] || @connection
|
|
886
|
+
obj.instance_eval do
|
|
887
|
+
initialize_by_ldap_data(dn, attributes)
|
|
888
|
+
end
|
|
889
|
+
obj
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
def to_real_attribute_name(name, allow_normalized_name=false)
|
|
893
|
+
ensure_apply_object_class
|
|
894
|
+
name = name.to_s
|
|
895
|
+
real_name = @attr_methods[name]
|
|
896
|
+
real_name ||= @attr_aliases[Inflector.underscore(name)]
|
|
897
|
+
if real_name
|
|
898
|
+
real_name
|
|
899
|
+
elsif allow_normalized_name
|
|
900
|
+
@attr_methods[@normalized_attr_names[normalize_attribute_name(name)]]
|
|
901
|
+
else
|
|
902
|
+
nil
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
def ensure_apply_object_class
|
|
907
|
+
current_object_class = @data['objectClass']
|
|
908
|
+
return if current_object_class.nil? or current_object_class == @last_oc
|
|
909
|
+
apply_object_class(current_object_class)
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
# enforce_type
|
|
913
|
+
#
|
|
914
|
+
# enforce_type applies your changes without attempting to write to LDAP.
|
|
915
|
+
# This means that if you set userCertificate to somebinary value, it will
|
|
916
|
+
# wrap it up correctly.
|
|
917
|
+
def enforce_type(key, value)
|
|
918
|
+
ensure_apply_object_class
|
|
919
|
+
# Enforce attribute value formatting
|
|
920
|
+
normalize_attribute(key, value)[1]
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
def init_instance_variables
|
|
924
|
+
@mutex = Mutex.new
|
|
925
|
+
@data = {} # where the r/w entry data is stored
|
|
926
|
+
@ldap_data = {} # original ldap entry data
|
|
927
|
+
@attr_methods = {} # list of valid method calls for attributes used for
|
|
928
|
+
# dereferencing
|
|
929
|
+
@normalized_attr_names = {} # list of normalized attribute name
|
|
930
|
+
@attr_aliases = {} # aliases of @attr_methods
|
|
931
|
+
@last_oc = false # for use in other methods for "caching"
|
|
932
|
+
@dn_attribute = nil
|
|
933
|
+
@base = nil
|
|
934
|
+
@scope = nil
|
|
935
|
+
@connection ||= nil
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
# apply_object_class
|
|
939
|
+
#
|
|
940
|
+
# objectClass= special case for updating appropriately
|
|
941
|
+
# This updates the objectClass entry in @data. It also
|
|
942
|
+
# updating all required and allowed attributes while
|
|
943
|
+
# removing defined attributes that are no longer valid
|
|
944
|
+
# given the new objectclasses.
|
|
945
|
+
def apply_object_class(val)
|
|
946
|
+
new_oc = val
|
|
947
|
+
new_oc = [val] if new_oc.class != Array
|
|
948
|
+
new_oc = new_oc.uniq
|
|
949
|
+
return new_oc if @last_oc == new_oc
|
|
950
|
+
|
|
951
|
+
# Store for caching purposes
|
|
952
|
+
@last_oc = new_oc.dup
|
|
953
|
+
|
|
954
|
+
# Set the actual objectClass data
|
|
955
|
+
define_attribute_methods(schema.attribute('objectClass'))
|
|
956
|
+
replace_class(*new_oc)
|
|
957
|
+
|
|
958
|
+
# Build |data| from schema
|
|
959
|
+
# clear attr_method mapping first
|
|
960
|
+
@attr_methods = {}
|
|
961
|
+
@normalized_attr_names = {}
|
|
962
|
+
@attr_aliases = {}
|
|
963
|
+
@must = []
|
|
964
|
+
@may = []
|
|
965
|
+
@object_classes = []
|
|
966
|
+
new_oc.each do |objc|
|
|
967
|
+
# get all attributes for the class
|
|
968
|
+
object_class = schema.object_class(objc)
|
|
969
|
+
@object_classes << object_class
|
|
970
|
+
@must.concat(object_class.must)
|
|
971
|
+
@may.concat(object_class.may)
|
|
972
|
+
end
|
|
973
|
+
@must.uniq!
|
|
974
|
+
@may.uniq!
|
|
975
|
+
(@must + @may).each do |attr|
|
|
976
|
+
# Update attr_method with appropriate
|
|
977
|
+
define_attribute_methods(attr)
|
|
978
|
+
end
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
alias_method :base_of_class, :base
|
|
982
|
+
def base
|
|
983
|
+
[@base, base_of_class].compact.join(",")
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
undef_method :base=
|
|
987
|
+
def base=(object_local_base)
|
|
988
|
+
@base = object_local_base
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
alias_method :scope_of_class, :scope
|
|
992
|
+
def scope
|
|
993
|
+
@scope || scope_of_class
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
undef_method :scope=
|
|
997
|
+
def scope=(scope)
|
|
998
|
+
self.class.validate_scope(scope)
|
|
999
|
+
@scope = scope
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
# get_attribute
|
|
1003
|
+
#
|
|
1004
|
+
# Return the value of the attribute called by method_missing?
|
|
1005
|
+
def get_attribute(name, force_array=false)
|
|
1006
|
+
get_attribute_before_type_cast(name, force_array)
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
def get_attribute_as_query(name, force_array=false)
|
|
1010
|
+
value = get_attribute_before_type_cast(name, force_array)
|
|
1011
|
+
if force_array
|
|
1012
|
+
value.collect {|x| !false_value?(x)}
|
|
1013
|
+
else
|
|
1014
|
+
!false_value?(value)
|
|
1015
|
+
end
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
def false_value?(value)
|
|
1019
|
+
value.nil? or value == false or value == [] or
|
|
1020
|
+
value == "false" or value == "FALSE" or value == ""
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
def get_attribute_before_type_cast(name, force_array=false)
|
|
1024
|
+
attr = to_real_attribute_name(name)
|
|
1025
|
+
|
|
1026
|
+
value = @data[attr] || []
|
|
1027
|
+
# Return a copy of the stored data
|
|
1028
|
+
if force_array
|
|
1029
|
+
value.dup
|
|
1030
|
+
else
|
|
1031
|
+
array_of(value.dup, false)
|
|
1032
|
+
end
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
# set_attribute
|
|
1036
|
+
#
|
|
1037
|
+
# Set the value of the attribute called by method_missing?
|
|
1038
|
+
def set_attribute(name, value)
|
|
1039
|
+
# Get the attr and clean up the input
|
|
1040
|
+
attr = to_real_attribute_name(name)
|
|
1041
|
+
raise UnknownAttribute.new(name) if attr.nil?
|
|
1042
|
+
|
|
1043
|
+
if attr == dn_attribute and value.is_a?(String)
|
|
1044
|
+
new_dn_attribute, value, @base = split_dn_value(value)
|
|
1045
|
+
new_dn_attribute = to_real_attribute_name(new_dn_attribute)
|
|
1046
|
+
if dn_attribute != new_dn_attribute
|
|
1047
|
+
@dn_attribute = attr = new_dn_attribute
|
|
1048
|
+
end
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
# Enforce LDAP-pleasing values
|
|
1052
|
+
real_value = value
|
|
1053
|
+
# Squash empty values
|
|
1054
|
+
if value.class == Array
|
|
1055
|
+
real_value = value.collect {|c| (c.nil? or c.empty?) ? [] : c}.flatten
|
|
1056
|
+
end
|
|
1057
|
+
real_value = [] if real_value.nil?
|
|
1058
|
+
real_value = [] if real_value == ''
|
|
1059
|
+
real_value = [real_value] if real_value.class == String
|
|
1060
|
+
real_value = [real_value.to_s] if real_value.class == Fixnum
|
|
1061
|
+
# NOTE: Hashes are allowed for subtyping.
|
|
1062
|
+
|
|
1063
|
+
# Assign the value
|
|
1064
|
+
@data[attr] = enforce_type(attr, real_value)
|
|
1065
|
+
|
|
1066
|
+
# Return the passed in value
|
|
1067
|
+
@data[attr]
|
|
1068
|
+
end
|
|
1069
|
+
|
|
1070
|
+
def split_dn_value(value)
|
|
1071
|
+
dn_value = relative_dn_value = nil
|
|
1072
|
+
begin
|
|
1073
|
+
dn_value = DN.parse(value)
|
|
1074
|
+
rescue DistinguishedNameInvalid
|
|
1075
|
+
dn_value = DN.parse("#{dn_attribute}=#{value}")
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
begin
|
|
1079
|
+
relative_dn_value = dn_value - DN.parse(base_of_class)
|
|
1080
|
+
relative_dn_value = dn_value if relative_dn_value.rdns.empty?
|
|
1081
|
+
rescue ArgumentError
|
|
1082
|
+
relative_dn_value = dn_value
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
val, *bases = relative_dn_value.rdns
|
|
1086
|
+
dn_attribute_name, dn_attribute_value = val.to_a[0]
|
|
1087
|
+
[dn_attribute_name, dn_attribute_value,
|
|
1088
|
+
bases.empty? ? nil : DN.new(*bases).to_s]
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
# define_attribute_methods
|
|
1092
|
+
#
|
|
1093
|
+
# Make a method entry for _every_ alias of a valid attribute and map it
|
|
1094
|
+
# onto the first attribute passed in.
|
|
1095
|
+
def define_attribute_methods(attr)
|
|
1096
|
+
name = attr.name
|
|
1097
|
+
return if @attr_methods.has_key?(name)
|
|
1098
|
+
([name] + attr.aliases).each do |ali|
|
|
1099
|
+
@attr_methods[ali] = name
|
|
1100
|
+
@attr_aliases[Inflector.underscore(ali)] = name
|
|
1101
|
+
@normalized_attr_names[normalize_attribute_name(ali)] = name
|
|
1102
|
+
end
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
# array_of
|
|
1106
|
+
#
|
|
1107
|
+
# Returns the array form of a value, or not an array if
|
|
1108
|
+
# false is passed in.
|
|
1109
|
+
def array_of(value, to_a=true)
|
|
1110
|
+
case value
|
|
1111
|
+
when Array
|
|
1112
|
+
if to_a or value.size > 1
|
|
1113
|
+
value.collect {|v| array_of(v, to_a)}
|
|
1114
|
+
else
|
|
1115
|
+
if value.empty?
|
|
1116
|
+
nil
|
|
1117
|
+
else
|
|
1118
|
+
array_of(value.first, to_a)
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
when Hash
|
|
1122
|
+
if to_a
|
|
1123
|
+
[value]
|
|
1124
|
+
else
|
|
1125
|
+
result = {}
|
|
1126
|
+
value.each {|k, v| result[k] = array_of(v, to_a)}
|
|
1127
|
+
result
|
|
1128
|
+
end
|
|
1129
|
+
else
|
|
1130
|
+
to_a ? [value.to_s] : value.to_s
|
|
1131
|
+
end
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
def normalize_data(data, except=[])
|
|
1135
|
+
_schema = schema
|
|
1136
|
+
result = {}
|
|
1137
|
+
data.each do |key, values|
|
|
1138
|
+
next if except.include?(key)
|
|
1139
|
+
real_name = to_real_attribute_name(key)
|
|
1140
|
+
next if real_name and except.include?(real_name)
|
|
1141
|
+
real_name ||= key
|
|
1142
|
+
next if _schema.attribute(real_name).id.nil?
|
|
1143
|
+
result[real_name] ||= []
|
|
1144
|
+
result[real_name].concat(values)
|
|
1145
|
+
end
|
|
1146
|
+
result
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
def collect_modified_attributes(ldap_data, data)
|
|
1150
|
+
attributes = []
|
|
1151
|
+
# Now that all the options will be treated as unique attributes
|
|
1152
|
+
# we can see what's changed and add anything that is brand-spankin'
|
|
1153
|
+
# new.
|
|
1154
|
+
ldap_data.each do |k, v|
|
|
1155
|
+
value = data[k] || []
|
|
1156
|
+
|
|
1157
|
+
next if v == value
|
|
1158
|
+
|
|
1159
|
+
# Create mod entries
|
|
1160
|
+
if value.empty?
|
|
1161
|
+
# Since some types do not have equality matching rules,
|
|
1162
|
+
# delete doesn't work
|
|
1163
|
+
# Replacing with nothing is equivalent.
|
|
1164
|
+
if !data.has_key?(k) and schema.attribute(k).binary_required?
|
|
1165
|
+
value = [{'binary' => []}]
|
|
1166
|
+
end
|
|
1167
|
+
else
|
|
1168
|
+
# Ditched delete then replace because attribs with no equality
|
|
1169
|
+
# match rules will fails
|
|
1170
|
+
end
|
|
1171
|
+
attributes.push([:replace, k, value])
|
|
1172
|
+
end
|
|
1173
|
+
data.each do |k, v|
|
|
1174
|
+
value = v || []
|
|
1175
|
+
next if ldap_data.has_key?(k) or value.empty?
|
|
1176
|
+
|
|
1177
|
+
# Detect subtypes and account for them
|
|
1178
|
+
# REPLACE will function like ADD, but doesn't hit EQUALITY problems
|
|
1179
|
+
# TODO: Added equality(attr) to Schema
|
|
1180
|
+
attributes.push([:replace, k, value])
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
attributes
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
def collect_all_attributes(data)
|
|
1187
|
+
dn_attr = to_real_attribute_name(dn_attribute)
|
|
1188
|
+
dn_value = data[dn_attr]
|
|
1189
|
+
|
|
1190
|
+
attributes = []
|
|
1191
|
+
attributes.push([:add, dn_attr, dn_value])
|
|
1192
|
+
|
|
1193
|
+
oc_value = data['objectClass']
|
|
1194
|
+
attributes.push([:add, 'objectClass', oc_value])
|
|
1195
|
+
data.each do |key, value|
|
|
1196
|
+
next if value.empty? or key == 'objectClass' or key == dn_attr
|
|
1197
|
+
|
|
1198
|
+
attributes.push([:add, key, value])
|
|
1199
|
+
end
|
|
1200
|
+
|
|
1201
|
+
attributes
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
def check_configuration
|
|
1205
|
+
unless dn_attribute
|
|
1206
|
+
raise ConfigurationError,
|
|
1207
|
+
_("dn_attribute isn't set for this class: %s") % self.class
|
|
1208
|
+
end
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
def create_or_update
|
|
1212
|
+
new_entry? ? create : update
|
|
1213
|
+
end
|
|
1214
|
+
|
|
1215
|
+
def prepare_data_for_saving
|
|
1216
|
+
# Expand subtypes to real ldap_data attributes
|
|
1217
|
+
# We can't reuse @ldap_data because an exception would leave
|
|
1218
|
+
# an object in an unknown state
|
|
1219
|
+
ldap_data = normalize_data(@ldap_data)
|
|
1220
|
+
|
|
1221
|
+
# Expand subtypes to real data attributes, but leave @data alone
|
|
1222
|
+
bad_attrs = @data.keys - attribute_names
|
|
1223
|
+
data = normalize_data(@data, bad_attrs)
|
|
1224
|
+
|
|
1225
|
+
success = yield(data, ldap_data)
|
|
1226
|
+
|
|
1227
|
+
if success
|
|
1228
|
+
@ldap_data = Marshal.load(Marshal.dump(data))
|
|
1229
|
+
# Delete items disallowed by objectclasses.
|
|
1230
|
+
# They should have been removed from ldap.
|
|
1231
|
+
bad_attrs.each do |remove_me|
|
|
1232
|
+
@ldap_data.delete(remove_me)
|
|
1233
|
+
end
|
|
1234
|
+
end
|
|
1235
|
+
|
|
1236
|
+
success
|
|
1237
|
+
end
|
|
1238
|
+
|
|
1239
|
+
def create
|
|
1240
|
+
prepare_data_for_saving do |data, ldap_data|
|
|
1241
|
+
attributes = collect_all_attributes(data)
|
|
1242
|
+
add_entry(dn, attributes)
|
|
1243
|
+
@new_entry = false
|
|
1244
|
+
true
|
|
1245
|
+
end
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
def update
|
|
1249
|
+
prepare_data_for_saving do |data, ldap_data|
|
|
1250
|
+
attributes = collect_modified_attributes(ldap_data, data)
|
|
1251
|
+
modify_entry(dn, attributes)
|
|
1252
|
+
true
|
|
1253
|
+
end
|
|
1254
|
+
end
|
|
1255
|
+
end # Base
|
|
1256
|
+
end # ActiveLdap
|