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  }