diff --git a/README.md b/README.md index ba14738..869d72f 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,14 @@ cors: - Content-Length - Content-Range -# The list of users. If users is empty, then there will be no authentication. +# The list of users. If the list is empty, then there will be no authentication. +# Otherwise, basic authentication will automatically be configured. +# +# If you're delegating the authentication to a different service, you can proxy +# the username using basic authentication, and then disable webdav's password +# check using the option: +# +# noPassword: true users: # Example 'admin' user with plaintext password. - username: admin diff --git a/lib/config.go b/lib/config.go index f2ae7ec..1c44715 100644 --- a/lib/config.go +++ b/lib/config.go @@ -32,6 +32,7 @@ type Config struct { Key string Prefix string NoSniff bool + NoPassword bool Log Log CORS CORS Users []User @@ -77,6 +78,7 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) { v.SetDefault("Permissions", "R") v.SetDefault("Debug", false) v.SetDefault("NoSniff", false) + v.SetDefault("NoPassword", false) v.SetDefault("Log.Format", "console") v.SetDefault("Log.Outputs", []string{"stderr"}) v.SetDefault("Log.Colors", true) @@ -159,7 +161,7 @@ func (c *Config) Validate() error { } for _, u := range c.Users { - err := u.Validate() + err := u.Validate(c.NoPassword) if err != nil { return fmt.Errorf("invalid config: %w", err) } diff --git a/lib/handler.go b/lib/handler.go index dd78e7c..979e8cd 100644 --- a/lib/handler.go +++ b/lib/handler.go @@ -17,12 +17,14 @@ type handlerUser struct { } type Handler struct { - user *handlerUser - users map[string]*handlerUser + noPassword bool + user *handlerUser + users map[string]*handlerUser } func NewHandler(c *Config) (http.Handler, error) { h := &Handler{ + noPassword: c.NoPassword, user: &handlerUser{ User: User{ UserPermissions: c.UserPermissions, @@ -67,6 +69,10 @@ func NewHandler(c *Config) (http.Handler, error) { zap.L().Warn("unprotected config: no users have been set, so no authentication will be used") } + if c.NoPassword { + zap.L().Warn("unprotected config: password check is disabled, only intended when delegating authentication to another service") + } + return h, nil } @@ -92,7 +98,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if !user.checkPassword(password) { + if !h.noPassword && !user.checkPassword(password) { zap.L().Info("invalid password", zap.String("username", username), zap.String("remote_address", r.RemoteAddr)) http.Error(w, "Not authorized", http.StatusUnauthorized) return diff --git a/lib/handler_test.go b/lib/handler_test.go index c51b22c..c256916 100644 --- a/lib/handler_test.go +++ b/lib/handler_test.go @@ -183,6 +183,40 @@ users: }) } +func TestServerAuthenticationNoPassword(t *testing.T) { + t.Parallel() + + dir := makeTestDirectory(t, map[string][]byte{ + "foo.txt": []byte("foo"), + "sub/bar.txt": []byte("bar"), + }) + + srv := makeTestServer(t, fmt.Sprintf(` +directory: %s +noPassword: true +permissions: CRUD + +users: + - username: basic +`, dir)) + + t.Run("Basic Auth", func(t *testing.T) { + t.Parallel() + + client := gowebdav.NewClient(srv.URL, "basic", "") + files, err := client.ReadDir("/") + require.NoError(t, err) + require.Len(t, files, 2) + }) + + t.Run("Unauthorized Wrong User", func(t *testing.T) { + t.Parallel() + client := gowebdav.NewClient(srv.URL, "wrong", "") + _, err := client.ReadDir("/") + require.ErrorContains(t, err, "401") + }) +} + func TestServerRules(t *testing.T) { t.Parallel() diff --git a/lib/user.go b/lib/user.go index 81a74ef..bc1576e 100644 --- a/lib/user.go +++ b/lib/user.go @@ -24,12 +24,12 @@ func (u User) checkPassword(input string) bool { return u.Password == input } -func (u *User) Validate() error { +func (u *User) Validate(noPassword bool) error { if u.Username == "" { return errors.New("invalid user: username must be set") } - if u.Password == "" { + if u.Password == "" && !noPassword { return fmt.Errorf("invalid user %q: password must be set", u.Username) } else if strings.HasPrefix(u.Password, "{env}") {