github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/applicationoffers/access_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package applicationoffers_test
     5  
     6  import (
     7  	"regexp"
     8  
     9  	"github.com/juju/errors"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/apiserver/common"
    15  	"github.com/juju/juju/apiserver/common/crossmodel"
    16  	"github.com/juju/juju/apiserver/facades/client/applicationoffers"
    17  	"github.com/juju/juju/apiserver/params"
    18  	jujucrossmodel "github.com/juju/juju/core/crossmodel"
    19  	"github.com/juju/juju/environs/context"
    20  	"github.com/juju/juju/permission"
    21  	"github.com/juju/juju/state"
    22  )
    23  
    24  type offerAccessSuite struct {
    25  	baseSuite
    26  	api *applicationoffers.OffersAPIV2
    27  }
    28  
    29  var _ = gc.Suite(&offerAccessSuite{})
    30  
    31  func (s *offerAccessSuite) SetUpTest(c *gc.C) {
    32  	s.baseSuite.SetUpTest(c)
    33  	s.authorizer.Tag = names.NewUserTag("admin")
    34  	getApplicationOffers := func(interface{}) jujucrossmodel.ApplicationOffers {
    35  		return &stubApplicationOffers{}
    36  	}
    37  
    38  	resources := common.NewResources()
    39  	resources.RegisterNamed("dataDir", common.StringResource(c.MkDir()))
    40  	var err error
    41  	s.authContext, err = crossmodel.NewAuthContext(&mockCommonStatePool{s.mockStatePool}, s.bakery, s.bakery)
    42  	c.Assert(err, jc.ErrorIsNil)
    43  	apiV1, err := applicationoffers.CreateOffersAPI(
    44  		getApplicationOffers, nil, getFakeControllerInfo,
    45  		s.mockState, s.mockStatePool, s.authorizer, resources, s.authContext,
    46  		context.NewCloudCallContext(),
    47  	)
    48  	c.Assert(err, jc.ErrorIsNil)
    49  	s.api = &applicationoffers.OffersAPIV2{OffersAPI: apiV1}
    50  }
    51  
    52  func (s *offerAccessSuite) modifyAccess(
    53  	c *gc.C, user names.UserTag,
    54  	action params.OfferAction,
    55  	access params.OfferAccessPermission,
    56  	offerURL string,
    57  ) error {
    58  	args := params.ModifyOfferAccessRequest{
    59  		Changes: []params.ModifyOfferAccess{{
    60  			UserTag:  user.String(),
    61  			Action:   action,
    62  			Access:   access,
    63  			OfferURL: offerURL,
    64  		}}}
    65  
    66  	result, err := s.api.ModifyOfferAccess(args)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	return result.OneError()
    71  }
    72  
    73  func (s *offerAccessSuite) grant(c *gc.C, user names.UserTag, access params.OfferAccessPermission, offerURL string) error {
    74  	return s.modifyAccess(c, user, params.GrantOfferAccess, access, offerURL)
    75  }
    76  
    77  func (s *offerAccessSuite) revoke(c *gc.C, user names.UserTag, access params.OfferAccessPermission, offerURL string) error {
    78  	return s.modifyAccess(c, user, params.RevokeOfferAccess, access, offerURL)
    79  }
    80  
    81  func (s *offerAccessSuite) setupOffer(modelUUID, modelName, owner, offerName string) {
    82  	model := &mockModel{uuid: modelUUID, name: modelName, owner: owner, modelType: state.ModelTypeIAAS}
    83  	s.mockState.allmodels = []applicationoffers.Model{model}
    84  	st := &mockState{
    85  		modelUUID:         modelUUID,
    86  		applicationOffers: make(map[string]jujucrossmodel.ApplicationOffer),
    87  		users:             make(map[string]applicationoffers.User),
    88  		accessPerms:       make(map[offerAccess]permission.Access),
    89  		model:             model,
    90  	}
    91  	s.mockStatePool.st[modelUUID] = st
    92  	st.applicationOffers[offerName] = jujucrossmodel.ApplicationOffer{OfferUUID: offerName + "-uuid"}
    93  }
    94  
    95  func (s *offerAccessSuite) TestGrantMissingUserFails(c *gc.C) {
    96  	s.setupOffer("uuid", "test", "admin", "someoffer")
    97  	user := names.NewUserTag("foobar")
    98  	err := s.grant(c, user, params.OfferReadAccess, "test.someoffer")
    99  	expectedErr := `could not grant offer access: user "foobar" not found`
   100  	c.Assert(err, gc.ErrorMatches, expectedErr)
   101  }
   102  
   103  func (s *offerAccessSuite) TestGrantMissingOfferFails(c *gc.C) {
   104  	s.setupOffer("uuid", "test", "admin", "differentoffer")
   105  	user := names.NewUserTag("foobar")
   106  	err := s.grant(c, user, params.OfferReadAccess, "test.someoffer")
   107  	expectedErr := `.*application offer "someoffer" not found`
   108  	c.Assert(err, gc.ErrorMatches, expectedErr)
   109  }
   110  
   111  func (s *offerAccessSuite) TestRevokeAdminLeavesReadAccess(c *gc.C) {
   112  	s.setupOffer("uuid", "test", "admin", "someoffer")
   113  	st := s.mockStatePool.st["uuid"]
   114  	st.(*mockState).users["foobar"] = &mockUser{"foobar"}
   115  
   116  	user := names.NewUserTag("foobar")
   117  	offer := names.NewApplicationOfferTag("someoffer")
   118  	err := st.CreateOfferAccess(offer, user, permission.ConsumeAccess)
   119  	c.Assert(err, jc.ErrorIsNil)
   120  
   121  	err = s.revoke(c, user, params.OfferConsumeAccess, "test.someoffer")
   122  	c.Assert(err, jc.ErrorIsNil)
   123  
   124  	access, err := st.GetOfferAccess(offer.Id()+"-uuid", user)
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	c.Assert(access, gc.Equals, permission.ReadAccess)
   127  }
   128  
   129  func (s *offerAccessSuite) TestRevokeReadRemovesPermission(c *gc.C) {
   130  	s.setupOffer("uuid", "test", "admin", "someoffer")
   131  	st := s.mockStatePool.st["uuid"]
   132  	st.(*mockState).users["foobar"] = &mockUser{"foobar"}
   133  
   134  	user := names.NewUserTag("foobar")
   135  	offer := names.NewApplicationOfferTag("someoffer")
   136  	err := st.CreateOfferAccess(offer, user, permission.ConsumeAccess)
   137  	c.Assert(err, jc.ErrorIsNil)
   138  
   139  	err = s.revoke(c, user, params.OfferReadAccess, "test.someoffer")
   140  	c.Assert(err, gc.IsNil)
   141  
   142  	_, err = st.GetOfferAccess(offer.Id()+"-uuid", user)
   143  	c.Assert(errors.IsNotFound(err), jc.IsTrue)
   144  }
   145  
   146  func (s *offerAccessSuite) TestRevokeMissingUser(c *gc.C) {
   147  	s.setupOffer("uuid", "test", "admin", "someoffer")
   148  	st := s.mockStatePool.st["uuid"]
   149  
   150  	user := names.NewUserTag("bob")
   151  	err := s.revoke(c, user, params.OfferReadAccess, "test.someoffer")
   152  	c.Assert(err, gc.ErrorMatches, `could not revoke offer access: offer user "bob" does not exist`)
   153  
   154  	offer := names.NewApplicationOfferTag("someoffer")
   155  	_, err = st.GetOfferAccess(offer.Id()+"-uuid", user)
   156  	c.Assert(errors.IsNotFound(err), jc.IsTrue)
   157  }
   158  
   159  func (s *offerAccessSuite) TestGrantOnlyGreaterAccess(c *gc.C) {
   160  	s.setupOffer("uuid", "test", "admin", "someoffer")
   161  	st := s.mockStatePool.st["uuid"]
   162  	st.(*mockState).users["foobar"] = &mockUser{"foobar"}
   163  
   164  	user := names.NewUserTag("foobar")
   165  	err := s.grant(c, user, params.OfferReadAccess, "test.someoffer")
   166  	c.Assert(err, jc.ErrorIsNil)
   167  
   168  	err = s.grant(c, user, params.OfferReadAccess, "test.someoffer")
   169  	c.Assert(err, gc.ErrorMatches, `user already has "read" access or greater`)
   170  }
   171  
   172  func (s *offerAccessSuite) assertGrantOfferAddUser(c *gc.C, user names.UserTag) {
   173  	s.setupOffer("uuid", "test", "superuser-bob", "someoffer")
   174  	st := s.mockStatePool.st["uuid"]
   175  	st.(*mockState).users["other"] = &mockUser{"other"}
   176  	st.(*mockState).users[user.Name()] = &mockUser{user.Name()}
   177  
   178  	apiUser := names.NewUserTag("superuser-bob")
   179  	s.authorizer.Tag = apiUser
   180  
   181  	err := s.grant(c, user, params.OfferReadAccess, "superuser-bob/test.someoffer")
   182  	c.Assert(err, jc.ErrorIsNil)
   183  
   184  	offer := names.NewApplicationOfferTag("someoffer")
   185  	access, err := st.GetOfferAccess(offer.Id()+"-uuid", user)
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	c.Assert(access, gc.Equals, permission.ReadAccess)
   188  }
   189  
   190  func (s *offerAccessSuite) TestGrantOfferAddLocalUser(c *gc.C) {
   191  	s.assertGrantOfferAddUser(c, names.NewLocalUserTag("bob"))
   192  }
   193  
   194  func (s *offerAccessSuite) TestGrantOfferAddRemoteUser(c *gc.C) {
   195  	s.assertGrantOfferAddUser(c, names.NewUserTag("bob@remote"))
   196  }
   197  
   198  func (s *offerAccessSuite) TestGrantOfferSuperUser(c *gc.C) {
   199  	s.setupOffer("uuid", "test", "superuser-bob", "someoffer")
   200  	st := s.mockStatePool.st["uuid"]
   201  	st.(*mockState).users["other"] = &mockUser{"other"}
   202  
   203  	user := names.NewUserTag("superuser-bob")
   204  	s.authorizer.Tag = user
   205  
   206  	other := names.NewUserTag("other")
   207  	err := s.grant(c, other, params.OfferReadAccess, "superuser-bob/test.someoffer")
   208  	c.Assert(err, jc.ErrorIsNil)
   209  
   210  	offer := names.NewApplicationOfferTag("someoffer")
   211  	access, err := st.GetOfferAccess(offer.Id()+"-uuid", other)
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	c.Assert(access, gc.Equals, permission.ReadAccess)
   214  }
   215  
   216  func (s *offerAccessSuite) TestGrantIncreaseAccess(c *gc.C) {
   217  	s.setupOffer("uuid", "test", "other", "someoffer")
   218  	st := s.mockStatePool.st["uuid"]
   219  	st.(*mockState).users["other"] = &mockUser{"other"}
   220  
   221  	user := names.NewUserTag("other")
   222  	s.authorizer.Tag = user
   223  	s.authorizer.AdminTag = user
   224  
   225  	offer := names.NewApplicationOfferTag("someoffer")
   226  	err := st.CreateOfferAccess(offer, user, permission.ReadAccess)
   227  	c.Assert(err, jc.ErrorIsNil)
   228  
   229  	err = s.grant(c, user, params.OfferConsumeAccess, "other/test.someoffer")
   230  	c.Assert(err, jc.ErrorIsNil)
   231  
   232  	access, err := st.GetOfferAccess(offer.Id()+"-uuid", user)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	c.Assert(access, gc.Equals, permission.ConsumeAccess)
   235  }
   236  
   237  func (s *offerAccessSuite) TestGrantToOfferNoAccess(c *gc.C) {
   238  	s.setupOffer("uuid", "test", "bob@remote", "someoffer")
   239  	st := s.mockStatePool.st["uuid"]
   240  	st.(*mockState).users["other"] = &mockUser{"other"}
   241  	st.(*mockState).users["bob"] = &mockUser{"bob"}
   242  
   243  	user := names.NewUserTag("bob@remote")
   244  	s.authorizer.Tag = user
   245  
   246  	other := names.NewUserTag("other@remote")
   247  	err := s.grant(c, other, params.OfferReadAccess, "bob@remote/test.someoffer")
   248  	c.Assert(err, gc.ErrorMatches, "permission denied")
   249  }
   250  
   251  func (s *offerAccessSuite) assertGrantToOffer(c *gc.C, userAccess permission.Access) {
   252  	s.setupOffer("uuid", "test", "bob@remote", "someoffer")
   253  	st := s.mockStatePool.st["uuid"]
   254  	st.(*mockState).users["other"] = &mockUser{"other"}
   255  	st.(*mockState).users["bob"] = &mockUser{"bob"}
   256  
   257  	user := names.NewUserTag("bob@remote")
   258  	s.authorizer.Tag = user
   259  
   260  	offer := names.NewApplicationOfferTag("someoffer")
   261  	err := st.CreateOfferAccess(offer, user, userAccess)
   262  	c.Assert(err, jc.ErrorIsNil)
   263  
   264  	other := names.NewUserTag("other@remote")
   265  	err = s.grant(c, other, params.OfferReadAccess, "bob@remote/test.someoffer")
   266  	c.Assert(err, gc.ErrorMatches, "permission denied")
   267  }
   268  
   269  func (s *offerAccessSuite) TestGrantToOfferReadAccess(c *gc.C) {
   270  	s.assertGrantToOffer(c, permission.ReadAccess)
   271  }
   272  
   273  func (s *offerAccessSuite) TestGrantToOfferConsumeAccess(c *gc.C) {
   274  	s.assertGrantToOffer(c, permission.ConsumeAccess)
   275  }
   276  
   277  func (s *offerAccessSuite) TestGrantToOfferAdminAccess(c *gc.C) {
   278  	s.setupOffer("uuid", "test", "foobar", "someoffer")
   279  	st := s.mockStatePool.st["uuid"]
   280  	st.(*mockState).users["other"] = &mockUser{"other"}
   281  	st.(*mockState).users["foobar"] = &mockUser{"foobar"}
   282  
   283  	user := names.NewUserTag("foobar")
   284  	s.authorizer.Tag = user
   285  	s.authorizer.AdminTag = user
   286  	offer := names.NewApplicationOfferTag("someoffer")
   287  	err := st.CreateOfferAccess(offer, user, permission.AdminAccess)
   288  	c.Assert(err, jc.ErrorIsNil)
   289  
   290  	other := names.NewUserTag("other")
   291  	err = s.grant(c, other, params.OfferReadAccess, "foobar/test.someoffer")
   292  	c.Assert(err, jc.ErrorIsNil)
   293  
   294  	access, err := st.GetOfferAccess(offer.Id()+"-uuid", other)
   295  	c.Assert(err, jc.ErrorIsNil)
   296  	c.Assert(access, gc.Equals, permission.ReadAccess)
   297  }
   298  
   299  func (s *offerAccessSuite) TestGrantOfferInvalidUserTag(c *gc.C) {
   300  	s.setupOffer("uuid", "test", "admin", "someoffer")
   301  	for _, testParam := range []struct {
   302  		tag      string
   303  		validTag bool
   304  	}{{
   305  		tag:      "unit-foo/0",
   306  		validTag: true,
   307  	}, {
   308  		tag:      "application-foo",
   309  		validTag: true,
   310  	}, {
   311  		tag:      "relation-wordpress:db mysql:db",
   312  		validTag: true,
   313  	}, {
   314  		tag:      "machine-0",
   315  		validTag: true,
   316  	}, {
   317  		tag:      "user",
   318  		validTag: false,
   319  	}, {
   320  		tag:      "user-Mua^h^h^h^arh",
   321  		validTag: true,
   322  	}, {
   323  		tag:      "user@",
   324  		validTag: false,
   325  	}, {
   326  		tag:      "user@ubuntuone",
   327  		validTag: false,
   328  	}, {
   329  		tag:      "user@ubuntuone",
   330  		validTag: false,
   331  	}, {
   332  		tag:      "@ubuntuone",
   333  		validTag: false,
   334  	}, {
   335  		tag:      "in^valid.",
   336  		validTag: false,
   337  	}, {
   338  		tag:      "",
   339  		validTag: false,
   340  	},
   341  	} {
   342  		var expectedErr string
   343  		errPart := `could not modify offer access: "` + regexp.QuoteMeta(testParam.tag) + `" is not a valid `
   344  
   345  		if testParam.validTag {
   346  			// The string is a valid tag, but not a user tag.
   347  			expectedErr = errPart + `user tag`
   348  		} else {
   349  			// The string is not a valid tag of any kind.
   350  			expectedErr = errPart + `tag`
   351  		}
   352  
   353  		args := params.ModifyOfferAccessRequest{
   354  			Changes: []params.ModifyOfferAccess{{
   355  				UserTag:  testParam.tag,
   356  				Action:   params.GrantOfferAccess,
   357  				Access:   params.OfferReadAccess,
   358  				OfferURL: "test.someoffer",
   359  			}}}
   360  
   361  		result, err := s.api.ModifyOfferAccess(args)
   362  		c.Assert(err, jc.ErrorIsNil)
   363  		c.Assert(result.OneError(), gc.ErrorMatches, expectedErr)
   364  	}
   365  }
   366  
   367  func (s *offerAccessSuite) TestModifyOfferAccessEmptyArgs(c *gc.C) {
   368  	s.setupOffer("uuid", "test", "admin", "someoffer")
   369  	args := params.ModifyOfferAccessRequest{
   370  		Changes: []params.ModifyOfferAccess{{OfferURL: "test.someoffer"}}}
   371  
   372  	result, err := s.api.ModifyOfferAccess(args)
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	expectedErr := `could not modify offer access: "" offer access not valid`
   375  	c.Assert(result.OneError(), gc.ErrorMatches, expectedErr)
   376  }
   377  
   378  func (s *offerAccessSuite) TestModifyOfferAccessInvalidAction(c *gc.C) {
   379  	s.setupOffer("uuid", "test", "admin", "someoffer")
   380  
   381  	var dance params.OfferAction = "dance"
   382  	args := params.ModifyOfferAccessRequest{
   383  		Changes: []params.ModifyOfferAccess{{
   384  			UserTag:  "user-user",
   385  			Action:   dance,
   386  			Access:   params.OfferReadAccess,
   387  			OfferURL: "test.someoffer",
   388  		}}}
   389  
   390  	result, err := s.api.ModifyOfferAccess(args)
   391  	c.Assert(err, jc.ErrorIsNil)
   392  	expectedErr := `unknown action "dance"`
   393  	c.Assert(result.OneError(), gc.ErrorMatches, expectedErr)
   394  }