radum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,367 @@
1
+ module RADUM
2
+ # The Container class represents a directory entry which contains users and
3
+ # groups, usually an orgainizational unit (OU).
4
+ class Container
5
+ # The String represenation of the Container object's name. The name should
6
+ # be the LDAP distinguishedName attribute without the AD root path
7
+ # component.
8
+ attr_reader :name
9
+ # The AD object the Container belongs to.
10
+ attr_reader :directory
11
+ # The LDAP distinguishedName attribute for this Container.
12
+ attr_reader :distinguished_name
13
+ # An Array of User and UNIXUser objects that are in this Container.
14
+ attr_reader :users
15
+ # An Array of User and UNIXUser objects set for removal from this Container.
16
+ attr_reader :removed_users
17
+ # An Array of Group and UNIXGroup objects that are in this Container.
18
+ attr_reader :groups
19
+ # An Array of Group and UNIXGroup objects set for removal from this
20
+ # Container.
21
+ attr_reader :removed_groups
22
+
23
+ # Create a new Container object that represents an Active Directory
24
+ # container or organizational unit that contains users and groups. This
25
+ # method takes a Hash containing arguments, all of which are required.
26
+ # The supported arguments follow:
27
+ #
28
+ # * :name => The relative path in Active Directory [required]
29
+ # * :directory => The Container object's associated AD [required]
30
+ #
31
+ # An example instantiation follows:
32
+ #
33
+ # ad = RADUM::AD.new :root => 'dc=example,dc=com',
34
+ # :user => 'cn=Administrator,cn=Users',
35
+ # :password => 'password',
36
+ # :server => '192.168.1.1'
37
+ # cn = RADUM::Container.new :name => 'ou=People', :directory => ad
38
+ #
39
+ # The :name argument specifies the path to the container or organizational
40
+ # unit in Active Directory equivalent to the LDAP distinguishedName
41
+ # attribute for container or organizational unit without the root portion.
42
+ # The :directory argument is the AD object that owns the Container. A
43
+ # RuntimeError is raised if either of these arguments are missing.
44
+ #
45
+ # === Parameter Types
46
+ #
47
+ # * :name [String]
48
+ # * :directory [AD]
49
+ #
50
+ # Extraneous spaces are removed from the :name argument. The Container :name
51
+ # can have spaces, but extra spaces after any "," characters are removed
52
+ # automatically along with leading and traling white space. The Container
53
+ # must not already be in the AD or a RuntimeError is raised. Note that you
54
+ # can create Container objects for an actual container in Active Directory
55
+ # or an organizational unit (referred to here as a "container" since it
56
+ # logically contains objects and this is a higher level representation).
57
+ # Only specify Containers that are really containers ("cn=Foo") or
58
+ # organizational units ("ou=Foo"). Also note that orgainizational units can
59
+ # hold containers, but containers cannot hold organizational units.
60
+ # Therefore "ou=foo,cn=bar" is invalid, but "cn=foo,ou=bar" is valid. A
61
+ # RuntimeError is raised if this rule is violated. Lastly, Container
62
+ # objects are in a conceptually flat namespace. In other words,
63
+ # "cn=foo,ou=bar" is its own Container object. It is not represented as a
64
+ # child of the "ou=bar" organizational unit. This has been accounted for
65
+ # when synchronizing with AD#sync so that things work. For example, the
66
+ # "cn=foo,ou=bar" Container object will cause the "ou=bar" organizational
67
+ # unit to be created first, if necessary, before the "cn=bar" child
68
+ # container is created.
69
+ def initialize(args = {})
70
+ @name = args[:name] or raise "Container :name argument required."
71
+ @name.gsub!(/,\s+/, ",")
72
+ @name.strip!
73
+
74
+ if @name =~ /[Oo][Uu]=.*[Cc][Nn]=/
75
+ raise "Container CN objects cannot contain OU objects."
76
+ end
77
+
78
+ @directory = args[:directory] or raise "Container :directory argument" +
79
+ " required."
80
+
81
+ # The container name (like a user) must be unique (case-insensitive).
82
+ # We would not want someone accidently making two equal containers
83
+ # and adding users/groups in the wrong way.
84
+ if @directory.find_container(name)
85
+ raise "Container is already in the directory."
86
+ end
87
+
88
+ @distinguished_name = @name + "," + @directory.root
89
+ # This has to be set first before adding the Container to the AD.
90
+ @removed = false
91
+ @directory.add_container self
92
+ @users = []
93
+ @removed_users = []
94
+ @groups = []
95
+ @removed_groups = []
96
+ end
97
+
98
+ # Add User and UNIXUser objects to the Container. User and UNIXUser objects
99
+ # that were removed or destroyed cannot be added back again and are ignored.
100
+ # The User or UNIXUser must have the Container as its container attribute
101
+ # or a RuntimeError is raised.
102
+ #
103
+ # === Parameter Types
104
+ #
105
+ # * user [User or UNIXUser]
106
+ def add_user(user)
107
+ unless user.removed?
108
+ if self == user.container
109
+ # We don't want to add a user more than once.
110
+ unless @users.include?(user)
111
+ @users.push user
112
+ @removed_users.delete user
113
+ @directory.rids.push user.rid if user.rid
114
+ @directory.uids.push user.uid if user.instance_of?(UNIXUser)
115
+ end
116
+ else
117
+ raise "User must be in this container."
118
+ end
119
+ end
120
+ end
121
+
122
+ # Remove a User or UNIXUser object from the Container. This sets the
123
+ # User or UNIXUser object's removed attribute to true. If the User or
124
+ # UNIXUser is removed from the Container, it is effectively deleted
125
+ # from Active Directory. Any Group or UNIXGroup objects the User or UNIXUser
126
+ # belongs to will have their membership removed. This means that the
127
+ # User or UNIXUser will have their Group or UNIXGroup memberships
128
+ # removed for each Group or UNIXGroup they were in as well. The User or
129
+ # UNIXUser cannot be added back after removed, but it will only be
130
+ # removed from Active Directory after AD#sync is called. Any references
131
+ # to the User or UNIXUser should be discarded. The User or UNIXUser must
132
+ # be in the Container or a RuntimeError is raised. Already removed User
133
+ # or UNIXUser objects are ignored.
134
+ #
135
+ # === Parameter Types
136
+ #
137
+ # * user [User or UNIXUser]
138
+ def remove_user(user)
139
+ return if user.removed?
140
+ if self == user.container
141
+ @users.delete user
142
+ @directory.rids.delete user.rid if user.rid
143
+ @directory.uids.delete user.uid if user.instance_of?(UNIXUser)
144
+
145
+ @directory.groups.each do |group|
146
+ if group.users.include?(user)
147
+ if user.instance_of?(UNIXUser)
148
+ group.remove_user user unless group == user.unix_main_group
149
+ else
150
+ group.remove_user user
151
+ end
152
+ end
153
+ end
154
+
155
+ user.set_removed
156
+
157
+ # We have to remove the user first before we can remove the user's
158
+ # membership in their unix_main_group. It is safe to attempt removing
159
+ # a user from their unix_main_group if they are already removed.
160
+ if user.instance_of?(UNIXUser)
161
+ user.unix_main_group.remove_user user
162
+ end
163
+
164
+ @removed_users.push user unless @removed_users.include?(user)
165
+ else
166
+ raise "User must be in this container."
167
+ end
168
+ end
169
+
170
+ # Destroy a reference to the User or UNIXUser. This removes any reference
171
+ # to the User or UNIXUser from the RADUM system. This is different from
172
+ # removing a User or UNIXUser. Removal causes the User or UNIXUser to be
173
+ # deleted from Active Directory. Destroying the User or UNIXUser does not
174
+ # cause the User or UNIXUser to be removed from Active Directory, but
175
+ # it does remove all references to the User or UNIXUser from the system.
176
+ # The User or UNIXUser must be in the Container or a RuntimeError is
177
+ # raised. This does set the User or UNIXUser object's removed attribute
178
+ # to true and any references to the User or UNIXUser should be discarded.
179
+ # Once a User or UNIXUser is destroyed it cannot be added back to the
180
+ # Container.
181
+ #
182
+ # === Parameter Types
183
+ #
184
+ # * user [User or UNIXUser]
185
+ def destroy_user(user)
186
+ # Note you have to allow destruction of users even if they have been
187
+ # removed because this is the only way removed users are really deleted
188
+ # from the RADUM environment once they are deleted from Active Directory.
189
+ if self == user.container
190
+ @users.delete user
191
+ @removed_users.delete user
192
+ @directory.rids.delete user.rid if user.rid
193
+ @directory.uids.delete user.uid if user.instance_of?(UNIXUser)
194
+
195
+ @directory.groups.each do |group|
196
+ group.destroy_user user if group.users.include?(user)
197
+ end
198
+
199
+ user.set_removed
200
+ else
201
+ raise "User must be in this container."
202
+ end
203
+ end
204
+
205
+ # Add Group and UNIXGroup objects to the Container. Group and UNIXGroup
206
+ # objects that were removed or destroyed cannot be added back again and are
207
+ # ignored. The Group or UNIXGroup must have the Container as its container
208
+ # attribute or a RuntimeError is raised.
209
+ #
210
+ # === Parameter Types
211
+ #
212
+ # * group [Group or UNIXGroup]
213
+ def add_group(group)
214
+ unless group.removed?
215
+ if self == group.container
216
+ # We don't want to add a group more than once.
217
+ unless @groups.include?(group)
218
+ @groups.push group
219
+ @removed_groups.delete group
220
+ @directory.rids.push group.rid if group.rid
221
+ @directory.gids.push group.gid if group.instance_of?(UNIXGroup)
222
+ end
223
+ else
224
+ raise "Group must be in this container."
225
+ end
226
+ end
227
+ end
228
+
229
+ # Remove a Group or UNIXGroup object from the Container. This sets the
230
+ # Group or UNIXGroup object's removed attribute to true. A Group or
231
+ # UNIXGroup cannot be removed if it is still any User object's primary
232
+ # Windows group. A UNIXGroup cannot be removed if it is any User object's
233
+ # UNIX main group. In both cases, a RuntimeError will be raised. If the
234
+ # Group or UNIXGroup is removed from the Container, it is effectively
235
+ # deleted from Active Directory. Any Group or UNIXGroup objects the Group or
236
+ # UNIXGroup belongs to will have their membership removed. This
237
+ # means that the Group or UNIXGroup will have their Group or UNIXGroup
238
+ # memberships removed for each Group or UNIXGroup they were in as well.
239
+ # The Group or UNIXGroup cannot be added back after removed, but it will
240
+ # only be removed from Active Directory after AD#sync is called. Any
241
+ # references to the Group or UNIXGroup should be discarded. The Group or
242
+ # UNIXGroup must be in the Container or a RuntimeError is raised. Already
243
+ # removed Group or UNIXGroup objects are ignored.
244
+ #
245
+ # There are further checks in Active Directory to make sure the Group
246
+ # or UNIXGroup can really be removed. A warning is displayed if the Group
247
+ # or UNIXGroup cannot be removed in the AD#sync call. In this method,
248
+ # RADUM only checks objects it knows about at the time of the call.
249
+ #
250
+ # === Parameter Types
251
+ #
252
+ # * group [Group or UNIXGroup]
253
+ def remove_group(group)
254
+ return if group.removed?
255
+ if self == group.container
256
+ # We cannot remove a group that still has a user referencing it as
257
+ # their primary_group or unix_main_group.
258
+ @directory.users.each do |user|
259
+ if group == user.primary_group
260
+ raise "Cannot remove group #{group.name}: it is " +
261
+ "#{user.username}'s primary Windows group."
262
+ end
263
+
264
+ if user.instance_of?(UNIXUser)
265
+ if group == user.unix_main_group
266
+ raise "Cannot remove group #{group.name}: it is " +
267
+ "#{user.username}'s UNIX main group."
268
+ end
269
+ end
270
+ end
271
+
272
+ @groups.delete group
273
+ @directory.rids.delete group.rid if group.rid
274
+ @directory.gids.delete group.gid if group.instance_of?(UNIXGroup)
275
+
276
+ @directory.groups.each do |current_group|
277
+ if current_group.groups.include?(group)
278
+ current_group.remove_group group
279
+ end
280
+ end
281
+
282
+ @directory.users.each do |user|
283
+ if user.groups.include?(group)
284
+ user.remove_group group
285
+ end
286
+ end
287
+
288
+ group.set_removed
289
+ @removed_groups.push group unless @removed_groups.include?(group)
290
+ else
291
+ raise "Group must be in this container."
292
+ end
293
+ end
294
+
295
+ # Destroy a reference to the Group or UNIXGroup. This removes any reference
296
+ # to the Group or UNIXGroup from the RADUM system. This is different from
297
+ # removing a Group or UNIXGroup. Removal causes the Group or UNIXGroup to be
298
+ # deleted from Active Directory. Destroying the Group or UNIXGroup does not
299
+ # cause the Group or UNIXGroup to be removed from Active Directory, but
300
+ # it does remove all references to the Group or UNIXGroup from the system.
301
+ # The Group or UNIXGroup must be in the Container or a RuntimeError is
302
+ # raised. This does set the Group or UNIXGroup object's removed attribute
303
+ # to true and any references to the Group or UNIXGroup should be discarded.
304
+ # Once a Group or UNIXGroup is destroyed it cannot be added back to the
305
+ # Container.
306
+ #
307
+ # === Parameter Types
308
+ #
309
+ # * group [Group or UNIXGroup]
310
+ def destroy_group(group)
311
+ # Note you have to allow destruction of groups even if they have been
312
+ # removed because this is the only way removed groups are really deleted
313
+ # from the RADUM environment once they are deleted from Active Directory.
314
+ if self == group.container
315
+ # We cannot destroy a group that still has a user referencing it as
316
+ # their primary_group or unix_main_group.
317
+ @directory.users.each do |user|
318
+ if group == user.primary_group
319
+ raise "Cannot destroy group #{group.name}: it is " +
320
+ "#{user.username}'s primary Windows group."
321
+ end
322
+
323
+ if user.instance_of?(UNIXUser)
324
+ if group == user.unix_main_group
325
+ raise "Cannot destroy group #{group.name}: it is " +
326
+ "#{user.username}'s UNIX main group."
327
+ end
328
+ end
329
+ end
330
+
331
+ @groups.delete group
332
+ @removed_groups.delete group
333
+ @directory.rids.delete group.rid if group.rid
334
+ @directory.gids.delete group.gid if group.instance_of?(UNIXGroup)
335
+
336
+ @directory.groups.each do |current_group|
337
+ if current_group.groups.include?(group)
338
+ current_group.destroy_group group
339
+ end
340
+ end
341
+
342
+ @directory.users.each do |user|
343
+ user.destroy_group group
344
+ end
345
+
346
+ group.set_removed
347
+ else
348
+ raise "Group must be in this container."
349
+ end
350
+ end
351
+
352
+ # True if the Container has been removed from its AD, false otherwise.
353
+ def removed?
354
+ @removed
355
+ end
356
+
357
+ # Set the Container removed flag.
358
+ def set_removed # :nodoc:
359
+ @removed = true
360
+ end
361
+
362
+ # The String representation of the Container object.
363
+ def to_s
364
+ "Container [#{@name},#{@directory.root}]"
365
+ end
366
+ end
367
+ end
@@ -0,0 +1,455 @@
1
+ module RADUM
2
+ # The Group class represents a standard Windows group.
3
+ class Group
4
+ # The String representation of the Group or UNIXGroup name. This is similar
5
+ # to a User or UNIXUser username in that it does not contain any LDAP path
6
+ # components. This corresponds to the LDAP cn, msSFU30Name, name, and
7
+ # sAMAccountName attributes.
8
+ attr_reader :name
9
+ # The Container object the Group or UNIXGroup belongs to.
10
+ attr_reader :container
11
+ # The RADUM group type of the Group or UNIXGroup. This corresponds to the
12
+ # LDAP groupType attribute. This defaults to GROUP_GLOBAL_SECURITY when
13
+ # a Group or UNIXGroup is created using Group.new or UNIXGroup.new, but
14
+ # it is set to the correct value when a Group or UNIXGroup is loaded by
15
+ # AD#load from the AD object the Container belongs to.
16
+ attr_reader :type
17
+ # The RID of the Group or UNIXGroup object. This correponds to part of the
18
+ # LDAP objectSid attribute. This is set when the Group or UNIXGroup is
19
+ # loaded by AD#load from the AD object the Container belongs to. This
20
+ # attribute should not be specified in the Group.new or UNIXGroup.new
21
+ # methods when creating a new Group or UNIXGroup by hand.
22
+ attr_reader :rid
23
+ # The LDAP distinguishedName attribute for this Group or UNIXGroup.
24
+ attr_reader :distinguished_name
25
+ # The User or UNIXUser objects the Group or UNIXGroup has a members. User
26
+ # and UNIXUser objects are implicit members of their primary_group as well,
27
+ # but they are not added to the users array directly. This matches the
28
+ # implicit membership in the primary Windows group in Active Directory.
29
+ attr_reader :users
30
+ # An array of User or UNIXUser objects removed from the Group or UNIXGroup.
31
+ attr_reader :removed_users
32
+ # The Group or UNIXGroup objects that are members of the Group or UNIXGroup.
33
+ attr_reader :groups
34
+ # An array of Group or UNIXGroup objects removed from the Group or
35
+ # UNIXGroup.
36
+ attr_reader :removed_groups
37
+
38
+ # Create a new Group object that represents a Windows group in Active
39
+ # Directory. This method takes a Hash containing arguments, some of which
40
+ # are required and others optional. The supported arguments follow:
41
+ #
42
+ # * :name => The Group object's name [required]
43
+ # * :container => The Group object's associated Container [required]
44
+ # * :type => The RADUM group type [default GROUP_GLOBAL_SECURITY]
45
+ # * :rid => The RID of the Group object [optional]
46
+ #
47
+ # The :name argument (case-insensitive) and the :rid argument must be
48
+ # unique in the AD object, otherwise a RuntimeError is raised. The :name
49
+ # argument has leading and trailing white space removed. The :type
50
+ # argument must be one of the RADUM group type constants. The :rid argument
51
+ # should not be set directly except from the AD#load method itself.
52
+ # The Group object automatically adds itself to the Container object
53
+ # specified by the :container argument.
54
+ #
55
+ # === Parameter Types
56
+ #
57
+ # * :name [String]
58
+ # * :container [Container]
59
+ # * :type [integer => RADUM group type constant]
60
+ # * :rid [integer]
61
+ def initialize(args = {})
62
+ @rid = args[:rid] || nil
63
+ @container = args[:container] or raise "Group :container argument" +
64
+ " required."
65
+
66
+ # The RID must be unique.
67
+ if @container.directory.rids.include?(@rid)
68
+ raise "RID #{rid} is already in use in the directory."
69
+ end
70
+
71
+ @name = args[:name] or raise "Group :name argument required."
72
+ @name.strip!
73
+
74
+ # The group name (like a user) must be unique (case-insensitive). This
75
+ # is needed in case someone tries to make the same group name in two
76
+ # different containers.
77
+ if @container.directory.find_group_by_name(@name)
78
+ raise "Group is already in the directory."
79
+ end
80
+
81
+ @type = args[:type] || GROUP_GLOBAL_SECURITY
82
+ @distinguished_name = "cn=" + name + "," + @container.name + "," +
83
+ @container.directory.root
84
+ @users = []
85
+ @removed_users = []
86
+ @groups = []
87
+ @removed_groups = []
88
+ # This has to be set first before adding the Group to the Container. This
89
+ # is delayed for a UNIXGroup because it needs the rest of its attributes
90
+ # set before adding to the Container.
91
+ @removed = false
92
+ @container.add_group self unless instance_of?(UNIXGroup)
93
+ @modified = true
94
+ @loaded = false
95
+ end
96
+
97
+ # Make the User or UNIXUser a member of the Group or UNIXGroup. This
98
+ # represents the LDAP member attribute for the Group or UNIXGroup. A User
99
+ # or UNIXUser is listed in the Group or UNIXGroup object's LDAP member
100
+ # attribute unless it is their primary_group. In that case, the User or
101
+ # UNIXUser object's LDAP primaryGroupID attribute is used (which contains
102
+ # the RID of that Group or UNIXGroup - the Group or UNIXGroup does not list
103
+ # the User or UNIXUser in its LDAP member attribute, hence the logic in the
104
+ # code). The unix_main_group for UNIXUsers has the UNIXUser as a member in a
105
+ # similar way based on the LDAP gidNumber attribute for the UNIXUser. The
106
+ # UNIXGroup object's LDAP memberUid and msSFU30PosixMember attributes do
107
+ # not list the UNIXUser as a member of the UNIXGroup is their
108
+ # unix_main_group, but this module makes sure the UNIXUsers are also
109
+ # members of their unix_main_group from the Windows perspective. A
110
+ # RuntimeError is raised if the User or UNIXUser already has this Group or
111
+ # UNIXGroup as their primary_group or if the Group or UNIXGroup is not in
112
+ # the same AD object. A RuntimeError is raised if the User or UNIXUser has
113
+ # been removed.
114
+ #
115
+ # This automatically adds the Group or UNIXGroup to the User or UNIXUser
116
+ # object's list of groups.
117
+ #
118
+ # === Parameter Types
119
+ #
120
+ # * user [User or UNIXUser]
121
+ def add_user(user)
122
+ if user.removed?
123
+ raise "Cannot add a removed user."
124
+ end
125
+
126
+ if @container.directory == user.container.directory
127
+ unless self == user.primary_group
128
+ @users.push user unless @users.include?(user)
129
+ @removed_users.delete user
130
+ user.add_group self unless user.groups.include?(self)
131
+ @modified = true
132
+ else
133
+ raise "Group is already the user's primary_group."
134
+ end
135
+ else
136
+ raise "User must be in the same directory."
137
+ end
138
+ end
139
+
140
+ # Remove the User or UNIXUser membership in the Group. This automatically
141
+ # removes the Group from the User or UNIXUser object's list of groups.
142
+ # A RuntimeError is raised if the User or UNIXUser has been removed.
143
+ # Any external references to the User or UNIXUser should be discarded.
144
+ #
145
+ # === Parameter Types
146
+ #
147
+ # * user [User or UNIXUser]
148
+ def remove_user(user)
149
+ if user.removed?
150
+ raise "Cannot remove a removed user."
151
+ end
152
+
153
+ @users.delete user
154
+ @removed_users.push user unless @removed_users.include?(user)
155
+ user.remove_group self if user.groups.include?(self)
156
+ @modified = true
157
+ end
158
+
159
+ # Delete all references to a User or UNIXUser in this object. This should
160
+ # only be called from Container#destroy_user. The Group or UNIXGroup is not
161
+ # considered to be modified at this point. It is simply forgetting about the
162
+ # User or UNIXUser.
163
+ #
164
+ # === Parameter Types
165
+ #
166
+ # * user [User or UNIXUser]
167
+ def destroy_user(user) # :nodoc:
168
+ @users.delete user
169
+ @removed_users.delete user
170
+ end
171
+
172
+ # Determine if this Group or UNIXGroup is a member of the Group or
173
+ # UNIXGroup given as the argument.
174
+ #
175
+ # === Parameter Types
176
+ #
177
+ # * group [Group or UNIXGroup]
178
+ def member_of?(group)
179
+ # Memberships are already removed from removed groups. We have to check
180
+ # the group passed in since groups are only tracking things they contain.
181
+ # This method for User and UNIXUser objects can just check their own
182
+ # groups array, but that's not possible here obviously.
183
+ group.groups.include? self
184
+ end
185
+
186
+ # Make the Group or UNIXGroup given as the argument a member of this Group
187
+ # or UNIXGroup. This represents the LDAP member attribute for the Group or
188
+ # UNIXGroup. A RuntimeError is raised if the Group or UNIXGroup is the same
189
+ # as the current Group or UNIXGroup (cannot be a member of itself) or the
190
+ # Group or UNIXGroup is not in the same AD object. A RuntimeError is raised
191
+ # if the Group or UNIXGroup has been removed.
192
+ #
193
+ # === Parameter Types
194
+ #
195
+ # * group [Group or UNIXGroup]
196
+ def add_group(group)
197
+ if group.removed?
198
+ raise "Cannot add a removed group."
199
+ end
200
+
201
+ unless @container.directory == group.container.directory
202
+ raise "Group must be in the same directory."
203
+ end
204
+
205
+ if self == group
206
+ raise "A group cannot have itself as a member."
207
+ end
208
+
209
+ @groups.push group unless @groups.include?(group)
210
+ @removed_groups.delete group
211
+ @modified = true
212
+ end
213
+
214
+ # Remove the Group or UNIXGroup membership in the Group or UNIXGroup.
215
+ # A RuntimeError is raised if the Group or UNIXGroup has been removed.
216
+ # Any external references to the Group or UNIXGroup should be discarded.
217
+ #
218
+ # === Parameter Types
219
+ #
220
+ # * group [Group or UNIXGroup]
221
+ def remove_group(group)
222
+ if group.removed?
223
+ raise "Cannot remove a removed group."
224
+ end
225
+
226
+ @groups.delete group
227
+ @removed_groups.push group unless @removed_groups.include?(group)
228
+ @modified = true
229
+ end
230
+
231
+ # Delete all references to a Group or UNIXGroup in this object. This should
232
+ # only be called from Container#destroy_group. The Group or UNIXGroup is not
233
+ # considered to be modified at this point. It is simply forgetting about the
234
+ # Group or UNIXGroup.
235
+ #
236
+ # === Parameter Types
237
+ #
238
+ # * group [Group or UNIXGroup]
239
+ def destroy_group(group) # :nodoc:
240
+ @groups.delete group
241
+ @removed_groups.delete group
242
+ end
243
+
244
+ # Set the loaded flag. This also clears the modified flag. This should only
245
+ # be called from AD#load and AD#sync unless you really know what you are
246
+ # doing.
247
+ def set_loaded # :nodoc:
248
+ # This allows the modified attribute to be hidden.
249
+ @loaded = true
250
+ @modified = false
251
+ end
252
+
253
+ # Check if the Group or UNIXGroup was loaded from Active Directory.
254
+ def loaded?
255
+ @loaded
256
+ end
257
+
258
+ # True if the Group or UNIXGroup has been modified. This is true for
259
+ # manually created Group or UNIXGroup objects and false for initially
260
+ # loaded Group and UNIXGroup objects.
261
+ def modified?
262
+ @modified
263
+ end
264
+
265
+ # Set the RID only if it has not already been set. This is used by the AD
266
+ # class when doing synchronization. Once there is a RID value, it can be
267
+ # set. This is not meant for general use. It will only set the rid attribute
268
+ # if it has not already been set.
269
+ #
270
+ # === Parameter Types
271
+ #
272
+ # * rid [integer]
273
+ def set_rid(rid) # :nodoc:
274
+ if @rid.nil?
275
+ @rid = rid
276
+ @container.directory.rids.push rid
277
+ end
278
+ end
279
+
280
+ # True if the Group or UNIXGroup has been removed from its Container, false
281
+ # otherwise.
282
+ def removed?
283
+ @removed
284
+ end
285
+
286
+ # Set the Group or UNIXGroup removed flag.
287
+ def set_removed # :nodoc:
288
+ @removed = true
289
+ end
290
+
291
+ # The String representation of the Group object.
292
+ def to_s
293
+ "Group [(" + RADUM.group_type_to_s(@type) +
294
+ ", RID #{@rid}) #{@distinguished_name}]"
295
+ end
296
+ end
297
+
298
+ # The UNIXGroup class represents a UNIX Windows group. It is a subclass of
299
+ # the Group class. See the Group class documentation for its attributes and
300
+ # methods as well.
301
+ class UNIXGroup < Group
302
+ # The UNIXGroup UNIX GID. This corresponds to the LDAP gidNumber
303
+ # attribute.
304
+ attr_reader :gid
305
+
306
+ # Create a new UNIXGroup object that represents a UNIX group in Active
307
+ # Directory. A UNIX group is a Windows group that also has UNIX attributes.
308
+ # This method takes a Hash containing arguments, some of which are required
309
+ # and others optional. The supported arguments follow:
310
+ #
311
+ # * :name => The UNIXGroup object's name [required]
312
+ # * :container => The UNIXGroup object's associated Container [required]
313
+ # * :type => The RADUM group type [default GROUP_GLOBAL_SECURITY]
314
+ # * :rid => The RID of the UNIXGroup object [optional]
315
+ # * :gid => The UNIXGroup GID attribute [required]
316
+ # * :nis_domain => The UNIXGroup NIS domain attribute [default "radum"]
317
+ #
318
+ # The :name argument (case-insensitive) and the :rid argument must be
319
+ # unique in the AD object, otherwise a RuntimeError is raised. The :type
320
+ # argument must be one of the RADUM group type constants. The :rid argument
321
+ # should not be set directly except from the AD#load method itself.
322
+ # The UNIXGroup object automatically adds itself to the Container object
323
+ # specified by the :container argument. The :gid argument specifies the
324
+ # UNIX GID value of the UNIXGroup. The :gid value must be unique in the
325
+ # AD object or a RuntimeError is raised (this is an Active Directory
326
+ # restriction - in UNIX it is fine). The :nis_domain defaults to
327
+ # "radum". The use of an NIS domain is not strictly required as one could
328
+ # simply set the right attributes in Active Directory and use LDAP on
329
+ # clients to access that data, but specifying an NIS domain allows for easy
330
+ # editing of UNIX attributes using the GUI tools in Windows, thus the use
331
+ # of a default value.
332
+ #
333
+ # Be careful with the :gid argument. RADUM only checks in the AD object
334
+ # that the :container belongs to, which is the AD object the UNIXGroup
335
+ # belongs to as well. This does not include any GIDs for other objects in
336
+ # Active Directory. Creating a UNIXGroup with a duplicate GID will actually
337
+ # succeed when attempted in LDAP, but the GUI tools in Windows complain. If
338
+ # you need a new GID value, use AD#load_next_gid to get one as it does check
339
+ # all GIDs (those RADUM knows about and those in Active Directory). Creating
340
+ # a UNIXGroup object can't fail if the GID only exists in Active Directory
341
+ # because AD#load must be able to create UNIXGroup objects that already
342
+ # exist in Active Directory.
343
+ #
344
+ # === Parameter Types
345
+ #
346
+ # * :name [String]
347
+ # * :container [Container]
348
+ # * :type [integer => RADUM group type constant]
349
+ # * :rid [integer]
350
+ # * :gid [integer]
351
+ # * :nis_domain [String]
352
+ def initialize(args = {})
353
+ super args
354
+ @gid = args[:gid] or raise "UNIXGroup :gid argument required."
355
+
356
+ # The GID must be unique. This is an Active Directory restriction.
357
+ if @container.directory.gids.include?(@gid)
358
+ raise "GID #{gid} is already in use in the directory."
359
+ end
360
+
361
+ @nis_domain = args[:nis_domain] || "radum"
362
+ @unix_password = "*"
363
+ @container.add_group self
364
+ end
365
+
366
+ # Remove the User or UNIXUser membership in the UNIXGroup. This
367
+ # automatically removes the UNIXGroup from the User or UNIXUser object's
368
+ # list of groups. This method returns a RuntimeError if the user
369
+ # has this UNIXGroup as their UNIX main group unless this group is also
370
+ # the User or UNIXUser object's primary Windows group as well (due to
371
+ # implicit membership handling, but nothing happens in that case with
372
+ # respect to UNIX membership). UNIXGroup membership cannot be removed
373
+ # for the UNIXUser object's UNIX main group because RADUM enforces
374
+ # Windows group membership in the UNIX main group, unless the group
375
+ # is also the UNIXUser object's primary Windows group too.
376
+ #
377
+ # === Parameter Types
378
+ #
379
+ # * user [User or UNIXUser]
380
+ def remove_user(user)
381
+ if !user.removed? && user.instance_of?(UNIXUser) &&
382
+ self == user.unix_main_group && self != user.primary_group
383
+ raise "A UNIXUser cannot be removed from their unix_main_group."
384
+ end
385
+
386
+ # Removing a user from its unix_main_group is a special case due to
387
+ # the complicated logic. When called from Container#remove_user the
388
+ # user's removed flag is set to true when a removal from the user's
389
+ # unix_main_group is attempted. This catches that special case and
390
+ # does the right thing. This is needed because of all my checks for
391
+ # not working with removed objects.
392
+ if user.removed? && user.instance_of?(UNIXUser) &&
393
+ self == user.unix_main_group
394
+ @users.delete user
395
+
396
+ # The UNIXUser is not a Windows member of their UNIX main group
397
+ # directly if it is also their primary Windows group.
398
+ if self != user.primary_group
399
+ @removed_users.push user unless @removed_users.include?(user)
400
+ end
401
+
402
+ user.remove_group self if user.groups.include?(self)
403
+ @modified = true
404
+ else
405
+ super user
406
+ end
407
+ end
408
+
409
+ # The UNIXGroup UNIX NIS domain.
410
+ def nis_domain
411
+ @nis_domain
412
+ end
413
+
414
+ # Set the UNIXGroup UNIX NIS domain. This corresponds to the LDAP
415
+ # msSFU30NisDomain attribute. This needs to be set even if NIS services
416
+ # are not being used. This defaults to "radum" when a UNIXGroup is created
417
+ # using UNIXGroup.new, but it is set to the correct value when the UNIXGroup
418
+ # is loaded by AD#load from the AD object the Container belongs to.
419
+ #
420
+ # === Parameter Types
421
+ #
422
+ # * nis_domain [String]
423
+ def nis_domain=(nis_domain)
424
+ @nis_domain = nis_domain
425
+ @modified = true
426
+ end
427
+
428
+ # The UNIXGroup UNIX password field.
429
+ def unix_password
430
+ @unix_password
431
+ end
432
+
433
+ # Set the UNIXGroup UNIX password field. This can be a crypt or MD5 value
434
+ # (or whatever your system supports potentially - Windows works with crypt
435
+ # and MD5 in Microsoft Identity Management for UNIX). This corresponds to
436
+ # the LDAP unixUserPassword attribute. The unix_password value defaults
437
+ # to "*" when a UNIXGroup is created using UNIXGroup.new, but it is set
438
+ # to the correct value when the UNIXGroup is loaded by AD#load from the AD
439
+ # object the Container belongs to.
440
+ #
441
+ # === Parameter Types
442
+ #
443
+ # * unix_password [String]
444
+ def unix_password=(unix_password)
445
+ @unix_password = unix_password
446
+ @modified = true
447
+ end
448
+
449
+ # The String representation of the UNIXGroup object.
450
+ def to_s
451
+ "UNIXGroup [(" + RADUM.group_type_to_s(@type) +
452
+ ", RID #{@rid}, GID #{@gid}) #{@distinguished_name}]"
453
+ end
454
+ end
455
+ end