github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/client/perm_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package client_test 5 6 import ( 7 "strings" 8 9 "github.com/juju/errors" 10 "github.com/juju/names" 11 jc "github.com/juju/testing/checkers" 12 "github.com/juju/version" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/charm.v6-unstable" 15 16 "github.com/juju/juju/api" 17 "github.com/juju/juju/api/annotations" 18 "github.com/juju/juju/api/service" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/charmstore" 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/rpc" 23 "github.com/juju/juju/state" 24 jujuversion "github.com/juju/juju/version" 25 ) 26 27 type permSuite struct { 28 baseSuite 29 } 30 31 var _ = gc.Suite(&permSuite{}) 32 33 // Most (if not all) of the permission tests below aim to test 34 // end-to-end operations execution through the API, but do not care 35 // about the results. They only test that a call is succeeds or fails 36 // (usually due to "permission denied"). There are separate test cases 37 // testing each individual API call data flow later on. 38 39 // allowed returns the set of allowed entities given an allow list and a 40 // deny list. If an allow list is specified, only those entities are 41 // allowed; otherwise those in deny are disallowed. 42 func allowed(all, allow, deny []names.Tag) map[names.Tag]bool { 43 p := make(map[names.Tag]bool) 44 if allow != nil { 45 for _, e := range allow { 46 p[e] = true 47 } 48 return p 49 } 50 loop: 51 for _, e0 := range all { 52 for _, e1 := range deny { 53 if e1 == e0 { 54 continue loop 55 } 56 } 57 p[e0] = true 58 } 59 return p 60 } 61 62 func (s *permSuite) TestOperationPerm(c *gc.C) { 63 var ( 64 userAdmin = s.AdminUserTag(c) 65 userOther = names.NewLocalUserTag("other") 66 ) 67 entities := s.setUpScenario(c) 68 for i, t := range []struct { 69 about string 70 // op performs the operation to be tested using the given state 71 // connection. It returns a function that should be used to 72 // undo any changes made by the operation. 73 op func(c *gc.C, st api.Connection, mst *state.State) (reset func(), err error) 74 allow []names.Tag 75 deny []names.Tag 76 }{{ 77 about: "Client.Status", 78 op: opClientStatus, 79 allow: []names.Tag{userAdmin, userOther}, 80 }, { 81 about: "Service.Set", 82 op: opClientServiceSet, 83 allow: []names.Tag{userAdmin, userOther}, 84 }, { 85 about: "Service.Get", 86 op: opClientServiceGet, 87 allow: []names.Tag{userAdmin, userOther}, 88 }, { 89 about: "Client.Resolved", 90 op: opClientResolved, 91 allow: []names.Tag{userAdmin, userOther}, 92 }, { 93 about: "Service.Expose", 94 op: opClientServiceExpose, 95 allow: []names.Tag{userAdmin, userOther}, 96 }, { 97 about: "Service.Unexpose", 98 op: opClientServiceUnexpose, 99 allow: []names.Tag{userAdmin, userOther}, 100 }, { 101 about: "Service.Update", 102 op: opClientServiceUpdate, 103 allow: []names.Tag{userAdmin, userOther}, 104 }, { 105 about: "Service.SetCharm", 106 op: opClientServiceSetCharm, 107 allow: []names.Tag{userAdmin, userOther}, 108 }, { 109 about: "Annotations.GetAnnotations", 110 op: opClientGetAnnotations, 111 allow: []names.Tag{userAdmin, userOther}, 112 }, { 113 about: "Annotations.SetAnnotations", 114 op: opClientSetAnnotations, 115 allow: []names.Tag{userAdmin, userOther}, 116 }, { 117 about: "Service.AddUnits", 118 op: opClientAddServiceUnits, 119 allow: []names.Tag{userAdmin, userOther}, 120 }, { 121 about: "Service.DestroyUnits", 122 op: opClientDestroyServiceUnits, 123 allow: []names.Tag{userAdmin, userOther}, 124 }, { 125 about: "Service.Destroy", 126 op: opClientServiceDestroy, 127 allow: []names.Tag{userAdmin, userOther}, 128 }, { 129 about: "Service.GetConstraints", 130 op: opClientGetServiceConstraints, 131 allow: []names.Tag{userAdmin, userOther}, 132 }, { 133 about: "Service.SetConstraints", 134 op: opClientSetServiceConstraints, 135 allow: []names.Tag{userAdmin, userOther}, 136 }, { 137 about: "Client.SetModelConstraints", 138 op: opClientSetEnvironmentConstraints, 139 allow: []names.Tag{userAdmin, userOther}, 140 }, { 141 about: "Client.ModelGet", 142 op: opClientEnvironmentGet, 143 allow: []names.Tag{userAdmin, userOther}, 144 }, { 145 about: "Client.ModelSet", 146 op: opClientEnvironmentSet, 147 allow: []names.Tag{userAdmin, userOther}, 148 }, { 149 about: "Client.SetModelAgentVersion", 150 op: opClientSetEnvironAgentVersion, 151 allow: []names.Tag{userAdmin, userOther}, 152 }, { 153 about: "Client.WatchAll", 154 op: opClientWatchAll, 155 allow: []names.Tag{userAdmin, userOther}, 156 }, { 157 about: "Client.CharmInfo", 158 op: opClientCharmInfo, 159 allow: []names.Tag{userAdmin, userOther}, 160 }, { 161 about: "Service.AddRelation", 162 op: opClientAddRelation, 163 allow: []names.Tag{userAdmin, userOther}, 164 }, { 165 about: "Service.DestroyRelation", 166 op: opClientDestroyRelation, 167 allow: []names.Tag{userAdmin, userOther}, 168 }} { 169 allow := allowed(entities, t.allow, t.deny) 170 for j, e := range entities { 171 c.Logf("\n------\ntest %d,%d; %s; entity %q", i, j, t.about, e) 172 st := s.openAs(c, e) 173 reset, err := t.op(c, st, s.State) 174 if allow[e] { 175 c.Check(err, jc.ErrorIsNil) 176 } else { 177 c.Check(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 178 Message: "permission denied", 179 Code: "unauthorized access", 180 }) 181 c.Check(err, jc.Satisfies, params.IsCodeUnauthorized) 182 } 183 reset() 184 st.Close() 185 } 186 } 187 } 188 189 func opClientCharmInfo(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 190 info, err := st.Client().CharmInfo("local:quantal/wordpress-3") 191 if err != nil { 192 c.Check(info, gc.IsNil) 193 return func() {}, err 194 } 195 c.Assert(info.URL, gc.Equals, "local:quantal/wordpress-3") 196 c.Assert(info.Meta.Name, gc.Equals, "wordpress") 197 c.Assert(info.Revision, gc.Equals, 3) 198 c.Assert(info.Actions, jc.DeepEquals, &charm.Actions{ 199 ActionSpecs: map[string]charm.ActionSpec{ 200 "fakeaction": { 201 Description: "No description", 202 Params: map[string]interface{}{ 203 "type": "object", 204 "description": "No description", 205 "properties": map[string]interface{}{}, 206 "title": "fakeaction", 207 }, 208 }, 209 }, 210 }) 211 return func() {}, nil 212 } 213 214 func opClientAddRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 215 _, err := service.NewClient(st).AddRelation("nosuch1", "nosuch2") 216 if params.IsCodeNotFound(err) { 217 err = nil 218 } 219 return func() {}, err 220 } 221 222 func opClientDestroyRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 223 err := service.NewClient(st).DestroyRelation("nosuch1", "nosuch2") 224 if params.IsCodeNotFound(err) { 225 err = nil 226 } 227 return func() {}, err 228 } 229 230 func opClientStatus(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 231 status, err := st.Client().Status(nil) 232 if err != nil { 233 c.Check(status, gc.IsNil) 234 return func() {}, err 235 } 236 clearSinceTimes(status) 237 c.Assert(status, jc.DeepEquals, scenarioStatus) 238 return func() {}, nil 239 } 240 241 func resetBlogTitle(c *gc.C, st api.Connection) func() { 242 return func() { 243 err := service.NewClient(st).Set("wordpress", map[string]string{ 244 "blog-title": "", 245 }) 246 c.Assert(err, jc.ErrorIsNil) 247 } 248 } 249 250 func opClientServiceSet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 251 err := service.NewClient(st).Set("wordpress", map[string]string{ 252 "blog-title": "foo", 253 }) 254 if err != nil { 255 return func() {}, err 256 } 257 return resetBlogTitle(c, st), nil 258 } 259 260 func opClientServiceGet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 261 _, err := service.NewClient(st).Get("wordpress") 262 if err != nil { 263 return func() {}, err 264 } 265 return func() {}, nil 266 } 267 268 func opClientServiceExpose(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 269 err := service.NewClient(st).Expose("wordpress") 270 if err != nil { 271 return func() {}, err 272 } 273 return func() { 274 svc, err := mst.Service("wordpress") 275 c.Assert(err, jc.ErrorIsNil) 276 svc.ClearExposed() 277 }, nil 278 } 279 280 func opClientServiceUnexpose(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 281 err := service.NewClient(st).Unexpose("wordpress") 282 if err != nil { 283 return func() {}, err 284 } 285 return func() {}, nil 286 } 287 288 func opClientResolved(c *gc.C, st api.Connection, _ *state.State) (func(), error) { 289 err := st.Client().Resolved("wordpress/1", false) 290 // There are several scenarios in which this test is called, one is 291 // that the user is not authorized. In that case we want to exit now, 292 // letting the error percolate out so the caller knows that the 293 // permission error was correctly generated. 294 if err != nil && params.IsCodeUnauthorized(err) { 295 return func() {}, err 296 } 297 // Otherwise, the user was authorized, but we expect an error anyway 298 // because the unit is not in an error state when we tried to resolve 299 // the error. Therefore, since it is complaining it means that the 300 // call to Resolved worked, so we're happy. 301 c.Assert(err, gc.NotNil) 302 c.Assert(err.Error(), gc.Equals, `unit "wordpress/1" is not in an error state`) 303 return func() {}, nil 304 } 305 306 func opClientGetAnnotations(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 307 ann, err := annotations.NewClient(st).Get([]string{"service-wordpress"}) 308 if err != nil { 309 return func() {}, err 310 } 311 c.Assert(ann, gc.DeepEquals, []params.AnnotationsGetResult{{ 312 EntityTag: "service-wordpress", 313 Annotations: map[string]string{}, 314 }}) 315 return func() {}, nil 316 } 317 318 func opClientSetAnnotations(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 319 pairs := map[string]string{"key1": "value1", "key2": "value2"} 320 setParams := map[string]map[string]string{ 321 "service-wordpress": pairs, 322 } 323 _, err := annotations.NewClient(st).Set(setParams) 324 if err != nil { 325 return func() {}, err 326 } 327 return func() { 328 pairs := map[string]string{"key1": "", "key2": ""} 329 setParams := map[string]map[string]string{ 330 "service-wordpress": pairs, 331 } 332 annotations.NewClient(st).Set(setParams) 333 }, nil 334 } 335 336 func opClientServiceUpdate(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 337 args := params.ServiceUpdate{ 338 ServiceName: "no-such-charm", 339 CharmUrl: "cs:quantal/wordpress-42", 340 ForceCharmUrl: true, 341 SettingsStrings: map[string]string{"blog-title": "foo"}, 342 SettingsYAML: `"wordpress": {"blog-title": "foo"}`, 343 } 344 err := service.NewClient(st).Update(args) 345 if params.IsCodeNotFound(err) { 346 err = nil 347 } 348 return func() {}, err 349 } 350 351 func opClientServiceSetCharm(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 352 cfg := service.SetCharmConfig{ 353 ServiceName: "nosuch", 354 CharmID: charmstore.CharmID{ 355 URL: charm.MustParseURL("local:quantal/wordpress"), 356 }, 357 } 358 err := service.NewClient(st).SetCharm(cfg) 359 if params.IsCodeNotFound(err) { 360 err = nil 361 } 362 return func() {}, err 363 } 364 365 func opClientAddServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 366 _, err := service.NewClient(st).AddUnits("nosuch", 1, nil) 367 if params.IsCodeNotFound(err) { 368 err = nil 369 } 370 return func() {}, err 371 } 372 373 func opClientDestroyServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 374 err := service.NewClient(st).DestroyUnits("wordpress/99") 375 if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") { 376 err = nil 377 } 378 return func() {}, err 379 } 380 381 func opClientServiceDestroy(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 382 err := service.NewClient(st).Destroy("non-existent") 383 if params.IsCodeNotFound(err) { 384 err = nil 385 } 386 return func() {}, err 387 } 388 389 func opClientGetServiceConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 390 _, err := service.NewClient(st).GetConstraints("wordpress") 391 return func() {}, err 392 } 393 394 func opClientSetServiceConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 395 nullConstraints := constraints.Value{} 396 err := service.NewClient(st).SetConstraints("wordpress", nullConstraints) 397 if err != nil { 398 return func() {}, err 399 } 400 return func() {}, nil 401 } 402 403 func opClientSetEnvironmentConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 404 nullConstraints := constraints.Value{} 405 err := st.Client().SetModelConstraints(nullConstraints) 406 if err != nil { 407 return func() {}, err 408 } 409 return func() {}, nil 410 } 411 412 func opClientEnvironmentGet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 413 _, err := st.Client().ModelGet() 414 if err != nil { 415 return func() {}, err 416 } 417 return func() {}, nil 418 } 419 420 func opClientEnvironmentSet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 421 args := map[string]interface{}{"some-key": "some-value"} 422 err := st.Client().ModelSet(args) 423 if err != nil { 424 return func() {}, err 425 } 426 return func() { 427 args["some-key"] = nil 428 st.Client().ModelSet(args) 429 }, nil 430 } 431 432 func opClientSetEnvironAgentVersion(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 433 attrs, err := st.Client().ModelGet() 434 if err != nil { 435 return func() {}, err 436 } 437 err = st.Client().SetModelAgentVersion(jujuversion.Current) 438 if err != nil { 439 return func() {}, err 440 } 441 442 return func() { 443 oldAgentVersion, found := attrs["agent-version"] 444 if found { 445 versionString := oldAgentVersion.(string) 446 st.Client().SetModelAgentVersion(version.MustParse(versionString)) 447 } 448 }, nil 449 } 450 451 func opClientWatchAll(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 452 watcher, err := st.Client().WatchAll() 453 if err == nil { 454 watcher.Stop() 455 } 456 return func() {}, err 457 }