From 3c2d0a229c4d3c61ffd259ce8789bff03ea770db Mon Sep 17 00:00:00 2001 From: Gyu-Ho Lee Date: Fri, 6 May 2016 10:41:12 -0700 Subject: [PATCH] v2http: allow empty role for GET /users Fix https://github.com/coreos/etcd/issues/5246. --- etcdserver/api/v2http/client_auth.go | 15 +++-- etcdserver/api/v2http/client_auth_test.go | 80 +++++++++++++++++++++-- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/etcdserver/api/v2http/client_auth.go b/etcdserver/api/v2http/client_auth.go index 83706f04f..a086a3cbe 100644 --- a/etcdserver/api/v2http/client_auth.go +++ b/etcdserver/api/v2http/client_auth.go @@ -285,6 +285,10 @@ type userWithRoles struct { Roles []auth.Role `json:"roles,omitempty"` } +type usersCollections struct { + Users []userWithRoles `json:"users"` +} + func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET") { return @@ -311,9 +315,7 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) { return } - var usersCollections struct { - Users []userWithRoles `json:"users"` - } + ucs := usersCollections{} for _, userName := range users { var user auth.User user, err = sh.sec.GetUser(userName) @@ -327,15 +329,14 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) { var role auth.Role role, err = sh.sec.GetRole(roleName) if err != nil { - writeError(w, r, err) - return + continue } uwr.Roles = append(uwr.Roles, role) } - usersCollections.Users = append(usersCollections.Users, uwr) + ucs.Users = append(ucs.Users, uwr) } - err = json.NewEncoder(w).Encode(usersCollections) + err = json.NewEncoder(w).Encode(ucs) if err != nil { plog.Warningf("baseUsers error encoding on %s", r.URL) diff --git a/etcdserver/api/v2http/client_auth_test.go b/etcdserver/api/v2http/client_auth_test.go index a4f301118..fa779dc64 100644 --- a/etcdserver/api/v2http/client_auth_test.go +++ b/etcdserver/api/v2http/client_auth_test.go @@ -15,10 +15,14 @@ package v2http import ( + "encoding/json" "errors" + "fmt" "net/http" "net/http/httptest" + "net/url" "path" + "sort" "strings" "testing" @@ -43,7 +47,14 @@ type mockAuthStore struct { enabled bool } -func (s *mockAuthStore) AllUsers() ([]string, error) { return []string{"alice", "bob", "root"}, s.err } +func (s *mockAuthStore) AllUsers() ([]string, error) { + var us []string + for u := range s.users { + us = append(us, u) + } + sort.Strings(us) + return us, s.err +} func (s *mockAuthStore) GetUser(name string) (auth.User, error) { u, ok := s.users[name] if !ok { @@ -67,9 +78,15 @@ func (s *mockAuthStore) UpdateUser(user auth.User) (auth.User, error) { func (s *mockAuthStore) AllRoles() ([]string, error) { return []string{"awesome", "guest", "root"}, s.err } -func (s *mockAuthStore) GetRole(name string) (auth.Role, error) { return *s.roles[name], s.err } -func (s *mockAuthStore) CreateRole(role auth.Role) error { return s.err } -func (s *mockAuthStore) DeleteRole(name string) error { return s.err } +func (s *mockAuthStore) GetRole(name string) (auth.Role, error) { + r, ok := s.roles[name] + if ok { + return *r, s.err + } + return auth.Role{}, fmt.Errorf("%q does not exist (%v)", name, s.err) +} +func (s *mockAuthStore) CreateRole(role auth.Role) error { return s.err } +func (s *mockAuthStore) DeleteRole(name string) error { return s.err } func (s *mockAuthStore) UpdateRole(role auth.Role) (auth.Role, error) { return *s.roles[role.Role], s.err } @@ -361,6 +378,61 @@ func TestAuthFlow(t *testing.T) { } } +func TestGetUserGrantedWithNonexistingRole(t *testing.T) { + sh := &authHandler{ + sec: &mockAuthStore{ + users: map[string]*auth.User{ + "root": { + User: "root", + Roles: []string{"root", "foo"}, + }, + }, + roles: map[string]*auth.Role{ + "root": { + Role: "root", + }, + }, + }, + cluster: &fakeCluster{id: 1}, + } + srv := httptest.NewServer(http.HandlerFunc(sh.baseUsers)) + defer srv.Close() + + req, err := http.NewRequest("GET", "", nil) + if err != nil { + t.Fatal(err) + } + req.URL, err = url.Parse(srv.URL) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + + cli := http.DefaultClient + resp, err := cli.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + var uc usersCollections + if err := json.NewDecoder(resp.Body).Decode(&uc); err != nil { + t.Fatal(err) + } + if len(uc.Users) != 1 { + t.Fatalf("expected 1 user, got %+v", uc.Users) + } + if uc.Users[0].User != "root" { + t.Fatalf("expected 'root', got %q", uc.Users[0].User) + } + if len(uc.Users[0].Roles) != 1 { + t.Fatalf("expected 1 role, got %+v", uc.Users[0].Roles) + } + if uc.Users[0].Roles[0].Role != "root" { + t.Fatalf("expected 'root', got %q", uc.Users[0].Roles[0].Role) + } +} + func mustAuthRequest(method, username, password string) *http.Request { req, err := http.NewRequest(method, "path", strings.NewReader("")) if err != nil {