Various fixes about Reseller mode in frontend.

see #3268
This commit is contained in:
Antoine Nguyen
2025-07-14 16:03:57 +02:00
parent c28e42ec77
commit e306b0799a
24 changed files with 237 additions and 120 deletions
+6 -1
View File
@@ -4,7 +4,12 @@ const resource = 'accounts'
export default {
getAll({ domain, role }) {
return repository.get(`/${resource}/`, { params: { domain, role } })
return repository.get(`/${resource}/`, {
params: { domain, role },
paramsSerializer: {
indexes: null,
},
})
},
get(accountId) {
return repository.get(`${resource}/${accountId}/`)
@@ -80,6 +80,7 @@
<script setup>
import accountApi from '@/api/accounts'
import domainApi from '@/api/domains'
import constants from '@/constants.json'
import { useBusStore } from '@/stores'
import { useGettext } from 'vue3-gettext'
import { ref, onMounted } from 'vue'
@@ -128,13 +129,15 @@ function fetchAdministrators(domain) {
}
function fetchAccounts() {
accountApi.getAll({ role: 'DomainAdmins' }).then((resp) => {
accounts.value = resp.data.filter(
(account) =>
administrators.value.find((admin) => admin.id === account.pk) ===
undefined
)
})
accountApi
.getAll({ role: [constants.RESELLER, constants.DOMAIN_ADMIN] })
.then((resp) => {
accounts.value = resp.data.filter(
(account) =>
administrators.value.find((admin) => admin.id === account.pk) ===
undefined
)
})
}
function addAdministrator() {
domainApi
@@ -138,7 +138,8 @@
v-if="
limitsConfig.params &&
limitsConfig.params.enable_domain_limits &&
(authUser.role === 'SuperAdmins' || authUser.role === 'Resellers')
(authUser.role === constants.SUPER_ADMIN ||
authUser.role === constants.RESELLER)
"
value="resourcesForm"
eager
@@ -184,6 +185,7 @@ import { useGettext } from 'vue3-gettext'
import { useAuthStore } from '@/stores'
import { useRouter } from 'vue-router'
import domainsApi from '@/api/domains'
import constants from '@/constants.json'
const router = useRouter()
const { $gettext } = useGettext()
@@ -219,8 +221,8 @@ const formMap = computed(() => {
if (
limitsConfig.value.params &&
limitsConfig.value.params.enable_domain_limits &&
(authUser.value.role === 'SuperAdmins' ||
authUser.value.role === 'Resellers')
(authUser.value.role === constants.SUPER_ADMIN ||
authUser.value.role === constants.RESELLER)
) {
map.resourcesForm = resourcesForm
}
@@ -68,6 +68,7 @@ import AccountRoleForm from './form_steps/AccountRoleForm.vue'
import AccountGeneralForm from './form_steps/AccountGeneralForm.vue'
import AccountMailboxForm from './form_steps/AccountMailboxForm.vue'
import AccountAliasForm from './form_steps/AccountAliasForm.vue'
import constants from '@/constants.json'
import { useGettext } from 'vue3-gettext'
import { ref, computed } from 'vue'
@@ -81,10 +82,7 @@ const domainsStore = useDomainsStore()
const emit = defineEmits(['close', 'created'])
const needsMailbox = computed(
() =>
account.value.role !== 'SuperAdmins' &&
account.value.username &&
account.value.username.indexOf('@') !== -1
() => account.value.username && account.value.username.indexOf('@') !== -1
)
const summarySections = computed(() => {
@@ -187,7 +185,7 @@ const steps = computed(() => {
const account = ref({
aliases: [],
is_active: true,
role: 'SimpleUsers',
role: constants.USER,
mailbox: {
use_domain_quota: true,
},
@@ -234,19 +232,33 @@ function setFormErrors(step, errors) {
function preparePayload(payload) {
const cleaned_payload = { ...payload }
if (
cleaned_payload.role === 'SuperAdmins' &&
[constants.RESELLER, constants.SUPER_ADMIN].includes(
cleaned_payload.role
) &&
cleaned_payload.username &&
cleaned_payload.username.indexOf('@') === -1
) {
delete cleaned_payload.mailbox
}
if (cleaned_payload.mailbox && cleaned_payload.mailbox.message_limit === '') {
cleaned_payload.mailbox.message_limit = null
if (cleaned_payload.mailbox) {
if (cleaned_payload.mailbox.message_limit === '') {
cleaned_payload.mailbox.message_limit = null
}
if (
cleaned_payload.mailbox.quota === undefined ||
cleaned_payload.mailbox.quota === null
) {
cleaned_payload.mailbox.quota = 0
}
}
return cleaned_payload
}
function validateAccount() {
function validateAccount(step) {
if (step === 1) {
// No validation after role selection
return true
}
const payload = preparePayload({ ...account.value })
return accountsApi.validate(payload)
}
@@ -209,6 +209,7 @@ import { ref, computed, onMounted, watch } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useRoute, useRouter } from 'vue-router'
import { usePermissions } from '@/composables/permissions'
import constants from '@/constants.json'
const { $gettext } = useGettext()
const authStore = useAuthStore()
@@ -229,8 +230,8 @@ const needsResources = computed(() => {
const isNeeded =
limitsConfig.value.params &&
limitsConfig.value.params.enable_admin_limits &&
editedAccount.value.role !== 'SimpleUsers' &&
editedAccount.value.role !== 'SuperAdmins'
editedAccount.value.role !== constants.USER &&
editedAccount.value.role !== constants.SUPER_ADMIN
if (isNeeded === undefined) {
return false
}
@@ -285,7 +286,7 @@ async function save() {
const data = { ...editedAccount.value }
if (usernameIsEmail.value) {
data.mailbox.full_address = data.username
} else {
} else if (data.mailbox) {
delete data.mailbox.full_address
}
if (data.new_password) {
@@ -303,7 +304,7 @@ async function save() {
if (editedAccount.value.pk === authStore.authUser.pk) {
data.role = authStore.authUser.role
} else {
data.role = 'SimpleUsers'
data.role = constants.USER
}
}
await accountsApi.patch(editedAccount.value.pk, data)
@@ -50,7 +50,7 @@
<v-col v-else cols="6">
<v-progress-linear
:color="account.mailbox.quota_usage < 80 ? 'primary' : 'warning'"
:value="account.mailbox.quota_usage"
:model-value="account.mailbox.quota_usage"
height="25"
class="white--text"
>
@@ -57,6 +57,7 @@ import { ref, computed } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useAuthStore } from '@/stores'
import rules from '@/plugins/rules'
import constants from '@/constants.json'
const { $gettext } = useGettext()
const authStore = useAuthStore()
@@ -72,21 +73,18 @@ const formErrors = ref({})
const account = computed(() => props.modelValue)
const usernamePlaceholder = computed(() => {
if (account.value.role === 'SimpleUsers') {
if (account.value.role === constants.USER) {
return $gettext('Enter an email address')
}
return $gettext('Enter a simple username or an email address')
})
const usernameInputType = computed(() => {
return account.value.role === 'SimpleUsers' ? 'email' : 'text'
return account.value.role === constants.USER ? 'email' : 'text'
})
function updateUsername() {
if (
account.value.role != 'SuperAdmins' &&
account.value.username.indexOf('@') !== -1
) {
if (account.value.username.indexOf('@') !== -1) {
account.value.mailbox.full_address = account.value.username
account.value.mailbox.message_limit = null
}
@@ -44,11 +44,12 @@
</v-form>
</template>
<script setup lang="js">
<script setup>
import { ref, computed, watch } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useDomainsStore } from '@/stores'
import rules from '@/plugins/rules'
import constants from '@/constants.json'
const { $gettext } = useGettext()
const emit = defineEmits(['update:modelValue'])
@@ -68,7 +69,7 @@ watch(
(value) => {
if (value) {
form.value = { ...value }
if (form.value.role === 'SimpleUsers') {
if (form.value.role === constants.USER) {
if (!form.value.mailbox) {
form.value.mailbox = {}
}
@@ -25,11 +25,12 @@
</v-form>
</template>
<script setup lang="js">
<script setup>
import ChoiceField from '@/components/tools/ChoiceField'
import { computed, ref } from 'vue'
import { useAuthStore } from '@/stores'
import { useGettext } from 'vue3-gettext'
import constants from '@/constants.json'
const authStore = useAuthStore()
const { $gettext } = useGettext()
@@ -59,16 +60,16 @@ const roleHelp = computed(() => {
})
const accountRoles = computed(() => {
if (authUser.value.role === 'SuperAdmins') {
if (authUser.value.role === constants.SUPER_ADMIN) {
return [
...simpleUserRole,
...domainAdminsRole,
...resellerRole,
...simpleUserRole,
...superAdminsRole,
]
} else if (authUser.value.role === 'DomainAdmins') {
} else if (authUser.value.role === constants.DOMAIN_ADMIN) {
return [...simpleUserRole]
} else if (authUser.value.role === 'Resellers') {
} else if (authUser.value.role === constants.RESELLER) {
return [...domainAdminsRole, ...simpleUserRole]
} else {
return []
@@ -78,7 +79,7 @@ const accountRoles = computed(() => {
const simpleUserRole = [
{
label: $gettext('Simple user'),
value: 'SimpleUsers',
value: constants.USER,
help: $gettext(
'A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.'
),
@@ -88,7 +89,7 @@ const simpleUserRole = [
const domainAdminsRole = [
{
label: $gettext('Domain administrator'),
value: 'DomainAdmins',
value: constants.DOMAIN_ADMIN,
help: $gettext(
'A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.'
),
@@ -97,14 +98,16 @@ const domainAdminsRole = [
const resellerRole = [
{
label: $gettext('Reseller'),
value: 'Resellers',
help: $gettext('help'),
value: constants.RESELLER,
help: $gettext(
'An intermediate user who has the same privileges than a Domain administrator, plus the possibility to create domains and to assign resources.'
),
},
]
const superAdminsRole = [
{
label: $gettext('Super administrator'),
value: 'SuperAdmins',
value: constants.SUPER_ADMIN,
help: $gettext(
"A user with all privileges, can do anything. By default, such a user does not have a mailbox so he can't access end-user features."
),
+2 -1
View File
@@ -94,6 +94,7 @@
import { useRouter } from 'vue-router'
import { ref, computed, onMounted } from 'vue'
import { useGlobalStore, useAuthStore, useLayoutStore } from '@/stores'
import constants from '@/constants.json'
const props = defineProps({
color: {
@@ -141,7 +142,7 @@ function displayMenuItem(item) {
}
onMounted(() => {
if (authUser.value.role === 'SuperAdmins') {
if (authUser.value.role === constants.SUPER_ADMIN) {
globalStore.fetchNotifications()
}
})
@@ -8,7 +8,7 @@
<div
v-for="(lineChoices, index1) in formatedChoices"
:key="index1"
class="d-flex"
class="d-flex flex-grow-0"
>
<div
v-for="(choice, index2) in lineChoices"
@@ -150,7 +150,7 @@ async function goToNextStep(current, next) {
}
if (props.validateObject) {
try {
await props.validateObject()
await props.validateObject(current)
} catch (error) {
emit('validationError', current, error.response.data)
return
+7 -16
View File
@@ -25,17 +25,16 @@
</div>
</template>
<script setup lang="js">
<script setup>
import { ref, computed, onMounted, onUnmounted, onUpdated, nextTick } from 'vue'
import { useDomainsStore } from '@/stores'
import { useGettext } from 'vue3-gettext'
import constants from '@/constants.json'
const { $gettext } = useGettext()
const domainsStore = useDomainsStore()
const props = defineProps({
modelValue: { type: String, default: '' },
allowAdd: { type: Boolean, default: false },
role: { type: String, default: 'SimpleUsers' },
role: { type: String, default: constants.USER },
errorMsg: { type: Array, default: () => [] },
})
const emit = defineEmits(['domain-selected', 'update:model-value'])
@@ -47,15 +46,11 @@ const input = computed({
set(value) {
errors.value = []
if (value.indexOf('@') !== -1) {
if (props.role !== 'SuperAdmins') {
domainSearch.value = value.split('@')[1]
if (props.allowAdd) {
showMenu.value = filteredDomains.value.length > 0
} else {
showMenu.value = true
}
domainSearch.value = value.split('@')[1]
if (props.allowAdd) {
showMenu.value = filteredDomains.value.length > 0
} else {
errors.value = [$gettext('SuperAdmins cannot own a mailbox')]
showMenu.value = true
}
} else {
showMenu.value = false
@@ -87,10 +82,6 @@ const inputRef = ref()
const localErrors = ref([])
function onKeyDown(e) {
if (props.role === 'SuperAdmins') {
// No mailbox for SuperAdmins
return
}
const keyCode = e.keyCode
if (keyCode === 40 || keyCode === 34) {
// on arrow down or page down
@@ -5,21 +5,40 @@
</v-card-title>
<v-card-text>
<div v-for="resource in resources" :key="resource.name" class="mt-4">
{{ resource.label }} ({{ Math.ceil(resource.usage) }}%)
<v-progress-linear
:value="resource.usage"
:color="getProgressColor(resource.usage)"
/>
<template v-if="resource.max_value === -1">
{{ resource.label }} ({{ $gettext('Unlimited') }})
</template>
<template v-else-if="resource.max_value === 0">
{{ resource.label }} ({{ noLimitLabel(resource) }})
</template>
<template v-else>
{{ resource.label }} ({{ Math.ceil(resource.usage) }}%)
<v-progress-linear
:model-value="resource.usage"
:color="getProgressColor(resource.usage)"
/>
</template>
</div>
</v-card-text>
</v-card>
</template>
<script setup lang="js">
<script setup>
import { useGettext } from 'vue3-gettext'
defineProps({
resources: { type: Array, default: null },
})
const { $gettext } = useGettext()
const noLimitLabel = (resource) => {
if (resource.name === 'quota') {
return $gettext('No quota')
}
return $gettext('Creation denied')
}
function getProgressColor(value) {
if (value < 50) {
return 'primary'
+7 -2
View File
@@ -1,15 +1,20 @@
import { computed } from 'vue'
import { useAuthStore } from '@/stores'
import constants from '@/constants.json'
export function usePermissions() {
const authStore = useAuthStore()
const canSetRole = computed(() => {
return ['Resellers', 'SuperAdmins'].includes(authStore.authUser.role)
return [constants.RESELLER, constants.SUPER_ADMIN].includes(
authStore.authUser.role
)
})
const canAddDomain = computed(() => {
return ['Resellers', 'SuperAdmins'].includes(authStore.authUser.role)
return [constants.RESELLER, constants.SUPER_ADMIN].includes(
authStore.authUser.role
)
})
return {
+6 -2
View File
@@ -1,4 +1,8 @@
{
"ALARM_OPENED": 1,
"ALARM_CLOSED": 2
"ALARM_OPENED": 1,
"ALARM_CLOSED": 2,
"SUPER_ADMIN": "SuperAdmins",
"RESELLER": "Resellers",
"DOMAIN_ADMIN": "DomainAdmins",
"USER": "SimpleUsers"
}
+18 -13
View File
@@ -9,6 +9,7 @@ import { useAuthStore, useParametersStore } from '@/stores'
import parametersApi from '@/api/parameters'
import capabilitiesApi from '@/api/capabilities'
import ConnectedLayout from '@/layouts/connected/ConnectedLayout.vue'
import constants from '@/constants.json'
const { $gettext } = useGettext()
const authStore = useAuthStore()
@@ -38,13 +39,13 @@ const adminMenuItems = [
text: $gettext('Domains'),
to: { name: 'DomainList' },
icon: 'mdi-domain',
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
roles: [constants.DOMAIN_ADMIN, constants.RESELLER, constants.SUPER_ADMIN],
},
{
text: $gettext('Identities'),
to: { name: 'Identities' },
icon: 'mdi-account',
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
roles: [constants.DOMAIN_ADMIN, constants.RESELLER, constants.SUPER_ADMIN],
},
{
text: $gettext('Alarms'),
@@ -54,40 +55,44 @@ const adminMenuItems = [
{
icon: 'mdi-history',
text: $gettext('Monitoring'),
roles: ['SuperAdmins', 'Resellers', 'DomainAdmins'],
roles: [constants.SUPER_ADMIN, constants.RESELLER, constants.DOMAIN_ADMIN],
children: [
{
text: $gettext('Statistics'),
to: { name: 'Statistics' },
roles: ['SuperAdmins'],
roles: [constants.SUPER_ADMIN],
},
{
text: $gettext('Audit trail'),
to: { name: 'AuditTrail' },
roles: ['SuperAdmins'],
roles: [constants.SUPER_ADMIN],
},
{
text: $gettext('Messages'),
to: { name: 'MessageLog' },
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
roles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
],
},
{
icon: 'mdi-email-sync-outline',
text: $gettext('IMAP Migration'),
roles: ['SuperAdmins', 'Resellers'],
roles: [constants.SUPER_ADMIN, constants.RESELLER],
condition: () => imapMigrationEnabled.value,
children: [
{
text: $gettext('Email providers'),
to: { name: 'ProvidersList' },
roles: ['SuperAdmins', 'Resellers'],
roles: [constants.SUPER_ADMIN, constants.RESELLER],
},
{
text: $gettext('Migrations'),
to: { name: 'MigrationsList' },
roles: ['Resellers', 'SuperAdmins'],
roles: [constants.RESELLER, constants.SUPER_ADMIN],
},
],
},
@@ -95,26 +100,26 @@ const adminMenuItems = [
icon: 'mdi-cog',
text: $gettext('Settings'),
children: settings,
roles: ['SuperAdmins'],
roles: [constants.SUPER_ADMIN],
},
{
icon: 'mdi-email-check-outline',
text: 'Rspamd',
action: openRspamdDashboard,
roles: ['SuperAdmins'],
roles: [constants.SUPER_ADMIN],
condition: () => isRspamdVisible.value,
},
{
icon: 'mdi-information',
text: $gettext('Information'),
roles: ['SuperAdmins'],
roles: [constants.SUPER_ADMIN],
to: { name: 'Information' },
withBell: true,
},
]
onMounted(() => {
if (authUser.value.role === 'SuperAdmins') {
if (authUser.value.role === constants.SUPER_ADMIN) {
parametersApi.getGlobalApplications().then((response) => {
response.data.forEach((item) => {
settings.push({
+3 -2
View File
@@ -1,4 +1,5 @@
import { useAuthStore } from '@/stores'
import constants from '@/constants.json'
export default {
install: (app) => {
@@ -8,7 +9,7 @@ export default {
const permission = binding.value
if (authStore.authUser) {
if (
authUser.authUser.role !== 'SuperAdmins' &&
authUser.authUser.role !== constants.SUPER_ADMIN &&
authStore.authUser.permissions.indexOf(permission) === -1
) {
el.style.display = 'none'
@@ -21,7 +22,7 @@ export default {
return false
}
if (
authStore.authUser.role === 'SuperAdmins' ||
authStore.authUser.role === constants.SUPER_ADMIN ||
authStore.authUser.permissions.indexOf(permission) !== -1
) {
return true
+70 -22
View File
@@ -1,6 +1,6 @@
// Composables
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores'
import constants from '@/constants.json'
const routes = [
{
@@ -26,7 +26,11 @@ const routes = [
path: '/admin',
component: () => import('@/layouts/admin/AdminLayout.vue'),
meta: {
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
children: [
{
@@ -42,7 +46,11 @@ const routes = [
component: () => import('@/layouts/empty/EmptyLayout.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
children: [
{
@@ -51,7 +59,11 @@ const routes = [
component: () => import('@/views/admin/domains/DomainsView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
},
{
@@ -59,7 +71,11 @@ const routes = [
name: 'DomainDetail',
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
component: () => import('@/views/admin/domains/DomainView.vue'),
},
@@ -68,7 +84,11 @@ const routes = [
name: 'DomainEdit',
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
component: () => import('@/views/admin/domains/DomainEditView.vue'),
},
@@ -79,7 +99,11 @@ const routes = [
component: () => import('@/layouts/empty/EmptyLayout.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
children: [
{
@@ -89,7 +113,7 @@ const routes = [
import('@/views/admin/imap_migration/MigrationsView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['Resellers', 'SuperAdmins'],
allowedRoles: [constants.RESELLER, constants.SUPER_ADMIN],
},
},
{
@@ -97,7 +121,7 @@ const routes = [
component: () => import('@/layouts/empty/EmptyLayout.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['Resellers', 'SuperAdmins'],
allowedRoles: [constants.RESELLER, constants.SUPER_ADMIN],
},
children: [
{
@@ -107,7 +131,7 @@ const routes = [
import('@/views/admin/imap_migration/ProvidersView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['Resellers', 'SuperAdmins'],
allowedRoles: [constants.RESELLER, constants.SUPER_ADMIN],
},
},
{
@@ -117,7 +141,7 @@ const routes = [
import('@/views/admin/imap_migration/ProviderEditView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['Resellers', 'SuperAdmins'],
allowedRoles: [constants.RESELLER, constants.SUPER_ADMIN],
},
},
],
@@ -129,7 +153,11 @@ const routes = [
component: () => import('@/layouts/empty/EmptyLayout.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
children: [
{
@@ -139,7 +167,11 @@ const routes = [
import('@/views/admin/identities/IdentitiesView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
},
{
@@ -148,7 +180,11 @@ const routes = [
component: () => import('@/views/admin/identities/AccountView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
},
{
@@ -158,7 +194,11 @@ const routes = [
import('@/views/admin/identities/AccountEditView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
},
{
@@ -167,7 +207,11 @@ const routes = [
component: () => import('@/views/admin/identities/AliasView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
},
{
@@ -177,7 +221,11 @@ const routes = [
import('@/views/admin/identities/AliasEditView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
allowedRoles: [
constants.DOMAIN_ADMIN,
constants.RESELLER,
constants.SUPER_ADMIN,
],
},
},
],
@@ -188,7 +236,7 @@ const routes = [
component: () => import('@/views/admin/ParametersView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['SuperAdmins'],
allowedRoles: [constants.SUPER_ADMIN],
},
},
{
@@ -205,7 +253,7 @@ const routes = [
component: () => import('@/views/admin/monitoring/StatisticsView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['SuperAdmins'],
allowedRoles: [constants.SUPER_ADMIN],
},
},
{
@@ -214,7 +262,7 @@ const routes = [
component: () => import('@/views/admin/monitoring/AuditTrailView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['SuperAdmins'],
allowedRoles: [constants.SUPER_ADMIN],
},
},
{
@@ -223,7 +271,7 @@ const routes = [
component: () => import('@/views/admin/monitoring/MessagesView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['DomainAdmins', 'SuperAdmins'],
allowedRoles: [constants.DOMAIN_ADMIN, constants.SUPER_ADMIN],
},
},
{
@@ -232,7 +280,7 @@ const routes = [
component: () => import('@/views/admin/InformationView.vue'),
meta: {
requiresAuth: true,
allowedRoles: ['SuperAdmins'],
allowedRoles: [constants.SUPER_ADMIN],
},
},
],
+6 -2
View File
@@ -16,7 +16,10 @@
<v-tab v-if="authStore.userHasMailbox" value="autoreply">
{{ $gettext('Auto-reply message') }}
</v-tab>
<v-tab v-if="authStore.authUser.role === 'SuperAdmins'" value="api">
<v-tab
v-if="authStore.authUser.role === constants.SUPER_ADMIN"
value="api"
>
{{ $gettext('API access') }}
</v-tab>
</v-tabs>
@@ -36,7 +39,7 @@
<AutoReplyForm />
</v-tabs-window-item>
<v-tabs-window-item
v-if="authStore.authUser.role === 'SuperAdmins'"
v-if="authStore.authUser.role === constants.SUPER_ADMIN"
value="api"
>
<APISetupForm />
@@ -55,6 +58,7 @@ import AutoReplyForm from '@/components/account/AutoReplyForm.vue'
import APISetupForm from '@/components/account/APISetupForm.vue'
import FidoAuthForm from '@/components/account/FidoAuthForm.vue'
import { useAuthStore } from '@/stores'
import constants from '@/constants.json'
const authStore = useAuthStore()
const tab = ref()
@@ -14,7 +14,7 @@
<v-col cols="6">
<AccountSummary :account="account" />
<DomainAdminDomains
v-if="account.role === 'DomainAdmins'"
v-if="account.role === constants.DOMAIN_ADMIN"
class="mt-2"
:account="account"
/>
@@ -24,8 +24,8 @@
v-if="
limitsConfig.params &&
limitsConfig.params.enable_admin_limits &&
account.role !== 'SimpleUsers' &&
account.role !== 'SuperAdmins'
account.role !== constants.USER &&
account.role !== constants.SUPER_ADMIN
"
:resources="account.resources"
/>
@@ -40,7 +40,7 @@
</template>
<script setup lang="js">
import { ref, computed } from 'vue'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { usePermissions } from '@/composables/permissions'
import accountsApi from '@/api/accounts'
@@ -50,6 +50,7 @@ import AccountSenderAddresses from '@/components/admin/identities/AccountSenderA
import AccountSummary from '@/components/admin/identities/AccountSummary.vue'
import DomainAdminDomains from '@/components/admin/identities/DomainAdminDomains.vue'
import ResourcesView from '@/components/tools/ResourcesView.vue'
import constants from '@/constants.json'
const route = useRoute()
const { canSetRole } = usePermissions()
+5 -3
View File
@@ -19,7 +19,7 @@ from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from modoboa.admin.api.v1 import viewsets as v1_viewsets
from modoboa.core import models as core_models
from modoboa.core import constants as core_constants, models as core_models
from modoboa.lib import pagination
from modoboa.lib import renderers as lib_renderers
from modoboa.lib import viewsets as lib_viewsets
@@ -153,14 +153,16 @@ class AccountFilterSet(dj_filters.FilterSet):
queryset=lambda request: models.Domain.objects.get_for_admin(request.user),
field_name="mailbox__domain",
)
role = dj_filters.CharFilter(method="filter_role")
role = dj_filters.MultipleChoiceFilter(
choices=core_constants.ROLES, method="filter_role"
)
class Meta:
model = core_models.User
fields = ["domain", "role"]
def filter_role(self, queryset, name, value):
return queryset.filter(groups__name=value)
return queryset.filter(groups__name__in=value)
class AccountViewSet(v1_viewsets.AccountViewSet):
+7
View File
@@ -152,3 +152,10 @@ class IsSuperUser(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.is_superuser
class CanCreateDomain(permissions.BasePermission):
"""Permissions class to allow users with add_domain right."""
def has_permission(self, request, view):
return request.user.has_perm("admin.add_domain")
+6 -2
View File
@@ -4,7 +4,7 @@ from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework import permissions, response, viewsets
from rest_framework.decorators import action
from modoboa.lib.permissions import IsSuperUser
from modoboa.lib.permissions import CanCreateDomain, IsSuperUser
from modoboa.lib.throttle import GetThrottleViewsetMixin
from . import serializers
@@ -90,7 +90,11 @@ class GlobalParametersViewSet(BaseParametersViewSet):
"""Parameter viewset, global level."""
level = "global"
permission_classes = [permissions.IsAuthenticated, IsSuperUser]
def get_permissions(self):
if self.action == "retrieve":
return [permissions.IsAuthenticated(), CanCreateDomain()]
return [permissions.IsAuthenticated(), IsSuperUser()]
def _get_parameter_values(self, request, app: str) -> dict:
return request.localconfig.parameters.get_values_dict(app)