1
from landscape.lib import md5crypt
2
from landscape.user.management import UserManagement, UserManagementError
3
from landscape.user.tests.helpers import FakeUserProvider
4
from landscape.user.provider import UserNotFoundError, GroupNotFoundError
5
from landscape.tests.helpers import LandscapeTest, MakePathHelper, MockPopen
8
def guess_password(generated_password, plaintext_password):
9
salt = generated_password[len("$1$"):generated_password.rfind("$")]
10
crypted = md5crypt.md5crypt(plaintext_password, salt)
14
class UserWriteTest(LandscapeTest):
16
helpers = [MakePathHelper]
19
LandscapeTest.setUp(self)
20
self.shadow_file = self.make_path("""\
21
jdoe:$1$xFlQvTqe$cBtrNEDOIKMy/BuJoUdeG0:13348:0:99999:7:::
22
psmith:!:13348:0:99999:7:::
23
sbarnes:$1$q7sz09uw$q.A3526M/SHu8vUb.Jo1A/:13349:0:99999:7:::
26
def test_add_user(self):
27
"""L{UserManagement.add_user} should use C{adduser} to add users."""
28
groups = [("users", "x", 1001, [])]
29
provider = FakeUserProvider(groups=groups, popen=MockPopen(""))
30
management = UserManagement(provider=provider)
31
management.add_user("jdoe", "John Doe", "password", False, "users",
32
"Room 101", "+123456", None)
33
self.assertEquals(len(provider.popen.popen_inputs), 2)
34
self.assertEquals(provider.popen.popen_inputs[0],
35
["adduser", "jdoe", "--disabled-password",
36
"--gecos", "John Doe,Room 101,+123456,",
39
usermod = provider.popen.popen_inputs[1]
40
self.assertEquals(len(usermod), 4, usermod)
41
password = guess_password(usermod[2], "password")
42
self.assertEquals(usermod, ["usermod", "-p", password, "jdoe"])
44
def test_add_user_error(self):
46
L{UserManagement.add_user} should raise an L{UserManagementError} if
49
provider = FakeUserProvider(popen=MockPopen("", return_codes=[1, 0]))
50
management = UserManagement(provider=provider)
51
self.assertRaises(UserManagementError, management.add_user,
52
"jdoe", u"John Doe", "password", False, None, None,
55
def test_change_password_error(self):
57
L{UserManagement.add_user} should raise an L{UserManagementError} if
60
provider = FakeUserProvider(popen=MockPopen("", return_codes=[0, 1]))
61
management = UserManagement(provider=provider)
62
self.assertRaises(UserManagementError, management.add_user,
63
"jdoe", u"John Doe", "password", False, None, None,
66
def test_expire_password_error(self):
68
L{UserManagement.add_user} should raise an L{UserManagementError} if
71
provider = FakeUserProvider(popen=MockPopen("",
72
return_codes=[0, 0,1]))
73
management = UserManagement(provider=provider)
74
self.assertRaises(UserManagementError, management.add_user,
75
"jdoe", u"John Doe", "password", True, None, None,
78
def test_set_password(self):
80
L{UserManagement.set_password} should use C{usermod} to change
83
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
84
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
85
popen=MockPopen("no output"))
86
management = UserManagement(provider=provider)
87
management.set_user_details("jdoe", password="password")
89
self.assertEquals(len(provider.popen.popen_inputs), 1)
90
password = provider.popen.popen_inputs[0][2]
91
password = guess_password(password, "password")
92
self.assertEquals(provider.popen.popen_inputs,
93
[["usermod", "-p", password, "jdoe"]])
95
def test_set_password_with_system_user(self):
97
L{UserManagement.set_password} should allow us to edit system
100
data = [("root", "x", 0, 0, ",,,,", "/home/root", "/bin/zsh")]
101
provider = FakeUserProvider(users=data,
102
shadow_file=self.shadow_file,
103
popen=MockPopen("no output"))
104
management = UserManagement(provider=provider)
105
management.set_user_details("root", password="password")
106
self.assertEquals(len(provider.popen.popen_inputs), 1)
107
password = provider.popen.popen_inputs[0][2]
108
password = guess_password(password, "password")
109
self.assertEquals(provider.popen.popen_inputs,
110
[["usermod", "-p", password, "root"]])
112
def test_set_password_unicode(self):
114
Make sure passing unicode as username and password doesn't
115
change things much (note that using something that's
116
non-ASCII-encodable still probably won't work).
118
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
119
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
120
popen=MockPopen("no output"))
121
management = UserManagement(provider=provider)
122
management.set_user_details("jdoe", password=u"password")
124
self.assertEquals(len(provider.popen.popen_inputs), 1)
125
password = provider.popen.popen_inputs[0][2]
126
password = guess_password(password, "password")
127
self.assertEquals(provider.popen.popen_inputs,
128
[["usermod", "-p", password, "jdoe"]])
130
def test_set_name(self):
132
L{UserManagement.set_user_details} should use C{chfn} to
133
change a user's name.
135
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
136
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
137
popen=MockPopen("no output"))
138
management = UserManagement(provider=provider)
139
management.set_user_details("jdoe", name="JD")
141
self.assertEquals(len(provider.popen.popen_inputs), 1)
142
self.assertEquals(provider.popen.popen_inputs,
143
[["chfn", "-f", "JD", "jdoe"]])
145
def test_set_location(self):
147
L{UserManagement.set_user_details} should use C{chfn} to
148
change a user's location.
150
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
151
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
152
popen=MockPopen("no output"))
153
management = UserManagement(provider=provider)
154
management.set_user_details("jdoe", location="Everywhere")
156
self.assertEquals(len(provider.popen.popen_inputs), 1)
157
self.assertEquals(provider.popen.popen_inputs,
158
[["chfn", "-r", "Everywhere", "jdoe"]])
160
def test_clear_user_location(self):
162
L{UserManagement.set_user_details} should use C{chfn} to
163
change a user's location.
165
data = [("jdoe", "x", 1000, 1000, "JD,Room 101,,,", "/home/jdoe",
167
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
168
popen=MockPopen("no output"))
169
management = UserManagement(provider=provider)
170
management.set_user_details("jdoe", location="")
171
self.assertEquals(len(provider.popen.popen_inputs), 1)
172
self.assertEquals(provider.popen.popen_inputs,
173
[["chfn", "-r", "", "jdoe"]])
175
def test_clear_telephone_numbers(self):
177
L{UserManagement.set_user_details} should use C{chfn} to
178
change a user's telephone numbers.
180
data = [("jdoe", "x", 1000, 1000, "JD,,+123456,+123456", "/home/jdoe",
182
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
183
popen=MockPopen("no output"))
184
management = UserManagement(provider=provider)
185
management.set_user_details("jdoe", home_number="", work_number="")
186
self.assertEquals(len(provider.popen.popen_inputs), 1)
187
self.assertEquals(provider.popen.popen_inputs,
188
[["chfn", "-w", "", "-h", "", "jdoe"]])
190
def test_set_user_details_fails(self):
192
L{UserManagement.set_user_details} should raise an
193
L{EditUserError} if C{chfn} fails.
195
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
196
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
197
popen=MockPopen("", return_codes=[1]))
198
management = UserManagement(provider=provider)
199
self.assertRaises(UserNotFoundError, management.set_user_details, 1000,
202
def test_contact_details_in_general(self):
204
L{UserManagement.set_user_details} should use C{chfn} to
205
change a user's contact details.
207
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
208
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
209
popen=MockPopen("no output"))
210
management = UserManagement(provider=provider)
211
location = u"Everywhere"
212
work_number = u"1-800-123-4567"
213
home_number = u"764-4321"
214
management.set_user_details("jdoe", location=location,
215
work_number=work_number,
216
home_number=home_number)
218
self.assertEquals(len(provider.popen.popen_inputs), 1)
219
self.assertEquals(provider.popen.popen_inputs,
220
[["chfn", "-r", location, "-w", work_number,
221
"-h", home_number, "jdoe"]])
223
def test_set_user_details_with_unknown_username(self):
225
L{UserManagement.set_user_details} should raise a
226
L{UserManagementError} if the user being edited doesn't exist.
228
provider = FakeUserProvider(popen=MockPopen(""))
229
management = UserManagement(provider=provider)
230
self.assertRaises(UserNotFoundError, management.set_user_details,
231
"kevin", name=u"John Doe")
233
def test_set_primary_group(self):
235
L{UserManagement.set_set_user_details} should use C{usermod} to change
236
the user's primary group.
238
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
239
groups = [("users", "x", 1001, [])]
240
provider = FakeUserProvider(users=data, groups=groups,
241
shadow_file=self.shadow_file,
242
popen=MockPopen("no output"))
244
management = UserManagement(provider=provider)
245
management.set_user_details("jdoe", primary_group_name="users")
247
self.assertEquals(provider.popen.popen_inputs,
248
[["usermod", "-g", "1001", "jdoe"]])
250
def test_set_primary_group_unknown_group(self):
252
L{UserManagement.set_user_details should use C{usermod} to change the
253
user's primary group, in the event that we have an invalid group, we
254
should raise a UserManagement error.
256
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
257
groups = [("staff", "x", 1001, [])]
258
provider = FakeUserProvider(users=data, groups=groups,
259
shadow_file=self.shadow_file,
260
popen=MockPopen("group id 1002 unknown",
262
management = UserManagement(provider=provider)
263
self.assertRaises(GroupNotFoundError, management.set_user_details,
264
"jdoe", primary_group_name="unknown")
266
def test_lock_user(self):
267
"""L{UserManagement.lock_user} should use C{usermod} to lock users."""
268
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
269
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
270
popen=MockPopen("no output"))
271
management = UserManagement(provider=provider)
272
management.lock_user("jdoe")
273
self.assertEquals(provider.popen.popen_inputs,
274
[["usermod", "-L", "jdoe"]])
276
def test_lock_user_fails(self):
278
L{UserManagement.lock_user} should raise a L{UserManagementError} if
281
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
282
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
283
popen=MockPopen("", [1]))
284
management = UserManagement(provider=provider)
285
self.assertRaises(UserNotFoundError, management.lock_user, 1000)
287
def test_lock_user_with_unknown_uid(self):
289
L{UserManagement.lock_user} should raise a L{UserManagementError}
290
if the user being removed doesn't exist.
292
provider = FakeUserProvider(popen=MockPopen(""))
293
management = UserManagement(provider=provider)
294
self.assertRaises(UserNotFoundError, management.lock_user, 1000)
296
def test_unlock_user(self):
298
L{UserManagement.unlock_user} should use C{usermod} to unlock
301
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
302
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
303
popen=MockPopen("no output"))
304
management = UserManagement(provider=provider)
305
result = management.unlock_user("jdoe")
306
self.assertEquals(provider.popen.popen_inputs,
307
[["usermod", "-U", "jdoe"]])
309
def test_unlock_user_fails(self):
311
L{UserManagement.unlock_user} should raise an
312
L{UserManagementError} if a C{usermod} fails.
314
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
315
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
316
popen=MockPopen("", [1]))
317
management = UserManagement(provider=provider)
318
self.assertRaises(UserNotFoundError, management.unlock_user, 1000)
320
def test_unlock_user_with_unknown_uid(self):
322
L{UserManagement.unlock_user} should raise a
323
L{UserManagementError} if the user being removed doesn't exist.
325
provider = FakeUserProvider(popen=MockPopen(""))
326
management = UserManagement(provider=provider)
327
self.assertRaises(UserNotFoundError, management.unlock_user, 1000)
329
def test_remove_user(self):
331
L{UserManagement.remove_user} should use C{deluser} to remove
334
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
335
popen = MockPopen("Removing user `jdoe'...\r\ndone.")
336
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
338
management = UserManagement(provider=provider)
339
management.remove_user("jdoe")
340
self.assertEquals(popen.popen_inputs, [["deluser", "jdoe"]])
342
def test_remove_user_with_unknown_username(self):
344
L{UserManagement.remove_user} should raise a
345
L{UserManagementError} if the user being removed doesn't exist.
347
provider = FakeUserProvider(popen=MockPopen(""))
348
management = UserManagement(provider=provider)
349
self.assertRaises(UserNotFoundError, management.remove_user, "smith")
351
def test_remove_user_fails(self):
353
L{UserManagement.remove_user} should raise a
354
L{UserManagementError} if the user can't be removed.
356
self.log_helper.ignore_errors(UserNotFoundError)
357
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
358
popen = MockPopen("/usr/sbin/deluser: Only root may remove a user or "
359
"group from the system.", [1])
360
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
362
management = UserManagement(provider=provider)
363
self.assertRaises(UserNotFoundError, management.remove_user, "smith")
365
def test_remove_user_and_home(self):
367
L{UserManagement.remove_user} should use C{deluser} to remove
368
the contents of a user's home directory.
370
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
371
popen = MockPopen("Removing user `jdoe`...\r\ndone.", [0])
372
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
374
management = UserManagement(provider=provider)
375
management.remove_user("jdoe", delete_home=True)
376
self.assertEquals(popen.popen_inputs,
377
[["deluser", "jdoe", "--remove-home"]])
381
class GroupWriteTest(LandscapeTest):
383
helpers = [MakePathHelper]
386
LandscapeTest.setUp(self)
387
self.shadow_file = self.make_path("""\
388
jdoe:$1$xFlQvTqe$cBtrNEDOIKMy/BuJoUdeG0:13348:0:99999:7:::
389
psmith:!:13348:0:99999:7:::
390
sbarnes:$1$q7sz09uw$q.A3526M/SHu8vUb.Jo1A/:13349:0:99999:7:::
393
def test_add_group(self):
395
L{UserManagement.add_group} should use the system tool
396
C{addgroup} to create groups.
398
provider = FakeUserProvider(popen=MockPopen("Result"))
399
management = UserManagement(provider=provider)
400
result = management.add_group("webdev")
401
self.assertEquals(provider.popen.popen_inputs, [["addgroup", "webdev"]])
402
self.assertEquals(result, "Result")
404
def test_add_group_handles_errors(self):
406
If the system tool C{addgroup} returns a non-0 exit code,
407
L{UserManagement.add_group} should raise an L{UserManagementError}.
409
provider = FakeUserProvider(popen=MockPopen("Error Result", [1]))
410
management = UserManagement(provider=provider)
411
self.assertRaises(UserManagementError, management.add_group, "kaboom")
413
def test_set_group_details(self):
415
L{UserManagement.set_group_details} should use C{groupmode} to
416
change a group's name.
418
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
419
groups = [("bizdev", "x", 1001, [])]
420
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
421
groups=groups, popen=MockPopen("no output"))
422
management = UserManagement(provider=provider)
423
management.set_group_details("bizdev", "sales")
425
self.assertEquals(provider.popen.popen_inputs,
426
[["groupmod", "-n", "sales", "bizdev"]])
428
def test_set_group_details_with_unknown_groupname(self):
430
L{UserManagement.set_group_details} should raise a
431
L{UserManagementError} if the group being updated doesn't exist.
433
provider = FakeUserProvider(popen=MockPopen(""))
434
management = UserManagement(provider=provider)
435
self.assertRaises(GroupNotFoundError, management.set_group_details,
436
"sales", u"newsales")
438
def test_set_group_details_fails(self):
440
L{UserManagement.set_group_details} should raise a
441
L{UserManagementError} if the group can't be renamed.
443
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
444
groups = [("bizdev", "x", 1001, [])]
445
popen = MockPopen("groupmod: sales is not a unique name", [1])
446
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
447
groups=groups, popen=popen)
448
management = UserManagement(provider=provider)
449
self.assertRaises(UserManagementError, management.set_group_details,
452
def test_add_member(self):
454
L{UserManagement.add_group_member} should use the system tool
455
C{gpasswd} via the process factory to add a member to a group.
457
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
458
groups = [("bizdev", "x", 1001, [])]
459
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
462
"Removing user jdoe from group bizdev"))
463
management = UserManagement(provider=provider)
465
output = management.add_group_member("jdoe", "bizdev")
466
self.assertEquals(provider.popen.popen_inputs,
467
[["gpasswd", "-a", "jdoe", "bizdev"]])
468
self.assertEquals(output, "Removing user jdoe from group bizdev")
470
def test_add_member_with_unknown_groupname(self):
472
L{UserManagement.add_group_member} should raise a
473
L{UserManagementError} if the group to add the member to doesn't
476
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
477
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
479
management = UserManagement(provider=provider)
480
self.assertRaises(GroupNotFoundError, management.add_group_member,
483
def test_add_member_with_unknown_username(self):
485
L{UserManagement.add_group_member} should raise a
486
L{UserManagementError} if the user being associated doesn't
489
groups = [("bizdev", "x", 1001, [])]
490
provider = FakeUserProvider(groups=groups, popen=MockPopen(""))
491
management = UserManagement(provider=provider)
492
self.assertRaises(UserNotFoundError, management.add_group_member,
495
def test_add_member_failure(self):
497
If adding a member to a group fails,
498
L{UserManagement.add_group_member} should raise an
499
L{UserManagementError}.
501
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
502
groups = [("bizdev", "x", 1001, [])]
503
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
505
popen=MockPopen("no output", [1]))
506
management = UserManagement(provider=provider)
507
self.assertRaises(UserNotFoundError, management.add_group_member,
510
def test_remove_member(self):
512
L{UserManagement.remove_group_member} should use the system
513
tool C{gpasswd} via the process factory to remove a member
516
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
517
groups = [("bizdev", "x", 1001, [])]
518
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
521
"Removing user jdoe from group bizdev"))
522
management = UserManagement(provider=provider)
523
output = management.remove_group_member("jdoe", "bizdev")
524
self.assertEquals(provider.popen.popen_inputs,
525
[["gpasswd", "-d", "jdoe", "bizdev"]])
526
self.assertEquals(output, "Removing user jdoe from group bizdev")
528
def test_remove_member_with_unknown_groupname(self):
530
L{UserManagement.remove_group_member} should raise a
531
L{UserManagementError} if the group to remove the member to
534
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
535
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
536
popen=MockPopen("", return_codes=[2]))
537
management = UserManagement(provider=provider)
538
self.assertRaises(GroupNotFoundError, management.remove_group_member,
541
def test_remove_member_with_unknown_username(self):
543
L{UserManagement.remove_group_member} should raise a
544
L{UserManagementError} if the user being associated doesn't
547
groups = [("bizdev", "x", 1001, [])]
548
provider = FakeUserProvider(groups=groups,
549
popen=MockPopen("", return_codes=[4]))
550
management = UserManagement(provider=provider)
551
self.assertRaises(UserNotFoundError, management.remove_group_member,
554
def test_remove_member_failure(self):
556
If removing a member from a group fails,
557
L{UserManagement.remove_group_member} should raise a
558
L{UserManagementError}.
560
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
561
groups = [("bizdev", "x", 1001, [])]
562
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
564
popen=MockPopen("no output", [1]))
565
management = UserManagement(provider=provider)
566
self.assertRaises(UserManagementError,
567
management.remove_group_member, "jdoe", "bizdev")
569
def test_remove_group(self):
571
L{UserManagement.remove_group} should use C{groupdel} to
574
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
575
groups = [("bizdev", "x", 50, [])]
576
popen = MockPopen("Removing group `bizdev'...\r\ndone.")
577
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
578
groups=groups, popen=popen)
579
management = UserManagement(provider=provider)
580
output = management.remove_group("bizdev")
581
self.assertEquals(provider.popen.popen_inputs, [["groupdel", "bizdev"]])
583
def test_remove_group_with_unknown_groupname(self):
585
L{UserManagement.remove_group} should raise a
586
L{GroupMissingError} if the group being removed doesn't exist.
588
provider = FakeUserProvider(popen=MockPopen(""))
589
management = UserManagement(provider=provider)
590
self.assertRaises(GroupNotFoundError, management.remove_group, "ubuntu")
592
def test_remove_group_fails(self):
594
L{UserManagement.remove_user} should raise a
595
L{RemoveUserError} if the user can't be removed.
597
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
598
groups = [("bizdev", "x", 50, [])]
599
popen = MockPopen("/usr/sbin/deluser: Only root may remove a user or "
600
"group from the system.", [1])
601
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
602
groups=groups, popen=popen)
603
management = UserManagement(provider=provider)
604
self.assertRaises(GroupNotFoundError, management.remove_group,