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 }