mirror of
https://github.com/hacdias/webdav.git
synced 2026-09-22 03:20:41 +08:00
feat!: fine-grained permissions
This commit is contained in:
@@ -75,8 +75,10 @@ debug: false
|
|||||||
# Default is '.' (current directory).
|
# Default is '.' (current directory).
|
||||||
directory: .
|
directory: .
|
||||||
|
|
||||||
# The default modification permissions for users. Default is 'false'.
|
# The default permissions for users. This is a case insensitive option. Possible
|
||||||
modify: true
|
# permissions: C (Create), R (Read), U (Update), D (Delete). You can combine multiple
|
||||||
|
# permissions. For example, to allow to read and create, set "RC". Default is "R".
|
||||||
|
permissions: R
|
||||||
|
|
||||||
# The default permissions rules for users. Default is none.
|
# The default permissions rules for users. Default is none.
|
||||||
rules: []
|
rules: []
|
||||||
@@ -120,19 +122,19 @@ users:
|
|||||||
password: "{env}ENV_PASSWORD"
|
password: "{env}ENV_PASSWORD"
|
||||||
- username: basic
|
- username: basic
|
||||||
password: basic
|
password: basic
|
||||||
# Override default modify.
|
# Override default permissions.
|
||||||
modify: false
|
permissions: CRUD
|
||||||
rules:
|
rules:
|
||||||
# With this rule, the user CANNOT access /some/files.
|
# With this rule, the user CANNOT access /some/files.
|
||||||
- path: /some/file
|
- path: /some/file
|
||||||
allow: false
|
permissions: none
|
||||||
# With this rule, the user CAN modify /public/access.
|
# With this rule, the user CAN create, read, update and delete within /public/access.
|
||||||
- path: /public/access/
|
- path: /public/access/
|
||||||
modify: true
|
permissions: CRUD
|
||||||
# With this rule, the user CAN modify all files ending with .js. It uses
|
# With this rule, the user CAN read and update all files ending with .js. It uses
|
||||||
# a regular expression.
|
# a regular expression.
|
||||||
- regex: "^.+.js$"
|
- regex: "^.+.js$"
|
||||||
modify: true
|
permissions: RU
|
||||||
```
|
```
|
||||||
|
|
||||||
### CORS
|
### CORS
|
||||||
|
|||||||
+5
-5
@@ -23,7 +23,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Permissions `mapstructure:",squash"`
|
UserPermissions `mapstructure:",squash"`
|
||||||
Debug bool
|
Debug bool
|
||||||
Address string
|
Address string
|
||||||
Port int
|
Port int
|
||||||
@@ -74,7 +74,7 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) {
|
|||||||
|
|
||||||
// Other defaults
|
// Other defaults
|
||||||
v.SetDefault("Directory", ".")
|
v.SetDefault("Directory", ".")
|
||||||
v.SetDefault("Modify", false)
|
v.SetDefault("Permissions", "R")
|
||||||
v.SetDefault("Debug", false)
|
v.SetDefault("Debug", false)
|
||||||
v.SetDefault("NoSniff", false)
|
v.SetDefault("NoSniff", false)
|
||||||
v.SetDefault("Log.Format", "console")
|
v.SetDefault("Log.Format", "console")
|
||||||
@@ -108,8 +108,8 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) {
|
|||||||
cfg.Users[i].Directory = cfg.Directory
|
cfg.Users[i].Directory = cfg.Directory
|
||||||
}
|
}
|
||||||
|
|
||||||
if !v.IsSet(fmt.Sprintf("Users.%d.Modify", i)) {
|
if !v.IsSet(fmt.Sprintf("Users.%d.Permissions", i)) {
|
||||||
cfg.Users[i].Modify = cfg.Modify
|
cfg.Users[i].Permissions = cfg.Permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
if !v.IsSet(fmt.Sprintf("Users.%d.Rules", i)) {
|
if !v.IsSet(fmt.Sprintf("Users.%d.Rules", i)) {
|
||||||
@@ -153,7 +153,7 @@ func (c *Config) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Permissions.Validate()
|
err = c.UserPermissions.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid config: %w", err)
|
return fmt.Errorf("invalid config: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-20
@@ -49,17 +49,25 @@ func TestConfigCascade(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
check := func(t *testing.T, cfg *Config) {
|
check := func(t *testing.T, cfg *Config) {
|
||||||
require.True(t, cfg.Modify)
|
require.True(t, cfg.Permissions.Read)
|
||||||
|
require.True(t, cfg.Permissions.Create)
|
||||||
|
require.False(t, cfg.Permissions.Delete)
|
||||||
|
require.False(t, cfg.Permissions.Update)
|
||||||
require.Equal(t, "/", cfg.Directory)
|
require.Equal(t, "/", cfg.Directory)
|
||||||
require.Len(t, cfg.Rules, 1)
|
require.Len(t, cfg.Rules, 1)
|
||||||
|
|
||||||
require.Len(t, cfg.Users, 2)
|
require.Len(t, cfg.Users, 2)
|
||||||
|
require.True(t, cfg.Users[0].Permissions.Read)
|
||||||
require.True(t, cfg.Users[0].Modify)
|
require.True(t, cfg.Users[0].Permissions.Create)
|
||||||
|
require.False(t, cfg.Users[0].Permissions.Delete)
|
||||||
|
require.False(t, cfg.Users[0].Permissions.Update)
|
||||||
require.Equal(t, "/", cfg.Users[0].Directory)
|
require.Equal(t, "/", cfg.Users[0].Directory)
|
||||||
require.Len(t, cfg.Users[0].Rules, 1)
|
require.Len(t, cfg.Users[0].Rules, 1)
|
||||||
|
|
||||||
require.False(t, cfg.Users[1].Modify)
|
require.True(t, cfg.Users[1].Permissions.Read)
|
||||||
|
require.False(t, cfg.Users[1].Permissions.Create)
|
||||||
|
require.False(t, cfg.Users[1].Permissions.Delete)
|
||||||
|
require.False(t, cfg.Users[1].Permissions.Update)
|
||||||
require.Equal(t, "/basic", cfg.Users[1].Directory)
|
require.Equal(t, "/basic", cfg.Users[1].Directory)
|
||||||
require.Len(t, cfg.Users[1].Rules, 0)
|
require.Len(t, cfg.Users[1].Rules, 0)
|
||||||
}
|
}
|
||||||
@@ -67,10 +75,10 @@ func TestConfigCascade(t *testing.T) {
|
|||||||
t.Run("YAML", func(t *testing.T) {
|
t.Run("YAML", func(t *testing.T) {
|
||||||
content := `
|
content := `
|
||||||
directory: /
|
directory: /
|
||||||
modify: true
|
permissions: CR
|
||||||
rules:
|
rules:
|
||||||
- path: /public/access/
|
- path: /public/access/
|
||||||
modify: true
|
permissions: R
|
||||||
|
|
||||||
users:
|
users:
|
||||||
- username: admin
|
- username: admin
|
||||||
@@ -78,7 +86,7 @@ users:
|
|||||||
- username: basic
|
- username: basic
|
||||||
password: basic
|
password: basic
|
||||||
directory: /basic
|
directory: /basic
|
||||||
modify: false
|
permissions: R
|
||||||
rules: []`
|
rules: []`
|
||||||
|
|
||||||
cfg := writeAndParseConfig(t, content, ".yml")
|
cfg := writeAndParseConfig(t, content, ".yml")
|
||||||
@@ -90,11 +98,11 @@ users:
|
|||||||
t.Run("JSON", func(t *testing.T) {
|
t.Run("JSON", func(t *testing.T) {
|
||||||
content := `{
|
content := `{
|
||||||
"directory": "/",
|
"directory": "/",
|
||||||
"modify": true,
|
"permissions": "CR",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"path": "/public/access/",
|
"path": "/public/access/",
|
||||||
"modify": true
|
"permissions": "R"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"users": [
|
"users": [
|
||||||
@@ -106,7 +114,7 @@ users:
|
|||||||
"username": "basic",
|
"username": "basic",
|
||||||
"password": "basic",
|
"password": "basic",
|
||||||
"directory": "/basic",
|
"directory": "/basic",
|
||||||
"modify": false,
|
"permissions": "R",
|
||||||
"rules": []
|
"rules": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -121,11 +129,11 @@ users:
|
|||||||
t.Run("`TOML", func(t *testing.T) {
|
t.Run("`TOML", func(t *testing.T) {
|
||||||
content := `
|
content := `
|
||||||
directory = "/"
|
directory = "/"
|
||||||
modify = true
|
permissions = "CR"
|
||||||
|
|
||||||
[[rules]]
|
[[rules]]
|
||||||
path = "/public/access/"
|
path = "/public/access/"
|
||||||
modify = true
|
permissions = "R"
|
||||||
|
|
||||||
[[users]]
|
[[users]]
|
||||||
username = "admin"
|
username = "admin"
|
||||||
@@ -135,7 +143,7 @@ password = "admin"
|
|||||||
username = "basic"
|
username = "basic"
|
||||||
password = "basic"
|
password = "basic"
|
||||||
directory = "/basic"
|
directory = "/basic"
|
||||||
modify = false
|
permissions = "R"
|
||||||
rules = []
|
rules = []
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -175,12 +183,9 @@ cors:
|
|||||||
func TestConfigRules(t *testing.T) {
|
func TestConfigRules(t *testing.T) {
|
||||||
content := `
|
content := `
|
||||||
directory: /
|
directory: /
|
||||||
modify: true
|
|
||||||
rules:
|
rules:
|
||||||
- regex: '^.+\.js$'
|
- regex: '^.+\.js$'
|
||||||
modify: true
|
- path: /public/access/`
|
||||||
- path: /public/access/
|
|
||||||
modify: true`
|
|
||||||
|
|
||||||
cfg := writeAndParseConfig(t, content, ".yaml")
|
cfg := writeAndParseConfig(t, content, ".yaml")
|
||||||
require.NoError(t, cfg.Validate())
|
require.NoError(t, cfg.Validate())
|
||||||
@@ -199,7 +204,7 @@ rules:
|
|||||||
func TestConfigEnv(t *testing.T) {
|
func TestConfigEnv(t *testing.T) {
|
||||||
require.NoError(t, os.Setenv("WD_PORT", "1234"))
|
require.NoError(t, os.Setenv("WD_PORT", "1234"))
|
||||||
require.NoError(t, os.Setenv("WD_DEBUG", "true"))
|
require.NoError(t, os.Setenv("WD_DEBUG", "true"))
|
||||||
require.NoError(t, os.Setenv("WD_MODIFY", "true"))
|
require.NoError(t, os.Setenv("WD_PERMISSIONS", "CRUD"))
|
||||||
require.NoError(t, os.Setenv("WD_DIRECTORY", "/test"))
|
require.NoError(t, os.Setenv("WD_DIRECTORY", "/test"))
|
||||||
|
|
||||||
cfg, err := ParseConfig("", nil)
|
cfg, err := ParseConfig("", nil)
|
||||||
@@ -208,11 +213,14 @@ func TestConfigEnv(t *testing.T) {
|
|||||||
assert.Equal(t, 1234, cfg.Port)
|
assert.Equal(t, 1234, cfg.Port)
|
||||||
assert.Equal(t, "/test", cfg.Directory)
|
assert.Equal(t, "/test", cfg.Directory)
|
||||||
assert.Equal(t, true, cfg.Debug)
|
assert.Equal(t, true, cfg.Debug)
|
||||||
assert.Equal(t, true, cfg.Modify)
|
require.True(t, cfg.Permissions.Read)
|
||||||
|
require.True(t, cfg.Permissions.Create)
|
||||||
|
require.True(t, cfg.Permissions.Delete)
|
||||||
|
require.True(t, cfg.Permissions.Update)
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
require.NoError(t, os.Setenv("WD_PORT", ""))
|
require.NoError(t, os.Setenv("WD_PORT", ""))
|
||||||
require.NoError(t, os.Setenv("WD_DEBUG", ""))
|
require.NoError(t, os.Setenv("WD_DEBUG", ""))
|
||||||
require.NoError(t, os.Setenv("WD_MODIFY", ""))
|
require.NoError(t, os.Setenv("WD_PERMISSIONS", ""))
|
||||||
require.NoError(t, os.Setenv("WD_DIRECTORY", ""))
|
require.NoError(t, os.Setenv("WD_DIRECTORY", ""))
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-2
@@ -2,6 +2,8 @@ package lib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
@@ -23,7 +25,7 @@ func NewHandler(c *Config) (http.Handler, error) {
|
|||||||
h := &Handler{
|
h := &Handler{
|
||||||
user: &handlerUser{
|
user: &handlerUser{
|
||||||
User: User{
|
User: User{
|
||||||
Permissions: c.Permissions,
|
UserPermissions: c.UserPermissions,
|
||||||
},
|
},
|
||||||
Handler: webdav.Handler{
|
Handler: webdav.Handler{
|
||||||
Prefix: c.Prefix,
|
Prefix: c.Prefix,
|
||||||
@@ -100,7 +102,15 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checks for user permissions relatively to this PATH.
|
// Checks for user permissions relatively to this PATH.
|
||||||
allowed := user.Allowed(r)
|
allowed := user.Allowed(r, func(destination string) bool {
|
||||||
|
u, err := url.Parse(destination)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
path := strings.TrimPrefix(u.Path, user.Prefix)
|
||||||
|
_, err = user.FileSystem.Stat(r.Context(), path)
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
})
|
||||||
|
|
||||||
zap.L().Debug("allowed & method & path", zap.Bool("allowed", allowed), zap.String("method", r.Method), zap.String("path", r.URL.Path))
|
zap.L().Debug("allowed & method & path", zap.Bool("allowed", allowed), zap.String("method", r.Method), zap.String("path", r.URL.Path))
|
||||||
|
|
||||||
|
|||||||
+33
-8
@@ -133,7 +133,7 @@ func TestServerAuthentication(t *testing.T) {
|
|||||||
|
|
||||||
srv := makeTestServer(t, fmt.Sprintf(`
|
srv := makeTestServer(t, fmt.Sprintf(`
|
||||||
directory: %s
|
directory: %s
|
||||||
modify: true
|
permissions: CRUD
|
||||||
|
|
||||||
users:
|
users:
|
||||||
- username: basic
|
- username: basic
|
||||||
@@ -191,39 +191,58 @@ func TestServerRules(t *testing.T) {
|
|||||||
"a/foo.js": []byte("foo js"),
|
"a/foo.js": []byte("foo js"),
|
||||||
"a/foo.txt": []byte("foo txt"),
|
"a/foo.txt": []byte("foo txt"),
|
||||||
"b/foo.txt": []byte("foo b"),
|
"b/foo.txt": []byte("foo b"),
|
||||||
|
"c/a.txt": []byte("b"),
|
||||||
|
"c/b.txt": []byte("b"),
|
||||||
|
"c/c.txt": []byte("b"),
|
||||||
})
|
})
|
||||||
|
|
||||||
srv := makeTestServer(t, fmt.Sprintf(`
|
srv := makeTestServer(t, fmt.Sprintf(`
|
||||||
directory: %s
|
directory: %s
|
||||||
modify: true
|
permissions: CRUD
|
||||||
|
|
||||||
users:
|
users:
|
||||||
- username: basic
|
- username: basic
|
||||||
password: basic
|
password: basic
|
||||||
rules:
|
rules:
|
||||||
- regex: "^.+.js$"
|
- regex: "^.+.js$"
|
||||||
modify: false
|
permissions: R
|
||||||
- path: "/b"
|
- path: "/b"
|
||||||
modify: false
|
permissions: R
|
||||||
|
- path: "/a/foo.txt"
|
||||||
|
permissions: none
|
||||||
|
- path: "/c"
|
||||||
|
permissions: none
|
||||||
`, dir))
|
`, dir))
|
||||||
|
|
||||||
client := gowebdav.NewClient(srv.URL, "basic", "basic")
|
client := gowebdav.NewClient(srv.URL, "basic", "basic")
|
||||||
|
|
||||||
files, err := client.ReadDir("/")
|
files, err := client.ReadDir("/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, files, 3)
|
require.Len(t, files, 4)
|
||||||
|
|
||||||
err = client.Write("/foo.txt", []byte("new"), 0666)
|
err = client.Write("/foo.txt", []byte("new"), 0666)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = client.Write("/a/foo.txt", []byte("new"), 0666)
|
err = client.Write("/new.txt", []byte("new"), 0666)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = client.Read("/a/foo.txt")
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
err = client.Write("/a/foo.js", []byte("new"), 0666)
|
err = client.Write("/a/foo.js", []byte("new"), 0666)
|
||||||
require.ErrorContains(t, err, "403")
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
err = client.Write("/b/foo.txt", []byte("new"), 0666)
|
err = client.Write("/b/foo.txt", []byte("new"), 0666)
|
||||||
require.ErrorContains(t, err, "403")
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
_, err = client.ReadDir("/c")
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
_, err = client.Read("/c/a.txt")
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
err = client.Write("/c/b.txt", []byte("new"), 0666)
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerPermissions(t *testing.T) {
|
func TestServerPermissions(t *testing.T) {
|
||||||
@@ -237,7 +256,7 @@ func TestServerPermissions(t *testing.T) {
|
|||||||
|
|
||||||
srv := makeTestServer(t, fmt.Sprintf(`
|
srv := makeTestServer(t, fmt.Sprintf(`
|
||||||
directory: %s
|
directory: %s
|
||||||
modify: true
|
permissions: CR
|
||||||
|
|
||||||
users:
|
users:
|
||||||
- username: a
|
- username: a
|
||||||
@@ -246,7 +265,7 @@ users:
|
|||||||
- username: b
|
- username: b
|
||||||
password: b
|
password: b
|
||||||
directory: %s/b
|
directory: %s/b
|
||||||
modify: false
|
permissions: R
|
||||||
`, dir, dir, dir))
|
`, dir, dir, dir))
|
||||||
|
|
||||||
t.Run("User A", func(t *testing.T) {
|
t.Run("User A", func(t *testing.T) {
|
||||||
@@ -265,6 +284,12 @@ users:
|
|||||||
err = client.Copy("/foo.txt", "/copy.txt", false)
|
err = client.Copy("/foo.txt", "/copy.txt", false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = client.Copy("/foo.txt", "/copy.txt", true)
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
err = client.Rename("/foo.txt", "/copy.txt", true)
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
data, err = client.Read("/copy.txt")
|
data, err = client.Read("/copy.txt")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, []byte("foo a"), data)
|
require.EqualValues(t, []byte("foo a"), data)
|
||||||
|
|||||||
+68
-24
@@ -9,16 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var readMethods = []string{
|
|
||||||
http.MethodGet,
|
|
||||||
http.MethodHead,
|
|
||||||
http.MethodOptions,
|
|
||||||
"PROPFIND",
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Allow bool
|
Permissions Permissions
|
||||||
Modify bool
|
|
||||||
Path string
|
Path string
|
||||||
Regex *regexp.Regexp
|
Regex *regexp.Regexp
|
||||||
}
|
}
|
||||||
@@ -40,36 +32,27 @@ func (r *Rule) Matches(path string) bool {
|
|||||||
return strings.HasPrefix(path, r.Path)
|
return strings.HasPrefix(path, r.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Permissions struct {
|
type UserPermissions struct {
|
||||||
Directory string
|
Directory string
|
||||||
Modify bool
|
Permissions Permissions
|
||||||
Rules []*Rule
|
Rules []*Rule
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allowed checks if the user has permission to access a directory/file
|
// Allowed checks if the user has permission to access a directory/file
|
||||||
func (p Permissions) Allowed(r *http.Request) bool {
|
func (p UserPermissions) Allowed(r *http.Request, destinationExists func(string) bool) bool {
|
||||||
// Determine whether or not it is a read or write request.
|
|
||||||
readRequest := false
|
|
||||||
for _, method := range readMethods {
|
|
||||||
if r.Method == method {
|
|
||||||
readRequest = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through rules beginning from the last one.
|
// Go through rules beginning from the last one.
|
||||||
for i := len(p.Rules) - 1; i >= 0; i-- {
|
for i := len(p.Rules) - 1; i >= 0; i-- {
|
||||||
rule := p.Rules[i]
|
rule := p.Rules[i]
|
||||||
|
|
||||||
if rule.Matches(r.URL.Path) {
|
if rule.Matches(r.URL.Path) {
|
||||||
return rule.Allow && (readRequest || rule.Modify)
|
return rule.Permissions.Allowed(r, destinationExists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return readRequest || p.Modify
|
return p.Permissions.Allowed(r, destinationExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permissions) Validate() error {
|
func (p *UserPermissions) Validate() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
p.Directory, err = filepath.Abs(p.Directory)
|
p.Directory, err = filepath.Abs(p.Directory)
|
||||||
@@ -85,3 +68,64 @@ func (p *Permissions) Validate() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Permissions struct {
|
||||||
|
Create bool
|
||||||
|
Read bool
|
||||||
|
Update bool
|
||||||
|
Delete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Permissions) UnmarshalText(data []byte) error {
|
||||||
|
text := strings.ToLower(string(data))
|
||||||
|
if text == "none" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range text {
|
||||||
|
switch c {
|
||||||
|
case 'c':
|
||||||
|
p.Create = true
|
||||||
|
case 'r':
|
||||||
|
p.Read = true
|
||||||
|
case 'u':
|
||||||
|
p.Update = true
|
||||||
|
case 'd':
|
||||||
|
p.Delete = true
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid permission: %q", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Permissions) Allowed(r *http.Request, destinationExists func(string) bool) bool {
|
||||||
|
switch r.Method {
|
||||||
|
case "GET", "HEAD", "OPTIONS", "POST", "PROPFIND":
|
||||||
|
// Note: POST backend implementation just returns the same thing as GET.
|
||||||
|
return p.Read
|
||||||
|
case "MKCOL":
|
||||||
|
return p.Create
|
||||||
|
case "PROPPATCH":
|
||||||
|
return p.Update
|
||||||
|
case "PUT":
|
||||||
|
if destinationExists(r.URL.Path) {
|
||||||
|
return p.Update
|
||||||
|
} else {
|
||||||
|
return p.Create
|
||||||
|
}
|
||||||
|
case "COPY", "MOVE":
|
||||||
|
if destinationExists(r.Header.Get("Destination")) {
|
||||||
|
return p.Update
|
||||||
|
} else {
|
||||||
|
return p.Create
|
||||||
|
}
|
||||||
|
case "DELETE":
|
||||||
|
return p.Delete
|
||||||
|
case "LOCK", "UNLOCK":
|
||||||
|
return p.Create || p.Read || p.Update || p.Delete
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Permissions `mapstructure:",squash"`
|
UserPermissions `mapstructure:",squash"`
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ func (u *User) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.Permissions.Validate(); err != nil {
|
if err := u.UserPermissions.Validate(); err != nil {
|
||||||
return fmt.Errorf("invalid user %q: %w", u.Username, err)
|
return fmt.Errorf("invalid user %q: %w", u.Username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user