feat: add Read/Write Permission

This commit is contained in:
bear0412 2023-07-15 20:08:30 +00:00
parent 6744cd47ce
commit b6cce0e0e2
8 changed files with 135 additions and 22 deletions

View File

@ -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
}

View File

@ -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"),
},
}

View File

@ -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
}

View File

@ -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];

View File

@ -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")

View File

@ -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,9 +183,20 @@ 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) {
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
}

View File

@ -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 {

View File

@ -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