Add support for per path authenticators

This commit is contained in:
ssobolewski 2018-03-24 10:12:02 -06:00
parent 1dba36f9f5
commit 6ed95d15cf
7 changed files with 252 additions and 72 deletions

View File

@ -4,6 +4,28 @@ import (
"net/http" "net/http"
) )
type AuthManager interface {
DoAuth(w http.ResponseWriter,r *http.Request) (string,bool) type AuthData struct {
User string
Group string
} }
type AuthManager interface {
AddUser(user string, group string, password string) error
DeleteUser(user string) error
DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool)
}
//----------------------------------------------------------------------------------------------------------------------
func NewAuth(kind string) AuthManager {
switch kind {
case "basic":
return NewBasicAuth()
case "token":
return NewTokenAuth()
default:
return NewNoAuth()
}
}

View File

@ -6,53 +6,69 @@ import (
"encoding/base64" "encoding/base64"
) )
type BasicAuth struct { type BasicAuthInfo struct {
users map[string]string Group string
Password string
} }
type BasicAuth struct {
users map[string]BasicAuthInfo
}
func (ba *BasicAuth) authenticate(user, password string) bool { func (ba *BasicAuth) authenticate(user, password string) bool {
pass,ok := ba.users[user] rec, ok := ba.users[user]
if !ok { if !ok {
return false return false
} }
if pass == password { if rec.Password == password {
return true return true
} }
return false return false
} }
func (ba *BasicAuth) DoAuth(w http.ResponseWriter,r *http.Request) (string, bool) { func (ba *BasicAuth) DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 { if len(s) != 2 {
return "", false return nil, false
} }
b, err := base64.StdEncoding.DecodeString(s[1]) b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil { if err != nil {
return "", false return nil, false
} }
pair := strings.SplitN(string(b), ":", 2) pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 { if len(pair) != 2 {
http.Error(w, "Not authorized", 401) http.Error(w, "Not authorized", 401)
return "", false return nil, false
} }
return pair[0], ba.authenticate(pair[0], pair[1]) if ba.authenticate(pair[0], pair[1]) {
return &AuthData{User: pair[0], Group: ""}, true
}
return nil, false
} }
func (ba *BasicAuth) AddUser(user, group, password string) error {
func (ba *BasicAuth) AddUser(user, password string) { ba.users[user] = BasicAuthInfo{
ba.users[user] = password Password: password,
Group: group,
}
return nil
} }
func NewBasicAuth() *BasicAuth { func (ba *BasicAuth) DeleteUser(user string) error {
delete(ba.users, user)
return nil
}
func NewBasicAuth() AuthManager {
return &BasicAuth{ return &BasicAuth{
users: make(map[string]string), users: make(map[string]BasicAuthInfo),
} }
} }

29
auth/noauth.go Normal file
View File

@ -0,0 +1,29 @@
package auth
import "net/http"
//----------------------------------------------------------------------------------------------------------------------
type NoAuth struct {
User AuthData
}
func (a *NoAuth) DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool) {
return &a.User, true
}
func (a *NoAuth) AddUser(user string, group string, password string) error {
return nil
}
func (a *NoAuth) DeleteUser(user string) error {
return nil
}
func NewNoAuth() AuthManager {
return &NoAuth{
User: AuthData{
User: "admin",
Group: "admin",
},
}
}

45
auth/token.go Normal file
View File

@ -0,0 +1,45 @@
package auth
import "net/http"
//----------------------------------------------------------------------------------------------------------------------
type TokenAuth struct {
users map[string]TokenUser
}
type TokenUser struct {
User string
Group string
}
func (a *TokenAuth) DoAuth(w http.ResponseWriter, r *http.Request) (*AuthData, bool) {
token := r.Header.Get("token")
user, ok := a.users[token]
if !ok {
return nil, false
}
return &AuthData{
User: user.User,
Group: user.Group,
}, true
}
func (a *TokenAuth) AddUser(user string, group string, password string) error {
a.users[password] = TokenUser{
User: user,
Group: group,
}
return nil
}
func (a *TokenAuth) DeleteUser(user string) error {
return nil
}
func NewTokenAuth() *TokenAuth {
return &TokenAuth{
users: make(map[string]TokenUser),
}
}

View File

@ -1,18 +1,24 @@
package snap package snap
import "net/http" import (
"net/http"
"encoding/json"
"git.thirdmartini.com/pub/snap/auth"
)
type Context struct { type Context struct {
Username string srv *Server
srv *server auth *auth.AuthData
w http.ResponseWriter w http.ResponseWriter
r *http.Request r *http.Request
vars map[string]string vars map[string]string
} }
type SnapContent struct { type SnapContent struct {
Username string User string
AppData interface{} Theme string
Content interface{}
} }
func (c *Context) GetRequest() *http.Request { func (c *Context) GetRequest() *http.Request {
@ -25,7 +31,10 @@ func (c *Context) Writer() http.ResponseWriter {
func (c *Context) GetUser() string { func (c *Context) GetUser() string {
return c.Username if c.auth == nil {
return ""
}
return c.auth.User
} }
func (c *Context) Error(code int, msg string) { func (c *Context) Error(code int, msg string) {
@ -33,6 +42,20 @@ func (c *Context) Error(code int, msg string) {
} }
func (c *Context) Reply(msg string) {
c.srv.reply(c.w, msg)
}
func (c *Context) ReplyObject(obj interface{}) {
msg, err := json.Marshal(obj)
if err != nil {
c.Error(400, "Internal Server Error")
}
c.Reply(string(msg))
}
func (c *Context) Render(tmpl string, content interface{}) { func (c *Context) Render(tmpl string, content interface{}) {
c.srv.render(c.w, tmpl, content) c.srv.render(c.w, tmpl, content)
@ -41,10 +64,10 @@ func (c *Context) Render(tmpl string, content interface{}) {
func (c *Context) RenderEx(tmpl string, content interface{}) { func (c *Context) RenderEx(tmpl string, content interface{}) {
cnt := SnapContent { cnt := SnapContent {
Username:c.Username, User: c.GetUser(),
AppData:content, Theme: c.srv.theme,
Content:content,
} }
c.srv.render(c.w, tmpl, &cnt) c.srv.render(c.w, tmpl, &cnt)
} }

32
examples/simple/main.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"git.thirdmartini.com/pub/snap"
"git.thirdmartini.com/pub/snap/auth"
)
func handler(c *snap.Context) {
c.Reply("snap/example/simple 1.0")
}
func handlerAuthenticated(c *snap.Context) {
c.Reply("snap/example/simple 1.0 (authenticated)")
}
func mustRunServer(auth auth.AuthManager) {
srv := snap.New("127.0.0.1:9000", "./", nil)
srv.SetDebug(true)
srv.HandleFunc("/", handler)
srv.HandleFuncCustomAuth(auth,"/auth", handlerAuthenticated)
srv.Serve()
}
func main() {
auth := auth.NewAuth("basic")
auth.AddUser("admin", "admin", "password")
mustRunServer(auth)
}

107
server.go
View File

@ -15,17 +15,11 @@ import (
"git.thirdmartini.com/pub/snap/auth" "git.thirdmartini.com/pub/snap/auth"
) )
type Server interface {
Serve() error
ServeTLS(keyPath string, certPath string) error
SetDebug(enable bool)
HandleFunc(path string, f func(c *Context)) error
HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route
}
type server struct { type Server struct {
address string address string
path string path string
theme string
debug bool debug bool
auth auth.AuthManager auth auth.AuthManager
router *mux.Router router *mux.Router
@ -33,55 +27,53 @@ type server struct {
} }
func (s *server) plain(f func(c *Context)) http.HandlerFunc { type SnapBaseContent struct {
Theme string
Content interface{}
}
func (s *Server) makeContext( auth *auth.AuthData, w http.ResponseWriter, r *http.Request ) (*Context) {
c := &Context{
r: r,
w: w,
srv: s,
auth: auth,
vars: mux.Vars(r),
}
return c
}
func (s *Server) plain(f func(c *Context)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
c := &Context{ c := s.makeContext( nil, w,r)
Username: "",
r: r,
w: w,
srv: s,
}
f(c) f(c)
} }
} }
func (s *server) authenticated(handle func(c *Context)) http.HandlerFunc { func (s *Server) authenticated(auth auth.AuthManager, handle func(c *Context)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
name, ok := s.auth.DoAuth( w, r ) rec, ok := auth.DoAuth( w, r )
if !ok { if !ok {
http.Error(w, "Not authorized", 401) http.Error(w, "Not authorized", 401)
} else { } else {
c:= &Context{ c := s.makeContext( rec, w,r)
Username: name,
r: r,
w: w,
srv: s,
vars: mux.Vars(r),
}
handle(c) handle(c)
} }
} }
} }
func (s *server) wrapper(handle func(c *Context)) http.HandlerFunc { func (s *Server) wrapper(handle func(c *Context)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
c:= &Context{ c := s.makeContext( nil, w,r)
Username: "",
r: r,
w: w,
srv: s,
vars: mux.Vars(r),
}
handle(c) handle(c)
} }
} }
func (s *Server) loadTemplates() *template.Template {
func (s *server) loadTemplates() *template.Template {
tmpl := template.New("") tmpl := template.New("")
err := filepath.Walk(path.Join(s.path, "templates"), func(path string, info os.FileInfo, err error) error { err := filepath.Walk(path.Join(s.path, "templates"), func(path string, info os.FileInfo, err error) error {
@ -101,7 +93,7 @@ func (s *server) loadTemplates() *template.Template {
return tmpl return tmpl
} }
func (s *server) getTemplates() *template.Template { func (s *Server) getTemplates() *template.Template {
if s.debug { if s.debug {
return s.loadTemplates() return s.loadTemplates()
} }
@ -112,37 +104,57 @@ func (s *server) getTemplates() *template.Template {
return s.cachedTmpl return s.cachedTmpl
} }
func (s *server) render(w http.ResponseWriter, tmpl string, content interface{} ) { func (s *Server) render(w http.ResponseWriter, tmpl string, content interface{} ) {
s.getTemplates().ExecuteTemplate(w, tmpl, content) s.getTemplates().ExecuteTemplate(w, tmpl, content)
} }
func (s *server) renderError(w http.ResponseWriter, code int, msg string) { func (s *Server) reply(w http.ResponseWriter, msg string) {
w.Write([]byte(msg))
}
func (s *Server) renderError(w http.ResponseWriter, code int, msg string) {
w.WriteHeader(code) w.WriteHeader(code)
w.Write([]byte(msg)) w.Write([]byte(msg))
} }
func (s *server) HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route { func (s *Server) HandleFuncAuthenticated(path string, f func(c *Context)) *mux.Route {
if s.auth == nil { if s.auth == nil {
return nil return nil
} }
return s.router.HandleFunc(path, s.authenticated(f)) return s.router.HandleFunc(path, s.authenticated(s.auth, f))
}
func (s *Server) HandleFuncCustomAuth(auth auth.AuthManager, path 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))
} }
func (s *Server) HandleFunc(path string, f func(c *Context)) error {
func (s *server) HandleFunc(path string, f func(c *Context)) error {
s.router.HandleFunc(path, s.wrapper(f)) s.router.HandleFunc(path, s.wrapper(f))
return nil return nil
} }
func (s *server) SetDebug(enable bool) { func (s *Server) SetDebug(enable bool) {
s.debug = enable s.debug = enable
} }
func (s *server) ServeTLS(keyPath string, certPath string) error { func (s *Server) EnableStatus(path string) {
}
func (s *Server) SetTheme(theme string) {
s.theme = theme
}
func (s *Server) ServeTLS(keyPath string, certPath string) error {
srv := &http.Server{ srv := &http.Server{
Handler: s.router, Handler: s.router,
Addr: s.address, Addr: s.address,
@ -155,7 +167,7 @@ func (s *server) ServeTLS(keyPath string, certPath string) error {
} }
// Serve serve content forever // Serve serve content forever
func (s *server) Serve() error { func (s *Server) Serve() error {
srv := &http.Server{ srv := &http.Server{
Handler: s.router, Handler: s.router,
Addr: s.address, Addr: s.address,
@ -167,12 +179,13 @@ func (s *server) Serve() error {
return srv.ListenAndServe() return srv.ListenAndServe()
} }
func New(address string, path string, auth auth.AuthManager) Server { func New(address string, path string, auth auth.AuthManager) *Server {
s := server{ s := Server{
router: mux.NewRouter(), router: mux.NewRouter(),
auth: auth, auth: auth,
address: address, address: address,
path: path, path: path,
theme: "/static/css/default.css",
} }
s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/")))) s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/"))))