datacom_active_directory 1.5.5.datacom
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/README.md +45 -0
- data/Rakefile +20 -0
- data/active_directory.gemspec +22 -0
- data/lib/active_directory.rb +95 -0
- data/lib/active_directory/base.rb +587 -0
- data/lib/active_directory/computer.rb +35 -0
- data/lib/active_directory/container.rb +114 -0
- data/lib/active_directory/field_type/binary.rb +39 -0
- data/lib/active_directory/field_type/date.rb +39 -0
- data/lib/active_directory/field_type/dn_array.rb +40 -0
- data/lib/active_directory/field_type/group_dn_array.rb +40 -0
- data/lib/active_directory/field_type/member_dn_array.rb +47 -0
- data/lib/active_directory/field_type/password.rb +41 -0
- data/lib/active_directory/field_type/timestamp.rb +45 -0
- data/lib/active_directory/field_type/user_dn_array.rb +40 -0
- data/lib/active_directory/group.rb +138 -0
- data/lib/active_directory/member.rb +53 -0
- data/lib/active_directory/user.rb +167 -0
- data/lib/active_directory/version.rb +3 -0
- metadata +79 -0
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
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
|