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"
|
"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"
|
"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, password string) {
|
|
||||||
ba.users[user] = password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
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
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
"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 {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
Theme string
|
||||||
|
Content interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) makeContext( auth *auth.AuthData, w http.ResponseWriter, r *http.Request ) (*Context) {
|
||||||
c := &Context{
|
c := &Context{
|
||||||
Username: "",
|
|
||||||
r: r,
|
r: r,
|
||||||
w: w,
|
w: w,
|
||||||
srv: s,
|
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)
|
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/"))))
|
||||||
|
|
Loading…
Reference in New Issue