github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/daemon/api_users.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2019 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 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "net/http" 26 "os/user" 27 "path/filepath" 28 "regexp" 29 "time" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/client" 33 "github.com/snapcore/snapd/logger" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/overlord/assertstate" 36 "github.com/snapcore/snapd/overlord/auth" 37 "github.com/snapcore/snapd/overlord/configstate/config" 38 "github.com/snapcore/snapd/overlord/snapstate" 39 "github.com/snapcore/snapd/overlord/state" 40 "github.com/snapcore/snapd/release" 41 "github.com/snapcore/snapd/store" 42 "github.com/snapcore/snapd/strutil" 43 ) 44 45 var ( 46 loginCmd = &Command{ 47 Path: "/v2/login", 48 POST: loginUser, 49 WriteAccess: authenticatedAccess{Polkit: polkitActionLogin}, 50 } 51 52 logoutCmd = &Command{ 53 Path: "/v2/logout", 54 POST: logoutUser, 55 WriteAccess: authenticatedAccess{Polkit: polkitActionLogin}, 56 } 57 58 // backwards compat; to-be-deprecated 59 createUserCmd = &Command{ 60 Path: "/v2/create-user", 61 POST: postCreateUser, 62 WriteAccess: rootAccess{}, 63 } 64 65 usersCmd = &Command{ 66 Path: "/v2/users", 67 GET: getUsers, 68 POST: postUsers, 69 ReadAccess: rootAccess{}, 70 WriteAccess: rootAccess{}, 71 } 72 ) 73 74 var ( 75 osutilAddUser = osutil.AddUser 76 osutilDelUser = osutil.DelUser 77 ) 78 79 // userResponseData contains the data releated to user creation/login/query 80 type userResponseData struct { 81 ID int `json:"id,omitempty"` 82 Username string `json:"username,omitempty"` 83 Email string `json:"email,omitempty"` 84 SSHKeys []string `json:"ssh-keys,omitempty"` 85 86 Macaroon string `json:"macaroon,omitempty"` 87 Discharges []string `json:"discharges,omitempty"` 88 } 89 90 var isEmailish = regexp.MustCompile(`.@.*\..`).MatchString 91 92 func loginUser(c *Command, r *http.Request, user *auth.UserState) Response { 93 var loginData struct { 94 Username string `json:"username"` 95 Email string `json:"email"` 96 Password string `json:"password"` 97 Otp string `json:"otp"` 98 } 99 100 decoder := json.NewDecoder(r.Body) 101 if err := decoder.Decode(&loginData); err != nil { 102 return BadRequest("cannot decode login data from request body: %v", err) 103 } 104 105 if loginData.Email == "" && isEmailish(loginData.Username) { 106 // for backwards compatibility, if no email is provided assume username is the email 107 loginData.Email = loginData.Username 108 loginData.Username = "" 109 } 110 111 if loginData.Email == "" && user != nil && user.Email != "" { 112 loginData.Email = user.Email 113 } 114 115 // the "username" needs to look a lot like an email address 116 if !isEmailish(loginData.Email) { 117 return &apiError{ 118 Status: 400, 119 Message: "please use a valid email address.", 120 Kind: client.ErrorKindInvalidAuthData, 121 Value: map[string][]string{"email": {"invalid"}}, 122 } 123 } 124 125 overlord := c.d.overlord 126 st := overlord.State() 127 theStore := storeFrom(c.d) 128 macaroon, discharge, err := theStore.LoginUser(loginData.Email, loginData.Password, loginData.Otp) 129 switch err { 130 case store.ErrAuthenticationNeeds2fa: 131 return &apiError{ 132 Status: 401, 133 Message: err.Error(), 134 Kind: client.ErrorKindTwoFactorRequired, 135 } 136 case store.Err2faFailed: 137 return &apiError{ 138 Status: 401, 139 Message: err.Error(), 140 Kind: client.ErrorKindTwoFactorFailed, 141 } 142 default: 143 switch err := err.(type) { 144 case store.InvalidAuthDataError: 145 return &apiError{ 146 Status: 400, 147 Message: err.Error(), 148 Kind: client.ErrorKindInvalidAuthData, 149 Value: err, 150 } 151 case store.PasswordPolicyError: 152 return &apiError{ 153 Status: 401, 154 Message: err.Error(), 155 Kind: client.ErrorKindPasswordPolicy, 156 Value: err, 157 } 158 } 159 return Unauthorized(err.Error()) 160 case nil: 161 // continue 162 } 163 st.Lock() 164 if user != nil { 165 // local user logged-in, set its store macaroons 166 user.StoreMacaroon = macaroon 167 user.StoreDischarges = []string{discharge} 168 // user's email address authenticated by the store 169 user.Email = loginData.Email 170 err = auth.UpdateUser(st, user) 171 } else { 172 user, err = auth.NewUser(st, loginData.Username, loginData.Email, macaroon, []string{discharge}) 173 } 174 st.Unlock() 175 if err != nil { 176 return InternalError("cannot persist authentication details: %v", err) 177 } 178 179 result := userResponseData{ 180 ID: user.ID, 181 Username: user.Username, 182 Email: user.Email, 183 Macaroon: user.Macaroon, 184 Discharges: user.Discharges, 185 } 186 return SyncResponse(result) 187 } 188 189 func logoutUser(c *Command, r *http.Request, user *auth.UserState) Response { 190 state := c.d.overlord.State() 191 state.Lock() 192 defer state.Unlock() 193 194 if user == nil { 195 return BadRequest("not logged in") 196 } 197 _, err := auth.RemoveUser(state, user.ID) 198 if err != nil { 199 return InternalError(err.Error()) 200 } 201 202 return SyncResponse(nil) 203 } 204 205 // this might need to become a function, if having user admin becomes a config option 206 var hasUserAdmin = !release.OnClassic 207 208 const noUserAdmin = "system user administration via snapd is not allowed on this system" 209 210 func postUsers(c *Command, r *http.Request, user *auth.UserState) Response { 211 if !hasUserAdmin { 212 return MethodNotAllowed(noUserAdmin) 213 } 214 215 var postData postUserData 216 217 decoder := json.NewDecoder(r.Body) 218 if err := decoder.Decode(&postData); err != nil { 219 return BadRequest("cannot decode user action data from request body: %v", err) 220 } 221 if decoder.More() { 222 return BadRequest("spurious content after user action") 223 } 224 switch postData.Action { 225 case "create": 226 return createUser(c, postData.postUserCreateData) 227 case "remove": 228 return removeUser(c, postData.Username, postData.postUserDeleteData) 229 case "": 230 return BadRequest("missing user action") 231 } 232 return BadRequest("unsupported user action %q", postData.Action) 233 } 234 235 func removeUser(c *Command, username string, opts postUserDeleteData) Response { 236 // TODO: allow to remove user entries by email as well 237 238 // catch silly errors 239 if username == "" { 240 return BadRequest("need a username to remove") 241 } 242 // check the user is known to snapd 243 st := c.d.overlord.State() 244 st.Lock() 245 _, err := auth.UserByUsername(st, username) 246 st.Unlock() 247 if err == auth.ErrInvalidUser { 248 return BadRequest("user %q is not known", username) 249 } 250 if err != nil { 251 return InternalError(err.Error()) 252 } 253 254 // first remove the system user 255 if err := osutilDelUser(username, &osutil.DelUserOptions{ExtraUsers: !release.OnClassic}); err != nil { 256 return InternalError(err.Error()) 257 } 258 259 // then the UserState 260 st.Lock() 261 u, err := auth.RemoveUserByUsername(st, username) 262 st.Unlock() 263 // ErrInvalidUser means "not found" in this case 264 if err != nil && err != auth.ErrInvalidUser { 265 return InternalError(err.Error()) 266 } 267 268 result := map[string]interface{}{ 269 "removed": []userResponseData{ 270 {ID: u.ID, Username: u.Username, Email: u.Email}, 271 }, 272 } 273 return SyncResponse(result) 274 } 275 276 func postCreateUser(c *Command, r *http.Request, user *auth.UserState) Response { 277 if !hasUserAdmin { 278 return Forbidden(noUserAdmin) 279 } 280 var createData postUserCreateData 281 282 decoder := json.NewDecoder(r.Body) 283 if err := decoder.Decode(&createData); err != nil { 284 return BadRequest("cannot decode create-user data from request body: %v", err) 285 } 286 287 // this is /v2/create-user, meaning we want the 288 // backwards-compatible wackiness 289 createData.singleUserResultCompat = true 290 291 return createUser(c, createData) 292 } 293 294 func createUser(c *Command, createData postUserCreateData) Response { 295 // verify request 296 st := c.d.overlord.State() 297 st.Lock() 298 users, err := auth.Users(st) 299 st.Unlock() 300 if err != nil { 301 return InternalError("cannot get user count: %s", err) 302 } 303 304 if !createData.ForceManaged { 305 if len(users) > 0 && createData.Automatic { 306 // no users created but no error with the automatic flag 307 return SyncResponse([]userResponseData{}) 308 } 309 if len(users) > 0 { 310 return BadRequest("cannot create user: device already managed") 311 } 312 if release.OnClassic { 313 return BadRequest("cannot create user: device is a classic system") 314 } 315 } 316 if createData.Automatic { 317 var enabled bool 318 st.Lock() 319 tr := config.NewTransaction(st) 320 err := tr.Get("core", "users.create.automatic", &enabled) 321 st.Unlock() 322 if err != nil { 323 if !config.IsNoOption(err) { 324 return InternalError("%v", err) 325 } 326 // defaults to enabled 327 enabled = true 328 } 329 if !enabled { 330 // disabled, do nothing 331 return SyncResponse([]userResponseData{}) 332 } 333 // Automatic implies known/sudoers 334 createData.Known = true 335 createData.Sudoer = true 336 } 337 338 var model *asserts.Model 339 var serial *asserts.Serial 340 createKnown := createData.Known 341 if createKnown { 342 var err error 343 st.Lock() 344 model, err = c.d.overlord.DeviceManager().Model() 345 st.Unlock() 346 if err != nil { 347 return InternalError("cannot create user: cannot get model assertion: %v", err) 348 } 349 st.Lock() 350 serial, err = c.d.overlord.DeviceManager().Serial() 351 st.Unlock() 352 if err != nil && err != state.ErrNoState { 353 return InternalError("cannot create user: cannot get serial: %v", err) 354 } 355 } 356 357 // special case: the user requested the creation of all known 358 // system-users 359 if createData.Email == "" && createKnown { 360 return createAllKnownSystemUsers(st, model, serial, &createData) 361 } 362 if createData.Email == "" { 363 return BadRequest("cannot create user: 'email' field is empty") 364 } 365 366 var username string 367 var opts *osutil.AddUserOptions 368 if createKnown { 369 username, opts, err = getUserDetailsFromAssertion(st, model, serial, createData.Email) 370 } else { 371 username, opts, err = getUserDetailsFromStore(storeFrom(c.d), createData.Email) 372 } 373 if err != nil { 374 return BadRequest("%s", err) 375 } 376 377 // FIXME: duplicated code 378 opts.Sudoer = createData.Sudoer 379 opts.ExtraUsers = !release.OnClassic 380 381 if err := osutilAddUser(username, opts); err != nil { 382 return BadRequest("cannot create user %s: %s", username, err) 383 } 384 385 if err := setupLocalUser(c.d.overlord.State(), username, createData.Email); err != nil { 386 return InternalError("%s", err) 387 } 388 389 result := userResponseData{ 390 Username: username, 391 SSHKeys: opts.SSHKeys, 392 } 393 394 if createData.singleUserResultCompat { 395 // return a single userResponseData in this case 396 return SyncResponse(&result) 397 } else { 398 return SyncResponse([]userResponseData{result}) 399 } 400 } 401 402 func getUserDetailsFromStore(theStore snapstate.StoreService, email string) (string, *osutil.AddUserOptions, error) { 403 v, err := theStore.UserInfo(email) 404 if err != nil { 405 return "", nil, fmt.Errorf("cannot create user %q: %s", email, err) 406 } 407 if len(v.SSHKeys) == 0 { 408 return "", nil, fmt.Errorf("cannot create user for %q: no ssh keys found", email) 409 } 410 411 gecos := fmt.Sprintf("%s,%s", email, v.OpenIDIdentifier) 412 opts := &osutil.AddUserOptions{ 413 SSHKeys: v.SSHKeys, 414 Gecos: gecos, 415 } 416 return v.Username, opts, nil 417 } 418 419 func createAllKnownSystemUsers(st *state.State, modelAs *asserts.Model, serialAs *asserts.Serial, createData *postUserCreateData) Response { 420 var createdUsers []userResponseData 421 headers := map[string]string{ 422 "brand-id": modelAs.BrandID(), 423 } 424 425 st.Lock() 426 db := assertstate.DB(st) 427 assertions, err := db.FindMany(asserts.SystemUserType, headers) 428 st.Unlock() 429 if err != nil && !asserts.IsNotFound(err) { 430 return BadRequest("cannot find system-user assertion: %s", err) 431 } 432 433 for _, as := range assertions { 434 email := as.(*asserts.SystemUser).Email() 435 // we need to use getUserDetailsFromAssertion as this verifies 436 // the assertion against the current brand/model/time 437 username, opts, err := getUserDetailsFromAssertion(st, modelAs, serialAs, email) 438 if err != nil { 439 logger.Noticef("ignoring system-user assertion for %q: %s", email, err) 440 continue 441 } 442 // ignore already existing users 443 if _, err := userLookup(username); err == nil { 444 continue 445 } 446 447 // FIXME: duplicated code 448 opts.Sudoer = createData.Sudoer 449 opts.ExtraUsers = !release.OnClassic 450 451 if err := osutilAddUser(username, opts); err != nil { 452 return InternalError("cannot add user %q: %s", username, err) 453 } 454 if err := setupLocalUser(st, username, email); err != nil { 455 return InternalError("%s", err) 456 } 457 createdUsers = append(createdUsers, userResponseData{ 458 Username: username, 459 SSHKeys: opts.SSHKeys, 460 }) 461 } 462 463 return SyncResponse(createdUsers) 464 } 465 466 func getUserDetailsFromAssertion(st *state.State, modelAs *asserts.Model, serialAs *asserts.Serial, email string) (string, *osutil.AddUserOptions, error) { 467 errorPrefix := fmt.Sprintf("cannot add system-user %q: ", email) 468 469 st.Lock() 470 db := assertstate.DB(st) 471 st.Unlock() 472 473 brandID := modelAs.BrandID() 474 series := modelAs.Series() 475 model := modelAs.Model() 476 477 a, err := db.Find(asserts.SystemUserType, map[string]string{ 478 "brand-id": brandID, 479 "email": email, 480 }) 481 if err != nil { 482 return "", nil, fmt.Errorf(errorPrefix+"%v", err) 483 } 484 // the asserts package guarantees that this cast will work 485 su := a.(*asserts.SystemUser) 486 487 // cross check that the assertion is valid for the given series/model 488 489 // check that the signer of the assertion is one of the accepted ones 490 sysUserAuths := modelAs.SystemUserAuthority() 491 if len(sysUserAuths) > 0 && !strutil.ListContains(sysUserAuths, su.AuthorityID()) { 492 return "", nil, fmt.Errorf(errorPrefix+"%q not in accepted authorities %q", email, su.AuthorityID(), sysUserAuths) 493 } 494 if len(su.Series()) > 0 && !strutil.ListContains(su.Series(), series) { 495 return "", nil, fmt.Errorf(errorPrefix+"%q not in series %q", email, series, su.Series()) 496 } 497 if len(su.Models()) > 0 && !strutil.ListContains(su.Models(), model) { 498 return "", nil, fmt.Errorf(errorPrefix+"%q not in models %q", model, su.Models()) 499 } 500 if len(su.Serials()) > 0 { 501 if serialAs == nil { 502 return "", nil, fmt.Errorf(errorPrefix + "bound to serial assertion but device not yet registered") 503 } 504 serial := serialAs.Serial() 505 if !strutil.ListContains(su.Serials(), serial) { 506 return "", nil, fmt.Errorf(errorPrefix+"%q not in serials %q", serial, su.Serials()) 507 } 508 } 509 510 if !su.ValidAt(time.Now()) { 511 return "", nil, fmt.Errorf(errorPrefix + "assertion not valid anymore") 512 } 513 514 gecos := fmt.Sprintf("%s,%s", email, su.Name()) 515 opts := &osutil.AddUserOptions{ 516 SSHKeys: su.SSHKeys(), 517 Gecos: gecos, 518 Password: su.Password(), 519 ForcePasswordChange: su.ForcePasswordChange(), 520 } 521 return su.Username(), opts, nil 522 } 523 524 type postUserData struct { 525 Action string `json:"action"` 526 Username string `json:"username"` 527 postUserCreateData 528 postUserDeleteData 529 } 530 531 type postUserCreateData struct { 532 Email string `json:"email"` 533 Sudoer bool `json:"sudoer"` 534 Known bool `json:"known"` 535 ForceManaged bool `json:"force-managed"` 536 Automatic bool `json:"automatic"` 537 538 // singleUserResultCompat indicates whether to preserve 539 // backwards compatibility, which results in more clunky 540 // return values (userResponseData OR [userResponseData] vs now 541 // uniform [userResponseData]); internal, not from JSON. 542 singleUserResultCompat bool 543 } 544 545 type postUserDeleteData struct{} 546 547 var userLookup = user.Lookup 548 549 func setupLocalUser(st *state.State, username, email string) error { 550 user, err := userLookup(username) 551 if err != nil { 552 return fmt.Errorf("cannot lookup user %q: %s", username, err) 553 } 554 uid, gid, err := osutil.UidGid(user) 555 if err != nil { 556 return err 557 } 558 authDataFn := filepath.Join(user.HomeDir, ".snap", "auth.json") 559 if err := osutil.MkdirAllChown(filepath.Dir(authDataFn), 0700, uid, gid); err != nil { 560 return err 561 } 562 563 // setup new user, local-only 564 st.Lock() 565 authUser, err := auth.NewUser(st, username, email, "", nil) 566 st.Unlock() 567 if err != nil { 568 return fmt.Errorf("cannot persist authentication details: %v", err) 569 } 570 // store macaroon auth, user's ID, email and username in auth.json in 571 // the new users home dir 572 outStr, err := json.Marshal(struct { 573 ID int `json:"id"` 574 Username string `json:"username"` 575 Email string `json:"email"` 576 Macaroon string `json:"macaroon"` 577 }{ 578 ID: authUser.ID, 579 Username: authUser.Username, 580 Email: authUser.Email, 581 Macaroon: authUser.Macaroon, 582 }) 583 if err != nil { 584 return fmt.Errorf("cannot marshal auth data: %s", err) 585 } 586 if err := osutil.AtomicWriteFileChown(authDataFn, []byte(outStr), 0600, 0, uid, gid); err != nil { 587 return fmt.Errorf("cannot write auth file %q: %s", authDataFn, err) 588 } 589 590 return nil 591 } 592 593 func getUsers(c *Command, r *http.Request, user *auth.UserState) Response { 594 st := c.d.overlord.State() 595 st.Lock() 596 users, err := auth.Users(st) 597 st.Unlock() 598 if err != nil { 599 return InternalError("cannot get users: %s", err) 600 } 601 602 resp := make([]userResponseData, len(users)) 603 for i, u := range users { 604 resp[i] = userResponseData{ 605 Username: u.Username, 606 Email: u.Email, 607 ID: u.ID, 608 } 609 } 610 return SyncResponse(resp) 611 }