Add support for per path authenticators
This commit is contained in:
parent
1dba36f9f5
commit
6ed95d15cf
26
auth/auth.go
26
auth/auth.go
|
@ -4,6 +4,28 @@ import (
|
|||
"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()
|
||||
}
|
||||
}
|
|
@ -6,53 +6,69 @@ import (
|
|||
"encoding/base64"
|
||||
)
|
||||
|
||||
type BasicAuth struct {
|
||||
users map[string]string
|
||||
type BasicAuthInfo struct {
|
||||
Group string
|
||||
Password string
|
||||
}
|
||||
|
||||
type BasicAuth struct {
|
||||
users map[string]BasicAuthInfo
|
||||
}
|
||||
|
||||
func (ba *BasicAuth) authenticate(user, password string) bool {
|
||||
pass,ok := ba.users[user]
|
||||
rec, ok := ba.users[user]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if pass == password {
|
||||
if rec.Password == password {
|
||||
return true
|
||||
}
|
||||
|
||||
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"`)
|
||||
|
||||
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||
if len(s) != 2 {
|
||||
return "", false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return "", false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
func (ba *BasicAuth) AddUser(user, password string) {
|
||||
ba.users[user] = password
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func NewBasicAuth() *BasicAuth {
|
||||
func (ba *BasicAuth) AddUser(user, group, password string) error {
|
||||
ba.users[user] = BasicAuthInfo{
|
||||
Password: password,
|
||||
Group: group,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ba *BasicAuth) DeleteUser(user string) error {
|
||||
delete(ba.users, user)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBasicAuth() AuthManager {
|
||||
return &BasicAuth{
|
||||
users: make(map[string]string),
|
||||
users: make(map[string]BasicAuthInfo),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
41
context.go
41
context.go
|
@ -1,18 +1,24 @@
|
|||
package snap
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"git.thirdmartini.com/pub/snap/auth"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Username string
|
||||
srv *server
|
||||
srv *Server
|
||||
auth *auth.AuthData
|
||||
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
vars map[string]string
|
||||
}
|
||||
|
||||
type SnapContent struct {
|
||||
Username string
|
||||
AppData interface{}
|
||||
User string
|
||||
Theme string
|
||||
Content interface{}
|
||||
}
|
||||
|
||||
func (c *Context) GetRequest() *http.Request {
|
||||
|
@ -25,7 +31,10 @@ func (c *Context) Writer() http.ResponseWriter {
|
|||
|
||||
|
||||
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) {
|
||||
|
@ -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{}) {
|
||||
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{}) {
|
||||
cnt := SnapContent {
|
||||
Username:c.Username,
|
||||
AppData:content,
|
||||
User: c.GetUser(),
|
||||
Theme: c.srv.theme,
|
||||
Content:content,
|
||||
}
|
||||
|
||||
c.srv.render(c.w, tmpl, &cnt)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
99
server.go
99
server.go
|
@ -15,17 +15,11 @@ import (
|
|||
"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
|
||||
path string
|
||||
theme string
|
||||
debug bool
|
||||
auth auth.AuthManager
|
||||
router *mux.Router
|
||||
|
@ -33,55 +27,53 @@ type server struct {
|
|||
}
|
||||
|
||||
|
||||
func (s *server) plain(f func(c *Context)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
type SnapBaseContent struct {
|
||||
Theme string
|
||||
Content interface{}
|
||||
}
|
||||
|
||||
func (s *Server) makeContext( auth *auth.AuthData, w http.ResponseWriter, r *http.Request ) (*Context) {
|
||||
c := &Context{
|
||||
Username: "",
|
||||
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) {
|
||||
c := s.makeContext( nil, w,r)
|
||||
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) {
|
||||
name, ok := s.auth.DoAuth( w, r )
|
||||
rec, ok := auth.DoAuth( w, r )
|
||||
|
||||
if !ok {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
} else {
|
||||
c:= &Context{
|
||||
Username: name,
|
||||
r: r,
|
||||
w: w,
|
||||
srv: s,
|
||||
vars: mux.Vars(r),
|
||||
}
|
||||
c := s.makeContext( rec, w,r)
|
||||
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) {
|
||||
c:= &Context{
|
||||
Username: "",
|
||||
r: r,
|
||||
w: w,
|
||||
srv: s,
|
||||
vars: mux.Vars(r),
|
||||
}
|
||||
c := s.makeContext( nil, w,r)
|
||||
handle(c)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *server) loadTemplates() *template.Template {
|
||||
func (s *Server) loadTemplates() *template.Template {
|
||||
tmpl := template.New("")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (s *server) getTemplates() *template.Template {
|
||||
func (s *Server) getTemplates() *template.Template {
|
||||
if s.debug {
|
||||
return s.loadTemplates()
|
||||
}
|
||||
|
@ -112,37 +104,57 @@ func (s *server) getTemplates() *template.Template {
|
|||
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)
|
||||
}
|
||||
|
||||
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.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 {
|
||||
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))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (s *server) SetDebug(enable bool) {
|
||||
func (s *Server) SetDebug(enable bool) {
|
||||
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{
|
||||
Handler: s.router,
|
||||
Addr: s.address,
|
||||
|
@ -155,7 +167,7 @@ func (s *server) ServeTLS(keyPath string, certPath string) error {
|
|||
}
|
||||
|
||||
// Serve serve content forever
|
||||
func (s *server) Serve() error {
|
||||
func (s *Server) Serve() error {
|
||||
srv := &http.Server{
|
||||
Handler: s.router,
|
||||
Addr: s.address,
|
||||
|
@ -167,12 +179,13 @@ func (s *server) Serve() error {
|
|||
return srv.ListenAndServe()
|
||||
}
|
||||
|
||||
func New(address string, path string, auth auth.AuthManager) Server {
|
||||
s := server{
|
||||
func New(address string, path string, auth auth.AuthManager) *Server {
|
||||
s := Server{
|
||||
router: mux.NewRouter(),
|
||||
auth: auth,
|
||||
address: address,
|
||||
path: path,
|
||||
theme: "/static/css/default.css",
|
||||
}
|
||||
|
||||
s.router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(s.path+"static/"))))
|
||||
|
|
Loading…
Reference in New Issue