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
self.assertEquals(len(provider.popen.popen_inputs), 2)
33
self.assertEquals(provider.popen.popen_inputs[0],
34
["adduser", "jdoe", "--disabled-password",
35
"--gecos", "John Doe,,,,", "--gid", "1001"])
37
usermod = provider.popen.popen_inputs[1]
38
self.assertEquals(len(usermod), 4, usermod)
39
password = guess_password(usermod[2], "password")
40
self.assertEquals(usermod, ["usermod", "-p", password, "jdoe"])
42
def test_add_user_error(self):
44
L{UserManagement.add_user} should raise an L{UserManagementError} if
47
provider = FakeUserProvider(popen=MockPopen("", return_codes=[1, 0]))
48
management = UserManagement(provider=provider)
49
self.assertRaises(UserManagementError, management.add_user,
50
"jdoe", u"John Doe", "password", False, None)
52
def test_change_password_error(self):
54
L{UserManagement.add_user} should raise an L{UserManagementError} if
57
provider = FakeUserProvider(popen=MockPopen("", return_codes=[0, 1]))
58
management = UserManagement(provider=provider)
59
self.assertRaises(UserManagementError, management.add_user,
60
"jdoe", u"John Doe", "password", False, None)
62
def test_expire_password_error(self):
64
L{UserManagement.add_user} should raise an L{UserManagementError} if
67
provider = FakeUserProvider(popen=MockPopen("",
68
return_codes=[0, 0,1]))
69
management = UserManagement(provider=provider)
70
self.assertRaises(UserManagementError, management.add_user,
71
"jdoe", u"John Doe", "password", True, None)
73
def test_set_password(self):
75
L{UserManagement.set_password} should use C{usermod} to change
78
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
79
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
80
popen=MockPopen("no output"))
81
management = UserManagement(provider=provider)
82
management.set_user_details("jdoe", password="password")
84
self.assertEquals(len(provider.popen.popen_inputs), 1)
85
password = provider.popen.popen_inputs[0][2]
86
password = guess_password(password, "password")
87
self.assertEquals(provider.popen.popen_inputs,
88
[["usermod", "-p", password, "jdoe"]])
90
def test_set_password_with_system_user(self):
92
L{UserManagement.set_password} should allow us to edit system
95
data = [("root", "x", 0, 0, ",,,,", "/home/root", "/bin/zsh")]
96
provider = FakeUserProvider(users=data,
97
shadow_file=self.shadow_file,
98
popen=MockPopen("no output"))
99
management = UserManagement(provider=provider)
100
management.set_user_details("root", password="password")
101
self.assertEquals(len(provider.popen.popen_inputs), 1)
102
password = provider.popen.popen_inputs[0][2]
103
password = guess_password(password, "password")
104
self.assertEquals(provider.popen.popen_inputs,
105
[["usermod", "-p", password, "root"]])
107
def test_set_password_unicode(self):
109
Make sure passing unicode as username and password doesn't
110
change things much (note that using something that's
111
non-ASCII-encodable still probably won't work).
113
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
114
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
115
popen=MockPopen("no output"))
116
management = UserManagement(provider=provider)
117
management.set_user_details("jdoe", password=u"password")
119
self.assertEquals(len(provider.popen.popen_inputs), 1)
120
password = provider.popen.popen_inputs[0][2]
121
password = guess_password(password, "password")
122
self.assertEquals(provider.popen.popen_inputs,
123
[["usermod", "-p", password, "jdoe"]])
125
def test_set_name(self):
127
L{UserManagement.set_user_details} should use C{chfn} to
128
change a user's name.
130
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
131
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
132
popen=MockPopen("no output"))
133
management = UserManagement(provider=provider)
134
management.set_user_details("jdoe", name="JD")
136
self.assertEquals(len(provider.popen.popen_inputs), 1)
137
self.assertEquals(provider.popen.popen_inputs,
138
[["chfn", "-f", "JD", "jdoe"]])
140
def test_set_location(self):
142
L{UserManagement.set_user_details} should use C{chfn} to
143
change a user's location.
145
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
146
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
147
popen=MockPopen("no output"))
148
management = UserManagement(provider=provider)
149
management.set_user_details("jdoe", location="Everywhere")
151
self.assertEquals(len(provider.popen.popen_inputs), 1)
152
self.assertEquals(provider.popen.popen_inputs,
153
[["chfn", "-r", "Everywhere", "jdoe"]])
155
def test_set_user_details_fails(self):
157
L{UserManagement.set_user_details} should raise an
158
L{EditUserError} if C{chfn} fails.
160
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
161
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
162
popen=MockPopen("", return_codes=[1]))
163
management = UserManagement(provider=provider)
164
self.assertRaises(UserNotFoundError, management.set_user_details, 1000,
167
def test_contact_details_in_general(self):
169
L{UserManagement.set_user_details} should use C{chfn} to
170
change a user's contact details.
172
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
173
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
174
popen=MockPopen("no output"))
175
management = UserManagement(provider=provider)
176
location = u"Everywhere"
177
work_number = u"1-800-123-4567"
178
home_number = u"764-4321"
179
management.set_user_details("jdoe", location=location,
180
work_number=work_number,
181
home_number=home_number)
183
self.assertEquals(len(provider.popen.popen_inputs), 1)
184
self.assertEquals(provider.popen.popen_inputs,
185
[["chfn", "-r", location, "-w", work_number,
186
"-h", home_number, "jdoe"]])
188
def test_set_user_details_with_unknown_username(self):
190
L{UserManagement.set_user_details} should raise a
191
L{UserManagementError} if the user being edited doesn't exist.
193
provider = FakeUserProvider(popen=MockPopen(""))
194
management = UserManagement(provider=provider)
195
self.assertRaises(UserNotFoundError, management.set_user_details,
196
"kevin", name=u"John Doe")
198
def test_set_primary_group(self):
200
L{UserManagement.set_set_user_details} should use C{usermod} to change
201
the user's primary group.
203
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
204
groups = [("users", "x", 1001, [])]
205
provider = FakeUserProvider(users=data, groups=groups,
206
shadow_file=self.shadow_file,
207
popen=MockPopen("no output"))
209
management = UserManagement(provider=provider)
210
management.set_user_details("jdoe", primary_group_name="users")
212
self.assertEquals(provider.popen.popen_inputs,
213
[["usermod", "-g", "1001", "jdoe"]])
215
def test_set_primary_group_unknown_group(self):
217
L{UserManagement.set_user_details should use C{usermod} to change the
218
user's primary group, in the event that we have an invalid group, we
219
should raise a UserManagement error.
221
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
222
groups = [("staff", "x", 1001, [])]
223
provider = FakeUserProvider(users=data, groups=groups,
224
shadow_file=self.shadow_file,
225
popen=MockPopen("group id 1002 unknown",
227
management = UserManagement(provider=provider)
228
self.assertRaises(GroupNotFoundError, management.set_user_details,
229
"jdoe", primary_group_name="unknown")
231
def test_lock_user(self):
232
"""L{UserManagement.lock_user} should use C{usermod} to lock users."""
233
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
234
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
235
popen=MockPopen("no output"))
236
management = UserManagement(provider=provider)
237
management.lock_user("jdoe")
238
self.assertEquals(provider.popen.popen_inputs,
239
[["usermod", "-L", "jdoe"]])
241
def test_lock_user_fails(self):
243
L{UserManagement.lock_user} should raise a L{UserManagementError} if
246
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
247
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
248
popen=MockPopen("", [1]))
249
management = UserManagement(provider=provider)
250
self.assertRaises(UserNotFoundError, management.lock_user, 1000)
252
def test_lock_user_with_unknown_uid(self):
254
L{UserManagement.lock_user} should raise a L{UserManagementError}
255
if the user being removed doesn't exist.
257
provider = FakeUserProvider(popen=MockPopen(""))
258
management = UserManagement(provider=provider)
259
self.assertRaises(UserNotFoundError, management.lock_user, 1000)
261
def test_unlock_user(self):
263
L{UserManagement.unlock_user} should use C{usermod} to unlock
266
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
267
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
268
popen=MockPopen("no output"))
269
management = UserManagement(provider=provider)
270
result = management.unlock_user("jdoe")
271
self.assertEquals(provider.popen.popen_inputs,
272
[["usermod", "-U", "jdoe"]])
274
def test_unlock_user_fails(self):
276
L{UserManagement.unlock_user} should raise an
277
L{UserManagementError} if a C{usermod} fails.
279
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
280
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
281
popen=MockPopen("", [1]))
282
management = UserManagement(provider=provider)
283
self.assertRaises(UserNotFoundError, management.unlock_user, 1000)
285
def test_unlock_user_with_unknown_uid(self):
287
L{UserManagement.unlock_user} should raise a
288
L{UserManagementError} if the user being removed doesn't exist.
290
provider = FakeUserProvider(popen=MockPopen(""))
291
management = UserManagement(provider=provider)
292
self.assertRaises(UserNotFoundError, management.unlock_user, 1000)
294
def test_remove_user(self):
296
L{UserManagement.remove_user} should use C{deluser} to remove
299
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
300
popen = MockPopen("Removing user `jdoe'...\r\ndone.")
301
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
303
management = UserManagement(provider=provider)
304
management.remove_user("jdoe")
305
self.assertEquals(popen.popen_inputs, [["deluser", "jdoe"]])
307
def test_remove_user_with_unknown_username(self):
309
L{UserManagement.remove_user} should raise a
310
L{UserManagementError} if the user being removed doesn't exist.
312
provider = FakeUserProvider(popen=MockPopen(""))
313
management = UserManagement(provider=provider)
314
self.assertRaises(UserNotFoundError, management.remove_user, "smith")
316
def test_remove_user_fails(self):
318
L{UserManagement.remove_user} should raise a
319
L{UserManagementError} if the user can't be removed.
321
self.log_helper.ignore_errors(UserNotFoundError)
322
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
323
popen = MockPopen("/usr/sbin/deluser: Only root may remove a user or "
324
"group from the system.", [1])
325
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
327
management = UserManagement(provider=provider)
328
self.assertRaises(UserNotFoundError, management.remove_user, "smith")
330
def test_remove_user_and_home(self):
332
L{UserManagement.remove_user} should use C{deluser} to remove
333
the contents of a user's home directory.
335
data = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
336
popen = MockPopen("Removing user `jdoe`...\r\ndone.", [0])
337
provider = FakeUserProvider(users=data, shadow_file=self.shadow_file,
339
management = UserManagement(provider=provider)
340
management.remove_user("jdoe", delete_home=True)
341
self.assertEquals(popen.popen_inputs,
342
[["deluser", "jdoe", "--remove-home"]])
346
class GroupWriteTest(LandscapeTest):
348
helpers = [MakePathHelper]
351
LandscapeTest.setUp(self)
352
self.shadow_file = self.make_path("""\
353
jdoe:$1$xFlQvTqe$cBtrNEDOIKMy/BuJoUdeG0:13348:0:99999:7:::
354
psmith:!:13348:0:99999:7:::
355
sbarnes:$1$q7sz09uw$q.A3526M/SHu8vUb.Jo1A/:13349:0:99999:7:::
358
def test_add_group(self):
360
L{UserManagement.add_group} should use the system tool
361
C{addgroup} to create groups.
363
provider = FakeUserProvider(popen=MockPopen("Result"))
364
management = UserManagement(provider=provider)
365
result = management.add_group("webdev")
366
self.assertEquals(provider.popen.popen_inputs, [["addgroup", "webdev"]])
367
self.assertEquals(result, "Result")
369
def test_add_group_handles_errors(self):
371
If the system tool C{addgroup} returns a non-0 exit code,
372
L{UserManagement.add_group} should raise an L{UserManagementError}.
374
provider = FakeUserProvider(popen=MockPopen("Error Result", [1]))
375
management = UserManagement(provider=provider)
376
self.assertRaises(UserManagementError, management.add_group, "kaboom")
378
def test_set_group_details(self):
380
L{UserManagement.set_group_details} should use C{groupmode} to
381
change a group's name.
383
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
384
groups = [("bizdev", "x", 1001, [])]
385
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
386
groups=groups, popen=MockPopen("no output"))
387
management = UserManagement(provider=provider)
388
management.set_group_details("bizdev", "sales")
390
self.assertEquals(provider.popen.popen_inputs,
391
[["groupmod", "-n", "sales", "bizdev"]])
393
def test_set_group_details_with_unknown_groupname(self):
395
L{UserManagement.set_group_details} should raise a
396
L{UserManagementError} if the group being updated doesn't exist.
398
provider = FakeUserProvider(popen=MockPopen(""))
399
management = UserManagement(provider=provider)
400
self.assertRaises(GroupNotFoundError, management.set_group_details,
401
"sales", u"newsales")
403
def test_set_group_details_fails(self):
405
L{UserManagement.set_group_details} should raise a
406
L{UserManagementError} if the group can't be renamed.
408
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
409
groups = [("bizdev", "x", 1001, [])]
410
popen = MockPopen("groupmod: sales is not a unique name", [1])
411
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
412
groups=groups, popen=popen)
413
management = UserManagement(provider=provider)
414
self.assertRaises(UserManagementError, management.set_group_details,
417
def test_add_member(self):
419
L{UserManagement.add_group_member} should use the system tool
420
C{gpasswd} via the process factory to add a member to a group.
422
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
423
groups = [("bizdev", "x", 1001, [])]
424
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
427
"Removing user jdoe from group bizdev"))
428
management = UserManagement(provider=provider)
430
output = management.add_group_member("jdoe", "bizdev")
431
self.assertEquals(provider.popen.popen_inputs,
432
[["gpasswd", "-a", "jdoe", "bizdev"]])
433
self.assertEquals(output, "Removing user jdoe from group bizdev")
435
def test_add_member_with_unknown_groupname(self):
437
L{UserManagement.add_group_member} should raise a
438
L{UserManagementError} if the group to add the member to doesn't
441
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
442
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
444
management = UserManagement(provider=provider)
445
self.assertRaises(GroupNotFoundError, management.add_group_member,
448
def test_add_member_with_unknown_username(self):
450
L{UserManagement.add_group_member} should raise a
451
L{UserManagementError} if the user being associated doesn't
454
groups = [("bizdev", "x", 1001, [])]
455
provider = FakeUserProvider(groups=groups, popen=MockPopen(""))
456
management = UserManagement(provider=provider)
457
self.assertRaises(UserNotFoundError, management.add_group_member,
460
def test_add_member_failure(self):
462
If adding a member to a group fails,
463
L{UserManagement.add_group_member} should raise an
464
L{UserManagementError}.
466
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
467
groups = [("bizdev", "x", 1001, [])]
468
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
470
popen=MockPopen("no output", [1]))
471
management = UserManagement(provider=provider)
472
self.assertRaises(UserNotFoundError, management.add_group_member,
475
def test_remove_member(self):
477
L{UserManagement.remove_group_member} should use the system
478
tool C{gpasswd} via the process factory to remove a member
481
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
482
groups = [("bizdev", "x", 1001, [])]
483
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
486
"Removing user jdoe from group bizdev"))
487
management = UserManagement(provider=provider)
488
output = management.remove_group_member("jdoe", "bizdev")
489
self.assertEquals(provider.popen.popen_inputs,
490
[["gpasswd", "-d", "jdoe", "bizdev"]])
491
self.assertEquals(output, "Removing user jdoe from group bizdev")
493
def test_remove_member_with_unknown_groupname(self):
495
L{UserManagement.remove_group_member} should raise a
496
L{UserManagementError} if the group to remove the member to
499
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
500
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
501
popen=MockPopen("", return_codes=[2]))
502
management = UserManagement(provider=provider)
503
self.assertRaises(GroupNotFoundError, management.remove_group_member,
506
def test_remove_member_with_unknown_username(self):
508
L{UserManagement.remove_group_member} should raise a
509
L{UserManagementError} if the user being associated doesn't
512
groups = [("bizdev", "x", 1001, [])]
513
provider = FakeUserProvider(groups=groups,
514
popen=MockPopen("", return_codes=[4]))
515
management = UserManagement(provider=provider)
516
self.assertRaises(UserNotFoundError, management.remove_group_member,
519
def test_remove_member_failure(self):
521
If removing a member from a group fails,
522
L{UserManagement.remove_group_member} should raise a
523
L{UserManagementError}.
525
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
526
groups = [("bizdev", "x", 1001, [])]
527
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
529
popen=MockPopen("no output", [1]))
530
management = UserManagement(provider=provider)
531
self.assertRaises(UserManagementError,
532
management.remove_group_member, "jdoe", "bizdev")
534
def test_remove_group(self):
536
L{UserManagement.remove_group} should use C{groupdel} to
539
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
540
groups = [("bizdev", "x", 50, [])]
541
popen = MockPopen("Removing group `bizdev'...\r\ndone.")
542
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
543
groups=groups, popen=popen)
544
management = UserManagement(provider=provider)
545
output = management.remove_group("bizdev")
546
self.assertEquals(provider.popen.popen_inputs, [["groupdel", "bizdev"]])
548
def test_remove_group_with_unknown_groupname(self):
550
L{UserManagement.remove_group} should raise a
551
L{GroupMissingError} if the group being removed doesn't exist.
553
provider = FakeUserProvider(popen=MockPopen(""))
554
management = UserManagement(provider=provider)
555
self.assertRaises(GroupNotFoundError, management.remove_group, "ubuntu")
557
def test_remove_group_fails(self):
559
L{UserManagement.remove_user} should raise a
560
L{RemoveUserError} if the user can't be removed.
562
users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/zsh")]
563
groups = [("bizdev", "x", 50, [])]
564
popen = MockPopen("/usr/sbin/deluser: Only root may remove a user or "
565
"group from the system.", [1])
566
provider = FakeUserProvider(users=users, shadow_file=self.shadow_file,
567
groups=groups, popen=popen)
568
management = UserManagement(provider=provider)
569
self.assertRaises(GroupNotFoundError, management.remove_group,