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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/mgo/v3/bson"
    12  	"github.com/juju/mgo/v3/txn"
    13  	"github.com/juju/names/v5"
    14  
    15  	"github.com/juju/juju/cloud"
    16  	"github.com/juju/juju/core/permission"
    17  	"github.com/juju/juju/mongo"
    18  )
    19  
    20  // CreateCloudAccess creates a new access permission for a user on a cloud.
    21  func (st *State) CreateCloudAccess(cloud string, user names.UserTag, access permission.Access) error {
    22  	if err := permission.ValidateCloudAccess(access); err != nil {
    23  		return errors.Trace(err)
    24  	}
    25  
    26  	// Local users must exist.
    27  	if user.IsLocal() {
    28  		_, err := st.User(user)
    29  		if err != nil {
    30  			if errors.IsNotFound(err) {
    31  				return errors.Annotatef(err, "user %q does not exist locally", user.Name())
    32  			}
    33  			return errors.Trace(err)
    34  		}
    35  	}
    36  
    37  	op := createPermissionOp(cloudGlobalKey(cloud), userGlobalKey(userAccessID(user)), access)
    38  
    39  	err := st.db().RunTransaction([]txn.Op{op})
    40  	if err == txn.ErrAborted {
    41  		err = errors.AlreadyExistsf("permission for user %q for cloud %q", user.Id(), cloud)
    42  	}
    43  	return errors.Trace(err)
    44  }
    45  
    46  // GetCloudAccess gets the access permission for the specified user on a cloud.
    47  func (st *State) GetCloudAccess(cloud string, user names.UserTag) (permission.Access, error) {
    48  	perm, err := st.userPermission(cloudGlobalKey(cloud), userGlobalKey(userAccessID(user)))
    49  	if err != nil {
    50  		return "", errors.Trace(err)
    51  	}
    52  	return perm.access(), nil
    53  }
    54  
    55  // GetCloudUsers gets the access permissions on a cloud.
    56  func (st *State) GetCloudUsers(cloud string) (map[string]permission.Access, error) {
    57  	perms, err := st.usersPermissions(cloudGlobalKey(cloud))
    58  	if err != nil {
    59  		return nil, errors.Trace(err)
    60  	}
    61  	result := make(map[string]permission.Access)
    62  	for _, p := range perms {
    63  		result[userIDFromGlobalKey(p.doc.SubjectGlobalKey)] = p.access()
    64  	}
    65  	return result, nil
    66  }
    67  
    68  // UpdateCloudAccess changes the user's access permissions on a cloud.
    69  func (st *State) UpdateCloudAccess(cloud string, user names.UserTag, access permission.Access) error {
    70  	if err := permission.ValidateCloudAccess(access); err != nil {
    71  		return errors.Trace(err)
    72  	}
    73  
    74  	buildTxn := func(int) ([]txn.Op, error) {
    75  		_, err := st.GetCloudAccess(cloud, user)
    76  		if err != nil {
    77  			return nil, errors.Trace(err)
    78  		}
    79  		ops := []txn.Op{updatePermissionOp(cloudGlobalKey(cloud), userGlobalKey(userAccessID(user)), access)}
    80  		return ops, nil
    81  	}
    82  
    83  	err := st.db().Run(buildTxn)
    84  	return errors.Trace(err)
    85  }
    86  
    87  // RemoveCloudAccess removes the access permission for a user on a cloud.
    88  func (st *State) RemoveCloudAccess(cloud string, user names.UserTag) error {
    89  	buildTxn := func(int) ([]txn.Op, error) {
    90  		_, err := st.GetCloudAccess(cloud, user)
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  		ops := []txn.Op{removePermissionOp(cloudGlobalKey(cloud), userGlobalKey(userAccessID(user)))}
    95  		return ops, nil
    96  	}
    97  
    98  	err := st.db().Run(buildTxn)
    99  	return errors.Trace(err)
   100  }
   101  
   102  // CloudInfo describes interesting information for a given cloud.
   103  type CloudInfo struct {
   104  	cloud.Cloud
   105  
   106  	// Access is the access level the supplied user has on this cloud.
   107  	Access permission.Access
   108  }
   109  
   110  // CloudsForUser returns details including access level of clouds which can
   111  // be seen by the specified user, or all users if the caller is a superuser.
   112  func (st *State) CloudsForUser(user names.UserTag, isSuperuser bool) ([]CloudInfo, error) {
   113  	ci, err := st.ControllerInfo()
   114  	if err != nil {
   115  		return nil, errors.Trace(err)
   116  	}
   117  
   118  	clouds, closer := st.db().GetCollection(cloudsC)
   119  	defer closer()
   120  
   121  	var cloudQuery mongo.Query
   122  	if isSuperuser {
   123  		// Fast path, we just get all the clouds.
   124  		cloudQuery = clouds.Find(nil)
   125  	} else {
   126  		cloudNames, err := st.cloudNamesForUser(user)
   127  		if err != nil {
   128  			return nil, errors.Trace(err)
   129  		}
   130  		cloudQuery = clouds.Find(bson.M{
   131  			"_id": bson.M{"$in": cloudNames},
   132  		})
   133  	}
   134  	cloudQuery = cloudQuery.Sort("name")
   135  
   136  	var cloudDocs []cloudDoc
   137  	if err := cloudQuery.All(&cloudDocs); err != nil {
   138  		return nil, errors.Trace(err)
   139  	}
   140  	result := make([]CloudInfo, len(cloudDocs))
   141  	for i, c := range cloudDocs {
   142  		result[i] = CloudInfo{
   143  			Cloud: c.toCloud(ci.CloudName),
   144  		}
   145  	}
   146  	if err := st.fillInCloudUserAccess(user, result); err != nil {
   147  		return nil, errors.Trace(err)
   148  	}
   149  	return result, nil
   150  }
   151  
   152  // cloudNamesForUser returns the cloud names a user can see.
   153  func (st *State) cloudNamesForUser(user names.UserTag) ([]string, error) {
   154  	// Start by looking up cloud names that the user has access to, and then load only the records that are
   155  	// included in that set
   156  	permissions, permCloser := st.db().GetRawCollection(permissionsC)
   157  	defer permCloser()
   158  
   159  	findExpr := fmt.Sprintf("^.*#%s$", userGlobalKey(user.Id()))
   160  	query := permissions.Find(
   161  		bson.D{{"_id", bson.D{{"$regex", findExpr}}}},
   162  	).Batch(100)
   163  
   164  	var doc permissionDoc
   165  	iter := query.Iter()
   166  	var cloudNames []string
   167  	for iter.Next(&doc) {
   168  		cloudName := strings.TrimPrefix(doc.ObjectGlobalKey, "cloud#")
   169  		cloudNames = append(cloudNames, cloudName)
   170  	}
   171  	if err := iter.Close(); err != nil {
   172  		return nil, errors.Trace(err)
   173  	}
   174  	return cloudNames, nil
   175  }
   176  
   177  // fillInCloudUserAccess fills in the Access rights for this user on the clouds (but not other users).
   178  func (st *State) fillInCloudUserAccess(user names.UserTag, cloudInfo []CloudInfo) error {
   179  	// Note: Even for Superuser we track the individual Access for each model.
   180  	username := strings.ToLower(user.Name())
   181  	var permissionIds []string
   182  	for _, info := range cloudInfo {
   183  		permId := permissionID(cloudGlobalKey(info.Name), userGlobalKey(username))
   184  		permissionIds = append(permissionIds, permId)
   185  	}
   186  
   187  	// Record index by name so we can fill access details below.
   188  	indexByName := make(map[string]int, len(cloudInfo))
   189  	for i, info := range cloudInfo {
   190  		indexByName[info.Name] = i
   191  	}
   192  
   193  	perms, closer := st.db().GetCollection(permissionsC)
   194  	defer closer()
   195  	query := perms.Find(bson.M{"_id": bson.M{"$in": permissionIds}}).Batch(100)
   196  	iter := query.Iter()
   197  
   198  	var doc permissionDoc
   199  	for iter.Next(&doc) {
   200  		cloudName := strings.TrimPrefix(doc.ObjectGlobalKey, "cloud#")
   201  		cloudIdx := indexByName[cloudName]
   202  
   203  		details := &cloudInfo[cloudIdx]
   204  		access := permission.Access(doc.Access)
   205  		if err := access.Validate(); err == nil {
   206  			details.Access = access
   207  		}
   208  	}
   209  	return iter.Close()
   210  }