test server auth
parent
09c06a4a0c
commit
b951e057c4
|
|
@ -1,75 +1,191 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"hash/crc32"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var cookieSecret = os.Getenv("COOKIE_SECRET")
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
User string
|
||||||
|
Groups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cookie struct {
|
||||||
|
Hash string
|
||||||
|
Salt string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
func (server *Server) authenticate(w http.ResponseWriter, r *http.Request) (*Server, bool, error) {
|
func (server *Server) authenticate(w http.ResponseWriter, r *http.Request) (*Server, bool, error) {
|
||||||
if err := server.parseLogin(w, r); err != nil {
|
if done, err := server.parseLogin(w, r); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
} else if done {
|
||||||
if ok, err := server.needsLogin(r); err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
} else if ok {
|
|
||||||
w.Header().Set("WWW-Authenticate", "Basic")
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
}
|
}
|
||||||
// TODO: if bad cookie OR no cookie: https://blog.stevensanderson.com/2008/08/25/using-the-browsers-native-login-prompt/
|
|
||||||
// TODO: prompt for user-pass if nothing supplied
|
if ok, err := needsLogin(r); err != nil {
|
||||||
// TODO: login
|
return nil, false, err
|
||||||
// TODO: logged in
|
} else if ok {
|
||||||
// TODO: get namespaces
|
promptLogin(w)
|
||||||
// TODO: verify cookie namespace is OK
|
return nil, true, nil
|
||||||
// TODO: ~~logout~~ // client side
|
}
|
||||||
return server.WithLoggedIn("", "", []string{}), false, errors.New("not impl")
|
|
||||||
|
user, _ := loginCookie(r)
|
||||||
|
namespace, _ := namespaceCookie(r)
|
||||||
|
return server.WithLoggedIn(user.User, namespace, user.Groups), false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) error {
|
func promptLogin(w http.ResponseWriter) {
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) parseLogin(w http.ResponseWriter, r *http.Request) (bool, error) {
|
||||||
username, password, ok := r.BasicAuth()
|
username, password, ok := r.BasicAuth()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return false, nil
|
||||||
}
|
}
|
||||||
_, _ = username, password
|
ok, err := server.auth.Login(username, password)
|
||||||
server.setLoginCookie(w, r, "abc")
|
if err != nil {
|
||||||
return errors.New("todo: use username+password to set cookie")
|
return false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
promptLogin(w)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
groups, err := server.auth.Groups(username)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return false, errors.New("user has no groups")
|
||||||
|
}
|
||||||
|
setLoginCookie(w, r, User{
|
||||||
|
User: username,
|
||||||
|
Groups: groups,
|
||||||
|
})
|
||||||
|
setNamespaceCookie(w, r, groups[0])
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) needsLogin(r *http.Request) (bool, error) {
|
func needsLogin(r *http.Request) (bool, error) {
|
||||||
_, ok := server.loginCookie(r)
|
user, ok := loginCookie(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
// TODO compare namespace + cookie groups
|
group, ok := namespaceCookie(r)
|
||||||
return false, errors.New("not impl")
|
if !ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
for i := range user.Groups {
|
||||||
|
if group == user.Groups[i] {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) setLoginCookie(w http.ResponseWriter, r *http.Request, value string) {
|
func setLoginCookie(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: "login",
|
Name: "login",
|
||||||
Value: server.encodeCookie(value),
|
Value: encodeUserCookie(user),
|
||||||
Expires: time.Now().Add(time.Hour * 24),
|
Expires: time.Now().Add(time.Hour * 24),
|
||||||
}
|
}
|
||||||
w.Header().Set("Set-Cookie", cookie.String())
|
w.Header().Add("Set-Cookie", cookie.String())
|
||||||
r.AddCookie(cookie)
|
r.AddCookie(cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) loginCookie(r *http.Request) (string, bool) {
|
func loginCookie(r *http.Request) (User, bool) {
|
||||||
|
c, ok := getCookie("login", r)
|
||||||
|
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()
|
cookies := r.Cookies()
|
||||||
for i := range cookies {
|
for i := range cookies {
|
||||||
if cookies[i].Name == "login" && time.Now().Before(cookies[i].Expires) {
|
if cookies[i].Name == key && (cookies[i].Expires.IsZero() || time.Now().Before(cookies[i].Expires)) {
|
||||||
return server.decodeCookie(cookies[i].Value)
|
return cookies[i].Value, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) decodeCookie(s string) (string, bool) {
|
func decodeUserCookie(raw string) (User, bool) {
|
||||||
panic("not impl")
|
decoded, ok := decodeCookie(raw)
|
||||||
|
if !ok {
|
||||||
|
return User{}, ok
|
||||||
|
}
|
||||||
|
var user User
|
||||||
|
err := json.Unmarshal([]byte(decoded), &user)
|
||||||
|
return user, err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) encodeCookie(s string) string {
|
func encodeUserCookie(user User) string {
|
||||||
panic("not impl")
|
b, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return encodeCookie(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeCookie(s string) string {
|
||||||
|
cookie := Cookie{
|
||||||
|
Salt: uuid.New().String(),
|
||||||
|
Value: s,
|
||||||
|
}
|
||||||
|
hash := crc32.NewIEEE()
|
||||||
|
hash.Write([]byte(cookieSecret))
|
||||||
|
hash.Write([]byte(cookie.Salt))
|
||||||
|
hash.Write([]byte(cookie.Value))
|
||||||
|
cookie.Hash = base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||||
|
b, err := json.Marshal(cookie)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeCookie(s string) (string, bool) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
var cookie Cookie
|
||||||
|
if err := json.Unmarshal(b, &cookie); err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
hash := crc32.NewIEEE()
|
||||||
|
hash.Write([]byte(cookieSecret))
|
||||||
|
hash.Write([]byte(cookie.Salt))
|
||||||
|
hash.Write([]byte(cookie.Value))
|
||||||
|
if got := base64.StdEncoding.EncodeToString(hash.Sum(nil)); cookie.Hash != got {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return cookie.Value, true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,269 @@
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,28 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"local/args"
|
"local/args"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
as := args.NewArgSet()
|
as := args.NewArgSet()
|
||||||
as.Append(args.INT, "p", "port to listen on", 3004)
|
as.Append(args.INT, "p", "port to listen on", 3004)
|
||||||
as.Append(args.STRING, "d", "root dir with /index.html and /media and /files", "./public")
|
as.Append(args.STRING, "d", "root dir with /index.html and /media and /files", "./public")
|
||||||
as.Append(args.BOOL, "ldap", "ldap features", false)
|
as.Append(args.STRING, "auth", "auth mode [none, path/to/some.yaml, ldap", "none")
|
||||||
if err := as.Parse(); err != nil {
|
if err := as.Parse(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
s := NewServer(as.GetString("d"), as.GetBool("ldap"))
|
auth, err := authFactory(as.GetString("auth"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
s := NewServer(as.GetString("d"), auth)
|
||||||
if err := s.Routes(); err != nil {
|
if err := s.Routes(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -22,3 +30,21 @@ func main() {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authFactory(key string) (auth, error) {
|
||||||
|
switch path.Base(strings.ToLower(key)) {
|
||||||
|
case "none", "":
|
||||||
|
return nil, nil
|
||||||
|
case "ldap":
|
||||||
|
return nil, errors.New("not impl ldap auth")
|
||||||
|
}
|
||||||
|
stat, err := os.Stat(key)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, errors.New("looks like auth path does not exist")
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if stat.IsDir() {
|
||||||
|
return nil, errors.New("looks like auth path is a dir")
|
||||||
|
}
|
||||||
|
return NewFileAuth(key), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,34 @@ import (
|
||||||
type Server struct {
|
type Server struct {
|
||||||
router *router.Router
|
router *router.Router
|
||||||
root string
|
root string
|
||||||
ldap bool
|
auth auth
|
||||||
|
loggedIn *loggedIn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(root string, ldap bool) *Server {
|
type loggedIn struct {
|
||||||
|
user string
|
||||||
|
group string
|
||||||
|
groups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(root string, auth auth) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
router: router.New(),
|
router: router.New(),
|
||||||
root: root,
|
root: root,
|
||||||
ldap: ldap,
|
auth: auth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) WithLoggedIn(user, group string, groups []string) *Server {
|
||||||
|
s2 := *server
|
||||||
|
s2.loggedIn = &loggedIn{
|
||||||
|
user: user,
|
||||||
|
group: group,
|
||||||
|
groups: groups,
|
||||||
|
}
|
||||||
|
return &s2
|
||||||
|
}
|
||||||
|
|
||||||
func (server *Server) Routes() error {
|
func (server *Server) Routes() error {
|
||||||
wildcard := func(s string) string {
|
wildcard := func(s string) string {
|
||||||
return strings.TrimSuffix(s, "/") + "/" + router.Wildcard
|
return strings.TrimSuffix(s, "/") + "/" + router.Wildcard
|
||||||
|
|
@ -67,6 +84,16 @@ func (server *Server) Routes() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if server.auth != nil {
|
||||||
|
if s2, done, err := server.authenticate(w, r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
} else if done {
|
||||||
|
return
|
||||||
|
} else if s2 != nil {
|
||||||
|
server = s2
|
||||||
|
}
|
||||||
|
}
|
||||||
server.router.ServeHTTP(w, r)
|
server.router.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerRoutes(t *testing.T) {
|
func TestServerRoutes(t *testing.T) {
|
||||||
server := NewServer(t.TempDir())
|
server := NewServer(t.TempDir(), nil)
|
||||||
if err := server.Routes(); err != nil {
|
if err := server.Routes(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +153,7 @@ func TestServerRoutes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerPutTreeGetFile(t *testing.T) {
|
func TestServerPutTreeGetFile(t *testing.T) {
|
||||||
server := NewServer(t.TempDir())
|
server := NewServer(t.TempDir(), nil)
|
||||||
if err := server.Routes(); err != nil {
|
if err := server.Routes(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
todo:
|
todo:
|
||||||
- LDAP login
|
- encrypt files at docker build time, put decrypt key in vault
|
||||||
|
- create fileauth login file
|
||||||
|
- secret for cookie encrypt+decrypt
|
||||||
- secrets
|
- secrets
|
||||||
- team-specific deployment;; prob grab a VM
|
- team-specific deployment;; prob grab a VM
|
||||||
- mark generated via meta so other files in the dir can be created, deleted, replaced safely
|
- mark generated via meta so other files in the dir can be created, deleted, replaced safely
|
||||||
- links like `/Smoktests` in user-files home wiki don't rewrite
|
- links like `/Smoktests` in user-files home wiki don't rewrite
|
||||||
- map fullURLScraped->internalURL for relative links sometimes
|
- map fullURLScraped->internalURL for relative links sometimes
|
||||||
|
- LDAP login
|
||||||
- scrape odo
|
- scrape odo
|
||||||
- rewrite links if available to local
|
- rewrite links if available to local
|
||||||
- anchor per line
|
- anchor per line
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue