Browse Source

feat: group permissions

Nicolas Giard 6 năm trước cách đây
mục cha
commit
c08b5ac837

+ 148 - 34
client/components/admin/admin-groups-edit.vue

@@ -10,7 +10,7 @@
           v-spacer
           v-spacer
           v-btn(color='indigo', large, outline, to='/groups')
           v-btn(color='indigo', large, outline, to='/groups')
             v-icon arrow_back
             v-icon arrow_back
-          v-dialog(v-model='deleteGroupDialog', max-width='500')
+          v-dialog(v-model='deleteGroupDialog', max-width='500', v-if='!group.isSystem')
             v-btn(color='red', large, outline, slot='activator')
             v-btn(color='red', large, outline, slot='activator')
               v-icon(color='red') delete
               v-icon(color='red') delete
             v-card
             v-card
@@ -45,28 +45,29 @@
                   .caption.mt-3.grey--text ID: {{group.id}}
                   .caption.mt-3.grey--text ID: {{group.id}}
 
 
             v-tab-item(key='permissions', :transition='false', :reverse-transition='false')
             v-tab-item(key='permissions', :transition='false', :reverse-transition='false')
-              v-card
+              v-container.pa-3(fluid, grid-list-md)
+                v-layout(row, wrap)
+                  v-flex(xs12, md6, lg4, v-for='pmGroup in permissions')
+                    v-card.md2.grey.lighten-5(flat)
+                      v-subheader {{pmGroup.category}}
+                      v-card-text.pt-0
+                        template(v-for='(pm, idx) in pmGroup.items')
+                          v-checkbox.pt-0(
+                            :key='pm.permission'
+                            :label='pm.permission'
+                            :hint='pm.hint'
+                            persistent-hint
+                            color='primary'
+                            v-model='group.permissions'
+                            :value='pm.permission'
+                            :append-icon='pm.warning ? "warning" : null',
+                            :disabled='(group.isSystem && pm.restrictedForSystem) || group.id === 1 || pm.disabled'
+                          )
+                          v-divider.mt-3(v-if='idx < pmGroup.items.length - 1')
 
 
             v-tab-item(key='rules', :transition='false', :reverse-transition='false')
             v-tab-item(key='rules', :transition='false', :reverse-transition='false')
               v-card
               v-card
                 v-card-title.pb-0
                 v-card-title.pb-0
-                  v-subheader
-                    v-icon.mr-2 border_color
-                    .subheading Read and Write
-                  v-spacer
-                  v-btn(flat, outline)
-                    v-icon(left) arrow_drop_down
-                    | Load Preset
-                  v-btn(flat, outline)
-                    v-icon(left) vertical_align_bottom
-                    | Import Rules
-                .pa-3.pl-4
-                  criterias
-                v-divider.my-0
-                v-card-title.pb-0
-                  v-subheader
-                    v-icon.mr-2 pageview
-                    .subheading Read Only
                   v-spacer
                   v-spacer
                   v-btn(flat, outline)
                   v-btn(flat, outline)
                     v-icon(left) arrow_drop_down
                     v-icon(left) arrow_drop_down
@@ -76,25 +77,12 @@
                     | Import Rules
                     | Import Rules
                 .pa-3.pl-4
                 .pa-3.pl-4
                   criterias
                   criterias
-                v-divider.my-0
-                v-card-title.pb-0
-                  v-subheader Legend
-                .px-4.pb-4
-                  .body-1.px-1.py-2 Any number of rules can be used at the same time. However, some rules requires more processing time than others. Rule types are color-coded as followed:
-                  .caption
-                    v-icon(color='blue') stop
-                    span Fast rules. None or insignificant latency introduced to all page loads.
-                  .caption
-                    v-icon(color='orange') stop
-                    span Medium rules. Some latency added to all page loads.
-                  .caption
-                    v-icon(color='red') stop
-                    span Slow rules. May adds noticeable latency to all page loads. Avoid using in multiple rules.
 
 
             v-tab-item(key='users', :transition='false', :reverse-transition='false')
             v-tab-item(key='users', :transition='false', :reverse-transition='false')
               v-card
               v-card
                 v-card-title.pb-0
                 v-card-title.pb-0
-                  v-btn(color='primary', @click='searchUserDialog = true')
+                  v-spacer
+                  v-btn(color='primary', outline, flat, @click='searchUserDialog = true')
                     v-icon(left) assignment_ind
                     v-icon(left) assignment_ind
                     | Assign User
                     | Assign User
                 v-data-table(
                 v-data-table(
@@ -148,12 +136,138 @@ export default {
       group: {
       group: {
         id: 0,
         id: 0,
         name: '',
         name: '',
+        isSystem: false,
+        permissions: [],
+        pageRules: [],
         users: []
         users: []
       },
       },
       name: '',
       name: '',
       deleteGroupDialog: false,
       deleteGroupDialog: false,
       searchUserDialog: false,
       searchUserDialog: false,
       pagination: {},
       pagination: {},
+      permissions: [
+        {
+          category: 'Content',
+          items: [
+            {
+              permission: 'read:pages',
+              hint: 'Can view pages, as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
+            {
+              permission: 'write:pages',
+              hint: 'Can view and create new pages, as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
+            {
+              permission: 'manage:pages',
+              hint: 'Can view, create, edit and move existing pages as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
+            {
+              permission: 'delete:pages',
+              hint: 'Can delete existing pages, as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
+            {
+              permission: 'write:assets',
+              hint: 'Can upload assets (such as images and files), as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
+            {
+              permission: 'read:comments',
+              hint: 'Can view comments, as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
+            {
+              permission: 'write:comments',
+              hint: 'Can post new comments, as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            }
+          ]
+        },
+        {
+          category: 'Users',
+          items: [
+            {
+              permission: 'write:users',
+              hint: 'Can create or authorize new users, but not modify existing ones',
+              warning: false,
+              restrictedForSystem: true,
+              disabled: false
+            },
+            {
+              permission: 'manage:users',
+              hint: 'Can manage all users (but not users with administrative permissions)',
+              warning: false,
+              restrictedForSystem: true,
+              disabled: false
+            },
+            {
+              permission: 'write:groups',
+              hint: 'Can manage groups and assign CONTENT permissions / page rules',
+              warning: false,
+              restrictedForSystem: true,
+              disabled: false
+            },
+            {
+              permission: 'manage:groups',
+              hint: 'Can manage groups and assign ANY permissions (but not manage:system) / page rules',
+              warning: true,
+              restrictedForSystem: true,
+              disabled: false
+            }
+          ]
+        },
+        {
+          category: 'Administration',
+          items: [
+            {
+              permission: 'manage:navigation',
+              hint: 'Can manage the site navigation',
+              warning: false,
+              restrictedForSystem: true,
+              disabled: false
+            },
+            {
+              permission: 'manage:theme',
+              hint: 'Can manage and modify themes',
+              warning: false,
+              restrictedForSystem: true,
+              disabled: false
+            },
+            {
+              permission: 'manage:api',
+              hint: 'Can generate and revoke API keys',
+              warning: true,
+              restrictedForSystem: true,
+              disabled: false
+            },
+            {
+              permission: 'manage:system',
+              hint: 'Can manage and access everything. Root administrator.',
+              warning: true,
+              restrictedForSystem: true,
+              disabled: true
+
+            }
+          ]
+        }
+      ],
       users: [],
       users: [],
       headers: [
       headers: [
         { text: 'ID', value: 'id', width: 50, align: 'right' },
         { text: 'ID', value: 'id', width: 50, align: 'right' },

+ 17 - 2
client/components/admin/admin-groups.vue

@@ -17,7 +17,17 @@
             v-card
             v-card
               .dialog-header.is-short New Group
               .dialog-header.is-short New Group
               v-card-text
               v-card-text
-                v-text-field(v-model='newGroupName', label='Group Name', autofocus, counter='255', @keyup.enter='createGroup')
+                v-text-field.md2(
+                  solo,
+                  flat,
+                  background-color='grey lighten-4'
+                  prepend-icon='people'
+                  v-model='newGroupName'
+                  label='Group Name'
+                  counter='255'
+                  @keyup.enter='createGroup'
+                  ref='groupNameInput'
+                  )
               v-card-chin
               v-card-chin
                 v-spacer
                 v-spacer
                 v-btn(flat, @click='newGroupDialog = false') Cancel
                 v-btn(flat, @click='newGroupDialog = false') Cancel
@@ -38,6 +48,10 @@
                 td {{ props.item.userCount }}
                 td {{ props.item.userCount }}
                 td {{ props.item.createdAt | moment('calendar') }}
                 td {{ props.item.createdAt | moment('calendar') }}
                 td {{ props.item.updatedAt | moment('calendar') }}
                 td {{ props.item.updatedAt | moment('calendar') }}
+                td
+                  v-tooltip(left, v-if='props.item.isSystem')
+                    v-icon(slot='activator') lock_outline
+                    span System Group
             template(slot='no-data')
             template(slot='no-data')
               v-alert.ma-3(icon='warning', :value='true', outline) No groups to display.
               v-alert.ma-3(icon='warning', :value='true', outline) No groups to display.
           .text-xs-center.py-2(v-if='groups.length > 15')
           .text-xs-center.py-2(v-if='groups.length > 15')
@@ -64,7 +78,8 @@ export default {
         { text: 'Name', value: 'name' },
         { text: 'Name', value: 'name' },
         { text: 'Users', value: 'userCount', width: 200 },
         { text: 'Users', value: 'userCount', width: 200 },
         { text: 'Created', value: 'createdAt', width: 250 },
         { text: 'Created', value: 'createdAt', width: 250 },
-        { text: 'Last Updated', value: 'updatedAt', width: 250 }
+        { text: 'Last Updated', value: 'updatedAt', width: 250 },
+        { text: '', value: 'isSystem', width: 20, sortable: false }
       ],
       ],
       search: ''
       search: ''
     }
     }

+ 1 - 0
client/graph/admin/groups/groups-query-list.gql

@@ -3,6 +3,7 @@ query {
     list {
     list {
       id
       id
       name
       name
+      isSystem
       userCount
       userCount
       createdAt
       createdAt
       updatedAt
       updatedAt

+ 3 - 1
client/graph/admin/groups/groups-query-single.gql

@@ -3,7 +3,9 @@ query ($id: Int!) {
     single(id: $id) {
     single(id: $id) {
       id
       id
       name
       name
-      rights {
+      isSystem
+      permissions
+      pageRules {
         id
         id
         path
         path
         role
         role

+ 1 - 1
client/scss/components/v-data-table.scss

@@ -1,4 +1,4 @@
-.datatable {
+.v-datatable {
   .is-clickable {
   .is-clickable {
     cursor: pointer;
     cursor: pointer;
   }
   }

+ 6 - 0
server/app/data.yml

@@ -76,6 +76,12 @@ jobs:
     onInit: false
     onInit: false
     cron: false
     cron: false
     concurrency: 1
     concurrency: 1
+groups:
+  defaultPermissions:
+    - 'manage:pages'
+    - 'write:assets'
+    - 'read:comments'
+    - 'write:comments'
 telemetry:
 telemetry:
   BUGSNAG_ID: 'bb4b324d0675bcbba10025617fd2cec8'
   BUGSNAG_ID: 'bb4b324d0675bcbba10025617fd2cec8'
   BUGSNAG_REMOTE: 'https://notify.bugsnag.com'
   BUGSNAG_REMOTE: 'https://notify.bugsnag.com'

+ 3 - 1
server/graph/resolvers/group.js

@@ -39,7 +39,9 @@ module.exports = {
     },
     },
     async create(obj, args) {
     async create(obj, args) {
       const group = await WIKI.models.groups.query().insertAndFetch({
       const group = await WIKI.models.groups.query().insertAndFetch({
-        name: args.name
+        name: args.name,
+        permissions: JSON.stringify(WIKI.data.groups.defaultPermissions),
+        isSystem: false
       })
       })
       return {
       return {
         responseResult: graphHelper.generateSuccess('Group created successfully.'),
         responseResult: graphHelper.generateSuccess('Group created successfully.'),

+ 1 - 1
server/graph/schemas/authentication.graphql

@@ -38,7 +38,7 @@ type AuthenticationMutation {
 
 
   updateStrategies(
   updateStrategies(
     strategies: [AuthenticationStrategyInput]
     strategies: [AuthenticationStrategyInput]
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 12 - 9
server/graph/schemas/group.graphql

@@ -18,11 +18,11 @@ type GroupQuery {
   list(
   list(
     filter: String
     filter: String
     orderBy: String
     orderBy: String
-  ): [GroupMinimal]
+  ): [GroupMinimal] @auth(requires: ["write:groups", "manage:groups", "manage:system"])
 
 
   single(
   single(
     id: Int!
     id: Int!
-  ): Group
+  ): Group @auth(requires: ["write:groups", "manage:groups", "manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -32,26 +32,26 @@ type GroupQuery {
 type GroupMutation {
 type GroupMutation {
   create(
   create(
     name: String!
     name: String!
-  ): GroupResponse
+  ): GroupResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
 
 
   update(
   update(
     id: Int!
     id: Int!
     name: String!
     name: String!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
 
 
   delete(
   delete(
     id: Int!
     id: Int!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
 
 
   assignUser(
   assignUser(
     groupId: Int!
     groupId: Int!
     userId: Int!
     userId: Int!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
 
 
   unassignUser(
   unassignUser(
     groupId: Int!
     groupId: Int!
     userId: Int!
     userId: Int!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -66,6 +66,7 @@ type GroupResponse {
 type GroupMinimal {
 type GroupMinimal {
   id: Int!
   id: Int!
   name: String!
   name: String!
+  isSystem: Boolean!
   userCount: Int
   userCount: Int
   createdAt: Date!
   createdAt: Date!
   updatedAt: Date!
   updatedAt: Date!
@@ -74,8 +75,10 @@ type GroupMinimal {
 type Group {
 type Group {
   id: Int!
   id: Int!
   name: String!
   name: String!
-  rights: [Right]
-  users: [User]
+  isSystem: Boolean!
+  permissions: [String]!
+  pageRules: [Right]
+  users: [UserMinimal]
   createdAt: Date!
   createdAt: Date!
   updatedAt: Date!
   updatedAt: Date!
 }
 }

+ 2 - 2
server/graph/schemas/localization.graphql

@@ -26,14 +26,14 @@ type LocalizationQuery {
 type LocalizationMutation {
 type LocalizationMutation {
   downloadLocale(
   downloadLocale(
     locale: String!
     locale: String!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:system"])
 
 
   updateLocale(
   updateLocale(
     locale: String!
     locale: String!
     autoUpdate: Boolean!
     autoUpdate: Boolean!
     namespacing: Boolean!
     namespacing: Boolean!
     namespaces: [String]!
     namespaces: [String]!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 2 - 2
server/graph/schemas/logging.graphql

@@ -22,7 +22,7 @@ type LoggingQuery {
   loggers(
   loggers(
     filter: String
     filter: String
     orderBy: String
     orderBy: String
-  ): [Logger]
+  ): [Logger] @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -32,7 +32,7 @@ type LoggingQuery {
 type LoggingMutation {
 type LoggingMutation {
   updateLoggers(
   updateLoggers(
     loggers: [LoggerInput]
     loggers: [LoggerInput]
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 3 - 3
server/graph/schemas/page.graphql

@@ -45,7 +45,7 @@ type PageMutation {
     publishStartDate: Date
     publishStartDate: Date
     tags: [String]!
     tags: [String]!
     title: String!
     title: String!
-  ): PageResponse
+  ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
 
 
   update(
   update(
     id: Int!
     id: Int!
@@ -60,11 +60,11 @@ type PageMutation {
     publishStartDate: Date
     publishStartDate: Date
     tags: [String]
     tags: [String]
     title: String
     title: String
-  ): PageResponse
+  ): PageResponse @auth(requires: ["manage:pages", "manage:system"])
 
 
   delete(
   delete(
     id: Int!
     id: Int!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["delete:pages", "manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 2 - 2
server/graph/schemas/rendering.graphql

@@ -18,7 +18,7 @@ type RenderingQuery {
   renderers(
   renderers(
     filter: String
     filter: String
     orderBy: String
     orderBy: String
-  ): [Renderer]
+  ): [Renderer] @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -28,7 +28,7 @@ type RenderingQuery {
 type RenderingMutation {
 type RenderingMutation {
   updateRenderers(
   updateRenderers(
     renderers: [RendererInput]
     renderers: [RendererInput]
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 2 - 2
server/graph/schemas/search.graphql

@@ -18,7 +18,7 @@ type SearchQuery {
   searchEngines(
   searchEngines(
     filter: String
     filter: String
     orderBy: String
     orderBy: String
-  ): [SearchEngine]
+  ): [SearchEngine] @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -28,7 +28,7 @@ type SearchQuery {
 type SearchMutation {
 type SearchMutation {
   updateSearchEngines(
   updateSearchEngines(
     searchEngines: [SearchEngineInput]
     searchEngines: [SearchEngineInput]
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 2 - 2
server/graph/schemas/storage.graphql

@@ -18,7 +18,7 @@ type StorageQuery {
   targets(
   targets(
     filter: String
     filter: String
     orderBy: String
     orderBy: String
-  ): [StorageTarget]
+  ): [StorageTarget] @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -28,7 +28,7 @@ type StorageQuery {
 type StorageMutation {
 type StorageMutation {
   updateTargets(
   updateTargets(
     targets: [StorageTargetInput]
     targets: [StorageTargetInput]
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 1 - 1
server/graph/schemas/system.graphql

@@ -15,7 +15,7 @@ extend type Mutation {
 # -----------------------------------------------
 # -----------------------------------------------
 
 
 type SystemQuery {
 type SystemQuery {
-  info: SystemInfo
+  info: SystemInfo @auth(requires: ["manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 3 - 3
server/graph/schemas/theming.graphql

@@ -15,8 +15,8 @@ extend type Mutation {
 # -----------------------------------------------
 # -----------------------------------------------
 
 
 type ThemingQuery {
 type ThemingQuery {
-  themes: [ThemingTheme]
-  config: ThemingConfig
+  themes: [ThemingTheme] @auth(requires: ["manage:theme", "manage:system"])
+  config: ThemingConfig @auth(requires: ["manage:theme", "manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -27,7 +27,7 @@ type ThemingMutation {
   setConfig(
   setConfig(
     theme: String!
     theme: String!
     darkMode: Boolean!
     darkMode: Boolean!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:theme", "manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------

+ 6 - 6
server/graph/schemas/user.graphql

@@ -18,15 +18,15 @@ type UserQuery {
   list(
   list(
     filter: String
     filter: String
     orderBy: String
     orderBy: String
-  ): [UserMinimal]
+  ): [UserMinimal] @auth(requires: ["write:users", "manage:users", "manage:system"])
 
 
   search(
   search(
     query: String!
     query: String!
-  ): [UserMinimal]
+  ): [UserMinimal] @auth(requires: ["write:groups", "manage:groups", "write:users", "manage:users", "manage:system"])
 
 
   single(
   single(
     id: Int!
     id: Int!
-  ): User
+  ): User @auth(requires: ["manage:users", "manage:system"])
 }
 }
 
 
 # -----------------------------------------------
 # -----------------------------------------------
@@ -41,7 +41,7 @@ type UserMutation {
     provider: String!
     provider: String!
     providerId: String
     providerId: String
     role: UserRole!
     role: UserRole!
-  ): UserResponse
+  ): UserResponse @auth(requires: ["write:users", "manage:users", "manage:system"])
 
 
   update(
   update(
     id: Int!
     id: Int!
@@ -50,11 +50,11 @@ type UserMutation {
     provider: String
     provider: String
     providerId: String
     providerId: String
     role: UserRole
     role: UserRole
-  ): UserResponse
+  ): UserResponse @auth(requires: ["manage:users", "manage:system"])
 
 
   delete(
   delete(
     id: Int!
     id: Int!
-  ): DefaultResponse
+  ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
 
 
   resetPassword(
   resetPassword(
     id: Int!
     id: Int!

+ 1 - 1
server/setup.js

@@ -142,7 +142,7 @@ module.exports = () => {
       })
       })
       const guestGroup = await WIKI.models.groups.query().insert({
       const guestGroup = await WIKI.models.groups.query().insert({
         name: 'Guests',
         name: 'Guests',
-        permissions: JSON.stringify(['read:page:/']),
+        permissions: JSON.stringify(['read:pages']),
         isSystem: true
         isSystem: true
       })
       })