active_directory 1.1.1

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