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  }