radum 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|