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