feat: permission changing (#5)
parent
b098e4cb99
commit
8fbda126e4
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
<p class="modified">
|
||||
<time :datetime="modified">{{ humanTime() }}</time>
|
||||
</p>
|
||||
|
||||
<p class="permissions">{{ permissions() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
<template>
|
||||
<div class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("prompts.permissions") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<table class="permissions">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{{ $t("prompts.read") }}</td>
|
||||
<td>{{ $t("prompts.write") }}</td>
|
||||
<td>{{ $t("prompts.execute") }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="permission-row">
|
||||
<td>{{ $t("prompts.owner") }}</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.owner.read" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.owner.write" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.owner.execute" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="permission-row">
|
||||
<td>{{ $t("prompts.group") }}</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.group.read" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.group.write" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.group.execute" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="permission-row">
|
||||
<td>{{ $t("prompts.others") }}</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.others.read" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.others.write" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" v-model="permissions.others.execute" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<code>{{ permModeString }} ({{ permMode.toString(8) }})</code>
|
||||
</p>
|
||||
<p v-if="dirSelected">
|
||||
<input type="checkbox" v-model="recursive" />
|
||||
{{ $t("prompts.recursive") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="chmod"
|
||||
:disabled="loading"
|
||||
:aria-label="$t('buttons.update')"
|
||||
:title="$t('buttons.update')"
|
||||
>
|
||||
{{ $t("buttons.update") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
|
||||
export default {
|
||||
name: "permissions",
|
||||
data: function () {
|
||||
return {
|
||||
recursive: false,
|
||||
permissions: {
|
||||
owner: {
|
||||
read: false,
|
||||
write: false,
|
||||
execute: false,
|
||||
},
|
||||
group: {
|
||||
read: false,
|
||||
write: false,
|
||||
execute: false,
|
||||
},
|
||||
others: {
|
||||
read: false,
|
||||
write: false,
|
||||
execute: false,
|
||||
},
|
||||
},
|
||||
masks: {
|
||||
permissions: 511,
|
||||
owner: {
|
||||
read: 256,
|
||||
write: 128,
|
||||
execute: 64,
|
||||
},
|
||||
group: {
|
||||
read: 32,
|
||||
write: 16,
|
||||
execute: 8,
|
||||
},
|
||||
others: {
|
||||
read: 4,
|
||||
write: 2,
|
||||
execute: 1,
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "selected"]),
|
||||
...mapGetters(["isFiles", "isListing"]),
|
||||
permMode() {
|
||||
let mode = 0;
|
||||
mode |= this.masks.owner.read * this.permissions.owner.read;
|
||||
mode |= this.masks.owner.write * this.permissions.owner.write;
|
||||
mode |= this.masks.owner.execute * this.permissions.owner.execute;
|
||||
mode |= this.masks.group.read * this.permissions.group.read;
|
||||
mode |= this.masks.group.write * this.permissions.group.write;
|
||||
mode |= this.masks.group.execute * this.permissions.group.execute;
|
||||
mode |= this.masks.others.read * this.permissions.others.read;
|
||||
mode |= this.masks.others.write * this.permissions.others.write;
|
||||
mode |= this.masks.others.execute * this.permissions.others.execute;
|
||||
return mode;
|
||||
},
|
||||
permModeString() {
|
||||
let perms = this.permMode;
|
||||
let s = "";
|
||||
s += (perms & this.masks.owner.read) != 0 ? "r" : "-";
|
||||
s += (perms & this.masks.owner.write) != 0 ? "w" : "-";
|
||||
s += (perms & this.masks.owner.execute) != 0 ? "x" : "-";
|
||||
s += (perms & this.masks.group.read) != 0 ? "r" : "-";
|
||||
s += (perms & this.masks.group.write) != 0 ? "w" : "-";
|
||||
s += (perms & this.masks.group.execute) != 0 ? "x" : "-";
|
||||
s += (perms & this.masks.others.read) != 0 ? "r" : "-";
|
||||
s += (perms & this.masks.others.write) != 0 ? "w" : "-";
|
||||
s += (perms & this.masks.others.execute) != 0 ? "x" : "-";
|
||||
return s;
|
||||
},
|
||||
dirSelected() {
|
||||
return this.req.items[this.selected[0]].isDir;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
let item = this.req.items[this.selected[0]];
|
||||
let perms = item.mode & this.masks.permissions;
|
||||
|
||||
// OWNER PERMS
|
||||
this.permissions.owner.read = (perms & this.masks.owner.read) != 0;
|
||||
this.permissions.owner.write = (perms & this.masks.owner.write) != 0;
|
||||
this.permissions.owner.execute = (perms & this.masks.owner.execute) != 0;
|
||||
// GROUP PERMS
|
||||
this.permissions.group.read = (perms & this.masks.group.read) != 0;
|
||||
this.permissions.group.write = (perms & this.masks.group.write) != 0;
|
||||
this.permissions.group.execute = (perms & this.masks.group.execute) != 0;
|
||||
// OTHERS PERMS
|
||||
this.permissions.others.read = (perms & this.masks.others.read) != 0;
|
||||
this.permissions.others.write = (perms & this.masks.others.write) != 0;
|
||||
this.permissions.others.execute = (perms & this.masks.others.execute) != 0;
|
||||
},
|
||||
methods: {
|
||||
cancel: function () {
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
chmod: async function () {
|
||||
let item = this.req.items[this.selected[0]];
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
await api.chmod(item.url, this.permMode, this.recursive);
|
||||
|
||||
this.$store.commit("setReload", true);
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -37,6 +37,13 @@
|
|||
:label="$t('buttons.moveFile')"
|
||||
show="move"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.permissions"
|
||||
id="permissions-button"
|
||||
icon="lock"
|
||||
:label="$t('buttons.permissions')"
|
||||
show="permissions"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.archive"
|
||||
id="archive-button"
|
||||
|
@ -217,6 +224,7 @@
|
|||
v-bind:link="item.link"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:mode="item.mode"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size"
|
||||
|
@ -235,6 +243,7 @@
|
|||
v-bind:link="item.link"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:mode="item.mode"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size"
|
||||
|
@ -384,6 +393,7 @@ export default {
|
|||
share: this.selectedCount === 1 && this.user.perm.share,
|
||||
move: this.selectedCount > 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,
|
||||
};
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue