activedirectory 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+
2
+ The ActiveDirectory Module for Ruby is licensed under the MIT license (below).
3
+
4
+ ------------------------------------------------------------------------------
5
+
6
+ Copyright (c) 2005-2006 Justin Mecham
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to
10
+ deal in the Software without restriction, including without limitation the
11
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
+ sell copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
+ IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,139 @@
1
+ = Active Directory Module for Ruby
2
+
3
+ This module enables effortless interaction with Active Directory servers.
4
+
5
+ It is built atop the Ruby/LDAP[http://ruby-ldap.sourceforge.net/] extension
6
+ with explicit support for the idiosyncrasies of Microsoft Windows Servers and
7
+ the LDAP objects defined therein. This includes support for:
8
+
9
+ * Users (objectClass=user)
10
+ * Groups (objectClass=group)
11
+ * Additional objects will be supported in future releases (e.g. Computers,
12
+ Printers, etc)
13
+
14
+ == Installation
15
+
16
+ === RubyGems
17
+
18
+ The preferred method if installing the Active Directory Module for Ruby is
19
+ with RubyGems[http://docs.rubygems.org/]. Installing with RubyGems is as
20
+ simple as typing:
21
+
22
+ gem install activedirectory-x.x.x.gem
23
+
24
+ If you did not download the gem file and instead have the source distribution
25
+ extracted you may create a gem by typing:
26
+
27
+ rake package
28
+
29
+ in the directory containing the Active Directory Module for Ruby source.
30
+
31
+ === Source
32
+
33
+ If you prefer not to use RubyGems to install the library, you may install the
34
+ module from source by typing:
35
+
36
+ ruby install.rb
37
+
38
+ in the directory containing the Active Directory Module for Ruby source.
39
+
40
+ == Usage Instructions
41
+
42
+ === Including the module
43
+
44
+ Once installed, you must include the ActiveDirectory module into your project.
45
+ If you are using RubyGems, use:
46
+
47
+ require_gem "activedirectory"
48
+
49
+ Otherwise, include the module with:
50
+
51
+ require "activedirectory"
52
+
53
+ === Configuring your Active Directory instance
54
+
55
+ Interacting with an Active Directory instance requires that you first
56
+ configure the connection to a Windows domain controller. This configuration
57
+ needs to be called only once when your application starts up. If you are using
58
+ this module within a Ruby on Rails[http://www.rubyonrails.org/] application,
59
+ you would place this in your environment.rb file.
60
+
61
+ ActiveDirectory::Base.server_settings = {
62
+ :host => "server.example.com",
63
+ :username => "username",
64
+ :password => "password",
65
+ :domain => "example.com",
66
+ :base_dn => "DC=example,DC=com"
67
+ }
68
+
69
+ Once ActiveDirectory::Base has been configured, any call into the API will
70
+ open a new connection to the server on-demand (or use an existing open
71
+ connection).
72
+
73
+ == Example Usage
74
+
75
+ === User Objects
76
+
77
+ Locating users within a directory is very flexible. You may load individual
78
+ users by their account name (sAMAccountName), their distinguished name (dn),
79
+ or any number of users based on the criteria used in your LDAP search base and
80
+ filters.
81
+
82
+ # Load all users (including disabled) within the default Base DN.
83
+ all_users = ActiveDirectory::User.find(:all)
84
+
85
+ # Load all disabled users within the default Base DN.
86
+ disabled_users = ActiveDirectory::User.find(:all,
87
+ :filter => "(userAccountControl=514)")
88
+
89
+ # Load all users who are in the Managers organizational unit whose accounts
90
+ # are not disabled.
91
+ managers = ActiveDirectory::User.find(:all,
92
+ :base => "OU=Managers,DC=example,DC=com",
93
+ :filter => "(userAccountControl=512)")
94
+
95
+ # Load the user "John Doe" by his sAMAccountName.
96
+ user = ActiveDirectory::User.find("jdoe")
97
+
98
+ # Load the user "John Doe" by his distinguished name (DN).
99
+ user = ActiveDirectory::User.find("CN=John Doe,CN=Users,DC=example,DC=com")
100
+
101
+ === Group Objects
102
+
103
+ Locating Groups is just as simple as Users, however, you must provide a
104
+ Distinguished Name (DN) for loading.
105
+
106
+ # Load all groups within the default Base DN.
107
+ all_groups = ActiveDirectory::Group.find(:all)
108
+
109
+ # Load the "Developers" group by its distinguished name (DN).
110
+ developers = ActiveDirectory::Group.find("CN=Developers,DC=example,DC=com")
111
+
112
+ == License
113
+
114
+ The Active Directory Module for Ruby module is licensed under the MIT License.
115
+
116
+ Copyright (c) 2005-2006 Justin Mecham
117
+
118
+ Permission is hereby granted, free of charge, to any person obtaining a copy
119
+ of this software and associated documentation files (the "Software"), to
120
+ deal in the Software without restriction, including without limitation the
121
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
122
+ sell copies of the Software, and to permit persons to whom the Software is
123
+ furnished to do so, subject to the following conditions:
124
+
125
+ The above copyright notice and this permission notice shall be included in
126
+ all copies or substantial portions of the Software.
127
+
128
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
129
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
130
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
131
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
132
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
133
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
134
+ IN THE SOFTWARE.
135
+
136
+ == Contact
137
+
138
+ The Active Directory Module for Ruby is developed and maintained by Justin
139
+ Mecham (jmecham@justin@aspect.net[mailto:jmecham@justin@aspect.net]).
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require File.join(File.dirname(__FILE__), 'lib', 'active_directory', 'version')
8
+
9
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
10
+ PKG_NAME = 'activedirectory'
11
+ PKG_VERSION = ActiveDirectory::VERSION::STRING + PKG_BUILD
12
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
13
+
14
+ RELEASE_NAME = "REL #{PKG_VERSION}"
15
+
16
+ desc "Default Task"
17
+ task :default => [ :test ]
18
+
19
+
20
+ # Run the unit tests
21
+ Rake::TestTask.new { |t|
22
+ t.libs << "test"
23
+ t.pattern = 'test/*_test.rb'
24
+ t.verbose = true
25
+ }
26
+
27
+
28
+ # Genereate the RDoc documentation
29
+ Rake::RDocTask.new { |rdoc|
30
+ rdoc.rdoc_dir = 'doc'
31
+ rdoc.title = "Active Directory Module for Ruby -- Effortless Interaction with Active Directory"
32
+ rdoc.options << "--line-numbers" << "--inline-source"
33
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
34
+ rdoc.rdoc_files.include(['README', 'LICENSE'])
35
+ rdoc.rdoc_files.include('lib/**/*.rb')
36
+ }
37
+
38
+ # Create compressed packages
39
+ spec = Gem::Specification.new do |s|
40
+ s.platform = Gem::Platform::RUBY
41
+ s.name = PKG_NAME
42
+ s.summary = "Module for easy interaction with Active Directory servers."
43
+ s.description = %q{Makes it trivial to integrate with Active Directory servers for information and authentication concerning users and groups.}
44
+ s.version = PKG_VERSION
45
+
46
+ s.author = "Justin Mecham"
47
+ s.email = "justin@aspect.net"
48
+
49
+ s.has_rdoc = true
50
+ s.requirements << 'none'
51
+ s.require_path = 'lib'
52
+ s.autorequire = 'active_directory'
53
+
54
+ s.files = [ "Rakefile", "install.rb", "README", "LICENSE" ]
55
+ s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
56
+ s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
57
+ end
58
+
59
+ Rake::GemPackageTask.new(spec) do |pkg|
60
+ pkg.gem_spec = spec
61
+ pkg.need_tar = true
62
+ pkg.need_zip = true
63
+ end
64
+
65
+
66
+ #desc "Publish the API documentation"
67
+ #task :pgem => [:package] do
68
+ # Rake::SshFilePublisher.new("[user@host]", "[path]", "pkg", "#{PKG_FILE_NAME}.gem").upload
69
+ #end
70
+
71
+
72
+ #desc "Publish the API documentation"
73
+ #task :pdoc => [:rdoc] do
74
+ # Rake::SshDirPublisher.new("[user@host]", "[path]", "doc").upload
75
+ #end
76
+
data/install.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ $sitedir = CONFIG["sitelibdir"]
8
+ unless $sitedir
9
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
10
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
11
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
12
+ if !$sitedir
13
+ $sitedir = File.join($libdir, "site_ruby")
14
+ elsif $sitedir !~ Regexp.quote(version)
15
+ $sitedir = File.join($sitedir, version)
16
+ end
17
+ end
18
+
19
+ Dir.chdir("lib")
20
+
21
+ Find.find("active_directory", "active_directory.rb") { |f|
22
+ if f[-3..-1] == ".rb"
23
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
24
+ else
25
+ File::makedirs(File.join($sitedir, *f.split(/\//)))
26
+ end
27
+ }
@@ -0,0 +1,15 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/active_directory/vendor/")
2
+
3
+ # External Dependencies
4
+ require 'ldap'
5
+ require 'logger'
6
+
7
+ # Extensions to Ruby
8
+ Dir[File.dirname(__FILE__) + "/active_directory/ext/*.rb"].each { |file|
9
+ require file
10
+ }
11
+
12
+ # Active Directory Classes
13
+ require 'active_directory/base'
14
+ require 'active_directory/user'
15
+ require 'active_directory/group'
@@ -0,0 +1,368 @@
1
+ #--
2
+ # Active Directory Module for Ruby
3
+ #
4
+ # Copyright (c) 2005-2006 Justin Mecham
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #++
24
+
25
+ module ActiveDirectory
26
+
27
+ #
28
+ # The ActiveDirectory module contains the classes used for communicating with
29
+ # Active Directory LDAP servers. ActiveDirectory::Base is the basis from which
30
+ # all classes derive.
31
+ #
32
+ class Base
33
+
34
+ #
35
+ # Configuration
36
+ #
37
+ @@server_settings = {
38
+ :host => "localhost",
39
+ :port => 389,
40
+ :username => nil,
41
+ :password => nil,
42
+ :domain => nil,
43
+ :base_dn => nil
44
+ }
45
+ cattr_accessor :server_settings
46
+
47
+ #
48
+ # Logging
49
+ #
50
+ cattr_writer :logger
51
+ def self.logger
52
+ @@logger || Logger.new(STDOUT)
53
+ end
54
+
55
+ #
56
+ # Returns a connection to the configured Active Directory instance. If a
57
+ # connection is not already established, it will attempt to establish the
58
+ # connection.
59
+ #
60
+ def self.connection
61
+ @@connection ||= connect
62
+ end
63
+
64
+ #
65
+ # Opens and returns a connection to the Active Directory instance. By
66
+ # default, secure connections will be attempted first while gracefully
67
+ # falling back to lesser secure methods.
68
+ #
69
+ # The order by which connections are attempted are: TLS, SSL, unencrypted.
70
+ #
71
+ # Calling #connection will automatically call this method if a connection
72
+ # has not already been established.
73
+ #
74
+ def self.connect
75
+
76
+ host = @@server_settings[:host]
77
+ port = @@server_settings[:port] || 389
78
+
79
+ # Attempt to connect using TLS
80
+ begin
81
+ connection = LDAP::SSLConn.new(host, port, true)
82
+ connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
83
+ bind(connection)
84
+ logger.info("ActiveDirectory: Connected to #{@@server_settings[:host]} using TLS...") unless logger.nil?
85
+ rescue
86
+ logger.debug("ActiveDirectory: Failed to connect to #{@@server_settings[:host]} using TLS!") unless logger.nil?
87
+ # Attempt to connect using SSL
88
+ begin
89
+ connection = LDAP::SSLConn.new(host, port, false)
90
+ connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
91
+ bind(connection)
92
+ logger.info("ActiveDirectory: Connected to #{@@server_settings[:host]} over SSL...") unless logger.nil?
93
+ rescue
94
+ logger.debug("ActiveDirectory: Failed to connect to #{@@server_settings[:host]} over SSL!") unless logger.nil?
95
+ # Attempt to connect without encryption
96
+ begin
97
+ connection = LDAP::Conn.new(host, port)
98
+ connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
99
+ bind(connection)
100
+ logger.info("ActiveDirectory: Connected to #{@@server_settings[:host]} without encryption!") unless logger.nil?
101
+ rescue
102
+ logger.error("ActiveDirectory: Failed to connect to #{@@server_settings[:host]}!") unless logger.nil?
103
+ puts "EXCEPTION: #{$!}"
104
+ connection = nil
105
+ raise
106
+ end
107
+ end
108
+ end
109
+
110
+ connection.set_option(LDAP::LDAP_OPT_REFERRALS, 0)
111
+
112
+ connection
113
+
114
+ end
115
+
116
+ #
117
+ # Unbinds (if bound) and closes the current connection.
118
+ #
119
+ def self.close
120
+ begin
121
+ @@connection.unbind unless @@connection.nil?
122
+ rescue
123
+ end
124
+ @@connection = nil
125
+ end
126
+
127
+ #
128
+ # Attempts to reconnect to the server by closing the existing connection
129
+ # and reconnecting.
130
+ #
131
+ def self.reconnect
132
+ close
133
+ @@connection = connect
134
+ end
135
+
136
+ #
137
+ # Search interface for querying for objects within the directory, such as
138
+ # users and groups. Searching the directory is quite similar to a normal
139
+ # LDAP search, but with a better API.
140
+ #
141
+ # If you call this method from User or Group, it will narrow your searches
142
+ # to those specific objects by default. Calling this method on Base will
143
+ # return objects of any class and provides the most flexibility.
144
+ #
145
+ # ==== Searching Users
146
+ #
147
+ # Users may be located within the directory by their distinguished name
148
+ # (DN), their username (sAMAccountName), or through any other valid LDAP
149
+ # filter and optional search base.
150
+ #
151
+ # # Load all users (including disabled) within the default Base DN.
152
+ # all_users = ActiveDirectory::User.find(:all)
153
+ #
154
+ # # Load all disabled users within the default Base DN.
155
+ # disabled_users = ActiveDirectory::User.find(:all,
156
+ # :filter => "(userAccountControl=514)")
157
+ #
158
+ # # Load all users who are in the Managers organizational unit whose
159
+ # # accounts are not disabled.
160
+ # managers = ActiveDirectory::User.find(:all,
161
+ # :base => "OU=Managers,DC=example,DC=com",
162
+ # :filter => "(userAccountControl=512)")
163
+ #
164
+ # # Load the user "John Doe" by his sAMAccountName.
165
+ # user = ActiveDirectory::User.find("jdoe")
166
+ #
167
+ # # Load the user "John Doe" by his distinguished name (DN).
168
+ # user = ActiveDirectory::User.find("CN=John Doe,CN=Users,DC=example,DC=com")
169
+ #
170
+ # ==== Searching Groups
171
+ #
172
+ # Groups may be located within the diretctory by their distinguished name
173
+ # (DN) or through any other valid LDAP filter and optional search base.
174
+ #
175
+ # # Load all groups within the default Base DN.
176
+ # all_groups = ActiveDirectory::Group.find(:all)
177
+ #
178
+ # # Load the "Developers" group by its distinguished name (DN).
179
+ # developers = ActiveDirectory::Group.find("CN=Developers,DC=example,DC=com")
180
+ #
181
+ # ==== More Advanced Examples
182
+ #
183
+ # By calling ActiveDirectory::Base#find you can query objects across
184
+ # classes, allowing you to pull in both Groups and Users that match your
185
+ # criteria.
186
+ #
187
+ # # Load all Contacts
188
+ # contacts = ActiveDirectory::Base.find(:all,
189
+ # :filter => "(&(objectClass=User)(objectCategory=Contact))")
190
+ #
191
+ def self.find(*args)
192
+
193
+ options = extract_options_from_args!(args)
194
+ attributes = [ "distinguishedName", "objectClass" ]
195
+
196
+ # Determine the appropriate search filter
197
+ if self.name == "ActiveDirectory::Base"
198
+ if options[:filter]
199
+ filter = options[:filter]
200
+ else
201
+ filter = "(|(&(objectCategory=Person)(objectClass=User))(objectClass=Group))"
202
+ end
203
+ else
204
+ subklass = class_name_of_active_directory_descendent(self)
205
+ if subklass == "ActiveDirectory::User"
206
+ filter = "(&(objectCategory=Person)(objectClass=User)#{options[:filter]})"
207
+ elsif subklass == "ActiveDirectory::Group"
208
+ filter = "(&(objectClass=Group)#{options[:filter]})"
209
+ end
210
+ end
211
+
212
+ # Determine the appropriate search base
213
+ base_dn = options[:base] ? options[:base] : @@server_settings[:base_dn]
214
+
215
+ # Determine the appropriate scope
216
+ scope = options[:scope] ? options[:scope] : LDAP::LDAP_SCOPE_SUBTREE
217
+
218
+ # Load all matching objects
219
+ if args.first == :all
220
+
221
+ logger.debug "Searching Active Directory" \
222
+ " - Filter: #{filter}" \
223
+ " - Search Base: #{base_dn} " \
224
+ " - Scope: #{scope}"
225
+
226
+ # Perform search
227
+ entries = self.search(base_dn, scope, filter, attributes)
228
+
229
+ result = Array.new
230
+ unless entries.nil?
231
+ for entry in entries
232
+ if entry['objectClass'].include? "person"
233
+ result << User.new(entry['distinguishedName'][0])
234
+ elsif entry['objectClass'].include? "group"
235
+ result << Group.new(entry['distinguishedName'][0])
236
+ end
237
+ end
238
+ end
239
+ result
240
+
241
+ else
242
+
243
+ # Load a single matching object by either a common name or a
244
+ # sAMAccountName
245
+ if args.first =~ /(CN|cn)=/
246
+ base_dn = args.first
247
+ else
248
+ filter = "(&(objectCategory=Person)(objectClass=User)(samAccountName=#{args.first})#{options[:filter]})"
249
+ end
250
+
251
+ logger.debug "Searching Active Directory" \
252
+ " - Filter: #{filter}" \
253
+ " - Search Base: #{base_dn} " \
254
+ " - Scope: #{scope}"
255
+
256
+ begin
257
+ entry = self.search(base_dn, scope, filter, attributes)
258
+ rescue
259
+ if $!.message == "No such object"
260
+ raise UnknownUserError
261
+ else
262
+ raise
263
+ end
264
+ end
265
+
266
+ entry = entry[0]
267
+ if entry['objectClass'].include? "person"
268
+ User.new(entry['distinguishedName'][0])
269
+ elsif entry['objectClass'].include? "group"
270
+ Group.new(entry['distinguishedName'][0])
271
+ end
272
+
273
+ end
274
+
275
+ end
276
+
277
+ protected
278
+
279
+ #
280
+ # Implement our own interface to LDAP::Conn#search so that we can attempt
281
+ # graceful reconnections when the connection becomes stale or otherwise
282
+ # terminates.
283
+ #
284
+ def self.search(base_dn, scope, filter, attrs = nil) #:nodoc:
285
+ retries = 0
286
+ entries = nil
287
+ begin
288
+ connection.search(base_dn, scope, filter, attrs) { |entry|
289
+ entries = Array.new if entries.nil?
290
+ entries << entry.to_hash
291
+ }
292
+ rescue
293
+ logger.debug("ActiveDirectory: Attempting to re-establish connection to Active Directory server... (Exception: \"#{$!}\" – Retry ##{retries} – #{$!.class})}") unless logger.nil?
294
+ begin
295
+ reconnect
296
+ rescue
297
+ (retries += 1) < 3 ? retry : raise
298
+ end
299
+ retry
300
+ end
301
+ entries
302
+ end
303
+
304
+ private
305
+
306
+ #
307
+ # Attempts to bind to the Active Directory instance. If a bind attempt
308
+ # fails by simple authentication an attempt at anonymous binding will be
309
+ # made.
310
+ #
311
+ def self.bind(connection)
312
+ # Attempt to bind with a username and password
313
+ begin
314
+ connection.bind("#{@@server_settings[:username]}@#{@@server_settings[:domain]}", @@server_settings[:password])
315
+ logger.info("ActiveDirectory: Bound to Active Directory server as #{@@server_settings[:username]}.") unless logger.nil?
316
+ rescue
317
+ logger.debug("ActiveDirectory: Failed to bind to Active Directory server as \"#{@@server_settings[:username]}\"!") unless logger.nil?
318
+ # Attempt to bind anonymously
319
+ begin
320
+ connection.bind()
321
+ logger.info("ActiveDirectory: Bound anonymously to Active Directory server.") unless logger.nil?
322
+ rescue
323
+ logger.debug("ActiveDirectory: Failed to bind anonymously to Active Directory server!") unless logger.nil?
324
+ raise
325
+ end
326
+ end
327
+ end
328
+
329
+ def self.extract_options_from_args!(args)
330
+ options = args.last.is_a?(Hash) ? args.pop : {}
331
+ validate_find_options(options)
332
+ options
333
+ end
334
+
335
+ def self.validate_find_options(options)
336
+ options.assert_valid_keys [ :base, :filter, :scope ]
337
+ end
338
+
339
+ def self.class_of_active_directory_descendent(klass)
340
+ if klass.superclass == Base
341
+ klass
342
+ elsif klass.superclass.nil?
343
+ raise ActiveDirectoryError, "#{name} doesn't belong in a class \
344
+ hierarchy descending from ActiveDirectory"
345
+ else
346
+ self.class_of_active_directory_descendent(klass.superclass)
347
+ end
348
+ end
349
+
350
+ def self.class_name_of_active_directory_descendent(klass)
351
+ self.class_of_active_directory_descendent(klass).name
352
+ end
353
+
354
+ end
355
+
356
+ class ActiveDirectoryError < StandardError #:nodoc:
357
+ end
358
+
359
+ class UnknownUserError < StandardError #:nodoc:
360
+ end
361
+
362
+ class UnknownGroupError < StandardError #:nodoc:
363
+ end
364
+
365
+ class PasswordInvalid < StandardError #:nodoc:
366
+ end
367
+
368
+ end
@@ -0,0 +1,50 @@
1
+ #
2
+ # Extends the class object with class and instance accessors for class
3
+ # attributes, just like the native attr* accessors for instance attributes.
4
+ #
5
+ # This was taken from ActiveSupport (see rubyonrails.org)
6
+ #
7
+ class Class # :nodoc:
8
+
9
+ def cattr_reader(*syms)
10
+ syms.flatten.each do |sym|
11
+ class_eval(<<-EOS, __FILE__, __LINE__)
12
+ unless defined? @@#{sym}
13
+ @@#{sym} = nil
14
+ end
15
+
16
+ def self.#{sym}
17
+ @@#{sym}
18
+ end
19
+
20
+ def #{sym}
21
+ @@#{sym}
22
+ end
23
+ EOS
24
+ end
25
+ end
26
+
27
+ def cattr_writer(*syms)
28
+ syms.flatten.each do |sym|
29
+ class_eval(<<-EOS, __FILE__, __LINE__)
30
+ unless defined? @@#{sym}
31
+ @@#{sym} = nil
32
+ end
33
+
34
+ def self.#{sym}=(obj)
35
+ @@#{sym} = obj
36
+ end
37
+
38
+ def #{sym}=(obj)
39
+ @@#{sym} = obj
40
+ end
41
+ EOS
42
+ end
43
+ end
44
+
45
+ def cattr_accessor(*syms)
46
+ cattr_reader(*syms)
47
+ cattr_writer(*syms)
48
+ end
49
+
50
+ end
@@ -0,0 +1,10 @@
1
+ #
2
+ # This was taken from ActiveSupport (see rubyonrails.org)
3
+ #
4
+ class Hash #:nodoc:
5
+ def assert_valid_keys(*valid_keys)
6
+ unknown_keys = keys - [valid_keys].flatten
7
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") \
8
+ unless unknown_keys.empty?
9
+ end
10
+ end
@@ -0,0 +1,109 @@
1
+ #--
2
+ # Active Directory Module for Ruby
3
+ #
4
+ # Copyright (c) 2005-2006 Justin Mecham
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #++
24
+
25
+ module ActiveDirectory
26
+
27
+ #
28
+ # Represents a Group object within an Active Directory instance.
29
+ #
30
+ class Group < Base
31
+
32
+ # Distinguished Name (DN)
33
+ attr_accessor :dn
34
+
35
+ # Description
36
+ attr_accessor :description
37
+
38
+ # Common Name (CN)
39
+ attr_accessor :name
40
+
41
+ # Users who are members of this Group
42
+ attr :members
43
+
44
+ #
45
+ # Attributes that we wish to pull from Active Directory for any Group that
46
+ # can be located within the directory.
47
+ #
48
+ ATTRIBUTES = ["distinguishedName", # DN of Group
49
+ "name", # Group Name
50
+ "member", # DN References to Members
51
+ "description"] #:nodoc: # Description
52
+
53
+ #
54
+ # Attempts to load a Group by a Distinguished Name (DN).
55
+ #
56
+ def initialize(identifier)
57
+
58
+ identifier = identifier.delete("\\")
59
+
60
+ load_by_dn(identifier)
61
+
62
+ end
63
+
64
+ #
65
+ # Proxy for loading and returning the members of this group.
66
+ #
67
+ def members #:nodoc:
68
+ members = Array.new
69
+ unless @members.nil?
70
+ for user_dn in @members
71
+ members << User.new(user_dn)
72
+ end
73
+ end
74
+ members
75
+ end
76
+
77
+ private
78
+
79
+ #
80
+ # Attempt to load a Group by its Distinguished Name (DN).
81
+ #
82
+ def load_by_dn(dn)
83
+
84
+ if dn.nil? or dn.length == 0
85
+ raise ArgumentError, "No distinguished name provided."
86
+ end
87
+
88
+ # De-escape the DN
89
+ dn = dn.gsub(/"/, "\\\"")
90
+
91
+ entries = Base.search(dn, LDAP::LDAP_SCOPE_BASE,
92
+ "(&(objectClass=group))", ATTRIBUTES)
93
+
94
+ raise UnknownGroupError if (entries.nil? or entries.length != 1)
95
+
96
+ parse_ldap_entry(entries[0])
97
+
98
+ end
99
+
100
+ def parse_ldap_entry(entry)
101
+ @dn = entry['distinguishedName'][0].delete("\\")
102
+ @name = entry['name'][0].delete("\\")
103
+ @description = entry['description'][0] unless entry['description'].nil?
104
+ @members = entry['member']
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,296 @@
1
+ #--
2
+ # Active Directory Module for Ruby
3
+ #
4
+ # Copyright (c) 2005-2006 Justin Mecham
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #++
24
+
25
+ module ActiveDirectory
26
+
27
+ #
28
+ # Represents a User object within an Active Directory instance.
29
+ #
30
+ class User < Base
31
+
32
+ # Distinguished Name (DN)
33
+ attr_reader :dn
34
+
35
+ # Display Name (e.g. "John Q. Public")
36
+ attr_reader :name
37
+
38
+ # Given Name (e.g. "John")
39
+ attr_reader :given_name
40
+
41
+ # Surname (e.g. "Public")
42
+ attr_reader :surname
43
+
44
+ # Primary E-Mail Address
45
+ attr_reader :email
46
+
47
+ # Account/Username (e.g. "jpublic")
48
+ attr_reader :username
49
+
50
+ # Job Title
51
+ attr_reader :title
52
+
53
+ # Company Name
54
+ attr_reader :company
55
+
56
+ # Department Name
57
+ attr_reader :department
58
+
59
+ # Primary Phone Number
60
+ attr_reader :main_number
61
+
62
+ # Mobile Number
63
+ attr_reader :mobile_number
64
+
65
+ # Street Address
66
+ attr_reader :street_address
67
+
68
+ # City / Town
69
+ attr_reader :city
70
+
71
+ # State/Province
72
+ attr_reader :state
73
+
74
+ # Zip/Postal Code
75
+ attr_reader :zip
76
+
77
+ # Country
78
+ attr_reader :country
79
+
80
+ # Direct Reports
81
+ attr :direct_reports
82
+
83
+ # Manager
84
+ attr :manager
85
+
86
+ # Groups this User is a member of
87
+ attr :groups
88
+
89
+ #
90
+ # Attributes that we wish to pull from Active Directory for any User that
91
+ # can be located within the directory.
92
+ #
93
+ ATTRIBUTES = ["displayName", # Name (e.g. "John Doe")
94
+ "givenName", # Given (First) Name
95
+ "sn", # Surname (Last)
96
+ "distinguishedName", # DN of User
97
+ "sAMAccountName", # Account Name
98
+ "mail", # Primary E-Mail Address
99
+ "manager", # DN Reference to Manager
100
+ "directReports", # DN References to Minions
101
+ "memberOf", # Group Membership
102
+ "company", # Company Name
103
+ "department", # Department Name
104
+ "title", # Title
105
+ "mobile", # Mobile Phone Number
106
+ "telephoneNumber", # Primary Phone Number
107
+ "streetAddress", # Street Address
108
+ "l", # City
109
+ "st", # State
110
+ "postalCode", # Zip Code
111
+ "co"] #:nodoc: # Country
112
+
113
+ #
114
+ # Attempts to load a User by a Distinguished Name (DN) or sAMAccountName.
115
+ #
116
+ def initialize(identifier)
117
+
118
+ if (identifier =~ /(CN|cn)=/) != nil
119
+ load_by_dn(identifier)
120
+ else
121
+ load_by_username(identifier)
122
+ end
123
+
124
+ end
125
+
126
+ #
127
+ # Attempts to authenticate the loaded user with the supplied password.
128
+ # Returns true if the authentication attempt was successful.
129
+ #
130
+ def authenticate(password)
131
+
132
+ # Clean up the password before we run it through our series of tests.
133
+ password.strip!
134
+
135
+ # If no password was specified, raise an exception. This check must
136
+ # occur to avoid a huge security hole if anonymous bind is on - if this
137
+ # check is not performed, someone can authenticate without providing a
138
+ # password when anonymous bind is turned on.
139
+ raise PasswordInvalid unless (!password.nil? and password.length > 0)
140
+
141
+ # Clone our shared connection for isolated use in determining the
142
+ # validity of our user's credentials.
143
+ auth_connection = Base.connection.clone
144
+
145
+ # Unbind the connection if it is already bound.
146
+ auth_connection.unbind if auth_connection.bound?
147
+
148
+ begin
149
+
150
+ # Attempt to bind to the connection as the currently loaded user with
151
+ # the supplied password.
152
+ auth_connection.bind("#{@username}@#{@@server_settings[:domain]}",
153
+ password)
154
+
155
+ return true
156
+
157
+ rescue LDAP::ResultError
158
+ if ($!.to_s == "Invalid credentials")
159
+ raise PasswordInvalid
160
+ else
161
+ raise
162
+ end
163
+ ensure
164
+ auth_connection.unbind
165
+ auth_connection = nil
166
+ end
167
+
168
+ return false
169
+
170
+ end
171
+
172
+ #
173
+ # Conveniently return the name of the User if the object is called
174
+ # directly.
175
+ #
176
+ def to_s #:nodoc:
177
+ @name
178
+ end
179
+
180
+ #
181
+ # Proxy for loading and returning this users' manager.
182
+ #
183
+ def manager #:nodoc:
184
+ @manager ||= User.new(@manager_dn) unless @manager_dn.nil?
185
+ end
186
+
187
+ #
188
+ # Proxy for loading and returning the group membership of this user.
189
+ #
190
+ def groups #:nodoc:
191
+ groups = Array.new
192
+ unless @groups.nil?
193
+ for group_dn in @groups
194
+ groups << Group.new(group_dn)
195
+ end
196
+ end
197
+ groups
198
+ end
199
+
200
+ #
201
+ # Proxy for loading and returning the users who report directly to this
202
+ # user.
203
+ #
204
+ def direct_reports #:nodoc:
205
+ direct_reports = Array.new
206
+ unless @direct_reports.nil?
207
+ for user_dn in @direct_reports
208
+ direct_reports << User.new(user_dn)
209
+ end
210
+ end
211
+ direct_reports
212
+ end
213
+
214
+ #
215
+ # Determines if the user is a member of the given group. Returns true if
216
+ # the user is in the passed group.
217
+ #
218
+ def member_of?(group)
219
+ @groups.include?(group.dn)
220
+ end
221
+
222
+ private
223
+
224
+ #
225
+ # Attempt to load a user by their username (sAMAccountName).
226
+ #
227
+ def load_by_username(username)
228
+
229
+ if username.nil? or username.length == 0
230
+ raise ArgumentError, "No username provided."
231
+ end
232
+
233
+ @username = username
234
+
235
+ # Set our filter to include only information about the requested user of
236
+ # class user.
237
+ filter = "(&(objectClass=user)(sAMAccountName=#{@username}))"
238
+
239
+ entries = Base.search(@@server_settings[:base_dn],
240
+ LDAP::LDAP_SCOPE_SUBTREE, filter, ATTRIBUTES)
241
+
242
+ raise UnknownUserError if (entries.nil? or entries.length != 1)
243
+
244
+ parse_ldap_entry(entries[0])
245
+
246
+ end
247
+
248
+ #
249
+ # Attempt to load a User by its Distinguished Name (DN).
250
+ #
251
+ def load_by_dn(dn)
252
+
253
+ if dn.nil? or dn.length == 0
254
+ raise ArgumentError, "No distinguished name provided."
255
+ end
256
+
257
+ # De-escape the DN
258
+ dn = dn.gsub(/"/, "\\\"")
259
+
260
+ # Set our filter to include only information about the requested user of
261
+ # class user.
262
+ filter = "(&(objectClass=user))"
263
+
264
+ entries = Base.search(dn, LDAP::LDAP_SCOPE_BASE, filter)
265
+
266
+ raise UnknownUserError if (entries.nil? or entries.length != 1)
267
+
268
+ parse_ldap_entry(entries[0])
269
+
270
+ end
271
+
272
+ def parse_ldap_entry(entry)
273
+ @dn = entry['distinguishedName'][0]
274
+ @username = entry['sAMAccountName'][0]
275
+ @name = entry['displayName'][0] unless entry['displayName'].nil?
276
+ @given_name = entry['givenName'][0] unless entry['givenName'].nil?
277
+ @surname = entry['sn'][0] unless entry['sn'].nil?
278
+ @email = entry['mail'][0] unless entry['mail'].nil?
279
+ @manager_dn = entry['manager'][0] unless entry['manager'].nil?
280
+ @company = entry['company'][0] unless entry['company'].nil?
281
+ @department = entry['department'][0] unless entry['department'].nil?
282
+ @title = entry['title'][0] unless entry['title'].nil?
283
+ @main_number = entry['telephoneNumber'][0] unless entry['telephoneNumber'].nil?
284
+ @mobile_number = entry['mobile'][0] unless entry['mobile'].nil?
285
+ @street_address = entry['streetAddress'][0] unless entry['streetAddress'].nil?
286
+ @city = entry['l'][0] unless entry['l'].nil?
287
+ @state = entry['st'][0] unless entry['st'].nil?
288
+ @zip = entry['postalCode'][0] unless entry['postalCode'].nil?
289
+ @country = entry['co'][0] unless entry['co'].nil?
290
+ @direct_reports = entry['directReports']
291
+ @groups = entry['memberOf']
292
+ end
293
+
294
+ end
295
+
296
+ end
@@ -0,0 +1,37 @@
1
+ #--
2
+ # Active Directory Module for Ruby
3
+ #
4
+ # Copyright (c) 2005-2006 Justin Mecham
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #++
24
+
25
+ module ActiveDirectory
26
+
27
+ module VERSION #:nodoc:
28
+
29
+ MAJOR = 0
30
+ MINOR = 9
31
+ TINY = 3
32
+
33
+ STRING = [MAJOR, MINOR, TINY].join('.')
34
+
35
+ end
36
+
37
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: activedirectory
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.9.3
7
+ date: 2006-05-11 00:00:00 -06:00
8
+ summary: Module for easy interaction with Active Directory servers.
9
+ require_paths:
10
+ - lib
11
+ email: justin@aspect.net
12
+ homepage:
13
+ rubyforge_project:
14
+ description: Makes it trivial to integrate with Active Directory servers for information and authentication concerning users and groups.
15
+ autorequire: active_directory
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Justin Mecham
30
+ files:
31
+ - Rakefile
32
+ - install.rb
33
+ - README
34
+ - LICENSE
35
+ - lib/active_directory
36
+ - lib/active_directory.rb
37
+ - lib/active_directory/base.rb
38
+ - lib/active_directory/ext
39
+ - lib/active_directory/group.rb
40
+ - lib/active_directory/user.rb
41
+ - lib/active_directory/version.rb
42
+ - lib/active_directory/ext/class.rb
43
+ - lib/active_directory/ext/hash.rb
44
+ test_files: []
45
+
46
+ rdoc_options: []
47
+
48
+ extra_rdoc_files: []
49
+
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ requirements:
55
+ - none
56
+ dependencies: []
57
+