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/files"
|
||||
"github.com/filebrowser/filebrowser/v2/rules"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/users"
|
||||
)
|
||||
@ -31,6 +32,7 @@ type HookAuth struct {
|
||||
Cred hookCred `json:"-"`
|
||||
Fields hookFields `json:"-"`
|
||||
Command string `json:"command"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
}
|
||||
|
||||
// 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),
|
||||
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),
|
||||
HideDotfiles: a.Fields.GetBoolean("user.hideDotfiles", d.HideDotfiles),
|
||||
Perm: perms,
|
||||
@ -261,6 +264,7 @@ var validHookFields = []string{
|
||||
"user.perm.delete",
|
||||
"user.perm.share",
|
||||
"user.perm.download",
|
||||
"user.perm.rule",
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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{
|
||||
Name: mustGetString(flags, "branding.name"),
|
||||
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
||||
DisableUsedPercentage: mustGetBool(flags, "branding.DisableUsedPercentage"),
|
||||
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"),
|
||||
Files: mustGetString(flags, "branding.files"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ type FileOptions struct {
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
|
||||
@ -319,7 +319,7 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
||||
name := f.Name()
|
||||
fPath := path.Join(i.Path, name)
|
||||
|
||||
if !checker.Check(fPath) {
|
||||
if !checker.CheckReadPerm(fPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@ -4,20 +4,15 @@
|
||||
<input type="checkbox" v-model="rule.regex" /><label>Regex</label>
|
||||
<input type="checkbox" v-model="rule.allow" /><label>Allow</label>
|
||||
|
||||
<input
|
||||
@keypress.enter.prevent
|
||||
type="text"
|
||||
v-if="rule.regex"
|
||||
v-model="rule.regexp.raw"
|
||||
:placeholder="$t('settings.insertRegex')"
|
||||
/>
|
||||
<input
|
||||
@keypress.enter.prevent
|
||||
type="text"
|
||||
v-else
|
||||
v-model="rule.path"
|
||||
:placeholder="$t('settings.insertPath')"
|
||||
/>
|
||||
<input @keypress.enter.prevent type="text" style="margin-right: 8px;" v-if="rule.regex" v-model="rule.regexp.raw"
|
||||
:placeholder="$t('settings.insertRegex')" />
|
||||
<input @keypress.enter.prevent type="text" style="margin-right: 8px;" v-else v-model="rule.path"
|
||||
:placeholder="$t('settings.insertPath')" />
|
||||
|
||||
<input type="checkbox" :checked="rule.perm && rule.perm.includes('read')"
|
||||
@input="changePermOfRule('read', index)" /><label>Read</label>
|
||||
<input type="checkbox" :checked="rule.perm && rule.perm.includes('write')"
|
||||
@input="changePermOfRule('write', index)" /><label>Write</label>
|
||||
|
||||
<button class="button button--red" @click="remove($event, index)">
|
||||
-
|
||||
@ -37,6 +32,12 @@ export default {
|
||||
name: "rules-textarea",
|
||||
props: ["rules"],
|
||||
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) {
|
||||
event.preventDefault();
|
||||
let rules = [...this.rules];
|
||||
|
||||
59
http/data.go
59
http/data.go
@ -4,6 +4,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tomasen/realip"
|
||||
|
||||
@ -47,6 +48,64 @@ func (d *data) Check(path string) bool {
|
||||
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 {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
||||
|
||||
func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||
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
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -183,8 +183,19 @@ func resourcePatchHandler(fileCache FileCache) handleFunc {
|
||||
dst := r.URL.Query().Get("destination")
|
||||
action := r.URL.Query().Get("action")
|
||||
dst, err := url.QueryUnescape(dst)
|
||||
if !d.Check(src) || !d.Check(dst) {
|
||||
return http.StatusForbidden, nil
|
||||
switch action {
|
||||
case "rename": // rename and move action
|
||||
if !d.CheckWritePerm(src) || !d.CheckWritePerm(dst) {
|
||||
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 {
|
||||
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) {
|
||||
if !d.CheckReadPerm(r.URL.Path) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
s, err := d.store.Share.Gets(r.URL.Path, d.user.ID)
|
||||
if err == errors.ErrNotExist {
|
||||
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) {
|
||||
if !d.CheckReadPerm(r.URL.Path) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
hash := strings.TrimSuffix(r.URL.Path, "/")
|
||||
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) {
|
||||
if !d.CheckReadPerm(r.URL.Path) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
var s *share.Link
|
||||
var body share.CreateBody
|
||||
if r.Body != nil {
|
||||
|
||||
@ -9,6 +9,8 @@ import (
|
||||
// Checker is a Rules checker.
|
||||
type Checker interface {
|
||||
Check(path string) bool
|
||||
CheckReadPerm(path string) bool
|
||||
CheckWritePerm(path string) bool
|
||||
}
|
||||
|
||||
// Rule is a allow/disallow rule.
|
||||
@ -17,6 +19,13 @@ type Rule struct {
|
||||
Allow bool `json:"allow"`
|
||||
Path string `json:"path"`
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user