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  }