github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/applicationofferuser.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/errors" 9 "github.com/juju/mgo/v3/bson" 10 "github.com/juju/mgo/v3/txn" 11 "github.com/juju/names/v5" 12 13 "github.com/juju/juju/core/permission" 14 ) 15 16 // GetOfferAccess gets the access permission for the specified user on an offer. 17 func (st *State) GetOfferAccess(offerUUID string, user names.UserTag) (permission.Access, error) { 18 perm, err := st.userPermission(applicationOfferKey(offerUUID), userGlobalKey(userAccessID(user))) 19 if err != nil { 20 return "", errors.Trace(err) 21 } 22 return perm.access(), nil 23 } 24 25 // GetOfferUsers gets the access permissions on an offer. 26 func (st *State) GetOfferUsers(offerUUID string) (map[string]permission.Access, error) { 27 perms, err := st.usersPermissions(applicationOfferKey(offerUUID)) 28 if err != nil { 29 return nil, errors.Trace(err) 30 } 31 result := make(map[string]permission.Access) 32 for _, p := range perms { 33 result[userIDFromGlobalKey(p.doc.SubjectGlobalKey)] = p.access() 34 } 35 return result, nil 36 } 37 38 // CreateOfferAccess creates a new access permission for a user on an offer. 39 func (st *State) CreateOfferAccess(offer names.ApplicationOfferTag, user names.UserTag, access permission.Access) error { 40 if err := permission.ValidateOfferAccess(access); err != nil { 41 return errors.Trace(err) 42 } 43 44 // Local users must exist. 45 if user.IsLocal() { 46 _, err := st.User(user) 47 if err != nil { 48 if errors.IsNotFound(err) { 49 return errors.Annotatef(err, "user %q does not exist locally", user.Name()) 50 } 51 return errors.Trace(err) 52 } 53 } 54 55 offerUUID := offer.Id() 56 op := createPermissionOp(applicationOfferKey(offerUUID), userGlobalKey(userAccessID(user)), access) 57 58 err := st.db().RunTransaction([]txn.Op{op}) 59 if err == txn.ErrAborted { 60 err = errors.AlreadyExistsf("permission for user %q for offer %q", user.Id(), offer.Name) 61 } 62 return errors.Trace(err) 63 } 64 65 // UpdateOfferAccess changes the user's access permissions on an offer. 66 func (st *State) UpdateOfferAccess(offer names.ApplicationOfferTag, user names.UserTag, access permission.Access) error { 67 if err := permission.ValidateOfferAccess(access); err != nil { 68 return errors.Trace(err) 69 } 70 offerUUID := offer.Id() 71 72 buildTxn := func(int) ([]txn.Op, error) { 73 _, err := st.GetOfferAccess(offerUUID, user) 74 if err != nil { 75 return nil, errors.Trace(err) 76 } 77 isAdmin, err := st.isControllerOrModelAdmin(user) 78 if err != nil { 79 return nil, errors.Trace(err) 80 } 81 ops := []txn.Op{updatePermissionOp(applicationOfferKey(offerUUID), userGlobalKey(userAccessID(user)), access)} 82 if !isAdmin && access != permission.ConsumeAccess && access != permission.AdminAccess { 83 suspendOps, err := st.suspendRevokedRelationsOps(offerUUID, user.Id()) 84 if err != nil { 85 return nil, errors.Trace(err) 86 } 87 ops = append(ops, suspendOps...) 88 } 89 return ops, nil 90 } 91 92 err := st.db().Run(buildTxn) 93 return errors.Trace(err) 94 } 95 96 // suspendRevokedRelationsOps suspends any relations the given user has against 97 // the specified offer. 98 func (st *State) suspendRevokedRelationsOps(offerUUID, userId string) ([]txn.Op, error) { 99 conns, err := st.OfferConnections(offerUUID) 100 if err != nil { 101 return nil, errors.Trace(err) 102 } 103 104 var ops []txn.Op 105 relIdsToSuspend := make(set.Ints) 106 for _, oc := range conns { 107 if oc.UserName() == userId { 108 rel, err := st.Relation(oc.RelationId()) 109 if err != nil { 110 return nil, errors.Trace(err) 111 } 112 if rel.Suspended() { 113 continue 114 } 115 relIdsToSuspend.Add(rel.Id()) 116 suspendOp := txn.Op{ 117 C: relationsC, 118 Id: rel.doc.DocID, 119 Assert: txn.DocExists, 120 Update: bson.D{{"$set", bson.D{{"suspended", true}}}}, 121 } 122 ops = append(ops, suspendOp) 123 } 124 } 125 126 // Add asserts that the relations against the offered application don't change. 127 // This is broader than what we need but it's all that's possible. 128 ao := NewApplicationOffers(st) 129 offer, err := ao.ApplicationOfferForUUID(offerUUID) 130 if err != nil { 131 return nil, errors.Trace(err) 132 } 133 app, err := st.Application(offer.ApplicationName) 134 if err != nil { 135 return nil, errors.Trace(err) 136 } 137 relations, err := app.Relations() 138 if err != nil { 139 return nil, errors.Trace(err) 140 } 141 sameRelCount := bson.D{{"relationcount", len(relations)}} 142 ops = append(ops, txn.Op{ 143 C: applicationsC, 144 Id: app.doc.DocID, 145 Assert: sameRelCount, 146 }) 147 // Ensure any relations not being updated still exist. 148 for _, r := range relations { 149 if relIdsToSuspend.Contains(r.Id()) { 150 continue 151 } 152 ops = append(ops, txn.Op{ 153 C: relationsC, 154 Id: r.doc.DocID, 155 Assert: txn.DocExists, 156 }) 157 } 158 159 return ops, nil 160 } 161 162 // RemoveOfferAccess removes the access permission for a user on an offer. 163 func (st *State) RemoveOfferAccess(offer names.ApplicationOfferTag, user names.UserTag) error { 164 buildTxn := func(int) ([]txn.Op, error) { 165 offerUUID := offer.Id() 166 _, err := st.GetOfferAccess(offerUUID, user) 167 if err != nil { 168 return nil, err 169 } 170 isAdmin, err := st.isControllerOrModelAdmin(user) 171 if err != nil { 172 return nil, errors.Trace(err) 173 } 174 ops := []txn.Op{removePermissionOp(applicationOfferKey(offerUUID), userGlobalKey(userAccessID(user)))} 175 if !isAdmin { 176 suspendOps, err := st.suspendRevokedRelationsOps(offerUUID, user.Id()) 177 if err != nil { 178 return nil, errors.Trace(err) 179 } 180 ops = append(ops, suspendOps...) 181 } 182 return ops, nil 183 } 184 185 err := st.db().Run(buildTxn) 186 return errors.Trace(err) 187 }