From f6e1813d15f0912fa85fb5321f7011c872c8b67d Mon Sep 17 00:00:00 2001 From: ston1th Date: Sat, 31 Aug 2019 16:39:50 +0200 Subject: [PATCH] initial commit --- LICENSE | 24 +++++++++++++++++++ README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++ authdav.go | 57 ++++++++++++++++++++++++++++++++++++++++++++ file.go | 46 ++++++++++++++++++++++++++++++++++++ fileinfo.go | 44 ++++++++++++++++++++++++++++++++++ filesystem.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + 7 files changed, 296 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 authdav.go create mode 100644 file.go create mode 100644 fileinfo.go create mode 100644 filesystem.go create mode 100644 go.mod diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..45785b1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2019 Marius Schellenberger +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. + * The names of the authors and/or contributors may not 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 ston1th 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aca18a0 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# authdav - a x/net/webdav wrapper + +Wrappers: +* HTTP Basic Auth wrapper for `webdav.Handler` +* write only once FileSystem wrapper for `webdav.FileSystem` + +## Usage + +### HTTP Basic Auth + +``` +package main + +import ( + "git.giftfish.de/ston1th/authdav" + "golang.org/x/net/webdav" + "log" + "net/http" +) + +type exampleAuthenticator struct {} + +func (exampleAuthenticator) BasicAuth(user, password string) bool { + if user == "user" && password == "password" { + return true + } + return false +} + +func main() { + auth := exampleAuthenticator{} + handler := authdav.NewWebdavBasicAuth("", webdav.Dir("."), nil, nil, auth, "webdav with basic auth") + log.Fatal(http.ListenAndServe(":8080", handler)) +} +``` + +### Write Only + +``` +package main + +import ( + "git.giftfish.de/ston1th/authdav" + "golang.org/x/net/webdav" + "log" + "net/http" +) + +func main() { + fs := authdav.NewWriteOnlyOnceFileSystem(webdav.Dir(".")) + handler := &webdav.Handler{ + Prefix: "", + FileSystem: fs, + LockSystem: webdav.NewMemLS(), + Logger: nil, + } + log.Fatal(http.ListenAndServe(":8080", handler)) +} +``` diff --git a/authdav.go b/authdav.go new file mode 100644 index 0000000..268bc4a --- /dev/null +++ b/authdav.go @@ -0,0 +1,57 @@ +// Copyright (C) 2019 Marius Schellenberger + +package authdav + +import ( + "golang.org/x/net/webdav" + "net/http" +) + +// DefaultRealm is the HTTP Basic Auth default realm +const DefaultRealm = "WebDav" + +// Authenticator is the interface for Webdav HTTP Basic Auth +type Authenticator interface { + BasicAuth(username string, password string) bool +} + +// WebdavBasicAuth is a WebDav wrapper for HTTP Basic Auth and implements the http.Handler interface +type WebdavBasicAuth struct { + dav *webdav.Handler + auth Authenticator + realm string +} + +// NewWebdavBasicAuth returns a new WebdavBasicAuth wrapper +// prefix can be empty +// ls and log can be nil +// an empty authenticator disables HTTP Basic Auth +// realm can be empty and defaults to authdav.DefaultRealm +func NewWebdavBasicAuth(prefix string, fs webdav.FileSystem, ls webdav.LockSystem, log func(*http.Request, error), auth Authenticator, realm string) *WebdavBasicAuth { + if ls == nil { + ls = webdav.NewMemLS() + } + if realm == "" { + realm = DefaultRealm + } + dav := &webdav.Handler{ + Prefix: prefix, + FileSystem: fs, + LockSystem: ls, + Logger: log, + } + return &WebdavBasicAuth{dav, auth, `Basic realm="` + realm + `"`} +} + +func (h *WebdavBasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if h.auth != nil { + u, p, ok := r.BasicAuth() + if !ok || !h.auth.BasicAuth(u, p) { + w.Header().Set("WWW-Authenticate", h.realm) + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Unauthorised\n")) + return + } + } + h.dav.ServeHTTP(w, r) +} diff --git a/file.go b/file.go new file mode 100644 index 0000000..75e2364 --- /dev/null +++ b/file.go @@ -0,0 +1,46 @@ +// Copyright (C) 2019 Marius Schellenberger + +package authdav + +import ( + "golang.org/x/net/webdav" + "io" + "os" +) + +// File is a wrapper for webdav.File providing write only functionality +type File struct { + f webdav.File +} + +func (f *File) Close() error { + return f.f.Close() +} + +func (f *File) Read(p []byte) (int, error) { + return 0, io.EOF +} + +func (f *File) Seek(offset int64, whence int) (int64, error) { + return 0, nil +} + +func (f *File) Readdir(count int) (fi []os.FileInfo, err error) { + ffi, err := f.f.Readdir(count) + for _, f := range ffi { + fi = append(fi, &FileInfo{f}) + } + return +} + +func (f *File) Stat() (os.FileInfo, error) { + fi, err := f.f.Stat() + if err != nil { + return nil, err + } + return &FileInfo{fi}, nil +} + +func (f *File) Write(p []byte) (int, error) { + return f.f.Write(p) +} diff --git a/fileinfo.go b/fileinfo.go new file mode 100644 index 0000000..3b4589f --- /dev/null +++ b/fileinfo.go @@ -0,0 +1,44 @@ +// Copyright (C) 2019 Marius Schellenberger + +package authdav + +import ( + "os" + "time" +) + +// FileInfo is a wrapper for os.FileInfo which always raturns a zero length for regular files +type FileInfo struct { + fi os.FileInfo +} + +func (fi *FileInfo) Name() string { + return fi.fi.Name() +} + +func (fi *FileInfo) RealSize() int64 { + return fi.fi.Size() +} + +func (fi *FileInfo) Size() int64 { + if fi.Mode().IsRegular() { + return 0 + } + return fi.fi.Size() +} + +func (fi *FileInfo) Mode() os.FileMode { + return fi.fi.Mode() +} + +func (fi *FileInfo) ModTime() time.Time { + return fi.fi.ModTime() +} + +func (fi *FileInfo) IsDir() bool { + return fi.fi.IsDir() +} + +func (fi *FileInfo) Sys() interface{} { + return fi.fi.Sys() +} diff --git a/filesystem.go b/filesystem.go new file mode 100644 index 0000000..cec9633 --- /dev/null +++ b/filesystem.go @@ -0,0 +1,65 @@ +// Copyright (C) 2019 Marius Schellenberger + +package authdav + +import ( + "context" + "errors" + "golang.org/x/net/webdav" + "os" +) + +// ErrWonly indicates forbidden operation +var ErrWonly = errors.New("authdav: only file write once operations permitted") + +const writeFlag = os.O_RDWR | os.O_CREATE | os.O_TRUNC + +// WriteOnlyOnceFileSystem is a file write only wrapper for a webdav.FileSystem +type WriteOnlyOnceFileSystem struct { + fs webdav.FileSystem +} + +// NewWriteOnlyOnceFileSystem returns a new WriteOnlyOnceFileSystem wrapping a webdav.FileSystem +func NewWriteOnlyOnceFileSystem(fs webdav.FileSystem) *WriteOnlyOnceFileSystem { + return &WriteOnlyOnceFileSystem{fs} +} + +// Mkdir is not permitted +func (fs *WriteOnlyOnceFileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error { + return ErrWonly +} + +// OpenFile allows write only once operations +func (fs *WriteOnlyOnceFileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { + fi, err := fs.Stat(ctx, name) + if err == nil && fi.Mode().IsRegular() { + f, ok := fi.(*FileInfo) + if ok && flag == writeFlag && f.RealSize() > 0 { + return nil, ErrWonly + } + } + f, err := fs.fs.OpenFile(ctx, name, flag, perm) + if err != nil { + return nil, err + } + return &File{f}, nil +} + +// RemoveAll is not permitted +func (fs *WriteOnlyOnceFileSystem) RemoveAll(ctx context.Context, name string) error { + return ErrWonly +} + +// Rename is not permitted +func (fs *WriteOnlyOnceFileSystem) Rename(ctx context.Context, oldName, newName string) error { + return ErrWonly +} + +// Stat returns os.FileInfo wrapped in FileInfo +func (fs *WriteOnlyOnceFileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) { + fi, err := fs.fs.Stat(ctx, name) + if err != nil { + return nil, err + } + return &FileInfo{fi}, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ac73fd3 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module git.giftfish.de/ston1th/authdav