github.com/rigado/snapd@v2.42.5-go-mod+incompatible/osutil/user_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil_test 21 22 import ( 23 "fmt" 24 "os" 25 "os/user" 26 "path/filepath" 27 "strconv" 28 29 "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/osutil" 32 "github.com/snapcore/snapd/osutil/sys" 33 "github.com/snapcore/snapd/testutil" 34 ) 35 36 type createUserSuite struct { 37 testutil.BaseTest 38 39 mockHome string 40 restorer func() 41 42 mockAddUser *testutil.MockCmd 43 mockUserMod *testutil.MockCmd 44 mockPasswd *testutil.MockCmd 45 } 46 47 var _ = check.Suite(&createUserSuite{}) 48 49 func (s *createUserSuite) SetUpTest(c *check.C) { 50 s.mockHome = c.MkDir() 51 s.restorer = osutil.MockUserLookup(func(string) (*user.User, error) { 52 current, err := user.Current() 53 if err != nil { 54 c.Fatalf("user.Current() failed with %s", err) 55 } 56 return &user.User{ 57 HomeDir: s.mockHome, 58 Gid: current.Gid, 59 Uid: current.Uid, 60 }, nil 61 }) 62 s.mockAddUser = testutil.MockCommand(c, "adduser", "") 63 s.mockUserMod = testutil.MockCommand(c, "usermod", "") 64 s.mockPasswd = testutil.MockCommand(c, "passwd", "") 65 } 66 67 func (s *createUserSuite) TearDownTest(c *check.C) { 68 s.restorer() 69 s.mockAddUser.Restore() 70 s.mockUserMod.Restore() 71 s.mockPasswd.Restore() 72 } 73 74 func (s *createUserSuite) TestAddUserExtraUsersFalse(c *check.C) { 75 err := osutil.AddUser("lakatos", &osutil.AddUserOptions{ 76 Gecos: "my gecos", 77 ExtraUsers: false, 78 }) 79 c.Assert(err, check.IsNil) 80 81 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 82 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "lakatos"}, 83 }) 84 } 85 86 func (s *createUserSuite) TestAddUserExtraUsersTrue(c *check.C) { 87 err := osutil.AddUser("lakatos", &osutil.AddUserOptions{ 88 Gecos: "my gecos", 89 ExtraUsers: true, 90 }) 91 c.Assert(err, check.IsNil) 92 93 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 94 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "--extrausers", "lakatos"}, 95 }) 96 } 97 98 func (s *createUserSuite) TestAddSudoUser(c *check.C) { 99 mockSudoers := c.MkDir() 100 restorer := osutil.MockSudoersDotD(mockSudoers) 101 defer restorer() 102 103 err := osutil.AddUser("karl.sagan", &osutil.AddUserOptions{ 104 Gecos: "my gecos", 105 Sudoer: true, 106 ExtraUsers: true, 107 }) 108 c.Assert(err, check.IsNil) 109 110 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 111 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "--extrausers", "karl.sagan"}, 112 }) 113 114 fs, _ := filepath.Glob(filepath.Join(mockSudoers, "*")) 115 c.Assert(fs, check.HasLen, 1) 116 c.Assert(filepath.Base(fs[0]), check.Equals, "create-user-karl%2Esagan") 117 c.Check(fs[0], testutil.FileEquals, ` 118 # Created by snap create-user 119 120 # User rules for karl.sagan 121 karl.sagan ALL=(ALL) NOPASSWD:ALL 122 `) 123 } 124 125 func (s *createUserSuite) TestAddUserSSHKeys(c *check.C) { 126 err := osutil.AddUser("karl.sagan", &osutil.AddUserOptions{ 127 SSHKeys: []string{"ssh-key1", "ssh-key2"}, 128 }) 129 c.Assert(err, check.IsNil) 130 c.Check(filepath.Join(s.mockHome, ".ssh", "authorized_keys"), testutil.FileEquals, "ssh-key1\nssh-key2") 131 132 } 133 134 func (s *createUserSuite) TestAddUserInvalidUsername(c *check.C) { 135 err := osutil.AddUser("k!", nil) 136 c.Assert(err, check.ErrorMatches, `cannot add user "k!": name contains invalid characters`) 137 } 138 139 func (s *createUserSuite) TestAddUserWithPassword(c *check.C) { 140 mockSudoers := c.MkDir() 141 restorer := osutil.MockSudoersDotD(mockSudoers) 142 defer restorer() 143 144 err := osutil.AddUser("karl.sagan", &osutil.AddUserOptions{ 145 Gecos: "my gecos", 146 Password: "$6$salt$hash", 147 }) 148 c.Assert(err, check.IsNil) 149 150 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 151 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "karl.sagan"}, 152 }) 153 c.Check(s.mockUserMod.Calls(), check.DeepEquals, [][]string{ 154 {"usermod", "--password", "$6$salt$hash", "karl.sagan"}, 155 }) 156 } 157 158 func (s *createUserSuite) TestAddUserWithPasswordForceChange(c *check.C) { 159 mockSudoers := c.MkDir() 160 restorer := osutil.MockSudoersDotD(mockSudoers) 161 defer restorer() 162 163 err := osutil.AddUser("karl.popper", &osutil.AddUserOptions{ 164 Gecos: "my gecos", 165 Password: "$6$salt$hash", 166 ForcePasswordChange: true, 167 }) 168 c.Assert(err, check.IsNil) 169 170 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 171 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "karl.popper"}, 172 }) 173 c.Check(s.mockUserMod.Calls(), check.DeepEquals, [][]string{ 174 {"usermod", "--password", "$6$salt$hash", "karl.popper"}, 175 }) 176 c.Check(s.mockPasswd.Calls(), check.DeepEquals, [][]string{ 177 {"passwd", "--expire", "karl.popper"}, 178 }) 179 } 180 181 func (s *createUserSuite) TestAddUserPasswordForceChangeUnhappy(c *check.C) { 182 mockSudoers := c.MkDir() 183 restorer := osutil.MockSudoersDotD(mockSudoers) 184 defer restorer() 185 186 err := osutil.AddUser("karl.popper", &osutil.AddUserOptions{ 187 Gecos: "my gecos", 188 ForcePasswordChange: true, 189 }) 190 c.Assert(err, check.ErrorMatches, `cannot force password change when no password is provided`) 191 } 192 193 func (s *createUserSuite) TestRealUser(c *check.C) { 194 oldUser := os.Getenv("SUDO_USER") 195 defer func() { os.Setenv("SUDO_USER", oldUser) }() 196 197 for _, t := range []struct { 198 SudoUsername string 199 CurrentUsername string 200 CurrentUid int 201 }{ 202 // simulate regular "root", no SUDO_USER set 203 {"", os.Getenv("USER"), 0}, 204 // simulate a normal sudo invocation 205 {"guy", "guy", 0}, 206 // simulate running "sudo -u some-user -i" as root 207 // (LP: #1638656) 208 {"root", os.Getenv("USER"), 1000}, 209 } { 210 restore := osutil.MockUserCurrent(func() (*user.User, error) { 211 return &user.User{ 212 Username: t.CurrentUsername, 213 Uid: strconv.Itoa(t.CurrentUid), 214 }, nil 215 }) 216 defer restore() 217 218 os.Setenv("SUDO_USER", t.SudoUsername) 219 cur, err := osutil.RealUser() 220 c.Assert(err, check.IsNil) 221 c.Check(cur.Username, check.Equals, t.CurrentUsername) 222 } 223 } 224 225 func (s *createUserSuite) TestUidGid(c *check.C) { 226 for k, t := range map[string]struct { 227 User *user.User 228 Uid sys.UserID 229 Gid sys.GroupID 230 Err string 231 }{ 232 "happy": {&user.User{Uid: "10", Gid: "10"}, 10, 10, ""}, 233 "bad uid": {&user.User{Uid: "x", Gid: "10"}, sys.FlagID, sys.FlagID, "cannot parse user id x"}, 234 "bad gid": {&user.User{Uid: "10", Gid: "x"}, sys.FlagID, sys.FlagID, "cannot parse group id x"}, 235 } { 236 uid, gid, err := osutil.UidGid(t.User) 237 c.Check(uid, check.Equals, t.Uid, check.Commentf(k)) 238 c.Check(gid, check.Equals, t.Gid, check.Commentf(k)) 239 if t.Err == "" { 240 c.Check(err, check.IsNil, check.Commentf(k)) 241 } else { 242 c.Check(err, check.ErrorMatches, ".*"+t.Err+".*", check.Commentf(k)) 243 } 244 } 245 } 246 247 func (s *createUserSuite) TestAddUserUnhappy(c *check.C) { 248 mockAddUser := testutil.MockCommand(c, "adduser", "echo some error; exit 1") 249 defer mockAddUser.Restore() 250 251 err := osutil.AddUser("lakatos", nil) 252 c.Assert(err, check.ErrorMatches, "adduser failed with: some error") 253 254 } 255 256 func (s *createUserSuite) TestIsValidUsername(c *check.C) { 257 for k, v := range map[string]bool{ 258 "a": true, 259 "a-b": true, 260 "a+b": true, 261 "a.b": true, 262 "a_b": true, 263 "1": true, 264 "1+": true, 265 "1.": true, 266 "1_": true, 267 "-": false, 268 "+": false, 269 ".": false, 270 "_": false, 271 "-a": false, 272 "+a": false, 273 ".a": false, 274 "_a": false, 275 "a:b": false, 276 "inval!d": false, 277 } { 278 c.Check(osutil.IsValidUsername(k), check.Equals, v) 279 } 280 } 281 282 type ensureUserSuite struct { 283 mockUserAdd *testutil.MockCmd 284 mockGroupAdd *testutil.MockCmd 285 mockGroupDel *testutil.MockCmd 286 } 287 288 var _ = check.Suite(&ensureUserSuite{}) 289 290 func (s *ensureUserSuite) SetUpTest(c *check.C) { 291 s.mockUserAdd = testutil.MockCommand(c, "useradd", "") 292 s.mockGroupAdd = testutil.MockCommand(c, "groupadd", "") 293 s.mockGroupDel = testutil.MockCommand(c, "groupdel", "") 294 } 295 296 func (s *ensureUserSuite) TearDownTest(c *check.C) { 297 s.mockUserAdd.Restore() 298 s.mockGroupAdd.Restore() 299 s.mockGroupDel.Restore() 300 } 301 302 func (s *ensureUserSuite) TestEnsureUserGroupExtraUsersFalse(c *check.C) { 303 falsePath = osutil.LookPathDefault("false", "/bin/false") 304 err := osutil.EnsureUserGroup("lakatos", 123456, false) 305 c.Assert(err, check.IsNil) 306 307 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string{ 308 {"groupadd", "--system", "--gid", "123456", "lakatos"}, 309 }) 310 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string{ 311 {"useradd", "--system", "--home-dir", "/nonexistent", "--no-create-home", "--shell", falsePath, "--gid", "123456", "--no-user-group", "--uid", "123456", "lakatos"}, 312 }) 313 } 314 315 func (s *ensureUserSuite) TestEnsureUserGroupExtraUsersTrue(c *check.C) { 316 falsePath = osutil.LookPathDefault("false", "/bin/false") 317 err := osutil.EnsureUserGroup("lakatos", 123456, true) 318 c.Assert(err, check.IsNil) 319 320 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string{ 321 {"groupadd", "--system", "--gid", "123456", "--extrausers", "lakatos"}, 322 }) 323 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string{ 324 {"useradd", "--system", "--home-dir", "/nonexistent", "--no-create-home", "--shell", falsePath, "--gid", "123456", "--no-user-group", "--uid", "123456", "--extrausers", "lakatos"}, 325 }) 326 } 327 328 func (s *ensureUserSuite) TestEnsureUserGroupBadUser(c *check.C) { 329 err := osutil.EnsureUserGroup("k!", 123456, false) 330 c.Assert(err, check.ErrorMatches, `cannot add user/group "k!": name contains invalid characters`) 331 332 // shouldn't run these on error 333 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 334 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 335 } 336 337 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedFindUidError(c *check.C) { 338 restore := osutil.MockFindUid(func(string) (uint64, error) { 339 return 0, fmt.Errorf("some odd FindUid error") 340 }) 341 defer restore() 342 343 err := osutil.EnsureUserGroup("lakatos", 1234, false) 344 c.Assert(err, check.ErrorMatches, `some odd FindUid error`) 345 346 // shouldn't run these on error 347 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 348 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 349 } 350 351 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedFindGidError(c *check.C) { 352 restore := osutil.MockFindGid(func(string) (uint64, error) { 353 return 0, fmt.Errorf("some odd FindGid error") 354 }) 355 defer restore() 356 357 err := osutil.EnsureUserGroup("lakatos", 1234, false) 358 c.Assert(err, check.ErrorMatches, `some odd FindGid error`) 359 360 // shouldn't run these on error 361 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 362 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 363 } 364 365 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedUid(c *check.C) { 366 restore := osutil.MockFindUid(func(string) (uint64, error) { 367 return uint64(5432), nil 368 }) 369 defer restore() 370 restore = osutil.MockFindGid(func(string) (uint64, error) { 371 return uint64(1234), nil 372 }) 373 defer restore() 374 375 err := osutil.EnsureUserGroup("lakatos", 1234, false) 376 c.Assert(err, check.ErrorMatches, `found unexpected uid for user "lakatos": 5432`) 377 378 // shouldn't run these on error 379 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 380 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 381 } 382 383 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedGid(c *check.C) { 384 restore := osutil.MockFindUid(func(string) (uint64, error) { 385 return uint64(1234), nil 386 }) 387 defer restore() 388 restore = osutil.MockFindGid(func(string) (uint64, error) { 389 return uint64(5432), nil 390 }) 391 defer restore() 392 393 err := osutil.EnsureUserGroup("lakatos", 1234, false) 394 c.Assert(err, check.ErrorMatches, `found unexpected gid for group "lakatos": 5432`) 395 396 // shouldn't run these on error 397 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 398 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 399 } 400 401 func (s *ensureUserSuite) TestEnsureUserGroupFoundBoth(c *check.C) { 402 restore := osutil.MockFindUid(func(string) (uint64, error) { 403 return uint64(1234), nil 404 }) 405 defer restore() 406 restore = osutil.MockFindGid(func(string) (uint64, error) { 407 return uint64(1234), nil 408 }) 409 defer restore() 410 411 err := osutil.EnsureUserGroup("lakatos", 1234, false) 412 c.Assert(err, check.IsNil) 413 414 // we found both with expected values, shouldn't run these 415 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 416 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 417 } 418 419 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedGroupMissing(c *check.C) { 420 restore := osutil.MockFindUid(func(string) (uint64, error) { 421 return uint64(1234), nil 422 }) 423 defer restore() 424 425 err := osutil.EnsureUserGroup("lakatos", 1234, false) 426 c.Assert(err, check.ErrorMatches, `cannot add user/group "lakatos": user exists and group does not`) 427 428 // shouldn't run these on error 429 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 430 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 431 } 432 433 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedUserMissing(c *check.C) { 434 restore := osutil.MockFindGid(func(string) (uint64, error) { 435 return uint64(1234), nil 436 }) 437 defer restore() 438 439 err := osutil.EnsureUserGroup("lakatos", 1234, false) 440 c.Assert(err, check.ErrorMatches, `cannot add user/group "lakatos": group exists and user does not`) 441 442 // shouldn't run these on error 443 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 444 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 445 } 446 447 func (s *ensureUserSuite) TestEnsureUserGroupFailedGroupadd(c *check.C) { 448 mockGroupAdd := testutil.MockCommand(c, "groupadd", "echo some error; exit 1") 449 defer mockGroupAdd.Restore() 450 451 err := osutil.EnsureUserGroup("lakatos", 123456, false) 452 c.Assert(err, check.ErrorMatches, "groupadd failed with: some error") 453 454 // shouldn't run this on error 455 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 456 } 457 458 func (s *ensureUserSuite) TestEnsureUserGroupFailedUseraddClassic(c *check.C) { 459 mockUserAdd := testutil.MockCommand(c, "useradd", "echo some error; exit 1") 460 defer mockUserAdd.Restore() 461 462 err := osutil.EnsureUserGroup("lakatos", 123456, false) 463 c.Assert(err, check.ErrorMatches, "useradd failed with: some error") 464 465 c.Check(s.mockGroupDel.Calls(), check.DeepEquals, [][]string{ 466 {"groupdel", "lakatos"}, 467 }) 468 } 469 470 func (s *ensureUserSuite) TestEnsureUserGroupFailedUseraddCore(c *check.C) { 471 mockUserAdd := testutil.MockCommand(c, "useradd", "echo some error; exit 1") 472 defer mockUserAdd.Restore() 473 474 err := osutil.EnsureUserGroup("lakatos", 123456, true) 475 c.Assert(err, check.ErrorMatches, "useradd failed with: some error") 476 477 // TODO: LP: #1840375 478 /* 479 c.Check(s.mockGroupDel.Calls(), check.DeepEquals, [][]string{ 480 {"groupdel", "--extrausers", "lakatos"}, 481 }) 482 */ 483 c.Check(s.mockGroupDel.Calls(), check.DeepEquals, [][]string(nil)) 484 }