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