radum 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/lib/radum.rb +10 -0
- data/lib/radum/ad.rb +3355 -0
- data/lib/radum/container.rb +367 -0
- data/lib/radum/group.rb +455 -0
- data/lib/radum/logger.rb +67 -0
- data/lib/radum/user.rb +1087 -0
- data/test/tc_ad.rb +220 -0
- data/test/tc_container.rb +205 -0
- data/test/tc_group.rb +161 -0
- data/test/tc_unix_user.rb +98 -0
- data/test/tc_user.rb +175 -0
- metadata +91 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright 2009 Shaun Rowland. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification,
|
4
|
+
are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
1. Redistributions of source code must retain the above copyright notice,
|
7
|
+
this list of conditions and the following disclaimer.
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
9
|
+
this list of conditions and the following disclaimer in the documentation
|
10
|
+
and/or other materials provided with the distribution.
|
11
|
+
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
13
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
14
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
15
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
16
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
17
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
18
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
19
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
20
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
21
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
|
+
|
data/lib/radum.rb
ADDED
data/lib/radum/ad.rb
ADDED
@@ -0,0 +1,3355 @@
|
|
1
|
+
# The RADUM module provides an interface to Microsoft Active Directory for
|
2
|
+
# working with users and groups. The User class represents a standard Windows
|
3
|
+
# user account. The UNIXUser class represents a Windows account that has UNIX
|
4
|
+
# attributes. Similarly, the Group class represents a standard Windows group,
|
5
|
+
# and a UNIXGroup represents a Windows group that has UNIX attributes. UNIX
|
6
|
+
# attributes are supported if Active Directory has been extended, such as
|
7
|
+
# when Microsoft Identity Management for UNIX has been installed. LDAP
|
8
|
+
# extensions for UNIX are not required if only Windows users and groups are
|
9
|
+
# operated on. This module concentrates only on users and groups at this time.
|
10
|
+
#
|
11
|
+
# This is a pure Ruby implementation. Windows command line tools are not
|
12
|
+
# used in any way, so this will work from other platforms such as Mac OS X
|
13
|
+
# and Linux in addition to Windows. RADUM does require the Active Directory
|
14
|
+
# server to support SSL because that is a requirement for creating user
|
15
|
+
# accounts through LDAP. This means that a domain must have a certificate
|
16
|
+
# server. Using a self-signed certificate should be fine.
|
17
|
+
#
|
18
|
+
# RADUM considers its view of user and group attributes to be authoritative,
|
19
|
+
# with the exception that it will not modify the members of a group that it
|
20
|
+
# is not already aware of. The general RADUM pattern is:
|
21
|
+
#
|
22
|
+
# * AD.new(...)
|
23
|
+
# * Container.new(...) [for any Containers of interest]
|
24
|
+
# * AD#load()
|
25
|
+
# * Create, update, or remove existing loaded objects.
|
26
|
+
# * AD#sync()
|
27
|
+
#
|
28
|
+
# See the class documenation for more details, especially the AD#load and
|
29
|
+
# AD#sync methods.
|
30
|
+
#
|
31
|
+
# = AD
|
32
|
+
#
|
33
|
+
# The AD class represents an Active Directory. An AD object can be created
|
34
|
+
# with something like the following code:
|
35
|
+
#
|
36
|
+
# ad = RADUM::AD.new :root => 'dc=example,dc=com',
|
37
|
+
# :user => 'cn=Administrator,cn=Users',
|
38
|
+
# :password => 'password',
|
39
|
+
# :server => '192.168.1.1'
|
40
|
+
#
|
41
|
+
# The AD class will connect to Active Directory when it needs to do so
|
42
|
+
# automatically. A connection will not be made until required. Some arguments
|
43
|
+
# in RADUM are specified using a relative path sans the :root argument to
|
44
|
+
# the AD.new method, such as the :user argument and Container names discussed
|
45
|
+
# next. The AD class provides a read-only ldap attriubte that allows direct
|
46
|
+
# interaction with Active Directory if needed. The AD object automatically
|
47
|
+
# adds the "cn=Users" Container object because most users have "Domain Users"
|
48
|
+
# as their primary Windows group. The primary Windows group is necessary for
|
49
|
+
# creating User and UNIXUser objects, especially when AD#load is called. Note
|
50
|
+
# that the :root and :user arguments can use lowercase or uppercase for the
|
51
|
+
# "dc=", "cn=", or "ou=" path elements.
|
52
|
+
#
|
53
|
+
# = Container
|
54
|
+
#
|
55
|
+
# Container objects are added to the AD object before AD#load is called in
|
56
|
+
# most cases. Container objects can be created with something like the
|
57
|
+
# following code:
|
58
|
+
#
|
59
|
+
# people = RADUM::Container.new :name => 'ou=People',
|
60
|
+
# :directory => ad
|
61
|
+
#
|
62
|
+
# It is possible to have nested Container objects as well, such as:
|
63
|
+
#
|
64
|
+
# staff = RADUM::Container.new :name => 'ou=Staff,ou=People',
|
65
|
+
# :directory => ad
|
66
|
+
# faculty = RADUM::Container.new :name => 'ou=Faculty,ou=People',
|
67
|
+
# :directory => ad
|
68
|
+
# grads = RADUM::Container.new :name => 'ou=Grads,ou=People',
|
69
|
+
# :directory => ad
|
70
|
+
#
|
71
|
+
# Container objects do not have a direct reference to Container objects they
|
72
|
+
# logically contain, but the RADUM code handles these cases when creating and
|
73
|
+
# removing Container objects from Active Directory. Container objects do have
|
74
|
+
# references to all users and groups they contain as well as the AD object
|
75
|
+
# they belong to.
|
76
|
+
#
|
77
|
+
# The :name of a Container object is the only other case where a relative path
|
78
|
+
# is used. The :name should be the directory path (the distinguished name) sans
|
79
|
+
# the :root path given to the AD object that owns the Container. Container
|
80
|
+
# objects can be Active Directory organizational units or containers. Note that
|
81
|
+
# Active Directory containers cannot contain organizational units, but the
|
82
|
+
# reverse is true, therefore the following :name would be invalid:
|
83
|
+
#
|
84
|
+
# invalid = RADUM::Container.new :name => 'ou=Staff,cn=People',
|
85
|
+
# :directory => ad
|
86
|
+
#
|
87
|
+
# but the following :name is valid:
|
88
|
+
#
|
89
|
+
# valid = RADUM::Container.new :name => 'cn=Staff,ou=People',
|
90
|
+
# :directory => ad
|
91
|
+
#
|
92
|
+
# In general, one should use Active Directory organizational units instead of
|
93
|
+
# containers. They are much more useful. The :name argument can use lowercase
|
94
|
+
# or uppercase for the "cn=" and "ou=" path elements.
|
95
|
+
#
|
96
|
+
# Container objects should be added to the AD object before doing serious
|
97
|
+
# work in the general case for the best results. This doesn't necessarily have
|
98
|
+
# to be done if only creating new objects, but it is still a good idea that
|
99
|
+
# won't hurt anyway. The "cn=Users" container is added by default to an AD
|
100
|
+
# object when it is created to help make things easier as it is usually
|
101
|
+
# required, and if it really is not needed, it should not take too many
|
102
|
+
# resources.
|
103
|
+
#
|
104
|
+
# == Removing Container Objects
|
105
|
+
#
|
106
|
+
# Removing a Container causes that Container to be removed from Active Directory
|
107
|
+
# when AD#sync is called, but only if that is possible. Container objects
|
108
|
+
# cannot be removed if they have groups that cannot be removed nor if they
|
109
|
+
# logically contain other Container objects. When calling the
|
110
|
+
# AD#remove_container method, checks are made for all the objects RADUM knows
|
111
|
+
# about. When calling AD#sync checks are made in Active Directory. See the
|
112
|
+
# AD#remove_container method documentation for more details. Note that removing
|
113
|
+
# a Container will always remove all User or UNIXUser objects it contains
|
114
|
+
# regardless, and when AD#sync is called, all possible objects that can be
|
115
|
+
# removed will be due to the implementation of AD#remove_container.
|
116
|
+
#
|
117
|
+
# It is also possible to destroy a Container. Unlike with users and groups,
|
118
|
+
# destroying a Container with AD#destroy_container simply removes the reference
|
119
|
+
# to the Container in the AD object. It does not result in the Container
|
120
|
+
# objects contents from being removed. The Container simply will not be
|
121
|
+
# processed in AD#load and AD#sync calls. The AD object determines all user
|
122
|
+
# and group references based on the Container objects it owns.
|
123
|
+
#
|
124
|
+
# = Loading the AD Object
|
125
|
+
#
|
126
|
+
# Once the Container objects have been added to the AD object, the AD object
|
127
|
+
# can be loaded. Calling AD#load is straightforward:
|
128
|
+
#
|
129
|
+
# ad.load
|
130
|
+
#
|
131
|
+
# This will create User, UNIXUser, Group, and UNIXGroup objects to represent
|
132
|
+
# all such objects in the Container objects added to the AD object. The AD#load
|
133
|
+
# method determines if a user or group is a Windows or UNIX type based on the
|
134
|
+
# object's LDAP attributes. AD#load can be called multiple times, but it will
|
135
|
+
# ignore loading any user or group objects that already exist in the RADUM
|
136
|
+
# environment.
|
137
|
+
#
|
138
|
+
# = Working with Users and Groups
|
139
|
+
#
|
140
|
+
# User and group object creation results in the users and groups being added to
|
141
|
+
# their Container object automatically. Users and groups also have direct
|
142
|
+
# references to their corresponding collections of groups for users and
|
143
|
+
# groups plus users for groups (since groups can contain other groups as
|
144
|
+
# members). User memberships in groups can be accomplished from either
|
145
|
+
# perspective due to this automatic handling:
|
146
|
+
#
|
147
|
+
# * Users can be added to groups through User#add_group.
|
148
|
+
# * Users can be added to groups through Group#add_user.
|
149
|
+
#
|
150
|
+
# Group membership in groups has to be handled by using the Group#add_group
|
151
|
+
# method only, which adds the Group or UNIXGroup used as the argument as a
|
152
|
+
# member of the Group object to which the method belongs.
|
153
|
+
#
|
154
|
+
# RADUM ensures that duplicate users and groups cannot be created, including
|
155
|
+
# specific attributes of those objects which must be unique. Trying to do
|
156
|
+
# something that would result in duplication generally raises a RuntimeError.
|
157
|
+
#
|
158
|
+
# RADUM handles implicit group memberships from the Windows perspective for
|
159
|
+
# UNIXUser objects when adding them to UNIXGroup objects. When adding a
|
160
|
+
# UNIXUser to a UNIXGroup, the UNIXUser gets a membership in the UNIXGroup
|
161
|
+
# from both the UNIX and Windows perspectives.
|
162
|
+
#
|
163
|
+
# == Creating Group and UNIXGroup Objects
|
164
|
+
#
|
165
|
+
# Group objects represent Windows groups and UNIXGroup objects represent UNIX
|
166
|
+
# groups, which are Windows groups that have UNIX attributes. Examples include:
|
167
|
+
#
|
168
|
+
# research_users = RADUM::Group.new :name => 'Research Users',
|
169
|
+
# :container => faculty
|
170
|
+
# unix_staff = RADUM::UNIXGroup.new :name => 'staff',
|
171
|
+
# :container => staff,
|
172
|
+
# :gid => 1005
|
173
|
+
#
|
174
|
+
# There are additional argumements in both cases. See Group.new and
|
175
|
+
# UNIXGroup.new for additional details. In both cases, the :rid argument should
|
176
|
+
# only be used by AD#load to specify the group object's relative identifier,
|
177
|
+
# which is based on the LDAP objectSid attribute. There is an :nis_domain
|
178
|
+
# argument for UNIXGroup objects that defaults to "radum" if not specified.
|
179
|
+
# NIS is not required for using Active Directory in a centralized authentication
|
180
|
+
# design. One could access the UNIX attributes through LDAP directly for
|
181
|
+
# example. However, the NIS domain needs to be present if one wants to use
|
182
|
+
# the Active Directory Users and Computers GUI tool, therefore one is specified
|
183
|
+
# by default. Of course, this should still be set correctly in order to work
|
184
|
+
# in the Active Directory Users and Computers GUI tool, but it can be changed
|
185
|
+
# there easily and the attributes should still show up correctly. The :type
|
186
|
+
# argument applies to both Group and UNIXGroup objects. The default value is
|
187
|
+
# RADUM::GROUP_GLOBAL_SECURITY, which is the default when creating group
|
188
|
+
# objects using the Active Directory Users and Computers GUI tool in Windows.
|
189
|
+
# More details about the restrictions for certain group types can be found in
|
190
|
+
# the User#primary_group= method documentation.
|
191
|
+
#
|
192
|
+
# There is a useful method for finding the next available GID for the UNIXGroup
|
193
|
+
# object's :gid argument:
|
194
|
+
#
|
195
|
+
# gid = ad.load_next_gid
|
196
|
+
#
|
197
|
+
# The AD#load_next_gid method searches Active Directory and the current RADUM
|
198
|
+
# objects for the next GID value that can be used. The :gid attribute for a
|
199
|
+
# UNIXGroup also becomes a UNIXUser object's GID value, depending on the
|
200
|
+
# :unix_main_group for that UNIXUser.
|
201
|
+
#
|
202
|
+
# == Removing Group and UNIXGroup Objects
|
203
|
+
#
|
204
|
+
# Group and UNIXGroup objects can be removed from RADUM by using the following
|
205
|
+
# method for the Container object that they belong to:
|
206
|
+
#
|
207
|
+
# faculty.remove_group research_users
|
208
|
+
#
|
209
|
+
# or by searching for a group:
|
210
|
+
#
|
211
|
+
# faculty.remove_group ad.find_group_by_name('Research Users')
|
212
|
+
#
|
213
|
+
# Removing a group causes that group to be removed from Active Directory when
|
214
|
+
# AD#sync is called, but only if that is possible. Group and UNIXGroup objects
|
215
|
+
# cannot be removed if they are the primary Windows group of any user in
|
216
|
+
# Active Directory nor if they are the UNIX main group for any UNIXUser objects
|
217
|
+
# in Active Directory. When calling the Container#remove_group method, checks
|
218
|
+
# are made for all the objects RADUM knows about. When calling AD#sync checks
|
219
|
+
# are made for all users in Active Directory.
|
220
|
+
#
|
221
|
+
# It is also possible to destroy a Group or UNIXGroup object. Destroying a
|
222
|
+
# Group or UNIXGroup does not remove the object from Active Directory. Instead
|
223
|
+
# it removes all references to the object in RADUM itself. The same checks are
|
224
|
+
# made against all objects RADUM knows about before destroying the object can
|
225
|
+
# succeed. See the Container#destroy_group method documentation for more
|
226
|
+
# details.
|
227
|
+
#
|
228
|
+
# In both cases, any external references ot the orginal object should be
|
229
|
+
# discarded.
|
230
|
+
#
|
231
|
+
# == Converting Group and UNIXGroup Objects
|
232
|
+
#
|
233
|
+
# It is possible to convert Group objects to UNIXGroup objects and vice versa
|
234
|
+
# under certain conditions. Group objects can converted to UNIXGroup objects
|
235
|
+
# if the Group is not the primary Windows group for a user RADUM knows about.
|
236
|
+
# This is not really a strict requirement for users in Active Directory, but
|
237
|
+
# it is a limitation of the current RADUM implementation. UNIXGroup objects
|
238
|
+
# can be converted to Group objects with the same implementation restriction
|
239
|
+
# with the additional restriction that the UNIXGroup cannot be the UNIX main
|
240
|
+
# group for any user in Active Directory. Conversion causes this check to be
|
241
|
+
# made outside of the AD#load and AD#sync methods immediately before proceeding.
|
242
|
+
# If the conversion is successful, a new object of the desired type is created
|
243
|
+
# and placed into the RADUM system in place of the original object. Any external
|
244
|
+
# references to the orginal object should be discarded as they will be
|
245
|
+
# treated as if they were removed objects, even though they are not removed
|
246
|
+
# from Active Directory. Note that UNIX attributes are removed from UNIXGroup
|
247
|
+
# objects in Active Directory immediately before AD#sync when converted to
|
248
|
+
# Group objects.
|
249
|
+
#
|
250
|
+
# == Creating User and UNIXUser Objects
|
251
|
+
#
|
252
|
+
# User objects represent Windows users and UNIXUser objects represent UNIX
|
253
|
+
# users, which are Windows users that have UNIX attributes. Examples include:
|
254
|
+
#
|
255
|
+
# RADUM::User.new :username => 'martin',
|
256
|
+
# :container => faculty,
|
257
|
+
# :primary_group => ad.find_group_by_name('Domain Users')
|
258
|
+
# RADUM::UNIXUser.new :username => 'rowland',
|
259
|
+
# :container => staff,
|
260
|
+
# :primary_group => ad.find_group_by_name('Domain Users'),
|
261
|
+
# :uid => 5437,
|
262
|
+
# :unix_main_group => unix_staff,
|
263
|
+
# :shell => '/bin/bash',
|
264
|
+
# :home_directory => '/home/rowland'
|
265
|
+
#
|
266
|
+
# There are additional argumements in both cases. See User.new and
|
267
|
+
# UNIXUser.new for additional details. In both cases, the :rid argument should
|
268
|
+
# only be used by AD#load to specify the user object's relative identifier,
|
269
|
+
# which is based on the LDAP objectSid attribute. There is an :nis_domain
|
270
|
+
# argument for UNIXUser objects that defaults to "radum" if not specified.
|
271
|
+
# NIS is not required for using Active Directory in a centralized authentication
|
272
|
+
# design. One could access the UNIX attributes through LDAP directly for
|
273
|
+
# example. However, the NIS domain needs to be present if one wants to use
|
274
|
+
# the Active Directory Users and Computers GUI tool, therefore one is specified
|
275
|
+
# by default. Of course, this should still be set correctly in order to work
|
276
|
+
# in the Active Directory Users and Computers GUI tool, but it can be changed
|
277
|
+
# there easily and the attributes should still show up correctly. The :disabled
|
278
|
+
# argument applies to both User and UNIXUser objects. The default value is
|
279
|
+
# false. The password attribute for User and UNIXUser objects is nil unless
|
280
|
+
# set, and when set it causes the user in Active Directory to have that
|
281
|
+
# password if it meets the Group Policy password requirements. Once set through
|
282
|
+
# AD#sync, the password attribute is set to nil again. If there is no password
|
283
|
+
# set when the user is created in Active Directory, a random password that
|
284
|
+
# probably meets the Group Policy password requirements will be generated, and
|
285
|
+
# the user will not be forced to change that password unless
|
286
|
+
# User#force_change_password is also called before calling AD#sync. The
|
287
|
+
# GID value for a UNIXUser comes from its :unix_main_group argument UNIXGroup
|
288
|
+
# value. There are many attributes that can be set for User and UNIXUser
|
289
|
+
# objects. See the User and UNIXUser class documentation for more details.
|
290
|
+
#
|
291
|
+
# There is a useful method for finding the next available UID for the UNIXUser
|
292
|
+
# object's :uid argument:
|
293
|
+
#
|
294
|
+
# uid = ad.load_next_uid
|
295
|
+
#
|
296
|
+
# The AD#load_next_uid method searches Active Directory and the current RADUM
|
297
|
+
# objects for the next UID value that can be used.
|
298
|
+
#
|
299
|
+
# == Removing User and UNIXUser Objects
|
300
|
+
#
|
301
|
+
# User and UNIXUser objects can be removed from RADUM by using the following
|
302
|
+
# method for the Container object that they belong to:
|
303
|
+
#
|
304
|
+
# faculty.remove_user ad.find_user_by_username('martin')
|
305
|
+
#
|
306
|
+
# or by specifying a reference to a User or UNIXUser object directly.
|
307
|
+
#
|
308
|
+
# Removing a user causes that user to be removed from Active Directory when
|
309
|
+
# AD#sync is called.
|
310
|
+
#
|
311
|
+
# It is also possible to destroy a User or UNIXUser object. Destroying a
|
312
|
+
# User or UNIXUser does not remove the object from Active Directory. Instead
|
313
|
+
# it removes all references to the object in RADUM itself.
|
314
|
+
#
|
315
|
+
# In both cases, any external references ot the orginal object should be
|
316
|
+
# discarded.
|
317
|
+
#
|
318
|
+
# == Converting User and UNIXUser Objects
|
319
|
+
#
|
320
|
+
# It is possible to convert User objects to UNIXUser objects and vice versa.
|
321
|
+
# If the conversion is successful, a new object of the desired type is created
|
322
|
+
# and placed into the RADUM system in place of the original object. Any external
|
323
|
+
# references to the orginal object should be discarded as they will be
|
324
|
+
# treated as if they were removed objects, even though they are not removed
|
325
|
+
# from Active Directory. Note that UNIX attributes are removed from UNIXUser
|
326
|
+
# objects in Active Directory immediately before AD#sync when converted to
|
327
|
+
# User objects.
|
328
|
+
#
|
329
|
+
# = Synchronizing to Active Directory
|
330
|
+
#
|
331
|
+
# After making any changes, synchronizing those changes to Active Directory can
|
332
|
+
# be accomplished with the following:
|
333
|
+
#
|
334
|
+
# ad.sync
|
335
|
+
#
|
336
|
+
# This method can be called whenever changes are made. Changes in RADUM are
|
337
|
+
# considered authoritative and Active Directory objects are updated to reflect
|
338
|
+
# their attributes as RADUM sees the world. This is true for group memberships
|
339
|
+
# except for members that RADUM does not know about. This means that AD#sync
|
340
|
+
# will not cause users and groups it does not know about to be removed from
|
341
|
+
# a Group or UNIXGroup it does know about, but it will remove users and groups
|
342
|
+
# that have been explicitly removed in the RADUM system.
|
343
|
+
#
|
344
|
+
# = Logging
|
345
|
+
#
|
346
|
+
# The RADUM module instantiates an object of the Logger class. This can be used
|
347
|
+
# to log operational progress:
|
348
|
+
#
|
349
|
+
# RADUM::logger.log("\nInitializing...\n\n", RADUM::LOG_DEBUG)
|
350
|
+
#
|
351
|
+
# The RADUM::LOG_DEBUG level includes a lot of verbose progress information
|
352
|
+
# that can be highly useful. The log level argument is optional and defaults
|
353
|
+
# to RADUM::LOG_NORMAL. See the Logger class documentation for more details.
|
354
|
+
# Note that log output can also be sent to a file.
|
355
|
+
#
|
356
|
+
# = Windows Server Versions
|
357
|
+
#
|
358
|
+
# RADUM has been exclusively tested against Windows Server 2008 and Windows
|
359
|
+
# Server 2003 R2 Standard Edition SP2. The testing system had Microsoft
|
360
|
+
# Identity Management for UNIX installed and had the Certificate Services Role
|
361
|
+
# added, which is added using Add/Remove Programs in Windows Server 2003.
|
362
|
+
# Microsoft Identity Management for UNIX is generally required for editing
|
363
|
+
# UNIXUser and UNIXGroup objects in the Active Directory Users and Computers
|
364
|
+
# GUI tool if desired, and the Certificate Services Role is required in the
|
365
|
+
# domain because SSL is required for creating User and UNIXUser objects using
|
366
|
+
# LDAP. The LDAP attributes required for UNIXUser objects don't necessarily
|
367
|
+
# require Microsoft Identity Management for UNIX to be installed.
|
368
|
+
#
|
369
|
+
# RADUM works with a domain functional level of Windows 2003 Server or higher.
|
370
|
+
# Most of RADUM will work with a domain functional level of Windows 2000 native
|
371
|
+
# except for Universal group types and groups being members of groups. If those
|
372
|
+
# features are not needed, the Windows 2000 native domain functional level can
|
373
|
+
# be used, but RADUM assumes these features are possible, so one would need to
|
374
|
+
# ensure they do not use them if working at a domain functional level lower
|
375
|
+
# than Windows Server 2003. RADUM was not tested at a domain functional level
|
376
|
+
# lower than Windows 2000 native.
|
377
|
+
#
|
378
|
+
# Author:: Shaun Rowland <mailto:rowand@shaunrowland.com>
|
379
|
+
# Copyright:: Copyright 2009 Shaun Rowland. All rights reserved.
|
380
|
+
# License:: BSD License included in the project LICENSE file.
|
381
|
+
|
382
|
+
module RADUM
|
383
|
+
# Group type constants.
|
384
|
+
#
|
385
|
+
# These are the Fixnum representations of what should be Bignum objects in
|
386
|
+
# some cases as far as I am aware. In the Active Directory Users and Groups
|
387
|
+
# GUI tool, they are shown as hexidecimal values, indicating they should be
|
388
|
+
# Bignums (well, some of them). However, if you try to edit the values in
|
389
|
+
# that tool (with advanced attribute editing enabled or with the ADSI Edit
|
390
|
+
# tool) these show up as the Fixnum values here. We are going to stick with
|
391
|
+
# that, even though it is lame. I could not pull these out as Bignum objects.
|
392
|
+
# Some of these are small enough to be Fixnums though, so I left them as their
|
393
|
+
# hexidecimal values. These values correspond to the LDAP groupType attribute
|
394
|
+
# for group objects.
|
395
|
+
GROUP_DOMAIN_LOCAL_SECURITY = -2147483644
|
396
|
+
GROUP_DOMAIN_LOCAL_DISTRIBUTION = 0x4
|
397
|
+
GROUP_GLOBAL_SECURITY = -2147483646
|
398
|
+
GROUP_GLOBAL_DISTRIBUTION = 0x2
|
399
|
+
GROUP_UNIVERSAL_SECURITY = -2147483640
|
400
|
+
GROUP_UNIVERSAL_DISTRIBUTION = 0x8
|
401
|
+
|
402
|
+
# Some useful constants from lmaccess.h for use with creating user accounts.
|
403
|
+
UF_ACCOUNTDISABLE = 0x0002;
|
404
|
+
UF_PASSWD_NOTREQD = 0x0020;
|
405
|
+
UF_PASSWD_CANT_CHANGE = 0x0040;
|
406
|
+
UF_NORMAL_ACCOUNT = 0x0200;
|
407
|
+
# I don't think these are working - I am sure the last one doesn't work.
|
408
|
+
#UF_DONT_EXPIRE_PASSWD = 0x10000;
|
409
|
+
#UF_PASSWORD_EXPIRED = 0x800000;
|
410
|
+
|
411
|
+
# This is a convenience method to return a String representation of a
|
412
|
+
# Group or UNIXGroup object's type attribute, which has the value of one of
|
413
|
+
# the RADUM group type constants.
|
414
|
+
def RADUM.group_type_to_s(type) # :nodoc:
|
415
|
+
case type
|
416
|
+
when GROUP_DOMAIN_LOCAL_SECURITY
|
417
|
+
"GROUP_DOMAIN_LOCAL_SECURITY"
|
418
|
+
when GROUP_DOMAIN_LOCAL_DISTRIBUTION
|
419
|
+
"GROUP_DOMAIN_LOCAL_DISTRIBUTION"
|
420
|
+
when GROUP_GLOBAL_SECURITY
|
421
|
+
"GROUP_GLOBAL_SECURITY"
|
422
|
+
when GROUP_GLOBAL_DISTRIBUTION
|
423
|
+
"GROUP_GLOBAL_DISTRIBUTION"
|
424
|
+
when GROUP_UNIVERSAL_SECURITY
|
425
|
+
"GROUP_UNIVERSAL_SECURITY"
|
426
|
+
when GROUP_UNIVERSAL_DISTRIBUTION
|
427
|
+
"GROUP_UNIVERSAL_DISTRIBUTION"
|
428
|
+
else "UNKNOWN"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# The AD class represents the Active Directory. All opeartions that involve
|
433
|
+
# communication between the Container, User, UNIXUser, Group, and UNIXGroup
|
434
|
+
# objects and Active Directory are handled by the AD object. The AD object
|
435
|
+
# should be the first object created, generally followed by Container objects.
|
436
|
+
# The Container object requires an AD object. All other objects require a
|
437
|
+
# Container object.
|
438
|
+
class AD
|
439
|
+
# A handle the the Net::LDAP object used for this AD.
|
440
|
+
attr_reader :ldap
|
441
|
+
# The root of the Active Directory. This is a String representing an LDAP
|
442
|
+
# path, such as "dc=example,dc=com".
|
443
|
+
attr_reader :root
|
444
|
+
# The domain name of the Active Directory. This is calculated from the root
|
445
|
+
# attribute. This is automatically made lowercase.
|
446
|
+
attr_reader :domain
|
447
|
+
# The Active Directory user used to connect to the Active Directory. This
|
448
|
+
# is specified using an LDAP path to the user account, without the root
|
449
|
+
# component, such as "cn=Administrator,cn=Users". This defaults to
|
450
|
+
# "cn=Administrator,cn=Users" when an AD is created using AD.new.
|
451
|
+
attr_reader :user
|
452
|
+
# The server hostname or IP address of the Active Directory server. This
|
453
|
+
# defaults to "localhost" when an AD is created using AD.new.
|
454
|
+
attr_reader :server
|
455
|
+
# The minimum UID value to use if no other UIDs are found. This defaults to
|
456
|
+
# 1000.
|
457
|
+
attr_accessor :min_uid
|
458
|
+
# The array of UID values from UNIXUser objects in the AD object. This is
|
459
|
+
# automatically managed by the other objects and should not be modified
|
460
|
+
# directly.
|
461
|
+
attr_accessor :uids
|
462
|
+
# The minimum GID value to use if no other GIDs are found. This defaults to
|
463
|
+
# 1000.
|
464
|
+
attr_accessor :min_gid
|
465
|
+
# The array of GID values from UNIXGroup objects in the AD object. This is
|
466
|
+
# automatically managed by the other objects and should not be modified
|
467
|
+
# directly.
|
468
|
+
attr_accessor :gids
|
469
|
+
# The array of RID values for User, UNIXUser, Group, and UNIXGroup objects
|
470
|
+
# in the AD object. This is automatically managed by the other objects and
|
471
|
+
# should not be modified directly.
|
472
|
+
attr_accessor :rids
|
473
|
+
# The array of Containers in the AD object.
|
474
|
+
attr_reader :containers
|
475
|
+
# The array of Containers set for removal in the AD object.
|
476
|
+
attr_reader :removed_containers
|
477
|
+
|
478
|
+
# Create a new AD object that represents an Active Directory environment.
|
479
|
+
# This method takes a Hash containing arguments, some of which are required
|
480
|
+
# and others optional. The supported arguments follow:
|
481
|
+
#
|
482
|
+
# * :root => The root of the Active Directory [required]
|
483
|
+
# * :user => The user for an LDAP bind [default "cn=Administrator,cn=Users"]
|
484
|
+
# * :password => The user password for an LDAP bind [optional]
|
485
|
+
# * :server => The Active Directory server host [default "localhost"]
|
486
|
+
#
|
487
|
+
# RADUM requires TLS to create user accounts in Active Directory properly,
|
488
|
+
# so you will need to make sure you have a certificate server so that you
|
489
|
+
# can connect with SSL on port 636. An example instantiation follows:
|
490
|
+
#
|
491
|
+
# ad = RADUM::AD.new :root => 'dc=example,dc=com',
|
492
|
+
# :user => 'cn=Administrator,cn=Users',
|
493
|
+
# :password => 'password',
|
494
|
+
# :server => '192.168.1.1'
|
495
|
+
#
|
496
|
+
# The :user argument specifies the path to the user account in Active
|
497
|
+
# Directory equivalent to the distinguished_name attribute for the user
|
498
|
+
# without the :root portion. The :server argument can be an IP address
|
499
|
+
# or a hostname. The :root argument is required. If it is not specified,
|
500
|
+
# a RuntimeError is raised. Extraneous spaces are removed from the :root
|
501
|
+
# and :user arguments. They can have spaces, but extra spaces after any
|
502
|
+
# "," characters are removed automatically along with leading and traling
|
503
|
+
# white space.
|
504
|
+
#
|
505
|
+
# === Parameter Types
|
506
|
+
#
|
507
|
+
# * :root [String]
|
508
|
+
# * :user [String]
|
509
|
+
# * :password [String]
|
510
|
+
# * :server [String]
|
511
|
+
#
|
512
|
+
# A Container object for "cn=Users" is automatically created and added to
|
513
|
+
# the AD object when it is created. This is meant to be a convenience
|
514
|
+
# because most, if not all, User and UNIXUser objects will have the
|
515
|
+
# "Domain Users" Windows group as their primary Windows group. It is
|
516
|
+
# impossible to remove this Container.
|
517
|
+
def initialize(args = {})
|
518
|
+
@root = args[:root] or raise "AD :root argument required."
|
519
|
+
@root.gsub!(/,\s+/, ",")
|
520
|
+
@root.strip!
|
521
|
+
@domain = @root.gsub(/[Dd][Cc]=/, "").gsub(/,/, ".").downcase
|
522
|
+
@user = args[:user] || "cn=Administrator,cn=Users"
|
523
|
+
@user.gsub!(/,\s+/, ",")
|
524
|
+
@user.strip!
|
525
|
+
@password = args[:password]
|
526
|
+
@server = args[:server] || "localhost"
|
527
|
+
@containers = []
|
528
|
+
@removed_containers = []
|
529
|
+
@min_uid = 1000
|
530
|
+
@uids = []
|
531
|
+
@min_gid = 1000
|
532
|
+
@gids = []
|
533
|
+
# RIDs are in a flat namespace, so there's no need to keep track of them
|
534
|
+
# for user or group objects specifically, just in the directory overall.
|
535
|
+
@rids = []
|
536
|
+
@port = 636
|
537
|
+
|
538
|
+
@ldap = Net::LDAP.new :host => @server,
|
539
|
+
:port => @port,
|
540
|
+
:encryption => :simple_tls,
|
541
|
+
:auth => {
|
542
|
+
:method => :simple,
|
543
|
+
:username => @user + "," + @root,
|
544
|
+
:password => @password
|
545
|
+
}
|
546
|
+
|
547
|
+
# We add the cn=Users container by default because it is highly likely
|
548
|
+
# that users have the Domain Users Windows group as their Windows
|
549
|
+
# primary group. If we did not do this, there would likely be a ton
|
550
|
+
# of warning messages in the load() method. Keep in mind that containers
|
551
|
+
# automatically add themselves to their AD object.
|
552
|
+
@cn_users = Container.new :name => "cn=Users", :directory => self
|
553
|
+
end
|
554
|
+
|
555
|
+
# The port number used to communicate with the Active Directory server.
|
556
|
+
def port
|
557
|
+
@port
|
558
|
+
end
|
559
|
+
|
560
|
+
# Set the port number used to communicate with the Active Directory server.
|
561
|
+
# This defaults to 636 for TLS in order to create user accounts properly,
|
562
|
+
# but can be set here for nonstandard configurations.
|
563
|
+
#
|
564
|
+
# === Parameter Types
|
565
|
+
#
|
566
|
+
# * port [integer]
|
567
|
+
def port=(port)
|
568
|
+
@port = port
|
569
|
+
@ldap.port = port
|
570
|
+
end
|
571
|
+
|
572
|
+
# Find a Container in the AD by name. The search is case-insensitive. The
|
573
|
+
# Container is returned if found, otherwise nil is returned.
|
574
|
+
#
|
575
|
+
# === Parameter Types
|
576
|
+
#
|
577
|
+
# * name [String]
|
578
|
+
def find_container(name)
|
579
|
+
@containers.find do |container|
|
580
|
+
# This relies on the fact that a container name must be unique in a
|
581
|
+
# directory.
|
582
|
+
container.name.downcase == name.downcase
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
# Add a Container a container to the AD. The Container must have the AD
|
587
|
+
# as its directory attribute or a RuntimeError is raised. Container objects
|
588
|
+
# that were removed cannot be added back and are ignored.
|
589
|
+
#
|
590
|
+
# === Parameter Types
|
591
|
+
#
|
592
|
+
# * container [Container]
|
593
|
+
def add_container(container) # :nodoc:
|
594
|
+
return if container.removed?
|
595
|
+
|
596
|
+
if self == container.directory
|
597
|
+
# We don't want to add the Container more than once.
|
598
|
+
@containers.push container unless @containers.include?(container)
|
599
|
+
@removed_containers.delete container
|
600
|
+
else
|
601
|
+
raise "Container must be in the same directory."
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
# Remove a Container from the AD. This attempts to set the Container
|
606
|
+
# object's removed attribute to true as well as remove any User, UNIXUser,
|
607
|
+
# Group, and UNIXGroup objects it contains. If any Group or UNIXGroup
|
608
|
+
# object cannot be removed because it is a dependency, the Container cannot
|
609
|
+
# be fully removed either, but all objects that can be removed will be
|
610
|
+
# removed. This can happen if a Group or UNIXGroup is another User or
|
611
|
+
# UNIXUser object's primary Windows group or UNIX main group and that User
|
612
|
+
# or UNIXUser is not in the same Container. Removed Container objects are
|
613
|
+
# ignored.
|
614
|
+
#
|
615
|
+
# Note that this method might succeed based on the user and group objects
|
616
|
+
# it knows about, but it still might fail when AD#sync is called because a
|
617
|
+
# more extensive Active Directory search will be performed at that point.
|
618
|
+
# In any case, all users will be removed and all groups (and the Container)
|
619
|
+
# if possible. This method is greedy in that it tries to remove as many
|
620
|
+
# objects from Active Directory as possible.
|
621
|
+
#
|
622
|
+
# This method refuses to remove the "cn=Users" container as a safety
|
623
|
+
# measure. There is no error raised in this case, but a warning is logged
|
624
|
+
# using RADUM::logger with a log level of LOG_NORMAL.
|
625
|
+
#
|
626
|
+
# Any external reference to the Container should be discarded unless it
|
627
|
+
# was not possible to fully remove the Container. This method returns a
|
628
|
+
# boolean that indicates if it was possible to fully remove the Container.
|
629
|
+
#
|
630
|
+
# === Parameter Types
|
631
|
+
#
|
632
|
+
# * container [Container]
|
633
|
+
def remove_container(container)
|
634
|
+
return if container.removed?
|
635
|
+
|
636
|
+
if container == @cn_users
|
637
|
+
RADUM::logger.log("Cannot remove #{container.name} - safety measure.",
|
638
|
+
LOG_NORMAL)
|
639
|
+
return false
|
640
|
+
end
|
641
|
+
|
642
|
+
can_remove = true
|
643
|
+
|
644
|
+
# The next two steps clone the arrays because we are modifying them while
|
645
|
+
# iterting over them at the same time of course.
|
646
|
+
container.users.clone.each do |user|
|
647
|
+
container.remove_user user
|
648
|
+
end
|
649
|
+
|
650
|
+
container.groups.clone.each do |group|
|
651
|
+
begin
|
652
|
+
container.remove_group group
|
653
|
+
rescue RuntimeError => error
|
654
|
+
RADUM::logger.log(error, LOG_NORMAL)
|
655
|
+
can_remove = false
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
@containers.each do |current_container|
|
660
|
+
next if current_container == container
|
661
|
+
|
662
|
+
if current_container.name =~ /#{container.name}/
|
663
|
+
RADUM::logger.log("Container #{container.name} contains container " +
|
664
|
+
"#{current_container.name}.", LOG_NORMAL)
|
665
|
+
can_remove = false
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
if can_remove
|
670
|
+
@containers.delete container
|
671
|
+
|
672
|
+
unless @removed_containers.include?(container)
|
673
|
+
@removed_containers.push container
|
674
|
+
end
|
675
|
+
|
676
|
+
container.set_removed
|
677
|
+
else
|
678
|
+
RADUM::logger.log("Cannot fully remove container #{container.name}.",
|
679
|
+
LOG_NORMAL)
|
680
|
+
end
|
681
|
+
|
682
|
+
can_remove
|
683
|
+
end
|
684
|
+
|
685
|
+
# Destroy all references to a Container. This also destroys any references
|
686
|
+
# to User, UNIXUser, Group, and UNIXGroup objects the Container owns. This
|
687
|
+
# can fail if any of the Group or UNIXGroup objects are the primary Windows
|
688
|
+
# group or UNIX main group for any User or UNIXUser objects RADUM knows
|
689
|
+
# about.
|
690
|
+
#
|
691
|
+
# This method refuses to destroy the "cn=Users" container as a safety
|
692
|
+
# measure. There is no error raised in this case, but a warning is logged
|
693
|
+
# using RADUM::logger with a log level of LOG_NORMAL.
|
694
|
+
#
|
695
|
+
# Any external reference to the Container should be discarded unless it
|
696
|
+
# was not possible to fully destroy the Container. This method returns a
|
697
|
+
# boolean that indicates if it was possible to fully destroy the Container.
|
698
|
+
#
|
699
|
+
# === Parameter Types
|
700
|
+
#
|
701
|
+
# * container [Container]
|
702
|
+
def destroy_container(container)
|
703
|
+
# We have to allow removed Container objects to be destroyed because
|
704
|
+
# that's the only way they are deleted from the RADUM environment
|
705
|
+
# when they are deleted from Active Directory.
|
706
|
+
if container == @cn_users
|
707
|
+
RADUM::logger.log("Cannot destroy #{container.name} - safety measure.",
|
708
|
+
LOG_NORMAL)
|
709
|
+
return
|
710
|
+
end
|
711
|
+
|
712
|
+
can_destroy = true
|
713
|
+
|
714
|
+
# The next two steps clone the arrays because we are modifying them while
|
715
|
+
# iterting over them at the same time of course.
|
716
|
+
container.users.clone.each do |user|
|
717
|
+
container.destroy_user user
|
718
|
+
end
|
719
|
+
|
720
|
+
container.groups.clone.each do |group|
|
721
|
+
begin
|
722
|
+
container.destroy_group group
|
723
|
+
rescue RuntimeError => error
|
724
|
+
RADUM::logger.log(error, LOG_NORMAL)
|
725
|
+
can_destroy = false
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
# There is no need to worry about sub-container objects.
|
730
|
+
|
731
|
+
if can_destroy
|
732
|
+
@containers.delete container
|
733
|
+
# Removed Containers can be destroyed as well, so we want to make sure
|
734
|
+
# all references are removed.
|
735
|
+
@removed_containers.delete container
|
736
|
+
container.set_removed
|
737
|
+
end
|
738
|
+
|
739
|
+
can_destroy
|
740
|
+
end
|
741
|
+
|
742
|
+
# Returns an Array of all User and UNIXUser objects in the AD.
|
743
|
+
def users
|
744
|
+
all_users = []
|
745
|
+
|
746
|
+
@containers.each do |container|
|
747
|
+
all_users += container.users
|
748
|
+
end
|
749
|
+
|
750
|
+
all_users
|
751
|
+
end
|
752
|
+
|
753
|
+
# Returns an Array of all removed User and UNIXUser objects in the AD.
|
754
|
+
def removed_users
|
755
|
+
all_removed_users = []
|
756
|
+
|
757
|
+
@containers.each do |container|
|
758
|
+
all_removed_users += container.removed_users
|
759
|
+
end
|
760
|
+
|
761
|
+
# We also need to check removed Containers too because they can have
|
762
|
+
# removed users too.
|
763
|
+
@removed_containers.each do |container|
|
764
|
+
all_removed_users += container.removed_users
|
765
|
+
end
|
766
|
+
|
767
|
+
all_removed_users
|
768
|
+
end
|
769
|
+
|
770
|
+
# Find a User or UNIXUser in the AD object. The first argument is a boolean
|
771
|
+
# that indicates if the search should be for removed users. It is optional
|
772
|
+
# and defaults to false. The second required argument is a block.
|
773
|
+
# The block is passed each User and UNIXUser object and contains the
|
774
|
+
# desired testing expression. Attributes for either User or UNIXUser
|
775
|
+
# objects can be used without worrying about the accessor methods existing.
|
776
|
+
# Examples follow:
|
777
|
+
#
|
778
|
+
# * Find a User or UNIXUser by username:
|
779
|
+
# find_user { |user| user.username == "username" }
|
780
|
+
# * Find a UNIXUser by gid:
|
781
|
+
# find_user { |user| user.gid == 1002 }
|
782
|
+
# * Find a removed User or UNIXUser by username:
|
783
|
+
# find_user(true) { |user| user.username == "username" }
|
784
|
+
# * Find a removed UNIXUser by gid:
|
785
|
+
# find_user(true) { |user| user.gid == 1002 }
|
786
|
+
#
|
787
|
+
# If no block is given the method returns nil. If the User or UNIXUser is
|
788
|
+
# not found, the method also returns nil, otherwise the User or UNIXUser
|
789
|
+
# object found is returned.
|
790
|
+
#
|
791
|
+
# There are convenient find_user_by_<attribute> methods defined that take
|
792
|
+
# care of the example cases here as well, but this method allows any block
|
793
|
+
# test to be given.
|
794
|
+
#
|
795
|
+
# === Parameter Types
|
796
|
+
#
|
797
|
+
# * removed [boolean]
|
798
|
+
# * [block]
|
799
|
+
def find_user(removed = false)
|
800
|
+
if block_given?
|
801
|
+
if removed
|
802
|
+
search_users = removed_users
|
803
|
+
else
|
804
|
+
search_users = users
|
805
|
+
end
|
806
|
+
|
807
|
+
found = search_users.find do |user|
|
808
|
+
begin
|
809
|
+
yield user
|
810
|
+
rescue NoMethodError
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
return found if found
|
815
|
+
return nil
|
816
|
+
end
|
817
|
+
|
818
|
+
return nil
|
819
|
+
end
|
820
|
+
|
821
|
+
# Find a User or UNIXUser in the AD by username. The search is
|
822
|
+
# case-insensitive. The User or UNIXUser is returned if found, otherwise
|
823
|
+
# nil is returned. Specify the second argument as true if you wish to
|
824
|
+
# search for removed User or UNIXUser objects.
|
825
|
+
#
|
826
|
+
# === Parameter Types
|
827
|
+
#
|
828
|
+
# * username [String]
|
829
|
+
# * removed [boolean]
|
830
|
+
def find_user_by_username(username, removed = false)
|
831
|
+
find_user removed do |user|
|
832
|
+
user.username.downcase == username.downcase
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
# Find a User or UNIXUser in the AD by RID. The User or UNIXUser is
|
837
|
+
# returned if found, otherwise nil is returned. Specify the second argument
|
838
|
+
# as true if you wish to search for removed User or UNIXUser objects.
|
839
|
+
#
|
840
|
+
# === Parameter Types
|
841
|
+
#
|
842
|
+
# * rid [integer]
|
843
|
+
# * removed [boolean]
|
844
|
+
def find_user_by_rid(rid, removed = false)
|
845
|
+
find_user(removed) { |user| user.rid == rid }
|
846
|
+
end
|
847
|
+
|
848
|
+
# Find a UNIXUser in the AD by UID. The UNIXUser is returned if found,
|
849
|
+
# otherwise nil is returned. Specify the second argument as true if you
|
850
|
+
# wish to search for removed UNIXUser objects.
|
851
|
+
#
|
852
|
+
# === Parameter Types
|
853
|
+
#
|
854
|
+
# * uid [integer]
|
855
|
+
# * removed [boolean]
|
856
|
+
def find_user_by_uid(uid, removed = false)
|
857
|
+
find_user(removed) { |user| user.uid == uid }
|
858
|
+
end
|
859
|
+
|
860
|
+
# Find a User or UNIXUser in the AD by distinguished name. The search is
|
861
|
+
# case-insensitive. The User or UNIXUser is returned if found, otherwise
|
862
|
+
# nil is returned. Specify the second argument as true if you wish to
|
863
|
+
# search for removed User or UNIXUser objects.
|
864
|
+
#
|
865
|
+
# === Parameter Types
|
866
|
+
#
|
867
|
+
# * dn [String]
|
868
|
+
# * removed [boolean]
|
869
|
+
def find_user_by_dn(dn, removed = false)
|
870
|
+
find_user removed do |user|
|
871
|
+
user.distinguished_name.downcase == dn.downcase
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
# Convert a User to a UNIXUser. This method returns the new UNIXUser
|
876
|
+
# if successful, otherwise there will be a RuntimeError somewhere first.
|
877
|
+
# The original User is destroyed with Container#destroy_user and a new
|
878
|
+
# UNIXUser is created with the same information. New UNIX attributes will
|
879
|
+
# be added to Active Directory. Any external references to the old User
|
880
|
+
# should be discarded and replaced with the new UNIXUser object returned.
|
881
|
+
# Supported arguments follow:
|
882
|
+
#
|
883
|
+
# * :user => The User to convert to a UNIXUser [required]
|
884
|
+
# * :uid => The UNIXUser UID attribute [required]
|
885
|
+
# * :unix_main_group => The UNIXUser object's UNIX main group [required]
|
886
|
+
# * :shell => The UNIXUser shell attribute [required]
|
887
|
+
# * :home_directory => The UNIXUser home directory attribute [required]
|
888
|
+
# * :nis_domain => The UNIXUser NIS domain attribute [default "radum"]
|
889
|
+
#
|
890
|
+
# The :uid argument specifies the UNIX UID value of the UNIXUser. The
|
891
|
+
# :unix_main_group argument must be a UNIXGroup object or a RuntimeError
|
892
|
+
# is raised. The :nis_domain defaults to "radum". The use of an NIS domain
|
893
|
+
# is not strictly required as one could simply set the right attributes in
|
894
|
+
# Active Directory and use LDAP on clients to access that data, but
|
895
|
+
# specifying an NIS domain allows for easy editing of UNIX attributes
|
896
|
+
# using the GUI tools in Windows, thus the use of a default value.
|
897
|
+
#
|
898
|
+
# === Parameter Types
|
899
|
+
#
|
900
|
+
# * :user [User]
|
901
|
+
# * :uid [integer]
|
902
|
+
# * :unix_main_group [UNIXGroup]
|
903
|
+
# * :shell [String]
|
904
|
+
# * :home_directory [String]
|
905
|
+
# * :nis_domain [String]
|
906
|
+
#
|
907
|
+
# If the :user argument is not a User object, a RuntimeError is raised.
|
908
|
+
# If the User has already been removed a RuntimeError is raised.
|
909
|
+
# The :uid argument is also checked first in case it already is in use
|
910
|
+
# so that the original user is not destroyed by accident due to an error.
|
911
|
+
#
|
912
|
+
# In this case, unlike when simply creating a UNIXUser object, all UIDs
|
913
|
+
# are checked, including those already in Active Directory.
|
914
|
+
#
|
915
|
+
# No changes to the User happen until AD#sync is called.
|
916
|
+
def user_to_unix_user(args = {})
|
917
|
+
user = args[:user]
|
918
|
+
|
919
|
+
# Make sure we are working with a User object only.
|
920
|
+
unless user.instance_of?(User)
|
921
|
+
raise ":user argument just be a User object."
|
922
|
+
end
|
923
|
+
|
924
|
+
if user.removed?
|
925
|
+
raise ":user has been removed."
|
926
|
+
end
|
927
|
+
|
928
|
+
uid = args[:uid]
|
929
|
+
all_uids = load_ldap_uids
|
930
|
+
|
931
|
+
# The UID must be unique.
|
932
|
+
if (all_uids + @uids).include?(uid)
|
933
|
+
raise "UID #{uid} is already in use in the directory."
|
934
|
+
end
|
935
|
+
|
936
|
+
unix_main_group = args[:unix_main_group]
|
937
|
+
|
938
|
+
unless unix_main_group.instance_of?(UNIXGroup)
|
939
|
+
raise ":unix_main_group is not a UNIXGroup object."
|
940
|
+
end
|
941
|
+
|
942
|
+
shell = args[:shell]
|
943
|
+
home_directory = args[:home_directory]
|
944
|
+
nis_domain = args[:nis_domain]
|
945
|
+
# User attributes.
|
946
|
+
username = user.username
|
947
|
+
container = user.container
|
948
|
+
primary_group = user.primary_group
|
949
|
+
disabled = user.disabled?
|
950
|
+
rid = user.rid
|
951
|
+
first_name = user.first_name
|
952
|
+
initials = user.initials
|
953
|
+
middle_name = user.middle_name
|
954
|
+
surname = user.surname
|
955
|
+
script_path = user.script_path
|
956
|
+
profile_path = user.profile_path
|
957
|
+
local_path = user.local_path
|
958
|
+
local_drive = user.local_drive
|
959
|
+
password = user.password
|
960
|
+
must_change_password = user.must_change_password?
|
961
|
+
groups = user.groups.clone
|
962
|
+
removed_groups = user.removed_groups.clone
|
963
|
+
loaded = user.loaded?
|
964
|
+
|
965
|
+
# Destroy the user now that we have its information.
|
966
|
+
container.destroy_user user
|
967
|
+
user = UNIXUser.new :username => username, :container => container,
|
968
|
+
:primary_group => primary_group,
|
969
|
+
:disabled => disabled, :rid => rid, :uid => uid,
|
970
|
+
:unix_main_group => unix_main_group,
|
971
|
+
:shell => shell, :home_directory => home_directory,
|
972
|
+
:nis_domain => nis_domain
|
973
|
+
|
974
|
+
# Set the user to loaded if it was loaded orginally. This sets the
|
975
|
+
# modified attribute to false, but the actions below will ensure the
|
976
|
+
# modified attribute is actually true when we are done, which is required
|
977
|
+
# in order to update the attributes in Active Directory through AD#sync.
|
978
|
+
user.set_loaded if loaded
|
979
|
+
|
980
|
+
# Set other User attributes.
|
981
|
+
user.first_name = first_name
|
982
|
+
user.initials = initials
|
983
|
+
user.middle_name = middle_name
|
984
|
+
user.surname = surname
|
985
|
+
user.script_path = script_path
|
986
|
+
user.profile_path = profile_path
|
987
|
+
|
988
|
+
# Figure out the Windows home directory type attributes.
|
989
|
+
if local_path && local_drive
|
990
|
+
user.connect_drive_to local_drive, local_path
|
991
|
+
elsif local_path
|
992
|
+
user.local_path = local_path
|
993
|
+
end
|
994
|
+
|
995
|
+
user.password = password
|
996
|
+
|
997
|
+
if must_change_password
|
998
|
+
user.force_change_password
|
999
|
+
end
|
1000
|
+
|
1001
|
+
(groups + removed_groups).each do |group_member|
|
1002
|
+
user.add_group group_member
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
removed_groups.each do |group_member|
|
1006
|
+
user.remove_group group_member
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
user
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
# Convert a UNIXUser to a User. This method returns the new User
|
1013
|
+
# if successful, otherwise there will be a RuntimeError somewhere first.
|
1014
|
+
# The original UNIXUser is destroyed with Container#destroy_user and a
|
1015
|
+
# new User is created with the same information where applicable. Old
|
1016
|
+
# UNIX attributes will be removed from Active Directory if possible
|
1017
|
+
# immediately (not waiting on AD#sync because AD#sync will think this is
|
1018
|
+
# now only a User object with no UNIX attributes). Any external references
|
1019
|
+
# to the old UNIXUser should be discarded and replaced with the new User
|
1020
|
+
# object returned. Supported arguments follow:
|
1021
|
+
#
|
1022
|
+
# * :user => The UNIXUser to convert to a User [required]
|
1023
|
+
# * :remove_unix_groups => Remove UNIXGroup memberships [default false]
|
1024
|
+
#
|
1025
|
+
# The :user argument is the UNIXUser to convert. If the :user argument
|
1026
|
+
# is not a UNIXUser object, a RuntimeError is raised. If the UNIXUser
|
1027
|
+
# has already been removed a RuntimeError is raised. The :remove_unix_groups
|
1028
|
+
# argument is a boolean flag that determines if the new User object should
|
1029
|
+
# continue to be a member of the UNIXGroup objects it was previously.
|
1030
|
+
# UNIXUser objects are members of their UNIXGroup objects from the Windows
|
1031
|
+
# perspective by default because they are members from the UNIX perspective.
|
1032
|
+
# This is the default behavior in RADUM. The default action is to not remove
|
1033
|
+
# their Windows group memberships when converting a UNIXUser to a User.
|
1034
|
+
#
|
1035
|
+
# === Parameter Types
|
1036
|
+
#
|
1037
|
+
# * :user [UNIXUser]
|
1038
|
+
# * :remove_unix_groups [boolean]
|
1039
|
+
#
|
1040
|
+
# UNIX attributes are removed from Active Directory immedately if it is
|
1041
|
+
# actually possible to destroy the UNIXUser properly without waiting for
|
1042
|
+
# AD#sync to be called.
|
1043
|
+
def unix_user_to_user(args = {})
|
1044
|
+
RADUM::logger.log("[AD #{self.root}] entering unix_user_to_user()",
|
1045
|
+
LOG_DEBUG)
|
1046
|
+
user = args[:user]
|
1047
|
+
|
1048
|
+
# Make sure we are working with a UNIXUser object only.
|
1049
|
+
unless user.instance_of?(UNIXUser)
|
1050
|
+
raise ":user argument just be a UNIXUser object."
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
if user.removed?
|
1054
|
+
raise ":user has been removed."
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
remove_unix_groups = args[:remove_unix_groups] || false
|
1058
|
+
|
1059
|
+
# User attributes.
|
1060
|
+
username = user.username
|
1061
|
+
container = user.container
|
1062
|
+
primary_group = user.primary_group
|
1063
|
+
# This is needed later if removal from UNIXGroup objects was requested.
|
1064
|
+
unix_main_group = user.unix_main_group
|
1065
|
+
disabled = user.disabled?
|
1066
|
+
rid = user.rid
|
1067
|
+
first_name = user.first_name
|
1068
|
+
initials = user.initials
|
1069
|
+
middle_name = user.middle_name
|
1070
|
+
surname = user.surname
|
1071
|
+
script_path = user.script_path
|
1072
|
+
profile_path = user.profile_path
|
1073
|
+
local_path = user.local_path
|
1074
|
+
local_drive = user.local_drive
|
1075
|
+
password = user.password
|
1076
|
+
must_change_password = user.must_change_password?
|
1077
|
+
groups = user.groups.clone
|
1078
|
+
removed_groups = user.removed_groups.clone
|
1079
|
+
loaded = user.loaded?
|
1080
|
+
|
1081
|
+
# Destroy the user now that we have its information.
|
1082
|
+
container.destroy_user user
|
1083
|
+
|
1084
|
+
# If the user was destroyed and we got this far, we need to remove
|
1085
|
+
# any of its UNIX attributes in Active Directory directly. We do need
|
1086
|
+
# to make sure it is actually there first of course.
|
1087
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
1088
|
+
found = @ldap.search(:base => user.distinguished_name,
|
1089
|
+
:filter => user_filter,
|
1090
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
1091
|
+
:return_result => false)
|
1092
|
+
|
1093
|
+
unless found == false
|
1094
|
+
ops = [
|
1095
|
+
[:replace, :loginShell, nil],
|
1096
|
+
[:replace, :unixHomeDirectory, nil],
|
1097
|
+
[:replace, :msSFU30NisDomain, nil],
|
1098
|
+
[:replace, :gecos, nil],
|
1099
|
+
[:replace, :unixUserPassword, nil],
|
1100
|
+
[:replace, :shadowExpire, nil],
|
1101
|
+
[:replace, :shadowFlag, nil],
|
1102
|
+
[:replace, :shadowInactive, nil],
|
1103
|
+
[:replace, :shadowLastChange, nil],
|
1104
|
+
[:replace, :shadowMax, nil],
|
1105
|
+
[:replace, :shadowMin, nil],
|
1106
|
+
[:replace, :shadowWarning, nil],
|
1107
|
+
[:replace, :gidNumber, nil],
|
1108
|
+
[:replace, :uidNumber, nil]
|
1109
|
+
]
|
1110
|
+
|
1111
|
+
RADUM::logger.log("\tRemoving user's previous UNIX attributes.",
|
1112
|
+
LOG_DEBUG)
|
1113
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
1114
|
+
@ldap.modify :dn => user.distinguished_name, :operations => ops
|
1115
|
+
check_ldap_result
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
user = User.new :username => username, :container => container,
|
1119
|
+
:primary_group => primary_group, :disabled => disabled,
|
1120
|
+
:rid => rid
|
1121
|
+
|
1122
|
+
# Set the user to loaded if it was loaded orginally. This sets the
|
1123
|
+
# modified attribute to false, but the actions below will ensure the
|
1124
|
+
# modified attribute is actually true when we are done, which is required
|
1125
|
+
# in order to update the attributes in Active Directory through AD#sync.
|
1126
|
+
user.set_loaded if loaded
|
1127
|
+
|
1128
|
+
# Set other User attributes.
|
1129
|
+
user.first_name = first_name
|
1130
|
+
user.initials = initials
|
1131
|
+
user.middle_name = middle_name
|
1132
|
+
user.surname = surname
|
1133
|
+
user.script_path = script_path
|
1134
|
+
user.profile_path = profile_path
|
1135
|
+
|
1136
|
+
# Figure out the Windows home directory type attributes.
|
1137
|
+
if local_path && local_drive
|
1138
|
+
user.connect_drive_to local_drive, local_path
|
1139
|
+
elsif local_path
|
1140
|
+
user.local_path = local_path
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
user.password = password
|
1144
|
+
|
1145
|
+
if must_change_password
|
1146
|
+
user.force_change_password
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
(groups + removed_groups).each do |group_member|
|
1150
|
+
user.add_group group_member
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
removed_groups.each do |group_member|
|
1154
|
+
user.remove_group group_member
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
# An extra step to remove any UNIXGroup objects if that was requested.
|
1158
|
+
if remove_unix_groups
|
1159
|
+
user.groups.clone.each do |group|
|
1160
|
+
if group.instance_of?(UNIXGroup)
|
1161
|
+
# The user is not a member of their primary Windows group in the
|
1162
|
+
# groups array for the user, so no check is needed. In that case,
|
1163
|
+
# nothing happens.
|
1164
|
+
user.remove_group group
|
1165
|
+
|
1166
|
+
# Don't try to remove a membership in UNIX LDAP attributes that does
|
1167
|
+
# not exist.
|
1168
|
+
if group != unix_main_group
|
1169
|
+
# Immediately remove UNIX group membership through UNIX LDAP
|
1170
|
+
# attributes if the UNIXGroup is actually in Active Directory at
|
1171
|
+
# this point.
|
1172
|
+
group_filter = Net::LDAP::Filter.eq("objectclass", "group")
|
1173
|
+
found = @ldap.search(:base => group.distinguished_name,
|
1174
|
+
:filter => group_filter,
|
1175
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
1176
|
+
:return_result => false)
|
1177
|
+
|
1178
|
+
unless found == false
|
1179
|
+
ops = [
|
1180
|
+
[:delete, :memberUid, user.username],
|
1181
|
+
[:delete, :msSFU30PosixMember, user.distinguished_name]
|
1182
|
+
]
|
1183
|
+
|
1184
|
+
RADUM::logger.log("\tRemoving UNIX LDAP attribute membership" +
|
1185
|
+
" for <#{group.name}>.", LOG_DEBUG)
|
1186
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
1187
|
+
@ldap.modify :dn => group.distinguished_name, :operations => ops
|
1188
|
+
check_ldap_result
|
1189
|
+
end
|
1190
|
+
end
|
1191
|
+
end
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
RADUM::logger.log("[AD #{self.root}] exiting unix_user_to_user()",
|
1196
|
+
LOG_DEBUG)
|
1197
|
+
user
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
# Returns an Array of all Group and UNIXGroup objects in the AD.
|
1201
|
+
def groups
|
1202
|
+
all_groups = []
|
1203
|
+
|
1204
|
+
@containers.each do |container|
|
1205
|
+
all_groups += container.groups
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
all_groups
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
# Returns an Array of all removed Group and UNIXGroup objects in the AD.
|
1212
|
+
def removed_groups
|
1213
|
+
all_removed_groups = []
|
1214
|
+
|
1215
|
+
@containers.each do |container|
|
1216
|
+
all_removed_groups += container.removed_groups
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
# We also need to check removed Containers too because they can have
|
1220
|
+
# removed groups too.
|
1221
|
+
@removed_containers.each do |container|
|
1222
|
+
all_removed_groups += container.removed_groups
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
all_removed_groups
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
# Find a Group or UNIXGroup in the AD object. The first argument is a
|
1229
|
+
# boolean that indicates if the search should be for removed groups. It is
|
1230
|
+
# optional and defaults to false. The second required argument is a block.
|
1231
|
+
# The block is passed each Group and UNIXGroup object and contains the
|
1232
|
+
# desired testing expression. Attributes for either Group or UNIXGroup
|
1233
|
+
# objects can be used without worrying about the accessor methods existing.
|
1234
|
+
# Examples follow:
|
1235
|
+
#
|
1236
|
+
# * Find a Group or UNIXGroup by name:
|
1237
|
+
# find_group { |group| group.name == "name" }
|
1238
|
+
# * Find a UNIXGroup by gid:
|
1239
|
+
# find_group { |group| group.gid == 1002 }
|
1240
|
+
# * Find a removed Group or UNIXGroup by name:
|
1241
|
+
# find_group(true) { |group| group.name == "name" }
|
1242
|
+
# * Find a removed UNIXGroup by gid:
|
1243
|
+
# find_group(true) { |group| group.gid == 1002 }
|
1244
|
+
#
|
1245
|
+
# If no block is given the method returns nil. If the Group or UNIXGroup is
|
1246
|
+
# not found, the method also returns nil, otherwise the Group or UNIXGroup
|
1247
|
+
# object found is returned.
|
1248
|
+
#
|
1249
|
+
# There are convenient find_group_by_<attribute> methods defined that take
|
1250
|
+
# care of the example cases here as well, but this method allows any block
|
1251
|
+
# test to be given.
|
1252
|
+
#
|
1253
|
+
# === Parameter Types
|
1254
|
+
#
|
1255
|
+
# * removed [boolean]
|
1256
|
+
# * [block]
|
1257
|
+
def find_group(removed = false)
|
1258
|
+
if block_given?
|
1259
|
+
if removed
|
1260
|
+
search_groups = removed_groups
|
1261
|
+
else
|
1262
|
+
search_groups = groups
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
found = search_groups.find do |group|
|
1266
|
+
begin
|
1267
|
+
yield group
|
1268
|
+
rescue NoMethodError
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
return found if found
|
1273
|
+
return nil
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
return nil
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
# Find a Group or UNIXGroup in the AD by name. The search is
|
1280
|
+
# case-insensitive. The Group or UNIXGroup is returned if found, otherwise
|
1281
|
+
# nil is returned. Specify the second argument as true if you wish to
|
1282
|
+
# search for removed Group or UNIXGroup objects.
|
1283
|
+
#
|
1284
|
+
# === Parameter Types
|
1285
|
+
#
|
1286
|
+
# * name [String]
|
1287
|
+
# * removed [boolean]
|
1288
|
+
def find_group_by_name(name, removed = false)
|
1289
|
+
find_group removed do |group|
|
1290
|
+
group.name.downcase == name.downcase
|
1291
|
+
end
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
# Find a Group or UNIXGroup in the AD by RID. The Group or UNIXGroup is
|
1295
|
+
# returned if found, otherwise nil is returned. Specify the second argument
|
1296
|
+
# as true if you wish to search for removed Group or UNIXGroup objects.
|
1297
|
+
#
|
1298
|
+
# === Parameter Types
|
1299
|
+
#
|
1300
|
+
# * rid [integer]
|
1301
|
+
# * removed [boolean]
|
1302
|
+
def find_group_by_rid(rid, removed = false)
|
1303
|
+
find_group(removed) { |group| group.rid == rid }
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
# Find a UNIXGroup in the AD by GID. The UNIXGroup is returned if found,
|
1307
|
+
# otherwise nil is returned. Specify the second argument as true if you
|
1308
|
+
# wish to search for removed UNIXGroup objects.
|
1309
|
+
#
|
1310
|
+
# === Parameter Types
|
1311
|
+
#
|
1312
|
+
# * gid [integer]
|
1313
|
+
# * removed [boolean]
|
1314
|
+
def find_group_by_gid(gid, removed = false)
|
1315
|
+
find_group(removed) { |group| group.gid == gid }
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
# Find a Group or UNIXGroup in the AD by distinguished name. The search is
|
1319
|
+
# case-insensitive. The Group or UNIXGroup is returned if found, otherwise
|
1320
|
+
# nil is returned. Specify the second argument as true if you wish to
|
1321
|
+
# search for removed Group or UNIXGroup objects.
|
1322
|
+
#
|
1323
|
+
# === Parameter Types
|
1324
|
+
#
|
1325
|
+
# * dn [String]
|
1326
|
+
# * removed [boolean]
|
1327
|
+
def find_group_by_dn(dn, removed = false)
|
1328
|
+
find_group removed do |group|
|
1329
|
+
group.distinguished_name.downcase == dn.downcase
|
1330
|
+
end
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
# Convert a Group to a UNIXGroup. This method returns the new UNIXGroup
|
1334
|
+
# if successful, otherwise there will be a RuntimeError somewhere first.
|
1335
|
+
# The original Group is destroyed with Container#destroy_group and a new
|
1336
|
+
# UNIXGroup is created with the same information. New UNIX attributes will
|
1337
|
+
# be added to Active Directory. Any external references to the old Group
|
1338
|
+
# should be discarded and replaced with the new UNIXGroup object returned.
|
1339
|
+
# Supported arguments follow:
|
1340
|
+
#
|
1341
|
+
# * :group => The Group to convert to a UNIXGroup [required]
|
1342
|
+
# * :gid => The UNIXGroup GID attribute [required]
|
1343
|
+
# * :nis_domain => The UNIXGroup NIS domain attribute [default "radum"]
|
1344
|
+
#
|
1345
|
+
# The :gid argument specifies the UNIX GID value of the UNIXGroup. The
|
1346
|
+
# :nis_domain defaults to "radum". The use of an NIS domain is not
|
1347
|
+
# strictly required as one could simply set the right attributes in Active
|
1348
|
+
# Directory and use LDAP on clients to access that data, but specifying an
|
1349
|
+
# NIS domain allows for easy editing of UNIX attributes using the GUI tools
|
1350
|
+
# in Windows, thus the use of a default value.
|
1351
|
+
#
|
1352
|
+
# === Parameter Types
|
1353
|
+
#
|
1354
|
+
# * :group [Group]
|
1355
|
+
# * :gid [integer]
|
1356
|
+
# * :nis_domain [String]
|
1357
|
+
#
|
1358
|
+
# If the :group argument is not a Group object, a RuntimeError is raised.
|
1359
|
+
# If the Group has already been removed a RuntimeError is raised.
|
1360
|
+
# The :gid argument is also checked first in case it already is in use
|
1361
|
+
# so that the original group is not destroyed by accident due to an error.
|
1362
|
+
# Note that Container#destroy_group checks to make sure the group is not
|
1363
|
+
# the primary Windows group for any User or UNIXUser first, so the :group
|
1364
|
+
# must not be the primary Windows group for any users. If the group is
|
1365
|
+
# someone's primary Windows group a RuntimeError is raised. You will have
|
1366
|
+
# to modify it by hand in Active Directory if you want to convert it. The
|
1367
|
+
# primary Windows group check is not done for all users in Active Directory
|
1368
|
+
# because it is safe to convert unless RADUM thinks the Group is one of
|
1369
|
+
# its User or UNIXUser object's primary Windows group. In that specific
|
1370
|
+
# case, you'll have to modify the group in Active Directory by hand
|
1371
|
+
# unfortunately.
|
1372
|
+
#
|
1373
|
+
# In this case, unlike when simply creating a UNIXGroup object, all GIDs
|
1374
|
+
# are checked, including those already in Active Directory.
|
1375
|
+
#
|
1376
|
+
# No changes to the Group happen until AD#sync is called.
|
1377
|
+
def group_to_unix_group(args = {})
|
1378
|
+
group = args[:group]
|
1379
|
+
|
1380
|
+
# Make sure we are working with a Group object only.
|
1381
|
+
unless group.instance_of?(Group)
|
1382
|
+
raise ":group argument just be a Group object."
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
if group.removed?
|
1386
|
+
raise ":group has been removed."
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
gid = args[:gid]
|
1390
|
+
all_gids = load_ldap_gids
|
1391
|
+
|
1392
|
+
# The GID must be unique.
|
1393
|
+
if (all_gids + @gids).include?(gid)
|
1394
|
+
raise "GID #{gid} is already in use in the directory."
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
nis_domain = args[:nis_domain]
|
1398
|
+
# Group attributes.
|
1399
|
+
name = group.name
|
1400
|
+
container = group.container
|
1401
|
+
type = group.type
|
1402
|
+
rid = group.rid
|
1403
|
+
users = group.users.clone
|
1404
|
+
groups = group.groups.clone
|
1405
|
+
removed_users = group.removed_users.clone
|
1406
|
+
removed_groups = group.removed_groups.clone
|
1407
|
+
loaded = group.loaded?
|
1408
|
+
|
1409
|
+
# Destroy the group now that we have its information. This will fail if
|
1410
|
+
# the group is someone's primary Windows group from the RADUM perspective
|
1411
|
+
# because of the logic I have to employ, but it is fine if the group is
|
1412
|
+
# some other user account's primary Windows group that RADUM doesn't
|
1413
|
+
# know about.
|
1414
|
+
container.destroy_group group
|
1415
|
+
|
1416
|
+
group = UNIXGroup.new :name => name, :container => container,
|
1417
|
+
:type => type, :rid => rid, :gid => gid,
|
1418
|
+
:nis_domain => nis_domain
|
1419
|
+
|
1420
|
+
# Set the group to loaded if it was loaded orginally. This sets the
|
1421
|
+
# modified attribute to false, but the actions below will ensure the
|
1422
|
+
# modified attribute is actually true when we are done, which is required
|
1423
|
+
# in order to update the attributes in Active Directory through AD#sync.
|
1424
|
+
group.set_loaded if loaded
|
1425
|
+
|
1426
|
+
(users + removed_users).each do |user_member|
|
1427
|
+
group.add_user user_member
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
removed_users.each do |user_member|
|
1431
|
+
group.remove_user user_member
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
(groups + removed_groups).each do |group_member|
|
1435
|
+
group.add_group group_member
|
1436
|
+
end
|
1437
|
+
|
1438
|
+
removed_groups.each do |group_member|
|
1439
|
+
group.remove_group group_member
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
group
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
# Convert a UNIXGroup to a Group. This method returns the new Group
|
1446
|
+
# if successful, otherwise there will be a RuntimeError somewhere first.
|
1447
|
+
# The original UNIXGroup is destroyed with Container#destroy_group and a
|
1448
|
+
# new Group is created with the same information where applicable. Old
|
1449
|
+
# UNIX attributes will be removed from Active Directory if possible
|
1450
|
+
# immediately (not waiting on AD#sync because AD#sync will think this is
|
1451
|
+
# now only a Group object with no UNIX attributes). Any external references
|
1452
|
+
# to the old UNIXGroup should be discarded and replaced with the new Group
|
1453
|
+
# object returned. Supported arguments follow:
|
1454
|
+
#
|
1455
|
+
# * :group => The UNIXGroup to convert to a Group [required]
|
1456
|
+
# * :remove_unix_users => Remove UNIXUser object members [default false]
|
1457
|
+
#
|
1458
|
+
# The :group argument is the UNIXGroup to convert. If the :group argument
|
1459
|
+
# is not a UNIXGroup object, a RuntimeError is raised. If the UNIXGroup
|
1460
|
+
# has already been removed a RuntimeError is raised. The :remove_unix_users
|
1461
|
+
# argument is a boolean flag that determines if UNIXUser objects who were
|
1462
|
+
# members of the UNIXGroup from the Windows perspective should be removed
|
1463
|
+
# as members when converting to a Group object. UNIXUser objects are
|
1464
|
+
# members from the Windows perspective as well by default because they are
|
1465
|
+
# members from the UNIX perspective. This is the default behavior in
|
1466
|
+
# RADUM. The default action is to not remove their Windows user
|
1467
|
+
# memberships when converting a UNIXGroup to a Group.
|
1468
|
+
#
|
1469
|
+
# === Parameter Types
|
1470
|
+
#
|
1471
|
+
# * :group [UNIXGroup]
|
1472
|
+
# * :remove_unix_users [boolean]
|
1473
|
+
#
|
1474
|
+
# Note that Container#destroy_group checks to make sure the group is not
|
1475
|
+
# the primary Windows group or UNIX main group for any User or UNIXUser,
|
1476
|
+
# so the :group must not be the primary Windows group or UNIX main group
|
1477
|
+
# for any users. If the group is someone's primary Windows group or UNIX
|
1478
|
+
# main group a RuntimeError will be raised. You will have to modify it by
|
1479
|
+
# hand in Active Directory if you want to convert it. The UNIX main group
|
1480
|
+
# condition is checked for all users in Active Directory before attempting
|
1481
|
+
# to use Container#destroy_group to ensure the conversion is safe. The
|
1482
|
+
# primary Windows group check is not done for all users in Active Directory
|
1483
|
+
# because it is safe to convert unless RADUM thinks the UNIXGroup
|
1484
|
+
# is one of its User or UNIXUser object's primary Windows group. In that
|
1485
|
+
# specific case, you'll have to modify the group in Active Directory by
|
1486
|
+
# hand unfortunately.
|
1487
|
+
#
|
1488
|
+
# UNIX attributes are removed from Active Directory immedately if it is
|
1489
|
+
# actually possible to destroy the UNIXGroup properly without waiting for
|
1490
|
+
# AD#sync to be called.
|
1491
|
+
def unix_group_to_group(args = {})
|
1492
|
+
RADUM::logger.log("[AD #{self.root}] entering unix_group_to_group()",
|
1493
|
+
LOG_DEBUG)
|
1494
|
+
group = args[:group]
|
1495
|
+
|
1496
|
+
# Make sure we are working with a UNIXGroup object only.
|
1497
|
+
unless group.instance_of?(UNIXGroup)
|
1498
|
+
raise ":group argument just be a UNIXGroup object."
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
if group.removed?
|
1502
|
+
raise ":group has been removed."
|
1503
|
+
end
|
1504
|
+
|
1505
|
+
remove_unix_users = args[:remove_unix_users] || false
|
1506
|
+
|
1507
|
+
# Group attributes.
|
1508
|
+
name = group.name
|
1509
|
+
container = group.container
|
1510
|
+
type = group.type
|
1511
|
+
rid = group.rid
|
1512
|
+
users = group.users.clone
|
1513
|
+
groups = group.groups.clone
|
1514
|
+
removed_users = group.removed_users.clone
|
1515
|
+
removed_groups = group.removed_groups.clone
|
1516
|
+
loaded = group.loaded?
|
1517
|
+
|
1518
|
+
# Make sure the group is not someone's UNIX main group for all users
|
1519
|
+
# in Active Directory before trying to destroy the group. The
|
1520
|
+
# Container#destroy_group method only checks for objects in RADUM
|
1521
|
+
# itself.
|
1522
|
+
if ldap_is_unix_main_group?(group)
|
1523
|
+
raise ":group is someone's UNIX main group."
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
# Destroy the group now that we have its information. This will fail if
|
1527
|
+
# the group is someone's primary Windows group from the RADUM perspective
|
1528
|
+
# because of the logic I have to employ, but it is fine if the group is
|
1529
|
+
# some other user account's primary Windows group that RADUM doesn't
|
1530
|
+
# know about.
|
1531
|
+
container.destroy_group group
|
1532
|
+
|
1533
|
+
# If the group was destroyed and we got this far, we need to remove
|
1534
|
+
# any of its UNIX attributes in Active Directory directly. We do need
|
1535
|
+
# to make sure it is actually there first of course.
|
1536
|
+
group_filter = Net::LDAP::Filter.eq("objectclass", "group")
|
1537
|
+
found = @ldap.search(:base => group.distinguished_name,
|
1538
|
+
:filter => group_filter,
|
1539
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
1540
|
+
:return_result => false)
|
1541
|
+
|
1542
|
+
unless found == false
|
1543
|
+
ops = [
|
1544
|
+
[:replace, :gidNumber, nil],
|
1545
|
+
[:replace, :msSFU30NisDomain, nil],
|
1546
|
+
[:replace, :unixUserPassword, nil],
|
1547
|
+
[:replace, :memberUid, nil],
|
1548
|
+
[:replace, :msSFU30PosixMember, nil]
|
1549
|
+
]
|
1550
|
+
|
1551
|
+
RADUM::logger.log("\tRemoving groups's previous UNIX attributes.",
|
1552
|
+
LOG_DEBUG)
|
1553
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
1554
|
+
@ldap.modify :dn => group.distinguished_name, :operations => ops
|
1555
|
+
check_ldap_result
|
1556
|
+
end
|
1557
|
+
|
1558
|
+
group = Group.new :name => name, :container => container,
|
1559
|
+
:type => type, :rid => rid
|
1560
|
+
|
1561
|
+
# Set the group to loaded if it was loaded orginally. This sets the
|
1562
|
+
# modified attribute to false, but the actions below will ensure the
|
1563
|
+
# modified attribute is actually true when we are done, which is required
|
1564
|
+
# in order to update the attributes in Active Directory through AD#sync.
|
1565
|
+
group.set_loaded if loaded
|
1566
|
+
|
1567
|
+
(users + removed_users).each do |user_member|
|
1568
|
+
group.add_user user_member
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
removed_users.each do |user_member|
|
1572
|
+
group.remove_user user_member
|
1573
|
+
end
|
1574
|
+
|
1575
|
+
(groups + removed_groups).each do |group_member|
|
1576
|
+
group.add_group group_member
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
removed_groups.each do |group_member|
|
1580
|
+
group.remove_group group_member
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
# An extra step to remove any UNIXUser objects if that was requested.
|
1584
|
+
if remove_unix_users
|
1585
|
+
group.users.clone.each do |user|
|
1586
|
+
group.remove_user user if user.instance_of?(UNIXUser)
|
1587
|
+
end
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
RADUM::logger.log("[AD #{self.root}] exiting unix_group_to_group()",
|
1591
|
+
LOG_DEBUG)
|
1592
|
+
group
|
1593
|
+
end
|
1594
|
+
|
1595
|
+
# Load all user and group objects in Active Directory that are in the AD
|
1596
|
+
# object's Containers. This automatically creates User, UNIXUser, Group,
|
1597
|
+
# and UNIXGroup objects as needed and sets all of their attributes
|
1598
|
+
# correctly. This can be used to initialize a program using the RADUM
|
1599
|
+
# module for account management work.
|
1600
|
+
#
|
1601
|
+
# User objects are not created if their primary Windows group is not found
|
1602
|
+
# during the load. UNIXUser objects are not created if their UNIX main group
|
1603
|
+
# is not found during the load. Warning messages are printed in each
|
1604
|
+
# case. Make sure all required Containers are in the AD before loading
|
1605
|
+
# data from Active Directory to avoid this problem.
|
1606
|
+
#
|
1607
|
+
# You generally should call AD#load to ensure the RADUM system has a valid
|
1608
|
+
# representation of the Active Directory objects. You can call AD#sync
|
1609
|
+
# without calling AD#load first, but your object values are authoritative.
|
1610
|
+
# Unless you set every attribute correctly, unset object attributes will
|
1611
|
+
# overwrite current values in Active Directory. Note that AD#sync will
|
1612
|
+
# not touch Active Directory group memberships it does not know about
|
1613
|
+
# explicitly, so AD#sync will not remove group and user memberships in
|
1614
|
+
# groups that were not explicitly removed in RADUM. The general RADUM
|
1615
|
+
# pattern is:
|
1616
|
+
#
|
1617
|
+
# * AD.new(...)
|
1618
|
+
# * Container.new(...) [for any Containers of interest]
|
1619
|
+
# * AD#load()
|
1620
|
+
# * Create, update, or remove existing loaded objects.
|
1621
|
+
# * AD#sync()
|
1622
|
+
#
|
1623
|
+
# This methods will sliently ignore objects that already exist in the AD
|
1624
|
+
# object unless the Logger default_level is set to LOG_DEBUG. Therefore,
|
1625
|
+
# it is possible to load new Container objects after calling this method
|
1626
|
+
# previously to create new objects. Anything that previously exists will
|
1627
|
+
# be ignored.
|
1628
|
+
def load
|
1629
|
+
RADUM::logger.log("[AD #{self.root}] entering load()", LOG_DEBUG)
|
1630
|
+
# This method can be called more than once. After loading users and
|
1631
|
+
# groups, we just want to work with those after they are created. This
|
1632
|
+
# allows the method to be called more than once.
|
1633
|
+
loaded_users = []
|
1634
|
+
loaded_groups = []
|
1635
|
+
# Find all the groups first. We might need one to represent the main
|
1636
|
+
# group of a UNIX user.
|
1637
|
+
group_filter = Net::LDAP::Filter.eq("objectclass", "group")
|
1638
|
+
|
1639
|
+
@containers.each do |container|
|
1640
|
+
@ldap.search(:base => container.distinguished_name,
|
1641
|
+
:filter => group_filter,
|
1642
|
+
:scope => Net::LDAP::SearchScope_SingleLevel) do |entry|
|
1643
|
+
attr = group_ldap_entry_attr(entry)
|
1644
|
+
|
1645
|
+
# Skip any Group or UNIXGroup objects that already exist (have this
|
1646
|
+
# :name attribute).
|
1647
|
+
if find_group_by_name(attr[:name])
|
1648
|
+
RADUM::logger.log("\tNot loading group <#{attr[:name]}>: already" +
|
1649
|
+
" exists.", LOG_DEBUG)
|
1650
|
+
next
|
1651
|
+
end
|
1652
|
+
|
1653
|
+
# Note that groups add themselves to their container.
|
1654
|
+
unless attr[:gid].nil?
|
1655
|
+
attr[:nis_domain] = "radum" unless attr[:nis_domain]
|
1656
|
+
group = UNIXGroup.new :name => attr[:name], :container => container,
|
1657
|
+
:gid => attr[:gid], :type => attr[:type],
|
1658
|
+
:nis_domain => attr[:nis_domain],
|
1659
|
+
:rid => attr[:rid]
|
1660
|
+
group.unix_password = attr[:unix_password] if attr[:unix_password]
|
1661
|
+
loaded_groups.push group
|
1662
|
+
else
|
1663
|
+
group = Group.new :name => attr[:name], :container => container,
|
1664
|
+
:type => attr[:type], :rid => attr[:rid]
|
1665
|
+
loaded_groups.push group
|
1666
|
+
end
|
1667
|
+
end
|
1668
|
+
end
|
1669
|
+
|
1670
|
+
# Find all the users. The UNIX main group must be set for UNIXUser
|
1671
|
+
# objects, so it will be necessary to search for that.
|
1672
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
1673
|
+
|
1674
|
+
@containers.each do |container|
|
1675
|
+
@ldap.search(:base => container.distinguished_name,
|
1676
|
+
:filter => user_filter,
|
1677
|
+
:scope => Net::LDAP::SearchScope_SingleLevel) do |entry|
|
1678
|
+
attr = user_ldap_entry_attr(entry)
|
1679
|
+
|
1680
|
+
# Skip any User or UNIXUser objects that already exist (have this
|
1681
|
+
# :username attribute).
|
1682
|
+
if find_user_by_username(attr[:username])
|
1683
|
+
RADUM::logger.log("\tNot loading user <#{attr[:username]}>:" +
|
1684
|
+
" already exists.", LOG_DEBUG)
|
1685
|
+
next
|
1686
|
+
end
|
1687
|
+
|
1688
|
+
# Note that users add themselves to their container. We have to have
|
1689
|
+
# found the primary_group already, or we can't make the user. The
|
1690
|
+
# primary group is important information, but it is stored as a RID
|
1691
|
+
# value in the primaryGroupID AD attribute. The group membership
|
1692
|
+
# it defines is defined nowhere else however. We will print a warning
|
1693
|
+
# for any users skipped. This is why the AD object automatically
|
1694
|
+
# adds a cn=Users container.
|
1695
|
+
if attr[:primary_group]
|
1696
|
+
unless attr[:uid].nil? || attr[:gid].nil?
|
1697
|
+
if unix_main_group = find_group_by_gid(attr[:gid])
|
1698
|
+
attr[:nis_domain] = "radum" unless attr[:nis_domain]
|
1699
|
+
user = UNIXUser.new :username => attr[:username],
|
1700
|
+
:container => container,
|
1701
|
+
:primary_group => attr[:primary_group],
|
1702
|
+
:uid => attr[:uid],
|
1703
|
+
:unix_main_group => unix_main_group,
|
1704
|
+
:shell => attr[:shell],
|
1705
|
+
:home_directory => attr[:home_directory],
|
1706
|
+
:nis_domain => attr[:nis_domain],
|
1707
|
+
:disabled => attr[:disabled?],
|
1708
|
+
:rid => attr[:rid]
|
1709
|
+
user.distinguished_name = attr[:distinguished_name]
|
1710
|
+
user.first_name = attr[:first_name] if attr[:first_name]
|
1711
|
+
user.initials = attr[:initials] if attr[:initials]
|
1712
|
+
user.middle_name = attr[:middle_name] if attr[:middle_name]
|
1713
|
+
user.surname = attr[:surname] if attr[:surname]
|
1714
|
+
user.script_path = attr[:script_path] if attr[:script_path]
|
1715
|
+
user.profile_path = attr[:profile_path] if attr[:profile_path]
|
1716
|
+
|
1717
|
+
if attr[:local_drive] && attr[:local_path]
|
1718
|
+
user.connect_drive_to(attr[:local_drive], attr[:local_path])
|
1719
|
+
elsif attr[:local_path]
|
1720
|
+
user.local_path = attr[:local_path]
|
1721
|
+
end
|
1722
|
+
|
1723
|
+
user.gecos = attr[:gecos] if attr[:gecos]
|
1724
|
+
user.unix_password = attr[:unix_password] if
|
1725
|
+
attr[:unix_password]
|
1726
|
+
user.shadow_expire = attr[:shadow_expire] if
|
1727
|
+
attr[:shadow_expire]
|
1728
|
+
user.shadow_flag = attr[:shadow_flag] if attr[:shadow_flag]
|
1729
|
+
user.shadow_inactive = attr[:shadow_inactive] if
|
1730
|
+
attr[:shadow_inactive]
|
1731
|
+
user.shadow_last_change = attr[:shadow_last_change] if
|
1732
|
+
attr[:shadow_last_change]
|
1733
|
+
user.shadow_max = attr[:shadow_max] if attr[:shadow_max]
|
1734
|
+
user.shadow_min = attr[:shadow_min] if attr[:shadow_min]
|
1735
|
+
user.shadow_warning = attr[:shadow_warning] if
|
1736
|
+
attr[:shadow_warning]
|
1737
|
+
|
1738
|
+
if attr[:must_change_password?]
|
1739
|
+
user.force_change_password
|
1740
|
+
end
|
1741
|
+
|
1742
|
+
loaded_users.push user
|
1743
|
+
else
|
1744
|
+
RADUM::logger.log("Warning: Main UNIX group could not be " +
|
1745
|
+
"found for: " + attr[:username], LOG_NORMAL)
|
1746
|
+
RADUM::logger.log("Not loading #{attr[:username]}.", LOG_NORMAL)
|
1747
|
+
end
|
1748
|
+
else
|
1749
|
+
user = User.new :username => attr[:username],
|
1750
|
+
:container => container,
|
1751
|
+
:primary_group => attr[:primary_group],
|
1752
|
+
:disabled => attr[:disabled?],
|
1753
|
+
:rid => attr[:rid]
|
1754
|
+
user.distinguished_name = attr[:distinguished_name]
|
1755
|
+
user.first_name = attr[:first_name] if attr[:first_name]
|
1756
|
+
user.initials = attr[:initials] if attr[:initials]
|
1757
|
+
user.middle_name = attr[:middle_name] if attr[:middle_name]
|
1758
|
+
user.surname = attr[:surname] if attr[:surname]
|
1759
|
+
user.script_path = attr[:script_path] if attr[:script_path]
|
1760
|
+
user.profile_path = attr[:profile_path] if attr[:profile_path]
|
1761
|
+
|
1762
|
+
if attr[:local_drive] && attr[:local_path]
|
1763
|
+
user.connect_drive_to(attr[:local_drive], attr[:local_path])
|
1764
|
+
elsif attr[:local_path]
|
1765
|
+
user.local_path = attr[:local_path]
|
1766
|
+
end
|
1767
|
+
|
1768
|
+
if attr[:must_change_password?]
|
1769
|
+
user.force_change_password
|
1770
|
+
end
|
1771
|
+
|
1772
|
+
loaded_users.push user
|
1773
|
+
end
|
1774
|
+
else
|
1775
|
+
RADUM::logger.log("Warning: Windows primary group not found for: " +
|
1776
|
+
attr[:username], LOG_NORMAL)
|
1777
|
+
RADUM::logger.log("Not loading #{attr[:username]}.", LOG_NORMAL)
|
1778
|
+
end
|
1779
|
+
end
|
1780
|
+
end
|
1781
|
+
|
1782
|
+
# Add users to groups, which also adds the groups to the user, etc. The
|
1783
|
+
# Windows primary_group was taken care of when creating the users
|
1784
|
+
# previously. This can happen even if this method is called multiple
|
1785
|
+
# times because it is safe to add users to groups (and vice versa)
|
1786
|
+
# more than once. If the membership already exists, nothing happens.
|
1787
|
+
# Note that in this case, we do have to process all groups again in case
|
1788
|
+
# some of the new users are in already processed groups.
|
1789
|
+
#
|
1790
|
+
# Here it is key to process all groups, even if they were already
|
1791
|
+
# loaded once.
|
1792
|
+
groups.each do |group|
|
1793
|
+
begin
|
1794
|
+
# Note that this takes care of the case where a group was created
|
1795
|
+
# and then AD#load is called before AD#sync. In that case, the group
|
1796
|
+
# is not even in Active Directory yet and the pop method call will
|
1797
|
+
# not be possible because it does not exist. There are other ways
|
1798
|
+
# to check this, but I don't really care if it was not found because
|
1799
|
+
# I assume the user knows what they are doing if they do this type
|
1800
|
+
# of pattern.
|
1801
|
+
entry = @ldap.search(:base => group.distinguished_name,
|
1802
|
+
:filter => group_filter,
|
1803
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
1804
|
+
|
1805
|
+
entry.member.each do |member|
|
1806
|
+
# Groups can have groups or users as members, unlike UNIX where
|
1807
|
+
# groups cannot contain group members.
|
1808
|
+
member_group = find_group_by_dn(member)
|
1809
|
+
|
1810
|
+
if member_group
|
1811
|
+
group.add_group member_group
|
1812
|
+
end
|
1813
|
+
|
1814
|
+
member_user = find_user_by_dn(member)
|
1815
|
+
|
1816
|
+
if member_user
|
1817
|
+
group.add_user member_user
|
1818
|
+
end
|
1819
|
+
end
|
1820
|
+
rescue NoMethodError
|
1821
|
+
end
|
1822
|
+
end
|
1823
|
+
|
1824
|
+
# Set all users and groups as loaded. This has to be done last to make
|
1825
|
+
# sure the modified attribute is correct. The modified attribute needs
|
1826
|
+
# to be false, and it is hidden from direct access by the set_loaded
|
1827
|
+
# method. In this case "all users and groups" means ones we explicitly
|
1828
|
+
# processed because this method can be called more than once. If the
|
1829
|
+
# original object was loaded, but then modified, we don't want to reset
|
1830
|
+
# the modified attribute as well. We also don't want to set the loaded
|
1831
|
+
# attribute and reset the modified attribute for objects that were
|
1832
|
+
# created after an initial call to this method and were skipped on
|
1833
|
+
# later calls.
|
1834
|
+
#
|
1835
|
+
# Here it is key to only touch things explicitly loaded in this call.
|
1836
|
+
loaded_groups.each do |group|
|
1837
|
+
group.set_loaded
|
1838
|
+
end
|
1839
|
+
|
1840
|
+
loaded_users.each do |user|
|
1841
|
+
user.set_loaded
|
1842
|
+
end
|
1843
|
+
|
1844
|
+
RADUM::logger.log("[AD #{self.root}] exiting load()", LOG_DEBUG)
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
# Load the next free UID value. This is a convenience method that allows
|
1848
|
+
# one to find the next free UID value. This method returns the next free
|
1849
|
+
# UID value found while searching from the AD root and any current UID
|
1850
|
+
# values for UNIXUser objects that might not be in the Active Directory yet.
|
1851
|
+
# If nothing is found, the min_uid attribute is returned.
|
1852
|
+
def load_next_uid
|
1853
|
+
all_uids = load_ldap_uids
|
1854
|
+
next_uid = 0
|
1855
|
+
|
1856
|
+
# This accounts for any GIDs that might not be in Active Directory yet
|
1857
|
+
# as well.
|
1858
|
+
(all_uids + @uids).uniq.sort.each do |uid|
|
1859
|
+
if next_uid == 0 || next_uid + 1 == uid
|
1860
|
+
next_uid = uid
|
1861
|
+
else
|
1862
|
+
break
|
1863
|
+
end
|
1864
|
+
end
|
1865
|
+
|
1866
|
+
if next_uid == 0
|
1867
|
+
@min_uid
|
1868
|
+
else
|
1869
|
+
next_uid + 1
|
1870
|
+
end
|
1871
|
+
end
|
1872
|
+
|
1873
|
+
# Load the next free GID value. This is a convenince method that allows
|
1874
|
+
# one to find the next free GID value. This method returns the next free
|
1875
|
+
# GID value found while searching from the AD root and any current GID
|
1876
|
+
# values for UNIXGroup objects that might not be in the Active Directory
|
1877
|
+
# yet. If nothing is found, the min_gid attribute is returned.
|
1878
|
+
def load_next_gid
|
1879
|
+
all_gids = load_ldap_gids
|
1880
|
+
next_gid = 0
|
1881
|
+
|
1882
|
+
# This accounts for any GIDs that might not be in Active Directory yet
|
1883
|
+
# as well.
|
1884
|
+
(all_gids + @gids).uniq.sort.each do |gid|
|
1885
|
+
if next_gid == 0 || next_gid + 1 == gid
|
1886
|
+
next_gid = gid
|
1887
|
+
else
|
1888
|
+
break
|
1889
|
+
end
|
1890
|
+
end
|
1891
|
+
|
1892
|
+
if next_gid == 0
|
1893
|
+
@min_gid
|
1894
|
+
else
|
1895
|
+
next_gid + 1
|
1896
|
+
end
|
1897
|
+
end
|
1898
|
+
|
1899
|
+
# Synchronize all modified Container, User, UNIXUser, Group, and UNIXGroup
|
1900
|
+
# objects to Active Directory. This will create entries as needed after
|
1901
|
+
# checking to make sure they do not already exist. New attributes will be
|
1902
|
+
# added, unset attributes will be removed, and modified attributes will be
|
1903
|
+
# updated automatically. Removed objects will be deleted from Active
|
1904
|
+
# Directory.
|
1905
|
+
#
|
1906
|
+
# You generally should call AD#load to ensure the RADUM system has a valid
|
1907
|
+
# representation of the Active Directory objects. You can call AD#sync
|
1908
|
+
# without calling AD#load first, but your object values are authoritative.
|
1909
|
+
# Unless you set every attribute correctly, unset object attributes will
|
1910
|
+
# overwrite current values in Active Directory. Note that AD#sync will
|
1911
|
+
# not touch Active Directory group memberships it does not know about
|
1912
|
+
# explicitly, so AD#sync will not remove group and user memberships in
|
1913
|
+
# groups that were not explicitly removed in RADUM. The general RADUM
|
1914
|
+
# pattern is:
|
1915
|
+
#
|
1916
|
+
# * AD.new(...)
|
1917
|
+
# * Container.new(...) [for any Containers of interest]
|
1918
|
+
# * AD#load()
|
1919
|
+
# * Create, update, or remove existing loaded objects.
|
1920
|
+
# * AD#sync()
|
1921
|
+
def sync
|
1922
|
+
RADUM::logger.log("[AD #{self.root}] entering sync()", LOG_DEBUG)
|
1923
|
+
users_to_destroy = []
|
1924
|
+
containers_to_destroy = []
|
1925
|
+
|
1926
|
+
# First, delete any users that have been removed from a container here.
|
1927
|
+
# We need to remove users first because a group cannot be removed if
|
1928
|
+
# a user has it as their primary Windows group. Just in case, we remove
|
1929
|
+
# the removed users first. The same applies if the group is some other
|
1930
|
+
# user's UNIX main group. The code in this module makes sure that doesn't
|
1931
|
+
# happen for objects it knows about, but there could be others in Active
|
1932
|
+
# Directory the module does not know about.
|
1933
|
+
removed_users.each do |user|
|
1934
|
+
delete_user user
|
1935
|
+
# The delete_user() method cannot destroy a user because the system
|
1936
|
+
# still needs to reference the user when update_group() is called so
|
1937
|
+
# that the deleted user will end up having their UNIX attributes removed
|
1938
|
+
# from a UNIXGroup if that applies when updating the specific group.
|
1939
|
+
# This is not needed for Group or UNIXGroup objects because they only
|
1940
|
+
# have membership from the Windows perspective - and that's updated by
|
1941
|
+
# simply deleting the Group or UNIXGroup.
|
1942
|
+
users_to_destroy.push user
|
1943
|
+
end
|
1944
|
+
|
1945
|
+
# Second, remove any groups that have been removed from a contianer here.
|
1946
|
+
removed_groups.each do |group|
|
1947
|
+
# This method checks if the group is some other user's primary Windows
|
1948
|
+
# group by searching the entire Active Directory. A group cannot be
|
1949
|
+
# removed if it is any user's primary Windows group. The same applies
|
1950
|
+
# if the group is some other user's UNIX main group. The code in this
|
1951
|
+
# module makes sure that doesn't happen for objects it knows about, but
|
1952
|
+
# there could be others in Active Directory the module does not know
|
1953
|
+
# about, hence the checks in delete_group().
|
1954
|
+
delete_group group
|
1955
|
+
end
|
1956
|
+
|
1957
|
+
# Third, remove any containers that have been removed. This can only be
|
1958
|
+
# done after all the user and group removals hae been dealt with. This
|
1959
|
+
# can still fail if there are any objects in Active Directory inside of
|
1960
|
+
# the container (such as another container). Note that the
|
1961
|
+
# AD#remove_container method makes sure that a container is not removed
|
1962
|
+
# if it contains another container in the first place.
|
1963
|
+
@removed_containers.each do |container|
|
1964
|
+
delete_container container
|
1965
|
+
# The delete_container() method cannot destroy a container because the
|
1966
|
+
# system still needs a reference when update_group() is called for
|
1967
|
+
# users that have been deleted from the system to ensure their
|
1968
|
+
# membership in UNIX groups from the UNIX perspective is deleted.
|
1969
|
+
# The removed_users() method depends on the container still being
|
1970
|
+
# there for this later processing.
|
1971
|
+
containers_to_destroy.push container
|
1972
|
+
end
|
1973
|
+
|
1974
|
+
# Fourth, create any containers or organizational units that do not
|
1975
|
+
# already exist.
|
1976
|
+
@containers.each do |container|
|
1977
|
+
# This method only creates containers that do not already exist. Since
|
1978
|
+
# containers are not loaded directly at first, their status is directly
|
1979
|
+
# tested in the method.
|
1980
|
+
create_container container
|
1981
|
+
end
|
1982
|
+
|
1983
|
+
# Fifth, make sure any groups that need to be created are added to Active
|
1984
|
+
# Directory.
|
1985
|
+
groups.each do |group|
|
1986
|
+
# This method checks if the group actually needs to be created or not.
|
1987
|
+
create_group group
|
1988
|
+
end
|
1989
|
+
|
1990
|
+
# Sixth, make sure any users that need to be created are added to Active
|
1991
|
+
# Directory.
|
1992
|
+
users.each do |user|
|
1993
|
+
# This method checks if the user actually needs to be created or not.
|
1994
|
+
create_user user
|
1995
|
+
end
|
1996
|
+
|
1997
|
+
# Seventh, update any modified attributes on each group.
|
1998
|
+
groups.each do |group|
|
1999
|
+
# This method figures out what attributes need to be updated when
|
2000
|
+
# compared to Active Directory. All objects should exist in Active
|
2001
|
+
# Directory at this point, but the method handles cases where the
|
2002
|
+
# object is not in Active Directory by skipping the update in that
|
2003
|
+
# case.
|
2004
|
+
update_group group
|
2005
|
+
end
|
2006
|
+
|
2007
|
+
# Eighth, update any modified attributs on each user.
|
2008
|
+
users.each do |user|
|
2009
|
+
# This method figures out what attributes need to be updated when
|
2010
|
+
# compared to Active Directory. All objects should exist in Active
|
2011
|
+
# Directory at this point, but the method handles cases where the
|
2012
|
+
# object is not in Active Directory by skipping the update in that
|
2013
|
+
# case.
|
2014
|
+
update_user user
|
2015
|
+
end
|
2016
|
+
|
2017
|
+
# Finally, destroy any user and container objects that were deleted.
|
2018
|
+
users_to_destroy.each do |user|
|
2019
|
+
RADUM::logger.log("[AD #{self.root}] destroying user" +
|
2020
|
+
" <#{user.username}> at end of sync().", LOG_DEBUG)
|
2021
|
+
user.container.destroy_user user
|
2022
|
+
end
|
2023
|
+
|
2024
|
+
# Container objects are destroyed even if they were not removed from
|
2025
|
+
# Active Directory through LDAP. If they were destroyed or removed RADUM,
|
2026
|
+
# it is safe to discard their reference here. If they were not deleted
|
2027
|
+
# from Active Directory, that means there was some reference we were not
|
2028
|
+
# aware of in Active Directory - but we can still forget about them.
|
2029
|
+
containers_to_destroy.each do |container|
|
2030
|
+
RADUM::logger.log("[AD #{self.root}] destroying container" +
|
2031
|
+
" <#{container.name}> at end of sync().", LOG_DEBUG)
|
2032
|
+
destroy_container container
|
2033
|
+
end
|
2034
|
+
|
2035
|
+
RADUM::logger.log("[AD #{self.root}] exiting sync()", LOG_DEBUG)
|
2036
|
+
end
|
2037
|
+
|
2038
|
+
# The String representation of the AD object.
|
2039
|
+
def to_s
|
2040
|
+
"AD [#{@root} #{@server}:#{@port}]"
|
2041
|
+
end
|
2042
|
+
|
2043
|
+
private
|
2044
|
+
|
2045
|
+
# Unpack a RID from the SID value in the LDAP objectSid attribute for a
|
2046
|
+
# user or group in Active Directory.
|
2047
|
+
#
|
2048
|
+
# === Parameter Types
|
2049
|
+
#
|
2050
|
+
# * sid [binary LDAP attribute value]
|
2051
|
+
def sid2rid_int(sid)
|
2052
|
+
sid.unpack("qV*").pop.to_i
|
2053
|
+
end
|
2054
|
+
|
2055
|
+
# Convert a string to UTF-16LE. For ASCII characters, the result should be
|
2056
|
+
# each character followed by a NULL, so this is very easy. Windows expects
|
2057
|
+
# a UTF-16LE string for the unicodePwd attribute. Note that the password
|
2058
|
+
# Active Directory is expecting for the unicodePwd attribute has to be
|
2059
|
+
# explicitly quoted.
|
2060
|
+
#
|
2061
|
+
# === Parameter Types
|
2062
|
+
#
|
2063
|
+
# * str [String]
|
2064
|
+
def str2utf16le(str)
|
2065
|
+
('"' + str + '"').gsub(/./) { |c| "#{c}\0" }
|
2066
|
+
end
|
2067
|
+
|
2068
|
+
# Return a random number character as a string.
|
2069
|
+
def random_number
|
2070
|
+
srand
|
2071
|
+
(rand 10).to_s
|
2072
|
+
end
|
2073
|
+
|
2074
|
+
# Return a random lowercase letter as a string.
|
2075
|
+
def random_lowercase
|
2076
|
+
srand
|
2077
|
+
sprintf "%c", ((rand 26) + 97)
|
2078
|
+
end
|
2079
|
+
|
2080
|
+
# Return a random uppercase letter as a string.
|
2081
|
+
def random_uppercase
|
2082
|
+
random_lowercase.swapcase
|
2083
|
+
end
|
2084
|
+
|
2085
|
+
# Return a random symbol as a string.
|
2086
|
+
def random_symbol
|
2087
|
+
srand
|
2088
|
+
|
2089
|
+
case rand 4
|
2090
|
+
when 0
|
2091
|
+
code = rand(15) + 33
|
2092
|
+
when 1
|
2093
|
+
code = rand(7) + 58
|
2094
|
+
when 2
|
2095
|
+
code = rand(6) + 91
|
2096
|
+
when 3
|
2097
|
+
code = rand(4) + 123
|
2098
|
+
end
|
2099
|
+
|
2100
|
+
sprintf "%c", code
|
2101
|
+
end
|
2102
|
+
|
2103
|
+
# Return a random 8 character password as a string. There is a formula to
|
2104
|
+
# try and avoid any problems with Active Directory password requirements.
|
2105
|
+
# If you don't like this, supply your own password for User and UNIXUser
|
2106
|
+
# objects.
|
2107
|
+
def random_password
|
2108
|
+
random_lowercase + random_number + random_lowercase + random_uppercase +
|
2109
|
+
random_symbol + random_number + random_uppercase + random_symbol
|
2110
|
+
end
|
2111
|
+
|
2112
|
+
# Return an array of all GID values in Active Directory.
|
2113
|
+
def load_ldap_gids
|
2114
|
+
all_gids = []
|
2115
|
+
group_filter = Net::LDAP::Filter.eq("objectclass", "group")
|
2116
|
+
|
2117
|
+
@ldap.search(:base => @root, :filter => group_filter) do |entry|
|
2118
|
+
begin
|
2119
|
+
gid = entry.gidNumber.pop.to_i
|
2120
|
+
all_gids.push gid
|
2121
|
+
rescue NoMethodError
|
2122
|
+
end
|
2123
|
+
end
|
2124
|
+
|
2125
|
+
all_gids
|
2126
|
+
end
|
2127
|
+
|
2128
|
+
# Return an array of all UID values in Active Directory.
|
2129
|
+
def load_ldap_uids
|
2130
|
+
all_uids = []
|
2131
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
2132
|
+
|
2133
|
+
@ldap.search(:base => @root, :filter => user_filter) do |entry|
|
2134
|
+
begin
|
2135
|
+
uid = entry.uidNumber.pop.to_i
|
2136
|
+
all_uids.push uid
|
2137
|
+
rescue NoMethodError
|
2138
|
+
end
|
2139
|
+
end
|
2140
|
+
|
2141
|
+
all_uids
|
2142
|
+
end
|
2143
|
+
|
2144
|
+
# Return a hash with an Active Directory group's base LDAP attributes. The
|
2145
|
+
# key is the RADUM group attribute name and the value is the computed value
|
2146
|
+
# from the group's attributes in Active Directory.
|
2147
|
+
#
|
2148
|
+
# === Parameter Types
|
2149
|
+
#
|
2150
|
+
# * entry [String]
|
2151
|
+
def group_ldap_entry_attr(entry)
|
2152
|
+
attr = {}
|
2153
|
+
# These are attributes that might be empty. If they are empty,
|
2154
|
+
# a NoMethodError exception will be raised. We have to check each
|
2155
|
+
# individually and set an initial indicator value (nil). All the
|
2156
|
+
# other attributes should exist and do not require this level of
|
2157
|
+
# checking.
|
2158
|
+
attr[:gid] = nil
|
2159
|
+
attr[:nis_domain] = nil
|
2160
|
+
attr[:unix_password] = nil
|
2161
|
+
|
2162
|
+
begin
|
2163
|
+
attr[:gid] = entry.gidNumber.pop.to_i
|
2164
|
+
rescue NoMethodError
|
2165
|
+
end
|
2166
|
+
|
2167
|
+
begin
|
2168
|
+
attr[:nis_domain] = entry.msSFU30NisDomain.pop
|
2169
|
+
rescue NoMethodError
|
2170
|
+
end
|
2171
|
+
|
2172
|
+
begin
|
2173
|
+
attr[:unix_password] = entry.unixUserPassword.pop
|
2174
|
+
rescue NoMethodError
|
2175
|
+
end
|
2176
|
+
|
2177
|
+
attr[:name] = entry.name.pop
|
2178
|
+
attr[:rid] = sid2rid_int(entry.objectSid.pop)
|
2179
|
+
attr[:type] = entry.groupType.pop.to_i
|
2180
|
+
return attr
|
2181
|
+
end
|
2182
|
+
|
2183
|
+
# Return a hash with an Active Directory user's base LDAP attributes. The
|
2184
|
+
# key is the RADUM user attribute name and the value is the computed value
|
2185
|
+
# from the user's attributes in Active Directory.
|
2186
|
+
#
|
2187
|
+
# === Parameter Types
|
2188
|
+
#
|
2189
|
+
# * entry [String]
|
2190
|
+
def user_ldap_entry_attr(entry)
|
2191
|
+
attr = {}
|
2192
|
+
# These are attributes that might be empty. If they are empty,
|
2193
|
+
# a NoMethodError exception will be raised. We have to check each
|
2194
|
+
# individually and set an initial indicator value (nil). All the
|
2195
|
+
# other attributes should exist and do not require this level of
|
2196
|
+
# checking.
|
2197
|
+
attr[:first_name] = nil
|
2198
|
+
attr[:initials] = nil
|
2199
|
+
attr[:middle_name] = nil
|
2200
|
+
attr[:surname] = nil
|
2201
|
+
attr[:script_path] = nil
|
2202
|
+
attr[:profile_path] = nil
|
2203
|
+
attr[:local_path] = nil
|
2204
|
+
attr[:local_drive] = nil
|
2205
|
+
attr[:uid] = nil
|
2206
|
+
attr[:gid] = nil
|
2207
|
+
attr[:nis_domain] = nil
|
2208
|
+
attr[:gecos] = nil
|
2209
|
+
attr[:unix_password] = nil
|
2210
|
+
attr[:shadow_expire] = nil
|
2211
|
+
attr[:shadow_flag] = nil
|
2212
|
+
attr[:shadow_inactive] = nil
|
2213
|
+
attr[:shadow_last_change] = nil
|
2214
|
+
attr[:shadow_max] = nil
|
2215
|
+
attr[:shadow_min] = nil
|
2216
|
+
attr[:shadow_warning] = nil
|
2217
|
+
attr[:shell] = nil
|
2218
|
+
attr[:home_directory] = nil
|
2219
|
+
|
2220
|
+
begin
|
2221
|
+
attr[:first_name] = entry.givenName.pop
|
2222
|
+
rescue NoMethodError
|
2223
|
+
end
|
2224
|
+
|
2225
|
+
begin
|
2226
|
+
attr[:initials] = entry.initials.pop
|
2227
|
+
rescue NoMethodError
|
2228
|
+
end
|
2229
|
+
|
2230
|
+
begin
|
2231
|
+
attr[:middle_name] = entry.middleName.pop
|
2232
|
+
rescue NoMethodError
|
2233
|
+
end
|
2234
|
+
|
2235
|
+
begin
|
2236
|
+
attr[:surname] = entry.sn.pop
|
2237
|
+
rescue NoMethodError
|
2238
|
+
end
|
2239
|
+
|
2240
|
+
begin
|
2241
|
+
attr[:script_path] = entry.scriptPath.pop
|
2242
|
+
rescue NoMethodError
|
2243
|
+
end
|
2244
|
+
|
2245
|
+
begin
|
2246
|
+
attr[:profile_path] = entry.profilePath.pop
|
2247
|
+
rescue NoMethodError
|
2248
|
+
end
|
2249
|
+
|
2250
|
+
begin
|
2251
|
+
attr[:local_path] = entry.homeDirectory.pop
|
2252
|
+
rescue NoMethodError
|
2253
|
+
end
|
2254
|
+
|
2255
|
+
begin
|
2256
|
+
attr[:local_drive] = entry.homeDrive.pop
|
2257
|
+
rescue NoMethodError
|
2258
|
+
end
|
2259
|
+
|
2260
|
+
begin
|
2261
|
+
attr[:uid] = entry.uidNumber.pop.to_i
|
2262
|
+
rescue NoMethodError
|
2263
|
+
end
|
2264
|
+
|
2265
|
+
begin
|
2266
|
+
attr[:gid] = entry.gidNumber.pop.to_i
|
2267
|
+
rescue NoMethodError
|
2268
|
+
end
|
2269
|
+
|
2270
|
+
begin
|
2271
|
+
attr[:nis_domain] = entry.msSFU30NisDomain.pop
|
2272
|
+
rescue NoMethodError
|
2273
|
+
end
|
2274
|
+
|
2275
|
+
begin
|
2276
|
+
attr[:gecos] = entry.gecos.pop
|
2277
|
+
rescue NoMethodError
|
2278
|
+
end
|
2279
|
+
|
2280
|
+
begin
|
2281
|
+
attr[:unix_password] = entry.unixUserPassword.pop
|
2282
|
+
rescue NoMethodError
|
2283
|
+
end
|
2284
|
+
|
2285
|
+
begin
|
2286
|
+
attr[:shadow_expire] = entry.shadowExpire.pop.to_i
|
2287
|
+
rescue NoMethodError
|
2288
|
+
end
|
2289
|
+
|
2290
|
+
begin
|
2291
|
+
attr[:shadow_flag] = entry.shadowFlag.pop.to_i
|
2292
|
+
rescue NoMethodError
|
2293
|
+
end
|
2294
|
+
|
2295
|
+
begin
|
2296
|
+
attr[:shadow_inactive] = entry.shadowInactive.pop.to_i
|
2297
|
+
rescue NoMethodError
|
2298
|
+
end
|
2299
|
+
|
2300
|
+
begin
|
2301
|
+
attr[:shadow_last_change] = entry.shadowLastChange.pop.to_i
|
2302
|
+
rescue NoMethodError
|
2303
|
+
end
|
2304
|
+
|
2305
|
+
begin
|
2306
|
+
attr[:shadow_max] = entry.shadowMax.pop.to_i
|
2307
|
+
rescue NoMethodError
|
2308
|
+
end
|
2309
|
+
|
2310
|
+
begin
|
2311
|
+
attr[:shadow_min] = entry.shadowMin.pop.to_i
|
2312
|
+
rescue NoMethodError
|
2313
|
+
end
|
2314
|
+
|
2315
|
+
begin
|
2316
|
+
attr[:shadow_warning] = entry.shadowWarning.pop.to_i
|
2317
|
+
rescue NoMethodError
|
2318
|
+
end
|
2319
|
+
|
2320
|
+
begin
|
2321
|
+
attr[:shell] = entry.loginShell.pop
|
2322
|
+
rescue NoMethodError
|
2323
|
+
end
|
2324
|
+
|
2325
|
+
begin
|
2326
|
+
attr[:home_directory] = entry.unixHomeDirectory.pop
|
2327
|
+
rescue NoMethodError
|
2328
|
+
end
|
2329
|
+
|
2330
|
+
attr[:disabled?] = (entry.userAccountControl.pop.to_i ==
|
2331
|
+
UF_NORMAL_ACCOUNT + UF_ACCOUNTDISABLE ? true : false)
|
2332
|
+
attr[:must_change_password?] = (entry.pwdLastSet.pop.to_i == 0)
|
2333
|
+
attr[:primary_group] = find_group_by_rid(entry.primaryGroupID.pop.to_i)
|
2334
|
+
attr[:rid] = sid2rid_int(entry.objectSid.pop)
|
2335
|
+
attr[:username] = entry.sAMAccountName.pop
|
2336
|
+
attr[:distinguished_name] = entry.distinguishedName.pop
|
2337
|
+
return attr
|
2338
|
+
end
|
2339
|
+
|
2340
|
+
# Check the LDAP operation result code for an error message. This method
|
2341
|
+
# raises a RuntimeError if the operation result code is 49, which indicates
|
2342
|
+
# an authentication credentials error.
|
2343
|
+
def check_ldap_result
|
2344
|
+
unless @ldap.get_operation_result.code == 0
|
2345
|
+
RADUM::logger.log("LDAP ERROR: " + @ldap.get_operation_result.message,
|
2346
|
+
LOG_NORMAL)
|
2347
|
+
RADUM::logger.log("[Error code: " +
|
2348
|
+
@ldap.get_operation_result.code.to_s + "]",
|
2349
|
+
LOG_NORMAL)
|
2350
|
+
if @ldap.get_operation_result.code == 49
|
2351
|
+
raise "LDAP authentication credentials error."
|
2352
|
+
end
|
2353
|
+
end
|
2354
|
+
end
|
2355
|
+
|
2356
|
+
# Delete a Container from Active Directory. The Container is only deleted
|
2357
|
+
# if it is found.
|
2358
|
+
#
|
2359
|
+
# === Parameter Types
|
2360
|
+
#
|
2361
|
+
# * container [Container]
|
2362
|
+
def delete_container(container)
|
2363
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
2364
|
+
" delete_container(<#{container.name}>)", LOG_DEBUG)
|
2365
|
+
|
2366
|
+
if container.name =~ /^[Oo][Uu]=/
|
2367
|
+
type = "organizationalUnit"
|
2368
|
+
elsif container.name =~ /^[Cc][Nn]=/
|
2369
|
+
type = "container"
|
2370
|
+
else
|
2371
|
+
RADUM::logger.log("SYNC ERROR: " + container.name +
|
2372
|
+
" - unknown Container type.", LOG_NORMAL)
|
2373
|
+
return
|
2374
|
+
end
|
2375
|
+
|
2376
|
+
container_filter = Net::LDAP::Filter.eq("objectclass", type)
|
2377
|
+
found = @ldap.search(:base => container.distinguished_name,
|
2378
|
+
:filter => container_filter,
|
2379
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
2380
|
+
:return_result => false)
|
2381
|
+
|
2382
|
+
if found == false
|
2383
|
+
RADUM::logger.log("\t#{container.distinguished_name} not found" +
|
2384
|
+
" - not deleting.", LOG_DEBUG)
|
2385
|
+
return
|
2386
|
+
end
|
2387
|
+
|
2388
|
+
@ldap.delete :dn => container.distinguished_name
|
2389
|
+
check_ldap_result
|
2390
|
+
# Destroying the container is delayed until the end of the AD#sync call
|
2391
|
+
# because we still need a reference indicating that a user was removed
|
2392
|
+
# from Active Directory to ensure the user's UNIX group membership
|
2393
|
+
# attributes are updated properly in AD#update_group if necessary. The
|
2394
|
+
# user's Windows group memberships are handled automatically when they
|
2395
|
+
# are deleted from Active Directory. The AD class uses references to
|
2396
|
+
# containers to figure this out.
|
2397
|
+
end
|
2398
|
+
|
2399
|
+
# Create a Container in Active Directory. Each Container is searched for
|
2400
|
+
# directly and created if it does not already exist. This method also
|
2401
|
+
# automatically creates parent containers as required. This is safe to
|
2402
|
+
# do, even if one of those was also passed to this method later (since it
|
2403
|
+
# would then be found).
|
2404
|
+
#
|
2405
|
+
# === Parameter Types
|
2406
|
+
#
|
2407
|
+
# * container [Container]
|
2408
|
+
def create_container(container)
|
2409
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
2410
|
+
" create_container(<#{container.name}>)", LOG_DEBUG)
|
2411
|
+
distinguished_name = @root
|
2412
|
+
# This depends on the fact that the Container name had all spaces stripped
|
2413
|
+
# out in the initialize() method of the Container class.
|
2414
|
+
container.name.split(/,/).reverse.each do |current_name|
|
2415
|
+
# We have to keep track of the current path so that we have a
|
2416
|
+
# distinguished name to work wtih.
|
2417
|
+
distinguished_name = "#{current_name},#{distinguished_name}"
|
2418
|
+
|
2419
|
+
if current_name =~ /^[Oo][Uu]=/
|
2420
|
+
type = "organizationalUnit"
|
2421
|
+
elsif current_name =~ /^[Cc][Nn]=/
|
2422
|
+
type = "container"
|
2423
|
+
else
|
2424
|
+
RADUM::logger.log("SYNC ERROR: " + container.name +
|
2425
|
+
" - unknown Container type.", LOG_NORMAL)
|
2426
|
+
return
|
2427
|
+
end
|
2428
|
+
|
2429
|
+
container_filter = Net::LDAP::Filter.eq("objectclass", type)
|
2430
|
+
# The return value will be false explicitly if the search fails,
|
2431
|
+
# otherwise it will be an array of entries. Therefore it is important
|
2432
|
+
# to check for false explicitly for a failure. A failure indicates
|
2433
|
+
# that the container needs to be created.
|
2434
|
+
found = @ldap.search(:base => distinguished_name,
|
2435
|
+
:filter => container_filter,
|
2436
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
2437
|
+
:return_result => false)
|
2438
|
+
|
2439
|
+
if found == false
|
2440
|
+
RADUM::logger.log("\t#{distinguished_name} not found - creating.",
|
2441
|
+
LOG_DEBUG)
|
2442
|
+
|
2443
|
+
# Note that all the attributes need to be strings in the attr hash.
|
2444
|
+
if type == "organizationalUnit"
|
2445
|
+
attr = {
|
2446
|
+
:name => current_name.split(/,/)[0].gsub(/[Oo][Uu]=/, ""),
|
2447
|
+
:objectclass => ["top", "organizationalUnit"]
|
2448
|
+
}
|
2449
|
+
elsif type == "container"
|
2450
|
+
name = current_name.split(/,/)[0].gsub(/[Cc][Nn]=/, "")
|
2451
|
+
|
2452
|
+
attr = {
|
2453
|
+
:name => name,
|
2454
|
+
:objectclass => ["top", "container"]
|
2455
|
+
}
|
2456
|
+
else
|
2457
|
+
RADUM::logger.log("SYNC ERROR: " + container.name +
|
2458
|
+
" (#{current_name}) - unknown Container type.",
|
2459
|
+
LOG_NORMAL)
|
2460
|
+
return
|
2461
|
+
end
|
2462
|
+
|
2463
|
+
@ldap.add :dn => distinguished_name, :attributes => attr
|
2464
|
+
check_ldap_result
|
2465
|
+
else
|
2466
|
+
RADUM::logger.log("\t#{distinguished_name} found - not creating.",
|
2467
|
+
LOG_DEBUG)
|
2468
|
+
end
|
2469
|
+
end
|
2470
|
+
end
|
2471
|
+
|
2472
|
+
# Determine if the group is anyone's primary Windows group in Active
|
2473
|
+
# Directory. Returns true if the group is anyone's primary Windows group,
|
2474
|
+
# false otherwise. This works for Group and UNIXGroup objects.
|
2475
|
+
#
|
2476
|
+
# === Parameter Types
|
2477
|
+
#
|
2478
|
+
# * group [Group or UNIXGroup]
|
2479
|
+
def ldap_is_primary_windows_group?(group)
|
2480
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
2481
|
+
|
2482
|
+
@ldap.search(:base => @root, :filter => user_filter) do |entry|
|
2483
|
+
rid = entry.primaryGroupID.pop.to_i
|
2484
|
+
return true if rid == group.rid
|
2485
|
+
end
|
2486
|
+
|
2487
|
+
false
|
2488
|
+
end
|
2489
|
+
|
2490
|
+
# Determine if the group is anyone's UNIX main group in Active Directory.
|
2491
|
+
# Returns true if the group is anyone's UNIX main group, false otherwise.
|
2492
|
+
# This works for UNIXGroup objects and returns false for Group objects.
|
2493
|
+
#
|
2494
|
+
# === Parameter Types
|
2495
|
+
#
|
2496
|
+
# * group [UNIXGroup]
|
2497
|
+
def ldap_is_unix_main_group?(group)
|
2498
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
2499
|
+
|
2500
|
+
if group.instance_of?(UNIXGroup)
|
2501
|
+
@ldap.search(:base => @root, :filter => user_filter) do |entry|
|
2502
|
+
begin
|
2503
|
+
gid = entry.gidNumber.pop.to_i
|
2504
|
+
return true if gid == group.gid
|
2505
|
+
rescue NoMethodError
|
2506
|
+
end
|
2507
|
+
end
|
2508
|
+
end
|
2509
|
+
|
2510
|
+
false
|
2511
|
+
end
|
2512
|
+
|
2513
|
+
# Delete a Group or UNIXGroup from Active Directory.
|
2514
|
+
#
|
2515
|
+
# === Parameter Types
|
2516
|
+
#
|
2517
|
+
# * group [Group or UNIXGroup]
|
2518
|
+
def delete_group(group)
|
2519
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
2520
|
+
" delete_group(<#{group.name}>)", LOG_DEBUG)
|
2521
|
+
# First check to make sure the group is not the primary Windows group
|
2522
|
+
# or UNIX main group for any user in Active Directory. We could probably
|
2523
|
+
# rely on the attempt to delete the group failing, but I don't like doing
|
2524
|
+
# that. Yes, it is much less efficient this way, but removing a group is
|
2525
|
+
# not very common in my experience. Also note that this would probably not
|
2526
|
+
# fail for a UNIX main group because that's pretty much "tacked" onto
|
2527
|
+
# the standard Windows Active Directory logic (at least, I have been
|
2528
|
+
# able to remove a group that was someone's UNIX main group before, but
|
2529
|
+
# not their primary Windows group).
|
2530
|
+
found_primary = ldap_is_primary_windows_group?(group)
|
2531
|
+
found_unix = ldap_is_unix_main_group?(group)
|
2532
|
+
|
2533
|
+
unless found_primary || found_unix
|
2534
|
+
RADUM::logger.log("\tDeleted group <#{group.name}>.", LOG_DEBUG)
|
2535
|
+
@ldap.delete :dn => group.distinguished_name
|
2536
|
+
check_ldap_result
|
2537
|
+
# Now that the group has been removed from Active Directory, it is
|
2538
|
+
# destroyed from the Container it belongs to. There is no need to
|
2539
|
+
# care about it anymore.
|
2540
|
+
RADUM::logger.log("\tDestroying group <#{group.name}>.", LOG_DEBUG)
|
2541
|
+
group.container.destroy_group group
|
2542
|
+
else
|
2543
|
+
RADUM::logger.log("\tCannot delete group <#{group.name}>:", LOG_DEBUG)
|
2544
|
+
|
2545
|
+
if found_primary
|
2546
|
+
RADUM::logger("\t<#{group.name}> is the primary Windows group for a" +
|
2547
|
+
" user in Active Directory.", LOG_DEBUG)
|
2548
|
+
end
|
2549
|
+
|
2550
|
+
if found_unix
|
2551
|
+
RADUM::logger.log("\t<#{group.name}> is the UNIX main group for a" +
|
2552
|
+
" user in Active Directory.", LOG_DEBUG)
|
2553
|
+
end
|
2554
|
+
end
|
2555
|
+
end
|
2556
|
+
|
2557
|
+
# Create a Group or UNIXGroup in Active Directory. The Group or UNIXGroup
|
2558
|
+
# must have its loaded attribute set to false, which indicates it was
|
2559
|
+
# manually created. This method checks that, so it is not necessary to
|
2560
|
+
# worry about checking first. This method also makes sure the group is not
|
2561
|
+
# already in Active Directory, in case someone created a group that would
|
2562
|
+
# match one that already exists. Therefore, any Group or UNIXGroup can be
|
2563
|
+
# passed into this method.
|
2564
|
+
#
|
2565
|
+
# === Parameter Types
|
2566
|
+
#
|
2567
|
+
# * group [Group or UNIXGroup]
|
2568
|
+
def create_group(group)
|
2569
|
+
unless group.loaded?
|
2570
|
+
group_filter = Net::LDAP::Filter.eq("objectclass", "group")
|
2571
|
+
# The return value will be false explicitly if the search fails,
|
2572
|
+
# otherwise it will be an array of entries. Therefore it is important
|
2573
|
+
# to check for false explicitly for a failure. A failure indicates
|
2574
|
+
# that the group needs to be created.
|
2575
|
+
found = @ldap.search(:base => group.distinguished_name,
|
2576
|
+
:filter => group_filter,
|
2577
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
2578
|
+
:return_result => false)
|
2579
|
+
|
2580
|
+
# The group should not already exist of course. This is to make sure
|
2581
|
+
# it is not already there in the case it was manually created but
|
2582
|
+
# matches a group that already exists.
|
2583
|
+
if found == false
|
2584
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
2585
|
+
" create_group(<#{group.name}>)", LOG_DEBUG)
|
2586
|
+
|
2587
|
+
# Note that all the attributes need to be strings in this hash.
|
2588
|
+
attr = {
|
2589
|
+
:groupType => group.type.to_s,
|
2590
|
+
# All groups are of the objectclasses "top" and "group".
|
2591
|
+
:objectclass => ["top", "group"],
|
2592
|
+
:sAMAccountName => group.name
|
2593
|
+
}
|
2594
|
+
|
2595
|
+
attr.merge!({
|
2596
|
+
:gidNumber => group.gid.to_s,
|
2597
|
+
:msSFU30Name => group.name,
|
2598
|
+
:msSFU30NisDomain => group.nis_domain,
|
2599
|
+
:unixUserPassword => group.unix_password
|
2600
|
+
}) if group.instance_of?(UNIXGroup)
|
2601
|
+
|
2602
|
+
if group.instance_of?(UNIXGroup)
|
2603
|
+
attr.merge!({ :description => "UNIX group #{group.name}" })
|
2604
|
+
else
|
2605
|
+
attr.merge!({ :description => "Group #{group.name}" })
|
2606
|
+
end
|
2607
|
+
|
2608
|
+
RADUM::logger.log("\n" + attr.to_yaml + "\n\n", LOG_DEBUG)
|
2609
|
+
@ldap.add :dn => group.distinguished_name, :attributes => attr
|
2610
|
+
check_ldap_result
|
2611
|
+
end
|
2612
|
+
|
2613
|
+
# At this point, we need to pull the RID value back out and set it
|
2614
|
+
# because it is needed later. This is needed even if the group was
|
2615
|
+
# found and not created because it had not been loaded yet. Later
|
2616
|
+
# calls like create_user() look for the RID of the primary_group.
|
2617
|
+
entry = @ldap.search(:base => group.distinguished_name,
|
2618
|
+
:filter => group_filter,
|
2619
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
2620
|
+
group.set_rid sid2rid_int(entry.objectSid.pop)
|
2621
|
+
# Note: unlike a user, the group cannot be considered loaded at this
|
2622
|
+
# point because we have not handled any group memberships that might
|
2623
|
+
# have been set. Users at this poing in the create_user() method
|
2624
|
+
# can be considered loaded. Just noting this for my own reference.
|
2625
|
+
end
|
2626
|
+
end
|
2627
|
+
|
2628
|
+
# Update a Group or UNIXGroup in Active Directory. This method automatically
|
2629
|
+
# determines which attributes to update. The Group or UNIXGroup object must
|
2630
|
+
# exist in Active Directory or this method does nothing. This is checked, so
|
2631
|
+
# it is safe to pass any Group or UNIXGroup object to this method.
|
2632
|
+
#
|
2633
|
+
# === Parameter Types
|
2634
|
+
#
|
2635
|
+
# * group [Group or UNIXGroup]
|
2636
|
+
def update_group(group)
|
2637
|
+
if group.modified?
|
2638
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
2639
|
+
" update_group(<#{group.name}>)", LOG_DEBUG)
|
2640
|
+
group_filter = Net::LDAP::Filter.eq("objectclass", "group")
|
2641
|
+
entry = @ldap.search(:base => group.distinguished_name,
|
2642
|
+
:filter => group_filter,
|
2643
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
2644
|
+
attr = group_ldap_entry_attr(entry)
|
2645
|
+
ops = []
|
2646
|
+
RADUM::logger.log("\tKey: AD Value =? Object Value", LOG_DEBUG)
|
2647
|
+
|
2648
|
+
attr.keys.each do |key|
|
2649
|
+
# All keys in the attr hash apply to UNIXGroups, but some do not apply
|
2650
|
+
# to Groups. This is the easiest way to filter out inappropriate
|
2651
|
+
# checking.
|
2652
|
+
begin
|
2653
|
+
obj_value = group.send(key)
|
2654
|
+
rescue NoMethodError
|
2655
|
+
next
|
2656
|
+
end
|
2657
|
+
|
2658
|
+
ad_value = attr[key]
|
2659
|
+
RADUM::logger.log("\t#{key}: #{ad_value} =? #{obj_value}", LOG_DEBUG)
|
2660
|
+
|
2661
|
+
# Some attributes are integers and some are Strings, but they are
|
2662
|
+
# always Strings coming out of Active Directory. This is a safe
|
2663
|
+
# step to ensure a correct comparision.
|
2664
|
+
if ad_value.to_s != obj_value.to_s
|
2665
|
+
case key
|
2666
|
+
when :gid
|
2667
|
+
ops.push [:replace, :gidNumber, obj_value.to_s]
|
2668
|
+
when :nis_domain
|
2669
|
+
ops.push [:replace, :msSFU30NisDomain, obj_value]
|
2670
|
+
when :unix_password
|
2671
|
+
ops.push [:replace, :unixUserPassword, obj_value]
|
2672
|
+
end
|
2673
|
+
end
|
2674
|
+
end
|
2675
|
+
|
2676
|
+
begin
|
2677
|
+
entry.member.each do |member|
|
2678
|
+
# Groups can contain users and groups, so we need to check both
|
2679
|
+
# just in case. You can't tell from the DN which is which here.
|
2680
|
+
# First we remove any DNs that we've explicitly removed. Anything
|
2681
|
+
# we don't know about will be ignored.
|
2682
|
+
#
|
2683
|
+
# This check finds users or groups that were removed from the
|
2684
|
+
# container. This means the user has been deleted from Active
|
2685
|
+
# Directory in the AD object. It also finds users or groups that
|
2686
|
+
# were explicitly removed from the group.
|
2687
|
+
removed_group = find_group_by_dn(member, true)
|
2688
|
+
removed_user = find_user_by_dn(member, true)
|
2689
|
+
# The _membership versions find removed memberships for groups or
|
2690
|
+
# users who have not actually been removed from the AD object. This
|
2691
|
+
# reflects simple group and user membership changes, not removing
|
2692
|
+
# a user or group.
|
2693
|
+
removed_group_membership = find_group_by_dn(member)
|
2694
|
+
|
2695
|
+
unless group.removed_groups.include?(removed_group_membership)
|
2696
|
+
removed_group_membership = false
|
2697
|
+
end
|
2698
|
+
|
2699
|
+
removed_user_membership = find_user_by_dn(member)
|
2700
|
+
|
2701
|
+
unless group.removed_users.include?(removed_user_membership)
|
2702
|
+
removed_user_membership = false
|
2703
|
+
end
|
2704
|
+
|
2705
|
+
if removed_group || removed_user || removed_group_membership ||
|
2706
|
+
removed_user_membership
|
2707
|
+
ops.push [:delete, :member, member]
|
2708
|
+
user = removed_user || removed_user_membership
|
2709
|
+
|
2710
|
+
# There is a special case here with the last condition. If the
|
2711
|
+
# user is a UNIX member of the group and the group is the primary
|
2712
|
+
# Windows group, meaning we've removed their Windows membership
|
2713
|
+
# above (because it is implicit), we don't want to blow away the
|
2714
|
+
# UNIX membership too.
|
2715
|
+
if user && user.instance_of?(UNIXUser) &&
|
2716
|
+
group.instance_of?(UNIXGroup) && group != user.primary_group
|
2717
|
+
# There is a chance the user was never a UNIX member of this
|
2718
|
+
# UNIXGroup. This happens if the UNIX main group is changed
|
2719
|
+
# and then the user is removed from the group as well. We
|
2720
|
+
# should really also search the memberUid attribute, but
|
2721
|
+
# really... it should always match up with msSFU30PosixMember.
|
2722
|
+
begin
|
2723
|
+
found = entry.msSFU30PosixMember.find do |member|
|
2724
|
+
user.distinguished_name.downcase == member.downcase
|
2725
|
+
end
|
2726
|
+
|
2727
|
+
if found
|
2728
|
+
ops.push [:delete, :memberUid, user.username]
|
2729
|
+
ops.push [:delete, :msSFU30PosixMember, member]
|
2730
|
+
end
|
2731
|
+
rescue NoMethodError
|
2732
|
+
end
|
2733
|
+
end
|
2734
|
+
end
|
2735
|
+
end
|
2736
|
+
rescue NoMethodError
|
2737
|
+
end
|
2738
|
+
|
2739
|
+
# Now add any users or groups that are not already in the members
|
2740
|
+
# attribute array. We don't want to add the same thing twice because
|
2741
|
+
# this actually seems to duplicate the entries.
|
2742
|
+
(group.users + group.groups).each do |item|
|
2743
|
+
# As in the above begin block, the member attribute might not exist.
|
2744
|
+
# We have to take that into account.
|
2745
|
+
found = false
|
2746
|
+
|
2747
|
+
begin
|
2748
|
+
found = entry.member.find do |member|
|
2749
|
+
item.distinguished_name.downcase == member.downcase
|
2750
|
+
end
|
2751
|
+
rescue NoMethodError
|
2752
|
+
end
|
2753
|
+
|
2754
|
+
# There is an order of operations issue here. This method is called
|
2755
|
+
# before update_user() is called. If this group was the previous
|
2756
|
+
# user's primary Windows group (meaning we changed it), then this
|
2757
|
+
# code would try and add the user as a member of that group - as it
|
2758
|
+
# should. However, since we've not actually updated the user yet,
|
2759
|
+
# they are still a member of this group by way of their user current
|
2760
|
+
# account primaryGroupID attribute. When that attribute is updated
|
2761
|
+
# in the update_user() method, the group membership we are trying
|
2762
|
+
# to do here will be done implicitly. Trying to add the user as a
|
2763
|
+
# member here will not cause the sync() method to die, but it will
|
2764
|
+
# generate an LDAP error return message. We should avoid that.
|
2765
|
+
# The solution is to check if the object represented by the member
|
2766
|
+
# variable (which is a distinguished name) is:
|
2767
|
+
#
|
2768
|
+
# 1. A user account.
|
2769
|
+
# 2. A user account that has this group as its primaryGroupID still.
|
2770
|
+
#
|
2771
|
+
# If those two cases are true, we won't add the user as a member here
|
2772
|
+
# to avoid an LDAP error return message. Instead, the membership will
|
2773
|
+
# be implicitly dealt with when update_user() updates the user account
|
2774
|
+
# attributes. If this is not the case, we do add them as a member.
|
2775
|
+
if item.instance_of?(User) || item.instance_of?(UNIXUser)
|
2776
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
2777
|
+
obj = @ldap.search(:base => item.distinguished_name,
|
2778
|
+
:filter => user_filter,
|
2779
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
2780
|
+
curr_primary_group_id = obj.primaryGroupID.pop.to_i
|
2781
|
+
|
2782
|
+
unless found || curr_primary_group_id == group.rid
|
2783
|
+
ops.push [:add, :member, item.distinguished_name]
|
2784
|
+
end
|
2785
|
+
else
|
2786
|
+
# This is a Group or UNIXGroup member, which can just be added if
|
2787
|
+
# not already there.
|
2788
|
+
ops.push [:add, :member, item.distinguished_name] unless found
|
2789
|
+
end
|
2790
|
+
|
2791
|
+
# UNIX main group memberships are handled in update_user() when there
|
2792
|
+
# are changes if necessary.
|
2793
|
+
if item.instance_of?(UNIXUser) && group.instance_of?(UNIXGroup) &&
|
2794
|
+
group != item.unix_main_group
|
2795
|
+
# As with the member attribute, the msSFU30PosixMember attribute
|
2796
|
+
# might not exist yet either.
|
2797
|
+
found = false
|
2798
|
+
|
2799
|
+
begin
|
2800
|
+
# We should really also search the memberUid attribute, but
|
2801
|
+
# really... it should always match up with msSFU30PosixMember.
|
2802
|
+
found = entry.msSFU30PosixMember.find do |member|
|
2803
|
+
item.distinguished_name.downcase == member.downcase
|
2804
|
+
end
|
2805
|
+
rescue NoMethodError
|
2806
|
+
end
|
2807
|
+
|
2808
|
+
unless found
|
2809
|
+
ops.push [:add, :memberUid, item.username]
|
2810
|
+
ops.push [:add, :msSFU30PosixMember, item.distinguished_name]
|
2811
|
+
end
|
2812
|
+
end
|
2813
|
+
end
|
2814
|
+
|
2815
|
+
unless ops.empty?
|
2816
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
2817
|
+
@ldap.modify :dn => group.distinguished_name, :operations => ops
|
2818
|
+
check_ldap_result
|
2819
|
+
# At this point the group is the equivalent of a loaded group. Calling
|
2820
|
+
# this flags that fact as well as setting the hidden modified
|
2821
|
+
# attribute to false since we are up to date now.
|
2822
|
+
group.set_loaded
|
2823
|
+
else
|
2824
|
+
# The group did not need to be updated, so it can also be considered
|
2825
|
+
# loaded.
|
2826
|
+
group.set_loaded
|
2827
|
+
RADUM::logger.log("\tNo need to update group <#{group.name}>.",
|
2828
|
+
LOG_DEBUG)
|
2829
|
+
end
|
2830
|
+
end
|
2831
|
+
end
|
2832
|
+
|
2833
|
+
# Delete a User or UNIXUser from Active Directory.
|
2834
|
+
#
|
2835
|
+
# === Parameter Types
|
2836
|
+
#
|
2837
|
+
# * user [User or UNIXUser]
|
2838
|
+
def delete_user(user)
|
2839
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
2840
|
+
" delete_user(<#{user.username}>)", LOG_DEBUG)
|
2841
|
+
@ldap.delete :dn => user.distinguished_name
|
2842
|
+
check_ldap_result
|
2843
|
+
# Destroying the user is delayed until the end of the AD#sync call because
|
2844
|
+
# we still need a reference indicating the user was removed from Active
|
2845
|
+
# Directory to ensure the user's UNIX group membership attributes are
|
2846
|
+
# updated properly in AD#update_group if necessary. The user's Windows
|
2847
|
+
# group mebershpips were taken care of simply because it was deleted from
|
2848
|
+
# Active Directory.
|
2849
|
+
end
|
2850
|
+
|
2851
|
+
# Create a User or UNIXUser in Active Directory. The User or UNIXUser
|
2852
|
+
# must have its loaded attribute set to false, which indicates it was
|
2853
|
+
# manually created. This method checks that, so it is not necessary to
|
2854
|
+
# worry about checking first. This method also makes sure the user is not
|
2855
|
+
# already in Active Directory, in case someone created a user that would
|
2856
|
+
# match one that already exists. Therefore, any User or UNIXUser can be
|
2857
|
+
# passed into this method.
|
2858
|
+
#
|
2859
|
+
# === Parameter Types
|
2860
|
+
#
|
2861
|
+
# * user [User or UNIXUser]
|
2862
|
+
def create_user(user)
|
2863
|
+
unless user.loaded?
|
2864
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
2865
|
+
# The return value will be false explicitly if the search fails,
|
2866
|
+
# otherwise it will be an array of entries. Therefore it is important
|
2867
|
+
# to check for false explicitly for a failure. A failure indicates
|
2868
|
+
# that the user needs to be created.
|
2869
|
+
found = @ldap.search(:base => user.distinguished_name,
|
2870
|
+
:filter => user_filter,
|
2871
|
+
:scope => Net::LDAP::SearchScope_BaseObject,
|
2872
|
+
:return_result => false)
|
2873
|
+
|
2874
|
+
# The user should not already exist of course. This is to make sure
|
2875
|
+
# it is not already there.
|
2876
|
+
if found == false
|
2877
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
2878
|
+
" create_user(<#{user.username}>)", LOG_DEBUG)
|
2879
|
+
# We need the RID of the user's primary Windows group, and at this
|
2880
|
+
# point the create_group() method has grabbed the RID for any group
|
2881
|
+
# that was not loaded. Only groups in the RADUM environment can be
|
2882
|
+
# specified as the primary group, so this should always give the
|
2883
|
+
# primary group RID.
|
2884
|
+
rid = user.primary_group.rid
|
2885
|
+
|
2886
|
+
# We want to be sure though! The RID stuff is here so that we don't
|
2887
|
+
# even create a user if there is a primary Windows group RID issue.
|
2888
|
+
if rid.nil?
|
2889
|
+
RADUM::logger.log("SYNC ERROR: RID of " +
|
2890
|
+
" <#{user.primary_group.name}> was nil.",
|
2891
|
+
LOG_NORMAL)
|
2892
|
+
return
|
2893
|
+
end
|
2894
|
+
|
2895
|
+
# Note that all the attributes need to be strings in this hash.
|
2896
|
+
attr = {
|
2897
|
+
# All users are of the objectclasses "top", "person",
|
2898
|
+
# "orgainizationalPerson", and "user".
|
2899
|
+
:objectclass => ["top", "person", "organizationalPerson", "user"],
|
2900
|
+
:sAMAccountName => user.username,
|
2901
|
+
:userAccountControl => (UF_NORMAL_ACCOUNT + UF_PASSWD_NOTREQD +
|
2902
|
+
UF_ACCOUNTDISABLE).to_s
|
2903
|
+
}
|
2904
|
+
|
2905
|
+
description = ""
|
2906
|
+
|
2907
|
+
# These are optional attributes.
|
2908
|
+
unless user.first_name.nil?
|
2909
|
+
attr.merge!({ :givenName => user.first_name })
|
2910
|
+
description += "#{user.first_name}"
|
2911
|
+
end
|
2912
|
+
|
2913
|
+
unless user.initials.nil?
|
2914
|
+
attr.merge!({ :initials => user.initials })
|
2915
|
+
description += " #{user.initials}."
|
2916
|
+
end
|
2917
|
+
|
2918
|
+
unless user.middle_name.nil?
|
2919
|
+
attr.merge!({ :middleName => user.middle_name })
|
2920
|
+
end
|
2921
|
+
|
2922
|
+
unless user.surname.nil?
|
2923
|
+
attr.merge!({ :sn => user.surname })
|
2924
|
+
description += " #{user.surname}"
|
2925
|
+
end
|
2926
|
+
|
2927
|
+
# We should set these to something in case they were not set.
|
2928
|
+
if description == ""
|
2929
|
+
description = user.username
|
2930
|
+
end
|
2931
|
+
|
2932
|
+
realm = user.username + "@#{@domain}"
|
2933
|
+
|
2934
|
+
attr.merge!({
|
2935
|
+
:displayName => description,
|
2936
|
+
:description => description,
|
2937
|
+
:userPrincipalName => realm
|
2938
|
+
})
|
2939
|
+
|
2940
|
+
unless user.script_path.nil?
|
2941
|
+
attr.merge!({ :scriptPath => user.script_path })
|
2942
|
+
end
|
2943
|
+
|
2944
|
+
unless user.profile_path.nil?
|
2945
|
+
attr.merge!({ :profilePath => user.profile_path })
|
2946
|
+
end
|
2947
|
+
|
2948
|
+
if user.local_drive && user.local_path
|
2949
|
+
attr.merge!({
|
2950
|
+
:homeDrive => user.local_drive,
|
2951
|
+
:homeDirectory => user.local_path
|
2952
|
+
})
|
2953
|
+
elsif user.local_path
|
2954
|
+
attr.merge!({ :homeDirectory => user.local_path })
|
2955
|
+
end
|
2956
|
+
|
2957
|
+
attr.merge!({
|
2958
|
+
:gecos => user.gecos,
|
2959
|
+
:gidNumber => user.unix_main_group.gid.to_s,
|
2960
|
+
:loginShell => user.shell,
|
2961
|
+
:msSFU30Name => user.username,
|
2962
|
+
:msSFU30NisDomain => user.nis_domain,
|
2963
|
+
:uidNumber => user.uid.to_s,
|
2964
|
+
:unixHomeDirectory => user.home_directory,
|
2965
|
+
:unixUserPassword => user.unix_password
|
2966
|
+
}) if user.instance_of?(UNIXUser)
|
2967
|
+
|
2968
|
+
# The shadow file attributes are all optional, so we need to check
|
2969
|
+
# each one. The other UNIX attributes above are set to something
|
2970
|
+
# by default.
|
2971
|
+
if user.instance_of?(UNIXUser)
|
2972
|
+
unless user.shadow_expire.nil?
|
2973
|
+
attr.merge!({:shadowExpire => user.shadow_expire.to_s})
|
2974
|
+
end
|
2975
|
+
|
2976
|
+
unless user.shadow_flag.nil?
|
2977
|
+
attr.merge!({:shadowFlag => user.shadow_flag.to_s})
|
2978
|
+
end
|
2979
|
+
|
2980
|
+
unless user.shadow_inactive.nil?
|
2981
|
+
attr.merge!({:shadowInactive => user.shadow_inactive.to_s})
|
2982
|
+
end
|
2983
|
+
|
2984
|
+
unless user.shadow_last_change.nil?
|
2985
|
+
attr.merge!({:shadowLastChange => user.shadow_last_change.to_s})
|
2986
|
+
end
|
2987
|
+
|
2988
|
+
unless user.shadow_max.nil?
|
2989
|
+
attr.merge!({:shadowMax => user.shadow_max.to_s})
|
2990
|
+
end
|
2991
|
+
|
2992
|
+
unless user.shadow_min.nil?
|
2993
|
+
attr.merge!({:shadowMin => user.shadow_min.to_s})
|
2994
|
+
end
|
2995
|
+
|
2996
|
+
unless user.shadow_warning.nil?
|
2997
|
+
attr.merge!({:shadowWarning => user.shadow_warning.to_s})
|
2998
|
+
end
|
2999
|
+
end
|
3000
|
+
|
3001
|
+
RADUM::logger.log("\n" + attr.to_yaml + "\n\n", LOG_DEBUG)
|
3002
|
+
@ldap.add :dn => user.distinguished_name, :attributes => attr
|
3003
|
+
check_ldap_result
|
3004
|
+
|
3005
|
+
# Modify the attributes for the user password and userAccountControl
|
3006
|
+
# value to enable the account.
|
3007
|
+
user_status = UF_NORMAL_ACCOUNT
|
3008
|
+
user_status += UF_ACCOUNTDISABLE if user.disabled?
|
3009
|
+
|
3010
|
+
if user.password.nil?
|
3011
|
+
user.password = random_password
|
3012
|
+
RADUM::logger.log("\tGenerated password #{user.password} for" +
|
3013
|
+
" <#{user.username}>.", LOG_DEBUG)
|
3014
|
+
end
|
3015
|
+
|
3016
|
+
ops = [
|
3017
|
+
[:replace, :unicodePwd, str2utf16le(user.password)],
|
3018
|
+
[:replace, :userAccountControl, user_status.to_s]
|
3019
|
+
]
|
3020
|
+
|
3021
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
3022
|
+
@ldap.modify :dn => user.distinguished_name, :operations => ops
|
3023
|
+
check_ldap_result
|
3024
|
+
|
3025
|
+
# Set the user's password to nil. When a password has a value, that
|
3026
|
+
# means we need to set it, otherwise it should be nil. We just
|
3027
|
+
# set it, so we don't want the update set to try and set it again.
|
3028
|
+
user.password = nil
|
3029
|
+
|
3030
|
+
# If the user has to change their password, it must be done below
|
3031
|
+
# and not in the previous step that set their password because it
|
3032
|
+
# will ignore the additional flag (which I've commented out near
|
3033
|
+
# the top of this file because it does not work now). This works.
|
3034
|
+
if user.must_change_password?
|
3035
|
+
ops = [
|
3036
|
+
[:replace, :pwdLastSet, 0.to_s]
|
3037
|
+
]
|
3038
|
+
|
3039
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
3040
|
+
@ldap.modify :dn => user.distinguished_name, :operations => ops
|
3041
|
+
check_ldap_result
|
3042
|
+
end
|
3043
|
+
|
3044
|
+
# The user already has the primary Windows group as Domain Users
|
3045
|
+
# based on the default actions above. If the user has a different
|
3046
|
+
# primary Windows group, it is necessary to add the user to that
|
3047
|
+
# group first (as a member in the member attribute for the group)
|
3048
|
+
# before attempting to set their primaryGroupID attribute or Active
|
3049
|
+
# Directory will refuse to do it. Note that there is no guarentee
|
3050
|
+
# that AD#load() has been called yet, so the Domain Users group
|
3051
|
+
# might not even be in the RADUM system. The safest way to check
|
3052
|
+
# if the user's primary Windows group is Domain Users is as done
|
3053
|
+
# below.
|
3054
|
+
unless user.primary_group.name == "Domain Users"
|
3055
|
+
ops = [
|
3056
|
+
[:add, :member, user.distinguished_name]
|
3057
|
+
]
|
3058
|
+
|
3059
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
3060
|
+
@ldap.modify :dn => user.primary_group.distinguished_name,
|
3061
|
+
:operations => ops
|
3062
|
+
check_ldap_result
|
3063
|
+
|
3064
|
+
ops = [
|
3065
|
+
[:replace, :primaryGroupID, rid.to_s]
|
3066
|
+
]
|
3067
|
+
|
3068
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
3069
|
+
@ldap.modify :dn => user.distinguished_name, :operations => ops
|
3070
|
+
check_ldap_result
|
3071
|
+
# The user has now been made a regular member of the Domain Users
|
3072
|
+
# Windows group. This has been handled in Active Directory for us,
|
3073
|
+
# but now we want to reflect that in the Domain Users Group object
|
3074
|
+
# here. There is a problem however. It is possible that the
|
3075
|
+
# Domain Users group has not been loaded into the RADUM environment
|
3076
|
+
# yet. Therefore, we check first before trying.
|
3077
|
+
domain_users = find_group_by_name("Domain Users")
|
3078
|
+
domain_users.add_user user if domain_users
|
3079
|
+
end
|
3080
|
+
|
3081
|
+
# At this point, we need to pull the RID value back out and set it
|
3082
|
+
# because it is needed later. Actually, it isn't for users, but
|
3083
|
+
# I am pretending it is just as important because I am tracking
|
3084
|
+
# RIDs anyway (they are in a flat namespace).
|
3085
|
+
entry = @ldap.search(:base => user.distinguished_name,
|
3086
|
+
:filter => user_filter,
|
3087
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
3088
|
+
user.set_rid sid2rid_int(entry.objectSid.pop)
|
3089
|
+
# At this point the user is the equivalent as a loaded user.
|
3090
|
+
# Calling this flags that fact as well as setting the hidden
|
3091
|
+
# modified attribute to false since we are up to date now. Note
|
3092
|
+
# that the groups attribute is still not 100% accurate. It will
|
3093
|
+
# be dealt with later when groups are dealt with.
|
3094
|
+
user.set_loaded
|
3095
|
+
end
|
3096
|
+
end
|
3097
|
+
end
|
3098
|
+
|
3099
|
+
# Update a User or UNIXUser in Active Directory. This method automatically
|
3100
|
+
# determines which attributes to update. The User or UNIXUser object must
|
3101
|
+
# exist in Active Directory or this method does nothing. This is checked, so
|
3102
|
+
# it is safe to pass any User or UNIXUser object to this method.
|
3103
|
+
#
|
3104
|
+
# === Parameter Types
|
3105
|
+
#
|
3106
|
+
# * user [User or UNIXUser]
|
3107
|
+
def update_user(user)
|
3108
|
+
if user.modified?
|
3109
|
+
RADUM::logger.log("[AD #{self.root}]" +
|
3110
|
+
" update_user(<#{user.username}>)", LOG_DEBUG)
|
3111
|
+
user_filter = Net::LDAP::Filter.eq("objectclass", "user")
|
3112
|
+
entry = @ldap.search(:base => user.distinguished_name,
|
3113
|
+
:filter => user_filter,
|
3114
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
3115
|
+
attr = user_ldap_entry_attr(entry)
|
3116
|
+
ops = []
|
3117
|
+
# This for the UNIX group membership corner case below. Note that 0
|
3118
|
+
# is the case where the GID has been removed (UNIXUser converted to
|
3119
|
+
# a User). No one should have a GID of 0, so this is just to make the
|
3120
|
+
# check to proceed easier.
|
3121
|
+
old_gid = 0
|
3122
|
+
RADUM::logger.log("\tKey: AD Value =? Object Value", LOG_DEBUG)
|
3123
|
+
|
3124
|
+
attr.keys.each do |key|
|
3125
|
+
# All keys in the attr hash apply to UNIXUsers, but most do not apply
|
3126
|
+
# to Users. This is the easiest way to filter out inappropriate
|
3127
|
+
# checking.
|
3128
|
+
begin
|
3129
|
+
obj_value = user.send(key)
|
3130
|
+
rescue NoMethodError
|
3131
|
+
next
|
3132
|
+
end
|
3133
|
+
|
3134
|
+
ad_value = attr[key]
|
3135
|
+
RADUM::logger.log("\t#{key}: #{ad_value} =? #{obj_value}", LOG_DEBUG)
|
3136
|
+
|
3137
|
+
# Some attributes are integers and some are Strings, but they are
|
3138
|
+
# always Strings coming out of Active Directory. This is a safe
|
3139
|
+
# step to ensure a correct comparision.
|
3140
|
+
#
|
3141
|
+
# Note that in this case there is a comparision of the primary_group
|
3142
|
+
# value, which is represented as a Group/UNIXGroup object, but it
|
3143
|
+
# has a to_s() method that will work fine here. So yes, what I said
|
3144
|
+
# at first is not strictly true, but by "magic" this all works fine.
|
3145
|
+
# I mean, have you read this code? :-)
|
3146
|
+
if ad_value.to_s != obj_value.to_s
|
3147
|
+
case key
|
3148
|
+
when :disabled?
|
3149
|
+
user_status = UF_NORMAL_ACCOUNT
|
3150
|
+
user_status += UF_ACCOUNTDISABLE if obj_value
|
3151
|
+
ops.push [:replace, :userAccountControl, user_status.to_s]
|
3152
|
+
when :first_name
|
3153
|
+
ops.push [:replace, :givenName, obj_value]
|
3154
|
+
when :initials
|
3155
|
+
ops.push [:replace, :initials, obj_value]
|
3156
|
+
when :middle_name
|
3157
|
+
ops.push [:replace, :middleName, obj_value]
|
3158
|
+
when :surname
|
3159
|
+
ops.push [:replace, :sn, obj_value]
|
3160
|
+
when :script_path
|
3161
|
+
ops.push [:replace, :scriptPath, obj_value]
|
3162
|
+
when :profile_path
|
3163
|
+
ops.push [:replace, :profilePath, obj_value]
|
3164
|
+
when :local_path
|
3165
|
+
ops.push [:replace, :homeDirectory, obj_value]
|
3166
|
+
when :local_drive
|
3167
|
+
ops.push [:replace, :homeDrive, obj_value]
|
3168
|
+
when :primary_group
|
3169
|
+
@ldap.modify :dn => user.primary_group.distinguished_name,
|
3170
|
+
:operations => [[:add, :member,
|
3171
|
+
user.distinguished_name]]
|
3172
|
+
check_ldap_result
|
3173
|
+
ops.push [:replace, :primaryGroupID, user.primary_group.rid.to_s]
|
3174
|
+
when :shell
|
3175
|
+
ops.push [:replace, :loginShell, obj_value]
|
3176
|
+
when :home_directory
|
3177
|
+
ops.push [:replace, :unixHomeDirectory, obj_value]
|
3178
|
+
when :nis_domain
|
3179
|
+
ops.push [:replace, :msSFU30NisDomain, obj_value]
|
3180
|
+
when :gecos
|
3181
|
+
ops.push [:replace, :gecos, obj_value]
|
3182
|
+
when :unix_password
|
3183
|
+
ops.push [:replace, :unixUserPassword, obj_value]
|
3184
|
+
when :shadow_expire
|
3185
|
+
ops.push [:replace, :shadowExpire, obj_value.to_s]
|
3186
|
+
when :shadow_flag
|
3187
|
+
ops.push [:replace, :shadowFlag, obj_value.to_s]
|
3188
|
+
when :shadow_inactive
|
3189
|
+
ops.push [:replace, :shadowInactive, obj_value.to_s]
|
3190
|
+
when :shadow_last_change
|
3191
|
+
ops.push [:replace, :shadowLastChange, obj_value.to_s]
|
3192
|
+
when :shadow_max
|
3193
|
+
ops.push [:replace, :shadowMax, obj_value.to_s]
|
3194
|
+
when :shadow_min
|
3195
|
+
ops.push [:replace, :shadowMin, obj_value.to_s]
|
3196
|
+
when :shadow_warning
|
3197
|
+
ops.push [:replace, :shadowWarning, obj_value.to_s]
|
3198
|
+
when :gid
|
3199
|
+
old_gid = ad_value.to_i
|
3200
|
+
ops.push [:replace, :gidNumber, obj_value.to_s]
|
3201
|
+
when :uid
|
3202
|
+
ops.push [:replace, :uidNumber, obj_value.to_s]
|
3203
|
+
when :must_change_password?
|
3204
|
+
if obj_value
|
3205
|
+
ops.push [:replace, :pwdLastSet, 0.to_s]
|
3206
|
+
else
|
3207
|
+
ops.push [:replace, :pwdLastSet, -1.to_s]
|
3208
|
+
end
|
3209
|
+
end
|
3210
|
+
end
|
3211
|
+
end
|
3212
|
+
|
3213
|
+
# Update the LDAP description and displayName attributes. This only
|
3214
|
+
# updates them if they are different than what is currently there.
|
3215
|
+
description = ""
|
3216
|
+
|
3217
|
+
unless user.first_name.nil?
|
3218
|
+
description = "#{user.first_name}"
|
3219
|
+
end
|
3220
|
+
|
3221
|
+
unless user.initials.nil?
|
3222
|
+
description += " #{user.initials}."
|
3223
|
+
end
|
3224
|
+
|
3225
|
+
unless user.surname.nil?
|
3226
|
+
description += " #{user.surname}"
|
3227
|
+
end
|
3228
|
+
|
3229
|
+
curr_description = curr_display_name = nil
|
3230
|
+
|
3231
|
+
begin
|
3232
|
+
curr_description = entry.description.pop
|
3233
|
+
rescue NoMethodError
|
3234
|
+
end
|
3235
|
+
|
3236
|
+
begin
|
3237
|
+
curr_display_name = entry.displayName.pop
|
3238
|
+
rescue NoMethodError
|
3239
|
+
end
|
3240
|
+
|
3241
|
+
if description != curr_description
|
3242
|
+
ops.push [:replace, :description, description]
|
3243
|
+
end
|
3244
|
+
|
3245
|
+
if description != curr_display_name
|
3246
|
+
ops.push [:replace, :displayName, description]
|
3247
|
+
end
|
3248
|
+
|
3249
|
+
# If the password is set, change the user's password. Otherwise this
|
3250
|
+
# will be nil.
|
3251
|
+
unless user.password.nil?
|
3252
|
+
ops.push [:replace, :unicodePwd, str2utf16le(user.password)]
|
3253
|
+
# Set the user's password to nil. When a password has a value, that
|
3254
|
+
# means we need to set it, otherwise it should be nil. We just
|
3255
|
+
# set it, so we don't want the update set to try and set it again.
|
3256
|
+
user.password = nil
|
3257
|
+
end
|
3258
|
+
|
3259
|
+
# This is a corner case with the UNIX main group. Due to the
|
3260
|
+
# complications in implicit UNIX group membership, primary Windows
|
3261
|
+
# groups having users as implicit members, etc. we just make sure
|
3262
|
+
# the user is made a UNIX member of the previous UNIX main group
|
3263
|
+
# when it was changed just in case they are not already a member.
|
3264
|
+
#
|
3265
|
+
# Note that when converting a UNIXUser to a User, there will be a
|
3266
|
+
# gid change, but the gid will be "".to_i (0). In that case, we don't
|
3267
|
+
# want to proceed with this logic. This isn't C, so "if 0" is true.
|
3268
|
+
# The old_gid variable is initialized to 0 too in order to make this
|
3269
|
+
# check easier. None should have a GID of 0.
|
3270
|
+
unless old_gid == 0
|
3271
|
+
group_ops = []
|
3272
|
+
group_filter = Net::LDAP::Filter.eq("objectclass", "group")
|
3273
|
+
group = find_group_by_gid old_gid
|
3274
|
+
entry = @ldap.search(:base => group.distinguished_name,
|
3275
|
+
:filter => group_filter,
|
3276
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
3277
|
+
# Double check to make sure they are not already members. Since this
|
3278
|
+
# logic is difficult to deal with, the algorithm is simply to make
|
3279
|
+
# sure the UNIXUser is a member of their previous UNIX main group
|
3280
|
+
# if that has not been done by the update_group() method.
|
3281
|
+
found = false
|
3282
|
+
|
3283
|
+
begin
|
3284
|
+
found = entry.msSFU30PosixMember.find do |member|
|
3285
|
+
user.distinguished_name.downcase == member.downcase
|
3286
|
+
end
|
3287
|
+
rescue NoMethodError
|
3288
|
+
end
|
3289
|
+
|
3290
|
+
unless found
|
3291
|
+
group_ops.push [:add, :memberUid, user.username]
|
3292
|
+
group_ops.push [:add, :msSFU30PosixMember, user.distinguished_name]
|
3293
|
+
RADUM::logger.log("\nSpecial case 1: updating old UNIX main group" +
|
3294
|
+
" UNIX membership for group <#{group.name}>.",
|
3295
|
+
LOG_DEBUG)
|
3296
|
+
RADUM::logger.log("\n" + group_ops.to_yaml, LOG_DEBUG)
|
3297
|
+
@ldap.modify :dn => group.distinguished_name,
|
3298
|
+
:operations => group_ops
|
3299
|
+
check_ldap_result
|
3300
|
+
RADUM::logger.log("\nSpecial case 1: end.\n\n", LOG_DEBUG)
|
3301
|
+
end
|
3302
|
+
|
3303
|
+
# In this case, we also have to make sure the user is removed
|
3304
|
+
# from the new UNIX main group with respect to UNIX group membership.
|
3305
|
+
# This is because there is also a case where the UNIX main group is
|
3306
|
+
# being set to the primary Windows group, and thus would not cause
|
3307
|
+
# an update because the Windows group membership is implicit.
|
3308
|
+
group_ops = []
|
3309
|
+
group = user.unix_main_group
|
3310
|
+
entry = @ldap.search(:base => group.distinguished_name,
|
3311
|
+
:filter => group_filter,
|
3312
|
+
:scope => Net::LDAP::SearchScope_BaseObject).pop
|
3313
|
+
found = false
|
3314
|
+
|
3315
|
+
begin
|
3316
|
+
found = entry.msSFU30PosixMember.find do |member|
|
3317
|
+
user.distinguished_name.downcase == member.downcase
|
3318
|
+
end
|
3319
|
+
rescue NoMethodError
|
3320
|
+
end
|
3321
|
+
|
3322
|
+
if found
|
3323
|
+
group_ops.push [:delete, :memberUid, user.username]
|
3324
|
+
group_ops.push [:delete, :msSFU30PosixMember,
|
3325
|
+
user.distinguished_name]
|
3326
|
+
RADUM::logger.log("\nSpecial case 2: removing UNIX main group" +
|
3327
|
+
" UNIX membership for group <#{group.name}>.",
|
3328
|
+
LOG_DEBUG)
|
3329
|
+
RADUM::logger.log("\n" + group_ops.to_yaml, LOG_DEBUG)
|
3330
|
+
@ldap.modify :dn => group.distinguished_name,
|
3331
|
+
:operations => group_ops
|
3332
|
+
check_ldap_result
|
3333
|
+
RADUM::logger.log("\nSpecial case 2: end.\n\n", LOG_DEBUG)
|
3334
|
+
end
|
3335
|
+
end
|
3336
|
+
|
3337
|
+
unless ops.empty?
|
3338
|
+
RADUM::logger.log("\n" + ops.to_yaml + "\n\n", LOG_DEBUG)
|
3339
|
+
@ldap.modify :dn => user.distinguished_name, :operations => ops
|
3340
|
+
check_ldap_result
|
3341
|
+
# At this point the user is the equivalent as a loaded user. Calling
|
3342
|
+
# this flags that fact as well as setting the hidden modified
|
3343
|
+
# attribute to false since we are up to date now.
|
3344
|
+
user.set_loaded
|
3345
|
+
else
|
3346
|
+
# The user did not need to be updated, so it can also be considered
|
3347
|
+
# loaded.
|
3348
|
+
user.set_loaded
|
3349
|
+
RADUM::logger.log("\tNo need to update user <#{user.username}>.",
|
3350
|
+
LOG_DEBUG)
|
3351
|
+
end
|
3352
|
+
end
|
3353
|
+
end
|
3354
|
+
end
|
3355
|
+
end
|