diff --git a/examples/simple/main.go b/examples/simple/main.go index 9140cf8..de48927 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -14,12 +14,12 @@ func handlerAuthenticated(c *snap.Context) { } -func mustRunServer(auth auth.AuthManager) { +func mustRunServer(auth auth.Authenticator) { srv := snap.New("127.0.0.1:9000", "./", nil) srv.SetDebug(true) srv.HandleFunc("/", handler) - srv.HandleFuncCustomAuth(auth,"/auth", handlerAuthenticated) + srv.HandleFuncCustomAuth(auth,"/auth", "", handlerAuthenticated) srv.Serve() } diff --git a/go.mod b/go.mod index 3fee776..9f62527 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.15 require ( git.thirdmartini.com/pub/fancylog v0.0.0-20180118031448-3f3ebedd6016 github.com/gorilla/mux v1.8.0 + github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index ede51a7..194628d 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,15 @@ +git.thirdmartini.com/pub/fancylog v0.0.0-20180118031448-3f3ebedd6016 h1:egW0VTyAgD7FjG6iZKHBcHc7siCGjoxm1T71GecYUE4= git.thirdmartini.com/pub/fancylog v0.0.0-20180118031448-3f3ebedd6016/go.mod h1:nxr8UYB5P+7cpJPpqcbhMjfqm+GH3VDZQs2dFWibA68= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server.go b/server.go index b684942..dda593c 100644 --- a/server.go +++ b/server.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/http" "os" + "path" "path/filepath" "strings" "time" @@ -23,7 +24,7 @@ type Server struct { path string theme string debug bool - auth auth.AuthManager + auth auth.Authenticator router *mux.Router templates string cachedTmpl *template.Template @@ -45,14 +46,17 @@ func (s *Server) makeContext(auth *auth.AuthData, w http.ResponseWriter, r *http return c } -func (s *Server) authenticated(auth auth.AuthManager, handle func(c *Context)) http.HandlerFunc { +func (s *Server) authenticated(auth auth.Authenticator, redirect string, handle func(c *Context)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Debug("authenticated request: ", r.RequestURI) rec, ok := auth.DoAuth(w, r) - if !ok { - http.Error(w, "Not authorized", 401) + if redirect != "" { + http.Redirect(w, r, redirect, http.StatusSeeOther) + } else { + http.Error(w, "Not authorized", http.StatusForbidden) + } } else { c := s.makeContext(rec, w, r) handle(c) @@ -125,12 +129,6 @@ func (s *Server) loadTemplates() *template.Template { if err != nil { log.Println(err) } - /* - _, err = tmpl.ParseFiles(path) - if err != nil { - log.Println(err) - } - */ } return err }) @@ -169,20 +167,20 @@ func (s *Server) renderError(w http.ResponseWriter, code int, msg string) { w.Write([]byte(msg)) } -func (s *Server) HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route { +func (s *Server) HandleFuncAuthenticated(path,redirect string, f func(c *Context)) *mux.Route { if s.auth == nil { return nil } - return s.router.HandleFunc(path, s.authenticated(s.auth, f)) + return s.router.HandleFunc(path, s.authenticated(s.auth,redirect, f)) } -func (s *Server) HandleFuncCustomAuth(auth auth.AuthManager, path string, f func(c *Context)) *mux.Route { +func (s *Server) HandleFuncCustomAuth(auth auth.Authenticator, path, redirect string, f func(c *Context)) *mux.Route { if auth == nil { log.Warn("Nil auth on", path) return nil } - return s.router.HandleFunc(path, s.authenticated(auth, f)) + return s.router.HandleFunc(path, s.authenticated(auth,redirect, f)) } func (s *Server) HandleFunc(path string, f func(c *Context)) *mux.Route { @@ -196,12 +194,12 @@ func (s *Server) SetDebug(enable bool) { func (s *Server) EnableStatus(path string) { } -func (s *Server) SetTheme(theme string) { - s.theme = theme +func (s *Server) SetTheme(themePath string) { + s.theme = path.Clean(themePath) } -func (s *Server) SetTemplatePath(path string) { - s.templates = path +func (s *Server) SetTemplatePath(tmplPath string) { + s.templates = path.Clean(tmplPath) } func (s *Server) Router() *mux.Router { @@ -239,17 +237,24 @@ func (s *Server) WithStaticFiles(prefix string, path string) *Server { } func (s *Server) WithTheme(themeURL string) *Server { - s.theme = themeURL + s.theme = path.Clean(themeURL) return s } func (s *Server) WithDebug(debugURL string) *Server { - sub := s.router.PathPrefix("/sovereign/debug").Subrouter() + sub := s.router.PathPrefix(debugURL).Subrouter() setupDebugHandler(sub) return s } -func New(address string, path string, auth auth.AuthManager) *Server { +func (s *Server) Dump() { + fmt.Printf( " Path: %s\n", s.path) + fmt.Printf( " Theme: %s\n", s.theme) + fmt.Printf( " Templates: %s\n", s.templates) + +} + +func New(address string, path string, auth auth.Authenticator) *Server { s := Server{ router: mux.NewRouter(), auth: auth, @@ -258,7 +263,5 @@ func New(address string, path string, auth auth.AuthManager) *Server { templates: "templates", theme: "/static/css/default.css", } - - //s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/")))) return &s } diff --git a/server_test.go b/server_test.go index f658f89..703bb42 100644 --- a/server_test.go +++ b/server_test.go @@ -1,10 +1,152 @@ package snap import ( + "io/ioutil" + "net/http" + "net/http/httptest" "testing" + + "github.com/stretchr/testify/assert" + + "git.thirdmartini.com/pub/snap/auth" ) -func TestServer(t *testing.T) { + +var rootExpected = []byte(` + + + + + + +Test of things + + + +`) + +var testExpected = []byte(` + + + + + + +User: admin + + + +`) +func handlerRoot(c *Context) { + c.RenderEx("index.html", nil) } + +func handlerLogin(c *Context) { + c.RenderEx("index.html", nil) +} + +func handlerTest(c *Context) { + c.RenderEx("test.html", nil) +} + +func doTestRequest(t *testing.T, req *http.Request) ([]byte, int, error){ + resp, err := http.DefaultClient.Do(req) + assert.Nil(t, err) + assert.NotNil(t, resp) + + data, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + assert.Nil(t, err) + + return data, resp.StatusCode, err +} + +func TestServer_BasicAuth(t *testing.T) { + auth := auth.NewBasicAuth() + auth.AddUser("admin", "admin", "test") + + s := New("", "/test", auth) + s.SetDebug(true) + s.SetTemplatePath("test/templates") + s.WithStaticFiles("/static", "test/static" ) + s.WithTheme("skin/") + s.HandleFunc("/", handlerRoot) + s.HandleFuncAuthenticated("/login", "", handlerLogin) + s.HandleFuncAuthenticated("/test", "", handlerTest) + s.Dump() + + ts := httptest.NewServer(s.router) + defer ts.Close() + + // Simple Get:/ + req, err := http.NewRequest(http.MethodGet, ts.URL, nil) + assert.Nil(t, err) + data, code, err := doTestRequest(t, req) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, rootExpected, data) + + // Check GOOD password + req, err = http.NewRequest(http.MethodGet, ts.URL +"/login", nil) + assert.Nil(t, err) + req.SetBasicAuth("admin","test") + data, code, err = doTestRequest(t, req) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, rootExpected, data) + + // Check BAD password + req, err = http.NewRequest(http.MethodGet, ts.URL +"/login", nil) + assert.Nil(t, err) + req.SetBasicAuth("admin","badpass") + data, code, err = doTestRequest(t, req) + assert.Nil(t, err) + assert.Equal(t, http.StatusForbidden, code) + + // Check GOOD password and context in templates + req, err = http.NewRequest(http.MethodGet, ts.URL +"/test", nil) + assert.Nil(t, err) + req.SetBasicAuth("admin","test") + data, code, err = doTestRequest(t, req) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, testExpected, data) + + // Test Static Content + // Check GOOD password and context in templates + req, err = http.NewRequest(http.MethodGet, ts.URL +"/static/skin/skin.html", nil) + assert.Nil(t, err) + data, code, err = doTestRequest(t, req) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, []byte(`skin-file`), data) +} + +func TestServer_TokenAuth(t *testing.T) { + auth := auth.NewTokenAuth() + auth.AddUser("admin", "admin", "1234567890") + + s := New("", "/test", auth) + s.SetDebug(true) + s.SetTemplatePath("test/templates") + s.WithStaticFiles("/static", "test/static") + s.WithTheme("skin/") + s.HandleFunc("/", handlerRoot) + s.HandleFuncAuthenticated("/login", "", handlerLogin) + s.HandleFuncAuthenticated("/test", "", handlerTest) + s.Dump() + + ts := httptest.NewServer(s.router) + defer ts.Close() + + // Check GOOD password + req, err := http.NewRequest(http.MethodGet, ts.URL+"/login", nil) + assert.Nil(t, err) + req.Header.Add("token", "1234567890") + data, code, err := doTestRequest(t, req) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, code) + assert.Equal(t, rootExpected, data) +} \ No newline at end of file