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