From 8fbda126e4201c69ffc53fc7eba52711748e9e33 Mon Sep 17 00:00:00 2001 From: Laurynas Gadliauskas Date: Wed, 2 Jun 2021 11:15:09 +0300 Subject: [PATCH] feat: permission changing (#5) --- frontend/src/api/files.js | 6 + frontend/src/components/files/ListingItem.vue | 23 ++ .../src/components/prompts/Permissions.vue | 207 ++++++++++++++++++ frontend/src/components/prompts/Prompts.vue | 3 + frontend/src/css/listing.css | 19 +- frontend/src/css/mobile.css | 6 + frontend/src/i18n/en.json | 9 + frontend/src/views/files/Listing.vue | 10 + http/resource.go | 45 +++- 9 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/prompts/Permissions.vue diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js index 23620e92..37d9fe72 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.js @@ -185,3 +185,9 @@ export async function unarchive(path, name, override) { const url = `${path}?action=${action}&destination=${to}&override=${override}`; return resourceAction(url, "PATCH"); } + +export async function chmod(path, perms, recursive = false) { + const action = `chmod`; + const url = `${path}?action=${action}&permissions=${perms}&recursive=${recursive}`; + return resourceAction(url, "PATCH"); +} diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue index 89fc61e9..65aaac22 100644 --- a/frontend/src/components/files/ListingItem.vue +++ b/frontend/src/components/files/ListingItem.vue @@ -41,6 +41,8 @@

+ +

{{ permissions() }}

@@ -68,6 +70,7 @@ export default { "url", "type", "size", + "mode", "modified", "index", "readOnly", @@ -116,6 +119,26 @@ export default { }, methods: { ...mapMutations(["addSelected", "removeSelected", "resetSelected"]), + permissions() { + let s = ""; + if (this.isSymlink) { + s += "l"; + } else if (this.isDir) { + s += "d"; + } else { + s += "-"; + } + s += (this.mode & 256) != 0 ? "r" : "-"; + s += (this.mode & 128) != 0 ? "w" : "-"; + s += (this.mode & 64) != 0 ? "x" : "-"; + s += (this.mode & 32) != 0 ? "r" : "-"; + s += (this.mode & 16) != 0 ? "w" : "-"; + s += (this.mode & 8) != 0 ? "x" : "-"; + s += (this.mode & 4) != 0 ? "r" : "-"; + s += (this.mode & 2) != 0 ? "w" : "-"; + s += (this.mode & 1) != 0 ? "x" : "-"; + return s; + }, humanSize: function () { return filesize(this.size); }, diff --git a/frontend/src/components/prompts/Permissions.vue b/frontend/src/components/prompts/Permissions.vue new file mode 100644 index 00000000..aa34b2d5 --- /dev/null +++ b/frontend/src/components/prompts/Permissions.vue @@ -0,0 +1,207 @@ + + + diff --git a/frontend/src/components/prompts/Prompts.vue b/frontend/src/components/prompts/Prompts.vue index 5e4e190b..6a507970 100644 --- a/frontend/src/components/prompts/Prompts.vue +++ b/frontend/src/components/prompts/Prompts.vue @@ -14,6 +14,7 @@ import Download from "./Download"; import Move from "./Move"; import Archive from "./Archive"; import Unarchive from "./Unarchive"; +import Permissions from "./Permissions"; import Copy from "./Copy"; import NewFile from "./NewFile"; import NewDir from "./NewDir"; @@ -35,6 +36,7 @@ export default { Move, Archive, Unarchive, + Permissions, Copy, Share, NewFile, @@ -97,6 +99,7 @@ export default { "move", "archive", "unarchive", + "permissions", "copy", "newFile", "newDir", diff --git a/frontend/src/css/listing.css b/frontend/src/css/listing.css index d0a2c717..171ab97e 100644 --- a/frontend/src/css/listing.css +++ b/frontend/src/css/listing.css @@ -155,7 +155,19 @@ } #listing.list .item .size { - width: 25%; + width: 15%; +} + +#listing.list .item .modified { + width: 20%; +} + +#listing .item .permissions { + font-family: monospace, monospace; +} + +#listing.mosaic .item .permissions { + display: none; } #listing .item.header { @@ -247,6 +259,11 @@ position: absolute; } +#listing .item[aria-selected=true] .symlink-icon { + color: var(--blue); + position: absolute; +} + #listing.mosaic .item .symlink-icon.dir { font-size: 1.5em; left: .45em; diff --git a/frontend/src/css/mobile.css b/frontend/src/css/mobile.css index 6417080f..a0ec42a8 100644 --- a/frontend/src/css/mobile.css +++ b/frontend/src/css/mobile.css @@ -8,6 +8,9 @@ main { width: calc(100% - 13em) } + #listing.list .item .permissions { + display: none; + } } @media (max-width: 736px) { @@ -20,6 +23,9 @@ #listing.list .item .name { width: 60%; } + #listing.list .item .permissions { + display: none; + } #more { display: inherit } diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index d2c51f9f..7c32a0cc 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -3,6 +3,7 @@ "archive": "Archive", "unarchive": "Unarchive", "cancel": "Cancel", + "permissions": "Permissions", "close": "Close", "copy": "Copy", "copyFile": "Copy file", @@ -116,6 +117,14 @@ "permanent": "Permanent", "prompts": { "unsavedChanges": "Changes that you made may not be saved. Leave page?", + "permissions": "Permissions", + "read": "Read", + "write": "Write", + "execute": "Execute", + "owner": "Owner", + "group": "Group", + "others": "Others", + "recursive": "Recursive", "archive": "Archive", "archiveMessage": "Choose archive name and format:", "unarchive": "Unarchive", diff --git a/frontend/src/views/files/Listing.vue b/frontend/src/views/files/Listing.vue index dd69e03b..8f25ccc0 100644 --- a/frontend/src/views/files/Listing.vue +++ b/frontend/src/views/files/Listing.vue @@ -37,6 +37,13 @@ :label="$t('buttons.moveFile')" show="move" /> + 0 && this.user.perm.rename, copy: this.selectedCount > 0 && this.user.perm.create, + permissions: this.selectedCount === 1 && this.user.perm.modify, archive: this.selectedCount > 0 && this.user.perm.create, unarchive: this.selectedCount === 1 && this.onlyArchivesSelected, }; diff --git a/http/resource.go b/http/resource.go index d9596f47..e0ecf79c 100644 --- a/http/resource.go +++ b/http/resource.go @@ -10,6 +10,7 @@ import ( "os" "path" "path/filepath" + "strconv" "strings" "github.com/mholt/archiver" @@ -211,6 +212,12 @@ func resourcePatchHandler(fileCache FileCache) handleFunc { src := r.URL.Path dst := r.URL.Query().Get("destination") action := r.URL.Query().Get("action") + + if action == "chmod" { + err := chmodActionHandler(r, d) + return errToStatus(err), err + } + dst, err := url.QueryUnescape(dst) if !d.Check(src) || !d.Check(dst) { return http.StatusForbidden, nil @@ -229,7 +236,7 @@ func resourcePatchHandler(fileCache FileCache) handleFunc { override := r.URL.Query().Get("override") == "true" rename := r.URL.Query().Get("rename") == "true" - unarchive := r.URL.Query().Get("action") == "unarchive" + unarchive := action == "unarchive" if !override && !rename && !unarchive { if _, err = d.user.Fs.Stat(dst); err == nil { return http.StatusConflict, nil @@ -339,7 +346,6 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach src = d.user.FullPath(path.Clean("/" + src)) dst = d.user.FullPath(path.Clean("/" + dst)) - // THIS COULD BE VUNERABLE TO https://github.com/snyk/zip-slip-vulnerability err := archiver.Unarchive(src, dst) if err != nil { return errors.ErrInvalidRequestParams @@ -469,3 +475,38 @@ func parseArchiver(algo string) (string, archiver.Archiver, error) { return "", nil, fmt.Errorf("format not implemented") } } + +func chmodActionHandler(r *http.Request, d *data) error { + target := r.URL.Path + recursive := r.URL.Query().Get("recursive") == "true" + perms := r.URL.Query().Get("permissions") + + if !d.user.Perm.Modify { + return errors.ErrPermissionDenied + } + + if !d.Check(target) || target == "/" { + return errors.ErrPermissionDenied + } + + mode, err := strconv.ParseUint(perms, 10, 32) + if err != nil { + return errors.ErrInvalidRequestParams + } + + info, err := d.user.Fs.Stat(target) + if err != nil { + return err + } + + if recursive && info.IsDir() { + return afero.Walk(d.user.Fs, target, func(name string, info os.FileInfo, err error) error { + if err == nil { + err = d.user.Fs.Chmod(name, os.FileMode(mode)) + } + return err + }) + } else { + return d.user.Fs.Chmod(target, os.FileMode(mode)) + } +}