From db5256a99438bec2b1046c9f43768c373457fe7a Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Sat, 24 Nov 2018 18:10:06 +0100 Subject: [PATCH] * Externalized config * Add dynamic user/share handling --- config.go | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ example.toml | 17 ++++++++++ go.mod | 6 +++- go.sum | 4 +++ main.go | 24 +++++++++---- 5 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 config.go create mode 100644 example.toml diff --git a/config.go b/config.go new file mode 100644 index 0000000..2e937f1 --- /dev/null +++ b/config.go @@ -0,0 +1,95 @@ +// Copyright (c) 2018, Andreas Schneider +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "github.com/BurntSushi/toml" + "golang.org/x/crypto/bcrypt" + "log" + "strings" +) + +type Config struct { + ListenAddress string + BaseDirectory string + Shares map[string]Share + Logins map[string]Login +} + +type Share struct { + Directory string +} + +type Login struct { + Credentials map[string]Credential +} + +type Credential struct { + Password string + Share string +} + +func LoadConfig(filename string) Config { + c := Config{} + if _, err := toml.DecodeFile(filename, &c); err != nil { + panic(err) + } + + return c +} + +func (c *Config) ValidateDAVUser(username, password string) (valid bool, directory string) { + parts := strings.SplitN(username, "@", 2) + if len(parts) != 2 { + return false, "" + } + + l, ok := c.Logins[parts[0]] + if !ok { + return false, "" + } + + cred, ok := l.Credentials[parts[1]] + if !ok { + return false, "" + } + + share, ok := c.Shares[cred.Share] + if !ok { + log.Printf("Invalid share for user %s: %s\n", username, cred.Share) + return false, "" + } + + err := bcrypt.CompareHashAndPassword([]byte(cred.Password), []byte(password)) + if err == bcrypt.ErrMismatchedHashAndPassword { + return false, "" + } else if err != nil { + log.Printf("Cannot validate password for user %s: %s\n", username, err.Error()) + return false, "" + } else { + return true, share.Directory + } +} diff --git a/example.toml b/example.toml new file mode 100644 index 0000000..71b75b6 --- /dev/null +++ b/example.toml @@ -0,0 +1,17 @@ +ListenAddress = ":3000" +BaseDirectory = "data" + +[Shares] + + [Shares.Test1] + Directory = "share1" + + [Shares.Test2] + Directory = "share2" + +[Logins] + + [Logins.User1] + [Logins.User1.Credentials.Test1] + Password = "$2a$10$5AuehKad7TxDqW2HXdYaZ.ipFajhl7ULyTR3DLCquTA3B/dxHujIq" #test + Share = "Test1" \ No newline at end of file diff --git a/go.mod b/go.mod index 43ff479..1768b90 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,7 @@ module ShareDAV -require golang.org/x/net v0.0.0-20181114220301-adae6a3d119a +require ( + github.com/BurntSushi/toml v0.3.1 + golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 + golang.org/x/net v0.0.0-20181114220301-adae6a3d119a +) diff --git a/go.sum b/go.sum index 531b5b3..a0753c4 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,6 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/main.go b/main.go index b99b3c2..67948ef 100644 --- a/main.go +++ b/main.go @@ -27,27 +27,39 @@ package main import ( "context" + "flag" "golang.org/x/net/webdav" - "log" "net/http" ) +var configFile = flag.String("config", "sharedav.toml", "Config file to be used.") + func main() { + flag.Parse() + + c := LoadConfig(*configFile) + h := &webdav.Handler{} h.LockSystem = webdav.NewMemLS() - h.FileSystem = BaseDir("") + h.FileSystem = BaseDir(c.BaseDirectory) authenticatedWebdavHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if !ok || (username != "user1" && username != "user2") || password != "test" { - log.Println("invalid user pass", ok, username, password) + directory := "" + if ok { + ok, directory = c.ValidateDAVUser(username, password) + } + if !ok { w.Header().Set("WWW-Authenticate", `Basic realm="ShareDAV"`) http.Error(w, "unauthorized", http.StatusUnauthorized) return } - h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "dir", username))) + // Use the WebDAV handler to actually serve the request. Also enhance the context + // to contain the subdirectory (of the base directory) which contains the data for + // the authenticated user. + h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "dir", directory))) }) - http.ListenAndServe(":3000", authenticatedWebdavHandler) + http.ListenAndServe(c.ListenAddress, authenticatedWebdavHandler) }