diff --git a/client/components/comments.vue b/client/components/comments.vue index f6188856..3275d1e6 100644 --- a/client/components/comments.vue +++ b/client/components/comments.vue @@ -156,7 +156,7 @@ export default { }, computed: { pageId: get('page/id'), - permissions: get('page/commentsPermissions'), + permissions: get('page/effectivePermissions@comments'), isAuthenticated: get('user/authenticated'), userDisplayName: get('user/name') }, diff --git a/client/components/common/nav-header.vue b/client/components/common/nav-header.vue index 87c846ce..5ad14a81 100644 --- a/client/components/common/nav-header.vue +++ b/client/components/common/nav-header.vue @@ -111,7 +111,7 @@ //- PAGE ACTIONS - template(v-if='isAuthenticated && path && mode !== `edit`') + template(v-if='hasAnyPagePermissions && path && mode !== `edit`') v-menu(offset-y, bottom, transition='slide-y-transition', left) template(v-slot:activator='{ on: menu }') v-tooltip(bottom) @@ -122,33 +122,33 @@ v-list(nav, :light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark', :class='$vuetify.theme.dark ? `grey darken-4` : ``') .overline.pa-4.grey--text {{$t('common:header.currentPage')}} v-list-item.pl-4(@click='pageView', v-if='mode !== `view`') - v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-file-document-box-outline + v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-file-document-outline v-list-item-title.body-2 {{$t('common:header.view')}} - v-list-item.pl-4(@click='pageEdit', v-if='mode !== `edit` && isAuthenticated') + v-list-item.pl-4(@click='pageEdit', v-if='mode !== `edit` && hasWritePagesPermission') v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-file-document-edit-outline v-list-item-title.body-2 {{$t('common:header.edit')}} - v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history`') + v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history` && hasReadHistoryPermission') v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-history v-list-item-content v-list-item-title.body-2 {{$t('common:header.history')}} - v-list-item.pl-4(@click='pageSource', v-if='mode !== `source`') + v-list-item.pl-4(@click='pageSource', v-if='mode !== `source` && hasReadSourcePermission') v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-code-tags v-list-item-title.body-2 {{$t('common:header.viewSource')}} - v-list-item.pl-4(@click='pageDuplicate', v-if='isAuthenticated') + v-list-item.pl-4(@click='pageDuplicate', v-if='hasWritePagesPermission') v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-content-duplicate v-list-item-title.body-2 {{$t('common:header.duplicate')}} - v-list-item.pl-4(@click='pageMove', v-if='isAuthenticated') + v-list-item.pl-4(@click='pageMove', v-if='hasManagePagesPermission') v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-content-save-move-outline v-list-item-content v-list-item-title.body-2 {{$t('common:header.move')}} - v-list-item.pl-4(@click='pageDelete', v-if='isAuthenticated') + v-list-item.pl-4(@click='pageDelete', v-if='hasDeletePagesPermission') v-list-item-avatar(size='24', tile): v-icon(color='red darken-2') mdi-trash-can-outline v-list-item-title.body-2 {{$t('common:header.delete')}} v-divider(vertical) //- NEW PAGE - template(v-if='isAuthenticated && path && mode !== `edit`') + template(v-if='hasNewPagePermission && path && mode !== `edit`') v-tooltip(bottom) template(v-slot:activator='{ on }') v-btn(icon, tile, height='64', v-on='on', @click='pageNew') @@ -288,6 +288,19 @@ export default { }, isAdmin () { return _.intersection(this.permissions, ['manage:system', 'write:users', 'manage:users', 'write:groups', 'manage:groups', 'manage:navigation', 'manage:theme', 'manage:api']).length > 0 + }, + hasNewPagePermission () { + return this.hasAdminPermission || _.intersection(this.permissions, ['write:pages']).length > 0 + }, + hasAdminPermission: get('page/effectivePermissions@system.manage'), + hasWritePagesPermission: get('page/effectivePermissions@pages.write'), + hasManagePagesPermission: get('page/effectivePermissions@pages.manage'), + hasDeletePagesPermission: get('page/effectivePermissions@pages.delete'), + hasReadSourcePermission: get('page/effectivePermissions@source.read'), + hasReadHistoryPermission: get('page/effectivePermissions@history.read'), + hasAnyPagePermissions () { + return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission || + this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission } }, created () { diff --git a/client/components/editor.vue b/client/components/editor.vue index b847171d..e59badf0 100644 --- a/client/components/editor.vue +++ b/client/components/editor.vue @@ -133,6 +133,10 @@ export default { checkoutDate: { type: String, default: new Date().toISOString() + }, + effectivePermissions: { + type: String, + default: '' } }, data() { @@ -197,6 +201,10 @@ export default { this.setCurrentSavedState() this.checkoutDateActive = this.checkoutDate + + if (this.effectivePermissions) { + this.$store.set('page/effectivePermissions',JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) + } }, mounted() { this.$store.set('editor/mode', this.initMode || 'create') diff --git a/client/components/history.vue b/client/components/history.vue index 095def4a..d4475766 100644 --- a/client/components/history.vue +++ b/client/components/history.vue @@ -185,6 +185,10 @@ export default { liveContent: { type: String, default: '' + }, + effectivePermissions: { + type: String, + default: '' } }, data () { @@ -316,6 +320,10 @@ export default { }) this.target = this.cache[0] + + if (this.effectivePermissions) { + this.$store.set('page/effectivePermissions',JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) + } }, methods: { async loadVersion (versionId) { diff --git a/client/components/source.vue b/client/components/source.vue index fda24341..79bc08de 100644 --- a/client/components/source.vue +++ b/client/components/source.vue @@ -50,6 +50,10 @@ export default { versionDate: { type: String, default: '' + }, + effectivePermissions: { + type: String, + default: '' } }, data() { @@ -60,7 +64,11 @@ export default { this.$store.commit('page/SET_LOCALE', this.locale) this.$store.commit('page/SET_PATH', this.path) - this.$store.commit('page/SET_MODE', 'history') + this.$store.commit('page/SET_MODE', 'source') + + if (this.effectivePermissions) { + this.$store.set('page/effectivePermissions',JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) + } }, methods: { goLive() { diff --git a/client/store/page.js b/client/store/page.js index d85fdbe5..fe39e301 100644 --- a/client/store/page.js +++ b/client/store/page.js @@ -15,10 +15,26 @@ const state = { title: '', updatedAt: '', mode: '', - commentsPermissions: { - read: false, - write: false, - manage: false + effectivePermissions: { + comments: { + read: false, + write: false, + manage: false + }, + history: { + read: false + }, + source: { + read: false + }, + pages: { + write: false, + manage: false, + delete: false + }, + system: { + manage: false + } }, commentsCount: 0 } diff --git a/client/themes/default/components/page.vue b/client/themes/default/components/page.vue index e6957202..351f692d 100644 --- a/client/themes/default/components/page.vue +++ b/client/themes/default/components/page.vue @@ -132,7 +132,7 @@ v-spacer v-tooltip(right, v-if='isAuthenticated') template(v-slot:activator='{ on }') - v-btn.btn-animate-edit(icon, :href='"/h/" + locale + "/" + path', v-on='on', x-small) + v-btn.btn-animate-edit(icon, :href='"/h/" + locale + "/" + path', v-on='on', x-small, v-if="hasReadHistoryPermission") v-icon(color='indigo', dense) mdi-history span {{$t('common:header.history')}} .body-2.grey--text(:class='$vuetify.theme.dark ? `` : `text--darken-3`') {{ authorName }} @@ -176,7 +176,7 @@ v-spacer v-flex.page-col-content(xs12, lg9, xl10) - v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='isAuthenticated') + v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasAnyPagePermissions') template(v-slot:activator='{ on: onEditActivator }') v-speed-dial( v-model='pageEditFab' @@ -196,9 +196,10 @@ v-model='pageEditFab' @click='pageEdit' v-on='onEditActivator' + :disabled='!hasWritePagesPermission' ) v-icon mdi-pencil - v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl') + v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasReadHistoryPermission') template(v-slot:activator='{ on }') v-btn( fab @@ -210,7 +211,7 @@ ) v-icon(size='20') mdi-history span {{$t('common:header.history')}} - v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl') + v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasReadSourcePermission') template(v-slot:activator='{ on }') v-btn( fab @@ -222,7 +223,7 @@ ) v-icon(size='20') mdi-code-tags span {{$t('common:header.viewSource')}} - v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl') + v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasWritePagesPermission') template(v-slot:activator='{ on }') v-btn( fab @@ -234,7 +235,7 @@ ) v-icon(size='20') mdi-content-duplicate span {{$t('common:header.duplicate')}} - v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl') + v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasManagePagesPermission') template(v-slot:activator='{ on }') v-btn( fab @@ -246,7 +247,7 @@ ) v-icon(size='20') mdi-content-save-move-outline span {{$t('common:header.move')}} - v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl') + v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasDeletePagesPermission') template(v-slot:activator='{ on }') v-btn( fab @@ -402,7 +403,7 @@ export default { type: Boolean, default: false }, - commentsPermissions: { + effectivePermissions: { type: String, default: '' }, @@ -446,7 +447,7 @@ export default { computed: { isAuthenticated: get('user/authenticated'), commentsCount: get('page/commentsCount'), - commentsPerms: get('page/commentsPermissions'), + commentsPerms: get('page/effectivePermissions@comments'), rating: { get () { return 3.5 @@ -477,6 +478,16 @@ export default { }, tocDecoded () { return JSON.parse(Buffer.from(this.toc, 'base64').toString()) + }, + hasAdminPermission: get('page/effectivePermissions@system.manage'), + hasWritePagesPermission: get('page/effectivePermissions@pages.write'), + hasManagePagesPermission: get('page/effectivePermissions@pages.manage'), + hasDeletePagesPermission: get('page/effectivePermissions@pages.delete'), + hasReadSourcePermission: get('page/effectivePermissions@source.read'), + hasReadHistoryPermission: get('page/effectivePermissions@history.read'), + hasAnyPagePermissions () { + return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission || + this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission } }, created() { @@ -491,8 +502,8 @@ export default { this.$store.set('page/tags', this.tags) this.$store.set('page/title', this.title) this.$store.set('page/updatedAt', this.updatedAt) - if (this.commentsPermissions) { - this.$store.set('page/commentsPermissions', JSON.parse(atob(this.commentsPermissions))) + if (this.effectivePermissions) { + this.$store.set('page/effectivePermissions',JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) } this.$store.set('page/mode', 'view') diff --git a/server/controllers/common.js b/server/controllers/common.js index e79ecaf1..4ecc6a4d 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -7,6 +7,30 @@ const _ = require('lodash') const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/ +const getPageEffectivePermissions = (req, page) => { + return { + comments: { + read: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['read:comments'], page) : false, + write: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['write:comments'], page) : false, + manage: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['manage:comments'], page) : false + }, + history: { + read: WIKI.auth.checkAccess(req.user, ['read:history'], page) + }, + source: { + read: WIKI.auth.checkAccess(req.user, ['read:source'], page) + }, + pages: { + write: WIKI.auth.checkAccess(req.user, ['write:pages'], page), + manage: WIKI.auth.checkAccess(req.user, ['manage:pages'], page), + delete: WIKI.auth.checkAccess(req.user, ['delete:pages'], page) + }, + system: { + manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page) + } + } +} + /** * Robots.txt */ @@ -196,7 +220,11 @@ router.get(['/e', '/e/*'], async (req, res, next) => { } } } - res.render('editor', { page, injectCode }) + + // -> Effective Permissions + const effectivePermissions = getPageEffectivePermissions(req, pageArgs) + + res.render('editor', { page, injectCode, effectivePermissions }) }) /** @@ -234,7 +262,11 @@ router.get(['/h', '/h/*'], async (req, res, next) => { if (page) { _.set(res.locals, 'pageMeta.title', page.title) _.set(res.locals, 'pageMeta.description', page.description) - res.render('history', { page }) + + // -> Effective Permissions + const effectivePermissions = getPageEffectivePermissions(req, pageArgs) + + res.render('history', { page, effectivePermissions }) } else { res.redirect(`/${pageArgs.path}`) } @@ -335,7 +367,11 @@ router.get(['/s', '/s/*'], async (req, res, next) => { } else { _.set(res.locals, 'pageMeta.title', page.title) _.set(res.locals, 'pageMeta.description', page.description) - res.render('source', { page }) + + // -> Effective Permissions + const effectivePermissions = getPageEffectivePermissions(req, pageArgs) + + res.render('source', { page, effectivePermissions }) } } else { res.redirect(`/${pageArgs.path}`) @@ -447,16 +483,8 @@ router.get('/*', async (req, res, next) => { }) } - // -> Comments Permissions - const commentsPermissions = WIKI.config.features.featurePageComments ? { - read: WIKI.auth.checkAccess(req.user, ['read:comments'], pageArgs), - write: WIKI.auth.checkAccess(req.user, ['write:comments'], pageArgs), - manage: WIKI.auth.checkAccess(req.user, ['manage:comments'], pageArgs) - } : { - read: false, - write: false, - manage: false - } + // -> Effective Permissions + const effectivePermissions = getPageEffectivePermissions(req, pageArgs) // -> Render view res.render('page', { @@ -464,7 +492,7 @@ router.get('/*', async (req, res, next) => { sidebar, injectCode, comments: WIKI.data.commentProvider, - commentsPermissions + effectivePermissions }) } } else if (pageArgs.path === 'home') { diff --git a/server/views/editor.pug b/server/views/editor.pug index 52789404..5b35d3a5 100644 --- a/server/views/editor.pug +++ b/server/views/editor.pug @@ -18,4 +18,5 @@ block body init-editor=page.editorKey init-content=page.content checkout-date=page.updatedAt + effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') ) diff --git a/server/views/history.pug b/server/views/history.pug index 9da62934..5adb9c57 100644 --- a/server/views/history.pug +++ b/server/views/history.pug @@ -17,4 +17,5 @@ block body :author-id=page.authorId :is-published=page.isPublished.toString() live-content=page.content + effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') ) diff --git a/server/views/page.pug b/server/views/page.pug index e5883f4c..2096a7c8 100644 --- a/server/views/page.pug +++ b/server/views/page.pug @@ -26,7 +26,7 @@ block body sidebar=Buffer.from(JSON.stringify(sidebar)).toString('base64') nav-mode=config.nav.mode comments-enabled=config.features.featurePageComments - comments-permissions=Buffer.from(JSON.stringify(commentsPermissions)).toString('base64') + effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') comments-external=comments.codeTemplate ) template(slot='contents') diff --git a/server/views/source.pug b/server/views/source.pug index 657bfad2..c3cb1e95 100644 --- a/server/views/source.pug +++ b/server/views/source.pug @@ -10,4 +10,5 @@ block body path=page.path :version-id=page.versionId version-date=page.versionDate + effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') )= page.content