From c08b5ac837b2076c8c94ea494ae7e5d35fe0045a Mon Sep 17 00:00:00 2001 From: Nicolas Giard Date: Sun, 14 Oct 2018 17:38:39 -0400 Subject: [PATCH] feat: group permissions --- client/components/admin/admin-groups-edit.vue | 182 ++++++++++++++---- client/components/admin/admin-groups.vue | 19 +- .../graph/admin/groups/groups-query-list.gql | 1 + .../admin/groups/groups-query-single.gql | 4 +- client/scss/components/v-data-table.scss | 2 +- server/app/data.yml | 6 + server/graph/resolvers/group.js | 4 +- server/graph/schemas/authentication.graphql | 2 +- server/graph/schemas/group.graphql | 21 +- server/graph/schemas/localization.graphql | 4 +- server/graph/schemas/logging.graphql | 4 +- server/graph/schemas/page.graphql | 6 +- server/graph/schemas/rendering.graphql | 4 +- server/graph/schemas/search.graphql | 4 +- server/graph/schemas/storage.graphql | 4 +- server/graph/schemas/system.graphql | 2 +- server/graph/schemas/theming.graphql | 6 +- server/graph/schemas/user.graphql | 12 +- server/setup.js | 2 +- 19 files changed, 216 insertions(+), 73 deletions(-) diff --git a/client/components/admin/admin-groups-edit.vue b/client/components/admin/admin-groups-edit.vue index d5906117..b068aa99 100644 --- a/client/components/admin/admin-groups-edit.vue +++ b/client/components/admin/admin-groups-edit.vue @@ -10,7 +10,7 @@ v-spacer v-btn(color='indigo', large, outline, to='/groups') 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-icon(color='red') delete v-card @@ -45,14 +45,29 @@ .caption.mt-3.grey--text ID: {{group.id}} 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-card 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 @@ -62,39 +77,12 @@ | 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-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 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-card 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 | Assign User v-data-table( @@ -148,12 +136,138 @@ export default { group: { id: 0, name: '', + isSystem: false, + permissions: [], + pageRules: [], users: [] }, name: '', deleteGroupDialog: false, searchUserDialog: false, 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: [], headers: [ { text: 'ID', value: 'id', width: 50, align: 'right' }, diff --git a/client/components/admin/admin-groups.vue b/client/components/admin/admin-groups.vue index 0c49da67..610c0a39 100644 --- a/client/components/admin/admin-groups.vue +++ b/client/components/admin/admin-groups.vue @@ -17,7 +17,17 @@ v-card .dialog-header.is-short New Group 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-spacer v-btn(flat, @click='newGroupDialog = false') Cancel @@ -38,6 +48,10 @@ td {{ props.item.userCount }} td {{ props.item.createdAt | 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') v-alert.ma-3(icon='warning', :value='true', outline) No groups to display. .text-xs-center.py-2(v-if='groups.length > 15') @@ -64,7 +78,8 @@ export default { { text: 'Name', value: 'name' }, { text: 'Users', value: 'userCount', width: 200 }, { 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: '' } diff --git a/client/graph/admin/groups/groups-query-list.gql b/client/graph/admin/groups/groups-query-list.gql index 1d55617a..c2453f10 100644 --- a/client/graph/admin/groups/groups-query-list.gql +++ b/client/graph/admin/groups/groups-query-list.gql @@ -3,6 +3,7 @@ query { list { id name + isSystem userCount createdAt updatedAt diff --git a/client/graph/admin/groups/groups-query-single.gql b/client/graph/admin/groups/groups-query-single.gql index 8cfde451..43a65fc7 100644 --- a/client/graph/admin/groups/groups-query-single.gql +++ b/client/graph/admin/groups/groups-query-single.gql @@ -3,7 +3,9 @@ query ($id: Int!) { single(id: $id) { id name - rights { + isSystem + permissions + pageRules { id path role diff --git a/client/scss/components/v-data-table.scss b/client/scss/components/v-data-table.scss index cb874434..94756edb 100644 --- a/client/scss/components/v-data-table.scss +++ b/client/scss/components/v-data-table.scss @@ -1,4 +1,4 @@ -.datatable { +.v-datatable { .is-clickable { cursor: pointer; } diff --git a/server/app/data.yml b/server/app/data.yml index 31d54c3f..965cf810 100644 --- a/server/app/data.yml +++ b/server/app/data.yml @@ -76,6 +76,12 @@ jobs: onInit: false cron: false concurrency: 1 +groups: + defaultPermissions: + - 'manage:pages' + - 'write:assets' + - 'read:comments' + - 'write:comments' telemetry: BUGSNAG_ID: 'bb4b324d0675bcbba10025617fd2cec8' BUGSNAG_REMOTE: 'https://notify.bugsnag.com' diff --git a/server/graph/resolvers/group.js b/server/graph/resolvers/group.js index 154869fe..962e002c 100644 --- a/server/graph/resolvers/group.js +++ b/server/graph/resolvers/group.js @@ -39,7 +39,9 @@ module.exports = { }, async create(obj, args) { const group = await WIKI.models.groups.query().insertAndFetch({ - name: args.name + name: args.name, + permissions: JSON.stringify(WIKI.data.groups.defaultPermissions), + isSystem: false }) return { responseResult: graphHelper.generateSuccess('Group created successfully.'), diff --git a/server/graph/schemas/authentication.graphql b/server/graph/schemas/authentication.graphql index bb7d953d..aabb4295 100644 --- a/server/graph/schemas/authentication.graphql +++ b/server/graph/schemas/authentication.graphql @@ -38,7 +38,7 @@ type AuthenticationMutation { updateStrategies( strategies: [AuthenticationStrategyInput] - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/group.graphql b/server/graph/schemas/group.graphql index c3338cae..6db98f7c 100644 --- a/server/graph/schemas/group.graphql +++ b/server/graph/schemas/group.graphql @@ -18,11 +18,11 @@ type GroupQuery { list( filter: String orderBy: String - ): [GroupMinimal] + ): [GroupMinimal] @auth(requires: ["write:groups", "manage:groups", "manage:system"]) single( id: Int! - ): Group + ): Group @auth(requires: ["write:groups", "manage:groups", "manage:system"]) } # ----------------------------------------------- @@ -32,26 +32,26 @@ type GroupQuery { type GroupMutation { create( name: String! - ): GroupResponse + ): GroupResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) update( id: Int! name: String! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) delete( id: Int! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) assignUser( groupId: Int! userId: Int! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) unassignUser( groupId: Int! userId: Int! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) } # ----------------------------------------------- @@ -66,6 +66,7 @@ type GroupResponse { type GroupMinimal { id: Int! name: String! + isSystem: Boolean! userCount: Int createdAt: Date! updatedAt: Date! @@ -74,8 +75,10 @@ type GroupMinimal { type Group { id: Int! name: String! - rights: [Right] - users: [User] + isSystem: Boolean! + permissions: [String]! + pageRules: [Right] + users: [UserMinimal] createdAt: Date! updatedAt: Date! } diff --git a/server/graph/schemas/localization.graphql b/server/graph/schemas/localization.graphql index f035485e..98b5b077 100644 --- a/server/graph/schemas/localization.graphql +++ b/server/graph/schemas/localization.graphql @@ -26,14 +26,14 @@ type LocalizationQuery { type LocalizationMutation { downloadLocale( locale: String! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:system"]) updateLocale( locale: String! autoUpdate: Boolean! namespacing: Boolean! namespaces: [String]! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/logging.graphql b/server/graph/schemas/logging.graphql index 82135b3e..cf4042b8 100644 --- a/server/graph/schemas/logging.graphql +++ b/server/graph/schemas/logging.graphql @@ -22,7 +22,7 @@ type LoggingQuery { loggers( filter: String orderBy: String - ): [Logger] + ): [Logger] @auth(requires: ["manage:system"]) } # ----------------------------------------------- @@ -32,7 +32,7 @@ type LoggingQuery { type LoggingMutation { updateLoggers( loggers: [LoggerInput] - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/page.graphql b/server/graph/schemas/page.graphql index df3d6de3..9816209a 100644 --- a/server/graph/schemas/page.graphql +++ b/server/graph/schemas/page.graphql @@ -45,7 +45,7 @@ type PageMutation { publishStartDate: Date tags: [String]! title: String! - ): PageResponse + ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"]) update( id: Int! @@ -60,11 +60,11 @@ type PageMutation { publishStartDate: Date tags: [String] title: String - ): PageResponse + ): PageResponse @auth(requires: ["manage:pages", "manage:system"]) delete( id: Int! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["delete:pages", "manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/rendering.graphql b/server/graph/schemas/rendering.graphql index 13d5eb0f..0a918929 100644 --- a/server/graph/schemas/rendering.graphql +++ b/server/graph/schemas/rendering.graphql @@ -18,7 +18,7 @@ type RenderingQuery { renderers( filter: String orderBy: String - ): [Renderer] + ): [Renderer] @auth(requires: ["manage:system"]) } # ----------------------------------------------- @@ -28,7 +28,7 @@ type RenderingQuery { type RenderingMutation { updateRenderers( renderers: [RendererInput] - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/search.graphql b/server/graph/schemas/search.graphql index 28cc9195..c311be7a 100644 --- a/server/graph/schemas/search.graphql +++ b/server/graph/schemas/search.graphql @@ -18,7 +18,7 @@ type SearchQuery { searchEngines( filter: String orderBy: String - ): [SearchEngine] + ): [SearchEngine] @auth(requires: ["manage:system"]) } # ----------------------------------------------- @@ -28,7 +28,7 @@ type SearchQuery { type SearchMutation { updateSearchEngines( searchEngines: [SearchEngineInput] - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/storage.graphql b/server/graph/schemas/storage.graphql index 015f1dc8..a984995b 100644 --- a/server/graph/schemas/storage.graphql +++ b/server/graph/schemas/storage.graphql @@ -18,7 +18,7 @@ type StorageQuery { targets( filter: String orderBy: String - ): [StorageTarget] + ): [StorageTarget] @auth(requires: ["manage:system"]) } # ----------------------------------------------- @@ -28,7 +28,7 @@ type StorageQuery { type StorageMutation { updateTargets( targets: [StorageTargetInput] - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/system.graphql b/server/graph/schemas/system.graphql index 73373348..91e6563a 100644 --- a/server/graph/schemas/system.graphql +++ b/server/graph/schemas/system.graphql @@ -15,7 +15,7 @@ extend type Mutation { # ----------------------------------------------- type SystemQuery { - info: SystemInfo + info: SystemInfo @auth(requires: ["manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/theming.graphql b/server/graph/schemas/theming.graphql index 71c54e41..bdd51fa6 100644 --- a/server/graph/schemas/theming.graphql +++ b/server/graph/schemas/theming.graphql @@ -15,8 +15,8 @@ extend type Mutation { # ----------------------------------------------- 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( theme: String! darkMode: Boolean! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:theme", "manage:system"]) } # ----------------------------------------------- diff --git a/server/graph/schemas/user.graphql b/server/graph/schemas/user.graphql index 59fdc30d..f96e3296 100644 --- a/server/graph/schemas/user.graphql +++ b/server/graph/schemas/user.graphql @@ -18,15 +18,15 @@ type UserQuery { list( filter: String orderBy: String - ): [UserMinimal] + ): [UserMinimal] @auth(requires: ["write:users", "manage:users", "manage:system"]) search( query: String! - ): [UserMinimal] + ): [UserMinimal] @auth(requires: ["write:groups", "manage:groups", "write:users", "manage:users", "manage:system"]) single( id: Int! - ): User + ): User @auth(requires: ["manage:users", "manage:system"]) } # ----------------------------------------------- @@ -41,7 +41,7 @@ type UserMutation { provider: String! providerId: String role: UserRole! - ): UserResponse + ): UserResponse @auth(requires: ["write:users", "manage:users", "manage:system"]) update( id: Int! @@ -50,11 +50,11 @@ type UserMutation { provider: String providerId: String role: UserRole - ): UserResponse + ): UserResponse @auth(requires: ["manage:users", "manage:system"]) delete( id: Int! - ): DefaultResponse + ): DefaultResponse @auth(requires: ["manage:users", "manage:system"]) resetPassword( id: Int! diff --git a/server/setup.js b/server/setup.js index 6ab855cd..b8442ceb 100644 --- a/server/setup.js +++ b/server/setup.js @@ -142,7 +142,7 @@ module.exports = () => { }) const guestGroup = await WIKI.models.groups.query().insert({ name: 'Guests', - permissions: JSON.stringify(['read:page:/']), + permissions: JSON.stringify(['read:pages']), isSystem: true })