dave/app/security.go
2018-06-17 14:27:40 +02:00

123 lines
3.4 KiB
Go

package app
import (
"context"
"fmt"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"net/http"
)
type contextKey int
var authInfoKey contextKey
// AuthInfo holds the username and authentication status
type AuthInfo struct {
Username string
Authenticated bool
}
// authWebdavHandlerFunc is a type definition which holds a context and application reference to
// match the AuthWebdavHandler interface.
type authWebdavHandlerFunc func(c context.Context, w http.ResponseWriter, r *http.Request, a *App)
// ServeHTTP simply calls the AuthWebdavHandlerFunc with given parameters
func (f authWebdavHandlerFunc) ServeHTTP(c context.Context, w http.ResponseWriter, r *http.Request, a *App) {
f(c, w, r, a)
}
// NewBasicAuthWebdavHandler creates a new http handler with basic auth features.
// The handler will use the application config for user and password lookups.
func NewBasicAuthWebdavHandler(a *App) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
handlerFunc := authWebdavHandlerFunc(handle)
handlerFunc.ServeHTTP(ctx, w, r, a)
})
}
func authenticate(config *Config, username, password string) (*AuthInfo, error) {
if !config.AuthenticationNeeded() {
return &AuthInfo{Username: "", Authenticated: false}, nil
}
if username == "" || password == "" {
return &AuthInfo{Username: username, Authenticated: false}, errors.New("username not found or password empty")
}
user := config.Users[username]
if user == nil {
return &AuthInfo{Username: username, Authenticated: false}, errors.New("user not found")
}
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
return &AuthInfo{Username: username, Authenticated: false}, errors.New("Password doesn't match")
}
return &AuthInfo{Username: username, Authenticated: true}, nil
}
// AuthFromContext returns information about the authentication state of the current user.
func AuthFromContext(ctx context.Context) *AuthInfo {
info, ok := ctx.Value(authInfoKey).(*AuthInfo)
if !ok {
return nil
}
return info
}
func handle(ctx context.Context, w http.ResponseWriter, r *http.Request, a *App) {
// if there are no users, we don't need authentication here
if (!a.Config.AuthenticationNeeded()) {
a.Handler.ServeHTTP(w, r.WithContext(ctx))
return
}
username, password, ok := httpAuth(r, a.Config)
if !ok {
writeUnauthorized(w, a.Config.Realm)
return
}
authInfo, err := authenticate(a.Config, username, password)
if err != nil {
log.WithField("user", username).Warn(err.Error())
}
if !authInfo.Authenticated {
writeUnauthorized(w, a.Config.Realm)
return
}
ctx = context.WithValue(ctx, authInfoKey, authInfo)
a.Handler.ServeHTTP(w, r.WithContext(ctx))
}
func httpAuth(r *http.Request, config *Config) (string, string, bool) {
if config.AuthenticationNeeded() {
username, password, ok := r.BasicAuth()
return username, password, ok
}
return "", "", true
}
func writeUnauthorized(w http.ResponseWriter, realm string) {
w.Header().Set("WWW-Authenticate", "Basic realm="+realm)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(fmt.Sprintf("%d %s", http.StatusUnauthorized, "Unauthorized")))
}
// GenHash generates a bcrypt hashed password string
func GenHash(password []byte) string {
pw, err := bcrypt.GenerateFromPassword(password, 10)
if err != nil {
log.Fatal(err)
}
return string(pw)
}