package main import ( "fmt" "net/http" "net/http/httptest" "path" "testing" "time" "github.com/google/uuid" ) func TestEncodeDecodeCookie(t *testing.T) { newTestServer(t) for i := 0; i < 5; i++ { value := uuid.New().String() encoded := encodeCookie(value) for j := 0; j < 5; j++ { decoded, ok := decodeCookie(encoded) if !ok || decoded != value { t.Errorf("value=%s, encoded=%s, decoded=%s", value, encoded, decoded) } } } } func TestEncodeDecodeUserCookie(t *testing.T) { newTestServer(t) user := User{ User: "abc", Groups: []string{"def", "ghi"}, } encoded := encodeUserCookie(user) decoded, ok := decodeUserCookie(encoded) if !ok { t.Fatal(ok) } if fmt.Sprint(user) != fmt.Sprint(decoded) { t.Fatal(user, decoded) } } func TestGetCookie(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{ Name: "abc", Value: "def", Expires: time.Now().Add(time.Hour), }) got, _ := getCookie("abc", r) if got != "def" { t.Fatal(r.Cookies(), got) } } func TestGetSetLoginCookie(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) user := User{User: "a", Groups: []string{"g"}} setLoginCookie(w, r, user) if w.Header().Get("Set-Cookie") == "" { t.Error(w.Header()) } got, ok := loginCookie(r) if !ok { t.Error(ok) } if fmt.Sprint(user) != fmt.Sprint(got) { t.Error(user, got) } } func TestChangeNamespace(t *testing.T) { newTestServer(t) user := User{ User: "user", Groups: []string{"group", "othergroup"}, Group: "group", } t.Run("noop", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) w := httptest.NewRecorder() done, err := changeNamespace(w, r) if err != nil { t.Error(err) } if done { t.Error(done) } }) t.Run("change to ``", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/?namespace=", nil) w := httptest.NewRecorder() done, err := changeNamespace(w, r) if err != nil { t.Error(err) } if done { t.Error(done) } }) t.Run("change to bad", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/?namespace=never", nil) w := httptest.NewRecorder() setLoginCookie(w, r, user) done, err := changeNamespace(w, r) if err != nil { t.Error(err) } if done { t.Error(done) } user, ok := loginCookie(r) if !ok { t.Error(ok) } if user.Group == "never" { t.Error("change namespace acknowledged bad change") } }) t.Run("change without login", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/?namespace="+user.Group, nil) w := httptest.NewRecorder() done, err := changeNamespace(w, r) if err != nil { t.Error(err) } if !done { t.Error(done) } }) t.Run("change to same", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/?namespace="+user.Group, nil) w := httptest.NewRecorder() setLoginCookie(w, r, user) done, err := changeNamespace(w, r) if err != nil { t.Error(err) } if done { t.Error(done) } }) t.Run("change to ok", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/?namespace="+user.Groups[1], nil) w := httptest.NewRecorder() setLoginCookie(w, r, user) done, err := changeNamespace(w, r) if err != nil { t.Error(err) } if done { t.Error(done) } user, ok := loginCookie(r) if !ok { t.Error(ok) } if user.Group != user.Groups[1] { t.Error(user.Group) } if w.Header().Get("Set-Cookie") == "" { t.Error(w.Header()) } }) } func TestNeedsLogin(t *testing.T) { w := httptest.NewRecorder() user := User{User: "user", Groups: []string{"group0", "group1"}, Group: "group0"} t.Run("no login provided", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) if ok, err := needsLogin(r); err != nil { t.Fatal(err) } else if !ok { t.Fatal(ok) } }) t.Run("no namespace provided", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) u2 := user u2.Group = "" setLoginCookie(w, r, u2) if ok, err := needsLogin(r); err != nil { t.Fatal(err) } else if !ok { t.Fatal(ok) } }) t.Run("cookie tampered", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) setLoginCookie(w, r, user) cookieSecret += "modified" if ok, err := needsLogin(r); err != nil { t.Fatal(err) } else if !ok { t.Fatal(ok) } }) t.Run("bad namespace", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) u2 := user u2.Group = "teehee" setLoginCookie(w, r, u2) if ok, err := needsLogin(r); err != nil { t.Fatal(err) } else if !ok { t.Fatal(ok) } }) t.Run("ok", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) setLoginCookie(w, r, user) if ok, err := needsLogin(r); err != nil { t.Fatal(err) } else if ok { t.Fatal(ok) } }) } func TestServerParseLogin(t *testing.T) { server := newTestServer(t) t.Run("no basic auth", func(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) if done, err := server.parseLogin(w, r); done || err != nil { t.Fatal(done, err) } if w.Code == http.StatusUnauthorized { t.Error(w.Code) } }) t.Run("bad basic auth", func(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) r.SetBasicAuth("junk", "junk") if done, err := server.parseLogin(w, r); !done || err != nil { t.Fatal(done, err) } if w.Code != http.StatusUnauthorized { t.Error(w.Code) } }) t.Run("ok", func(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) r.SetBasicAuth("user", "passw") if done, err := server.parseLogin(w, r); done || err != nil { t.Fatal(done, err) } if w.Code == http.StatusUnauthorized { t.Error(w.Code) } if len(w.Header()["Set-Cookie"]) != 1 { t.Error(w.Header()) } if user, ok := loginCookie(r); !ok || user.User != "user" || user.Groups[0] != "group" || user.Groups[1] != "othergroup" { t.Error(user) } }) } func TestServerAuthenticate(t *testing.T) { server := newTestServer(t) t.Run("ok: already logged in", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) setLoginCookie(httptest.NewRecorder(), r, User{User: "user", Group: "othergroup", Groups: []string{"group", "othergroup"}}) s2, done, err := server.authenticate(nil, r) if err != nil { t.Error(err) } if done { t.Error(done) } if server == s2 { t.Error(done) } if server.user != nil { t.Error(server.user) } if s2.user == nil { t.Error(s2.user) } if s2.user.User != "user" { t.Error(s2.user) } if s2.user.Group != "othergroup" { t.Error(s2.user) } if fmt.Sprint(s2.user.Groups) != fmt.Sprint([]string{"group", "othergroup"}) { t.Error(s2.user) } }) t.Run("ok: basic auth", func(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) w := httptest.NewRecorder() r.SetBasicAuth("user", "passw") s2, done, err := server.authenticate(w, r) if err != nil { t.Error(err) } if done { t.Error(done) } if server == s2 { t.Error(done) } if server.user != nil { t.Error(server.user) } if s2.user == nil { t.Error(s2.user) } if s2.user.User != "user" { t.Error(s2.user) } if s2.user.Group != "group" { t.Error(s2.user) } if fmt.Sprint(s2.user.Groups) != fmt.Sprint([]string{"group", "othergroup"}) { t.Error(s2.user) } if w.Code != http.StatusOK { t.Error(w.Code) } if len(w.Header()["Set-Cookie"]) != 1 { t.Error(w.Header()) } }) } func newTestServer(t *testing.T) *Server { cookieSecret = uuid.New().String() p := path.Join(t.TempDir(), "auth.yaml") ensureAndWrite(p, []byte(`{"users":{"user":{"password":"passw", "groups":["group", "othergroup"]}}}`)) return &Server{ auth: NewFileAuth(p), } }