go.etcd.io/etcd@v3.3.27+incompatible/etcdserver/auth/auth_test.go (about)

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