github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 jc "github.com/juju/testing/checkers" 11 "github.com/juju/version" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/charm.v6-unstable" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/api" 17 "github.com/juju/juju/api/annotations" 18 "github.com/juju/juju/api/application" 19 "github.com/juju/juju/api/modelconfig" 20 "github.com/juju/juju/apiserver/params" 21 "github.com/juju/juju/charmstore" 22 "github.com/juju/juju/constraints" 23 "github.com/juju/juju/rpc" 24 "github.com/juju/juju/state" 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: "Application.Set", 82 op: opClientServiceSet, 83 allow: []names.Tag{userAdmin, userOther}, 84 }, { 85 about: "Application.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: "Application.Expose", 94 op: opClientServiceExpose, 95 allow: []names.Tag{userAdmin, userOther}, 96 }, { 97 about: "Application.Unexpose", 98 op: opClientServiceUnexpose, 99 allow: []names.Tag{userAdmin, userOther}, 100 }, { 101 about: "Application.Update", 102 op: opClientServiceUpdate, 103 allow: []names.Tag{userAdmin, userOther}, 104 }, { 105 about: "Application.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: "Application.AddUnits", 118 op: opClientAddServiceUnits, 119 allow: []names.Tag{userAdmin, userOther}, 120 }, { 121 about: "Application.DestroyUnits", 122 op: opClientDestroyServiceUnits, 123 allow: []names.Tag{userAdmin, userOther}, 124 }, { 125 about: "Application.Destroy", 126 op: opClientServiceDestroy, 127 allow: []names.Tag{userAdmin, userOther}, 128 }, { 129 about: "Application.GetConstraints", 130 op: opClientGetServiceConstraints, 131 allow: []names.Tag{userAdmin, userOther}, 132 }, { 133 about: "Application.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: opClientModelGet, 143 allow: []names.Tag{userAdmin, userOther}, 144 }, { 145 about: "Client.ModelSet", 146 op: opClientModelSet, 147 allow: []names.Tag{userAdmin, userOther}, 148 }, { 149 about: "Client.SetModelAgentVersion", 150 op: opClientSetModelAgentVersion, 151 allow: []names.Tag{userAdmin, userOther}, 152 }, { 153 about: "Client.WatchAll", 154 op: opClientWatchAll, 155 allow: []names.Tag{userAdmin, userOther}, 156 }, { 157 about: "Application.AddRelation", 158 op: opClientAddRelation, 159 allow: []names.Tag{userAdmin, userOther}, 160 }, { 161 about: "Application.DestroyRelation", 162 op: opClientDestroyRelation, 163 allow: []names.Tag{userAdmin, userOther}, 164 }} { 165 allow := allowed(entities, t.allow, t.deny) 166 for j, e := range entities { 167 c.Logf("\n------\ntest %d,%d; %s; entity %q", i, j, t.about, e) 168 st := s.openAs(c, e) 169 reset, err := t.op(c, st, s.State) 170 if allow[e] { 171 c.Check(err, jc.ErrorIsNil) 172 } else { 173 c.Check(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 174 Message: "permission denied", 175 Code: "unauthorized access", 176 }) 177 c.Check(err, jc.Satisfies, params.IsCodeUnauthorized) 178 } 179 reset() 180 st.Close() 181 } 182 } 183 } 184 185 func opClientAddRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 186 _, err := application.NewClient(st).AddRelation("nosuch1", "nosuch2") 187 if params.IsCodeNotFound(err) { 188 err = nil 189 } 190 return func() {}, err 191 } 192 193 func opClientDestroyRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 194 err := application.NewClient(st).DestroyRelation("nosuch1", "nosuch2") 195 if params.IsCodeNotFound(err) { 196 err = nil 197 } 198 return func() {}, err 199 } 200 201 func opClientStatus(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 202 status, err := st.Client().Status(nil) 203 if err != nil { 204 c.Check(status, gc.IsNil) 205 return func() {}, err 206 } 207 clearSinceTimes(status) 208 c.Assert(status, jc.DeepEquals, scenarioStatus) 209 return func() {}, nil 210 } 211 212 func resetBlogTitle(c *gc.C, st api.Connection) func() { 213 return func() { 214 err := application.NewClient(st).Set("wordpress", map[string]string{ 215 "blog-title": "", 216 }) 217 c.Assert(err, jc.ErrorIsNil) 218 } 219 } 220 221 func opClientServiceSet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 222 err := application.NewClient(st).Set("wordpress", map[string]string{ 223 "blog-title": "foo", 224 }) 225 if err != nil { 226 return func() {}, err 227 } 228 return resetBlogTitle(c, st), nil 229 } 230 231 func opClientServiceGet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 232 _, err := application.NewClient(st).Get("wordpress") 233 if err != nil { 234 return func() {}, err 235 } 236 return func() {}, nil 237 } 238 239 func opClientServiceExpose(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 240 err := application.NewClient(st).Expose("wordpress") 241 if err != nil { 242 return func() {}, err 243 } 244 return func() { 245 svc, err := mst.Application("wordpress") 246 c.Assert(err, jc.ErrorIsNil) 247 svc.ClearExposed() 248 }, nil 249 } 250 251 func opClientServiceUnexpose(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 252 err := application.NewClient(st).Unexpose("wordpress") 253 if err != nil { 254 return func() {}, err 255 } 256 return func() {}, nil 257 } 258 259 func opClientResolved(c *gc.C, st api.Connection, _ *state.State) (func(), error) { 260 err := st.Client().Resolved("wordpress/1", false) 261 // There are several scenarios in which this test is called, one is 262 // that the user is not authorized. In that case we want to exit now, 263 // letting the error percolate out so the caller knows that the 264 // permission error was correctly generated. 265 if err != nil && params.IsCodeUnauthorized(err) { 266 return func() {}, err 267 } 268 // Otherwise, the user was authorized, but we expect an error anyway 269 // because the unit is not in an error state when we tried to resolve 270 // the error. Therefore, since it is complaining it means that the 271 // call to Resolved worked, so we're happy. 272 c.Assert(err, gc.NotNil) 273 c.Assert(err.Error(), gc.Equals, `unit "wordpress/1" is not in an error state`) 274 return func() {}, nil 275 } 276 277 func opClientGetAnnotations(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 278 ann, err := annotations.NewClient(st).Get([]string{"application-wordpress"}) 279 if err != nil { 280 return func() {}, err 281 } 282 c.Assert(ann, gc.DeepEquals, []params.AnnotationsGetResult{{ 283 EntityTag: "application-wordpress", 284 Annotations: map[string]string{}, 285 }}) 286 return func() {}, nil 287 } 288 289 func opClientSetAnnotations(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 290 pairs := map[string]string{"key1": "value1", "key2": "value2"} 291 setParams := map[string]map[string]string{ 292 "application-wordpress": pairs, 293 } 294 _, err := annotations.NewClient(st).Set(setParams) 295 if err != nil { 296 return func() {}, err 297 } 298 return func() { 299 pairs := map[string]string{"key1": "", "key2": ""} 300 setParams := map[string]map[string]string{ 301 "application-wordpress": pairs, 302 } 303 annotations.NewClient(st).Set(setParams) 304 }, nil 305 } 306 307 func opClientServiceUpdate(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 308 args := params.ApplicationUpdate{ 309 ApplicationName: "no-such-charm", 310 CharmURL: "cs:quantal/wordpress-42", 311 ForceCharmURL: true, 312 SettingsStrings: map[string]string{"blog-title": "foo"}, 313 SettingsYAML: `"wordpress": {"blog-title": "foo"}`, 314 } 315 err := application.NewClient(st).Update(args) 316 if params.IsCodeNotFound(err) { 317 err = nil 318 } 319 return func() {}, err 320 } 321 322 func opClientServiceSetCharm(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 323 cfg := application.SetCharmConfig{ 324 ApplicationName: "nosuch", 325 CharmID: charmstore.CharmID{ 326 URL: charm.MustParseURL("local:quantal/wordpress"), 327 }, 328 } 329 err := application.NewClient(st).SetCharm(cfg) 330 if params.IsCodeNotFound(err) { 331 err = nil 332 } 333 return func() {}, err 334 } 335 336 func opClientAddServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 337 _, err := application.NewClient(st).AddUnits("nosuch", 1, nil) 338 if params.IsCodeNotFound(err) { 339 err = nil 340 } 341 return func() {}, err 342 } 343 344 func opClientDestroyServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 345 err := application.NewClient(st).DestroyUnits("wordpress/99") 346 if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") { 347 err = nil 348 } 349 return func() {}, err 350 } 351 352 func opClientServiceDestroy(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 353 err := application.NewClient(st).Destroy("non-existent") 354 if params.IsCodeNotFound(err) { 355 err = nil 356 } 357 return func() {}, err 358 } 359 360 func opClientGetServiceConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 361 _, err := application.NewClient(st).GetConstraints("wordpress") 362 return func() {}, err 363 } 364 365 func opClientSetServiceConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 366 nullConstraints := constraints.Value{} 367 err := application.NewClient(st).SetConstraints("wordpress", nullConstraints) 368 if err != nil { 369 return func() {}, err 370 } 371 return func() {}, nil 372 } 373 374 func opClientSetEnvironmentConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 375 nullConstraints := constraints.Value{} 376 err := st.Client().SetModelConstraints(nullConstraints) 377 if err != nil { 378 return func() {}, err 379 } 380 return func() {}, nil 381 } 382 383 func opClientModelGet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 384 _, err := modelconfig.NewClient(st).ModelGet() 385 if err != nil { 386 return func() {}, err 387 } 388 return func() {}, nil 389 } 390 391 func opClientModelSet(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 392 args := map[string]interface{}{"some-key": "some-value"} 393 err := modelconfig.NewClient(st).ModelSet(args) 394 if err != nil { 395 return func() {}, err 396 } 397 return func() { 398 args["some-key"] = nil 399 modelconfig.NewClient(st).ModelSet(args) 400 }, nil 401 } 402 403 func opClientSetModelAgentVersion(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 404 attrs, err := modelconfig.NewClient(st).ModelGet() 405 if err != nil { 406 return func() {}, err 407 } 408 ver := version.Number{Major: 1, Minor: 2, Patch: 3} 409 err = st.Client().SetModelAgentVersion(ver) 410 if err != nil { 411 return func() {}, err 412 } 413 414 return func() { 415 oldAgentVersion, found := attrs["agent-version"] 416 if found { 417 versionString := oldAgentVersion.(string) 418 st.Client().SetModelAgentVersion(version.MustParse(versionString)) 419 } 420 }, nil 421 } 422 423 func opClientWatchAll(c *gc.C, st api.Connection, mst *state.State) (func(), error) { 424 watcher, err := st.Client().WatchAll() 425 if err == nil { 426 watcher.Stop() 427 } 428 return func() {}, err 429 }