activedirectory 0.9.3 → 1.0.0
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.
- data/lib/active_directory.rb +34 -12
- data/lib/active_directory/base.rb +398 -362
- data/lib/active_directory/computer.rb +38 -0
- data/lib/active_directory/container.rb +117 -0
- data/lib/active_directory/group.rb +156 -103
- data/lib/active_directory/member.rb +56 -0
- data/lib/active_directory/password.rb +42 -0
- data/lib/active_directory/rails/synchronizer.rb +234 -0
- data/lib/active_directory/rails/user.rb +137 -0
- data/lib/active_directory/timestamp.rb +46 -0
- data/lib/active_directory/user.rb +149 -290
- metadata +31 -24
- data/LICENSE +0 -24
- data/README +0 -139
- data/Rakefile +0 -76
- data/install.rb +0 -27
- data/lib/active_directory/ext/class.rb +0 -50
- data/lib/active_directory/ext/hash.rb +0 -10
- data/lib/active_directory/version.rb +0 -37
@@ -1,296 +1,155 @@
|
|
1
|
-
#--
|
2
|
-
# Active Directory Module for Ruby
|
1
|
+
#-- license
|
3
2
|
#
|
4
|
-
#
|
3
|
+
# This file is part of the Ruby Active Directory Project
|
4
|
+
# on the web at http://rubyforge.org/projects/activedirectory
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
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:
|
6
|
+
# Copyright (c) 2008, James Hunt <filefrog@gmail.com>
|
7
|
+
# based on original code by Justin Mecham
|
12
8
|
#
|
13
|
-
#
|
14
|
-
#
|
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.
|
15
13
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# FITNESS FOR A PARTICULAR PURPOSE
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
|
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
|
24
23
|
|
25
24
|
module ActiveDirectory
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
25
|
+
class User < Base
|
26
|
+
include Member
|
27
|
+
|
28
|
+
UAC_ACCOUNT_DISABLED = 0x0002
|
29
|
+
UAC_NORMAL_ACCOUNT = 0x0200 # 512
|
30
|
+
|
31
|
+
def self.filter # :nodoc:
|
32
|
+
Net::LDAP::Filter.eq(:objectClass,'user') & ~Net::LDAP::Filter.eq(:objectClass,'computer')
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.required_attributes #:nodoc:
|
36
|
+
{ :objectClass => ['top', 'organizationalPerson', 'person', 'user'] }
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Try to authenticate the current User against Active Directory
|
41
|
+
# using the supplied password. Returns false upon failure.
|
42
|
+
#
|
43
|
+
# Authenticate can fail for a variety of reasons, primarily:
|
44
|
+
#
|
45
|
+
# * The password is wrong
|
46
|
+
# * The account is locked
|
47
|
+
# * The account is disabled
|
48
|
+
#
|
49
|
+
# User#locked? and User#disabled? can be used to identify the
|
50
|
+
# latter two cases, and if the account is enabled and unlocked,
|
51
|
+
# Athe password is probably invalid.
|
52
|
+
#
|
53
|
+
def authenticate(password)
|
54
|
+
return false if password.to_s.empty?
|
55
|
+
|
56
|
+
auth_ldap = @@ldap.dup.bind_as(
|
57
|
+
:filter => "(sAMAccountName=#{sAMAccountName})",
|
58
|
+
:password => password
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Return the User's manager (another User object), depending on
|
64
|
+
# what is stored in the manager attribute.
|
65
|
+
#
|
66
|
+
# Returns nil if the schema does not include the manager attribute
|
67
|
+
# or if no manager has been configured.
|
68
|
+
#
|
69
|
+
def manager
|
70
|
+
return nil if @entry.manager.nil?
|
71
|
+
User.find_by_distinguishedName(@entry.manager.to_s)
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Returns an array of Group objects that this User belongs to.
|
76
|
+
# Only the immediate parent groups are returned, so if the user
|
77
|
+
# Sally is in a group called Sales, and Sales is in a group
|
78
|
+
# called Marketting, this method would only return the Sales group.
|
79
|
+
#
|
80
|
+
def groups
|
81
|
+
@groups ||= memberOf.collect { |dn| Group.find_by_distinguishedName(dn) }
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Returns an array of User objects that have this
|
86
|
+
# User as their manager.
|
87
|
+
#
|
88
|
+
def direct_reports
|
89
|
+
return [] if @entry.directReports.nil?
|
90
|
+
@direct_reports ||= @entry.directReports.collect { |dn| User.find_by_distinguishedName(dn) }
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Returns true if this account has been locked out
|
95
|
+
# (usually because of too many invalid authentication attempts).
|
96
|
+
#
|
97
|
+
# Locked accounts can be unlocked with the User#unlock! method.
|
98
|
+
#
|
99
|
+
def locked?
|
100
|
+
!lockoutTime.nil? && lockoutTime.to_i != 0
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Returns true if this account has been disabled.
|
105
|
+
#
|
106
|
+
def disabled?
|
107
|
+
userAccountControl.to_i & UAC_ACCOUNT_DISABLED != 0
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Returns true if the user should be able to log in with a correct
|
112
|
+
# password (essentially, their account is not disabled or locked
|
113
|
+
# out).
|
114
|
+
#
|
115
|
+
def can_login?
|
116
|
+
!disabled? && !locked?
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Change the password for this account.
|
121
|
+
#
|
122
|
+
# This operation requires that the bind user specified in
|
123
|
+
# Base.setup have heightened privileges. It also requires an
|
124
|
+
# SSL connection.
|
125
|
+
#
|
126
|
+
# If the force_change argument is passed as true, the password will
|
127
|
+
# be marked as 'expired', forcing the user to change it the next
|
128
|
+
# time they successfully log into the domain.
|
129
|
+
#
|
130
|
+
def change_password(new_password, force_change = false)
|
131
|
+
settings = @@settings.dup.merge {
|
132
|
+
:port => 636,
|
133
|
+
:encryption => { :method => :simple_tls }
|
134
|
+
}
|
135
|
+
|
136
|
+
ldap = Net::LDAP.new(settings)
|
137
|
+
ldap.modify(
|
138
|
+
:dn => distinguishedName,
|
139
|
+
:operations => [
|
140
|
+
[ :replace, :lockoutTime, [ '0' ] ],
|
141
|
+
[ :replace, :unicodePwd, [ Password.encode(new_password) ] ],
|
142
|
+
[ :replace, :userAccountControl, [ UAC_NORMAL_ACCOUNT.to_s ] ],
|
143
|
+
[ :replace, :pwdLastSet, [ (force_change ? '0' : '-1') ] ]
|
144
|
+
]
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Unlocks this account.
|
150
|
+
#
|
151
|
+
def unlock!
|
152
|
+
@@ldap.replace_attribute(distinguishedName, :lockoutTime, ['0'])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
metadata
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.
|
2
|
+
rubygems_version: 0.9.4
|
3
3
|
specification_version: 1
|
4
4
|
name: activedirectory
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date:
|
8
|
-
summary:
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2008-08-02 00:00:00 -05:00
|
8
|
+
summary: An interface library for accessing Microsoft's Active Directory.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
|
-
email:
|
12
|
-
homepage:
|
13
|
-
rubyforge_project:
|
14
|
-
description:
|
15
|
-
autorequire:
|
11
|
+
email: filefrog@gmail.com
|
12
|
+
homepage: http://rubyforge.net/projects/activedirectory
|
13
|
+
rubyforge_project: activedirectory
|
14
|
+
description: ActiveDirectory uses Net::LDAP to provide a means of accessing and modifying an Active Directory data store.
|
15
|
+
autorequire:
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
18
18
|
has_rdoc: true
|
@@ -25,22 +25,21 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
|
|
25
25
|
platform: ruby
|
26
26
|
signing_key:
|
27
27
|
cert_chain:
|
28
|
+
post_install_message:
|
28
29
|
authors:
|
29
|
-
-
|
30
|
+
- James R. Hunt
|
30
31
|
files:
|
31
|
-
-
|
32
|
-
-
|
33
|
-
-
|
34
|
-
-
|
35
|
-
- lib/active_directory
|
36
|
-
- lib/active_directory.rb
|
32
|
+
- lib/active_directory/container.rb
|
33
|
+
- lib/active_directory/timestamp.rb
|
34
|
+
- lib/active_directory/user.rb
|
35
|
+
- lib/active_directory/computer.rb
|
36
|
+
- lib/active_directory/password.rb
|
37
|
+
- lib/active_directory/member.rb
|
37
38
|
- lib/active_directory/base.rb
|
38
|
-
- lib/active_directory/
|
39
|
+
- lib/active_directory/rails/user.rb
|
40
|
+
- lib/active_directory/rails/synchronizer.rb
|
39
41
|
- lib/active_directory/group.rb
|
40
|
-
- lib/active_directory
|
41
|
-
- lib/active_directory/version.rb
|
42
|
-
- lib/active_directory/ext/class.rb
|
43
|
-
- lib/active_directory/ext/hash.rb
|
42
|
+
- lib/active_directory.rb
|
44
43
|
test_files: []
|
45
44
|
|
46
45
|
rdoc_options: []
|
@@ -51,7 +50,15 @@ executables: []
|
|
51
50
|
|
52
51
|
extensions: []
|
53
52
|
|
54
|
-
requirements:
|
55
|
-
- none
|
56
|
-
dependencies: []
|
53
|
+
requirements: []
|
57
54
|
|
55
|
+
dependencies:
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: ruby-net-ldap
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.0.4
|
64
|
+
version:
|