datacom_active_directory 1.5.5.datacom

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 886034b6e2fad40d5739b27a5076cc7212c73c3a
4
+ data.tar.gz: cbcdda9012834a738fb4589a43ecdc33d3dda089
5
+ SHA512:
6
+ metadata.gz: 3f6bea786c086e7b9de8dc9e8fd7e4aa240c7c6c781b752c38b19e22b8a5799fc75eac9681e3a8e71fc1ad2e6808e52bcefbc314bc6888b9669965a6bc6f03cf
7
+ data.tar.gz: 68cd96583ea6aadb305e0ff161666de0ba00ac471112b18bd5e1cee13f704a0943c2308dfd60f6419bb15b3d3b3d0502f3b59fdd94c413284b7348b7b083be04
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ html
2
+ pkg
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ **Forked on Datacom account in order to tag it to a fixed point - Brad Murray**
2
+
3
+ = Active Directory
4
+
5
+ Ruby Integration with Microsoft's Active Directory system based on original code by Justin Mecham and James Hunt at http://rubyforge.org/projects/activedirectory
6
+
7
+ See documentation on ActiveDirectory::Base for more information.
8
+
9
+ Caching:
10
+ Queries for membership and group membership are based on the distinguished name of objects. Doing a lot of queries, especially for a Rails app, is a sizable slowdown. To alleviate the problem, I've implemented a very basic cache for queries which search by :distinguishedname. This is disabled by default. All other queries are unaffected.
11
+
12
+
13
+ A code example is worth a thousand words:
14
+
15
+ <pre>
16
+ require 'rubygems'
17
+ require 'active_directory'
18
+
19
+ # Uses the same settings as net/ldap
20
+ settings = {
21
+ :host => 'domain-controller.example.local',
22
+ :base => 'dc=example,dc=local',
23
+ :port => 636,
24
+ :encryption => :simple_tls,
25
+ :auth => {
26
+ :method => :simple,
27
+ :username => "username",
28
+ :password => "password"
29
+ }
30
+ }
31
+
32
+ # Basic usage
33
+ ActiveDirectory::Base.setup(settings)
34
+
35
+ ActiveDirectory::User.find(:all)
36
+ ActiveDirectory::User.find(:first, :userprincipalname => "john.smith@domain.com")
37
+
38
+ ActiveDirectory::Group.find(:all)
39
+
40
+ #Caching is disabled by default, to enable:
41
+ ActiveDirectory::Base.enable_cache
42
+ ActiveDirectory::Base.disable_cache
43
+ ActiveDirectory::Base.cache?
44
+
45
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'psych'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = "active_directory"
7
+ gem.summary = "An interface library for accessing Microsoft's Active Directory."
8
+ gem.description = "ActiveDirectory uses Net::LDAP to provide a means of accessing and modifying an Active Directory data store. This is a fork of the activedirectory gem."
9
+ gem.author = "Adam T Kerr"
10
+ gem.email = "ajrkerr@gmail.com"
11
+ gem.homepage = "http://github.com/ajrkerr/active_directory"
12
+
13
+ # gem.files = FileList["lib/**/*.rb"]
14
+ # gem.require_path = "lib"
15
+ gem.add_dependency 'net-ldap', '>= 0.1.1'
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_directory/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'datacom_active_directory'
8
+ spec.version = ActiveDirectory::VERSION
9
+ spec.authors = ["Brad Murray"]
10
+ spec.email = ["wyaeld@gmail.com"]
11
+ spec.description = %q{ Datacom NZ fork of ActiveDirectory. Uses Net::LDAP to provide a means of accessing and modifying an Active Directory data store. This is a fork of the activedirectory gem.}
12
+ spec.summary = %q{An interface library for accessing Microsoft's Active Directory.}
13
+ spec.homepage = %q{http://github.com/datacom/active_directory}
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "datacom-net-ldap", '0.5.0.datacom'
21
+ end
22
+
@@ -0,0 +1,95 @@
1
+ #-- license
2
+ #
3
+ # Based on original code by Justin Mecham and James Hunt
4
+ # at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ #++ license
20
+
21
+ require 'net/ldap'
22
+
23
+ require 'active_directory/base.rb'
24
+ require 'active_directory/container.rb'
25
+ require 'active_directory/member.rb'
26
+
27
+ require 'active_directory/user.rb'
28
+ require 'active_directory/group.rb'
29
+ require 'active_directory/computer.rb'
30
+
31
+ require 'active_directory/field_type/password.rb'
32
+ require 'active_directory/field_type/binary.rb'
33
+ require 'active_directory/field_type/date.rb'
34
+ require 'active_directory/field_type/timestamp.rb'
35
+ require 'active_directory/field_type/dn_array.rb'
36
+ require 'active_directory/field_type/user_dn_array.rb'
37
+ require 'active_directory/field_type/group_dn_array.rb'
38
+ require 'active_directory/field_type/member_dn_array.rb'
39
+
40
+ module ActiveDirectory
41
+
42
+ #Special Fields
43
+ def self.special_fields
44
+ @@special_fields
45
+ end
46
+
47
+ def self.special_fields= sp_fields
48
+ @@special_fields = sp_fields
49
+ end
50
+
51
+ @@special_fields = {
52
+
53
+ #All objects in the AD
54
+ :Base => {
55
+ :objectguid => :Binary,
56
+ :whencreated => :Date,
57
+ :whenchanged => :Date,
58
+ :memberof => :DnArray,
59
+ },
60
+
61
+ #User objects
62
+ :User => {
63
+ :objectguid => :Binary,
64
+ :whencreated => :Date,
65
+ :whenchanged => :Date,
66
+ :objectsid => :Binary,
67
+ :msexchmailboxguid => :Binary,
68
+ :msexchmailboxsecuritydescriptor => :Binary,
69
+ :lastlogontimestamp => :Timestamp,
70
+ :pwdlastset => :Timestamp,
71
+ :accountexpires => :Timestamp,
72
+ :memberof => :MemberDnArray,
73
+ },
74
+
75
+ #Group objects
76
+ :Group => {
77
+ :objectguid => :Binary,
78
+ :whencreated => :Date,
79
+ :whenchanged => :Date,
80
+ :objectsid => :Binary,
81
+ :memberof => :GroupDnArray,
82
+ :member => :MemberDnArray,
83
+ },
84
+
85
+ #Computer objects
86
+ :Computer => {
87
+ :objectguid => :Binary,
88
+ :whencreated => :Date,
89
+ :whenchanged => :Date,
90
+ :objectsid => :Binary,
91
+ :memberof => :GroupDnArray,
92
+ :member => :MemberDnArray,
93
+ },
94
+ }
95
+ end
@@ -0,0 +1,587 @@
1
+ #-- license
2
+ #
3
+ # Based on original code by Justin Mecham and James Hunt
4
+ # at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ #++ license
20
+
21
+ module ActiveDirectory
22
+ #
23
+ # Base class for all Ruby/ActiveDirectory Entry Objects (like User and Group)
24
+ #
25
+ class Base
26
+ #
27
+ # A Net::LDAP::Filter object that doesn't do any filtering
28
+ # (outside of check that the CN attribute is present. This
29
+ # is used internally for specifying a 'no filter' condition
30
+ # for methods that require a filter object.
31
+ #
32
+ NIL_FILTER = Net::LDAP::Filter.pres('cn')
33
+
34
+ @@ldap = nil
35
+ @@ldap_connected = false
36
+ @@caching = false
37
+ @@cache = {}
38
+
39
+ #
40
+ # Configures the connection for the Ruby/ActiveDirectory library.
41
+ #
42
+ # For example:
43
+ #
44
+ # ActiveDirectory::Base.setup(
45
+ # :host => 'domain_controller1.example.org',
46
+ # :port => 389,
47
+ # :base => 'dc=example,dc=org',
48
+ # :auth => {
49
+ # :method => :simple,
50
+ # :username => 'querying_user@example.org',
51
+ # :password => 'querying_users_password'
52
+ # }
53
+ # )
54
+ #
55
+ # This will configure Ruby/ActiveDirectory to connect to the domain
56
+ # controller at domain_controller1.example.org, using port 389. The
57
+ # domain's base LDAP dn is expected to be 'dc=example,dc=org', and
58
+ # Ruby/ActiveDirectory will try to bind as the
59
+ # querying_user@example.org user, using the supplied password.
60
+ #
61
+ # Currently, there can be only one active connection per
62
+ # execution context.
63
+ #
64
+ # For more advanced options, refer to the Net::LDAP.new
65
+ # documentation.
66
+ #
67
+ def self.setup(settings)
68
+ @@settings = settings
69
+ @@ldap_connected = false
70
+ @@ldap = Net::LDAP.new(settings)
71
+ end
72
+
73
+ def self.error
74
+ "#{@@ldap.get_operation_result.code}: #{@@ldap.get_operation_result.message}"
75
+ end
76
+
77
+ ##
78
+ # Return the last errorcode that ldap generated
79
+ def self.error_code
80
+ @@ldap.get_operation_result.code
81
+ end
82
+
83
+ ##
84
+ # Check to see if the last query produced an error
85
+ # Note: Invalid username/password combinations will not
86
+ # produce errors
87
+ def self.error?
88
+ @@ldap.nil? ? false : @@ldap.get_operation_result.code != 0
89
+ end
90
+
91
+ ##
92
+ # Check to see if we are connected to the LDAP server
93
+ # This method will try to connect, if we haven't already
94
+ def self.connected?
95
+ begin
96
+ @@ldap_connected ||= @@ldap.bind unless @@ldap.nil?
97
+ @@ldap_connected
98
+ rescue Net::LDAP::LdapError => e
99
+ false
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Check to see if result caching is enabled
105
+ def self.cache?
106
+ @@caching
107
+ end
108
+
109
+ ##
110
+ # Clears the cache
111
+ def self.clear_cache
112
+ @@cache = {}
113
+ end
114
+
115
+ ##
116
+ # Enable caching for queries against the DN only
117
+ # This is to prevent membership lookups from hitting the
118
+ # AD unnecessarilly
119
+ def self.enable_cache
120
+ @@caching = true
121
+ end
122
+
123
+ ##
124
+ # Disable caching
125
+ def self.disable_cache
126
+ @@caching = false
127
+ end
128
+
129
+ def self.filter # :nodoc:
130
+ NIL_FILTER
131
+ end
132
+
133
+ def self.required_attributes # :nodoc:
134
+ {}
135
+ end
136
+
137
+ #
138
+ # Check to see if any entries matching the passed criteria exists.
139
+ #
140
+ # Filters should be passed as a hash of
141
+ # attribute_name => expected_value, like:
142
+ #
143
+ # User.exists?(
144
+ # :sn => 'Hunt',
145
+ # :givenName => 'James'
146
+ # )
147
+ #
148
+ # which will return true if one or more User entries have an
149
+ # sn (surname) of exactly 'Hunt' and a givenName (first name)
150
+ # of exactly 'James'.
151
+ #
152
+ # Partial attribute matches are available. For instance,
153
+ #
154
+ # Group.exists?(
155
+ # :description => 'OldGroup_*'
156
+ # )
157
+ #
158
+ # would return true if there are any Group objects in
159
+ # Active Directory whose descriptions start with OldGroup_,
160
+ # like OldGroup_Reporting, or OldGroup_Admins.
161
+ #
162
+ # Note that the * wildcard matches zero or more characters,
163
+ # so the above query would also return true if a group named
164
+ # 'OldGroup_' exists.
165
+ #
166
+ def self.exists?(filter_as_hash)
167
+ criteria = make_filter_from_hash(filter_as_hash) & filter
168
+ (@@ldap.search(:filter => criteria).size > 0)
169
+ end
170
+
171
+ #
172
+ # Whether or not the entry has local changes that have not yet been
173
+ # replicated to the Active Directory server via a call to Base#save
174
+ #
175
+ def changed?
176
+ !@attributes.empty?
177
+ end
178
+
179
+ ##
180
+ # Makes a single filter from a given key and value
181
+ # It will try to encode an array if there is a process for it
182
+ # Otherwise, it will treat it as an or condition
183
+ def self.make_filter(key, value)
184
+ #Join arrays using OR condition
185
+ if value.is_a? Array
186
+ filter = ~NIL_FILTER
187
+
188
+ value.each do |v|
189
+ filter |= Net::LDAP::Filter.eq(key, encode_field(key, v).to_s)
190
+ end
191
+ else
192
+ filter = Net::LDAP::Filter.eq(key, encode_field(key, value).to_s)
193
+ end
194
+
195
+ return filter
196
+ end
197
+
198
+ def self.make_filter_from_hash(hash) # :nodoc:
199
+ return NIL_FILTER if hash.nil? || hash.empty?
200
+
201
+ filter = NIL_FILTER
202
+
203
+ hash.each do |key, value|
204
+ filter &= make_filter(key, value)
205
+ end
206
+
207
+ return filter
208
+ end
209
+
210
+ #
211
+ # Performs a search on the Active Directory store, with similar
212
+ # syntax to the Rails ActiveRecord#find method.
213
+ #
214
+ # The first argument passed should be
215
+ # either :first or :all, to indicate that we want only one
216
+ # (:first) or all (:all) results back from the resultant set.
217
+ #
218
+ # The second argument should be a hash of attribute_name =>
219
+ # expected_value pairs.
220
+ #
221
+ # User.find(:all, :sn => 'Hunt')
222
+ #
223
+ # would find all of the User objects in Active Directory that
224
+ # have a surname of exactly 'Hunt'. As with the Base.exists?
225
+ # method, partial searches are allowed.
226
+ #
227
+ # This method always returns an array if the caller specifies
228
+ # :all for the search e (first argument). If no results
229
+ # are found, the array will be empty.
230
+ #
231
+ # If you call find(:first, ...), you will either get an object
232
+ # (a User or a Group) back, or nil, if there were no entries
233
+ # matching your filter.
234
+ #
235
+ def self.find(*args)
236
+ return false unless connected?
237
+
238
+ options = {
239
+ :filter => (args[1].nil?) ? NIL_FILTER : args[1],
240
+ :in => ''
241
+ }
242
+
243
+ cached_results = find_cached_results(args[1])
244
+ return cached_results if cached_results or cached_results.nil?
245
+
246
+ options[:in] = [ options[:in].to_s, @@settings[:base] ].delete_if { |part| part.empty? }.join(",")
247
+
248
+ if options[:filter].is_a? Hash
249
+ options[:filter] = make_filter_from_hash(options[:filter])
250
+ end
251
+
252
+ options[:filter] = options[:filter] & filter unless self.filter == NIL_FILTER
253
+
254
+ if (args.first == :all)
255
+ find_all(options)
256
+ elsif (args.first == :first)
257
+ find_first(options)
258
+ else
259
+ raise ArgumentError, 'Invalid specifier (not :all, and not :first) passed to find()'
260
+ end
261
+ end
262
+
263
+ ##
264
+ # Searches the cache and returns the result
265
+ # Returns false on failure, nil on wrong object type
266
+ #
267
+ def self.find_cached_results(filters)
268
+ return false unless cache?
269
+
270
+ #Check to see if we're only looking for :distinguishedname
271
+ return false unless filters.is_a? Hash and filters.keys == [:distinguishedname]
272
+
273
+ #Find keys we're looking up
274
+ dns = filters[:distinguishedname]
275
+
276
+ if dns.kind_of? Array
277
+ result = []
278
+
279
+ dns.each do |dn|
280
+ entry = @@cache[dn]
281
+
282
+ #If the object isn't in the cache just run the query
283
+ return false if entry.nil?
284
+
285
+ #Only permit objects of the type we're looking for
286
+ result << entry if entry.kind_of? self
287
+ end
288
+
289
+ return result
290
+ else
291
+ return false unless @@cache.key? dns
292
+ return @@cache[dns] if @@cache[dns].is_a? self
293
+ end
294
+ end
295
+
296
+ def self.find_all(options)
297
+ results = []
298
+ ldap_objs = @@ldap.search(:filter => options[:filter], :base => options[:in]) || []
299
+
300
+ ldap_objs.each do |entry|
301
+ ad_obj = new(entry)
302
+ @@cache[entry.dn] = ad_obj unless ad_obj.instance_of? Base
303
+ results << ad_obj
304
+ end
305
+
306
+ results
307
+ end
308
+
309
+ def self.find_first(options)
310
+ ldap_result = @@ldap.search(:filter => options[:filter], :base => options[:in])
311
+ return nil if ldap_result.empty?
312
+
313
+ ad_obj = new(ldap_result[0])
314
+ @@cache[ad_obj.dn] = ad_obj unless ad_obj.instance_of? Base
315
+ return ad_obj
316
+ end
317
+
318
+ def self.method_missing(name, *args) # :nodoc:
319
+ name = name.to_s
320
+ if (name[0,5] == 'find_')
321
+ find_spec, attribute_spec = parse_finder_spec(name)
322
+ raise ArgumentError, "find: Wrong number of arguments (#{args.size} for #{attribute_spec.size})" unless args.size == attribute_spec.size
323
+ filters = {}
324
+ [attribute_spec,args].transpose.each { |pr| filters[pr[0]] = pr[1] }
325
+ find(find_spec, :filter => filters)
326
+ else
327
+ super name.to_sym, args
328
+ end
329
+ end
330
+
331
+ def self.parse_finder_spec(method_name) # :nodoc:
332
+ # FIXME: This is a prime candidate for a
333
+ # first-class object, FinderSpec
334
+
335
+ method_name = method_name.gsub(/^find_/,'').gsub(/^by_/,'first_by_')
336
+ find_spec, attribute_spec = *(method_name.split('_by_'))
337
+ find_spec = find_spec.to_sym
338
+ attribute_spec = attribute_spec.split('_and_').collect { |s| s.to_sym }
339
+
340
+ return find_spec, attribute_spec
341
+ end
342
+
343
+ def ==(other) # :nodoc:
344
+ return false if other.nil?
345
+ other[:objectguid] == get_attr(:objectguid)
346
+ end
347
+
348
+ #
349
+ # Returns true if this entry does not yet exist in Active Directory.
350
+ #
351
+ def new_record?
352
+ @entry.nil?
353
+ end
354
+
355
+ #
356
+ # Refreshes the attributes for the entry with updated data from the
357
+ # domain controller.
358
+ #
359
+ def reload
360
+ return false if new_record?
361
+
362
+ @entry = @@ldap.search(:filter => Net::LDAP::Filter.eq('distinguishedName',distinguishedName))[0]
363
+ return !@entry.nil?
364
+ end
365
+
366
+ #
367
+ # Updates a single attribute (name) with one or more values
368
+ # (value), by immediately contacting the Active Directory
369
+ # server and initiating the update remotely.
370
+ #
371
+ # Entries are always reloaded (via Base.reload) after calling
372
+ # this method.
373
+ #
374
+ def update_attribute(name, value)
375
+ update_attributes(name.to_s => value)
376
+ end
377
+
378
+ #
379
+ # Updates multiple attributes, like ActiveRecord#update_attributes.
380
+ # The updates are immediately sent to the server for processing,
381
+ # and the entry is reloaded after the update (if all went well).
382
+ #
383
+ def update_attributes(attributes_to_update)
384
+ return true if attributes_to_update.empty?
385
+
386
+ operations = []
387
+ attributes_to_update.each do |attribute, values|
388
+ if values.nil? || values.empty?
389
+ operations << [ :delete, attribute, nil ]
390
+ else
391
+ values = [values] unless values.is_a? Array
392
+ values = values.collect { |v| v.to_s }
393
+
394
+ current_value = begin
395
+ @entry[attribute]
396
+ rescue NoMethodError
397
+ nil
398
+ end
399
+
400
+ operations << [ (current_value.nil? ? :add : :replace), attribute, values ]
401
+ end
402
+ end
403
+
404
+ @@ldap.modify(
405
+ :dn => distinguishedName,
406
+ :operations => operations
407
+ ) && reload
408
+ end
409
+
410
+ #
411
+ # Create a new entry in the Active Record store.
412
+ #
413
+ # dn is the Distinguished Name for the new entry. This must be
414
+ # a unique identifier, and can be passed as either a Container
415
+ # or a plain string.
416
+ #
417
+ # attributes is a symbol-keyed hash of attribute_name => value
418
+ # pairs.
419
+ #
420
+ def self.create(dn,attributes)
421
+ return nil if dn.nil? || attributes.nil?
422
+ begin
423
+ attributes.merge!(required_attributes)
424
+ if @@ldap.add(:dn => dn.to_s, :attributes => attributes)
425
+ return find_by_distinguishedName(dn.to_s)
426
+ else
427
+ return nil
428
+ end
429
+ rescue
430
+ return nil
431
+ end
432
+ end
433
+
434
+ #
435
+ # Deletes the entry from the Active Record store and returns true
436
+ # if the operation was successfully.
437
+ #
438
+ def destroy
439
+ return false if new_record?
440
+
441
+ if @@ldap.delete(:dn => distinguishedName)
442
+ @entry = nil
443
+ @attributes = {}
444
+ return true
445
+ else
446
+ return false
447
+ end
448
+ end
449
+
450
+ #
451
+ # Saves any pending changes to the entry by updating the remote
452
+ # entry.
453
+ #
454
+ def save
455
+ if update_attributes(@attributes)
456
+ @attributes = {}
457
+ return true
458
+ else
459
+ return false
460
+ end
461
+ end
462
+
463
+ #
464
+ # This method may one day provide the ability to move entries from
465
+ # container to container. Currently, it does nothing, as we are
466
+ # waiting on the Net::LDAP folks to either document the
467
+ # Net::LDAP#modrdn method, or provide a similar method for
468
+ # moving / renaming LDAP entries.
469
+ #
470
+ def move(new_rdn)
471
+ return false if new_record?
472
+ puts "Moving #{distinguishedName} to RDN: #{new_rdn}"
473
+
474
+ settings = @@settings.dup
475
+ settings[:port] = 636
476
+ settings[:encryption] = { :method => :simple_tls }
477
+
478
+ ldap = Net::LDAP.new(settings)
479
+
480
+ if ldap.rename(
481
+ :olddn => distinguishedName,
482
+ :newrdn => new_rdn,
483
+ :delete_attributes => false
484
+ )
485
+ return true
486
+ else
487
+ puts Base.error
488
+ return false
489
+ end
490
+ end
491
+
492
+ # FIXME: Need to document the Base::new
493
+ def initialize(attributes = {}) # :nodoc:
494
+ if attributes.is_a? Net::LDAP::Entry
495
+ @entry = attributes
496
+ @attributes = {}
497
+ else
498
+ @entry = nil
499
+ @attributes = attributes
500
+ end
501
+ end
502
+
503
+ ##
504
+ # Pull the class we're in
505
+ # This isn't quite right, as extending the object does funny things to how we
506
+ # lookup objects
507
+ def self.class_name
508
+ @klass ||= (self.name.include?('::') ? self.name[/.*::(.*)/, 1] : self.name)
509
+ end
510
+
511
+ ##
512
+ # Grabs the field type depending on the class it is called from
513
+ # Takes the field name as a parameter
514
+ def self.get_field_type(name)
515
+ #Extract class name
516
+ throw "Invalid field name" if name.nil?
517
+ type = ::ActiveDirectory.special_fields[class_name.to_sym][name.to_s.downcase.to_sym]
518
+ type.to_s unless type.nil?
519
+ end
520
+
521
+ @types = {}
522
+
523
+ def self.decode_field(name, value) # :nodoc:
524
+ type = get_field_type name
525
+ if !type.nil? and ::ActiveDirectory::FieldType::const_defined? type
526
+ return ::ActiveDirectory::FieldType::const_get(type).decode(value)
527
+ end
528
+ return value
529
+ end
530
+
531
+ def self.encode_field(name, value) # :nodoc:
532
+ type = get_field_type name
533
+ if !type.nil? and ::ActiveDirectory::FieldType::const_defined? type
534
+ return ::ActiveDirectory::FieldType::const_get(type).encode(value)
535
+ end
536
+ return value
537
+ end
538
+
539
+ def valid_attribute? name
540
+ @attributes.has_key?(name) || @entry.attribute_names.include?(name)
541
+ end
542
+
543
+ def get_attr(name)
544
+ name = name.to_s.downcase
545
+
546
+ return decode_field(name, @attributes[name.to_sym]) if @attributes.has_key?(name.to_sym)
547
+
548
+ if @entry.attribute_names.include? name.to_sym
549
+ value = @entry[name.to_sym]
550
+ value = value.first if value.kind_of?(Array) && value.size == 1
551
+ value = value.to_s if value.nil? || value.size == 1
552
+ value = nil.to_s if value.empty?
553
+ return self.class.decode_field(name, value)
554
+ end
555
+ end
556
+
557
+ def set_attr(name, value)
558
+ @attributes[name.to_sym] = encode_field(name, value)
559
+ end
560
+
561
+ ##
562
+ # Reads the array of values for the provided attribute. The attribute name
563
+ # is canonicalized prior to reading. Returns an empty array if the
564
+ # attribute does not exist.
565
+ alias [] get_attr
566
+ alias []= set_attr
567
+
568
+ ##
569
+ # Weird fluke with flattening, probably because of above attribute
570
+ def to_ary
571
+ end
572
+
573
+
574
+ def method_missing(name, args = []) # :nodoc:
575
+ name = name.to_s.downcase
576
+
577
+ return set_attr(name.chop, args) if name[-1] == '='
578
+
579
+ if valid_attribute? name.to_sym
580
+ get_attr(name)
581
+ else
582
+ super
583
+ end
584
+ end
585
+
586
+ end
587
+ end