UI: Add permission checks (photos) and refactor acl evaluation for public mode #98

This commit is contained in:
Timo Volkmann 2021-10-14 10:40:57 +02:00
parent e29dfcab1c
commit 0e09eee673
15 changed files with 79 additions and 82 deletions

View file

@ -57,7 +57,7 @@ import Hls from "hls.js";
import "common/maptiler-lang";
import { $gettext, Mount } from "common/vm";
import * as offline from "@lcdp/offline-plugin/runtime";
import Acl from "./common/acl";
import Acl, { Constants } from "./common/acl";
// Initialize helpers
const viewer = new Viewer();
@ -103,11 +103,26 @@ Vue.prototype.$earlyAccess = () => {
Vue.mixin({
data() {
return {};
return {
aclResources: Constants.resources,
aclActions: Constants.actions,
};
},
computed: {
acl() {
return new Acl(window.__CONFIG__.acl);
return new Acl(this.$config.values.acl);
// return new Acl(window.__CONFIG__.acl);
},
},
methods: {
hasPermission(resource, action) {
console.log(this.$config.values);
if (this.$config.values.public) return true;
// let acl = new Acl(window.__CONFIG__.acl);
console.log(`USER: ${this.$session.getUser().UserName}`);
console.log(this.$session.getUser());
console.log(`ROLE: ${this.$session.getUser().getRole()}`);
return this.acl.accessAllowed(this.$session.getUser().getRole(), resource, action);
},
},
});

View file

@ -23,6 +23,7 @@ export const Constants = {
ActionUpdate: "update",
ActionUpdateSelf: "update-self",
ActionDelete: "delete",
ActionArchive: "archive",
ActionPrivate: "private",
ActionUpload: "upload",
ActionDownload: "download",
@ -68,14 +69,9 @@ export default class Acl {
}
accessAllowed(role, resource, action) {
if (!this.acl) return false;
console.log("resource: ", resource);
console.log("role: ", role);
console.log("action: ", action);
let res;
if (!this.acl[resource]) {
console.log("resource not found");
if (!this.acl[Constants.resources.ResourceDefault]) return false;
console.log("using default resource");
res = this.acl[Constants.resources.ResourceDefault];
} else {
res = this.acl[resource];
@ -83,9 +79,7 @@ export default class Acl {
let rol;
if (!res[role]) {
console.log("role not found");
if (!res[Constants.roles.RoleDefault]) return false;
console.log("using default role");
rol = res[Constants.roles.RoleDefault];
} else {
rol = res[role];
@ -93,32 +87,14 @@ export default class Acl {
let act;
if (!rol[action]) {
console.log("action not found");
if (!rol[Constants.actions.ActionDefault]) return false;
console.log("using default action");
act = rol[Constants.actions.ActionDefault];
} else {
act = rol[action];
}
console.log("Result: ", act);
return act;
}
accessAllowedAny(role, resource, ...actions) {
// let result = false;
// for (const a in actions) {
// result = result || this.accessAllowed(role, resource, a);
// }
// return result;
// return actions.reduce((accumulator, action) => {
// return accumulator || this.accessAllowed(role, resource, action);
// });
// for (const a in actions) {
// if (this.accessAllowed(role, resource, a)) return true;
// }
// return false;
return actions.some((action) => this.accessAllowed(role, resource, action));
}
getConstants() {
return Constants;
}
}

View file

@ -116,7 +116,7 @@
</v-list-tile-content>
</v-list-tile>
<v-list-tile v-if="$config.feature('review') && this.acl.accessAllowed(session.getUser().getRole(), this.acl.getConstants().resources.ResourceReview, this.acl.getConstants().actions.ActionRead)" to="/review" class="nav-review"
<v-list-tile v-if="$config.feature('review') && hasPermission(aclResources.ResourceReview, aclActions.ActionRead)" to="/review" class="nav-review"
@click.stop="">
<v-list-tile-content>
<v-list-tile-title :class="`menu-item ${rtl ? '--rtl' : ''}`">
@ -126,7 +126,7 @@
</v-list-tile-content>
</v-list-tile>
<v-list-tile v-show="$config.feature('archive') && this.acl.accessAllowed(session.getUser().getRole(), this.acl.getConstants().resources.ResourceArchive, this.acl.getConstants().actions.ActionRead)" to="/archive" class="nav-archive" @click.stop="">
<v-list-tile v-show="$config.feature('archive') && hasPermission(aclResources.ResourceArchive, aclActions.ActionRead)" to="/archive" class="nav-archive" @click.stop="">
<v-list-tile-content>
<v-list-tile-title :class="`menu-item ${rtl ? '--rtl' : ''}`">
<translate>Archive</translate>
@ -301,7 +301,7 @@
</v-list-tile-content>
</v-list-tile>
<v-list-tile v-show="$config.feature('private') && this.acl.accessAllowed(session.getUser().getRole(), this.acl.getConstants().resources.ResourcePrivate, this.acl.getConstants().actions.ActionRead)" to="/private" class="nav-private" @click.stop="">
<v-list-tile v-show="$config.feature('private') && hasPermission(aclResources.ResourcePrivate, aclActions.ActionRead)" to="/private" class="nav-private" @click.stop="">
<v-list-tile-action :title="$gettext('Private')">
<v-icon>lock</v-icon>
</v-list-tile-action>
@ -314,7 +314,7 @@
</v-list-tile-content>
</v-list-tile>
<v-list-tile v-if="isMini && $config.feature('library') && this.acl.accessAllowed(session.getUser().getRole(), this.acl.getConstants().resources.ResourceLibrary, this.acl.getConstants().actions.ActionRead)" to="/library" class="nav-library" @click.stop="">
<v-list-tile v-if="isMini && $config.feature('library') && hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)" to="/library" class="nav-library" @click.stop="">
<v-list-tile-action :title="$gettext('Library')">
<v-icon>camera_roll</v-icon>
</v-list-tile-action>
@ -326,7 +326,7 @@
</v-list-tile-content>
</v-list-tile>
<v-list-group v-if="!isMini && $config.feature('library') && this.acl.accessAllowed(session.getUser().getRole(), this.acl.getConstants().resources.ResourceLibrary, this.acl.getConstants().actions.ActionRead)" prepend-icon="camera_roll" no-action>
<v-list-group v-if="!isMini && $config.feature('library') && hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)" prepend-icon="camera_roll" no-action>
<template #activator>
<v-list-tile to="/library" class="nav-library" @click.stop="">
<v-list-tile-content>
@ -364,7 +364,7 @@
</v-list-tile>
</v-list-group>
<template v-if="!config.disable.settings && this.acl.accessAllowed(session.getUser().getRole(), this.acl.getConstants().resources.ResourceSettings, this.acl.getConstants().actions.ActionRead)">
<template v-if="!config.disable.settings && hasPermission(aclResources.ResourceSettings, aclActions.ActionRead)">
<v-list-tile v-if="isMini" to="/settings" class="nav-settings" @click.stop="">
<v-list-tile-action :title="$gettext('Settings')">
<v-icon>settings</v-icon>

View file

@ -83,7 +83,7 @@
<v-icon color="white" class="action-play">play_arrow</v-icon>
</v-btn>
<v-btn v-if="hidePrivate" :ripple="false"
<v-btn v-if="hidePrivate && hasPermission(aclResources.ResourcePhotos, aclActions.ActionPrivate)" :ripple="false"
icon flat absolute
class="input-private">
<v-icon color="white" class="select-on">lock</v-icon>
@ -104,9 +104,10 @@
icon flat absolute
class="input-favorite"
@touchstart.stop.prevent="input.touchStart($event, index)"
@touchend.stop.prevent="toggleLike($event, index)"
@touchend.stop.prevent="hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike) && toggleLike($event, index)"
@touchmove.stop.prevent
@click.stop.prevent="toggleLike($event, index)">
@click.stop.prevent="hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike) && toggleLike($event, index)"
>
<v-icon color="white" class="select-on">favorite</v-icon>
<v-icon color="white" class="select-off">favorite_border</v-icon>
</v-btn>

View file

@ -57,7 +57,7 @@
<v-icon>edit</v-icon>
</v-btn>
<v-btn
v-if="context !== 'archive' && features.private" fab dark
v-if="context !== 'archive' && features.private && hasPermission(aclResources.ResourcePhotos, aclActions.ActionPrivate)" fab dark
small
:title="$gettext('Change private flag')"
color="private"
@ -89,7 +89,7 @@
<v-icon>bookmark</v-icon>
</v-btn>
<v-btn
v-if="!isAlbum && context !== 'archive' && features.archive" fab dark
v-if="!isAlbum && context !== 'archive' && features.archive && hasPermission(aclResources.ResourcePhotos, aclActions.ActionArchive)" fab dark
small
color="remove"
:title="$gettext('Archive')"
@ -100,7 +100,7 @@
<v-icon>archive</v-icon>
</v-btn>
<v-btn
v-if="!album && context === 'archive'" fab dark
v-if="!album && context === 'archive' && hasPermission(aclResources.ResourcePhotos, aclActions.ActionArchive)" fab dark
small
color="restore"
:title="$gettext('Restore')"
@ -122,7 +122,7 @@
<v-icon>eject</v-icon>
</v-btn>
<v-btn
v-if="!album && context === 'archive' && features.delete" fab dark
v-if="!album && context === 'archive' && features.delete && hasPermission(aclResources.ResourcePhotos, aclActions.ActionDelete)" fab dark
small
:title="$gettext('Delete')"
color="remove"

View file

@ -93,13 +93,13 @@
</span>
</td>
<td class="text-xs-center">
<v-btn v-if="hidePrivate" class="input-private" icon small flat :ripple="false"
<v-btn v-if="hidePrivate" class="input-private" icon small flat :ripple="false" :disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionPrivate)"
:data-uid="props.item.UID" @click.stop.prevent="props.item.togglePrivate()">
<v-icon v-if="props.item.Private" color="secondary-dark" class="select-on">lock</v-icon>
<v-icon v-else color="secondary" class="select-off">lock_open</v-icon>
</v-btn>
<v-btn class="input-like" icon small flat :ripple="false"
:data-uid="props.item.UID" @click.stop.prevent="props.item.toggleLike()">
<v-btn class="input-like" icon small flat :ripple="false" :disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike)"
:data-uid="props.item.UID" @click.stop.prevent="hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike) && props.item.toggleLike()">
<v-icon v-if="props.item.Favorite" color="pink lighten-3" :data-uid="props.item.UID" class="select-on">
favorite
</v-icon>

View file

@ -82,7 +82,7 @@
<v-icon color="white" class="action-play">play_arrow</v-icon>
</v-btn>
<v-btn v-if="hidePrivate" :ripple="false"
<v-btn v-if="hidePrivate && hasPermission(aclResources.ResourcePhotos, aclActions.ActionPrivate)" :ripple="false"
icon flat small absolute
class="input-private">
<v-icon color="white" class="select-on">lock</v-icon>
@ -103,9 +103,9 @@
icon flat small absolute
class="input-favorite"
@touchstart.stop.prevent="input.touchStart($event, index)"
@touchend.stop.prevent="toggleLike($event, index)"
@touchend.stop.prevent="hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike) && toggleLike($event, index)"
@touchmove.stop.prevent
@click.stop.prevent="toggleLike($event, index)">
@click.stop.prevent="hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike) && toggleLike($event, index)">
<v-icon color="white" class="select-on">favorite</v-icon>
<v-icon color="white" class="select-off">favorite_border</v-icon>
</v-btn>
@ -220,7 +220,7 @@ export default {
},
selectRange(index) {
this.$clipboard.addRange(index, this.photos);
}
},
},
};
</script>

View file

@ -28,7 +28,7 @@
<v-icon>view_column</v-icon>
</v-btn>
<v-btn v-if="!$config.values.readonly && $config.feature('upload')" icon class="hidden-sm-and-down action-upload"
<v-btn v-if="!$config.values.readonly && $config.feature('upload') && hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpload)" icon class="hidden-sm-and-down action-upload"
:title="$gettext('Upload')" @click.stop="showUpload()">
<v-icon>cloud_upload</v-icon>
</v-btn>

View file

@ -33,7 +33,7 @@
<v-icon v-else size="16" color="white">radio_button_off</v-icon>
</button>
<button class="pswp__button action-like hidden-shared-only" style="background: none;"
<button v-if="hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike)" class="pswp__button action-like hidden-shared-only" style="background: none;"
:title="$gettext('Like')" @click.exact="onLike">
<v-icon v-if="item.favorite" size="16" color="white">favorite</v-icon>
<v-icon v-else size="16" color="white">favorite_border</v-icon>
@ -255,7 +255,7 @@ export default {
g.close(); // Close Gallery
Event.publish("dialog.edit", {selection, album, index}); // Open Edit Dialog
}
},
}
};
</script>

View file

@ -422,7 +422,6 @@ export default {
},
data() {
return {
disabled: !this.$config.feature("edit"),
config: this.$config.values,
all: {
colors: [{label: this.$gettext("Unknown"), name: ""}],
@ -446,6 +445,9 @@ export default {
lensOptions() {
return this.config.lenses;
},
disabled() {
return !this.$config.feature("edit") || !this.hasPermission(this.aclResources.ResourcePhotos, this.aclActions.ActionUpdate);
}
},
watch: {
model() {

View file

@ -18,6 +18,7 @@
</td>
<td>
<v-select
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpdate)"
v-model="model.Type"
flat solo
browser-autocomplete="off"
@ -47,6 +48,7 @@
</td>
<td>
<v-text-field
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpdate)"
v-model="model.OriginalName"
flat solo dense hide-details color="secondary-dark"
@change="save"
@ -100,6 +102,7 @@
</td>
<td>
<v-switch
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpdate)"
v-model="model.Stack"
hide-details
class="input-stackable"
@ -116,6 +119,7 @@
</td>
<td>
<v-switch
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionLike)"
v-model="model.Favorite"
hide-details
class="input-favorite"
@ -130,6 +134,7 @@
</td>
<td>
<v-switch
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionPrivate)"
v-model="model.Private"
hide-details
class="input-private"
@ -144,6 +149,7 @@
</td>
<td>
<v-switch
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpdate)"
v-model="model.Scan"
hide-details
class="input-scan"
@ -158,6 +164,7 @@
</td>
<td>
<v-switch
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpdate)"
v-model="model.Panorama"
hide-details
class="input-panorama"
@ -205,6 +212,7 @@
</td>
<td>
<v-text-field
:disabled="!hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpdate)"
v-model="model.CellAccuracy"
flat solo dense hide-details color="secondary-dark"
type="number"

View file

@ -34,7 +34,7 @@
<v-icon>refresh</v-icon>
</v-btn>
<v-btn v-if="!$config.values.readonly && $config.feature('upload')" icon class="hidden-sm-and-down action-upload"
<v-btn v-if="!$config.values.readonly && $config.feature('upload') && hasPermission(aclResources.ResourcePhotos, aclActions.ActionUpload)" icon class="hidden-sm-and-down action-upload"
:title="$gettext('Upload')" @click.stop="showUpload()">
<v-icon>cloud_upload</v-icon>
</v-btn>

View file

@ -53,10 +53,17 @@ const c = window.__CONFIG__;
const appName = c.name;
const siteTitle = c.siteTitle ? c.siteTitle : c.name;
const acl = () => new Acl(window.__CONFIG__.acl);
const aclActions = Constants.actions;
const aclResources = Constants.resources;
const hasPermission = (resource, action) => {
if (config.values.public) return true;
// const acl = new Acl(window.__CONFIG__.acl);
const acl = new Acl(config.values.acl);
const userrole = session.getUser().getRole();
return acl.accessAllowed(userrole, resource, action);
};
export default [
{
name: "home",
@ -200,8 +207,7 @@ export default [
meta: { title: $gettext("Review"), auth: true },
props: { staticFilter: { review: true } },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceReview, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceReview, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -215,8 +221,7 @@ export default [
meta: { title: $gettext("Private"), auth: true },
props: { staticFilter: { private: true } },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourcePrivate, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourcePrivate, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -230,8 +235,7 @@ export default [
meta: { title: $gettext("Archive"), auth: true },
props: { staticFilter: { archived: true } },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceArchive, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceArchive, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -269,8 +273,7 @@ export default [
component: Files,
meta: { title: $gettext("File Browser"), auth: true },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceLibrary, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -284,8 +287,7 @@ export default [
meta: { title: $gettext("Hidden Files"), auth: true },
props: { staticFilter: { hidden: true } },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceLibrary, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -298,8 +300,7 @@ export default [
component: Errors,
meta: { title: appName, auth: true },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceLibrary, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -345,8 +346,7 @@ export default [
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
props: { tab: "library-index" },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceLibrary, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -360,8 +360,7 @@ export default [
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
props: { tab: "library-import" },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceLibrary, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -375,8 +374,7 @@ export default [
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
props: { tab: "library-logs" },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceLibrary, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceLibrary, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -395,8 +393,7 @@ export default [
},
props: { tab: "settings-general" },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceSettings, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceSettings, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -415,8 +412,7 @@ export default [
},
props: { tab: "settings-library" },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceSettings, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceSettings, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -435,8 +431,7 @@ export default [
},
props: { tab: "settings-sync" },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceSettings, aclActions.ActionRead)) {
if (hasPermission(aclResources.ResourceSettings, aclActions.ActionRead)) {
next();
} else {
next({ name: "home" });
@ -454,8 +449,7 @@ export default [
},
props: { tab: "settings-account" },
beforeEnter: (to, from, next) => {
const userrole = session.getUser().getRole();
if (acl().accessAllowed(userrole, aclResources.ResourceUsers, aclActions.ActionUpdateSelf)) {
if (hasPermission(aclResources.ResourceUsers, aclActions.ActionUpdateSelf)) {
next();
} else {
next({ name: "home" });

View file

@ -11,6 +11,7 @@ const (
ActionUpdate Action = "update"
ActionUpdateSelf Action = "update-self"
ActionDelete Action = "delete"
ActionArchive Action = "archive" // includes restore
ActionPrivate Action = "private"
ActionUpload Action = "upload"
ActionDownload Action = "download"

View file

@ -21,7 +21,7 @@ import (
// POST /api/v1/batch/photos/archive
func BatchPhotosArchive(router *gin.RouterGroup) {
router.POST("/batch/photos/archive", func(c *gin.Context) {
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionDelete)
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionArchive)
if s.Invalid() {
AbortUnauthorized(c)
@ -84,7 +84,7 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
// POST /api/v1/batch/photos/restore
func BatchPhotosRestore(router *gin.RouterGroup) {
router.POST("/batch/photos/restore", func(c *gin.Context) {
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionDelete)
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionArchive)
if s.Invalid() {
AbortUnauthorized(c)