radum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ module RADUM
2
+ # Logger constants.
3
+ LOG_NONE = 0
4
+ LOG_NORMAL = 1
5
+ LOG_DEBUG = 2
6
+
7
+ # The Logger class handles all logging output. Any output RADUM generates
8
+ # aside from exceptions goes through the Logger class. The possible log
9
+ # levels are:
10
+ #
11
+ # * LOG_NONE: Do not output any log information.
12
+ # * LOG_NORMAL: Output normal messages (warnings) for certain situations.
13
+ # * LOG_DEBUG: Output verbose debugging information.
14
+ #
15
+ # The RADUM module automatically instantiates a Logger instance for the
16
+ # module that is accessible through the RADUM::logger method.
17
+ class Logger
18
+ # The default logger level. Logger levels less than or equal to the default
19
+ # logger level will be displayed. Other messages will be ignored. If the
20
+ # logger level is set to LOG_NONE, no log messages will be displayed.
21
+ attr_accessor :default_level
22
+
23
+ # Create a new Logger instance. A Logger object is automatically created
24
+ # with a default_level of LOG_NORMAL.
25
+ #
26
+ # === Parameter Types
27
+ #
28
+ # * default_level [integer => RADUM log level constant]
29
+ def initialize(default_level)
30
+ @default_level = default_level
31
+ @output = $stdout
32
+ end
33
+
34
+ # Print a long message with the given log level. If the log level is
35
+ # LOG_NONE, the message will be discarded, otherwise the message will
36
+ # be processed as long as the log level is less than or equal to the
37
+ # default log level. The log level defaults to LOG_NORMAL.
38
+ #
39
+ # === Parameter Types
40
+ #
41
+ # * mesg [String]
42
+ # * log_level [integer => RADUM log level constant]
43
+ def log(mesg, log_level = LOG_NORMAL)
44
+ if @default_level != LOG_NONE && log_level != LOG_NONE &&
45
+ log_level <= @default_level
46
+ @output.puts mesg
47
+ end
48
+ end
49
+
50
+ # Set the logger output file. The file is opened with mode "a" so it is
51
+ # created if needed and then appended to.
52
+ #
53
+ # === Parameter Types
54
+ #
55
+ # * filename [String]
56
+ def output_file(filename)
57
+ @output = open(filename, "a")
58
+ end
59
+ end
60
+
61
+ @radum_logger = Logger.new(LOG_NORMAL)
62
+
63
+ # Access the RADUM Logger instance.
64
+ def RADUM.logger
65
+ @radum_logger
66
+ end
67
+ end
@@ -0,0 +1,1087 @@
1
+ module RADUM
2
+ # The User class represents a standard Windows user account.
3
+ class User
4
+ # The User or UNIXUser object username. This corresponds to the LDAP
5
+ # sAMAccountName and msSFU30Name attributes. This is also used for part of
6
+ # the LDAP userPrincipalName attribute. This does not contain any LDAP path
7
+ # components, unlike the Container objet's name attribute (because
8
+ # Containers can be different types of objects, like a cn=name or ou=name).
9
+ attr_reader :username
10
+ # The Container object the User or UNIXUser belongs to.
11
+ attr_reader :container
12
+ # The RID of the User or UNIXUser object. This corresponds to part of the
13
+ # LDAP objectSid attribute. This is set when the User or UNIXUser is loaded
14
+ # by AD#load from the AD object the Container belogs to. This attribute
15
+ # should not be specified in the User.new method when creating a new User
16
+ # or UNIXUser by hand.
17
+ attr_reader :rid
18
+ # The Group or UNIXGroup objects the User or UNIXUser is a member of. User
19
+ # and UNIXUser objects are implicit members of their primary_group as well,
20
+ # but that is not added to the groups array directly. This matches the
21
+ # implicit membership in the primary Windows group in Active Directory.
22
+ attr_reader :groups
23
+ # An array of Group or UNIXGroup objects removed from the User or
24
+ # UNIXUser.
25
+ attr_reader :removed_groups
26
+
27
+ # Create a new User object that represents a Windows user in Active
28
+ # Directory. This method takes a Hash containing arguments, some of which
29
+ # are required and others optional. The supported arguments follow:
30
+ #
31
+ # * :username => The User object's username [required]
32
+ # * :container => The User object's associated Container [required]
33
+ # * :primary_group => The User object's primary Windows group [required]
34
+ # * :disabled => User object disabled flag [default false]
35
+ # * :rid => The RID of the User object [optional]
36
+ #
37
+ # The :username argument (case-insensitive) and the :rid argument must
38
+ # be unique in the AD object, otherwise a RuntimeError is raised. The
39
+ # :username argument has leading and trailing white space removed. The
40
+ # :primary_group argument must be of the RADUM group type
41
+ # GROUP_GLOBAL_SECURITY or GROUP_UNIVERSAL_SECURITY and not a removed
42
+ # group, otherwise a RuntimeError is raised. The :disabled argument
43
+ # indicates if the User object should be disabled, and it defaults to
44
+ # false. The :rid argument should not be set directly except from the
45
+ # AD#load method itself. The User object automatically adds itself to the
46
+ # Container object specified by the :container argument.
47
+ #
48
+ # === Parameter Types
49
+ #
50
+ # * :username [String]
51
+ # * :container [Container]
52
+ # * :primary_group [Group or UNIXGroup]
53
+ # * :disabled [boolean]
54
+ # * :rid [integer]
55
+ #
56
+ # Note that a User will not be forced to change their Windows password on
57
+ # their first login unless this is changed by calling the
58
+ # toggle_must_change_password method. If no password is set for the User,
59
+ # a random password will be generated. The random password will probably
60
+ # meet Group Policy password security requirements, but it is suggested
61
+ # that a password be set to ensure this is the case, otherwise setting the
62
+ # User password during Active Directory creation might fail, which results
63
+ # in a disabled Active Directory user account that has no password.
64
+ #
65
+ # One difference with respect to the Active Directory Users and Computers
66
+ # GUI tool should be noted. When creating user accounts with the GUI tool,
67
+ # the LDAP cn attribute is set to "first_name initials. surname" and that
68
+ # is what is displayed in the Name column in the tool. This means you cannot
69
+ # create user accounts with the same first name, initials, and surname.
70
+ # RADUM uses the username as the LDAP cn attribute (it really just does
71
+ # not specify that when the first step is done creating an initial acocunt).
72
+ # This means that you can have two user accounts with different usernames
73
+ # but with the same first name, initials, and surname. In the RADUM case,
74
+ # the Name column in the GUI tool shows the username. RADUM adds the
75
+ # "first_name initals. surname" string to the LDAP description attribute,
76
+ # so the GUI tool shows that in the Description column, therefore all the
77
+ # useful information is there (by default the LDAP description attribute
78
+ # is blank). This implementation detail was chosen because it seemed like
79
+ # the best choice, otherwise the username and a combination of other
80
+ # name attributes would have to be all checked for unique values instead
81
+ # of just the username itself, and by default having the username and
82
+ # other name attributes (if set) show up in the GUI tool is more useful
83
+ # in the opinion of the author.
84
+ #
85
+ # See the documentation for each attribute method for what the default
86
+ # values of each attribute is based on calling this method.
87
+ def initialize(args = {})
88
+ @rid = args[:rid] || nil
89
+ @container = args[:container] or raise "User :container argument" +
90
+ " required."
91
+
92
+ # The RID must be unique.
93
+ if @container.directory.rids.include?(@rid)
94
+ raise "RID #{rid} is already in use in the directory."
95
+ end
96
+
97
+ @username = args[:username] or raise "User :username argument required."
98
+ @username.strip!
99
+
100
+ # The username (sAMAccountName) must be unique (case-insensitive). This
101
+ # is needed in case someone tries to make the same username in two
102
+ # different containers.
103
+ if @container.directory.find_user_by_username(@username)
104
+ raise "User is already in the directory."
105
+ end
106
+
107
+ @primary_group = args[:primary_group] or raise "User :primary_group" +
108
+ " argument required."
109
+
110
+ if @primary_group.removed?
111
+ raise "User primary_group cannot be a removed group."
112
+ end
113
+
114
+ # The primary group must be of one of these two types. It appears you can
115
+ # change a group's type to GROUP_DOMAIN_LOCAL_SECURITY in the AD Users and
116
+ # Groups tool if someone has that as their primary group, but you can't
117
+ # set a group of that type as someone's primary group. You can't change
118
+ # the type of a group to anything that has an AD group type of
119
+ # "Distribution" most definitely. The AD group type must be "Security"
120
+ # for primary groups. I am just going to avoid as much confusion as
121
+ # possible unless someone were to complain.
122
+ unless @primary_group.type == GROUP_GLOBAL_SECURITY ||
123
+ @primary_group.type == GROUP_UNIVERSAL_SECURITY
124
+ raise "User primary group must be of type GROUP_GLOBAL_SECURITY" +
125
+ " or GROUP_UNIVERSAL_SECURITY."
126
+ end
127
+
128
+ @disabled = args[:disabled] || false
129
+ @distinguished_name = "cn=" + @username + "," + @container.name +
130
+ "," + @container.directory.root
131
+ @groups = []
132
+ @removed_groups = []
133
+ @first_name = @username
134
+ @initials = nil
135
+ @middle_name = nil
136
+ @surname = nil
137
+ # These are attributes of the Profile tab in Active Directory Users and
138
+ # Computers.
139
+ @script_path = nil
140
+ @profile_path = nil
141
+ # The local_path variable is set alone if it represents the "Local
142
+ # path" part of the Home folder section of the Profile tab. In this
143
+ # case, local_drive should be left nil. If it is used to represent the
144
+ # "Connect" part of the Home folder section of the Profile tab,
145
+ # local_path and local_drive should both be set. Note that these
146
+ # two options in the Home folder section of the Profile tab are mutually
147
+ # exclusive. This is enforced in the setter methods. Also note these
148
+ # variables represent the following LDAP attributes:
149
+ #
150
+ # local_path --> homeDirectory
151
+ # local_drive --> homeDrive
152
+ #
153
+ # I am using these names because there is a home_directory instance
154
+ # variable to represent UNIX home directories, and the way these are
155
+ # set with the methods defined in this class better reflect the Active
156
+ # Directory Users and Computers tool.
157
+ @local_path = nil
158
+ @local_drive = nil
159
+ # Password related instance variables. The password itself is not
160
+ # reflected here unless we are trying to change it to a new value
161
+ # (otherwise it is just nil).
162
+ @password = nil
163
+ @must_change_password = false
164
+ # This has to be set first before adding the User to the Container. This
165
+ # is delayed for a UNIXUser because it needs the rest of its attributes
166
+ # set before adding to the Container.
167
+ @removed = false
168
+ @container.add_user self unless instance_of?(UNIXUser)
169
+ @modified = true
170
+ @loaded = false
171
+ end
172
+
173
+ # True if the User or UNIXUser account is disabled, false otherwise.
174
+ # This is a boolean representation of the LDAP userAccountControl attribute.
175
+ def disabled?
176
+ @disabled
177
+ end
178
+
179
+ # Disable a User or UNIXUser account.
180
+ def disable
181
+ unless @disabled
182
+ @disabled = true
183
+ @modified = true
184
+ end
185
+ end
186
+
187
+ # Enable a User or UNIXUser account.
188
+ def enable
189
+ if @disabled
190
+ @disabled = false
191
+ @modified = true
192
+ end
193
+ end
194
+
195
+ # The LDAP distinguishedName attribute for this User or UNIXUser. The
196
+ # default value is the username, Container, and AD root. The value
197
+ # should only be different if an account is loaded that was created
198
+ # by the Active Directory Users and Computers tool or some other mechanism.
199
+ def distinguished_name
200
+ @distinguished_name
201
+ end
202
+
203
+ # Set the User or UNIXUser LDAP distinguishedName attribute. This can only
204
+ # be set if the User or UNIXUser has not been loaded from Active Directory
205
+ # because the distinguishedName attribute cannot be modified. Attempting to
206
+ # change it for a loaded user results in a RuntimeError. Note that it is
207
+ # easy to mess up the LDAP distinguishedName attribute, so this is not
208
+ # documented and should only be used by AD#load or only if you really know
209
+ # what you are doing.
210
+ #
211
+ # === Parameter Types
212
+ #
213
+ # * distinguished_name [String]
214
+ def distinguished_name=(distinguished_name) # :nodoc:
215
+ if @loaded
216
+ raise "The distinguished name can only be set on new user accounts."
217
+ end
218
+
219
+ @distinguished_name = distinguished_name
220
+ end
221
+
222
+ # The User or UNIXUser first name.
223
+ def first_name
224
+ @first_name
225
+ end
226
+
227
+ # Set the User or UNIXUser first name. This corresponds to the LDAP
228
+ # givenName attribute and is used in the LDAP displayName, description,
229
+ # and name attributes. This defaults to the username when a User or
230
+ # UNIXUser is created using User.new or UNIXUser.new, but is set to the
231
+ # correct value when a User or UNIXUser is loaded by AD#load from the AD
232
+ # object the Container belongs to.
233
+ #
234
+ # === Parameter Types
235
+ #
236
+ # * first_name [String]
237
+ def first_name=(first_name)
238
+ @first_name = first_name
239
+ @modified = true
240
+ end
241
+
242
+ # The User or UNIXUser middle initials.
243
+ def initials
244
+ @initials
245
+ end
246
+
247
+ # Set the User or UNIXUser middle initials. This is usually what is set
248
+ # instead of their middle name when creating user accounts using the
249
+ # Active Directory Users and Computers GUI tool. The "." should not be
250
+ # added as it will be automatically displayed when necessary.
251
+ #
252
+ # === Parameter Types
253
+ #
254
+ # * initials [String]
255
+ def initials=(initials)
256
+ @initials = initials
257
+ @modified = true
258
+ end
259
+
260
+ # The User or UNIXUser middle name.
261
+ def middle_name
262
+ @middle_name
263
+ end
264
+
265
+ # Set the User or UNIXUser middle name. This corresponds to the LDAP
266
+ # middleName attribute and is used in the LDAP displayName and description
267
+ # attributes. This defaults to nil when a User or UNIXUser is created using
268
+ # User.new or UNIXUser.new, but is set to the correct value when a User or
269
+ # UNIXUser is loaded by AD#load from the AD object the Container belongs to.
270
+ #
271
+ # === Parameter Types
272
+ #
273
+ # * middle_name [String]
274
+ def middle_name=(middle_name)
275
+ @middle_name = middle_name
276
+ @modified = true
277
+ end
278
+
279
+ # The User or UNIXUser surname (last name).
280
+ def surname
281
+ @surname
282
+ end
283
+
284
+ # Set the User or UNIXUser surname (last name). This corresponds to the
285
+ # LDAP sn attribute and is used in the LDAP displayName, description, and
286
+ # name attributes. This defaults to nil when a User or UNIXUser is created
287
+ # using User.new or UNIXUser.new, but is set to the correct value when a
288
+ # User or UNIXUser is loaded by AD#load from the AD object the Container
289
+ # belongs to.
290
+ #
291
+ # === Parameter Types
292
+ #
293
+ # * surname [String]
294
+ def surname=(surname)
295
+ @surname = surname
296
+ @modified = true
297
+ end
298
+
299
+ # The path to the User or UNIXUser object's logon script.
300
+ def script_path
301
+ @script_path
302
+ end
303
+
304
+ # Set the User or UNIXUser logon script path. This corresponds to the
305
+ # LDAP scriptPath attribute and is the "Logon script" setting in the
306
+ # Profile tab of the Active Directory Users and Computers tool.
307
+ #
308
+ # === Parameter Types
309
+ #
310
+ # * script_path [String]
311
+ def script_path=(script_path)
312
+ @script_path = script_path
313
+ @modified = true
314
+ end
315
+
316
+ # The path to the User or UNIXUser object's Windows profile.
317
+ def profile_path
318
+ @profile_path
319
+ end
320
+
321
+ # Set the User or UNIXUser profile path. This corresponds to the LDAP
322
+ # profilePath attribute and is the "Profile path" setting in the Profile
323
+ # tab of the Active Directory Users and Computers tool.
324
+ #
325
+ # === Parameter Types
326
+ #
327
+ # * profile_path [String]
328
+ def profile_path=(profile_path)
329
+ @profile_path = profile_path
330
+ @modified = true
331
+ end
332
+
333
+ # The "Local path" represented in the Active Directory Users and Computers
334
+ # Profile tab Home folder section. This also represents the path value
335
+ # used in the User#connect_drive_to method and is used in conjunction with
336
+ # User#local_drive in the case User#connect_drive_to was called instead of
337
+ # simply calling the User#local_path= method.
338
+ def local_path
339
+ @local_path
340
+ end
341
+
342
+ # Set the User or UNIXUser "Local path" in the Active Directory Users and
343
+ # Computers Profile tab Home folder section. One can either set the "Local
344
+ # path" or set the "Connect ... To" part of the Home folder section. This
345
+ # sets the LDAP homeDirectory attribute only. If you want to connect a drive
346
+ # to a path for the Home folder, use then User#connect_drive_to method
347
+ # instead. Note that this method makes sure that the homeDrive LDAP
348
+ # attribute is not set to enforce the proper behavior on the LDAP side.
349
+ #
350
+ # === Parameter Types
351
+ #
352
+ # * path [String]
353
+ def local_path=(path)
354
+ @local_drive = nil
355
+ @local_path = path
356
+ @modified = true
357
+ end
358
+
359
+ # The drive used in the User#connect_drive_to method when setting the
360
+ # "Connect ... To" Home folder section of the Active Directory Users
361
+ # and Computers Profile tab section. This value should be used in
362
+ # conjunction with the User#local_path value if the User#connect_drive_to
363
+ # method was called.
364
+ def local_drive
365
+ @local_drive
366
+ end
367
+
368
+ # Set the User or UNIXUser "Connect ... To" in the Active Directory Users
369
+ # and Computers Profile tab Home folder section. One can either set the
370
+ # "Connect ... To" or set the "Local path" part of the Home folder section.
371
+ # This sets the LDAP homeDrive and homeDirectory attributes. If you want to
372
+ # simply set a "Local path" for the Home folder, use the User#local_path=
373
+ # method instead.
374
+ #
375
+ # As an example, to connect drive Z: to \\\\server\\share, do the following
376
+ # on a User or UNIXUser object named user:
377
+ #
378
+ # user.connect_drive_to "Z:", "\\\\server\\share"
379
+ #
380
+ # These values can be retrived using:
381
+ #
382
+ # user.local_drive # --> "Z:"
383
+ # user.local_path # --> "\\server\share"
384
+ #
385
+ # The user.local_path value is also used by itself if only the "Local path"
386
+ # was set for the Home folder section of the Profile tab in Active Directory
387
+ # Users and Computers using the User#local_path= method, but here it is
388
+ # also used in this case as well.
389
+ #
390
+ # === Parameter Types
391
+ #
392
+ # * drive [String]
393
+ # * path [String]
394
+ def connect_drive_to(drive, path)
395
+ @local_drive = drive
396
+ @local_path = path
397
+ @modified = true
398
+ end
399
+
400
+ # The User or UNIXUser Windows password. This is only set to a value other
401
+ # than nil if the password should be changed on the next AD#sync call. Once
402
+ # the User or UNIXUser is synchronized with Active Directory, the password
403
+ # attribute is set to nil again. This is because the password attribute does
404
+ # not actually reflect the current Active Directory user password, which
405
+ # cannot be read through LDAP directly.
406
+ def password
407
+ @password
408
+ end
409
+
410
+ # Set the User or UNIXUser Windows password. This defaults to nil when a
411
+ # User or UNIXUser is created using User.new or UNIXUser.new. This does not
412
+ # reflect the current User or UNIXUser password, but if it is set, the
413
+ # password will be changed. Once the User or UNIXUser is synchronized with
414
+ # Active Directory using AD#sync the password attribute is set to nil
415
+ # again. This is because the password attribute does not actually reflect
416
+ # the current Active Directory user password, which cannot be read through
417
+ # LDAP directly.
418
+ #
419
+ # === Parameter Types
420
+ #
421
+ # * password [String]
422
+ def password=(password)
423
+ @password = password
424
+ @modified = true
425
+ end
426
+
427
+ # Check if the User or UNIXUser has to change their Windows password on
428
+ # their first login. Returns true if this is the case, false otherwise.
429
+ # This defaults to false when User or UNIXUser objects are created.
430
+ def must_change_password?
431
+ @must_change_password
432
+ end
433
+
434
+ # Force the User or UNIXUser to change their password on their next
435
+ # login. Note that the default value is to not force a password change on
436
+ # the next login.
437
+ def force_change_password
438
+ @must_change_password = true
439
+ @modified = true
440
+ end
441
+
442
+ # Unset a forced password change for a User or UNIXUser. This will clear
443
+ # the forced password change.
444
+ def unset_change_password
445
+ @must_change_password = false
446
+ @modified = true
447
+ end
448
+
449
+ # The User or UNIXUser primary Windows group. This is usually the "Domain
450
+ # Users" Windows group. User and UNIXUser objects are not members of this
451
+ # group directly. They are members through their LDAP primaryGroupID
452
+ # attribute.
453
+ def primary_group
454
+ @primary_group
455
+ end
456
+
457
+ # Set the User or UNIXUser primary Windows group. The primary Windows group
458
+ # is used by the POSIX subsystem. This is something that Windows typically
459
+ # ignores in general, and User or UNIXUser objects are members implicitly by
460
+ # their LDAP primaryGroupID attribute. The Group or UNIXGroup specified
461
+ # must be of the RADUM group type GROUP_GLOBAL_SECURITY or
462
+ # GROUP_UNIVERSAL_SECURITY or a RuntimeError is raised. The Group or
463
+ # UNIXGroup specified must be in the same AD object or a RuntimeError is
464
+ # raised. A RuntimeError is raised if the Group or UNIXGroup has been
465
+ # removed.
466
+ #
467
+ # When a User or UNIXUser changes their primary Windows group, they are
468
+ # automatically given normal group membership in the old primary Windows
469
+ # group by Active Directory. This method does the same.
470
+ #
471
+ # === Parameter Types
472
+ #
473
+ # * group [Group or UNIXGroup]
474
+ def primary_group=(group)
475
+ if group.removed?
476
+ raise "Cannot set a removed group as the primary_group."
477
+ end
478
+
479
+ unless @container.directory == group.container.directory
480
+ raise "Group must be in the same directory."
481
+ end
482
+
483
+ unless group.type == GROUP_GLOBAL_SECURITY ||
484
+ group.type == GROUP_UNIVERSAL_SECURITY
485
+ raise "User primary group must be of type GROUP_GLOBAL_SECURITY" +
486
+ " or GROUP_UNIVERSAL_SECURITY."
487
+ end
488
+
489
+ old_group = @primary_group
490
+ # This must be set before calling remove_group() because there is a
491
+ # special case where the group is also the UNIX main group (in the
492
+ # UNIXUser remote_group() method).
493
+ @primary_group = group
494
+ remove_group group
495
+ add_group old_group
496
+ @modified = true
497
+ end
498
+
499
+ # Make the User or UNIXUser a member of the Group or UNIXGroup. This is
500
+ # represented in the LDAP member attribute for the Group or UNIXGroup. A
501
+ # User or UNIXUser is listed in the Group or UNIXGroup LDAP member attribute
502
+ # unless it is the User or UNIXUser object's primary_group. In that case,
503
+ # the User or UNIXUser object's membership is based solely on the User or
504
+ # UNIXUser object's LDAP primaryGroupID attribute (which contains the RID
505
+ # of that Group or UNIXGroup - the Group or UNIXGroup does not list the
506
+ # User or UNIXUser in its LDAP member attribute, hence the logic in the
507
+ # code). The unix_main_group for UNIXUsers has the UNIXUser as a member in
508
+ # a similar way based on the LDAP gidNumber attribute for the UNIXUser. The
509
+ # UNIXGroup object's LDAP memberUid and msSFU30PosixMember attributes do
510
+ # not list the UNIXUser as a member if the UNIXGroup is their
511
+ # unix_main_group, but this module makes sure UNIXUsers are also members of
512
+ # their unix_main_group from the Windows perspective. A RuntimeError is
513
+ # raised if the User or UNIXUser already has this Group or UNIXGroup as
514
+ # their primary_group or if the Group or UNIXGroup is not in the same AD
515
+ # object. A RuntimeError is raised if the Group or UNIXGroup has been
516
+ # removed.
517
+ #
518
+ # This automatically adds the User or UNIXUser to the Group or UNIXGroup
519
+ # object's list of users.
520
+ #
521
+ # === Parameter Types
522
+ #
523
+ # * group [Group or UNIXGroup]
524
+ def add_group(group)
525
+ if group.removed?
526
+ raise "Cannot add a removed group."
527
+ end
528
+
529
+ if @container.directory == group.container.directory
530
+ unless @primary_group == group
531
+ @groups.push group unless @groups.include?(group)
532
+ @removed_groups.delete group
533
+ group.add_user self unless group.users.include?(self)
534
+ else
535
+ raise "User is already a member of their primary group."
536
+ end
537
+ else
538
+ raise "Group must be in the same directory."
539
+ end
540
+ end
541
+
542
+ # Remove the User membership in the Group or UNIXGroup. This automatically
543
+ # removes the User from the Group or UNIXGroup object's list of users.
544
+ # A RuntimeError is raised if the Group or UNIXGroup has been removed.
545
+ # This method will ignore an attempt to remove a User or UNIXUser from
546
+ # their primary Windows group since that is an implicit membership.
547
+ #
548
+ # === Parameter Types
549
+ #
550
+ # * group [Group or UNIXGroup]
551
+ def remove_group(group)
552
+ if group.removed?
553
+ raise "Cannot remove a removed group."
554
+ end
555
+
556
+ # This method can be called on a primary_group change. If the user was a
557
+ # member of the primary_group, we want to make sure we remove that
558
+ # membership. It is also possible the user was not already a member of
559
+ # that primary_group. We only want to add that group to the
560
+ # @removed_groups array if they were really a member, otherwise we would
561
+ # not care.
562
+ if @groups.include? group
563
+ @removed_groups.push group unless @removed_groups.include?(group)
564
+ end
565
+
566
+ @groups.delete group
567
+ group.remove_user self if group.users.include?(self)
568
+ end
569
+
570
+
571
+ # Delete all references to a Group or UNIXGroup in this object. This should
572
+ # only be called from Container#destroy_group. The User or UNIXUser is not
573
+ # considered to be modified at this point. It is simply forgetting about the
574
+ # Group or UNIXGroup.
575
+ #
576
+ # === Parameter Types
577
+ #
578
+ # * group [Group or UNIXGroup]
579
+ def destroy_group(group) # :nodoc:
580
+ @groups.delete group
581
+ @removed_groups.delete group
582
+ end
583
+
584
+ # Determine if a User or UNIXUser is a member of the Group or UNIXGroup.
585
+ # This also evaluates to true if the Group or UNIXGroup is the
586
+ # User or UNIXUser object's primary_group.
587
+ #
588
+ # === Parameter Types
589
+ #
590
+ # * group [Group or UNIXGroup]
591
+ def member_of?(group)
592
+ # Group memberships are removed when groups are removed so there is
593
+ # no need to check the group's removed status.
594
+ @groups.include?(group) || @primary_group == group
595
+ end
596
+
597
+ # Set the loaded flag. This also clears the modified flag. This should only
598
+ # be called from AD#load and AD#sync unless you really know what you are
599
+ # doing.
600
+ def set_loaded # :nodoc:
601
+ # This allows the modified attribute to be hidden.
602
+ @loaded = true
603
+ @modified = false
604
+ end
605
+
606
+ # Check if the User or UNIXUser was loaded from Active Directory.
607
+ def loaded?
608
+ @loaded
609
+ end
610
+
611
+ # True if the User or UNIXUser has been modified. This is true for manually
612
+ # created User or UNIXUser objects and false for initially loaded User and
613
+ # UNIXUser objects.
614
+ def modified?
615
+ @modified
616
+ end
617
+
618
+ # Set the RID only if it has not already been set. This is used by the AD
619
+ # class when doing synchronization. Once there is a RID value, it can be
620
+ # set. This is not meant for general use. It will only set the rid attribute
621
+ # if it has not already been set.
622
+ #
623
+ # === Parameter Types
624
+ #
625
+ # * rid [integer]
626
+ def set_rid(rid) # :nodoc:
627
+ if @rid.nil?
628
+ @rid = rid
629
+ @container.directory.rids.push rid
630
+ end
631
+ end
632
+
633
+ # True if the User or UNIXUser has been removed from its Container, false
634
+ # otherwise.
635
+ def removed?
636
+ @removed
637
+ end
638
+
639
+ # Set the User or UNIXUser removed flag.
640
+ def set_removed # :nodoc:
641
+ @removed = true
642
+ end
643
+
644
+ # The String representation of the User object.
645
+ def to_s
646
+ "User [(" + (@disabled ? "USER_DISABLED" : "USER_ENABLED") +
647
+ ", RID #{@rid}) #{@username} #{@distinguished_name}]"
648
+ end
649
+ end
650
+
651
+ # The UNIXUser class represents a UNIX Windows user account. It is a subclass
652
+ # of the User class. See the User class documentation for its attributes and
653
+ # methods as well.
654
+ class UNIXUser < User
655
+ # The UNIXUser UNIX UID. This corresponds to the LDAP uidNumber attribute.
656
+ attr_reader :uid
657
+ # The UNIXUser UNIX GID. This corresponds to the LDAP gidNumber attribute.
658
+ # This is set by setting the UNIXUser unix_main_group attribute with the
659
+ # UNIXUser.unix_main_group= method.
660
+ attr_reader :gid
661
+
662
+ # Create a new UNIXUser object that represents a UNIX user in Active
663
+ # Directory. A UNIX user is a Windows user that also has UNIX attributes.
664
+ # This method takes a Hash containing arguments, some of which
665
+ # are required and others optional. The supported arguments follow:
666
+ #
667
+ # * :username => The UNIXUser object's username [required]
668
+ # * :container => The UNIXUser object's associated Container [required]
669
+ # * :primary_group => The UNIXUser object's primary Windows group [required]
670
+ # * :disabled => UNIXUser object disabled flag [default false]
671
+ # * :rid => The RID of the UNIXUser object [optional]
672
+ # * :uid => The UNIXUser UID attribute [required]
673
+ # * :unix_main_group => The UNIXUser object's UNIX main group [required]
674
+ # * :shell => The UNIXUser shell attribute [required]
675
+ # * :home_directory => The UNIXUser home directory attribute [required]
676
+ # * :nis_domain => The UNIXUser NIS domain attribute [default "radum"]
677
+ #
678
+ # The :username argument (case-insensitive) and the :rid argument must be
679
+ # unique in the AD object, otherwise a RuntimeError is raised. The
680
+ # :primary_group argument must be of the RADUM type GROUP_GLOBAL_SECURITY
681
+ # or GROUP_UNIVERSAL_SECURITY and not a removed group, otherwise a
682
+ # RuntimeError is raised. The :disabled argument indicates if the UNIXUser
683
+ # object should be disabled, and it defaults to false. The :rid argument
684
+ # should not be set directly except from the AD#load method itself. The
685
+ # :unix_main_group argument must be a UNIXGroup object and not removed or
686
+ # a RuntimeError is raised. The :uid argument must be unique in the AD
687
+ # object or a RuntimeError is raised (this is an Active Directory
688
+ # restriction - in UNIX it is fine). The UNIXUser object automatically
689
+ # adds itself to the Container object specified by the :container argument.
690
+ # The :nis_domain defaults to "radum". The use of an NIS domain is not
691
+ # strictly required as one could simply set the right attributes in Active
692
+ # Directory and use LDAP on clients to access that data, but specifying an
693
+ # NIS domain allows for easy editing of UNIX attributes using the GUI tools
694
+ # in Windows, thus the use of a default value.
695
+ #
696
+ # Be careful with the :uid argument. RADUM only checks in the AD object
697
+ # that the :container belongs to, which is the AD object the UNIXUser
698
+ # belongs to as well. This does not include any UIDs for other objects in
699
+ # Active Directory. Creating a UNIXUser with a duplicate UID will actually
700
+ # succeed when attempted in LDAP, but the GUI tools in Windows complain. If
701
+ # you need a new UID value, use AD#load_next_uid to get one as it does check
702
+ # all UIDs (those RADUM knows about and those in Active Directory). Creating
703
+ # a UNIXUser object can't fail if the UID only exists in Active Directory
704
+ # because AD#load must be able to create UNIXUser objects that already exist
705
+ # in Active Directory.
706
+ #
707
+ # === Parameter Types
708
+ #
709
+ # * :username [String]
710
+ # * :container [Container]
711
+ # * :primary_group [Group or UNIXGroup]
712
+ # * :disabled [boolean]
713
+ # * :rid [integer]
714
+ # * :uid [integer]
715
+ # * :unix_main_group [UNIXGroup]
716
+ # * :shell [String]
717
+ # * :home_directory [String]
718
+ # * :nis_domain [String]
719
+ #
720
+ # See the documentation for each attribute method for what the default
721
+ # values of each attribute is based on calling this method.
722
+ def initialize(args = {})
723
+ super args
724
+ @uid = args[:uid] or raise "UNIXUser :uid attribute required."
725
+
726
+ # The UID must be unique. This is an Active Directory restriction.
727
+ if @container.directory.uids.include?(@uid)
728
+ raise "UID #{uid} is already in use in the directory."
729
+ end
730
+
731
+ @unix_main_group = args[:unix_main_group] or raise "UNIXUser" +
732
+ " :unix_main_group" +
733
+ " argument required."
734
+
735
+ if @container.directory == @unix_main_group.container.directory
736
+ if @unix_main_group.removed?
737
+ raise "UNIXUser unix_main_group cannot be a removed UNIXGroup."
738
+ end
739
+
740
+ unless @unix_main_group.instance_of?(UNIXGroup)
741
+ raise "UNIXUser unix_main_group must be a UNIXGroup."
742
+ else
743
+ @gid = @unix_main_group.gid
744
+ # The UNIXUser is already a member of their primary Windows group
745
+ # implicitly.
746
+ add_group @unix_main_group unless @unix_main_group == @primary_group
747
+ end
748
+ else
749
+ raise "UNIXUser unix_main_group must be in the same directory."
750
+ end
751
+
752
+ @shell = args[:shell] or raise "UNIXUser :shell argument required."
753
+ @home_directory = args[:home_directory] or raise "UNIXUser" +
754
+ " :home_directory" +
755
+ " argument required."
756
+ @nis_domain = args[:nis_domain] || "radum"
757
+ @gecos = @username
758
+ @unix_password = "*"
759
+ @shadow_expire = nil
760
+ @shadow_flag = nil
761
+ @shadow_inactive = nil
762
+ @shadow_last_change = nil
763
+ @shadow_max = nil
764
+ @shadow_min = nil
765
+ @shadow_warning = nil
766
+ @container.add_user self
767
+ end
768
+
769
+ # The UNIXUser UNIX shell.
770
+ def shell
771
+ @shell
772
+ end
773
+
774
+ # Set the UNIXUser UNIX shell. This corresponds to the LDAP loginShell
775
+ # attribute.
776
+ #
777
+ # === Parameter Types
778
+ #
779
+ # * shell [String]
780
+ def shell=(shell)
781
+ @shell = shell
782
+ @modified = true
783
+ end
784
+
785
+ # The UNIXUser UNIX home directory.
786
+ def home_directory
787
+ @home_directory
788
+ end
789
+
790
+ # Set the UNIXUser UNIX home directory. This corresponds to the LDAP
791
+ # unixHomeDirectory attribute.
792
+ #
793
+ # === Parameter Types
794
+ #
795
+ # * home_directory [String]
796
+ def home_directory=(home_directory)
797
+ @home_directory = home_directory
798
+ @modified = true
799
+ end
800
+
801
+ # The UNIXUser UNIX NIS domain.
802
+ def nis_domain
803
+ @nis_domain
804
+ end
805
+
806
+ # Set the UNIXUser UNIX NIS domain. This corresponds to the LDAP
807
+ # msSFU30NisDomain attribute. This needs to be set even if NIS services
808
+ # are not being used. This defaults to "radum" when a UNIXUser is created
809
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
810
+ # is loaded by AD#load from the AD object the Container belongs to.
811
+ #
812
+ # === Parameter Types
813
+ #
814
+ # * nis_domain [String]
815
+ def nis_domain=(nis_domain)
816
+ @nis_domain = nis_domain
817
+ @modified = true
818
+ end
819
+
820
+ # The UNIXUser UNIX GECOS field.
821
+ def gecos
822
+ @gecos
823
+ end
824
+
825
+ # Set the UNIXUser UNIX GECOS field. This corresponds to the LDAP gecos
826
+ # attribute. This defaults to username when a UNIXUser is created using
827
+ # UNIXUser.new, but it is set to the correct value when the UNIXUser is
828
+ # loaded by AD#load from the AD object the Container belongs to.
829
+ #
830
+ # === Parameter Types
831
+ #
832
+ # * gecos [String]
833
+ def gecos=(gecos)
834
+ @gecos = gecos
835
+ @modified = true
836
+ end
837
+
838
+ # The UNIXUser UNIX password field.
839
+ def unix_password
840
+ @unix_password
841
+ end
842
+
843
+ # Set the UNIXUser UNIX password field. This can be a crypt or MD5 value
844
+ # (or whatever your system supports potentially - Windows works with
845
+ # crypt and MD5 in Microsoft Identity Management for UNIX). This
846
+ # corresponds to the LDAP unixUserPassword attribute. The unix_password
847
+ # value defaults to "*" when a UNIXUser is created using UNIXUser.new,
848
+ # but it is set to the correct value when the UNIXUser is loaded by
849
+ # AD#load from the AD object the Container belongs to.
850
+ #
851
+ # It is not necessary to set the LDAP unixUserPassword attribute if you
852
+ # are using Kerberos for authentication, but you might need it if using
853
+ # LDAP (or NIS by way of LDAP in Active Directory) for user information.
854
+ # In cases where it is not needed, it is best to set this field to "*",
855
+ # which is why that is the default.
856
+ #
857
+ # === Parameter Types
858
+ #
859
+ # * unix_password [String]
860
+ def unix_password=(unix_password)
861
+ @unix_password = unix_password
862
+ @modified = true
863
+ end
864
+
865
+ # The UNIXUser UNIX shadow file expire field. This field is an integer
866
+ # in Active Directory and resturned as an integer.
867
+ def shadow_expire
868
+ @shadow_expire
869
+ end
870
+
871
+ # Set the UNIXUser UNIX shadow file expire field. This is the 8th field
872
+ # of the /etc/shadow file. This defaults to nil when a UNIXUser is created
873
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
874
+ # is loaded by AD#load from the AD object the Container belongs to. This
875
+ # only needs to be set if the shadow file information is really needed.
876
+ # It would not be needed most of the time. This corresponds to the LDAP
877
+ # shadowExpire attribute.
878
+ #
879
+ # === Parameter Types
880
+ #
881
+ # * shadow_expire [integer]
882
+ def shadow_expire=(shadow_expire)
883
+ @shadow_expire = shadow_expire
884
+ @modified = true
885
+ end
886
+
887
+ # The UNIXUser UNIX shadow file reserved field. This field is an integer
888
+ # in Active Directory and resturned as an integer.
889
+ def shadow_flag
890
+ @shadow_flag
891
+ end
892
+
893
+ # Set the UNIXUser UNIX shadow file reserved field. This is the 9th field
894
+ # of the /etc/shadow file. This defaults to nil when a UNIXUser is created
895
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
896
+ # is loaded by AD#load from the AD object the Container belongs to. This
897
+ # only needs to be set if the shadow file information is really needed.
898
+ # It would not be needed most of the time. This corresponds to the LDAP
899
+ # shadowFlag attribute.
900
+ #
901
+ # === Parameter Types
902
+ #
903
+ # * shadow_flag [integer]
904
+ def shadow_flag=(shadow_flag)
905
+ @shadow_flag = shadow_flag
906
+ @modified = true
907
+ end
908
+
909
+ # The UNIXUser UNIX shadow file inactive field. This field is an integer
910
+ # in Active Directory and resturned as an integer.
911
+ def shadow_inactive
912
+ @shadow_inactive
913
+ end
914
+
915
+ # Set the UNIXUser UNIX shadow file inactive field. This is the 7th field
916
+ # of the /etc/shadow file. This defaults to nil when a UNIXUser is created
917
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
918
+ # is loaded by AD#load from the AD object the Container belongs to. This
919
+ # only needs to be set if the shadow file information is really needed.
920
+ # It would not be needed most of the time. This corresponds to the LDAP
921
+ # shadowInactive attribute.
922
+ #
923
+ # === Parameter Types
924
+ #
925
+ # * shadow_inactive [integer]
926
+ def shadow_inactive=(shadow_inactive)
927
+ @shadow_inactive = shadow_inactive
928
+ @modified = true
929
+ end
930
+
931
+ # The UNIXUser UNIX shadow file last change field. This field is an integer
932
+ # in Active Directory and resturned as an integer.
933
+ def shadow_last_change
934
+ @shadow_last_change
935
+ end
936
+
937
+ # Set the UNIXUser UNIX shadow file last change field. This is the 3rd field
938
+ # of the /etc/shadow file. This defaults to nil when a UNIXUser is created
939
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
940
+ # is loaded by AD#load from the AD object the Container belongs to. This
941
+ # only needs to be set if the shadow file information is really needed.
942
+ # It would not be needed most of the time. This corresponds to the LDAP
943
+ # shadowLastChange attribute.
944
+ #
945
+ # === Parameter Types
946
+ #
947
+ # * shadow_last_change [integer]
948
+ def shadow_last_change=(shadow_last_change)
949
+ @shadow_last_change = shadow_last_change
950
+ @modified = true
951
+ end
952
+
953
+ # The UNIXUser UNIX shadow file max field. This field is an integer
954
+ # in Active Directory and resturned as an integer.
955
+ def shadow_max
956
+ @shadow_max
957
+ end
958
+
959
+ # Set the UNIXUser UNIX shadow file max field. This is the 5th field of
960
+ # the /etc/shadow file. This defaults to nil when a UNIXUser is created
961
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
962
+ # is loaded by AD#load from the AD object the Container belongs to. This
963
+ # only needs to be set if the shadow file information is really needed.
964
+ # It would not be needed most of the time. This corresponds to the LDAP
965
+ # shadowMax attribute.
966
+ #
967
+ # === Parameter Types
968
+ #
969
+ # * shadow_max [integer]
970
+ def shadow_max=(shadow_max)
971
+ @shadow_max = shadow_max
972
+ @modified = true
973
+ end
974
+
975
+ # The UNIXUser UNIX shadow file min field. This field is an integer
976
+ # in Active Directory and resturned as an integer.
977
+ def shadow_min
978
+ @shadow_min
979
+ end
980
+
981
+ # Set the UNIXUser UNIX shadow file min field. This is the 4th field of
982
+ # the /etc/shadow file. This defaults to nil when a UNIXUser is created
983
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
984
+ # is loaded by AD#load from the AD object the Container belongs to. This
985
+ # only needs to be set if the shadow file information is really needed.
986
+ # It would not be needed most of the time. This corresponds to the LDAP
987
+ # shadowMin attribute.
988
+ #
989
+ # === Parameter Types
990
+ #
991
+ # * shadow_min [integer]
992
+ def shadow_min=(shadow_min)
993
+ @shadow_min = shadow_min
994
+ @modified = true
995
+ end
996
+
997
+ # The UNIXUser UNIX shadow file warning field. This field is an integer
998
+ # in Active Directory and resturned as an integer.
999
+ def shadow_warning
1000
+ @shadow_warning
1001
+ end
1002
+
1003
+ # Set the UNIXUser UNIX shadow file warning field. This is the 6th field of
1004
+ # the /etc/shadow file. This defaults to nil when a UNIXUser is created
1005
+ # using UNIXUser.new, but it is set to the correct value when the UNIXUser
1006
+ # is loaded by AD#load from the AD object the Container belongs to. This
1007
+ # only needs to be set if the shadow file information is really needed.
1008
+ # It would not be needed most of the time. This corresponds to the LDAP
1009
+ # shadowWarning attribute.
1010
+ #
1011
+ # === Parameter Types
1012
+ #
1013
+ # * shadow_warning [integer]
1014
+ def shadow_warning=(shadow_warning)
1015
+ @shadow_warning = shadow_warning
1016
+ @modified = true
1017
+ end
1018
+
1019
+ # Remove the UNIXUser membership in the Group or UNIXGroup. This
1020
+ # automatically removes the UNIXUser from the Group or UNIXGroup object's
1021
+ # list of users. This method returns a RuntimeError if the group is a
1022
+ # UNIXGroup and the UNIXUser object's UNIX main group unless it is also
1023
+ # the User UNIXUser object's primary Windows group as well (due to
1024
+ # implicit membership handling, but nothing happens in that case with
1025
+ # respect to UNIX membership). UNIXGroup membership cannot be removed
1026
+ # for the UNIXUser object's UNIX main group because RADUM enforces
1027
+ # Windows group membership in the UNIX main group, unless the group
1028
+ # is also the UNIXUser object's primary Windows group. In that case
1029
+ # UNIX group membership is kept because a UNIXUser is implicitly a
1030
+ # member of their primary Windows group anyway.
1031
+ #
1032
+ # === Parameter Types
1033
+ #
1034
+ # * group [Group or UNIXGroup]
1035
+ def remove_group(group)
1036
+ if !@removed && group.instance_of?(UNIXGroup) &&
1037
+ group == @unix_main_group && group != @primary_group
1038
+ raise "A UNIXUser cannot be removed from their unix_main_group."
1039
+ end
1040
+
1041
+ super group
1042
+ end
1043
+
1044
+ # The UNIXUser UNIX main group. This is where the UNIXUser UNIX GID
1045
+ # value comes from, which is reflected in the gid attribute.
1046
+ def unix_main_group
1047
+ @unix_main_group
1048
+ end
1049
+
1050
+ # Set the UNIXUser UNIX main group. This also sets the UNIXUser gid
1051
+ # attribute. The group must be of the type UNIXGroup and in the same AD
1052
+ # object or a RuntimeError is raised. A RuntimeError is raised
1053
+ # if the UNIXGroup has been removed. This method does not automatically
1054
+ # remove membership in the previous unix_main_group UNIXGroup.
1055
+ #
1056
+ # === Parameter Types
1057
+ #
1058
+ # * group [UNIXGroup]
1059
+ def unix_main_group=(group)
1060
+ if group.removed?
1061
+ raise "Cannot set unix_main_group to a removed group."
1062
+ end
1063
+
1064
+ if group.instance_of?(UNIXGroup)
1065
+ if @container.directory == group.container.directory
1066
+ @unix_main_group = group
1067
+ @gid = group.gid
1068
+ # The UNIXUser is already a member of their primary Windows group
1069
+ # implicitly.
1070
+ add_group group unless group == @primary_group
1071
+ @modified = true
1072
+ else
1073
+ raise "UNIXUser unix_main_group must be in the same directory."
1074
+ end
1075
+ else
1076
+ raise "UNIXUser unix_main_group must be a UNIXGroup."
1077
+ end
1078
+ end
1079
+
1080
+ # The String representation of the UNIXUser object.
1081
+ def to_s
1082
+ "UNIXUser [(" + (@disabled ? "USER_DISABLED" : "USER_ENABLED") +
1083
+ ", RID #{@rid}, UID #{@uid}, GID #{@unix_main_group.gid}) #{@username} " +
1084
+ "#{@distinguished_name}]"
1085
+ end
1086
+ end
1087
+ end