feat: permission changing (#5)

pull/3756/head
Laurynas Gadliauskas 2021-06-02 11:15:09 +03:00 committed by GitHub
parent b098e4cb99
commit 8fbda126e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 325 additions and 3 deletions

View File

@ -185,3 +185,9 @@ export async function unarchive(path, name, override) {
const url = `${path}?action=${action}&destination=${to}&override=${override}`; const url = `${path}?action=${action}&destination=${to}&override=${override}`;
return resourceAction(url, "PATCH"); 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");
}

View File

@ -41,6 +41,8 @@
<p class="modified"> <p class="modified">
<time :datetime="modified">{{ humanTime() }}</time> <time :datetime="modified">{{ humanTime() }}</time>
</p> </p>
<p class="permissions">{{ permissions() }}</p>
</div> </div>
</div> </div>
</template> </template>
@ -68,6 +70,7 @@ export default {
"url", "url",
"type", "type",
"size", "size",
"mode",
"modified", "modified",
"index", "index",
"readOnly", "readOnly",
@ -116,6 +119,26 @@ export default {
}, },
methods: { methods: {
...mapMutations(["addSelected", "removeSelected", "resetSelected"]), ...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 () { humanSize: function () {
return filesize(this.size); return filesize(this.size);
}, },

View File

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

View File

@ -14,6 +14,7 @@ import Download from "./Download";
import Move from "./Move"; import Move from "./Move";
import Archive from "./Archive"; import Archive from "./Archive";
import Unarchive from "./Unarchive"; import Unarchive from "./Unarchive";
import Permissions from "./Permissions";
import Copy from "./Copy"; import Copy from "./Copy";
import NewFile from "./NewFile"; import NewFile from "./NewFile";
import NewDir from "./NewDir"; import NewDir from "./NewDir";
@ -35,6 +36,7 @@ export default {
Move, Move,
Archive, Archive,
Unarchive, Unarchive,
Permissions,
Copy, Copy,
Share, Share,
NewFile, NewFile,
@ -97,6 +99,7 @@ export default {
"move", "move",
"archive", "archive",
"unarchive", "unarchive",
"permissions",
"copy", "copy",
"newFile", "newFile",
"newDir", "newDir",

View File

@ -155,7 +155,19 @@
} }
#listing.list .item .size { #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 { #listing .item.header {
@ -247,6 +259,11 @@
position: absolute; position: absolute;
} }
#listing .item[aria-selected=true] .symlink-icon {
color: var(--blue);
position: absolute;
}
#listing.mosaic .item .symlink-icon.dir { #listing.mosaic .item .symlink-icon.dir {
font-size: 1.5em; font-size: 1.5em;
left: .45em; left: .45em;

View File

@ -8,6 +8,9 @@
main { main {
width: calc(100% - 13em) width: calc(100% - 13em)
} }
#listing.list .item .permissions {
display: none;
}
} }
@media (max-width: 736px) { @media (max-width: 736px) {
@ -20,6 +23,9 @@
#listing.list .item .name { #listing.list .item .name {
width: 60%; width: 60%;
} }
#listing.list .item .permissions {
display: none;
}
#more { #more {
display: inherit display: inherit
} }

View File

@ -3,6 +3,7 @@
"archive": "Archive", "archive": "Archive",
"unarchive": "Unarchive", "unarchive": "Unarchive",
"cancel": "Cancel", "cancel": "Cancel",
"permissions": "Permissions",
"close": "Close", "close": "Close",
"copy": "Copy", "copy": "Copy",
"copyFile": "Copy file", "copyFile": "Copy file",
@ -116,6 +117,14 @@
"permanent": "Permanent", "permanent": "Permanent",
"prompts": { "prompts": {
"unsavedChanges": "Changes that you made may not be saved. Leave page?", "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", "archive": "Archive",
"archiveMessage": "Choose archive name and format:", "archiveMessage": "Choose archive name and format:",
"unarchive": "Unarchive", "unarchive": "Unarchive",

View File

@ -37,6 +37,13 @@
:label="$t('buttons.moveFile')" :label="$t('buttons.moveFile')"
show="move" show="move"
/> />
<action
v-if="headerButtons.permissions"
id="permissions-button"
icon="lock"
:label="$t('buttons.permissions')"
show="permissions"
/>
<action <action
v-if="headerButtons.archive" v-if="headerButtons.archive"
id="archive-button" id="archive-button"
@ -217,6 +224,7 @@
v-bind:link="item.link" v-bind:link="item.link"
v-bind:isDir="item.isDir" v-bind:isDir="item.isDir"
v-bind:url="item.url" v-bind:url="item.url"
v-bind:mode="item.mode"
v-bind:modified="item.modified" v-bind:modified="item.modified"
v-bind:type="item.type" v-bind:type="item.type"
v-bind:size="item.size" v-bind:size="item.size"
@ -235,6 +243,7 @@
v-bind:link="item.link" v-bind:link="item.link"
v-bind:isDir="item.isDir" v-bind:isDir="item.isDir"
v-bind:url="item.url" v-bind:url="item.url"
v-bind:mode="item.mode"
v-bind:modified="item.modified" v-bind:modified="item.modified"
v-bind:type="item.type" v-bind:type="item.type"
v-bind:size="item.size" v-bind:size="item.size"
@ -384,6 +393,7 @@ export default {
share: this.selectedCount === 1 && this.user.perm.share, share: this.selectedCount === 1 && this.user.perm.share,
move: this.selectedCount > 0 && this.user.perm.rename, move: this.selectedCount > 0 && this.user.perm.rename,
copy: this.selectedCount > 0 && this.user.perm.create, copy: this.selectedCount > 0 && this.user.perm.create,
permissions: this.selectedCount === 1 && this.user.perm.modify,
archive: this.selectedCount > 0 && this.user.perm.create, archive: this.selectedCount > 0 && this.user.perm.create,
unarchive: this.selectedCount === 1 && this.onlyArchivesSelected, unarchive: this.selectedCount === 1 && this.onlyArchivesSelected,
}; };

View File

@ -10,6 +10,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/mholt/archiver" "github.com/mholt/archiver"
@ -211,6 +212,12 @@ func resourcePatchHandler(fileCache FileCache) handleFunc {
src := r.URL.Path src := r.URL.Path
dst := r.URL.Query().Get("destination") dst := r.URL.Query().Get("destination")
action := r.URL.Query().Get("action") action := r.URL.Query().Get("action")
if action == "chmod" {
err := chmodActionHandler(r, d)
return errToStatus(err), err
}
dst, err := url.QueryUnescape(dst) dst, err := url.QueryUnescape(dst)
if !d.Check(src) || !d.Check(dst) { if !d.Check(src) || !d.Check(dst) {
return http.StatusForbidden, nil return http.StatusForbidden, nil
@ -229,7 +236,7 @@ func resourcePatchHandler(fileCache FileCache) handleFunc {
override := r.URL.Query().Get("override") == "true" override := r.URL.Query().Get("override") == "true"
rename := r.URL.Query().Get("rename") == "true" rename := r.URL.Query().Get("rename") == "true"
unarchive := r.URL.Query().Get("action") == "unarchive" unarchive := action == "unarchive"
if !override && !rename && !unarchive { if !override && !rename && !unarchive {
if _, err = d.user.Fs.Stat(dst); err == nil { if _, err = d.user.Fs.Stat(dst); err == nil {
return http.StatusConflict, 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)) src = d.user.FullPath(path.Clean("/" + src))
dst = d.user.FullPath(path.Clean("/" + dst)) dst = d.user.FullPath(path.Clean("/" + dst))
// THIS COULD BE VUNERABLE TO https://github.com/snyk/zip-slip-vulnerability
err := archiver.Unarchive(src, dst) err := archiver.Unarchive(src, dst)
if err != nil { if err != nil {
return errors.ErrInvalidRequestParams return errors.ErrInvalidRequestParams
@ -469,3 +475,38 @@ func parseArchiver(algo string) (string, archiver.Archiver, error) {
return "", nil, fmt.Errorf("format not implemented") 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))
}
}