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 }