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.
@@ -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