github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/names"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  	"gopkg.in/juju/charm.v6-unstable"
    13  
    14  	"github.com/juju/juju/api"
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/constraints"
    17  	"github.com/juju/juju/state"
    18  	"github.com/juju/juju/version"
    19  )
    20  
    21  type permSuite struct {
    22  	baseSuite
    23  }
    24  
    25  var _ = gc.Suite(&permSuite{})
    26  
    27  // Most (if not all) of the permission tests below aim to test
    28  // end-to-end operations execution through the API, but do not care
    29  // about the results. They only test that a call is succeeds or fails
    30  // (usually due to "permission denied"). There are separate test cases
    31  // testing each individual API call data flow later on.
    32  
    33  // allowed returns the set of allowed entities given an allow list and a
    34  // deny list.  If an allow list is specified, only those entities are
    35  // allowed; otherwise those in deny are disallowed.
    36  func allowed(all, allow, deny []names.Tag) map[names.Tag]bool {
    37  	p := make(map[names.Tag]bool)
    38  	if allow != nil {
    39  		for _, e := range allow {
    40  			p[e] = true
    41  		}
    42  		return p
    43  	}
    44  loop:
    45  	for _, e0 := range all {
    46  		for _, e1 := range deny {
    47  			if e1 == e0 {
    48  				continue loop
    49  			}
    50  		}
    51  		p[e0] = true
    52  	}
    53  	return p
    54  }
    55  
    56  func (s *permSuite) TestOperationPerm(c *gc.C) {
    57  	var (
    58  		userAdmin = s.AdminUserTag(c)
    59  		userOther = names.NewLocalUserTag("other")
    60  	)
    61  	entities := s.setUpScenario(c)
    62  	for i, t := range []struct {
    63  		about string
    64  		// op performs the operation to be tested using the given state
    65  		// connection. It returns a function that should be used to
    66  		// undo any changes made by the operation.
    67  		op    func(c *gc.C, st api.Connection, mst *state.State) (reset func(), err error)
    68  		allow []names.Tag
    69  		deny  []names.Tag
    70  	}{{
    71  		about: "Client.Status",
    72  		op:    opClientStatus,
    73  		allow: []names.Tag{userAdmin, userOther},
    74  	}, {
    75  		about: "Client.ServiceSet",
    76  		op:    opClientServiceSet,
    77  		allow: []names.Tag{userAdmin, userOther},
    78  	}, {
    79  		about: "Client.ServiceSetYAML",
    80  		op:    opClientServiceSetYAML,
    81  		allow: []names.Tag{userAdmin, userOther},
    82  	}, {
    83  		about: "Client.ServiceGet",
    84  		op:    opClientServiceGet,
    85  		allow: []names.Tag{userAdmin, userOther},
    86  	}, {
    87  		about: "Client.Resolved",
    88  		op:    opClientResolved,
    89  		allow: []names.Tag{userAdmin, userOther},
    90  	}, {
    91  		about: "Client.ServiceExpose",
    92  		op:    opClientServiceExpose,
    93  		allow: []names.Tag{userAdmin, userOther},
    94  	}, {
    95  		about: "Client.ServiceUnexpose",
    96  		op:    opClientServiceUnexpose,
    97  		allow: []names.Tag{userAdmin, userOther},
    98  	}, {
    99  		about: "Client.ServiceDeploy",
   100  		op:    opClientServiceDeploy,
   101  		allow: []names.Tag{userAdmin, userOther},
   102  	}, {
   103  		about: "Client.ServiceDeployWithNetworks",
   104  		op:    opClientServiceDeployWithNetworks,
   105  		allow: []names.Tag{userAdmin, userOther},
   106  	}, {
   107  		about: "Client.ServiceUpdate",
   108  		op:    opClientServiceUpdate,
   109  		allow: []names.Tag{userAdmin, userOther},
   110  	}, {
   111  		about: "Client.ServiceSetCharm",
   112  		op:    opClientServiceSetCharm,
   113  		allow: []names.Tag{userAdmin, userOther},
   114  	}, {
   115  		about: "Client.GetAnnotations",
   116  		op:    opClientGetAnnotations,
   117  		allow: []names.Tag{userAdmin, userOther},
   118  	}, {
   119  		about: "Client.SetAnnotations",
   120  		op:    opClientSetAnnotations,
   121  		allow: []names.Tag{userAdmin, userOther},
   122  	}, {
   123  		about: "Client.AddServiceUnits",
   124  		op:    opClientAddServiceUnits,
   125  		allow: []names.Tag{userAdmin, userOther},
   126  	}, {
   127  		about: "Client.DestroyServiceUnits",
   128  		op:    opClientDestroyServiceUnits,
   129  		allow: []names.Tag{userAdmin, userOther},
   130  	}, {
   131  		about: "Client.ServiceDestroy",
   132  		op:    opClientServiceDestroy,
   133  		allow: []names.Tag{userAdmin, userOther},
   134  	}, {
   135  		about: "Client.GetServiceConstraints",
   136  		op:    opClientGetServiceConstraints,
   137  		allow: []names.Tag{userAdmin, userOther},
   138  	}, {
   139  		about: "Client.SetServiceConstraints",
   140  		op:    opClientSetServiceConstraints,
   141  		allow: []names.Tag{userAdmin, userOther},
   142  	}, {
   143  		about: "Client.SetEnvironmentConstraints",
   144  		op:    opClientSetEnvironmentConstraints,
   145  		allow: []names.Tag{userAdmin, userOther},
   146  	}, {
   147  		about: "Client.EnvironmentGet",
   148  		op:    opClientEnvironmentGet,
   149  		allow: []names.Tag{userAdmin, userOther},
   150  	}, {
   151  		about: "Client.EnvironmentSet",
   152  		op:    opClientEnvironmentSet,
   153  		allow: []names.Tag{userAdmin, userOther},
   154  	}, {
   155  		about: "Client.SetEnvironAgentVersion",
   156  		op:    opClientSetEnvironAgentVersion,
   157  		allow: []names.Tag{userAdmin, userOther},
   158  	}, {
   159  		about: "Client.WatchAll",
   160  		op:    opClientWatchAll,
   161  		allow: []names.Tag{userAdmin, userOther},
   162  	}, {
   163  		about: "Client.CharmInfo",
   164  		op:    opClientCharmInfo,
   165  		allow: []names.Tag{userAdmin, userOther},
   166  	}, {
   167  		about: "Client.AddRelation",
   168  		op:    opClientAddRelation,
   169  		allow: []names.Tag{userAdmin, userOther},
   170  	}, {
   171  		about: "Client.DestroyRelation",
   172  		op:    opClientDestroyRelation,
   173  		allow: []names.Tag{userAdmin, userOther},
   174  	}} {
   175  		allow := allowed(entities, t.allow, t.deny)
   176  		for j, e := range entities {
   177  			c.Logf("\n------\ntest %d,%d; %s; entity %q", i, j, t.about, e)
   178  			st := s.openAs(c, e)
   179  			reset, err := t.op(c, st, s.State)
   180  			if allow[e] {
   181  				c.Check(err, jc.ErrorIsNil)
   182  			} else {
   183  				c.Check(err, gc.ErrorMatches, "permission denied")
   184  				c.Check(err, jc.Satisfies, params.IsCodeUnauthorized)
   185  			}
   186  			reset()
   187  			st.Close()
   188  		}
   189  	}
   190  }
   191  
   192  func opClientCharmInfo(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   193  	info, err := st.Client().CharmInfo("local:quantal/wordpress-3")
   194  	if err != nil {
   195  		c.Check(info, gc.IsNil)
   196  		return func() {}, err
   197  	}
   198  	c.Assert(info.URL, gc.Equals, "local:quantal/wordpress-3")
   199  	c.Assert(info.Meta.Name, gc.Equals, "wordpress")
   200  	c.Assert(info.Revision, gc.Equals, 3)
   201  	c.Assert(info.Actions, jc.DeepEquals, &charm.Actions{
   202  		ActionSpecs: map[string]charm.ActionSpec{
   203  			"fakeaction": {
   204  				Description: "No description",
   205  				Params: map[string]interface{}{
   206  					"type":        "object",
   207  					"description": "No description",
   208  					"properties":  map[string]interface{}{},
   209  					"title":       "fakeaction",
   210  				},
   211  			},
   212  		},
   213  	})
   214  	return func() {}, nil
   215  }
   216  
   217  func opClientAddRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   218  	_, err := st.Client().AddRelation("nosuch1", "nosuch2")
   219  	if params.IsCodeNotFound(err) {
   220  		err = nil
   221  	}
   222  	return func() {}, err
   223  }
   224  
   225  func opClientDestroyRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   226  	err := st.Client().DestroyRelation("nosuch1", "nosuch2")
   227  	if params.IsCodeNotFound(err) {
   228  		err = nil
   229  	}
   230  	return func() {}, err
   231  }
   232  
   233  func opClientStatus(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   234  	status, err := st.Client().Status(nil)
   235  	if err != nil {
   236  		c.Check(status, gc.IsNil)
   237  		return func() {}, err
   238  	}
   239  	clearSinceTimes(status)
   240  	c.Assert(status, jc.DeepEquals, scenarioStatus)
   241  	return func() {}, nil
   242  }
   243  
   244  func resetBlogTitle(c *gc.C, st api.Connection) func() {
   245  	return func() {
   246  		err := st.Client().ServiceSet("wordpress", map[string]string{
   247  			"blog-title": "",
   248  		})
   249  		c.Assert(err, jc.ErrorIsNil)
   250  	}
   251  }
   252  
   253  func opClientServiceSet(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   254  	err := st.Client().ServiceSet("wordpress", map[string]string{
   255  		"blog-title": "foo",
   256  	})
   257  	if err != nil {
   258  		return func() {}, err
   259  	}
   260  	return resetBlogTitle(c, st), nil
   261  }
   262  
   263  func opClientServiceSetYAML(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   264  	err := st.Client().ServiceSetYAML("wordpress", `"wordpress": {"blog-title": "foo"}`)
   265  	if err != nil {
   266  		return func() {}, err
   267  	}
   268  	return resetBlogTitle(c, st), nil
   269  }
   270  
   271  func opClientServiceGet(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   272  	_, err := st.Client().ServiceGet("wordpress")
   273  	if err != nil {
   274  		return func() {}, err
   275  	}
   276  	return func() {}, nil
   277  }
   278  
   279  func opClientServiceExpose(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   280  	err := st.Client().ServiceExpose("wordpress")
   281  	if err != nil {
   282  		return func() {}, err
   283  	}
   284  	return func() {
   285  		svc, err := mst.Service("wordpress")
   286  		c.Assert(err, jc.ErrorIsNil)
   287  		svc.ClearExposed()
   288  	}, nil
   289  }
   290  
   291  func opClientServiceUnexpose(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   292  	err := st.Client().ServiceUnexpose("wordpress")
   293  	if err != nil {
   294  		return func() {}, err
   295  	}
   296  	return func() {}, nil
   297  }
   298  
   299  func opClientResolved(c *gc.C, st api.Connection, _ *state.State) (func(), error) {
   300  	err := st.Client().Resolved("wordpress/1", false)
   301  	// There are several scenarios in which this test is called, one is
   302  	// that the user is not authorized.  In that case we want to exit now,
   303  	// letting the error percolate out so the caller knows that the
   304  	// permission error was correctly generated.
   305  	if err != nil && params.IsCodeUnauthorized(err) {
   306  		return func() {}, err
   307  	}
   308  	// Otherwise, the user was authorized, but we expect an error anyway
   309  	// because the unit is not in an error state when we tried to resolve
   310  	// the error.  Therefore, since it is complaining it means that the
   311  	// call to Resolved worked, so we're happy.
   312  	c.Assert(err, gc.NotNil)
   313  	c.Assert(err.Error(), gc.Equals, `unit "wordpress/1" is not in an error state`)
   314  	return func() {}, nil
   315  }
   316  
   317  func opClientGetAnnotations(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   318  	ann, err := st.Client().GetAnnotations("service-wordpress")
   319  	if err != nil {
   320  		return func() {}, err
   321  	}
   322  	c.Assert(ann, gc.DeepEquals, make(map[string]string))
   323  	return func() {}, nil
   324  }
   325  
   326  func opClientSetAnnotations(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   327  	pairs := map[string]string{"key1": "value1", "key2": "value2"}
   328  	err := st.Client().SetAnnotations("service-wordpress", pairs)
   329  	if err != nil {
   330  		return func() {}, err
   331  	}
   332  	return func() {
   333  		pairs := map[string]string{"key1": "", "key2": ""}
   334  		st.Client().SetAnnotations("service-wordpress", pairs)
   335  	}, nil
   336  }
   337  
   338  func opClientServiceDeploy(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   339  	err := st.Client().ServiceDeploy("mad:bad/url-1", "x", 1, "", constraints.Value{}, "")
   340  	if err.Error() == `charm or bundle URL has invalid schema: "mad:bad/url-1"` {
   341  		err = nil
   342  	}
   343  	return func() {}, err
   344  }
   345  
   346  func opClientServiceDeployWithNetworks(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   347  	err := st.Client().ServiceDeployWithNetworks("mad:bad/url-1", "x", 1, "", constraints.Value{}, "", nil)
   348  	if err.Error() == `charm or bundle URL has invalid schema: "mad:bad/url-1"` {
   349  		err = nil
   350  	}
   351  	return func() {}, err
   352  }
   353  
   354  func opClientServiceUpdate(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   355  	args := params.ServiceUpdate{
   356  		ServiceName:     "no-such-charm",
   357  		CharmUrl:        "cs:quantal/wordpress-42",
   358  		ForceCharmUrl:   true,
   359  		SettingsStrings: map[string]string{"blog-title": "foo"},
   360  		SettingsYAML:    `"wordpress": {"blog-title": "foo"}`,
   361  	}
   362  	err := st.Client().ServiceUpdate(args)
   363  	if params.IsCodeNotFound(err) {
   364  		err = nil
   365  	}
   366  	return func() {}, err
   367  }
   368  
   369  func opClientServiceSetCharm(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   370  	err := st.Client().ServiceSetCharm("nosuch", "local:quantal/wordpress", false)
   371  	if params.IsCodeNotFound(err) {
   372  		err = nil
   373  	}
   374  	return func() {}, err
   375  }
   376  
   377  func opClientAddServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   378  	_, err := st.Client().AddServiceUnits("nosuch", 1, "")
   379  	if params.IsCodeNotFound(err) {
   380  		err = nil
   381  	}
   382  	return func() {}, err
   383  }
   384  
   385  func opClientDestroyServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   386  	err := st.Client().DestroyServiceUnits("wordpress/99")
   387  	if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") {
   388  		err = nil
   389  	}
   390  	return func() {}, err
   391  }
   392  
   393  func opClientServiceDestroy(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   394  	err := st.Client().ServiceDestroy("non-existent")
   395  	if params.IsCodeNotFound(err) {
   396  		err = nil
   397  	}
   398  	return func() {}, err
   399  }
   400  
   401  func opClientGetServiceConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   402  	_, err := st.Client().GetServiceConstraints("wordpress")
   403  	return func() {}, err
   404  }
   405  
   406  func opClientSetServiceConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   407  	nullConstraints := constraints.Value{}
   408  	err := st.Client().SetServiceConstraints("wordpress", nullConstraints)
   409  	if err != nil {
   410  		return func() {}, err
   411  	}
   412  	return func() {}, nil
   413  }
   414  
   415  func opClientSetEnvironmentConstraints(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   416  	nullConstraints := constraints.Value{}
   417  	err := st.Client().SetEnvironmentConstraints(nullConstraints)
   418  	if err != nil {
   419  		return func() {}, err
   420  	}
   421  	return func() {}, nil
   422  }
   423  
   424  func opClientEnvironmentGet(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   425  	_, err := st.Client().EnvironmentGet()
   426  	if err != nil {
   427  		return func() {}, err
   428  	}
   429  	return func() {}, nil
   430  }
   431  
   432  func opClientEnvironmentSet(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   433  	args := map[string]interface{}{"some-key": "some-value"}
   434  	err := st.Client().EnvironmentSet(args)
   435  	if err != nil {
   436  		return func() {}, err
   437  	}
   438  	return func() {
   439  		args["some-key"] = nil
   440  		st.Client().EnvironmentSet(args)
   441  	}, nil
   442  }
   443  
   444  func opClientSetEnvironAgentVersion(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   445  	attrs, err := st.Client().EnvironmentGet()
   446  	if err != nil {
   447  		return func() {}, err
   448  	}
   449  	err = st.Client().SetEnvironAgentVersion(version.Current.Number)
   450  	if err != nil {
   451  		return func() {}, err
   452  	}
   453  
   454  	return func() {
   455  		oldAgentVersion, found := attrs["agent-version"]
   456  		if found {
   457  			versionString := oldAgentVersion.(string)
   458  			st.Client().SetEnvironAgentVersion(version.MustParse(versionString))
   459  		}
   460  	}, nil
   461  }
   462  
   463  func opClientWatchAll(c *gc.C, st api.Connection, mst *state.State) (func(), error) {
   464  	watcher, err := st.Client().WatchAll()
   465  	if err == nil {
   466  		watcher.Stop()
   467  	}
   468  	return func() {}, err
   469  }