github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modeluser.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/mgo/v3"
    13  	"github.com/juju/mgo/v3/bson"
    14  	"github.com/juju/mgo/v3/txn"
    15  	"github.com/juju/names/v5"
    16  
    17  	"github.com/juju/juju/core/permission"
    18  	"github.com/juju/juju/mongo"
    19  )
    20  
    21  // modelUserLastConnectionDoc is updated by the apiserver whenever the user
    22  // connects over the API. This update is not done using mgo.txn so the values
    23  // could well change underneath a normal transaction and as such, it should
    24  // NEVER appear in any transaction asserts. It is really informational only as
    25  // far as everyone except the api server is concerned.
    26  type modelUserLastConnectionDoc struct {
    27  	ID             string    `bson:"_id"`
    28  	ModelUUID      string    `bson:"model-uuid"`
    29  	UserName       string    `bson:"user"`
    30  	LastConnection time.Time `bson:"last-connection"`
    31  }
    32  
    33  // setModelAccess changes the user's access permissions on the model.
    34  func (st *State) setModelAccess(access permission.Access, userGlobalKey, modelUUID string) error {
    35  	if err := permission.ValidateModelAccess(access); err != nil {
    36  		return errors.Trace(err)
    37  	}
    38  	op := updatePermissionOp(modelKey(modelUUID), userGlobalKey, access)
    39  	err := st.db().RunTransactionFor(modelUUID, []txn.Op{op})
    40  	if err == txn.ErrAborted {
    41  		return errors.NotFoundf("existing permissions")
    42  	}
    43  	return errors.Trace(err)
    44  }
    45  
    46  // LastModelConnection returns when this User last connected through the API
    47  // in UTC. The resulting time will be nil if the user has never logged in.
    48  func (m *Model) LastModelConnection(user names.UserTag) (time.Time, error) {
    49  	lastConnections, closer := m.st.db().GetRawCollection(modelUserLastConnectionC)
    50  	defer closer()
    51  
    52  	username := user.Id()
    53  	var lastConn modelUserLastConnectionDoc
    54  	err := lastConnections.FindId(m.st.docID(username)).Select(bson.D{{"last-connection", 1}}).One(&lastConn)
    55  	if err != nil {
    56  		if err == mgo.ErrNotFound {
    57  			err = errors.Wrap(err, newNeverConnectedError(username))
    58  		}
    59  		return time.Time{}, errors.Trace(err)
    60  	}
    61  
    62  	return lastConn.LastConnection.UTC(), nil
    63  }
    64  
    65  // UpdateLastModelConnection updates the last connection time of the model user.
    66  func (m *Model) UpdateLastModelConnection(user names.UserTag) error {
    67  	return m.updateLastModelConnection(user, m.st.nowToTheSecond())
    68  }
    69  
    70  func (m *Model) updateLastModelConnection(user names.UserTag, when time.Time) error {
    71  	lastConnections, closer := m.st.db().GetCollection(modelUserLastConnectionC)
    72  	defer closer()
    73  
    74  	lastConnectionsW := lastConnections.Writeable()
    75  
    76  	// Update the safe mode of the underlying session to not require
    77  	// write majority, nor sync to disk.
    78  	session := lastConnectionsW.Underlying().Database.Session
    79  	session.SetSafe(&mgo.Safe{})
    80  
    81  	lastConn := modelUserLastConnectionDoc{
    82  		ID:             m.st.docID(strings.ToLower(user.Id())),
    83  		ModelUUID:      m.UUID(),
    84  		UserName:       user.Id(),
    85  		LastConnection: when,
    86  	}
    87  	_, err := lastConnectionsW.UpsertId(lastConn.ID, lastConn)
    88  	return errors.Trace(err)
    89  }
    90  
    91  // ModelUser a model userAccessDoc.
    92  func (st *State) modelUser(modelUUID string, user names.UserTag) (userAccessDoc, error) {
    93  	modelUser := userAccessDoc{}
    94  	modelUsers, closer := st.db().GetCollectionFor(modelUUID, modelUsersC)
    95  	defer closer()
    96  
    97  	username := strings.ToLower(user.Id())
    98  	err := modelUsers.FindId(username).One(&modelUser)
    99  	if err == mgo.ErrNotFound {
   100  		return userAccessDoc{}, errors.NotFoundf("model user %q", username)
   101  	}
   102  	if err != nil {
   103  		return userAccessDoc{}, errors.Trace(err)
   104  	}
   105  	// DateCreated is inserted as UTC, but read out as local time. So we
   106  	// convert it back to UTC here.
   107  	modelUser.DateCreated = modelUser.DateCreated.UTC()
   108  	return modelUser, nil
   109  }
   110  
   111  func createModelUserOps(modelUUID string, user, createdBy names.UserTag, displayName string, dateCreated time.Time, access permission.Access) []txn.Op {
   112  	creatorname := createdBy.Id()
   113  	doc := &userAccessDoc{
   114  		ID:          userAccessID(user),
   115  		ObjectUUID:  modelUUID,
   116  		UserName:    user.Id(),
   117  		DisplayName: displayName,
   118  		CreatedBy:   creatorname,
   119  		DateCreated: dateCreated,
   120  	}
   121  
   122  	ops := []txn.Op{
   123  		createPermissionOp(modelKey(modelUUID), userGlobalKey(userAccessID(user)), access),
   124  		{
   125  			C:      modelUsersC,
   126  			Id:     userAccessID(user),
   127  			Assert: txn.DocMissing,
   128  			Insert: doc,
   129  		},
   130  	}
   131  	return ops
   132  }
   133  
   134  func removeModelUserOps(modelUUID string, user names.UserTag) []txn.Op {
   135  	return []txn.Op{
   136  		removePermissionOp(modelKey(modelUUID), userGlobalKey(userAccessID(user))),
   137  		{
   138  			C:      modelUsersC,
   139  			Id:     userAccessID(user),
   140  			Assert: txn.DocExists,
   141  			Remove: true,
   142  		}}
   143  }
   144  
   145  func removeModelUserOpsGlobal(modelUUID string, user names.UserTag) []txn.Op {
   146  	return []txn.Op{
   147  		removePermissionOp(modelKey(modelUUID), userGlobalKey(userAccessID(user))),
   148  		{
   149  			C:      modelUsersC,
   150  			Id:     ensureModelUUID(modelUUID, userAccessID(user)),
   151  			Assert: txn.DocExists,
   152  			Remove: true,
   153  		}}
   154  }
   155  
   156  // removeModelUser removes a user from the database.
   157  func (st *State) removeModelUser(user names.UserTag) error {
   158  	ops := removeModelUserOps(st.ModelUUID(), user)
   159  	err := st.db().RunTransaction(ops)
   160  	if err == txn.ErrAborted {
   161  		err = errors.NewNotFound(nil, fmt.Sprintf("model user %q does not exist", user.Id()))
   162  	}
   163  	if err != nil {
   164  		return errors.Trace(err)
   165  	}
   166  	return nil
   167  }
   168  
   169  func (st *State) ModelSummariesForUser(user names.UserTag, isSuperuser bool) ([]ModelSummary, error) {
   170  	modelQuery, closer, err := st.modelQueryForUser(user, isSuperuser)
   171  	if err != nil {
   172  		return nil, errors.Trace(err)
   173  	}
   174  	defer closer()
   175  	var modelDocs []modelDoc
   176  	if err := modelQuery.All(&modelDocs); err != nil {
   177  		return nil, errors.Trace(err)
   178  	}
   179  	p := newProcessorFromModelDocs(st, modelDocs, user, isSuperuser)
   180  	modelDocs = nil
   181  	if err := p.fillInFromConfig(); err != nil {
   182  		return nil, errors.Trace(err)
   183  	}
   184  	if err := p.fillInFromStatus(); err != nil {
   185  		return nil, errors.Trace(err)
   186  	}
   187  	if err := p.fillInStatusBasedOnCloudCredentialValidity(); err != nil {
   188  		return nil, errors.Trace(err)
   189  	}
   190  	if err := p.fillInJustUser(); err != nil {
   191  		return nil, errors.Trace(err)
   192  	}
   193  	if err := p.fillInLastAccess(); err != nil {
   194  		return nil, errors.Trace(err)
   195  	}
   196  	if err := p.fillInMachineSummary(); err != nil {
   197  		return nil, errors.Trace(err)
   198  	}
   199  	if err := p.fillInApplicationSummary(); err != nil {
   200  		return nil, errors.Trace(err)
   201  	}
   202  	if err := p.fillInMigration(); err != nil {
   203  		return nil, errors.Trace(err)
   204  	}
   205  	return p.summaries, nil
   206  }
   207  
   208  // modelsForUser gives you the information about all models that a user has access to.
   209  // This includes the name and UUID, as well as the last time the user connected to that model.
   210  func (st *State) modelQueryForUser(user names.UserTag, isSuperuser bool) (mongo.Query, SessionCloser, error) {
   211  	var modelQuery mongo.Query
   212  	models, closer := st.db().GetCollection(modelsC)
   213  	if isSuperuser {
   214  		// Fast path, we just return all the models that aren't Importing
   215  		modelQuery = models.Find(bson.M{"migration-mode": bson.M{"$ne": MigrationModeImporting}})
   216  	} else {
   217  		// Start by looking up model uuids that the user has access to, and then load only the records that are
   218  		// included in that set
   219  		var modelUUID struct {
   220  			UUID string `bson:"object-uuid"`
   221  		}
   222  		modelUsers, userCloser := st.db().GetRawCollection(modelUsersC)
   223  		defer userCloser()
   224  		query := modelUsers.Find(bson.D{{"user", user.Id()}})
   225  		query.Select(bson.M{"object-uuid": 1, "_id": 0})
   226  		query.Batch(100)
   227  		iter := query.Iter()
   228  		var modelUUIDs []string
   229  		for iter.Next(&modelUUID) {
   230  			modelUUIDs = append(modelUUIDs, modelUUID.UUID)
   231  		}
   232  		if err := iter.Close(); err != nil {
   233  			closer()
   234  			return nil, nil, errors.Trace(err)
   235  		}
   236  		modelQuery = models.Find(bson.M{
   237  			"_id":            bson.M{"$in": modelUUIDs},
   238  			"migration-mode": bson.M{"$ne": MigrationModeImporting},
   239  		})
   240  	}
   241  	modelQuery.Sort("name", "owner")
   242  	return modelQuery, closer, nil
   243  }
   244  
   245  type ModelAccessInfo struct {
   246  	Name           string    `bson:"name"`
   247  	UUID           string    `bson:"_id"`
   248  	Owner          string    `bson:"owner"`
   249  	Type           ModelType `bson:"type"`
   250  	LastConnection time.Time
   251  }
   252  
   253  // ModelBasicInfoForUser gives you the information about all models that a user has access to.
   254  // This includes the name and UUID, as well as the last time the user connected to that model.
   255  func (st *State) ModelBasicInfoForUser(user names.UserTag, isSuperuser bool) ([]ModelAccessInfo, error) {
   256  	modelQuery, closer1, err := st.modelQueryForUser(user, isSuperuser)
   257  	if err != nil {
   258  		return nil, errors.Trace(err)
   259  	}
   260  	defer closer1()
   261  	modelQuery.Select(bson.M{"_id": 1, "name": 1, "owner": 1, "type": 1})
   262  	var accessInfo []ModelAccessInfo
   263  	if err := modelQuery.All(&accessInfo); err != nil {
   264  		return nil, errors.Trace(err)
   265  	}
   266  	// Now we need to find the last-connection time for each model for this user
   267  	username := user.Id()
   268  	connDocIds := make([]string, len(accessInfo))
   269  	for i, acc := range accessInfo {
   270  		connDocIds[i] = acc.UUID + ":" + username
   271  	}
   272  	lastConnections, closer2 := st.db().GetRawCollection(modelUserLastConnectionC)
   273  	defer closer2()
   274  	query := lastConnections.Find(bson.M{"_id": bson.M{"$in": connDocIds}})
   275  	query.Select(bson.M{"last-connection": 1, "_id": 0, "model-uuid": 1})
   276  	query.Batch(100)
   277  	iter := query.Iter()
   278  	lastConns := make(map[string]time.Time, len(connDocIds))
   279  	var connInfo modelUserLastConnectionDoc
   280  	for iter.Next(&connInfo) {
   281  		lastConns[connInfo.ModelUUID] = connInfo.LastConnection
   282  	}
   283  	if err := iter.Close(); err != nil {
   284  		return nil, errors.Trace(err)
   285  	}
   286  	for i := range accessInfo {
   287  		uuid := accessInfo[i].UUID
   288  		accessInfo[i].LastConnection = lastConns[uuid]
   289  	}
   290  	return accessInfo, nil
   291  }
   292  
   293  // ModelUUIDsForUser returns a list of models that the user is able to
   294  // access.
   295  // Results are sorted by (name, owner).
   296  func (st *State) ModelUUIDsForUser(user names.UserTag) ([]string, error) {
   297  	// Consider the controller permissions overriding Model permission, for
   298  	// this case the only relevant one is superuser.
   299  	// The mgo query below wont work for superuser case because it needs at
   300  	// least one model user per model.
   301  	access, err := st.UserAccess(user, st.controllerTag)
   302  	if err != nil && !errors.IsNotFound(err) {
   303  		return nil, errors.Trace(err)
   304  	}
   305  
   306  	var modelUUIDs []string
   307  	if access.Access == permission.SuperuserAccess {
   308  		var err error
   309  		modelUUIDs, err = st.AllModelUUIDs()
   310  		if err != nil {
   311  			return nil, errors.Trace(err)
   312  		}
   313  	} else {
   314  		// Since there are no groups at this stage, the simplest way to get all
   315  		// the models that a particular user can see is to look through the
   316  		// model user collection. A raw collection is required to support
   317  		// queries across multiple models.
   318  		modelUsers, userCloser := st.db().GetRawCollection(modelUsersC)
   319  		defer userCloser()
   320  
   321  		var userSlice []userAccessDoc
   322  		err := modelUsers.Find(bson.D{{"user", user.Id()}}).Select(bson.D{{"object-uuid", 1}, {"_id", 1}}).All(&userSlice)
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  		for _, doc := range userSlice {
   327  			modelUUIDs = append(modelUUIDs, doc.ObjectUUID)
   328  		}
   329  	}
   330  
   331  	modelsColl, close := st.db().GetCollection(modelsC)
   332  	defer close()
   333  	query := modelsColl.Find(bson.M{
   334  		"_id":            bson.M{"$in": modelUUIDs},
   335  		"migration-mode": bson.M{"$ne": MigrationModeImporting},
   336  	}).Sort("name", "owner").Select(bson.M{"_id": 1})
   337  
   338  	var docs []bson.M
   339  	err = query.All(&docs)
   340  	if err != nil {
   341  		return nil, errors.Trace(err)
   342  	}
   343  	out := make([]string, len(docs))
   344  	for i, doc := range docs {
   345  		out[i] = doc["_id"].(string)
   346  	}
   347  	return out, nil
   348  }
   349  
   350  // IsControllerAdmin returns true if the user specified has Super User Access.
   351  func (st *State) IsControllerAdmin(user names.UserTag) (bool, error) {
   352  	model, err := st.Model()
   353  	if err != nil {
   354  		return false, errors.Trace(err)
   355  	}
   356  	ua, err := st.UserAccess(user, model.ControllerTag())
   357  	if errors.IsNotFound(err) {
   358  		return false, nil
   359  	}
   360  	if err != nil {
   361  		return false, errors.Trace(err)
   362  	}
   363  	return ua.Access == permission.SuperuserAccess, nil
   364  }
   365  
   366  func (st *State) isControllerOrModelAdmin(user names.UserTag) (bool, error) {
   367  	isAdmin, err := st.IsControllerAdmin(user)
   368  	if err != nil {
   369  		return false, errors.Trace(err)
   370  	}
   371  	if isAdmin {
   372  		return true, nil
   373  	}
   374  	ua, err := st.UserAccess(user, names.NewModelTag(st.ModelUUID()))
   375  	if errors.IsNotFound(err) {
   376  		return false, nil
   377  	}
   378  	if err != nil {
   379  		return false, errors.Trace(err)
   380  	}
   381  	return ua.Access == permission.AdminAccess, nil
   382  }