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