active_directory 1.1.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.
@@ -0,0 +1,7 @@
1
+ 2008-12-01 James Hunt <filefrog@gmail.com>
2
+
3
+ * Fixed bug [#22307] "syntax errors"
4
+ (http://rubyforge.org/tracker/index.php?func=detail&aid=22307&group_id=1580&atid=6154)
5
+
6
+ Broke out module declarations
7
+ Added parens around _merge_ call
data/README ADDED
@@ -0,0 +1,4 @@
1
+ = Active Directory
2
+
3
+ Ruby Integration with Microsoft's Active Directory system
4
+
@@ -0,0 +1,19 @@
1
+
2
+ begin
3
+ require 'jeweler'
4
+ Jeweler::Tasks.new do |gem|
5
+ gem.name = "active_directory"
6
+ gem.summary = "An interface library for accessing Microsoft's Active Directory."
7
+ 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."
8
+ gem.author = "Adam T Kerr"
9
+ gem.email = "ajrkerr@gmail.com"
10
+ gem.homepage = "http://github.com/ajrkerr/active_directory"
11
+
12
+ # gem.files = FileList["lib/**/*.rb"]
13
+ # gem.require_path = "lib"
14
+ gem.add_dependency 'net-ldap', '>= 0.1.1'
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.1
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{active_directory}
8
+ s.version = "1.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Adam T Kerr"]
12
+ s.date = %q{2011-02-10}
13
+ s.description = %q{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.}
14
+ s.email = %q{ajrkerr@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ "CHANGELOG",
20
+ "README",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "active_directory.gemspec",
24
+ "lib/active_directory.rb",
25
+ "lib/active_directory/base.rb",
26
+ "lib/active_directory/computer.rb",
27
+ "lib/active_directory/container.rb",
28
+ "lib/active_directory/field_type/binary.rb",
29
+ "lib/active_directory/field_type/date.rb",
30
+ "lib/active_directory/field_type/password.rb",
31
+ "lib/active_directory/field_type/timestamp.rb",
32
+ "lib/active_directory/group.rb",
33
+ "lib/active_directory/member.rb",
34
+ "lib/active_directory/rails/synchronizer.rb",
35
+ "lib/active_directory/rails/user.rb",
36
+ "lib/active_directory/user.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/ajrkerr/active_directory}
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.5.0}
41
+ s.summary = %q{An interface library for accessing Microsoft's Active Directory.}
42
+
43
+ if s.respond_to? :specification_version then
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<net-ldap>, [">= 0.1.1"])
48
+ else
49
+ s.add_dependency(%q<net-ldap>, [">= 0.1.1"])
50
+ end
51
+ else
52
+ s.add_dependency(%q<net-ldap>, [">= 0.1.1"])
53
+ end
54
+ end
55
+
@@ -0,0 +1,68 @@
1
+ #-- license
2
+ #
3
+ # This file is part of the Ruby Active Directory Project
4
+ # on the web at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # Copyright (c) 2008, James Hunt <filefrog@gmail.com>
7
+ # based on original code by Justin Mecham
8
+ #
9
+ # This program is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ #
22
+ #++ license
23
+
24
+ require 'net/ldap'
25
+
26
+ require 'active_directory/base.rb'
27
+ require 'active_directory/container.rb'
28
+ require 'active_directory/member.rb'
29
+
30
+ require 'active_directory/user.rb'
31
+ require 'active_directory/group.rb'
32
+ require 'active_directory/computer.rb'
33
+
34
+ require 'active_directory/field_type/password.rb'
35
+ require 'active_directory/field_type/binary.rb'
36
+ require 'active_directory/field_type/date.rb'
37
+ require 'active_directory/field_type/timestamp.rb'
38
+
39
+ require 'active_directory/rails/user.rb'
40
+
41
+ module ActiveDirectory
42
+ #Special Fields
43
+ mattr_accessor :special_fields
44
+ @@special_fields = {
45
+
46
+ #All objects in the AD
47
+ :Base => {
48
+ :objectGUID => :binary,
49
+ :whenCreated => :date,
50
+ :whenChanged => :date
51
+ },
52
+
53
+ #User objects
54
+ :User => {
55
+ :objectSID => :binary,
56
+ :msExchMailboxGuid => :binary,
57
+ :msExchMailboxSecurityDescriptor => :binary,
58
+ :lastLogonTimestamp => :timestamp,
59
+ :pwdLastSet => :timestamp,
60
+ :accountExpires => :timestamp
61
+ },
62
+
63
+ #Group objects
64
+ :Group => {
65
+ :objectSID => :binary,
66
+ },
67
+ }
68
+ end
@@ -0,0 +1,472 @@
1
+ #-- license
2
+ #
3
+ # This file is part of the Ruby Active Directory Project
4
+ # on the web at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # Copyright (c) 2008, James Hunt <filefrog@gmail.com>
7
+ # based on original code by Justin Mecham
8
+ #
9
+ # This program is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ #
22
+ #++ license
23
+
24
+ module ActiveDirectory
25
+ #
26
+ # Base class for all Ruby/ActiveDirectory Entry Objects (like User and Group)
27
+ #
28
+ class Base
29
+ #
30
+ # A Net::LDAP::Filter object that doesn't do any filtering
31
+ # (outside of check that the CN attribute is present. This
32
+ # is used internally for specifying a 'no filter' condition
33
+ # for methods that require a filter object.
34
+ #
35
+ NIL_FILTER = Net::LDAP::Filter.pres('cn')
36
+
37
+ @@ldap = nil
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
+ # :username => 'querying_user@example.org',
50
+ # :password => 'querying_users_password'
51
+ # }
52
+ # )
53
+ #
54
+ # This will configure Ruby/ActiveDirectory to connect to the domain
55
+ # controller at domain_controller1.example.org, using port 389. The
56
+ # domain's base LDAP dn is expected to be 'dc=example,dc=org', and
57
+ # Ruby/ActiveDirectory will try to bind as the
58
+ # querying_user@example.org user, using the supplied password.
59
+ #
60
+ # Currently, there can be only one active connection per
61
+ # execution context.
62
+ #
63
+ # For more advanced options, refer to the Net::LDAP.new
64
+ # documentation.
65
+ #
66
+ def self.setup(settings)
67
+ @@settings = settings
68
+ @@ldap = Net::LDAP.new(settings)
69
+ end
70
+
71
+ def self.error
72
+ "#{@@ldap.get_operation_result.code}: #{@@ldap.get_operation_result.message}"
73
+ end
74
+
75
+ def self.error_code
76
+ @@ldap.get_operation_result.code
77
+ end
78
+
79
+ def self.error?
80
+ !success?
81
+ end
82
+
83
+ def self.success?
84
+ @@ldap.get_operation_result.code == 0
85
+ end
86
+
87
+ def self.connected?
88
+ @@ldap.bind
89
+ end
90
+
91
+ def self.filter # :nodoc:
92
+ NIL_FILTER
93
+ end
94
+
95
+ def self.required_attributes # :nodoc:
96
+ {}
97
+ end
98
+
99
+ #
100
+ # Check to see if any entries matching the passed criteria exists.
101
+ #
102
+ # Filters should be passed as a hash of
103
+ # attribute_name => expected_value, like:
104
+ #
105
+ # User.exists?(
106
+ # :sn => 'Hunt',
107
+ # :givenName => 'James'
108
+ # )
109
+ #
110
+ # which will return true if one or more User entries have an
111
+ # sn (surname) of exactly 'Hunt' and a givenName (first name)
112
+ # of exactly 'James'.
113
+ #
114
+ # Partial attribute matches are available. For instance,
115
+ #
116
+ # Group.exists?(
117
+ # :description => 'OldGroup_*'
118
+ # )
119
+ #
120
+ # would return true if there are any Group objects in
121
+ # Active Directory whose descriptions start with OldGroup_,
122
+ # like OldGroup_Reporting, or OldGroup_Admins.
123
+ #
124
+ # Note that the * wildcard matches zero or more characters,
125
+ # so the above query would also return true if a group named
126
+ # 'OldGroup_' exists.
127
+ #
128
+ def self.exists?(filter_as_hash)
129
+ criteria = make_filter_from_hash(filter_as_hash) & filter
130
+ (@@ldap.search(:filter => criteria).size > 0)
131
+ end
132
+
133
+ #
134
+ # Whether or not the entry has local changes that have not yet been
135
+ # replicated to the Active Directory server via a call to Base#save
136
+ #
137
+ def changed?
138
+ !@attributes.empty?
139
+ end
140
+
141
+ def self.make_filter_from_hash(hash) # :nodoc:
142
+ return NIL_FILTER if hash.nil? || hash.empty?
143
+
144
+ #I'm sure there's a better way to do this
145
+ #Grab the first one, then do the rest
146
+ key, value = hash.shift
147
+ f = Net::LDAP::Filter.eq(key, value.to_s)
148
+
149
+ hash.each do |key, value|
150
+ f = f & Net::LDAP::Filter.eq(key, value.to_s)
151
+ end
152
+
153
+ return f
154
+ end
155
+
156
+ #
157
+ # Performs a search on the Active Directory store, with similar
158
+ # syntax to the Rails ActiveRecord#find method.
159
+ #
160
+ # The first argument passed should be
161
+ # either :first or :all, to indicate that we want only one
162
+ # (:first) or all (:all) results back from the resultant set.
163
+ #
164
+ # The second argument should be a hash of attribute_name =>
165
+ # expected_value pairs.
166
+ #
167
+ # User.find(:all, :sn => 'Hunt')
168
+ #
169
+ # would find all of the User objects in Active Directory that
170
+ # have a surname of exactly 'Hunt'. As with the Base.exists?
171
+ # method, partial searches are allowed.
172
+ #
173
+ # This method always returns an array if the caller specifies
174
+ # :all for the search type (first argument). If no results
175
+ # are found, the array will be empty.
176
+ #
177
+ # If you call find(:first, ...), you will either get an object
178
+ # (a User or a Group) back, or nil, if there were no entries
179
+ # matching your filter.
180
+ #
181
+ def self.find(*args)
182
+ options = {
183
+ :filter => NIL_FILTER,
184
+ :in => ''
185
+ }
186
+ options.merge!(:filter => args[1]) unless args[1].nil?
187
+ options[:in] = [ options[:in].to_s, @@settings[:base] ].delete_if { |part| part.empty? }.join(",")
188
+ if options[:filter].is_a? Hash
189
+ options[:filter] = make_filter_from_hash(options[:filter])
190
+ end
191
+ options[:filter] = options[:filter] & filter unless self.filter == NIL_FILTER
192
+
193
+ if (args.first == :all)
194
+ find_all(options)
195
+ elsif (args.first == :first)
196
+ find_first(options)
197
+ else
198
+ raise ArgumentError, 'Invalid specifier (not :all, and not :first) passed to find()'
199
+ end
200
+ end
201
+
202
+ def self.find_all(options)
203
+ results = []
204
+ @@ldap.search(:filter => options[:filter], :base => options[:in], :return_result => false) do |entry|
205
+ results << new(entry)
206
+ end
207
+ results
208
+ end
209
+
210
+ def self.find_first(options)
211
+ @@ldap.search(:filter => options[:filter], :base => options[:in], :return_result => false) do |entry|
212
+ return new(entry)
213
+ end
214
+ end
215
+
216
+ def self.method_missing(name, *args) # :nodoc:
217
+ name = name.to_s
218
+ if (name[0,5] == 'find_')
219
+ find_spec, attribute_spec = parse_finder_spec(name)
220
+ raise ArgumentError, "find: Wrong number of arguments (#{args.size} for #{attribute_spec.size})" unless args.size == attribute_spec.size
221
+ filters = {}
222
+ [attribute_spec,args].transpose.each { |pr| filters[pr[0]] = pr[1] }
223
+ find(find_spec, :filter => filters)
224
+ else
225
+ super name.to_sym, args
226
+ end
227
+ end
228
+
229
+ def self.parse_finder_spec(method_name) # :nodoc:
230
+ # FIXME: This is a prime candidate for a
231
+ # first-class object, FinderSpec
232
+
233
+ method_name = method_name.gsub(/^find_/,'').gsub(/^by_/,'first_by_')
234
+ find_spec, attribute_spec = *(method_name.split('_by_'))
235
+ find_spec = find_spec.to_sym
236
+ attribute_spec = attribute_spec.split('_and_').collect { |s| s.to_sym }
237
+
238
+ return find_spec, attribute_spec
239
+ end
240
+
241
+ def ==(other) # :nodoc:
242
+ return false if other.nil?
243
+ other.objectGUID == objectGUID
244
+ end
245
+
246
+ #
247
+ # Returns true if this entry does not yet exist in Active Directory.
248
+ #
249
+ def new_record?
250
+ @entry.nil?
251
+ end
252
+
253
+ #
254
+ # Refreshes the attributes for the entry with updated data from the
255
+ # domain controller.
256
+ #
257
+ def reload
258
+ return false if new_record?
259
+
260
+ @entry = @@ldap.search(:filter => Net::LDAP::Filter.eq('distinguishedName',distinguishedName))[0]
261
+ return !@entry.nil?
262
+ end
263
+
264
+ #
265
+ # Updates a single attribute (name) with one or more values
266
+ # (value), by immediately contacting the Active Directory
267
+ # server and initiating the update remotely.
268
+ #
269
+ # Entries are always reloaded (via Base.reload) after calling
270
+ # this method.
271
+ #
272
+ def update_attribute(name, value)
273
+ update_attributes(name.to_s => value)
274
+ end
275
+
276
+ #
277
+ # Updates multiple attributes, like ActiveRecord#update_attributes.
278
+ # The updates are immediately sent to the server for processing,
279
+ # and the entry is reloaded after the update (if all went well).
280
+ #
281
+ def update_attributes(attributes_to_update)
282
+ return true if attributes_to_update.empty?
283
+
284
+ operations = []
285
+ attributes_to_update.each do |attribute, values|
286
+ if values.nil? || values.empty?
287
+ operations << [ :delete, attribute, nil ]
288
+ else
289
+ values = [values] unless values.is_a? Array
290
+ values = values.collect { |v| v.to_s }
291
+
292
+ current_value = begin
293
+ @entry.send(attribute)
294
+ rescue NoMethodError
295
+ nil
296
+ end
297
+
298
+ operations << [ (current_value.nil? ? :add : :replace), attribute, values ]
299
+ end
300
+ end
301
+
302
+ @@ldap.modify(
303
+ :dn => distinguishedName,
304
+ :operations => operations
305
+ ) && reload
306
+ end
307
+
308
+ #
309
+ # Create a new entry in the Active Record store.
310
+ #
311
+ # dn is the Distinguished Name for the new entry. This must be
312
+ # a unique identifier, and can be passed as either a Container
313
+ # or a plain string.
314
+ #
315
+ # attributes is a symbol-keyed hash of attribute_name => value
316
+ # pairs.
317
+ #
318
+ def self.create(dn,attributes)
319
+ return nil if dn.nil? || attributes.nil?
320
+ begin
321
+ attributes.merge!(required_attributes)
322
+ if @@ldap.add(:dn => dn.to_s, :attributes => attributes)
323
+ return find_by_distinguishedName(dn.to_s)
324
+ else
325
+ return nil
326
+ end
327
+ rescue
328
+ return nil
329
+ end
330
+ end
331
+
332
+ #
333
+ # Deletes the entry from the Active Record store and returns true
334
+ # if the operation was successfully.
335
+ #
336
+ def destroy
337
+ return false if new_record?
338
+
339
+ if @@ldap.delete(:dn => distinguishedName)
340
+ @entry = nil
341
+ @attributes = {}
342
+ return true
343
+ else
344
+ return false
345
+ end
346
+ end
347
+
348
+ #
349
+ # Saves any pending changes to the entry by updating the remote
350
+ # entry.
351
+ #
352
+ def save
353
+ if update_attributes(@attributes)
354
+ @attributes = {}
355
+ return true
356
+ else
357
+ return false
358
+ end
359
+ end
360
+
361
+ #
362
+ # This method may one day provide the ability to move entries from
363
+ # container to container. Currently, it does nothing, as we are
364
+ # waiting on the Net::LDAP folks to either document the
365
+ # Net::LDAP#modrdn method, or provide a similar method for
366
+ # moving / renaming LDAP entries.
367
+ #
368
+ def move(new_rdn)
369
+ return false if new_record?
370
+ puts "Moving #{distinguishedName} to RDN: #{new_rdn}"
371
+
372
+ settings = @@settings.dup
373
+ settings[:port] = 636
374
+ settings[:encryption] = { :method => :simple_tls }
375
+
376
+ ldap = Net::LDAP.new(settings)
377
+
378
+ if ldap.rename(
379
+ :olddn => distinguishedName,
380
+ :newrdn => new_rdn,
381
+ :delete_attributes => false
382
+ )
383
+ return true
384
+ else
385
+ puts Base.error
386
+ return false
387
+ end
388
+ end
389
+
390
+ # FIXME: Need to document the Base::new
391
+ def initialize(attributes = {}) # :nodoc:
392
+ if attributes.is_a? Net::LDAP::Entry
393
+ @entry = attributes
394
+ @attributes = {}
395
+ else
396
+ @entry = nil
397
+ @attributes = attributes
398
+ end
399
+ end
400
+
401
+ def get_field_type(name)
402
+ ::Rails.logger.add 0, "Looking for spec_fields[#{self.class.name.to_sym}][#{name.to_sym}]"
403
+ ::ActiveDirectory.special_fields[self.class.name.to_sym][name.to_sym].classify
404
+ end
405
+
406
+ def decode_field(name, value)
407
+ return value
408
+ ::Rails.logger.add 0, "Encoding #{name}, #{value}"
409
+ type = get_field_type name
410
+ return ::ActiveDirectory::const_get(type).decode(value) if ::ActiveDirectory::const_defined? type
411
+ return value
412
+ end
413
+
414
+ def encode_field(name, value)
415
+ return value
416
+ ::Rails.logger.add 0, "Encoding #{first}, #{name}, #{value}"
417
+ type = get_field_type name
418
+ return "Encode #{name} as #{type}"
419
+ return ::ActiveDirectory::const_get(type).encode(value) if ::ActiveDirectory::const_defined? type
420
+ return value
421
+ end
422
+
423
+ def method_missing(name, args = []) # :nodoc:
424
+ name = name.to_s.downcase
425
+
426
+ if name[-1,1] == '='
427
+ name.chop!
428
+ @attributes[name.to_sym] = encode_field(name, args)
429
+ end
430
+
431
+ return decode_field(name, @attributes[name.to_sym]) if @attributes.has_key?(name.to_sym)
432
+
433
+ if @entry
434
+ # begin
435
+ value = @entry.send(name.to_sym)
436
+ value = value.first if value.kind_of?(Array) && value.size == 1
437
+ value = value.to_s if value.nil? || value.size == 1
438
+ return decode_field(name, value)
439
+ # rescue NoMethodError => e
440
+ # ::Rails.logger.add 0, "#{e.inspect}"
441
+ # return nil
442
+ # end
443
+ end
444
+
445
+ super
446
+ end
447
+
448
+ # def method_missing(name, args = []) # :nodoc:
449
+ # name_s = name.to_s.downcase
450
+ # name = name_s.to_sym
451
+ # if name_s[-1,1] == '='
452
+ # @attributes[name_s[0,name_s.size-1].to_sym] = args
453
+ # else
454
+ # if @attributes.has_key?(name)
455
+ # return @attributes[name]
456
+ # elsif @entry
457
+ # begin
458
+ # value = @entry.send(name)
459
+ # value = value.first if value.kind_of?(Array) && value.size == 1
460
+ # value = value.to_s if value.nil? || value.size == 1
461
+ # return value
462
+ # rescue NoMethodError
463
+ # return nil
464
+ # end
465
+ # else
466
+ # super
467
+ # end
468
+ # end
469
+ # end
470
+
471
+ end
472
+ end