...

Source file src/go.etcd.io/etcd/server/v3/etcdserver/api/v2auth/auth_test.go

Documentation: go.etcd.io/etcd/server/v3/etcdserver/api/v2auth

     1  // Copyright 2015 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package v2auth
    16  
    17  import (
    18  	"context"
    19  	"reflect"
    20  	"testing"
    21  	"time"
    22  
    23  	"go.etcd.io/etcd/api/v3/etcdserverpb"
    24  	"go.etcd.io/etcd/server/v3/etcdserver"
    25  	"go.etcd.io/etcd/server/v3/etcdserver/api/v2error"
    26  	"go.etcd.io/etcd/server/v3/etcdserver/api/v2store"
    27  
    28  	"go.uber.org/zap"
    29  )
    30  
    31  type fakeDoer struct{}
    32  
    33  func (fakeDoer) Do(context.Context, etcdserverpb.Request) (etcdserver.Response, error) {
    34  	return etcdserver.Response{}, nil
    35  }
    36  
    37  func TestCheckPassword(t *testing.T) {
    38  	st := NewStore(zap.NewExample(), fakeDoer{}, 5*time.Second)
    39  	u := User{Password: "$2a$10$I3iddh1D..EIOXXQtsra4u8AjOtgEa2ERxVvYGfXFBJDo1omXwP.q"}
    40  	matched := st.CheckPassword(u, "foo")
    41  	if matched {
    42  		t.Fatalf("expected false, got %v", matched)
    43  	}
    44  }
    45  
    46  const testTimeout = time.Millisecond
    47  
    48  func TestMergeUser(t *testing.T) {
    49  	tbl := []struct {
    50  		input  User
    51  		merge  User
    52  		expect User
    53  		iserr  bool
    54  	}{
    55  		{
    56  			User{User: "foo"},
    57  			User{User: "bar"},
    58  			User{},
    59  			true,
    60  		},
    61  		{
    62  			User{User: "foo"},
    63  			User{User: "foo"},
    64  			User{User: "foo", Roles: []string{}},
    65  			false,
    66  		},
    67  		{
    68  			User{User: "foo"},
    69  			User{User: "foo", Grant: []string{"role1"}},
    70  			User{User: "foo", Roles: []string{"role1"}},
    71  			false,
    72  		},
    73  		{
    74  			User{User: "foo", Roles: []string{"role1"}},
    75  			User{User: "foo", Grant: []string{"role1"}},
    76  			User{},
    77  			true,
    78  		},
    79  		{
    80  			User{User: "foo", Roles: []string{"role1"}},
    81  			User{User: "foo", Revoke: []string{"role2"}},
    82  			User{},
    83  			true,
    84  		},
    85  		{
    86  			User{User: "foo", Roles: []string{"role1"}},
    87  			User{User: "foo", Grant: []string{"role2"}},
    88  			User{User: "foo", Roles: []string{"role1", "role2"}},
    89  			false,
    90  		},
    91  		{ // empty password will not overwrite the previous password
    92  			User{User: "foo", Password: "foo", Roles: []string{}},
    93  			User{User: "foo", Password: ""},
    94  			User{User: "foo", Password: "foo", Roles: []string{}},
    95  			false,
    96  		},
    97  	}
    98  
    99  	for i, tt := range tbl {
   100  		out, err := tt.input.merge(zap.NewExample(), tt.merge, passwordStore{})
   101  		if err != nil && !tt.iserr {
   102  			t.Fatalf("Got unexpected error on item %d", i)
   103  		}
   104  		if !tt.iserr {
   105  			if !reflect.DeepEqual(out, tt.expect) {
   106  				t.Errorf("Unequal merge expectation on item %d: got: %#v, expect: %#v", i, out, tt.expect)
   107  			}
   108  		}
   109  	}
   110  }
   111  
   112  func TestMergeRole(t *testing.T) {
   113  	tbl := []struct {
   114  		input  Role
   115  		merge  Role
   116  		expect Role
   117  		iserr  bool
   118  	}{
   119  		{
   120  			Role{Role: "foo"},
   121  			Role{Role: "bar"},
   122  			Role{},
   123  			true,
   124  		},
   125  		{
   126  			Role{Role: "foo"},
   127  			Role{Role: "foo", Grant: &Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
   128  			Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
   129  			false,
   130  		},
   131  		{
   132  			Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
   133  			Role{Role: "foo", Revoke: &Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
   134  			Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{}, Write: []string{}}}},
   135  			false,
   136  		},
   137  		{
   138  			Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/bardir"}}}},
   139  			Role{Role: "foo", Revoke: &Permissions{KV: RWPermission{Read: []string{"/foodir"}}}},
   140  			Role{},
   141  			true,
   142  		},
   143  	}
   144  	for i, tt := range tbl {
   145  		out, err := tt.input.merge(zap.NewExample(), tt.merge)
   146  		if err != nil && !tt.iserr {
   147  			t.Fatalf("Got unexpected error on item %d", i)
   148  		}
   149  		if !tt.iserr {
   150  			if !reflect.DeepEqual(out, tt.expect) {
   151  				t.Errorf("Unequal merge expectation on item %d: got: %#v, expect: %#v", i, out, tt.expect)
   152  			}
   153  		}
   154  	}
   155  }
   156  
   157  type testDoer struct {
   158  	get               []etcdserver.Response
   159  	put               []etcdserver.Response
   160  	getindex          int
   161  	putindex          int
   162  	explicitlyEnabled bool
   163  }
   164  
   165  func (td *testDoer) Do(_ context.Context, req etcdserverpb.Request) (etcdserver.Response, error) {
   166  	if td.explicitlyEnabled && (req.Path == StorePermsPrefix+"/enabled") {
   167  		t := "true"
   168  		return etcdserver.Response{
   169  			Event: &v2store.Event{
   170  				Action: v2store.Get,
   171  				Node: &v2store.NodeExtern{
   172  					Key:   StorePermsPrefix + "/users/cat",
   173  					Value: &t,
   174  				},
   175  			},
   176  		}, nil
   177  	}
   178  	if (req.Method == "GET" || req.Method == "QGET") && td.get != nil {
   179  		res := td.get[td.getindex]
   180  		if res.Event == nil {
   181  			td.getindex++
   182  			return etcdserver.Response{}, &v2error.Error{
   183  				ErrorCode: v2error.EcodeKeyNotFound,
   184  			}
   185  		}
   186  		td.getindex++
   187  		return res, nil
   188  	}
   189  	if req.Method == "PUT" && td.put != nil {
   190  		res := td.put[td.putindex]
   191  		if res.Event == nil {
   192  			td.putindex++
   193  			return etcdserver.Response{}, &v2error.Error{
   194  				ErrorCode: v2error.EcodeNodeExist,
   195  			}
   196  		}
   197  		td.putindex++
   198  		return res, nil
   199  	}
   200  	return etcdserver.Response{}, nil
   201  }
   202  
   203  func TestAllUsers(t *testing.T) {
   204  	d := &testDoer{
   205  		get: []etcdserver.Response{
   206  			{
   207  				Event: &v2store.Event{
   208  					Action: v2store.Get,
   209  					Node: &v2store.NodeExtern{
   210  						Nodes: v2store.NodeExterns([]*v2store.NodeExtern{
   211  							{
   212  								Key: StorePermsPrefix + "/users/cat",
   213  							},
   214  							{
   215  								Key: StorePermsPrefix + "/users/dog",
   216  							},
   217  						}),
   218  					},
   219  				},
   220  			},
   221  		},
   222  	}
   223  	expected := []string{"cat", "dog"}
   224  
   225  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
   226  	users, err := s.AllUsers()
   227  	if err != nil {
   228  		t.Error("Unexpected error", err)
   229  	}
   230  	if !reflect.DeepEqual(users, expected) {
   231  		t.Error("AllUsers doesn't match given store. Got", users, "expected", expected)
   232  	}
   233  }
   234  
   235  func TestGetAndDeleteUser(t *testing.T) {
   236  	data := `{"user": "cat", "roles" : ["animal"]}`
   237  	d := &testDoer{
   238  		get: []etcdserver.Response{
   239  			{
   240  				Event: &v2store.Event{
   241  					Action: v2store.Get,
   242  					Node: &v2store.NodeExtern{
   243  						Key:   StorePermsPrefix + "/users/cat",
   244  						Value: &data,
   245  					},
   246  				},
   247  			},
   248  		},
   249  		explicitlyEnabled: true,
   250  	}
   251  	expected := User{User: "cat", Roles: []string{"animal"}}
   252  
   253  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
   254  	out, err := s.GetUser("cat")
   255  	if err != nil {
   256  		t.Error("Unexpected error", err)
   257  	}
   258  	if !reflect.DeepEqual(out, expected) {
   259  		t.Error("GetUser doesn't match given store. Got", out, "expected", expected)
   260  	}
   261  	err = s.DeleteUser("cat")
   262  	if err != nil {
   263  		t.Error("Unexpected error", err)
   264  	}
   265  }
   266  
   267  func TestAllRoles(t *testing.T) {
   268  	d := &testDoer{
   269  		get: []etcdserver.Response{
   270  			{
   271  				Event: &v2store.Event{
   272  					Action: v2store.Get,
   273  					Node: &v2store.NodeExtern{
   274  						Nodes: v2store.NodeExterns([]*v2store.NodeExtern{
   275  							{
   276  								Key: StorePermsPrefix + "/roles/animal",
   277  							},
   278  							{
   279  								Key: StorePermsPrefix + "/roles/human",
   280  							},
   281  						}),
   282  					},
   283  				},
   284  			},
   285  		},
   286  		explicitlyEnabled: true,
   287  	}
   288  	expected := []string{"animal", "human", "root"}
   289  
   290  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
   291  	out, err := s.AllRoles()
   292  	if err != nil {
   293  		t.Error("Unexpected error", err)
   294  	}
   295  	if !reflect.DeepEqual(out, expected) {
   296  		t.Error("AllRoles doesn't match given store. Got", out, "expected", expected)
   297  	}
   298  }
   299  
   300  func TestGetAndDeleteRole(t *testing.T) {
   301  	data := `{"role": "animal"}`
   302  	d := &testDoer{
   303  		get: []etcdserver.Response{
   304  			{
   305  				Event: &v2store.Event{
   306  					Action: v2store.Get,
   307  					Node: &v2store.NodeExtern{
   308  						Key:   StorePermsPrefix + "/roles/animal",
   309  						Value: &data,
   310  					},
   311  				},
   312  			},
   313  		},
   314  		explicitlyEnabled: true,
   315  	}
   316  	expected := Role{Role: "animal"}
   317  
   318  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
   319  	out, err := s.GetRole("animal")
   320  	if err != nil {
   321  		t.Error("Unexpected error", err)
   322  	}
   323  	if !reflect.DeepEqual(out, expected) {
   324  		t.Error("GetRole doesn't match given store. Got", out, "expected", expected)
   325  	}
   326  	err = s.DeleteRole("animal")
   327  	if err != nil {
   328  		t.Error("Unexpected error", err)
   329  	}
   330  }
   331  
   332  func TestEnsure(t *testing.T) {
   333  	d := &testDoer{
   334  		get: []etcdserver.Response{
   335  			{
   336  				Event: &v2store.Event{
   337  					Action: v2store.Set,
   338  					Node: &v2store.NodeExtern{
   339  						Key: StorePermsPrefix,
   340  						Dir: true,
   341  					},
   342  				},
   343  			},
   344  			{
   345  				Event: &v2store.Event{
   346  					Action: v2store.Set,
   347  					Node: &v2store.NodeExtern{
   348  						Key: StorePermsPrefix + "/users/",
   349  						Dir: true,
   350  					},
   351  				},
   352  			},
   353  			{
   354  				Event: &v2store.Event{
   355  					Action: v2store.Set,
   356  					Node: &v2store.NodeExtern{
   357  						Key: StorePermsPrefix + "/roles/",
   358  						Dir: true,
   359  					},
   360  				},
   361  			},
   362  		},
   363  	}
   364  
   365  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
   366  	err := s.ensureAuthDirectories()
   367  	if err != nil {
   368  		t.Error("Unexpected error", err)
   369  	}
   370  }
   371  
   372  type fastPasswordStore struct {
   373  }
   374  
   375  func (fastPasswordStore) CheckPassword(user User, password string) bool {
   376  	return user.Password == password
   377  }
   378  
   379  func (fastPasswordStore) HashPassword(password string) (string, error) { return password, nil }
   380  
   381  func TestCreateAndUpdateUser(t *testing.T) {
   382  	olduser := `{"user": "cat", "roles" : ["animal"]}`
   383  	newuser := `{"user": "cat", "roles" : ["animal", "pet"]}`
   384  	d := &testDoer{
   385  		get: []etcdserver.Response{
   386  			{
   387  				Event: nil,
   388  			},
   389  			{
   390  				Event: &v2store.Event{
   391  					Action: v2store.Get,
   392  					Node: &v2store.NodeExtern{
   393  						Key:   StorePermsPrefix + "/users/cat",
   394  						Value: &olduser,
   395  					},
   396  				},
   397  			},
   398  			{
   399  				Event: &v2store.Event{
   400  					Action: v2store.Get,
   401  					Node: &v2store.NodeExtern{
   402  						Key:   StorePermsPrefix + "/users/cat",
   403  						Value: &olduser,
   404  					},
   405  				},
   406  			},
   407  		},
   408  		put: []etcdserver.Response{
   409  			{
   410  				Event: &v2store.Event{
   411  					Action: v2store.Update,
   412  					Node: &v2store.NodeExtern{
   413  						Key:   StorePermsPrefix + "/users/cat",
   414  						Value: &olduser,
   415  					},
   416  				},
   417  			},
   418  			{
   419  				Event: &v2store.Event{
   420  					Action: v2store.Update,
   421  					Node: &v2store.NodeExtern{
   422  						Key:   StorePermsPrefix + "/users/cat",
   423  						Value: &newuser,
   424  					},
   425  				},
   426  			},
   427  		},
   428  		explicitlyEnabled: true,
   429  	}
   430  	user := User{User: "cat", Password: "meow", Roles: []string{"animal"}}
   431  	update := User{User: "cat", Grant: []string{"pet"}}
   432  	expected := User{User: "cat", Roles: []string{"animal", "pet"}}
   433  
   434  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true, PasswordStore: fastPasswordStore{}}
   435  	out, created, err := s.CreateOrUpdateUser(user)
   436  	if !created {
   437  		t.Error("Should have created user, instead updated?")
   438  	}
   439  	if err != nil {
   440  		t.Error("Unexpected error", err)
   441  	}
   442  	out.Password = "meow"
   443  	if !reflect.DeepEqual(out, user) {
   444  		t.Error("UpdateUser doesn't match given update. Got", out, "expected", expected)
   445  	}
   446  	out, created, err = s.CreateOrUpdateUser(update)
   447  	if created {
   448  		t.Error("Should have updated user, instead created?")
   449  	}
   450  	if err != nil {
   451  		t.Error("Unexpected error", err)
   452  	}
   453  	if !reflect.DeepEqual(out, expected) {
   454  		t.Error("UpdateUser doesn't match given update. Got", out, "expected", expected)
   455  	}
   456  }
   457  
   458  func TestUpdateRole(t *testing.T) {
   459  	oldrole := `{"role": "animal", "permissions" : {"kv": {"read": ["/animal"], "write": []}}}`
   460  	newrole := `{"role": "animal", "permissions" : {"kv": {"read": ["/animal"], "write": ["/animal"]}}}`
   461  	d := &testDoer{
   462  		get: []etcdserver.Response{
   463  			{
   464  				Event: &v2store.Event{
   465  					Action: v2store.Get,
   466  					Node: &v2store.NodeExtern{
   467  						Key:   StorePermsPrefix + "/roles/animal",
   468  						Value: &oldrole,
   469  					},
   470  				},
   471  			},
   472  		},
   473  		put: []etcdserver.Response{
   474  			{
   475  				Event: &v2store.Event{
   476  					Action: v2store.Update,
   477  					Node: &v2store.NodeExtern{
   478  						Key:   StorePermsPrefix + "/roles/animal",
   479  						Value: &newrole,
   480  					},
   481  				},
   482  			},
   483  		},
   484  		explicitlyEnabled: true,
   485  	}
   486  	update := Role{Role: "animal", Grant: &Permissions{KV: RWPermission{Read: []string{}, Write: []string{"/animal"}}}}
   487  	expected := Role{Role: "animal", Permissions: Permissions{KV: RWPermission{Read: []string{"/animal"}, Write: []string{"/animal"}}}}
   488  
   489  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
   490  	out, err := s.UpdateRole(update)
   491  	if err != nil {
   492  		t.Error("Unexpected error", err)
   493  	}
   494  	if !reflect.DeepEqual(out, expected) {
   495  		t.Error("UpdateRole doesn't match given update. Got", out, "expected", expected)
   496  	}
   497  }
   498  
   499  func TestCreateRole(t *testing.T) {
   500  	role := `{"role": "animal", "permissions" : {"kv": {"read": ["/animal"], "write": []}}}`
   501  	d := &testDoer{
   502  		put: []etcdserver.Response{
   503  			{
   504  				Event: &v2store.Event{
   505  					Action: v2store.Create,
   506  					Node: &v2store.NodeExtern{
   507  						Key:   StorePermsPrefix + "/roles/animal",
   508  						Value: &role,
   509  					},
   510  				},
   511  			},
   512  			{
   513  				Event: nil,
   514  			},
   515  		},
   516  		explicitlyEnabled: true,
   517  	}
   518  	r := Role{Role: "animal", Permissions: Permissions{KV: RWPermission{Read: []string{"/animal"}, Write: []string{}}}}
   519  
   520  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
   521  	err := s.CreateRole(Role{Role: "root"})
   522  	if err == nil {
   523  		t.Error("Should error creating root role")
   524  	}
   525  	err = s.CreateRole(r)
   526  	if err != nil {
   527  		t.Error("Unexpected error", err)
   528  	}
   529  	err = s.CreateRole(r)
   530  	if err == nil {
   531  		t.Error("Creating duplicate role, should error")
   532  	}
   533  }
   534  
   535  func TestEnableAuth(t *testing.T) {
   536  	rootUser := `{"user": "root", "password": ""}`
   537  	guestRole := `{"role": "guest", "permissions" : {"kv": {"read": ["*"], "write": ["*"]}}}`
   538  	trueval := "true"
   539  	falseval := "false"
   540  	d := &testDoer{
   541  		get: []etcdserver.Response{
   542  			{
   543  				Event: &v2store.Event{
   544  					Action: v2store.Get,
   545  					Node: &v2store.NodeExtern{
   546  						Key:   StorePermsPrefix + "/enabled",
   547  						Value: &falseval,
   548  					},
   549  				},
   550  			},
   551  			{
   552  				Event: &v2store.Event{
   553  					Action: v2store.Get,
   554  					Node: &v2store.NodeExtern{
   555  						Key:   StorePermsPrefix + "/user/root",
   556  						Value: &rootUser,
   557  					},
   558  				},
   559  			},
   560  			{
   561  				Event: nil,
   562  			},
   563  		},
   564  		put: []etcdserver.Response{
   565  			{
   566  				Event: &v2store.Event{
   567  					Action: v2store.Create,
   568  					Node: &v2store.NodeExtern{
   569  						Key:   StorePermsPrefix + "/roles/guest",
   570  						Value: &guestRole,
   571  					},
   572  				},
   573  			},
   574  			{
   575  				Event: &v2store.Event{
   576  					Action: v2store.Update,
   577  					Node: &v2store.NodeExtern{
   578  						Key:   StorePermsPrefix + "/enabled",
   579  						Value: &trueval,
   580  					},
   581  				},
   582  			},
   583  		},
   584  		explicitlyEnabled: false,
   585  	}
   586  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
   587  	err := s.EnableAuth()
   588  	if err != nil {
   589  		t.Error("Unexpected error", err)
   590  	}
   591  }
   592  
   593  func TestDisableAuth(t *testing.T) {
   594  	trueval := "true"
   595  	falseval := "false"
   596  	d := &testDoer{
   597  		get: []etcdserver.Response{
   598  			{
   599  				Event: &v2store.Event{
   600  					Action: v2store.Get,
   601  					Node: &v2store.NodeExtern{
   602  						Key:   StorePermsPrefix + "/enabled",
   603  						Value: &falseval,
   604  					},
   605  				},
   606  			},
   607  			{
   608  				Event: &v2store.Event{
   609  					Action: v2store.Get,
   610  					Node: &v2store.NodeExtern{
   611  						Key:   StorePermsPrefix + "/enabled",
   612  						Value: &trueval,
   613  					},
   614  				},
   615  			},
   616  		},
   617  		put: []etcdserver.Response{
   618  			{
   619  				Event: &v2store.Event{
   620  					Action: v2store.Update,
   621  					Node: &v2store.NodeExtern{
   622  						Key:   StorePermsPrefix + "/enabled",
   623  						Value: &falseval,
   624  					},
   625  				},
   626  			},
   627  		},
   628  		explicitlyEnabled: false,
   629  	}
   630  	s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
   631  	err := s.DisableAuth()
   632  	if err == nil {
   633  		t.Error("Expected error; already disabled")
   634  	}
   635  
   636  	err = s.DisableAuth()
   637  	if err != nil {
   638  		t.Error("Unexpected error", err)
   639  	}
   640  }
   641  
   642  func TestSimpleMatch(t *testing.T) {
   643  	role := Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/foodir/*", "/fookey"}, Write: []string{"/bardir/*", "/barkey"}}}}
   644  	if !role.HasKeyAccess("/foodir/foo/bar", false) {
   645  		t.Fatal("role lacks expected access")
   646  	}
   647  	if !role.HasKeyAccess("/fookey", false) {
   648  		t.Fatal("role lacks expected access")
   649  	}
   650  	if !role.HasRecursiveAccess("/foodir/*", false) {
   651  		t.Fatal("role lacks expected access")
   652  	}
   653  	if !role.HasRecursiveAccess("/foodir/foo*", false) {
   654  		t.Fatal("role lacks expected access")
   655  	}
   656  	if !role.HasRecursiveAccess("/bardir/*", true) {
   657  		t.Fatal("role lacks expected access")
   658  	}
   659  	if !role.HasKeyAccess("/bardir/bar/foo", true) {
   660  		t.Fatal("role lacks expected access")
   661  	}
   662  	if !role.HasKeyAccess("/barkey", true) {
   663  		t.Fatal("role lacks expected access")
   664  	}
   665  
   666  	if role.HasKeyAccess("/bardir/bar/foo", false) {
   667  		t.Fatal("role has unexpected access")
   668  	}
   669  	if role.HasKeyAccess("/barkey", false) {
   670  		t.Fatal("role has unexpected access")
   671  	}
   672  	if role.HasKeyAccess("/foodir/foo/bar", true) {
   673  		t.Fatal("role has unexpected access")
   674  	}
   675  	if role.HasKeyAccess("/fookey", true) {
   676  		t.Fatal("role has unexpected access")
   677  	}
   678  }
   679  

View as plain text