github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_users_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2020 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 daemon_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "net/http" 26 "os/user" 27 "path/filepath" 28 "time" 29 30 "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/asserts" 33 "github.com/snapcore/snapd/asserts/assertstest" 34 "github.com/snapcore/snapd/daemon" 35 "github.com/snapcore/snapd/osutil" 36 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 37 "github.com/snapcore/snapd/overlord/auth" 38 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 39 "github.com/snapcore/snapd/release" 40 "github.com/snapcore/snapd/store" 41 "github.com/snapcore/snapd/testutil" 42 ) 43 44 var _ = check.Suite(&userSuite{}) 45 46 // TODO: also pull login/logout tests into this. 47 48 type userSuite struct { 49 apiBaseSuite 50 51 userInfoResult *store.User 52 userInfoExpectedEmail string 53 err error 54 55 mockUserHome string 56 trivialUserLookup func(username string) (*user.User, error) 57 } 58 59 func (s *userSuite) UserInfo(email string) (userinfo *store.User, err error) { 60 s.pokeStateLock() 61 62 if s.userInfoExpectedEmail != email { 63 panic(fmt.Sprintf("%q != %q", s.userInfoExpectedEmail, email)) 64 } 65 return s.userInfoResult, s.err 66 } 67 68 func (s *userSuite) SetUpTest(c *check.C) { 69 s.apiBaseSuite.SetUpTest(c) 70 71 s.AddCleanup(release.MockOnClassic(false)) 72 73 s.daemonWithStore(c, s) 74 75 s.mockUserHome = c.MkDir() 76 s.trivialUserLookup = mkUserLookup(s.mockUserHome) 77 s.AddCleanup(daemon.MockUserLookup(s.trivialUserLookup)) 78 79 s.AddCleanup(daemon.MockHasUserAdmin(true)) 80 81 // make sure we don't call these by accident 82 s.AddCleanup(daemon.MockOsutilAddUser(func(name string, opts *osutil.AddUserOptions) error { 83 c.Fatalf("unexpected add user %q call", name) 84 return fmt.Errorf("unexpected add user %q call", name) 85 })) 86 s.AddCleanup(daemon.MockOsutilDelUser(func(name string, opts *osutil.DelUserOptions) error { 87 c.Fatalf("unexpected del user %q call", name) 88 return fmt.Errorf("unexpected del user %q call", name) 89 })) 90 } 91 92 func mkUserLookup(userHomeDir string) func(string) (*user.User, error) { 93 return func(username string) (*user.User, error) { 94 cur, err := user.Current() 95 cur.Username = username 96 cur.HomeDir = userHomeDir 97 return cur, err 98 } 99 } 100 101 func (s *userSuite) TestPostCreateUserNoSSHKeys(c *check.C) { 102 s.userInfoExpectedEmail = "popper@lse.ac.uk" 103 s.userInfoResult = &store.User{ 104 Username: "karl", 105 OpenIDIdentifier: "xxyyzz", 106 } 107 buf := bytes.NewBufferString(fmt.Sprintf(`{"email": "%s"}`, s.userInfoExpectedEmail)) 108 req, err := http.NewRequest("POST", "/v2/create-user", buf) 109 c.Assert(err, check.IsNil) 110 111 rsp := s.req(c, req, nil).(*daemon.Resp) 112 113 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 114 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, `cannot create user for "popper@lse.ac.uk": no ssh keys found`) 115 } 116 117 func (s *userSuite) TestPostCreateUser(c *check.C) { 118 s.testCreateUser(c, true) 119 } 120 121 func (s *userSuite) TestPostUserCreate(c *check.C) { 122 s.testCreateUser(c, false) 123 } 124 125 func (s *userSuite) testCreateUser(c *check.C, oldWay bool) { 126 expectedUsername := "karl" 127 s.userInfoExpectedEmail = "popper@lse.ac.uk" 128 s.userInfoResult = &store.User{ 129 Username: expectedUsername, 130 SSHKeys: []string{"ssh1", "ssh2"}, 131 OpenIDIdentifier: "xxyyzz", 132 } 133 defer daemon.MockOsutilAddUser(func(username string, opts *osutil.AddUserOptions) error { 134 c.Check(username, check.Equals, expectedUsername) 135 c.Check(opts.SSHKeys, check.DeepEquals, []string{"ssh1", "ssh2"}) 136 c.Check(opts.Gecos, check.Equals, "popper@lse.ac.uk,xxyyzz") 137 c.Check(opts.Sudoer, check.Equals, false) 138 return nil 139 })() 140 141 var req *http.Request 142 var expected interface{} 143 expectedItem := daemon.UserResponseData{ 144 Username: expectedUsername, 145 SSHKeys: []string{"ssh1", "ssh2"}, 146 } 147 148 if oldWay { 149 var err error 150 buf := bytes.NewBufferString(fmt.Sprintf(`{"email": "%s"}`, s.userInfoExpectedEmail)) 151 req, err = http.NewRequest("POST", "/v2/create-user", buf) 152 c.Assert(err, check.IsNil) 153 expected = &expectedItem 154 } else { 155 var err error 156 buf := bytes.NewBufferString(fmt.Sprintf(`{"action":"create","email": "%s"}`, s.userInfoExpectedEmail)) 157 req, err = http.NewRequest("POST", "/v2/users", buf) 158 c.Assert(err, check.IsNil) 159 expected = []daemon.UserResponseData{expectedItem} 160 } 161 162 rsp := s.req(c, req, nil).(*daemon.Resp) 163 164 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 165 c.Check(rsp.Result, check.FitsTypeOf, expected) 166 c.Check(rsp.Result, check.DeepEquals, expected) 167 168 // user was setup in state 169 state := s.d.Overlord().State() 170 state.Lock() 171 user, err := auth.User(state, 1) 172 state.Unlock() 173 c.Check(err, check.IsNil) 174 c.Check(user.Username, check.Equals, expectedUsername) 175 c.Check(user.Email, check.Equals, s.userInfoExpectedEmail) 176 c.Check(user.Macaroon, check.NotNil) 177 // auth saved to user home dir 178 outfile := filepath.Join(s.mockUserHome, ".snap", "auth.json") 179 c.Check(osutil.FileExists(outfile), check.Equals, true) 180 c.Check(outfile, testutil.FileEquals, 181 fmt.Sprintf(`{"id":%d,"username":"%s","email":"%s","macaroon":"%s"}`, 182 1, expectedUsername, s.userInfoExpectedEmail, user.Macaroon)) 183 } 184 185 func (s *userSuite) TestNoUserAdminCreateUser(c *check.C) { s.testNoUserAdmin(c, "/v2/create-user") } 186 func (s *userSuite) TestNoUserAdminPostUser(c *check.C) { s.testNoUserAdmin(c, "/v2/users") } 187 func (s *userSuite) testNoUserAdmin(c *check.C, endpoint string) { 188 defer daemon.MockHasUserAdmin(false)() 189 190 buf := bytes.NewBufferString("{}") 191 req, err := http.NewRequest("POST", endpoint, buf) 192 c.Assert(err, check.IsNil) 193 194 rsp := s.req(c, req, nil) 195 196 const noUserAdmin = "system user administration via snapd is not allowed on this system" 197 switch endpoint { 198 case "/v2/users": 199 c.Check(rsp, check.DeepEquals, daemon.MethodNotAllowed(noUserAdmin)) 200 case "/v2/create-user": 201 c.Check(rsp, check.DeepEquals, daemon.Forbidden(noUserAdmin)) 202 default: 203 c.Fatalf("unknown endpoint %q", endpoint) 204 } 205 } 206 207 func (s *userSuite) TestPostUserBadBody(c *check.C) { 208 buf := bytes.NewBufferString(`42`) 209 req, err := http.NewRequest("POST", "/v2/users", buf) 210 c.Assert(err, check.IsNil) 211 212 rsp := s.req(c, req, nil).(*daemon.Resp) 213 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 214 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, "cannot decode user action data from request body: .*") 215 } 216 217 func (s *userSuite) TestPostUserBadAfterBody(c *check.C) { 218 buf := bytes.NewBufferString(`{}42`) 219 req, err := http.NewRequest("POST", "/v2/users", buf) 220 c.Assert(err, check.IsNil) 221 222 rsp := s.req(c, req, nil).(*daemon.Resp) 223 c.Check(rsp, check.DeepEquals, daemon.BadRequest("spurious content after user action")) 224 } 225 226 func (s *userSuite) TestPostUserNoAction(c *check.C) { 227 buf := bytes.NewBufferString("{}") 228 req, err := http.NewRequest("POST", "/v2/users", buf) 229 c.Assert(err, check.IsNil) 230 231 rsp := s.req(c, req, nil).(*daemon.Resp) 232 c.Check(rsp, check.DeepEquals, daemon.BadRequest("missing user action")) 233 } 234 235 func (s *userSuite) TestPostUserBadAction(c *check.C) { 236 buf := bytes.NewBufferString(`{"action":"patatas"}`) 237 req, err := http.NewRequest("POST", "/v2/users", buf) 238 c.Assert(err, check.IsNil) 239 240 rsp := s.req(c, req, nil).(*daemon.Resp) 241 c.Check(rsp, check.DeepEquals, daemon.BadRequest(`unsupported user action "patatas"`)) 242 } 243 244 func (s *userSuite) TestPostUserActionRemoveNoUsername(c *check.C) { 245 buf := bytes.NewBufferString(`{"action":"remove"}`) 246 req, err := http.NewRequest("POST", "/v2/users", buf) 247 c.Assert(err, check.IsNil) 248 249 rsp := s.req(c, req, nil).(*daemon.Resp) 250 c.Check(rsp, check.DeepEquals, daemon.BadRequest("need a username to remove")) 251 } 252 253 func (s *userSuite) TestPostUserActionRemoveDelUserErr(c *check.C) { 254 st := s.d.Overlord().State() 255 st.Lock() 256 _, err := auth.NewUser(st, "some-user", "email@test.com", "macaroon", []string{"discharge"}) 257 st.Unlock() 258 c.Check(err, check.IsNil) 259 260 called := 0 261 defer daemon.MockOsutilDelUser(func(username string, opts *osutil.DelUserOptions) error { 262 called++ 263 c.Check(username, check.Equals, "some-user") 264 return fmt.Errorf("wat") 265 })() 266 267 buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`) 268 req, err := http.NewRequest("POST", "/v2/users", buf) 269 c.Assert(err, check.IsNil) 270 271 rsp := s.req(c, req, nil).(*daemon.Resp) 272 c.Check(rsp.Status, check.Equals, 500) 273 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "wat") 274 c.Check(called, check.Equals, 1) 275 } 276 277 func (s *userSuite) TestPostUserActionRemoveStateErr(c *check.C) { 278 st := s.d.Overlord().State() 279 st.Lock() 280 st.Set("auth", 42) // breaks auth 281 st.Unlock() 282 called := 0 283 defer daemon.MockOsutilDelUser(func(username string, opts *osutil.DelUserOptions) error { 284 called++ 285 c.Check(username, check.Equals, "some-user") 286 return nil 287 })() 288 289 buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`) 290 req, err := http.NewRequest("POST", "/v2/users", buf) 291 c.Assert(err, check.IsNil) 292 293 rsp := s.req(c, req, nil).(*daemon.Resp) 294 c.Check(rsp.Status, check.Equals, 500) 295 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, `internal error: could not unmarshal state entry "auth": .*`) 296 c.Check(called, check.Equals, 0) 297 } 298 299 func (s *userSuite) TestPostUserActionRemoveNoUserInState(c *check.C) { 300 called := 0 301 defer daemon.MockOsutilDelUser(func(username string, opts *osutil.DelUserOptions) error { 302 called++ 303 c.Check(username, check.Equals, "some-user") 304 return nil 305 }) 306 307 buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`) 308 req, err := http.NewRequest("POST", "/v2/users", buf) 309 c.Assert(err, check.IsNil) 310 311 rsp := s.req(c, req, nil).(*daemon.Resp) 312 c.Check(rsp, check.DeepEquals, daemon.BadRequest(`user "some-user" is not known`)) 313 c.Check(called, check.Equals, 0) 314 } 315 316 func (s *userSuite) TestPostUserActionRemove(c *check.C) { 317 st := s.d.Overlord().State() 318 st.Lock() 319 user, err := auth.NewUser(st, "some-user", "email@test.com", "macaroon", []string{"discharge"}) 320 st.Unlock() 321 c.Check(err, check.IsNil) 322 323 called := 0 324 defer daemon.MockOsutilDelUser(func(username string, opts *osutil.DelUserOptions) error { 325 called++ 326 c.Check(username, check.Equals, "some-user") 327 return nil 328 })() 329 330 buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`) 331 req, err := http.NewRequest("POST", "/v2/users", buf) 332 c.Assert(err, check.IsNil) 333 rsp := s.req(c, req, nil).(*daemon.Resp) 334 c.Check(rsp.Status, check.Equals, 200) 335 expected := []daemon.UserResponseData{ 336 {ID: user.ID, Username: user.Username, Email: user.Email}, 337 } 338 c.Check(rsp.Result, check.DeepEquals, map[string]interface{}{ 339 "removed": expected, 340 }) 341 c.Check(called, check.Equals, 1) 342 343 // and the user is removed from state 344 st.Lock() 345 _, err = auth.User(st, user.ID) 346 st.Unlock() 347 c.Check(err, check.Equals, auth.ErrInvalidUser) 348 } 349 350 func (s *userSuite) setupSigner(accountID string, signerPrivKey asserts.PrivateKey) *assertstest.SigningDB { 351 st := s.d.Overlord().State() 352 353 signerSigning := s.Brands.Register(accountID, signerPrivKey, map[string]interface{}{ 354 "account-id": accountID, 355 "verification": "verified", 356 }) 357 acctNKey := s.Brands.AccountsAndKeys(accountID) 358 359 assertstest.AddMany(s.StoreSigning, acctNKey...) 360 assertstatetest.AddMany(st, acctNKey...) 361 362 return signerSigning 363 } 364 365 var ( 366 partnerPrivKey, _ = assertstest.GenerateKey(752) 367 unknownPrivKey, _ = assertstest.GenerateKey(752) 368 ) 369 370 func (s *userSuite) makeSystemUsers(c *check.C, systemUsers []map[string]interface{}) { 371 st := s.d.Overlord().State() 372 st.Lock() 373 defer st.Unlock() 374 375 assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey("")) 376 377 s.setupSigner("my-brand", brandPrivKey) 378 s.setupSigner("partner", partnerPrivKey) 379 s.setupSigner("unknown", unknownPrivKey) 380 381 model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{ 382 "architecture": "amd64", 383 "gadget": "pc", 384 "kernel": "pc-kernel", 385 "required-snaps": []interface{}{"required-snap1"}, 386 "system-user-authority": []interface{}{"my-brand", "partner"}, 387 }) 388 // now add model related stuff to the system 389 assertstatetest.AddMany(st, model) 390 // and a serial 391 deviceKey, _ := assertstest.GenerateKey(752) 392 encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey()) 393 c.Assert(err, check.IsNil) 394 serial, err := s.Brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{ 395 "authority-id": "my-brand", 396 "brand-id": "my-brand", 397 "model": "my-model", 398 "serial": "serialserial", 399 "device-key": string(encDevKey), 400 "device-key-sha3-384": deviceKey.PublicKey().ID(), 401 "timestamp": time.Now().Format(time.RFC3339), 402 }, nil, "") 403 c.Assert(err, check.IsNil) 404 assertstatetest.AddMany(st, serial) 405 406 for _, suMap := range systemUsers { 407 su, err := s.Brands.Signing(suMap["authority-id"].(string)).Sign(asserts.SystemUserType, suMap, nil, "") 408 c.Assert(err, check.IsNil) 409 su = su.(*asserts.SystemUser) 410 // now add system-user assertion to the system 411 assertstatetest.AddMany(st, su) 412 } 413 // create fake device 414 err = devicestatetest.SetDevice(st, &auth.DeviceState{ 415 Brand: "my-brand", 416 Model: "my-model", 417 Serial: "serialserial", 418 }) 419 c.Assert(err, check.IsNil) 420 } 421 422 var goodUser = map[string]interface{}{ 423 "authority-id": "my-brand", 424 "brand-id": "my-brand", 425 "email": "foo@bar.com", 426 "series": []interface{}{"16", "18"}, 427 "models": []interface{}{"my-model", "other-model"}, 428 "name": "Boring Guy", 429 "username": "guy", 430 "password": "$6$salt$hash", 431 "since": time.Now().Format(time.RFC3339), 432 "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339), 433 } 434 435 var partnerUser = map[string]interface{}{ 436 "authority-id": "partner", 437 "brand-id": "my-brand", 438 "email": "p@partner.com", 439 "series": []interface{}{"16", "18"}, 440 "models": []interface{}{"my-model"}, 441 "name": "Partner Guy", 442 "username": "partnerguy", 443 "password": "$6$salt$hash", 444 "since": time.Now().Format(time.RFC3339), 445 "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339), 446 } 447 448 var serialUser = map[string]interface{}{ 449 "format": "1", 450 "authority-id": "my-brand", 451 "brand-id": "my-brand", 452 "email": "serial@bar.com", 453 "series": []interface{}{"16", "18"}, 454 "models": []interface{}{"my-model"}, 455 "serials": []interface{}{"serialserial"}, 456 "name": "Serial Guy", 457 "username": "goodserialguy", 458 "password": "$6$salt$hash", 459 "since": time.Now().Format(time.RFC3339), 460 "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339), 461 } 462 463 var badUser = map[string]interface{}{ 464 // bad user (not valid for this model) 465 "authority-id": "my-brand", 466 "brand-id": "my-brand", 467 "email": "foobar@bar.com", 468 "series": []interface{}{"16", "18"}, 469 "models": []interface{}{"non-of-the-models-i-have"}, 470 "name": "Random Gal", 471 "username": "gal", 472 "password": "$6$salt$hash", 473 "since": time.Now().Format(time.RFC3339), 474 "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339), 475 } 476 477 var badUserNoMatchingSerial = map[string]interface{}{ 478 "format": "1", 479 "authority-id": "my-brand", 480 "brand-id": "my-brand", 481 "email": "noserial@bar.com", 482 "series": []interface{}{"16", "18"}, 483 "models": []interface{}{"my-model"}, 484 "serials": []interface{}{"different-serialserial"}, 485 "name": "No Serial Guy", 486 "username": "noserial", 487 "password": "$6$salt$hash", 488 "since": time.Now().Format(time.RFC3339), 489 "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339), 490 } 491 492 var unknownUser = map[string]interface{}{ 493 "authority-id": "unknown", 494 "brand-id": "my-brand", 495 "email": "x@partner.com", 496 "series": []interface{}{"16", "18"}, 497 "models": []interface{}{"my-model"}, 498 "name": "XGuy", 499 "username": "xguy", 500 "password": "$6$salt$hash", 501 "since": time.Now().Format(time.RFC3339), 502 "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339), 503 } 504 505 func (s *userSuite) TestGetUserDetailsFromAssertionHappy(c *check.C) { 506 s.makeSystemUsers(c, []map[string]interface{}{goodUser}) 507 508 st := s.d.Overlord().State() 509 510 st.Lock() 511 model, err := s.d.Overlord().DeviceManager().Model() 512 st.Unlock() 513 c.Assert(err, check.IsNil) 514 515 // ensure that if we query the details from the assert DB we get 516 // the expected user 517 username, opts, err := daemon.GetUserDetailsFromAssertion(st, model, nil, "foo@bar.com") 518 c.Check(username, check.Equals, "guy") 519 c.Check(opts, check.DeepEquals, &osutil.AddUserOptions{ 520 Gecos: "foo@bar.com,Boring Guy", 521 Password: "$6$salt$hash", 522 }) 523 c.Check(err, check.IsNil) 524 } 525 526 // FIXME: These tests all look similar, with small deltas. Would be 527 // nice to transform them into a table that is just the deltas, and 528 // run on a loop. 529 func (s *userSuite) TestPostCreateUserFromAssertion(c *check.C) { 530 s.makeSystemUsers(c, []map[string]interface{}{goodUser}) 531 532 // mock the calls that create the user 533 defer daemon.MockOsutilAddUser(func(username string, opts *osutil.AddUserOptions) error { 534 c.Check(username, check.Equals, "guy") 535 c.Check(opts.Gecos, check.Equals, "foo@bar.com,Boring Guy") 536 c.Check(opts.Sudoer, check.Equals, false) 537 c.Check(opts.Password, check.Equals, "$6$salt$hash") 538 c.Check(opts.ForcePasswordChange, check.Equals, false) 539 return nil 540 })() 541 542 // do it! 543 buf := bytes.NewBufferString(`{"email": "foo@bar.com","known":true}`) 544 req, err := http.NewRequest("POST", "/v2/create-user", buf) 545 c.Assert(err, check.IsNil) 546 547 rsp := s.req(c, req, nil).(*daemon.Resp) 548 549 expected := &daemon.UserResponseData{ 550 Username: "guy", 551 } 552 553 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 554 c.Check(rsp.Result, check.FitsTypeOf, expected) 555 c.Check(rsp.Result, check.DeepEquals, expected) 556 557 // ensure the user was added to the state 558 st := s.d.Overlord().State() 559 st.Lock() 560 users, err := auth.Users(st) 561 c.Assert(err, check.IsNil) 562 st.Unlock() 563 c.Check(users, check.HasLen, 1) 564 } 565 566 func (s *userSuite) TestPostCreateUserFromAssertionWithForcePasswordChange(c *check.C) { 567 user := make(map[string]interface{}) 568 for k, v := range goodUser { 569 user[k] = v 570 } 571 user["force-password-change"] = "true" 572 lusers := []map[string]interface{}{user} 573 s.makeSystemUsers(c, lusers) 574 575 // mock the calls that create the user 576 defer daemon.MockOsutilAddUser(func(username string, opts *osutil.AddUserOptions) error { 577 c.Check(username, check.Equals, "guy") 578 c.Check(opts.Gecos, check.Equals, "foo@bar.com,Boring Guy") 579 c.Check(opts.Sudoer, check.Equals, false) 580 c.Check(opts.Password, check.Equals, "$6$salt$hash") 581 c.Check(opts.ForcePasswordChange, check.Equals, true) 582 return nil 583 })() 584 585 // do it! 586 buf := bytes.NewBufferString(`{"email": "foo@bar.com","known":true}`) 587 req, err := http.NewRequest("POST", "/v2/create-user", buf) 588 c.Assert(err, check.IsNil) 589 590 rsp := s.req(c, req, nil).(*daemon.Resp) 591 592 expected := &daemon.UserResponseData{ 593 Username: "guy", 594 } 595 596 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 597 c.Check(rsp.Result, check.FitsTypeOf, expected) 598 c.Check(rsp.Result, check.DeepEquals, expected) 599 600 // ensure the user was added to the state 601 st := s.d.Overlord().State() 602 st.Lock() 603 users, err := auth.Users(st) 604 c.Assert(err, check.IsNil) 605 st.Unlock() 606 c.Check(users, check.HasLen, 1) 607 } 608 609 func (s *userSuite) TestPostCreateUserFromAssertionAllKnown(c *check.C) { 610 expectSudoer := false 611 s.testPostCreateUserFromAssertion(c, `{"known":true}`, expectSudoer) 612 } 613 614 func (s *userSuite) TestPostCreateUserFromAssertionAllAutomatic(c *check.C) { 615 // automatic implies "sudoder" 616 expectSudoer := true 617 s.testPostCreateUserFromAssertion(c, `{"automatic":true}`, expectSudoer) 618 } 619 620 func (s *userSuite) testPostCreateUserFromAssertion(c *check.C, postData string, expectSudoer bool) { 621 s.makeSystemUsers(c, []map[string]interface{}{goodUser, partnerUser, serialUser, badUser, badUserNoMatchingSerial, unknownUser}) 622 created := map[string]bool{} 623 // mock the calls that create the user 624 defer daemon.MockOsutilAddUser(func(username string, opts *osutil.AddUserOptions) error { 625 switch username { 626 case "guy": 627 c.Check(opts.Gecos, check.Equals, "foo@bar.com,Boring Guy") 628 case "partnerguy": 629 c.Check(opts.Gecos, check.Equals, "p@partner.com,Partner Guy") 630 case "goodserialguy": 631 c.Check(opts.Gecos, check.Equals, "serial@bar.com,Serial Guy") 632 default: 633 c.Logf("unexpected username %q", username) 634 c.Fail() 635 } 636 c.Check(opts.Sudoer, check.Equals, expectSudoer) 637 c.Check(opts.Password, check.Equals, "$6$salt$hash") 638 created[username] = true 639 return nil 640 })() 641 // make sure we report them as non-existing until created 642 defer daemon.MockUserLookup(func(username string) (*user.User, error) { 643 if created[username] { 644 return s.trivialUserLookup(username) 645 } 646 return nil, fmt.Errorf("not created yet") 647 })() 648 649 // do it! 650 buf := bytes.NewBufferString(postData) 651 req, err := http.NewRequest("POST", "/v2/create-user", buf) 652 c.Assert(err, check.IsNil) 653 654 rsp := s.req(c, req, nil).(*daemon.Resp) 655 656 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 657 // note that we get a list here instead of a single 658 // userResponseData item 659 c.Check(rsp.Result, check.FitsTypeOf, []daemon.UserResponseData{}) 660 seen := map[string]bool{} 661 for _, u := range rsp.Result.([]daemon.UserResponseData) { 662 seen[u.Username] = true 663 c.Check(u, check.DeepEquals, daemon.UserResponseData{Username: u.Username}) 664 } 665 c.Check(seen, check.DeepEquals, map[string]bool{ 666 "guy": true, 667 "partnerguy": true, 668 "goodserialguy": true, 669 }) 670 671 // ensure the user was added to the state 672 st := s.d.Overlord().State() 673 st.Lock() 674 users, err := auth.Users(st) 675 c.Assert(err, check.IsNil) 676 st.Unlock() 677 c.Check(users, check.HasLen, 3) 678 } 679 680 func (s *userSuite) TestPostCreateUserFromAssertionAllKnownClassicErrors(c *check.C) { 681 restore := release.MockOnClassic(true) 682 defer restore() 683 684 s.makeSystemUsers(c, []map[string]interface{}{goodUser}) 685 686 // do it! 687 buf := bytes.NewBufferString(`{"known":true}`) 688 req, err := http.NewRequest("POST", "/v2/create-user", buf) 689 c.Assert(err, check.IsNil) 690 691 rsp := s.req(c, req, nil).(*daemon.Resp) 692 693 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 694 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, `cannot create user: device is a classic system`) 695 } 696 697 func (s *userSuite) TestPostCreateUserFromAssertionAllKnownButOwnedErrors(c *check.C) { 698 s.makeSystemUsers(c, []map[string]interface{}{goodUser}) 699 700 st := s.d.Overlord().State() 701 st.Lock() 702 _, err := auth.NewUser(st, "username", "email@test.com", "macaroon", []string{"discharge"}) 703 st.Unlock() 704 c.Check(err, check.IsNil) 705 706 // do it! 707 buf := bytes.NewBufferString(`{"known":true}`) 708 req, err := http.NewRequest("POST", "/v2/create-user", buf) 709 c.Assert(err, check.IsNil) 710 711 rsp := s.req(c, req, nil).(*daemon.Resp) 712 713 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 714 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, `cannot create user: device already managed`) 715 } 716 717 func (s *userSuite) TestPostCreateUserAutomaticManagedDoesNotActOrError(c *check.C) { 718 s.makeSystemUsers(c, []map[string]interface{}{goodUser}) 719 720 st := s.d.Overlord().State() 721 st.Lock() 722 _, err := auth.NewUser(st, "username", "email@test.com", "macaroon", []string{"discharge"}) 723 st.Unlock() 724 c.Check(err, check.IsNil) 725 726 // do it! 727 buf := bytes.NewBufferString(`{"automatic":true}`) 728 req, err := http.NewRequest("POST", "/v2/create-user", buf) 729 c.Assert(err, check.IsNil) 730 731 rsp := s.req(c, req, nil).(*daemon.Resp) 732 733 // expecting an empty reply 734 expected := []daemon.UserResponseData{} 735 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 736 c.Check(rsp.Result, check.FitsTypeOf, expected) 737 c.Check(rsp.Result, check.DeepEquals, expected) 738 } 739 740 func (s *userSuite) TestPostCreateUserFromAssertionAllKnownNoModelError(c *check.C) { 741 restore := release.MockOnClassic(false) 742 defer restore() 743 744 st := s.d.Overlord().State() 745 // have not model yet 746 st.Lock() 747 err := devicestatetest.SetDevice(st, &auth.DeviceState{}) 748 st.Unlock() 749 c.Assert(err, check.IsNil) 750 751 // do it! 752 buf := bytes.NewBufferString(`{"known":true}`) 753 req, err := http.NewRequest("POST", "/v2/create-user", buf) 754 c.Assert(err, check.IsNil) 755 756 rsp := s.req(c, req, nil).(*daemon.Resp) 757 758 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 759 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, `cannot create user: cannot get model assertion: no state entry for key`) 760 } 761 762 func (s *userSuite) TestPostCreateUserFromAssertionNoModel(c *check.C) { 763 restore := release.MockOnClassic(false) 764 defer restore() 765 766 model := s.Brands.Model("my-brand", "other-model", map[string]interface{}{ 767 "architecture": "amd64", 768 "gadget": "pc", 769 "kernel": "pc-kernel", 770 "system-user-authority": []interface{}{"my-brand", "partner"}, 771 }) 772 s.makeSystemUsers(c, []map[string]interface{}{serialUser}) 773 774 st := s.d.Overlord().State() 775 st.Lock() 776 assertstatetest.AddMany(st, model) 777 err := devicestatetest.SetDevice(st, &auth.DeviceState{ 778 Brand: "my-brand", 779 Model: "my-model", 780 Serial: "other-serial-assertion", 781 }) 782 st.Unlock() 783 c.Assert(err, check.IsNil) 784 785 // do it! 786 buf := bytes.NewBufferString(`{"email":"serial@bar.com", "known":true}`) 787 req, err := http.NewRequest("POST", "/v2/create-user", buf) 788 c.Assert(err, check.IsNil) 789 790 rsp := s.req(c, req, nil).(*daemon.Resp) 791 792 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 793 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, `cannot add system-user "serial@bar.com": bound to serial assertion but device not yet registered`) 794 } 795 796 func (s *userSuite) TestPostCreateUserFromAssertionAllKnownButOwned(c *check.C) { 797 s.makeSystemUsers(c, []map[string]interface{}{goodUser}) 798 799 st := s.d.Overlord().State() 800 st.Lock() 801 _, err := auth.NewUser(st, "username", "email@test.com", "macaroon", []string{"discharge"}) 802 st.Unlock() 803 c.Check(err, check.IsNil) 804 805 // mock the calls that create the user 806 created := map[string]bool{} 807 defer daemon.MockOsutilAddUser(func(username string, opts *osutil.AddUserOptions) error { 808 c.Check(username, check.Equals, "guy") 809 c.Check(opts.Gecos, check.Equals, "foo@bar.com,Boring Guy") 810 c.Check(opts.Sudoer, check.Equals, false) 811 c.Check(opts.Password, check.Equals, "$6$salt$hash") 812 created[username] = true 813 return nil 814 })() 815 // make sure we report them as non-existing until created 816 defer daemon.MockUserLookup(func(username string) (*user.User, error) { 817 if created[username] { 818 return s.trivialUserLookup(username) 819 } 820 return nil, fmt.Errorf("not created yet") 821 })() 822 823 // do it! 824 buf := bytes.NewBufferString(`{"known":true,"force-managed":true}`) 825 req, err := http.NewRequest("POST", "/v2/create-user", buf) 826 c.Assert(err, check.IsNil) 827 828 rsp := s.req(c, req, nil).(*daemon.Resp) 829 830 // note that we get a list here instead of a single 831 // userResponseData item 832 expected := []daemon.UserResponseData{ 833 {Username: "guy"}, 834 } 835 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 836 c.Check(rsp.Result, check.FitsTypeOf, expected) 837 c.Check(rsp.Result, check.DeepEquals, expected) 838 } 839 840 func (s *userSuite) TestUsersEmpty(c *check.C) { 841 req, err := http.NewRequest("GET", "/v2/users", nil) 842 c.Assert(err, check.IsNil) 843 844 rsp := s.req(c, req, nil).(*daemon.Resp) 845 846 expected := []daemon.UserResponseData{} 847 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 848 c.Check(rsp.Result, check.FitsTypeOf, expected) 849 c.Check(rsp.Result, check.DeepEquals, expected) 850 } 851 852 func (s *userSuite) TestUsersHasUser(c *check.C) { 853 st := s.d.Overlord().State() 854 st.Lock() 855 u, err := auth.NewUser(st, "someuser", "mymail@test.com", "macaroon", []string{"discharge"}) 856 st.Unlock() 857 c.Assert(err, check.IsNil) 858 859 req, err := http.NewRequest("GET", "/v2/users", nil) 860 c.Assert(err, check.IsNil) 861 862 rsp := s.req(c, req, nil).(*daemon.Resp) 863 864 expected := []daemon.UserResponseData{ 865 {ID: u.ID, Username: u.Username, Email: u.Email}, 866 } 867 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 868 c.Check(rsp.Result, check.FitsTypeOf, expected) 869 c.Check(rsp.Result, check.DeepEquals, expected) 870 }