Explorar o código

fix: editing buttons showing up even if no action is allowed (#2043)

* feat: Edit / Page Create Buttons showing up even if no action is allowed #1780
Regev Brody %!s(int64=5) %!d(string=hai) anos
pai
achega
0a16929a57

+ 1 - 1
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')
   },

+ 22 - 9
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 () {

+ 8 - 0
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')

+ 8 - 0
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) {

+ 9 - 1
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() {

+ 20 - 4
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
 }

+ 22 - 11
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')

+ 42 - 14
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') {

+ 1 - 0
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')
       )

+ 1 - 0
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')
       )

+ 1 - 1
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')

+ 1 - 0
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