diff --git a/server/authenticate.go b/server/authenticate.go
index e7a1621..8a8059f 100644
--- a/server/authenticate.go
+++ b/server/authenticate.go
@@ -1,10 +1,12 @@
package main
import (
+ "context"
"encoding/base64"
"encoding/json"
"errors"
"hash/crc32"
+ "log"
"net/http"
"os"
"time"
@@ -16,9 +18,21 @@ var cookieSecret = os.Getenv("COOKIE_SECRET")
type User struct {
User string
+ Group string
Groups []string
}
+func (user User) Is(other User) bool {
+ for i := range user.Groups {
+ if i >= len(other.Groups) || user.Groups[i] != other.Groups[i] {
+ return false
+ }
+ }
+ return user.User == other.User &&
+ user.Group == other.Group &&
+ len(user.Groups) == len(other.Groups)
+}
+
type Cookie struct {
Hash string
Salt string
@@ -27,21 +41,30 @@ type Cookie struct {
func (server *Server) authenticate(w http.ResponseWriter, r *http.Request) (*Server, bool, error) {
if done, err := server.parseLogin(w, r); err != nil {
+ log.Printf("error parsing login: %v", err)
+ return nil, false, err
+ } else if done {
+ log.Printf("login rendered body")
+ return nil, true, nil
+ }
+
+ if ok, err := needsLogin(r); err != nil {
+ log.Printf("error checking if login needed: %v", err)
+ return nil, false, err
+ } else if ok {
+ log.Printf("needs login")
+ promptLogin(w)
+ return nil, true, nil
+ }
+
+ if done, err := changeNamespace(w, r); err != nil {
return nil, false, err
} else if done {
return nil, true, nil
}
- if ok, err := needsLogin(r); err != nil {
- return nil, false, err
- } else if ok {
- promptLogin(w)
- return nil, true, nil
- }
-
user, _ := loginCookie(r)
- namespace, _ := namespaceCookie(r)
- return server.WithLoggedIn(user.User, namespace, user.Groups), false, nil
+ return server.WithUser(user.User, user.Group, user.Groups), false, nil
}
func promptLogin(w http.ResponseWriter) {
@@ -54,6 +77,7 @@ func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) (bool,
if !ok {
return false, nil
}
+
ok, err := server.auth.Login(username, password)
if err != nil {
return false, err
@@ -62,6 +86,7 @@ func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) (bool,
promptLogin(w)
return true, nil
}
+
groups, err := server.auth.Groups(username)
if err != nil {
return false, err
@@ -69,11 +94,47 @@ func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) (bool,
if len(groups) == 0 {
return false, errors.New("user has no groups")
}
- setLoginCookie(w, r, User{
+
+ user := User{
User: username,
Groups: groups,
- })
- setNamespaceCookie(w, r, groups[0])
+ Group: groups[0],
+ }
+
+ olduser, _ := loginCookie(r)
+ for i := range groups {
+ if groups[i] == olduser.Group {
+ user.Group = olduser.Group
+ }
+ }
+ log.Printf("%+v => %+v", olduser, user)
+
+ setLoginCookie(w, r, user)
+ return false, nil
+}
+
+func changeNamespace(w http.ResponseWriter, r *http.Request) (bool, error) {
+ want := r.URL.Query().Get("namespace")
+ if want == "" {
+ return false, nil
+ }
+
+ user, ok := loginCookie(r)
+ if !ok {
+ promptLogin(w)
+ return true, nil
+ }
+
+ if user.Group == want {
+ return false, nil
+ }
+ for i := range user.Groups {
+ if want == user.Groups[i] {
+ user.Group = want
+ setLoginCookie(w, r, user)
+ return false, nil
+ }
+ }
return false, nil
}
@@ -82,12 +143,8 @@ func needsLogin(r *http.Request) (bool, error) {
if !ok {
return true, nil
}
- group, ok := namespaceCookie(r)
- if !ok {
- return true, nil
- }
for i := range user.Groups {
- if group == user.Groups[i] {
+ if user.Group == user.Groups[i] {
return false, nil
}
}
@@ -99,41 +156,39 @@ func setLoginCookie(w http.ResponseWriter, r *http.Request, user User) {
Name: "login",
Value: encodeUserCookie(user),
Expires: time.Now().Add(time.Hour * 24),
+ Path: "/",
}
- w.Header().Add("Set-Cookie", cookie.String())
- r.AddCookie(cookie)
+ if was, ok := requestLoginCookie(r); !ok || !was.Is(user) {
+ w.Header().Set("Set-Cookie", cookie.String())
+ }
+ log.Printf("setting login cookie: %+v", user)
+ *r = *r.WithContext(context.WithValue(r.Context(), "LOGIN_COOKIE", cookie.Value))
}
func loginCookie(r *http.Request) (User, bool) {
+ if v := r.Context().Value("LOGIN_COOKIE"); v != nil {
+ log.Printf("login cookie from ctx")
+ return decodeUserCookie(v.(string))
+ }
+ return requestLoginCookie(r)
+}
+
+func requestLoginCookie(r *http.Request) (User, bool) {
c, ok := getCookie("login", r)
+ log.Printf("request login cookie: %v, %v", c, ok)
if !ok {
return User{}, false
}
return decodeUserCookie(c)
}
-func setNamespaceCookie(w http.ResponseWriter, r *http.Request, s string) {
- cookie := &http.Cookie{
- Name: "namespace",
- Value: s,
- Expires: time.Now().Add(time.Hour * 24),
- }
- w.Header().Add("Set-Cookie", cookie.String())
- r.AddCookie(cookie)
-}
-
-func namespaceCookie(r *http.Request) (string, bool) {
- return getCookie("namespace", r)
-}
-
func getCookie(key string, r *http.Request) (string, bool) {
- cookies := r.Cookies()
- for i := range cookies {
- if cookies[i].Name == key && (cookies[i].Expires.IsZero() || time.Now().Before(cookies[i].Expires)) {
- return cookies[i].Value, true
- }
+ cookie, err := r.Cookie(key)
+ if err != nil {
+ log.Printf("err getting cookie %s: %v: %+v", key, err, r.Cookies())
+ return "", false
}
- return "", false
+ return cookie.Value, cookie.Expires.IsZero() || time.Now().Before(cookie.Expires)
}
func decodeUserCookie(raw string) (User, bool) {
diff --git a/server/authenticate_test.go b/server/authenticate_test.go
index ae72a64..7519d55 100644
--- a/server/authenticate_test.go
+++ b/server/authenticate_test.go
@@ -65,9 +65,6 @@ func TestGetSetLoginCookie(t *testing.T) {
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 {
@@ -78,9 +75,110 @@ func TestGetSetLoginCookie(t *testing.T) {
}
}
+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"}}
+ 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)
@@ -93,7 +191,9 @@ func TestNeedsLogin(t *testing.T) {
t.Run("no namespace provided", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
- setLoginCookie(w, r, user)
+ u2 := user
+ u2.Group = ""
+ setLoginCookie(w, r, u2)
if ok, err := needsLogin(r); err != nil {
t.Fatal(err)
} else if !ok {
@@ -104,7 +204,6 @@ func TestNeedsLogin(t *testing.T) {
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)
@@ -115,8 +214,9 @@ func TestNeedsLogin(t *testing.T) {
t.Run("bad namespace", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
- setLoginCookie(w, r, user)
- setNamespaceCookie(w, r, "teehee")
+ u2 := user
+ u2.Group = "teehee"
+ setLoginCookie(w, r, u2)
if ok, err := needsLogin(r); err != nil {
t.Fatal(err)
} else if !ok {
@@ -127,7 +227,6 @@ func TestNeedsLogin(t *testing.T) {
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 {
@@ -172,15 +271,9 @@ func TestServerParseLogin(t *testing.T) {
if w.Code == http.StatusUnauthorized {
t.Error(w.Code)
}
- if len(w.Header()["Set-Cookie"]) != 2 {
+ if len(w.Header()["Set-Cookie"]) != 1 {
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)
}
@@ -192,8 +285,7 @@ func TestServerAuthenticate(t *testing.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")
+ 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)
@@ -204,20 +296,20 @@ func TestServerAuthenticate(t *testing.T) {
if server == s2 {
t.Error(done)
}
- if server.loggedIn != nil {
- t.Error(server.loggedIn)
+ if server.user != nil {
+ t.Error(server.user)
}
- if s2.loggedIn == nil {
- t.Error(s2.loggedIn)
+ if s2.user == nil {
+ t.Error(s2.user)
}
- if s2.loggedIn.user != "user" {
- t.Error(s2.loggedIn)
+ if s2.user.User != "user" {
+ t.Error(s2.user)
}
- if s2.loggedIn.group != "othergroup" {
- t.Error(s2.loggedIn)
+ if s2.user.Group != "othergroup" {
+ t.Error(s2.user)
}
- if fmt.Sprint(s2.loggedIn.groups) != fmt.Sprint([]string{"group", "othergroup"}) {
- t.Error(s2.loggedIn)
+ if fmt.Sprint(s2.user.Groups) != fmt.Sprint([]string{"group", "othergroup"}) {
+ t.Error(s2.user)
}
})
@@ -235,25 +327,25 @@ func TestServerAuthenticate(t *testing.T) {
if server == s2 {
t.Error(done)
}
- if server.loggedIn != nil {
- t.Error(server.loggedIn)
+ if server.user != nil {
+ t.Error(server.user)
}
- if s2.loggedIn == nil {
- t.Error(s2.loggedIn)
+ if s2.user == nil {
+ t.Error(s2.user)
}
- if s2.loggedIn.user != "user" {
- t.Error(s2.loggedIn)
+ if s2.user.User != "user" {
+ t.Error(s2.user)
}
- if s2.loggedIn.group != "group" {
- t.Error(s2.loggedIn)
+ if s2.user.Group != "group" {
+ t.Error(s2.user)
}
- if fmt.Sprint(s2.loggedIn.groups) != fmt.Sprint([]string{"group", "othergroup"}) {
- t.Error(s2.loggedIn)
+ 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"]) != 2 {
+ if len(w.Header()["Set-Cookie"]) != 1 {
t.Error(w.Header())
}
})
diff --git a/server/main.go b/server/main.go
index b40663c..271ede1 100644
--- a/server/main.go
+++ b/server/main.go
@@ -3,6 +3,7 @@ package main
import (
"errors"
"local/args"
+ "log"
"net/http"
"os"
"path"
@@ -26,6 +27,7 @@ func main() {
if err := s.Routes(); err != nil {
panic(err)
}
+ log.Printf("listening on %v with %s", as.GetInt("p"), as.GetString("auth"))
if err := http.ListenAndServe(":"+strconv.Itoa(as.GetInt("p")), s); err != nil {
panic(err)
}
diff --git a/server/public/ui/render.go b/server/public/ui/render.go
index 57fcdb5..4482782 100644
--- a/server/public/ui/render.go
+++ b/server/public/ui/render.go
@@ -36,13 +36,13 @@ func main() {
return oneT
}
data := map[string]interface{}{
+ "Namespaces": []string{"datastore", "dp-orchestration"},
"This": map[string]interface{}{
- "Namespaces": []string{"datastore", "dp-orchestration"},
- "ID": "id00/id11",
- "Title": "title id11",
- "ReadOnly": false,
- "PID": "id00",
- "PTitle": "title id00",
+ "ID": "id00/id11",
+ "Title": "title id11",
+ "ReadOnly": false,
+ "PID": "id00",
+ "PTitle": "title id00",
"Content": `# hello
## world
diff --git a/server/public/ui/templates/_namespace.ctmpl b/server/public/ui/templates/_namespace.ctmpl
index 3b17f96..4cf1ccb 100644
--- a/server/public/ui/templates/_namespace.ctmpl
+++ b/server/public/ui/templates/_namespace.ctmpl
@@ -1,16 +1,16 @@
{{ define "_namespace" }}
-
-{{ if .This.Namespaces }}
-
-{{ end }}
+
+ {{ $cur := .Namespace }}
+ {{ if .Namespaces }}
+
+ {{ end }}
{{ end }}
diff --git a/server/server.go b/server/server.go
index 04ad180..0ef9a50 100644
--- a/server/server.go
+++ b/server/server.go
@@ -26,37 +26,31 @@ import (
)
type Server struct {
- router *router.Router
- root string
- auth auth
- loggedIn *loggedIn
-}
-
-type loggedIn struct {
- user string
- group string
- groups []string
+ router *router.Router
+ root string
+ auth auth
+ user *User
}
func NewServer(root string, auth auth) *Server {
return &Server{
- router: router.New(),
- root: root,
- auth: auth,
+ root: root,
+ auth: auth,
}
}
-func (server *Server) WithLoggedIn(user, group string, groups []string) *Server {
+func (server *Server) WithUser(user, group string, groups []string) *Server {
s2 := *server
- s2.loggedIn = &loggedIn{
- user: user,
- group: group,
- groups: groups,
+ s2.user = &User{
+ User: user,
+ Group: group,
+ Groups: groups,
}
return &s2
}
func (server *Server) Routes() error {
+ server.router = router.New()
wildcard := func(s string) string {
return strings.TrimSuffix(s, "/") + "/" + router.Wildcard
}
@@ -75,7 +69,6 @@ func (server *Server) Routes() error {
"/ui/search": server.uiSearchHandler,
wildcards("/ui/files"): server.uiFilesHandler,
} {
- log.Printf("listening for %s", path)
if err := server.router.Add(path, server.tryCatchHttpHandler(handler)); err != nil {
return err
}
@@ -85,15 +78,21 @@ func (server *Server) Routes() error {
func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if server.auth != nil {
- if s2, done, err := server.authenticate(w, r); err != nil {
+ s2, done, err := server.authenticate(w, r)
+ if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
- } else if done {
+ }
+ if done {
return
- } else if s2 != nil {
+ }
+ if s2 != nil {
server = s2
}
}
+ if err := server.Routes(); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
server.router.ServeHTTP(w, r)
}
@@ -264,11 +263,20 @@ func (server *Server) uiSearchHandler(w http.ResponseWriter, r *http.Request) er
return err
}
return t.Lookup("search").Execute(w, map[string]interface{}{
- "Results": data,
- "Tree": string(branchesJSON),
+ "Results": data,
+ "Tree": string(branchesJSON),
+ "Namespaces": server.getUser().Groups,
+ "Namespace": server.getUser().Group,
})
}
+func (server *Server) getUser() User {
+ if server.user != nil {
+ return *server.user
+ }
+ return User{}
+}
+
func (server *Server) uiFilesHandler(w http.ResponseWriter, r *http.Request) error {
id := NewID(strings.TrimPrefix(r.URL.Path, "/ui/files"))
t, err := server.uiSubTemplates()
@@ -318,7 +326,9 @@ func (server *Server) uiFilesHandler(w http.ResponseWriter, r *http.Request) err
"PID": id.Pop().String(),
"PTitle": parent.Meta.Title,
},
- "Tree": string(branchesJSON),
+ "Tree": string(branchesJSON),
+ "Namespaces": server.getUser().Groups,
+ "Namespace": server.getUser().Group,
}
return t.Lookup("files").Execute(w, data)
}
@@ -356,7 +366,7 @@ func (server *Server) rootHandler(w http.ResponseWriter, r *http.Request) error
}
func (server *Server) tree() Tree {
- return NewTree(path.Join(server.root, "files"))
+ return NewTree(path.Join(server.root, "files", server.getUser().Group))
}
func (server *Server) diskMediaPath(id string) string {
diff --git a/server/testdata/users.yaml b/server/testdata/users.yaml
new file mode 100644
index 0000000..f0231a1
--- /dev/null
+++ b/server/testdata/users.yaml
@@ -0,0 +1,6 @@
+users:
+ bel:
+ password: bel
+ groups:
+ - g1
+ - g2