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
15package v2http
16
17import (
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
37const goodPassword = "good"
38
39func 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
48type mockAuthStore struct {
49	users   map[string]*auth.User
50	roles   map[string]*auth.Role
51	err     error
52	enabled bool
53}
54
55func (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}
63func (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}
70func (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}
78func (s *mockAuthStore) CreateUser(user auth.User) (auth.User, error) { return user, s.err }
79func (s *mockAuthStore) DeleteUser(name string) error                 { return s.err }
80func (s *mockAuthStore) UpdateUser(user auth.User) (auth.User, error) {
81	return *s.users[user.User], s.err
82}
83func (s *mockAuthStore) AllRoles() ([]string, error) {
84	return []string{"awesome", "guest", "root"}, s.err
85}
86func (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}
93func (s *mockAuthStore) CreateRole(role auth.Role) error { return s.err }
94func (s *mockAuthStore) DeleteRole(name string) error    { return s.err }
95func (s *mockAuthStore) UpdateRole(role auth.Role) (auth.Role, error) {
96	return *s.roles[role.Role], s.err
97}
98func (s *mockAuthStore) AuthEnabled() bool  { return s.enabled }
99func (s *mockAuthStore) EnableAuth() error  { return s.err }
100func (s *mockAuthStore) DisableAuth() error { return s.err }
101
102func (s *mockAuthStore) CheckPassword(user auth.User, password string) bool {
103	return user.Password == password
104}
105
106func (s *mockAuthStore) HashPassword(password string) (string, error) {
107	return password, nil
108}
109
110func 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
383func 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
438func 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
447func 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
455func 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
473func 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
765func 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
847func 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}
911