feat: add Read/Write Permission
This commit is contained in:
parent
6744cd47ce
commit
b6cce0e0e2
21
auth/hook.go
21
auth/hook.go
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/errors"
|
"github.com/filebrowser/filebrowser/v2/errors"
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
"github.com/filebrowser/filebrowser/v2/files"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
@ -31,6 +32,7 @@ type HookAuth struct {
|
|||||||
Cred hookCred `json:"-"`
|
Cred hookCred `json:"-"`
|
||||||
Fields hookFields `json:"-"`
|
Fields hookFields `json:"-"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
|
Rules []rules.Rule `json:"rules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the user via a json in content body.
|
// Auth authenticates the user via a json in content body.
|
||||||
@ -228,6 +230,7 @@ func (a *HookAuth) GetUser(d *users.User) *users.User {
|
|||||||
Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc),
|
Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc),
|
||||||
By: a.Fields.GetString("user.sorting.by", d.Sorting.By),
|
By: a.Fields.GetString("user.sorting.by", d.Sorting.By),
|
||||||
},
|
},
|
||||||
|
Rules: a.Fields.GetRules("user.perm.rule", d.Rules),
|
||||||
Commands: a.Fields.GetArray("user.commands", d.Commands),
|
Commands: a.Fields.GetArray("user.commands", d.Commands),
|
||||||
HideDotfiles: a.Fields.GetBoolean("user.hideDotfiles", d.HideDotfiles),
|
HideDotfiles: a.Fields.GetBoolean("user.hideDotfiles", d.HideDotfiles),
|
||||||
Perm: perms,
|
Perm: perms,
|
||||||
@ -261,6 +264,7 @@ var validHookFields = []string{
|
|||||||
"user.perm.delete",
|
"user.perm.delete",
|
||||||
"user.perm.share",
|
"user.perm.share",
|
||||||
"user.perm.download",
|
"user.perm.download",
|
||||||
|
"user.perm.rule",
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid checks if the provided field is on the valid fields list
|
// IsValid checks if the provided field is on the valid fields list
|
||||||
@ -300,3 +304,20 @@ func (hf *hookFields) GetArray(k string, dv []string) []string {
|
|||||||
}
|
}
|
||||||
return dv
|
return dv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hf *hookFields) GetRules(k string, dv []rules.Rule) []rules.Rule {
|
||||||
|
val, ok := hf.Values[k]
|
||||||
|
if !ok {
|
||||||
|
return dv
|
||||||
|
}
|
||||||
|
var dvv []rules.Rule
|
||||||
|
for _, ruleString := range strings.Split(val, ";") {
|
||||||
|
var rule rules.Rule
|
||||||
|
err := json.Unmarshal([]byte(ruleString), &rule)
|
||||||
|
if err != nil {
|
||||||
|
return dv
|
||||||
|
}
|
||||||
|
dvv = append(dvv, rule)
|
||||||
|
}
|
||||||
|
return dvv
|
||||||
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ override the options.`,
|
|||||||
Branding: settings.Branding{
|
Branding: settings.Branding{
|
||||||
Name: mustGetString(flags, "branding.name"),
|
Name: mustGetString(flags, "branding.name"),
|
||||||
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
||||||
DisableUsedPercentage: mustGetBool(flags, "branding.DisableUsedPercentage"),
|
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"),
|
||||||
Files: mustGetString(flags, "branding.files"),
|
Files: mustGetString(flags, "branding.files"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ type FileOptions struct {
|
|||||||
// object will be automatically filled depending on if it is a directory
|
// object will be automatically filled depending on if it is a directory
|
||||||
// or a file. If it's a video file, it will also detect any subtitles.
|
// or a file. If it's a video file, it will also detect any subtitles.
|
||||||
func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||||
if !opts.Checker.Check(opts.Path) {
|
if !opts.Checker.CheckReadPerm(opts.Path) { // display folder/file list
|
||||||
return nil, os.ErrPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,7 +319,7 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
|||||||
name := f.Name()
|
name := f.Name()
|
||||||
fPath := path.Join(i.Path, name)
|
fPath := path.Join(i.Path, name)
|
||||||
|
|
||||||
if !checker.Check(fPath) {
|
if !checker.CheckReadPerm(fPath) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,20 +4,15 @@
|
|||||||
<input type="checkbox" v-model="rule.regex" /><label>Regex</label>
|
<input type="checkbox" v-model="rule.regex" /><label>Regex</label>
|
||||||
<input type="checkbox" v-model="rule.allow" /><label>Allow</label>
|
<input type="checkbox" v-model="rule.allow" /><label>Allow</label>
|
||||||
|
|
||||||
<input
|
<input @keypress.enter.prevent type="text" style="margin-right: 8px;" v-if="rule.regex" v-model="rule.regexp.raw"
|
||||||
@keypress.enter.prevent
|
:placeholder="$t('settings.insertRegex')" />
|
||||||
type="text"
|
<input @keypress.enter.prevent type="text" style="margin-right: 8px;" v-else v-model="rule.path"
|
||||||
v-if="rule.regex"
|
:placeholder="$t('settings.insertPath')" />
|
||||||
v-model="rule.regexp.raw"
|
|
||||||
:placeholder="$t('settings.insertRegex')"
|
<input type="checkbox" :checked="rule.perm && rule.perm.includes('read')"
|
||||||
/>
|
@input="changePermOfRule('read', index)" /><label>Read</label>
|
||||||
<input
|
<input type="checkbox" :checked="rule.perm && rule.perm.includes('write')"
|
||||||
@keypress.enter.prevent
|
@input="changePermOfRule('write', index)" /><label>Write</label>
|
||||||
type="text"
|
|
||||||
v-else
|
|
||||||
v-model="rule.path"
|
|
||||||
:placeholder="$t('settings.insertPath')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button class="button button--red" @click="remove($event, index)">
|
<button class="button button--red" @click="remove($event, index)">
|
||||||
-
|
-
|
||||||
@ -37,6 +32,12 @@ export default {
|
|||||||
name: "rules-textarea",
|
name: "rules-textarea",
|
||||||
props: ["rules"],
|
props: ["rules"],
|
||||||
methods: {
|
methods: {
|
||||||
|
changePermOfRule(ruleName, index) {
|
||||||
|
const rule = this.rules[index];
|
||||||
|
const isRead = ruleName === "read" ? rule.perm.includes("read") : !rule.perm.includes("read");
|
||||||
|
const isWrite = ruleName === "write" ? rule.perm.includes("write") : !rule.perm.includes("write");
|
||||||
|
this.rules[index].perm = `${!isRead ? "read" : ""}${!isRead && !isWrite ? "|" : ""}${!isWrite ? "write" : ""}`
|
||||||
|
},
|
||||||
remove(event, index) {
|
remove(event, index) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let rules = [...this.rules];
|
let rules = [...this.rules];
|
||||||
|
|||||||
59
http/data.go
59
http/data.go
@ -4,6 +4,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tomasen/realip"
|
"github.com/tomasen/realip"
|
||||||
|
|
||||||
@ -47,6 +48,64 @@ func (d *data) Check(path string) bool {
|
|||||||
return allow
|
return allow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *data) CheckReadPerm(path string) bool {
|
||||||
|
if d.user.HideDotfiles && rules.MatchHidden(path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
read := true
|
||||||
|
for _, rule := range d.settings.Rules {
|
||||||
|
if rule.Matches(path) {
|
||||||
|
if !rule.Allow {
|
||||||
|
read = false
|
||||||
|
} else {
|
||||||
|
read = strings.Contains(rule.Perm, "read")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range d.user.Rules {
|
||||||
|
if rule.Matches(path) {
|
||||||
|
if !rule.Allow {
|
||||||
|
read = false
|
||||||
|
} else {
|
||||||
|
read = strings.Contains(rule.Perm, "read")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return read
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *data) CheckWritePerm(path string) bool {
|
||||||
|
if d.user.HideDotfiles && rules.MatchHidden(path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
write := true
|
||||||
|
for _, rule := range d.settings.Rules {
|
||||||
|
if rule.Matches(path) {
|
||||||
|
if !rule.Allow {
|
||||||
|
write = false
|
||||||
|
} else {
|
||||||
|
write = strings.Contains(rule.Perm, "write")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range d.user.Rules {
|
||||||
|
if rule.Matches(path) {
|
||||||
|
if !rule.Allow {
|
||||||
|
write = false
|
||||||
|
} else {
|
||||||
|
write = strings.Contains(rule.Perm, "write")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return write
|
||||||
|
}
|
||||||
|
|
||||||
func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler {
|
func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler {
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
|||||||
@ -56,7 +56,7 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
|||||||
|
|
||||||
func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
||||||
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
if r.URL.Path == "/" || !d.user.Perm.Delete {
|
if r.URL.Path == "/" || !d.user.Perm.Delete || !d.CheckWritePerm(r.URL.Path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
|||||||
|
|
||||||
func resourcePostHandler(fileCache FileCache) handleFunc {
|
func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||||
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
if !d.user.Perm.Create || !d.Check(r.URL.Path) {
|
if !d.user.Perm.Create || !d.CheckWritePerm(r.URL.Path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
if !d.user.Perm.Modify || !d.Check(r.URL.Path) {
|
if !d.user.Perm.Modify || !d.CheckWritePerm(r.URL.Path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,9 +183,20 @@ func resourcePatchHandler(fileCache FileCache) handleFunc {
|
|||||||
dst := r.URL.Query().Get("destination")
|
dst := r.URL.Query().Get("destination")
|
||||||
action := r.URL.Query().Get("action")
|
action := r.URL.Query().Get("action")
|
||||||
dst, err := url.QueryUnescape(dst)
|
dst, err := url.QueryUnescape(dst)
|
||||||
if !d.Check(src) || !d.Check(dst) {
|
switch action {
|
||||||
|
case "rename": // rename and move action
|
||||||
|
if !d.CheckWritePerm(src) || !d.CheckWritePerm(dst) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
case "copy": // copy action
|
||||||
|
if !d.CheckReadPerm(src) || !d.CheckWritePerm(dst) {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,10 @@ var shareListHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
|||||||
})
|
})
|
||||||
|
|
||||||
var shareGetsHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var shareGetsHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
|
if !d.CheckReadPerm(r.URL.Path) {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
s, err := d.store.Share.Gets(r.URL.Path, d.user.ID)
|
s, err := d.store.Share.Gets(r.URL.Path, d.user.ID)
|
||||||
if err == errors.ErrNotExist {
|
if err == errors.ErrNotExist {
|
||||||
return renderJSON(w, r, []*share.Link{})
|
return renderJSON(w, r, []*share.Link{})
|
||||||
@ -69,6 +73,10 @@ var shareGetsHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
|||||||
})
|
})
|
||||||
|
|
||||||
var shareDeleteHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var shareDeleteHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
|
if !d.CheckReadPerm(r.URL.Path) {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
hash := strings.TrimSuffix(r.URL.Path, "/")
|
hash := strings.TrimSuffix(r.URL.Path, "/")
|
||||||
hash = strings.TrimPrefix(hash, "/")
|
hash = strings.TrimPrefix(hash, "/")
|
||||||
|
|
||||||
@ -81,6 +89,10 @@ var shareDeleteHandler = withPermShare(func(w http.ResponseWriter, r *http.Reque
|
|||||||
})
|
})
|
||||||
|
|
||||||
var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
|
if !d.CheckReadPerm(r.URL.Path) {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
var s *share.Link
|
var s *share.Link
|
||||||
var body share.CreateBody
|
var body share.CreateBody
|
||||||
if r.Body != nil {
|
if r.Body != nil {
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import (
|
|||||||
// Checker is a Rules checker.
|
// Checker is a Rules checker.
|
||||||
type Checker interface {
|
type Checker interface {
|
||||||
Check(path string) bool
|
Check(path string) bool
|
||||||
|
CheckReadPerm(path string) bool
|
||||||
|
CheckWritePerm(path string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule is a allow/disallow rule.
|
// Rule is a allow/disallow rule.
|
||||||
@ -17,6 +19,13 @@ type Rule struct {
|
|||||||
Allow bool `json:"allow"`
|
Allow bool `json:"allow"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Regexp *Regexp `json:"regexp"`
|
Regexp *Regexp `json:"regexp"`
|
||||||
|
Perm string `json:"perm"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perm is a read/write permission.
|
||||||
|
type Perms struct {
|
||||||
|
Read bool `json:"read"`
|
||||||
|
Write bool `json:"write"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchHidden matches paths with a basename
|
// MatchHidden matches paths with a basename
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user