github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  }