diff --git a/src/components/Status/index.vue b/src/components/Status/index.vue index f2b071c2bc8c169aa032e189cdef12c18d873d9f..a3283a29e03347fe7265f9164f9d02c5ce708ad4 100644 --- a/src/components/Status/index.vue +++ b/src/components/Status/index.vue @@ -23,7 +23,7 @@ </router-link> </div> </div> - <div class="status-actions"> + <div v-if="isPrivileged(['messages_delete'], [])" class="status-actions"> <div class="status-tags"> <el-tag v-if="status.sensitive" type="warning" size="large">{{ $t('reports.sensitive') }}</el-tag> <el-tag size="large">{{ capitalizeFirstLetter(status.visibility) }}</el-tag> @@ -184,6 +184,11 @@ export default { capitalizeFirstLetter(str) { return str.charAt(0).toUpperCase() + str.slice(1) }, + isPrivileged(accepted_privileges, accepted_roles) { + const user_privileges = this.$store.getters.privileges + const user_roles = this.$store.getters.roles + return accepted_privileges.some(privilege => user_privileges.indexOf(privilege) >= 0) || accepted_roles.some(role => user_roles.indexOf(role) >= 0) + }, changeStatus(statusId, isSensitive, visibility) { this.$store.dispatch('ChangeStatusScope', { statusId, diff --git a/src/permission.js b/src/permission.js index 8e155e5b18c9129e5a74e98841059bde8767b9fc..0530bdcf1af4182e5a450903b6f8fbd9c468df63 100644 --- a/src/permission.js +++ b/src/permission.js @@ -14,6 +14,25 @@ function hasPermission(roles, permissionRoles) { return roles.some(role => permissionRoles.indexOf(role) >= 0) } +function isPrivileged(route, privileges) { + if (!route.required_privileges) { + return true + } + + // We check for all the required privileges if the user has it + // If there's at least one privilege missing, the user isn't privileged so we return false + // If the logged in user has all required privileges, we return true + return route.required_privileges.map(required_privilege => privileges.indexOf(required_privilege)).indexOf(-1) === -1 +} + +function findFirstUnhiddenPath(addRouters) { + const unhiddenRoute = addRouters.find((route) => !route.hidden) + if (unhiddenRoute) { + return unhiddenRoute.path + '/index' + } + return '/401' +} + const whiteList = ['/login', '/auth-redirect', '/login-pleroma']// no redirect whitelist export const beforeEachRoute = (to, from, next) => { @@ -24,11 +43,20 @@ export const beforeEachRoute = (to, from, next) => { next({ path: '/' }) NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it } else { - if (store.getters.roles.length === 0) { + if (store.getters.roles.length === 0 && store.getters.privileges.length === 0) { store.dispatch('GetUserInfo').then(res => { - const roles = res.data.pleroma.is_admin ? ['admin'] : [] + const roles = store.getters.roles + const privileges = store.getters.privileges + store.dispatch('GenerateRoutes', { roles }).then(() => { - store.getters.addRouters.forEach(route => router.addRoute(route)) + const addRouters = store.getters.addRouters + addRouters.forEach(route => { + route.hidden = route.hidden || !isPrivileged(route, privileges) + if (route.path === '') { + route.redirect = findFirstUnhiddenPath(addRouters) + } + router.addRoute(route) + }) next({ ...to, replace: true }) }) }).catch((err) => { diff --git a/src/router/index.js b/src/router/index.js index 4d0463182d2f404faf7ae26b2b14b2685c3e0af4..a44a2aa98b2d86d3ccbe081e3f6a5fa6392f33a3 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -36,6 +36,7 @@ const settingsChildren = () => { const settings = { path: '/settings', component: Layout, + roles: ['admin'], name: 'Settings', hasSubmenu: true, meta: { title: 'settings', icon: 'el-icon-setting', noCache: true }, @@ -45,6 +46,7 @@ const statusesDisabled = disabledFeatures.includes('statuses') const statuses = { path: '/statuses', component: Layout, + required_privileges: ['messages_read', 'messages_delete'], children: [ { path: 'index', @@ -59,6 +61,7 @@ const reportsDisabled = disabledFeatures.includes('reports') const reports = { path: '/reports', component: Layout, + required_privileges: ['reports_manage_reports'], children: [ { path: 'index', @@ -73,6 +76,7 @@ const invitesDisabled = disabledFeatures.includes('invites') const invites = { path: '/invites', component: Layout, + required_privileges: ['users_manage_invites'], children: [ { path: 'index', @@ -87,6 +91,7 @@ const relaysDisabled = disabledFeatures.includes('relays') const relays = { path: '/relays', component: Layout, + roles: ['admin'], children: [ { path: 'index', @@ -101,6 +106,7 @@ const moderationLogDisabled = disabledFeatures.includes('moderation-log') const moderationLog = { path: '/moderation_log', component: Layout, + required_privileges: ['moderation_log_read'], children: [ { path: 'index', @@ -115,6 +121,7 @@ const mediaProxyCacheDisabled = disabledFeatures.includes('media-proxy-cache') const mediaProxyCache = { path: '/media_proxy_cache', component: Layout, + roles: ['admin'], children: [ { path: 'index', @@ -161,12 +168,6 @@ export const constantRouterMap = [ path: '/401', component: () => import('@/views/errorPage/401'), hidden: true - }, - { - path: '', - component: Layout, - redirect: '/users/index', - hidden: true } ] @@ -180,6 +181,7 @@ export const asyncRouterMap = [ { path: '/users', component: Layout, + required_privileges: ['users_read'], children: [ { path: 'index', @@ -191,11 +193,11 @@ export const asyncRouterMap = [ }, ...(statusesDisabled ? [] : [statuses]), ...(reportsDisabled ? [] : [reports]), - ...(invitesDisabled ? [] : [invites]), ...(moderationLogDisabled ? [] : [moderationLog]), ...(relaysDisabled ? [] : [relays]), ...(mediaProxyCacheDisabled ? [] : [mediaProxyCache]), ...(settingsDisabled ? [] : [settings]), + ...(invitesDisabled ? [] : [invites]), { path: '/users/:id', component: Layout, @@ -244,5 +246,10 @@ export const asyncRouterMap = [ ], hidden: true }, - { path: '*', redirect: '/404', hidden: true } + { path: '*', redirect: '/404', hidden: true }, + { + path: '', + component: Layout, + hidden: true + } ] diff --git a/src/store/getters.js b/src/store/getters.js index a287712ddb89645304ad06dab251cd12ced6aaa9..83621ac6b48b48882e1fbc3751125360cfc1a5ab 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -11,6 +11,7 @@ const getters = { introduction: state => state.user.introduction, status: state => state.user.status, roles: state => state.user.roles, + privileges: state => state.user.privileges, setting: state => state.user.setting, permission_routers: state => state.permission.routers, addRouters: state => state.permission.addRouters, diff --git a/src/store/modules/app.js b/src/store/modules/app.js index e06953aaaac6fc423e1f9e6ab094deb87c6ab986..b585da5bf3f6fc9578e20f29139b77804fa0e324 100644 --- a/src/store/modules/app.js +++ b/src/store/modules/app.js @@ -51,6 +51,9 @@ const app = { commit('CLOSE_SIDEBAR', withoutAnimation) }, async NeedReboot({ commit, getters }) { + if (!getters.roles.includes('admin')) { + return + } const response = await needReboot(getters.authHost, getters.token) commit('TOGGLE_REBOOT', response.data['need_reboot']) }, diff --git a/src/store/modules/user.js b/src/store/modules/user.js index 7b98ed58d0309ccdd4aa6f2337683a31e930800f..0616011aa7df3c40c1e80a74ed757f085d86e44d 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -14,6 +14,7 @@ const user = { avatar: '', introduction: '', roles: [], + privileges: [], setting: { articlePlatform: [] }, @@ -45,6 +46,9 @@ const user = { SET_ROLES: (state, roles) => { state.roles = roles }, + SET_PRIVILEGES: (state, privileges) => { + state.privileges = privileges || [] + }, SET_ID: (state, id) => { state.id = id }, @@ -82,16 +86,21 @@ const user = { return new Promise((resolve, reject) => { getUserInfo(state.token, state.authHost).then(response => { const data = response.data - const message = '<span>This user doesn\`t have admin rights. Try another credentials or see the </span>' + + const is_admin = data.pleroma?.is_admin + const is_privileged = !!data.pleroma?.privileges?.length + const message = '<span>This user doesn\'t have admin/moderator rights or privileges. Try another account or see the </span>' + '<u><a target="_blank" href="https://docs.pleroma.social/backend/administration/CLI_tasks/user/#set-the-value-of-the-given-users-settings">docs</a></u>' + - '<span> to find out how to make this user an admin</span>' + '<span> to find out how to grant permissions to this user</span>' if (!data) { reject('Verification failed, please login again.') } - if (data.pleroma && data.pleroma.is_admin) { - commit('SET_ROLES', ['admin']) + if (is_admin || is_privileged) { + if (is_admin) { + commit('SET_ROLES', ['admin']) + } + commit('SET_PRIVILEGES', data.pleroma.privileges || []) } else { reject(message) } diff --git a/src/store/modules/userProfile.js b/src/store/modules/userProfile.js index 40af065c53c656ff1008dfe9d692d3d637064e2e..000e07a6242292dac60ad093b366a102a4c64849 100644 --- a/src/store/modules/userProfile.js +++ b/src/store/modules/userProfile.js @@ -41,8 +41,12 @@ const userProfile = { commit('SET_USER', userResponse.data) commit('SET_USER_PROFILE_LOADING', false) - dispatch('FetchUserStatuses', { userId, godmode }) - dispatch('FetchUserChats', { userId }) + if (getters.roles.includes('admin') || getters.privileges.includes('messages_read')) { + dispatch('FetchUserStatuses', { userId, godmode }) + if (!godmode) { + dispatch('FetchUserChats', { userId }) + } + } }, FetchUserStatuses({ commit, dispatch, getters }, { userId, godmode }) { commit('SET_STATUSES_LOADING', true) @@ -61,8 +65,10 @@ const userProfile = { commit('SET_CHATS_LOADING', false) }, async FetchUserCredentials({ commit, getters }, { nickname }) { - const userResponse = await fetchUserCredentials(nickname, getters.authHost, getters.token) - commit('SET_USER_CREDENTIALS', userResponse.data) + if (getters.roles.includes('admin') || getters.privileges.includes('users_manage_credentials')) { + const userResponse = await fetchUserCredentials(nickname, getters.authHost, getters.token) + commit('SET_USER_CREDENTIALS', userResponse.data) + } }, SetStatuses({ commit }, statuses) { commit('SET_STATUSES', statuses) diff --git a/src/store/modules/users.js b/src/store/modules/users.js index 0ba8c998b31f7aceeacebc3a1c24044625a29094..7648bd0c109bc440124d886458b120398be5d3cc 100644 --- a/src/store/modules/users.js +++ b/src/store/modules/users.js @@ -220,6 +220,10 @@ const users = { dispatch('FetchTagPolicySetting') }, async FetchTagPolicySetting({ commit, getters }) { + if (!getters.roles.includes('admin')) { + return + } + // XXX: Use nodeinfo? const { data } = await fetchSettings(getters.authHost, getters.token) const mrfSettings = data.configs.find(el => el.key === ':mrf') ? data.configs.find(el => el.key === ':mrf').value diff --git a/src/views/layout/components/Sidebar/index.vue b/src/views/layout/components/Sidebar/index.vue index ed8bd849640e0898a12d0e2ab2b682a128a87ab7..d22f95c4e83824677b33efdbf916130694741c8a 100644 --- a/src/views/layout/components/Sidebar/index.vue +++ b/src/views/layout/components/Sidebar/index.vue @@ -27,6 +27,7 @@ export default { ...mapGetters([ 'permission_routers', 'roles', + 'privileges', 'sidebar', 'tabs' ]), @@ -38,7 +39,9 @@ export default { } }, mounted() { - this.$store.dispatch('FetchOpenReportsCount') + if (this.privileges?.indexOf('reports_manage_reports') !== -1) { + this.$store.dispatch('FetchOpenReportsCount') + } }, methods: { getMergedRoutes() { diff --git a/src/views/users/components/ModerationDropdown.vue b/src/views/users/components/ModerationDropdown.vue index 57023192af255e3756cc729d129c2e6be6af4a1b..913e52458ec1ede7a91ce0c70df74ca3be914a04 100644 --- a/src/views/users/components/ModerationDropdown.vue +++ b/src/views/users/components/ModerationDropdown.vue @@ -1,5 +1,5 @@ <template> - <el-dropdown :hide-on-click="false" size="small" trigger="click" placement="top-start" @click.native.stop> + <el-dropdown v-if="isPrivileged(['users_manage_activation_state', 'users_delete', 'users_manage_tags', 'users_manage_credentials'], ['admin'])" :hide-on-click="false" size="small" trigger="click" placement="top-start" @click.native.stop> <div> <el-button v-if="page === 'users'" type="text" class="el-dropdown-link"> {{ $t('users.moderation') }} @@ -17,6 +17,7 @@ </div> <el-dropdown-menu slot="dropdown" class="moderation-dropdown-menu"> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" class="actor-type-dropdown"> <el-select v-model="actorType" :placeholder="$t('userProfile.actorType')" class="actor-type-select"> <el-option :label="$t('users.service')" value="Service"/> @@ -24,51 +25,51 @@ </el-select> </el-dropdown-item> <el-dropdown-item - v-if="showAdminAction(user)" + v-if="isPrivileged([], ['admin']) && showAdminAction(user)" divided @click.native="toggleUserRight(user, 'admin')"> {{ user.roles.admin ? $t('users.revokeAdmin') : $t('users.grantAdmin') }} </el-dropdown-item> <el-dropdown-item - v-if="showAdminAction(user)" + v-if="isPrivileged([], ['admin']) && showAdminAction(user)" @click.native="toggleUserRight(user, 'moderator')"> {{ user.roles.moderator ? $t('users.revokeModerator') : $t('users.grantModerator') }} </el-dropdown-item> <el-dropdown-item - v-if="showDeactivatedButton(user.id) && page !== 'statusPage'" + v-if="isPrivileged(['users_manage_activation_state'], []) && showDeactivatedButton(user.id) && page !== 'statusPage'" :divided="showAdminAction(user)" @click.native="toggleActivation(user)"> {{ !user.is_active ? $t('users.activateAccount') : $t('users.deactivateAccount') }} </el-dropdown-item> <el-dropdown-item - v-if="showDeactivatedButton(user.id) && page !== 'statusPage'" + v-if="isPrivileged(['users_delete'], []) && showDeactivatedButton(user.id) && page !== 'statusPage'" @click.native="handleDeletion(user)"> {{ $t('users.deleteAccount') }} </el-dropdown-item> <el-dropdown-item - v-if="user.local && !user.is_approved" + v-if="isPrivileged([], ['admin']) && user.local && !user.is_approved" divided @click.native="handleAccountApproval(user)"> {{ $t('users.approveAccount') }} </el-dropdown-item> <el-dropdown-item - v-if="user.local && !user.is_approved" + v-if="isPrivileged([], ['admin']) && user.local && !user.is_approved" @click.native="handleAccountRejection(user)"> {{ $t('users.rejectAccount') }} </el-dropdown-item> <el-dropdown-item - v-if="user.local && !user.is_confirmed" + v-if="isPrivileged([], ['admin']) && user.local && !user.is_confirmed" divided @click.native="handleEmailConfirmation(user)"> {{ $t('users.confirmAccount') }} </el-dropdown-item> <el-dropdown-item - v-if="user.local && !user.is_confirmed" + v-if="isPrivileged([], ['admin']) && user.local && !user.is_confirmed" @click.native="handleConfirmationResend(user)"> {{ $t('users.resendConfirmation') }} </el-dropdown-item> <el-dropdown-item - v-if="tagPolicyEnabled" + v-if="isPrivileged(['users_manage_tags'], []) && tagPolicyEnabled" :divided="showAdminAction(user)" :class="{ 'active-tag': user.tags.includes('mrf_tag:media-force-nsfw') }" @click.native="toggleTag(user, 'mrf_tag:media-force-nsfw')"> @@ -76,60 +77,60 @@ <i v-if="user.tags.includes('mrf_tag:media-force-nsfw')" class="el-icon-check"/> </el-dropdown-item> <el-dropdown-item - v-if="tagPolicyEnabled" + v-if="isPrivileged(['users_manage_tags'], []) && tagPolicyEnabled" :class="{ 'active-tag': user.tags.includes('mrf_tag:media-strip') }" @click.native="toggleTag(user, 'mrf_tag:media-strip')"> {{ $t('users.stripMedia') }} <i v-if="user.tags.includes('mrf_tag:media-strip')" class="el-icon-check"/> </el-dropdown-item> <el-dropdown-item - v-if="tagPolicyEnabled" + v-if="isPrivileged(['users_manage_tags'], []) && tagPolicyEnabled" :class="{ 'active-tag': user.tags.includes('mrf_tag:force-unlisted') }" @click.native="toggleTag(user, 'mrf_tag:force-unlisted')"> {{ $t('users.forceUnlisted') }} <i v-if="user.tags.includes('mrf_tag:force-unlisted')" class="el-icon-check"/> </el-dropdown-item> <el-dropdown-item - v-if="tagPolicyEnabled" + v-if="isPrivileged(['users_manage_tags'], []) && tagPolicyEnabled" :class="{ 'active-tag': user.tags.includes('mrf_tag:sandbox') }" @click.native="toggleTag(user, 'mrf_tag:sandbox')"> {{ $t('users.sandbox') }} <i v-if="user.tags.includes('mrf_tag:sandbox')" class="el-icon-check"/> </el-dropdown-item> <el-dropdown-item - v-if="user.local && tagPolicyEnabled" + v-if="isPrivileged(['users_manage_tags'], []) && user.local && tagPolicyEnabled" :class="{ 'active-tag': user.tags.includes('mrf_tag:disable-remote-subscription') }" @click.native="toggleTag(user, 'mrf_tag:disable-remote-subscription')"> {{ $t('users.disableRemoteSubscription') }} <i v-if="user.tags.includes('mrf_tag:disable-remote-subscription')" class="el-icon-check"/> </el-dropdown-item> <el-dropdown-item - v-if="user.local && tagPolicyEnabled" + v-if="isPrivileged(['users_manage_tags'], []) && user.local && tagPolicyEnabled" :class="{ 'active-tag': user.tags.includes('mrf_tag:disable-any-subscription') }" @click.native="toggleTag(user, 'mrf_tag:disable-any-subscription')"> {{ $t('users.disableAnySubscription') }} <i v-if="user.tags.includes('mrf_tag:disable-any-subscription')" class="el-icon-check"/> </el-dropdown-item> <el-dropdown-item - v-if="!tagPolicyEnabled" + v-if="isPrivileged(['users_manage_tags'], []) && isPrivileged([], ['admin']) && !tagPolicyEnabled" divided class="no-hover" @click.native="enableTagPolicy"> {{ $t('users.enableTagPolicy') }} </el-dropdown-item> <el-dropdown-item - v-if="user.local" + v-if="isPrivileged(['users_manage_credentials'], []) && user.local" divided @click.native="getPasswordResetToken(user.nickname)"> {{ $t('users.getPasswordResetToken') }} </el-dropdown-item> <el-dropdown-item - v-if="user.local" + v-if="isPrivileged([], ['admin']) && user.local" @click.native="requirePasswordReset(user)"> {{ $t('users.requirePasswordReset') }} </el-dropdown-item> <el-dropdown-item - v-if="user.local" + v-if="isPrivileged([], ['admin']) && user.local" @click.native="disableMfa(user.nickname)"> {{ $t('users.disableMfa') }} </el-dropdown-item> @@ -181,6 +182,11 @@ export default { disableMfa(nickname) { this.$store.dispatch('DisableMfa', nickname) }, + isPrivileged(accepted_privileges, accepted_roles) { + const user_privileges = this.$store.getters.privileges + const user_roles = this.$store.getters.roles + return accepted_privileges.some(privilege => user_privileges.indexOf(privilege) >= 0) || accepted_roles.some(role => user_roles.indexOf(role) >= 0) + }, enableTagPolicy() { this.$confirm( this.$t('users.confirmEnablingTagPolicy'), diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue index 7ebd4ef6652aa75ce14cbd6f5ecc6942b6cffc3e..a98ba0217c80f8a81f788c02f20addf6f28fdcce 100644 --- a/src/views/users/components/MultipleUsersMenu.vue +++ b/src/views/users/components/MultipleUsersMenu.vue @@ -1,5 +1,5 @@ <template> - <el-dropdown size="small" trigger="click" placement="bottom-start"> + <el-dropdown v-if="isPrivileged(['users_manage_invites', 'users_delete', 'users_manage_activation_state', 'users_manage_tags'], ['admin'])" size="small" trigger="click" placement="bottom-start" class="multiple-users-menu" > <el-button v-if="isDesktop" class="actions-button"> <span class="actions-button-container"> <span> @@ -11,57 +11,70 @@ </el-button> <el-dropdown-menu v-if="showDropdownForMultipleUsers" slot="dropdown"> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" + class="grant-right-to-multiple-users" @click.native="grantRightToMultipleUsers('admin')"> {{ $t('users.grantAdmin') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" @click.native="revokeRightFromMultipleUsers('admin')"> {{ $t('users.revokeAdmin') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" @click.native="grantRightToMultipleUsers('moderator')"> {{ $t('users.grantModerator') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" @click.native="revokeRightFromMultipleUsers('moderator')"> {{ $t('users.revokeModerator') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged(['users_manage_invites'], [])" divided @click.native="approveAccountsForMultipleUsers"> {{ $t('users.approveAccounts') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged(['users_delete'], [])" @click.native="rejectAccountsForMultipleUsers"> {{ $t('users.rejectAccounts') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" divided @click.native="confirmAccountsForMultipleUsers"> {{ $t('users.confirmAccounts') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" @click.native="resendConfirmationForMultipleUsers"> {{ $t('users.resendConfirmation') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged(['users_manage_activation_state'], [])" divided @click.native="activateMultipleUsers"> {{ $t('users.activateAccounts') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged(['users_manage_activation_state'], [])" @click.native="deactivateMultipleUsers"> {{ $t('users.deactivateAccounts') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged(['users_delete'], [])" @click.native="deleteMultipleUsers"> {{ $t('users.deleteAccounts') }} </el-dropdown-item> <el-dropdown-item + v-if="isPrivileged([], ['admin'])" @click.native="requirePasswordReset"> {{ $t('users.requirePasswordReset') }} </el-dropdown-item> - <el-dropdown-item v-if="tagPolicyEnabled" divided class="no-hover"> + <el-dropdown-item v-if="tagPolicyEnabled && isPrivileged(['users_manage_tags'], [])" divided class="no-hover"> <div class="tag-container"> <span class="tag-text">{{ $t('users.forceNsfw') }}</span> <el-button-group class="tag-button-group"> @@ -74,7 +87,7 @@ </el-button-group> </div> </el-dropdown-item> - <el-dropdown-item v-if="tagPolicyEnabled" class="no-hover"> + <el-dropdown-item v-if="tagPolicyEnabled && isPrivileged(['users_manage_tags'], [])" class="no-hover"> <div class="tag-container"> <span class="tag-text">{{ $t('users.stripMedia') }}</span> <el-button-group class="tag-button-group"> @@ -87,7 +100,7 @@ </el-button-group> </div> </el-dropdown-item> - <el-dropdown-item v-if="tagPolicyEnabled" class="no-hover"> + <el-dropdown-item v-if="tagPolicyEnabled && isPrivileged(['users_manage_tags'], [])" class="no-hover"> <div class="tag-container"> <span class="tag-text">{{ $t('users.forceUnlisted') }}</span> <el-button-group class="tag-button-group"> @@ -100,7 +113,7 @@ </el-button-group> </div> </el-dropdown-item> - <el-dropdown-item v-if="tagPolicyEnabled" class="no-hover"> + <el-dropdown-item v-if="tagPolicyEnabled && isPrivileged(['users_manage_tags'], [])" class="no-hover"> <div class="tag-container"> <span class="tag-text">{{ $t('users.sandbox') }}</span> <el-button-group class="tag-button-group"> @@ -113,7 +126,7 @@ </el-button-group> </div> </el-dropdown-item> - <el-dropdown-item v-if="tagPolicyEnabled" class="no-hover"> + <el-dropdown-item v-if="tagPolicyEnabled && isPrivileged(['users_manage_tags'], [])" class="no-hover"> <div class="tag-container"> <span class="tag-text">{{ $t('users.disableRemoteSubscriptionForMultiple') }}</span> <el-button-group class="tag-button-group"> @@ -126,7 +139,7 @@ </el-button-group> </div> </el-dropdown-item> - <el-dropdown-item v-if="tagPolicyEnabled" class="no-hover"> + <el-dropdown-item v-if="tagPolicyEnabled && isPrivileged(['users_manage_tags'], [])" class="no-hover"> <div class="tag-container"> <span class="tag-text">{{ $t('users.disableAnySubscriptionForMultiple') }}</span> <el-button-group class="tag-button-group"> @@ -140,14 +153,14 @@ </div> </el-dropdown-item> <el-dropdown-item - v-if="!tagPolicyEnabled" + v-if="!tagPolicyEnabled && isPrivileged([], ['admin']) && isPrivileged(['users_manage_tags'], [])" divided @click.native="enableTagPolicy"> {{ $t('users.enableTagPolicy') }} </el-dropdown-item> </el-dropdown-menu> <el-dropdown-menu v-else slot="dropdown"> - <el-dropdown-item> + <el-dropdown-item class="select-users"> {{ $t('users.selectUsers') }} </el-dropdown-item> </el-dropdown-menu> @@ -257,6 +270,11 @@ export default { } } }, + isPrivileged(accepted_privileges, accepted_roles) { + const user_privileges = this.$store.getters.privileges + const user_roles = this.$store.getters.roles + return accepted_privileges.some(privilege => user_privileges.indexOf(privilege) >= 0) || accepted_roles.some(role => user_roles.indexOf(role) >= 0) + }, enableTagPolicy() { this.$confirm( this.$t('users.confirmEnablingTagPolicy'), diff --git a/src/views/users/index.vue b/src/views/users/index.vue index 510c7b23efbdebbbf0f109acf9160dedbbf0fa9a..e3f0bfc2933ac970887b5fcf141e8d35e083d333 100644 --- a/src/views/users/index.vue +++ b/src/views/users/index.vue @@ -17,7 +17,7 @@ @input="handleDebounceSearchInput"/> </div> <div class="actions-container"> - <el-button class="actions-button" @click="createAccountDialogOpen = true"> + <el-button v-if="isPrivileged([], ['admin'])" class="actions-button" @click="createAccountDialogOpen = true"> <span class="create-account"> <i class="el-icon-plus"/> {{ $t('users.createAccount') }} @@ -208,6 +208,11 @@ export default { clearSelection() { this.$refs.usersTable.clearSelection() }, + isPrivileged(accepted_privileges, accepted_roles) { + const user_privileges = this.$store.getters.privileges + const user_roles = this.$store.getters.roles + return accepted_privileges.some(privilege => user_privileges.indexOf(privilege) >= 0) || accepted_roles.some(role => user_roles.indexOf(role) >= 0) + }, closeResetPasswordDialog() { this.resetPasswordDialogOpen = false this.$store.dispatch('RemovePasswordToken') diff --git a/test/components/Status/index.test.js b/test/components/Status/index.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2401eec6c4e7b2b1dec8aed1040ae23276c24f11 --- /dev/null +++ b/test/components/Status/index.test.js @@ -0,0 +1,71 @@ +import Vuex from 'vuex' +import { mount, createLocalVue, config } from '@vue/test-utils' +import flushPromises from 'flush-promises' +import Element from 'element-ui' +import Statuses from '@/views/statuses/index' +import { + storeNoPrivilegesNoRoles, + storeWithPrivilegesMessagesDeleteNoRoles +} from './store.conf' +import { cloneDeep } from 'lodash' + +config.mocks["$t"] = (t) => t +config.stubs.transition = false + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(Element) + +jest.mock('@/api/app') +jest.mock('@/api/nodeInfo') +jest.mock('@/api/peers') +jest.mock('@/api/settings') +jest.mock('@/api/status') + +describe('Statuses', () => { + it('Allows to change scope when privileged', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithPrivilegesMessagesDeleteNoRoles)) + const wrapper = mount(Statuses, { + store: store, + localVue + }) + await flushPromises() + + store.dispatch('HandleFilterChange', 'heaven.com') + wrapper.vm.handleFilterChange() + await flushPromises() + + const menu = wrapper.find('.status-actions') + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual([ + 'reports.addSensitive', + 'reports.deleteStatus', + 'reports.private', + 'reports.public' + ]) + + done() + }) + + it('Allows to change scope when not privileged', async (done) => { + const store = new Vuex.Store(cloneDeep(storeNoPrivilegesNoRoles)) + const wrapper = mount(Statuses, { + store: store, + localVue + }) + await flushPromises() + + store.dispatch('HandleFilterChange', 'heaven.com') + wrapper.vm.handleFilterChange() + await flushPromises() + + const menu = wrapper.find('.status-actions') + + expect(menu.exists()).toEqual(false) + + done() + }) +}) diff --git a/test/components/Status/store.conf.js b/test/components/Status/store.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..18945a7457d7839754c94ba94634f112cb5e4b33 --- /dev/null +++ b/test/components/Status/store.conf.js @@ -0,0 +1,47 @@ +import app from '@/store/modules/app' +import peers from '@/store/modules/peers' +import user from '@/store/modules/user' +import users from '@/store/modules/users' +import settings from '@/store/modules/settings' +import status from '@/store/modules/status' +import getters from '@/store/getters' + +export const storeNoPrivilegesNoRoles = { + modules: { + app, + peers, + settings, + status, + user: { + ...user, + state: { + ...user.state, + authHost: 'localhost:4000', + roles: [], + privileges: [] + } + }, + users + }, + getters +} + +export const storeWithPrivilegesMessagesDeleteNoRoles = { + modules: { + app, + peers, + settings, + status, + user: { + ...user, + state: { + ...user.state, + authHost: 'localhost:4000', + roles: [], + privileges: ['messages_delete'] + } + }, + users + }, + getters +} diff --git a/test/views/statuses/statusShowStore.conf.js b/test/views/statuses/statusShowStore.conf.js index c83a8660947fb0ba52e56c5b44eb54cc7e89d4a1..38a3ec18c77969759f0f8cd8e0f4ebc40a00080b 100644 --- a/test/views/statuses/statusShowStore.conf.js +++ b/test/views/statuses/statusShowStore.conf.js @@ -13,7 +13,20 @@ export default { peers, settings, status, - user, + user: { + ...user, + state: { + ...user.state, + roles: ['admin'], + privileges: [ + 'users_manage_activation_state', + 'users_delete', + 'users_manage_tags', + 'users_manage_credentials', + 'messages_delete' + ] + } + }, userProfile, users }, diff --git a/test/views/users/components/ModerationDropdown.test.js b/test/views/users/components/ModerationDropdown.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7d6013dd804f5fd9dea8b088d3cfcbdebf3e5696 --- /dev/null +++ b/test/views/users/components/ModerationDropdown.test.js @@ -0,0 +1,210 @@ +import Vuex from 'vuex' +import { mount, createLocalVue, config, RouterLinkStub } from '@vue/test-utils' +import flushPromises from 'flush-promises' +import Element from 'element-ui' +import Users from '@/views/users/index' +import { + storeNoPrivilegesNoRoles, + storeWithTagPolicyNoPrivilegesRolesAdmin, + storeWithPrivilegesUsersManageActivationStateNoRoles, + storeWithPrivilegesUsersDeleteNoRoles, + storeWithTagPolicyPrivilegesUsersManageTagsNoRoles, + storeWithTagPolicyPrivilegesUsersManageTagsRolesAdmin, + storeWithPrivilegesUsersManageCredentialsNoRoles +} from './store.conf' +import { cloneDeep } from 'lodash' + +config.mocks["$t"] = (key) => key + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(Element) + +jest.mock('@/api/app') +jest.mock('@/api/nodeInfo') +jest.mock('@/api/users') +jest.mock('@/api/settings') + +describe('The Multiple Users Moderation Menu', () => { + it('doesnt show for someone with no privileges and no roles', async (done) => { + const store = new Vuex.Store(cloneDeep(storeNoPrivilegesNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + + expect(wrapper.find('.moderation-dropdown-menu').exists()).toEqual(false) + done() + }) + + it('shows for someone with admin role and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithTagPolicyNoPrivilegesRolesAdmin)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + const menu = wrapper.findAll('.moderation-dropdown-menu').at(0) + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual([ + 'users.disableMfa', + 'users.grantModerator', + 'users.requirePasswordReset', + 'users.revokeAdmin', + 'users.service users.person', + ]) + + store.state.users.mrfPolicies = [] + await flushPromises() + + expect(menu_items.length).toEqual(menu_items_text.length) + done() + }) + + it('shows for someone with users_manage_activation_state privilege and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithPrivilegesUsersManageActivationStateNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + const menu = wrapper.findAll('.moderation-dropdown-menu').at(0) + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual(['users.deactivateAccount']) + + done() + }) + + it('shows for someone with users_delete privilege and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithPrivilegesUsersDeleteNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + const menu = wrapper.findAll('.moderation-dropdown-menu').at(0) + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual(['users.deleteAccount']) + + done() + }) + + it('shows for someone with users_manage_tags privilege and shows proper entries depending on wether tagpolicy is set', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithTagPolicyPrivilegesUsersManageTagsNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + const menu = wrapper.findAll('.moderation-dropdown-menu').at(0) + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual([ + 'users.disableAnySubscription', + 'users.disableRemoteSubscription', + 'users.forceNsfw', + 'users.forceUnlisted', + 'users.sandbox', + 'users.stripMedia' + ]) + + store.state.users.mrfPolicies = [] + await flushPromises() + + expect(menu.findAll('.el-dropdown-menu__item').length).toEqual(0) + + done() + }) + + it('shows enable tagpolicy for someone with users_manage_tags privilege and admin role when tagpolicy is not set', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithTagPolicyPrivilegesUsersManageTagsRolesAdmin)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + const menu = wrapper.findAll('.moderation-dropdown-menu').at(0) + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()) + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text.includes('users.disableAnySubscription')).toBe(true) + expect(menu_items_text.includes('users.disableRemoteSubscription')).toBe(true) + expect(menu_items_text.includes('users.forceNsfw')).toBe(true) + expect(menu_items_text.includes('users.forceUnlisted')).toBe(true) + expect(menu_items_text.includes('users.sandbox')).toBe(true) + expect(menu_items_text.includes('users.stripMedia')).toBe(true) + + store.state.users.mrfPolicies = [] + await flushPromises() + + const menu_items_text_no_policy = menu.findAll('.el-dropdown-menu__item').wrappers.map(menu_item => menu_item.text()) + expect(menu_items_text_no_policy.includes('users.disableAnySubscription')).toBe(false) + expect(menu_items_text_no_policy.includes('users.disableRemoteSubscription')).toBe(false) + expect(menu_items_text_no_policy.includes('users.forceNsfw')).toBe(false) + expect(menu_items_text_no_policy.includes('users.forceUnlisted')).toBe(false) + expect(menu_items_text_no_policy.includes('users.sandbox')).toBe(false) + expect(menu_items_text_no_policy.includes('users.stripMedia')).toBe(false) + + expect(menu_items_text_no_policy.includes('users.enableTagPolicy')).toBe(true) + + done() + }) + + it('shows for someone with users_manage_credentials privilege and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithPrivilegesUsersManageCredentialsNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + const menu = wrapper.findAll('.moderation-dropdown-menu').at(0) + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual(['users.getPasswordResetToken']) + + done() + }) +}) diff --git a/test/views/users/components/MultipleUsersMenu.test.js b/test/views/users/components/MultipleUsersMenu.test.js new file mode 100644 index 0000000000000000000000000000000000000000..70b1c6b1b74ab4fee5ddb345091f5f91c3a9563d --- /dev/null +++ b/test/views/users/components/MultipleUsersMenu.test.js @@ -0,0 +1,200 @@ +import Vuex from 'vuex' +import { mount, createLocalVue, config, RouterLinkStub } from '@vue/test-utils' +import flushPromises from 'flush-promises' +import Element from 'element-ui' +import Users from '@/views/users/index' +import { + storeNoPrivilegesNoRoles, + storeWithTagPolicyNoPrivilegesRolesAdmin, + storeWithPrivilegesUsersManageInvitesNoRoles, + storeWithPrivilegesUsersDeleteNoRoles, + storeWithPrivilegesUsersManageActivationStateNoRoles, + storeWithTagPolicyPrivilegesUsersManageTagsNoRoles, + storeWithTagPolicyPrivilegesUsersManageTagsRolesAdmin +} from './store.conf' +import { cloneDeep } from 'lodash' + +config.mocks["$t"] = (key) => key + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(Element) + +jest.mock('@/api/app') +jest.mock('@/api/nodeInfo') +jest.mock('@/api/users') +jest.mock('@/api/settings') + +describe('The Multiple Users Moderation Menu', () => { + it('doesnt show for someone with no privileges and no roles', async (done) => { + const store = new Vuex.Store(cloneDeep(storeNoPrivilegesNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + + expect(wrapper.find('.multiple-users-menu').exists()).toEqual(false) + done() + }) + + it('shows for someone with admin role and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithTagPolicyNoPrivilegesRolesAdmin)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + await wrapper.find('.el-table').findAll('input').at(1).setChecked() + const menu = wrapper.find('.multiple-users-menu') + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual([ + 'users.confirmAccounts', + 'users.grantAdmin', + 'users.grantModerator', + 'users.requirePasswordReset', + 'users.resendConfirmation', + 'users.revokeAdmin', + 'users.revokeModerator' + ]) + + store.state.users.mrfPolicies = [] + await flushPromises() + + expect(menu_items.length).toEqual(7) + done() + }) + + it('shows for someone with users_manage_invites privilege and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithPrivilegesUsersManageInvitesNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + await wrapper.find('.el-table').findAll('input').at(1).setChecked() + const menu = wrapper.find('.multiple-users-menu') + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()) + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual(['users.approveAccounts']) + done() + }) + + it('shows for someone with users_delete privilege and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithPrivilegesUsersDeleteNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + await wrapper.find('.el-table').findAll('input').at(1).setChecked() + const menu = wrapper.find('.multiple-users-menu') + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual(['users.deleteAccounts', 'users.rejectAccounts']) + done() + }) + + it('shows for someone with users_manage_activation_state privilege and shows proper entries', async (done) => { + const store = new Vuex.Store(cloneDeep(storeWithPrivilegesUsersManageActivationStateNoRoles)) + const wrapper = mount(Users, { + store, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + await flushPromises() + await wrapper.find('.el-table').findAll('input').at(1).setChecked() + const menu = wrapper.find('.multiple-users-menu') + const menu_items = menu.findAll('.el-dropdown-menu__item') + const menu_items_text = menu_items.wrappers.map(menu_item => menu_item.text()).sort() + + expect(menu.isVisible()).toEqual(true) + expect(menu_items_text).toEqual(['users.activateAccounts', 'users.deactivateAccounts']) + done() + }) + + it('shows for someone with users_manage_tags privilege and shows proper entries for non-admin/admin and with/without TagPolicy enabled', async (done) => { + const store_no_admin = new Vuex.Store(cloneDeep(storeWithTagPolicyPrivilegesUsersManageTagsNoRoles)) + const wrapper_no_admin = mount(Users, { + store: store_no_admin, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + const store_admin = new Vuex.Store(cloneDeep(storeWithTagPolicyPrivilegesUsersManageTagsRolesAdmin)) + const wrapper_admin = mount(Users, { + store: store_admin, + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + + await flushPromises() + await wrapper_no_admin.find('.el-table').findAll('input').at(1).setChecked() + await wrapper_admin.find('.el-table').findAll('input').at(1).setChecked() + const menu_no_admin = wrapper_no_admin.find('.multiple-users-menu') + const menu_admin = wrapper_admin.find('.multiple-users-menu') + const menu_no_admin_items = menu_no_admin.findAll('.el-dropdown-menu__item') + const menu_admin_items = menu_admin.findAll('.el-dropdown-menu__item') + const filter = menu_item => { return menu_item.find('.tag-container .tag-text').exists() && menu_item.find('.tag-container .tag-text').text() } + const menu_no_admin_items_text = menu_no_admin_items.wrappers.map(filter).sort() + const menu_admin_items_text = menu_admin_items.wrappers.map(filter) + + expect(menu_no_admin.isVisible()).toEqual(true) + expect(menu_no_admin_items_text).toEqual([ + 'users.disableAnySubscriptionForMultiple', + 'users.disableRemoteSubscriptionForMultiple', + 'users.forceNsfw', + 'users.forceUnlisted', + 'users.sandbox', + 'users.stripMedia' + ]) + + expect(menu_admin.isVisible()).toEqual(true) + expect(menu_admin_items_text.includes('users.forceNsfw')).toBe(true) + expect(menu_admin_items_text.includes('users.stripMedia')).toBe(true) + expect(menu_admin_items_text.includes('users.forceUnlisted')).toBe(true) + expect(menu_admin_items_text.includes('users.sandbox')).toBe(true) + expect(menu_admin_items_text.includes('users.disableRemoteSubscriptionForMultiple')).toBe(true) + expect(menu_admin_items_text.includes('users.disableAnySubscriptionForMultiple')).toBe(true) + + store_no_admin.state.users.mrfPolicies = [] + store_admin.state.users.mrfPolicies = [] + await flushPromises() + + // Only an admin with users_manage_tags privilege can enable TagPolicy from the menu + expect(menu_no_admin.findAll('.el-dropdown-menu__item').length).toEqual(0) + expect(menu_admin.findAll('.el-dropdown-menu__item').wrappers.map(wrapper => wrapper.text()).includes('users.enableTagPolicy')).toBe(true) + done() + }) +}) diff --git a/test/views/users/components/store.conf.js b/test/views/users/components/store.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..1a084353bcd37bd689e655e20d261be008bbfcbc --- /dev/null +++ b/test/views/users/components/store.conf.js @@ -0,0 +1,150 @@ +import app from '@/store/modules/app' +import settings from '@/store/modules/settings' +import user from '@/store/modules/user' +import userProfile from '@/store/modules/userProfile' +import users from '@/store/modules/users' +import getters from '@/store/getters' + +export const storeNoPrivilegesNoRoles = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: [], + privileges: [] + } + }, + userProfile, + users + }, + getters +} + +export const storeWithTagPolicyNoPrivilegesRolesAdmin = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: ['admin'], + privileges: [] + } + }, + userProfile, + users: { ...users, state: { ...users.state, mrfPolicies: ['Pleroma.Web.ActivityPub.MRF.TagPolicy'] }} + }, + getters +} + +export const storeWithPrivilegesUsersManageInvitesNoRoles = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: [], + privileges: ['users_manage_invites'] + } + }, + userProfile, + users + }, + getters +} + +export const storeWithPrivilegesUsersDeleteNoRoles = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: [], + privileges: ['users_delete'] + } + }, + userProfile, + users + }, + getters +} + +export const storeWithPrivilegesUsersManageActivationStateNoRoles = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: [], + privileges: ['users_manage_activation_state'] + } + }, + userProfile, + users + }, + getters +} + +export const storeWithTagPolicyPrivilegesUsersManageTagsNoRoles = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: [], + privileges: ['users_manage_tags'] + } + }, + userProfile, + users: { ...users, state: { ...users.state, mrfPolicies: ['Pleroma.Web.ActivityPub.MRF.TagPolicy'] }} + }, + getters +} + +export const storeWithTagPolicyPrivilegesUsersManageTagsRolesAdmin = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: ['admin'], + privileges: ['users_manage_tags'] + } + }, + userProfile, + users: { ...users, state: { ...users.state, mrfPolicies: ['Pleroma.Web.ActivityPub.MRF.TagPolicy'] }} + }, + getters +} + +export const storeWithPrivilegesUsersManageCredentialsNoRoles = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: [], + privileges: ['users_manage_credentials'] + } + }, + userProfile, + users + }, + getters +} diff --git a/test/views/users/index.test.js b/test/views/users/index.test.js index 8c486cdf432e9cf0bbbfa071239a9f4894624475..195282998587a7a501f5cdee14da46b560f2616c 100644 --- a/test/views/users/index.test.js +++ b/test/views/users/index.test.js @@ -4,7 +4,11 @@ import flushPromises from 'flush-promises' import Element from 'element-ui' import Users from '@/views/users/index' import NewAccountDialog from '@/views/users/components/NewAccountDialog' -import { storeConfig } from './store.conf' +import { + storeConfig, + storeWithRoleAdminNoPrivileges, + storeWithNoRolesNoPrivileges +} from './store.conf' import { cloneDeep } from 'lodash' config.mocks["$t"] = () => {} @@ -82,6 +86,32 @@ describe('Users actions', () => { store = new Vuex.Store(cloneDeep(storeConfig)) }) + it('doesnt show create new account button if not privileged', async (done) => { + const wrapper_admin = mount(Users, { + store: new Vuex.Store(cloneDeep(storeWithRoleAdminNoPrivileges)), + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + + const wrapper_no_admin = mount(Users, { + store: new Vuex.Store(cloneDeep(storeWithNoRolesNoPrivileges)), + localVue, + sync: false, + stubs: { + RouterLink: RouterLinkStub + } + }) + + await flushPromises() + expect(wrapper_admin.find('.actions-button').isVisible()).toBe(true) + expect(wrapper_no_admin.find('.actions-button').exists()).toBe(false) + + done() + }) + it('grants admin right to a local user', async (done) => { const wrapper = mount(Users, { store, diff --git a/test/views/users/store.conf.js b/test/views/users/store.conf.js index 7edf286535d4c5156bc9defafb8b447bac9d35cc..6a7736ceced02d8e66f9485027234d27d8d24b3a 100644 --- a/test/views/users/store.conf.js +++ b/test/views/users/store.conf.js @@ -9,7 +9,14 @@ export const storeConfig = { modules: { app, settings, - user, + user: { + ...user, + state: { + ...user.state, + roles: ['admin'], + privileges: ['users_manage_activation_state', 'users_delete', 'users_manage_tags', 'users_manage_credentials'] + } + }, userProfile, users }, @@ -20,9 +27,59 @@ export const storeWithTagPolicy = { modules: { app, settings, - user, + user: { + ...user, + state: { + ...user.state, + roles: ['admin'], + privileges: ['users_manage_activation_state', 'users_delete', 'users_manage_tags', 'users_manage_credentials'] + } + }, userProfile, - users: { ...users, state: { ...users.state, mrfPolicies: ['Pleroma.Web.ActivityPub.MRF.TagPolicy'] }} + users: { + ...users, + state: { + ...users.state, + mrfPolicies: ['Pleroma.Web.ActivityPub.MRF.TagPolicy'] + } + } + }, + getters +} + +export const storeWithRoleAdminNoPrivileges = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: ['admin'], + privileges: [] + } + }, + userProfile, + users + }, + getters +} + + +export const storeWithNoRolesNoPrivileges = { + modules: { + app, + settings, + user: { + ...user, + state: { + ...user.state, + roles: [], + privileges: [] + } + }, + userProfile, + users }, getters }