go.etcd.io/etcd@v3.3.27+incompatible/etcdserver/api/v2http/client_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 v2http
    16  
    17  import (
    18  	"crypto/tls"
    19  	"crypto/x509"
    20  	"encoding/json"
    21  	"encoding/pem"
    22  	"errors"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"net/url"
    28  	"path"
    29  	"sort"
    30  	"strings"
    31  	"testing"
    32  
    33  	"github.com/coreos/etcd/etcdserver/api"
    34  	"github.com/coreos/etcd/etcdserver/auth"
    35  )
    36  
    37  const goodPassword = "good"
    38  
    39  func mustJSONRequest(t *testing.T, method string, p string, body string) *http.Request {
    40  	req, err := http.NewRequest(method, path.Join(authPrefix, p), strings.NewReader(body))
    41  	if err != nil {
    42  		t.Fatalf("Error making JSON request: %s %s %s\n", method, p, body)
    43  	}
    44  	req.Header.Set("Content-Type", "application/json")
    45  	return req
    46  }
    47  
    48  type mockAuthStore struct {
    49  	users   map[string]*auth.User
    50  	roles   map[string]*auth.Role
    51  	err     error
    52  	enabled bool
    53  }
    54  
    55  func (s *mockAuthStore) AllUsers() ([]string, error) {
    56  	var us []string
    57  	for u := range s.users {
    58  		us = append(us, u)
    59  	}
    60  	sort.Strings(us)
    61  	return us, s.err
    62  }
    63  func (s *mockAuthStore) GetUser(name string) (auth.User, error) {
    64  	u, ok := s.users[name]
    65  	if !ok {
    66  		return auth.User{}, s.err
    67  	}
    68  	return *u, s.err
    69  }
    70  func (s *mockAuthStore) CreateOrUpdateUser(user auth.User) (out auth.User, created bool, err error) {
    71  	if s.users == nil {
    72  		out, err = s.CreateUser(user)
    73  		return out, true, err
    74  	}
    75  	out, err = s.UpdateUser(user)
    76  	return out, false, err
    77  }
    78  func (s *mockAuthStore) CreateUser(user auth.User) (auth.User, error) { return user, s.err }
    79  func (s *mockAuthStore) DeleteUser(name string) error                 { return s.err }
    80  func (s *mockAuthStore) UpdateUser(user auth.User) (auth.User, error) {
    81  	return *s.users[user.User], s.err
    82  }
    83  func (s *mockAuthStore) AllRoles() ([]string, error) {
    84  	return []string{"awesome", "guest", "root"}, s.err
    85  }
    86  func (s *mockAuthStore) GetRole(name string) (auth.Role, error) {
    87  	r, ok := s.roles[name]
    88  	if ok {
    89  		return *r, s.err
    90  	}
    91  	return auth.Role{}, fmt.Errorf("%q does not exist (%v)", name, s.err)
    92  }
    93  func (s *mockAuthStore) CreateRole(role auth.Role) error { return s.err }
    94  func (s *mockAuthStore) DeleteRole(name string) error    { return s.err }
    95  func (s *mockAuthStore) UpdateRole(role auth.Role) (auth.Role, error) {
    96  	return *s.roles[role.Role], s.err
    97  }
    98  func (s *mockAuthStore) AuthEnabled() bool  { return s.enabled }
    99  func (s *mockAuthStore) EnableAuth() error  { return s.err }
   100  func (s *mockAuthStore) DisableAuth() error { return s.err }
   101  
   102  func (s *mockAuthStore) CheckPassword(user auth.User, password string) bool {
   103  	return user.Password == password
   104  }
   105  
   106  func (s *mockAuthStore) HashPassword(password string) (string, error) {
   107  	return password, nil
   108  }
   109  
   110  func TestAuthFlow(t *testing.T) {
   111  	api.EnableCapability(api.AuthCapability)
   112  	var testCases = []struct {
   113  		req   *http.Request
   114  		store mockAuthStore
   115  
   116  		wcode int
   117  		wbody string
   118  	}{
   119  		{
   120  			req:   mustJSONRequest(t, "PUT", "users/alice", `{{{{{{{`),
   121  			store: mockAuthStore{},
   122  			wcode: http.StatusBadRequest,
   123  			wbody: `{"message":"Invalid JSON in request body."}`,
   124  		},
   125  		{
   126  			req:   mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "password": "goodpassword"}`),
   127  			store: mockAuthStore{enabled: true},
   128  			wcode: http.StatusUnauthorized,
   129  			wbody: `{"message":"Insufficient credentials"}`,
   130  		},
   131  		// Users
   132  		{
   133  			req: mustJSONRequest(t, "GET", "users", ""),
   134  			store: mockAuthStore{
   135  				users: map[string]*auth.User{
   136  					"alice": {
   137  						User:     "alice",
   138  						Roles:    []string{"alicerole", "guest"},
   139  						Password: "wheeee",
   140  					},
   141  					"bob": {
   142  						User:     "bob",
   143  						Roles:    []string{"guest"},
   144  						Password: "wheeee",
   145  					},
   146  					"root": {
   147  						User:     "root",
   148  						Roles:    []string{"root"},
   149  						Password: "wheeee",
   150  					},
   151  				},
   152  				roles: map[string]*auth.Role{
   153  					"alicerole": {
   154  						Role: "alicerole",
   155  					},
   156  					"guest": {
   157  						Role: "guest",
   158  					},
   159  					"root": {
   160  						Role: "root",
   161  					},
   162  				},
   163  			},
   164  			wcode: http.StatusOK,
   165  			wbody: `{"users":[` +
   166  				`{"user":"alice","roles":[` +
   167  				`{"role":"alicerole","permissions":{"kv":{"read":null,"write":null}}},` +
   168  				`{"role":"guest","permissions":{"kv":{"read":null,"write":null}}}` +
   169  				`]},` +
   170  				`{"user":"bob","roles":[{"role":"guest","permissions":{"kv":{"read":null,"write":null}}}]},` +
   171  				`{"user":"root","roles":[{"role":"root","permissions":{"kv":{"read":null,"write":null}}}]}]}`,
   172  		},
   173  		{
   174  			req: mustJSONRequest(t, "GET", "users/alice", ""),
   175  			store: mockAuthStore{
   176  				users: map[string]*auth.User{
   177  					"alice": {
   178  						User:     "alice",
   179  						Roles:    []string{"alicerole"},
   180  						Password: "wheeee",
   181  					},
   182  				},
   183  				roles: map[string]*auth.Role{
   184  					"alicerole": {
   185  						Role: "alicerole",
   186  					},
   187  				},
   188  			},
   189  			wcode: http.StatusOK,
   190  			wbody: `{"user":"alice","roles":[{"role":"alicerole","permissions":{"kv":{"read":null,"write":null}}}]}`,
   191  		},
   192  		{
   193  			req:   mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "password": "goodpassword"}`),
   194  			store: mockAuthStore{},
   195  			wcode: http.StatusCreated,
   196  			wbody: `{"user":"alice","roles":null}`,
   197  		},
   198  		{
   199  			req:   mustJSONRequest(t, "DELETE", "users/alice", ``),
   200  			store: mockAuthStore{},
   201  			wcode: http.StatusOK,
   202  			wbody: ``,
   203  		},
   204  		{
   205  			req: mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "password": "goodpassword"}`),
   206  			store: mockAuthStore{
   207  				users: map[string]*auth.User{
   208  					"alice": {
   209  						User:     "alice",
   210  						Roles:    []string{"alicerole", "guest"},
   211  						Password: "wheeee",
   212  					},
   213  				},
   214  			},
   215  			wcode: http.StatusOK,
   216  			wbody: `{"user":"alice","roles":["alicerole","guest"]}`,
   217  		},
   218  		{
   219  			req: mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "grant": ["alicerole"]}`),
   220  			store: mockAuthStore{
   221  				users: map[string]*auth.User{
   222  					"alice": {
   223  						User:     "alice",
   224  						Roles:    []string{"alicerole", "guest"},
   225  						Password: "wheeee",
   226  					},
   227  				},
   228  			},
   229  			wcode: http.StatusOK,
   230  			wbody: `{"user":"alice","roles":["alicerole","guest"]}`,
   231  		},
   232  		{
   233  			req: mustJSONRequest(t, "GET", "users/alice", ``),
   234  			store: mockAuthStore{
   235  				users: map[string]*auth.User{},
   236  				err:   auth.Error{Status: http.StatusNotFound, Errmsg: "auth: User alice doesn't exist."},
   237  			},
   238  			wcode: http.StatusNotFound,
   239  			wbody: `{"message":"auth: User alice doesn't exist."}`,
   240  		},
   241  		{
   242  			req: mustJSONRequest(t, "GET", "roles/manager", ""),
   243  			store: mockAuthStore{
   244  				roles: map[string]*auth.Role{
   245  					"manager": {
   246  						Role: "manager",
   247  					},
   248  				},
   249  			},
   250  			wcode: http.StatusOK,
   251  			wbody: `{"role":"manager","permissions":{"kv":{"read":null,"write":null}}}`,
   252  		},
   253  		{
   254  			req:   mustJSONRequest(t, "DELETE", "roles/manager", ``),
   255  			store: mockAuthStore{},
   256  			wcode: http.StatusOK,
   257  			wbody: ``,
   258  		},
   259  		{
   260  			req:   mustJSONRequest(t, "PUT", "roles/manager", `{"role":"manager","permissions":{"kv":{"read":[],"write":[]}}}`),
   261  			store: mockAuthStore{},
   262  			wcode: http.StatusCreated,
   263  			wbody: `{"role":"manager","permissions":{"kv":{"read":[],"write":[]}}}`,
   264  		},
   265  		{
   266  			req: mustJSONRequest(t, "PUT", "roles/manager", `{"role":"manager","revoke":{"kv":{"read":["foo"],"write":[]}}}`),
   267  			store: mockAuthStore{
   268  				roles: map[string]*auth.Role{
   269  					"manager": {
   270  						Role: "manager",
   271  					},
   272  				},
   273  			},
   274  			wcode: http.StatusOK,
   275  			wbody: `{"role":"manager","permissions":{"kv":{"read":null,"write":null}}}`,
   276  		},
   277  		{
   278  			req: mustJSONRequest(t, "GET", "roles", ""),
   279  			store: mockAuthStore{
   280  				roles: map[string]*auth.Role{
   281  					"awesome": {
   282  						Role: "awesome",
   283  					},
   284  					"guest": {
   285  						Role: "guest",
   286  					},
   287  					"root": {
   288  						Role: "root",
   289  					},
   290  				},
   291  			},
   292  			wcode: http.StatusOK,
   293  			wbody: `{"roles":[{"role":"awesome","permissions":{"kv":{"read":null,"write":null}}},` +
   294  				`{"role":"guest","permissions":{"kv":{"read":null,"write":null}}},` +
   295  				`{"role":"root","permissions":{"kv":{"read":null,"write":null}}}]}`,
   296  		},
   297  		{
   298  			req: mustJSONRequest(t, "GET", "enable", ""),
   299  			store: mockAuthStore{
   300  				enabled: true,
   301  			},
   302  			wcode: http.StatusOK,
   303  			wbody: `{"enabled":true}`,
   304  		},
   305  		{
   306  			req: mustJSONRequest(t, "PUT", "enable", ""),
   307  			store: mockAuthStore{
   308  				enabled: false,
   309  			},
   310  			wcode: http.StatusOK,
   311  			wbody: ``,
   312  		},
   313  		{
   314  			req: (func() *http.Request {
   315  				req := mustJSONRequest(t, "DELETE", "enable", "")
   316  				req.SetBasicAuth("root", "good")
   317  				return req
   318  			})(),
   319  			store: mockAuthStore{
   320  				enabled: true,
   321  				users: map[string]*auth.User{
   322  					"root": {
   323  						User:     "root",
   324  						Password: goodPassword,
   325  						Roles:    []string{"root"},
   326  					},
   327  				},
   328  				roles: map[string]*auth.Role{
   329  					"root": {
   330  						Role: "root",
   331  					},
   332  				},
   333  			},
   334  			wcode: http.StatusOK,
   335  			wbody: ``,
   336  		},
   337  		{
   338  			req: (func() *http.Request {
   339  				req := mustJSONRequest(t, "DELETE", "enable", "")
   340  				req.SetBasicAuth("root", "bad")
   341  				return req
   342  			})(),
   343  			store: mockAuthStore{
   344  				enabled: true,
   345  				users: map[string]*auth.User{
   346  					"root": {
   347  						User:     "root",
   348  						Password: goodPassword,
   349  						Roles:    []string{"root"},
   350  					},
   351  				},
   352  				roles: map[string]*auth.Role{
   353  					"root": {
   354  						Role: "guest",
   355  					},
   356  				},
   357  			},
   358  			wcode: http.StatusUnauthorized,
   359  			wbody: `{"message":"Insufficient credentials"}`,
   360  		},
   361  	}
   362  
   363  	for i, tt := range testCases {
   364  		mux := http.NewServeMux()
   365  		h := &authHandler{
   366  			sec:     &tt.store,
   367  			cluster: &fakeCluster{id: 1},
   368  		}
   369  		handleAuth(mux, h)
   370  		rw := httptest.NewRecorder()
   371  		mux.ServeHTTP(rw, tt.req)
   372  		if rw.Code != tt.wcode {
   373  			t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
   374  		}
   375  		g := rw.Body.String()
   376  		g = strings.TrimSpace(g)
   377  		if g != tt.wbody {
   378  			t.Errorf("#%d: got body=%s, want %s", i, g, tt.wbody)
   379  		}
   380  	}
   381  }
   382  
   383  func TestGetUserGrantedWithNonexistingRole(t *testing.T) {
   384  	sh := &authHandler{
   385  		sec: &mockAuthStore{
   386  			users: map[string]*auth.User{
   387  				"root": {
   388  					User:  "root",
   389  					Roles: []string{"root", "foo"},
   390  				},
   391  			},
   392  			roles: map[string]*auth.Role{
   393  				"root": {
   394  					Role: "root",
   395  				},
   396  			},
   397  		},
   398  		cluster: &fakeCluster{id: 1},
   399  	}
   400  	srv := httptest.NewServer(http.HandlerFunc(sh.baseUsers))
   401  	defer srv.Close()
   402  
   403  	req, err := http.NewRequest("GET", "", nil)
   404  	if err != nil {
   405  		t.Fatal(err)
   406  	}
   407  	req.URL, err = url.Parse(srv.URL)
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  	req.Header.Set("Content-Type", "application/json")
   412  
   413  	cli := http.DefaultClient
   414  	resp, err := cli.Do(req)
   415  	if err != nil {
   416  		t.Fatal(err)
   417  	}
   418  	defer resp.Body.Close()
   419  
   420  	var uc usersCollections
   421  	if err := json.NewDecoder(resp.Body).Decode(&uc); err != nil {
   422  		t.Fatal(err)
   423  	}
   424  	if len(uc.Users) != 1 {
   425  		t.Fatalf("expected 1 user, got %+v", uc.Users)
   426  	}
   427  	if uc.Users[0].User != "root" {
   428  		t.Fatalf("expected 'root', got %q", uc.Users[0].User)
   429  	}
   430  	if len(uc.Users[0].Roles) != 1 {
   431  		t.Fatalf("expected 1 role, got %+v", uc.Users[0].Roles)
   432  	}
   433  	if uc.Users[0].Roles[0].Role != "root" {
   434  		t.Fatalf("expected 'root', got %q", uc.Users[0].Roles[0].Role)
   435  	}
   436  }
   437  
   438  func mustAuthRequest(method, username, password string) *http.Request {
   439  	req, err := http.NewRequest(method, "path", strings.NewReader(""))
   440  	if err != nil {
   441  		panic("Cannot make auth request: " + err.Error())
   442  	}
   443  	req.SetBasicAuth(username, password)
   444  	return req
   445  }
   446  
   447  func unauthedRequest(method string) *http.Request {
   448  	req, err := http.NewRequest(method, "path", strings.NewReader(""))
   449  	if err != nil {
   450  		panic("Cannot make request: " + err.Error())
   451  	}
   452  	return req
   453  }
   454  
   455  func tlsAuthedRequest(req *http.Request, certname string) *http.Request {
   456  	bytes, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.pem", certname))
   457  	if err != nil {
   458  		panic(err)
   459  	}
   460  
   461  	block, _ := pem.Decode(bytes)
   462  	cert, err := x509.ParseCertificate(block.Bytes)
   463  	if err != nil {
   464  		panic(err)
   465  	}
   466  
   467  	req.TLS = &tls.ConnectionState{
   468  		VerifiedChains: [][]*x509.Certificate{{cert}},
   469  	}
   470  	return req
   471  }
   472  
   473  func TestPrefixAccess(t *testing.T) {
   474  	var table = []struct {
   475  		key                string
   476  		req                *http.Request
   477  		store              *mockAuthStore
   478  		hasRoot            bool
   479  		hasKeyPrefixAccess bool
   480  		hasRecursiveAccess bool
   481  	}{
   482  		{
   483  			key: "/foo",
   484  			req: mustAuthRequest("GET", "root", "good"),
   485  			store: &mockAuthStore{
   486  				users: map[string]*auth.User{
   487  					"root": {
   488  						User:     "root",
   489  						Password: goodPassword,
   490  						Roles:    []string{"root"},
   491  					},
   492  				},
   493  				roles: map[string]*auth.Role{
   494  					"root": {
   495  						Role: "root",
   496  					},
   497  				},
   498  				enabled: true,
   499  			},
   500  			hasRoot:            true,
   501  			hasKeyPrefixAccess: true,
   502  			hasRecursiveAccess: true,
   503  		},
   504  		{
   505  			key: "/foo",
   506  			req: mustAuthRequest("GET", "user", "good"),
   507  			store: &mockAuthStore{
   508  				users: map[string]*auth.User{
   509  					"user": {
   510  						User:     "user",
   511  						Password: goodPassword,
   512  						Roles:    []string{"foorole"},
   513  					},
   514  				},
   515  				roles: map[string]*auth.Role{
   516  					"foorole": {
   517  						Role: "foorole",
   518  						Permissions: auth.Permissions{
   519  							KV: auth.RWPermission{
   520  								Read:  []string{"/foo"},
   521  								Write: []string{"/foo"},
   522  							},
   523  						},
   524  					},
   525  				},
   526  				enabled: true,
   527  			},
   528  			hasRoot:            false,
   529  			hasKeyPrefixAccess: true,
   530  			hasRecursiveAccess: false,
   531  		},
   532  		{
   533  			key: "/foo",
   534  			req: mustAuthRequest("GET", "user", "good"),
   535  			store: &mockAuthStore{
   536  				users: map[string]*auth.User{
   537  					"user": {
   538  						User:     "user",
   539  						Password: goodPassword,
   540  						Roles:    []string{"foorole"},
   541  					},
   542  				},
   543  				roles: map[string]*auth.Role{
   544  					"foorole": {
   545  						Role: "foorole",
   546  						Permissions: auth.Permissions{
   547  							KV: auth.RWPermission{
   548  								Read:  []string{"/foo*"},
   549  								Write: []string{"/foo*"},
   550  							},
   551  						},
   552  					},
   553  				},
   554  				enabled: true,
   555  			},
   556  			hasRoot:            false,
   557  			hasKeyPrefixAccess: true,
   558  			hasRecursiveAccess: true,
   559  		},
   560  		{
   561  			key: "/foo",
   562  			req: mustAuthRequest("GET", "user", "bad"),
   563  			store: &mockAuthStore{
   564  				users: map[string]*auth.User{
   565  					"user": {
   566  						User:     "user",
   567  						Password: goodPassword,
   568  						Roles:    []string{"foorole"},
   569  					},
   570  				},
   571  				roles: map[string]*auth.Role{
   572  					"foorole": {
   573  						Role: "foorole",
   574  						Permissions: auth.Permissions{
   575  							KV: auth.RWPermission{
   576  								Read:  []string{"/foo*"},
   577  								Write: []string{"/foo*"},
   578  							},
   579  						},
   580  					},
   581  				},
   582  				enabled: true,
   583  			},
   584  			hasRoot:            false,
   585  			hasKeyPrefixAccess: false,
   586  			hasRecursiveAccess: false,
   587  		},
   588  		{
   589  			key: "/foo",
   590  			req: mustAuthRequest("GET", "user", "good"),
   591  			store: &mockAuthStore{
   592  				users:   map[string]*auth.User{},
   593  				err:     errors.New("Not the user"),
   594  				enabled: true,
   595  			},
   596  			hasRoot:            false,
   597  			hasKeyPrefixAccess: false,
   598  			hasRecursiveAccess: false,
   599  		},
   600  		{
   601  			key: "/foo",
   602  			req: mustJSONRequest(t, "GET", "somepath", ""),
   603  			store: &mockAuthStore{
   604  				users: map[string]*auth.User{
   605  					"user": {
   606  						User:     "user",
   607  						Password: goodPassword,
   608  						Roles:    []string{"foorole"},
   609  					},
   610  				},
   611  				roles: map[string]*auth.Role{
   612  					"guest": {
   613  						Role: "guest",
   614  						Permissions: auth.Permissions{
   615  							KV: auth.RWPermission{
   616  								Read:  []string{"/foo*"},
   617  								Write: []string{"/foo*"},
   618  							},
   619  						},
   620  					},
   621  				},
   622  				enabled: true,
   623  			},
   624  			hasRoot:            false,
   625  			hasKeyPrefixAccess: true,
   626  			hasRecursiveAccess: true,
   627  		},
   628  		{
   629  			key: "/bar",
   630  			req: mustJSONRequest(t, "GET", "somepath", ""),
   631  			store: &mockAuthStore{
   632  				users: map[string]*auth.User{
   633  					"user": {
   634  						User:     "user",
   635  						Password: goodPassword,
   636  						Roles:    []string{"foorole"},
   637  					},
   638  				},
   639  				roles: map[string]*auth.Role{
   640  					"guest": {
   641  						Role: "guest",
   642  						Permissions: auth.Permissions{
   643  							KV: auth.RWPermission{
   644  								Read:  []string{"/foo*"},
   645  								Write: []string{"/foo*"},
   646  							},
   647  						},
   648  					},
   649  				},
   650  				enabled: true,
   651  			},
   652  			hasRoot:            false,
   653  			hasKeyPrefixAccess: false,
   654  			hasRecursiveAccess: false,
   655  		},
   656  		// check access for multiple roles
   657  		{
   658  			key: "/foo",
   659  			req: mustAuthRequest("GET", "user", "good"),
   660  			store: &mockAuthStore{
   661  				users: map[string]*auth.User{
   662  					"user": {
   663  						User:     "user",
   664  						Password: goodPassword,
   665  						Roles:    []string{"role1", "role2"},
   666  					},
   667  				},
   668  				roles: map[string]*auth.Role{
   669  					"role1": {
   670  						Role: "role1",
   671  					},
   672  					"role2": {
   673  						Role: "role2",
   674  						Permissions: auth.Permissions{
   675  							KV: auth.RWPermission{
   676  								Read:  []string{"/foo"},
   677  								Write: []string{"/foo"},
   678  							},
   679  						},
   680  					},
   681  				},
   682  				enabled: true,
   683  			},
   684  			hasRoot:            false,
   685  			hasKeyPrefixAccess: true,
   686  			hasRecursiveAccess: false,
   687  		},
   688  		{
   689  			key: "/foo",
   690  			req: (func() *http.Request {
   691  				req := mustJSONRequest(t, "GET", "somepath", "")
   692  				req.Header.Set("Authorization", "malformedencoding")
   693  				return req
   694  			})(),
   695  			store: &mockAuthStore{
   696  				enabled: true,
   697  				users: map[string]*auth.User{
   698  					"root": {
   699  						User:     "root",
   700  						Password: goodPassword,
   701  						Roles:    []string{"root"},
   702  					},
   703  				},
   704  				roles: map[string]*auth.Role{
   705  					"guest": {
   706  						Role: "guest",
   707  						Permissions: auth.Permissions{
   708  							KV: auth.RWPermission{
   709  								Read:  []string{"/foo*"},
   710  								Write: []string{"/foo*"},
   711  							},
   712  						},
   713  					},
   714  				},
   715  			},
   716  			hasRoot:            false,
   717  			hasKeyPrefixAccess: false,
   718  			hasRecursiveAccess: false,
   719  		},
   720  		{ // guest access in non-TLS mode
   721  			key: "/foo",
   722  			req: (func() *http.Request {
   723  				return mustJSONRequest(t, "GET", "somepath", "")
   724  			})(),
   725  			store: &mockAuthStore{
   726  				enabled: true,
   727  				users: map[string]*auth.User{
   728  					"root": {
   729  						User:     "root",
   730  						Password: goodPassword,
   731  						Roles:    []string{"root"},
   732  					},
   733  				},
   734  				roles: map[string]*auth.Role{
   735  					"guest": {
   736  						Role: "guest",
   737  						Permissions: auth.Permissions{
   738  							KV: auth.RWPermission{
   739  								Read:  []string{"/foo*"},
   740  								Write: []string{"/foo*"},
   741  							},
   742  						},
   743  					},
   744  				},
   745  			},
   746  			hasRoot:            false,
   747  			hasKeyPrefixAccess: true,
   748  			hasRecursiveAccess: true,
   749  		},
   750  	}
   751  
   752  	for i, tt := range table {
   753  		if tt.hasRoot != hasRootAccess(tt.store, tt.req, true) {
   754  			t.Errorf("#%d: hasRoot doesn't match (expected %v)", i, tt.hasRoot)
   755  		}
   756  		if tt.hasKeyPrefixAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, false, true) {
   757  			t.Errorf("#%d: hasKeyPrefixAccess doesn't match (expected %v)", i, tt.hasRoot)
   758  		}
   759  		if tt.hasRecursiveAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, true, true) {
   760  			t.Errorf("#%d: hasRecursiveAccess doesn't match (expected %v)", i, tt.hasRoot)
   761  		}
   762  	}
   763  }
   764  
   765  func TestUserFromClientCertificate(t *testing.T) {
   766  	witherror := &mockAuthStore{
   767  		users: map[string]*auth.User{
   768  			"user": {
   769  				User:     "user",
   770  				Roles:    []string{"root"},
   771  				Password: "password",
   772  			},
   773  			"basicauth": {
   774  				User:     "basicauth",
   775  				Roles:    []string{"root"},
   776  				Password: "password",
   777  			},
   778  		},
   779  		roles: map[string]*auth.Role{
   780  			"root": {
   781  				Role: "root",
   782  			},
   783  		},
   784  		err: errors.New(""),
   785  	}
   786  
   787  	noerror := &mockAuthStore{
   788  		users: map[string]*auth.User{
   789  			"user": {
   790  				User:     "user",
   791  				Roles:    []string{"root"},
   792  				Password: "password",
   793  			},
   794  			"basicauth": {
   795  				User:     "basicauth",
   796  				Roles:    []string{"root"},
   797  				Password: "password",
   798  			},
   799  		},
   800  		roles: map[string]*auth.Role{
   801  			"root": {
   802  				Role: "root",
   803  			},
   804  		},
   805  	}
   806  
   807  	var table = []struct {
   808  		req        *http.Request
   809  		userExists bool
   810  		store      auth.Store
   811  		username   string
   812  	}{
   813  		{
   814  			// non tls request
   815  			req:        unauthedRequest("GET"),
   816  			userExists: false,
   817  			store:      witherror,
   818  		},
   819  		{
   820  			// cert with cn of existing user
   821  			req:        tlsAuthedRequest(unauthedRequest("GET"), "user"),
   822  			userExists: true,
   823  			username:   "user",
   824  			store:      noerror,
   825  		},
   826  		{
   827  			// cert with cn of non-existing user
   828  			req:        tlsAuthedRequest(unauthedRequest("GET"), "otheruser"),
   829  			userExists: false,
   830  			store:      witherror,
   831  		},
   832  	}
   833  
   834  	for i, tt := range table {
   835  		user := userFromClientCertificate(tt.store, tt.req)
   836  		userExists := user != nil
   837  
   838  		if tt.userExists != userExists {
   839  			t.Errorf("#%d: userFromClientCertificate doesn't match (expected %v)", i, tt.userExists)
   840  		}
   841  		if user != nil && (tt.username != user.User) {
   842  			t.Errorf("#%d: userFromClientCertificate username doesn't match (expected %s, got %s)", i, tt.username, user.User)
   843  		}
   844  	}
   845  }
   846  
   847  func TestUserFromBasicAuth(t *testing.T) {
   848  	sec := &mockAuthStore{
   849  		users: map[string]*auth.User{
   850  			"user": {
   851  				User:     "user",
   852  				Roles:    []string{"root"},
   853  				Password: "password",
   854  			},
   855  		},
   856  		roles: map[string]*auth.Role{
   857  			"root": {
   858  				Role: "root",
   859  			},
   860  		},
   861  	}
   862  
   863  	var table = []struct {
   864  		username   string
   865  		req        *http.Request
   866  		userExists bool
   867  	}{
   868  		{
   869  			// valid user, valid pass
   870  			username:   "user",
   871  			req:        mustAuthRequest("GET", "user", "password"),
   872  			userExists: true,
   873  		},
   874  		{
   875  			// valid user, bad pass
   876  			username:   "user",
   877  			req:        mustAuthRequest("GET", "user", "badpass"),
   878  			userExists: false,
   879  		},
   880  		{
   881  			// valid user, no pass
   882  			username:   "user",
   883  			req:        mustAuthRequest("GET", "user", ""),
   884  			userExists: false,
   885  		},
   886  		{
   887  			// missing user
   888  			username:   "missing",
   889  			req:        mustAuthRequest("GET", "missing", "badpass"),
   890  			userExists: false,
   891  		},
   892  		{
   893  			// no basic auth
   894  			req:        unauthedRequest("GET"),
   895  			userExists: false,
   896  		},
   897  	}
   898  
   899  	for i, tt := range table {
   900  		user := userFromBasicAuth(sec, tt.req)
   901  		userExists := user != nil
   902  
   903  		if tt.userExists != userExists {
   904  			t.Errorf("#%d: userFromBasicAuth doesn't match (expected %v)", i, tt.userExists)
   905  		}
   906  		if user != nil && (tt.username != user.User) {
   907  			t.Errorf("#%d: userFromBasicAuth username doesn't match (expected %s, got %s)", i, tt.username, user.User)
   908  		}
   909  	}
   910  }