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()) } if r.Cookies()[0].Name != "login" { t.Error(r.Cookies()) } got, ok := loginCookie(r) if !ok { t.Error(ok) } if fmt.Sprint(user) != fmt.Sprint(got) { t.Error(user, got) } } func TestNeedsLogin(t *testing.T) { w := httptest.NewRecorder() user := User{User: "user", Groups: []string{"group0", "group1"}} 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) setLoginCookie(w, r, user) 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) setNamespaceCookie(w, r, user.Groups[0]) 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) setLoginCookie(w, r, user) setNamespaceCookie(w, r, "teehee") 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) setNamespaceCookie(w, r, user.Groups[0]) 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"]) != 2 { t.Error(w.Header()) } if len(r.Cookies()) != 2 { t.Error(r.Cookies()) } if v, ok := namespaceCookie(r); !ok || v != "group" { t.Error(r.Cookies()) } 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", Groups: []string{"group", "othergroup"}}) setNamespaceCookie(httptest.NewRecorder(), r, "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.loggedIn != nil { t.Error(server.loggedIn) } if s2.loggedIn == nil { t.Error(s2.loggedIn) } if s2.loggedIn.user != "user" { t.Error(s2.loggedIn) } if s2.loggedIn.group != "othergroup" { t.Error(s2.loggedIn) } if fmt.Sprint(s2.loggedIn.groups) != fmt.Sprint([]string{"group", "othergroup"}) { t.Error(s2.loggedIn) } }) 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.loggedIn != nil { t.Error(server.loggedIn) } if s2.loggedIn == nil { t.Error(s2.loggedIn) } if s2.loggedIn.user != "user" { t.Error(s2.loggedIn) } if s2.loggedIn.group != "group" { t.Error(s2.loggedIn) } if fmt.Sprint(s2.loggedIn.groups) != fmt.Sprint([]string{"group", "othergroup"}) { t.Error(s2.loggedIn) } if w.Code != http.StatusOK { t.Error(w.Code) } if len(w.Header()["Set-Cookie"]) != 2 { 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), } }