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