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  }