mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Various frontend fixes when connected user is a domain admin
This commit is contained in:
@@ -38,6 +38,7 @@ modoboa.kdev4
|
||||
test_project/cert.crt
|
||||
test_project/cert.key
|
||||
test_project/envfile
|
||||
test_project/media
|
||||
|
||||
# Docker
|
||||
docker/collections
|
||||
|
||||
@@ -9,6 +9,11 @@ export default {
|
||||
checkPassword(value) {
|
||||
return repository.post(`/${resource}/me/password/`, { password: value })
|
||||
},
|
||||
setPassword(data) {
|
||||
return repository.post(`/${resource}/me/password/change/`, data, {
|
||||
ignoreErrors: true,
|
||||
})
|
||||
},
|
||||
verifyTFACode(code) {
|
||||
const payload = { code }
|
||||
return repository.post(`${resource}/tfa/verify/`, payload)
|
||||
|
||||
@@ -36,9 +36,16 @@ _axios.interceptors.response.use(
|
||||
return Promise.reject(error)
|
||||
}
|
||||
if (error.response.status !== 401 || router.currentRoute.name === 'Login') {
|
||||
if (error.response.status !== 518) {
|
||||
if (
|
||||
error.response.status !== 518 &&
|
||||
error.config &&
|
||||
!error.config.ignoreErrors
|
||||
) {
|
||||
const busStore = useBusStore()
|
||||
busStore.displayNotification({ msg: error.response.data, type: 'error' })
|
||||
busStore.displayNotification({
|
||||
msg: error.response.data,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,11 @@
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<v-form ref="passwordForm" @submit.prevent="updatePassword">
|
||||
<AccountPasswordForm v-model="form" with-password-check />
|
||||
<AccountPasswordForm
|
||||
v-model="form"
|
||||
:form-errors="passwordFormErrors"
|
||||
with-password-check
|
||||
/>
|
||||
<v-btn color="success" type="submit" :loading="loadingPasswordUpdate">
|
||||
{{ $gettext('Update Password') }}
|
||||
</v-btn>
|
||||
@@ -97,6 +101,7 @@ const account = computed(() => authStore.authUser)
|
||||
const form = ref({})
|
||||
const profileForm = ref()
|
||||
const passwordForm = ref()
|
||||
const passwordFormErrors = ref({})
|
||||
const languages = ref([])
|
||||
const panel = ref(0)
|
||||
const loadingUpdateProfile = ref(false)
|
||||
@@ -136,12 +141,20 @@ async function updatePassword() {
|
||||
return
|
||||
}
|
||||
loadingPasswordUpdate.value = true
|
||||
authStore
|
||||
.updateAccount({ password: form.value.password })
|
||||
.then(() => {
|
||||
busStore.displayNotification({ msg: $gettext('Password updated') })
|
||||
try {
|
||||
await authStore.updatePassword({
|
||||
password: form.value.password,
|
||||
new_password: form.value.new_password,
|
||||
})
|
||||
.finally(() => (loadingPasswordUpdate.value = false))
|
||||
busStore.displayNotification({ msg: $gettext('Password updated') })
|
||||
passwordForm.value.reset()
|
||||
} catch (err) {
|
||||
if (err.status === 400) {
|
||||
passwordFormErrors.value = err.response.data
|
||||
}
|
||||
} finally {
|
||||
loadingPasswordUpdate.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
v-model="selectedAccount"
|
||||
:label="$gettext('Select an account')"
|
||||
:items="accounts"
|
||||
variant="outlined"
|
||||
item-title="username"
|
||||
class="mt-5"
|
||||
return-object
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<td>
|
||||
<v-progress-linear v-model="item.allocated_quota_in_percent" />
|
||||
</td>
|
||||
<td>
|
||||
<td v-if="canSetRole">
|
||||
<div class="text-right">
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ props }">
|
||||
@@ -181,9 +181,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { useBusStore, useDomainsStore } from '@/stores'
|
||||
import { useAuthStore, useBusStore, useDomainsStore } from '@/stores'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { usePermissions } from '@/composables/permissions'
|
||||
import DomainAdminList from './DomainAdminList.vue'
|
||||
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
|
||||
import DNSStatusChip from './DNSStatusChip.vue'
|
||||
@@ -195,6 +196,7 @@ import { computed, ref, onMounted } from 'vue'
|
||||
const { $gettext } = useGettext()
|
||||
const router = useRouter()
|
||||
|
||||
const { canSetRole } = usePermissions()
|
||||
const busStore = useBusStore()
|
||||
const domainStore = useDomainsStore()
|
||||
const domains = computed(() => domainStore.domains)
|
||||
@@ -211,13 +213,16 @@ const domainHeaders = [
|
||||
},
|
||||
{ title: $gettext('Sending limit'), key: 'message_limit' },
|
||||
{ title: $gettext('Quota'), key: 'allocated_quota_in_percent' },
|
||||
{
|
||||
]
|
||||
|
||||
if (canSetRole.value) {
|
||||
domainHeaders.push({
|
||||
title: $gettext('Actions'),
|
||||
value: 'actions',
|
||||
sortable: false,
|
||||
align: 'end',
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const confirm = ref()
|
||||
const keepDomainFolder = ref(false)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
:title="$gettext('Copy to clipboard')"
|
||||
@click="copyPassword"
|
||||
>
|
||||
{{ account.password }}
|
||||
{{ account.new_password }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -72,8 +72,10 @@ import AccountAliasForm from './form_steps/AccountAliasForm.vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useBusStore, useDomainsStore, useIdentitiesStore } from '@/stores'
|
||||
import { usePermissions } from '@/composables/permissions'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const { canSetRole } = usePermissions()
|
||||
const busStore = useBusStore()
|
||||
const domainsStore = useDomainsStore()
|
||||
const identitiesStore = useIdentitiesStore()
|
||||
@@ -157,16 +159,17 @@ const summarySections = computed(() => {
|
||||
})
|
||||
|
||||
const steps = computed(() => {
|
||||
const result = [
|
||||
{
|
||||
const result = []
|
||||
if (canSetRole.value) {
|
||||
result.push({
|
||||
name: 'role',
|
||||
title: $gettext('Role'),
|
||||
},
|
||||
{
|
||||
name: 'identification',
|
||||
title: $gettext('Identification'),
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
result.push({
|
||||
name: 'identification',
|
||||
title: $gettext('Identification'),
|
||||
})
|
||||
if (needsMailbox.value) {
|
||||
return result.concat([
|
||||
{
|
||||
@@ -206,7 +209,7 @@ const formStepsComponents = {
|
||||
}
|
||||
|
||||
function copyPassword() {
|
||||
navigator.clipboard.writeText(account.value.password).then(() => {
|
||||
navigator.clipboard.writeText(account.value.new_password).then(() => {
|
||||
busStore.displayNotification({
|
||||
msg: $gettext('Password copied to clipboard'),
|
||||
})
|
||||
@@ -254,6 +257,9 @@ function close() {
|
||||
|
||||
function submit() {
|
||||
const data = preparePayload({ ...account.value })
|
||||
data.password = data.new_password
|
||||
delete data.new_password
|
||||
delete data.password_confirmation
|
||||
identitiesStore.createIdentity('account', data).then(() => {
|
||||
emit('created')
|
||||
close()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<LoadingData v-if="!accountsStore.accountsLoaded || working" />
|
||||
<div v-else>
|
||||
<v-expansion-panels v-model="panel" :multiple="formErrors">
|
||||
<v-expansion-panel eager value="roleForm">
|
||||
<v-expansion-panel v-if="canSetRole" eager value="roleForm">
|
||||
<v-expansion-panel-title v-slot="{ expanded }">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="4">
|
||||
@@ -198,7 +198,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { useAccountsStore, useIdentitiesStore } from '@/stores'
|
||||
import { useAccountsStore, useAuthStore, useIdentitiesStore } from '@/stores'
|
||||
import AccountGeneralForm from './form_steps/AccountGeneralForm.vue'
|
||||
import AccountMailboxForm from './form_steps/AccountMailboxForm.vue'
|
||||
import AccountAliasForm from './form_steps/AccountAliasForm.vue'
|
||||
@@ -206,17 +206,18 @@ import AccountRoleForm from './form_steps/AccountRoleForm.vue'
|
||||
import ResourcesForm from '@/components/tools/ResourcesForm.vue'
|
||||
import LoadingData from '@/components/tools/LoadingData.vue'
|
||||
import parametersApi from '@/api/parameters'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { watch } from 'vue'
|
||||
import { usePermissions } from '@/composables/permissions'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const accountsStore = useAccountsStore()
|
||||
const authStore = useAuthStore()
|
||||
const identitiesStore = useIdentitiesStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { canSetRole } = usePermissions()
|
||||
|
||||
const editedAccount = ref({ pk: route.params.id })
|
||||
|
||||
@@ -290,8 +291,9 @@ async function save() {
|
||||
} else {
|
||||
delete data.mailbox.full_address
|
||||
}
|
||||
if (!data.password) {
|
||||
delete data.password
|
||||
if (data.new_password) {
|
||||
data.password = data.new_password
|
||||
delete data.new_password
|
||||
delete data.password_confirmation
|
||||
}
|
||||
if (needsResources.value && resourcesForm.value !== null) {
|
||||
@@ -300,6 +302,13 @@ async function save() {
|
||||
if (data.aliases === null) {
|
||||
delete data.aliases
|
||||
}
|
||||
if (!canSetRole.value) {
|
||||
if (editedAccount.value.pk === authStore.authUser.pk) {
|
||||
data.role = authStore.authUser.role
|
||||
} else {
|
||||
data.role = 'SimpleUsers'
|
||||
}
|
||||
}
|
||||
identitiesStore.updateIdentity('account', data).then(() => {
|
||||
router.push({
|
||||
name: 'AccountDetail',
|
||||
@@ -316,9 +325,11 @@ onMounted(() => {
|
||||
.getAccount(route.params.id)
|
||||
.then((response) => (editedAccount.value = { ...response.data }))
|
||||
|
||||
parametersApi.getGlobalApplication('limits').then((response) => {
|
||||
limitsConfig.value = response.data
|
||||
})
|
||||
if (canSetRole.value) {
|
||||
parametersApi.getGlobalApplication('limits').then((response) => {
|
||||
limitsConfig.value = response.data
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
|
||||
@@ -31,11 +31,15 @@
|
||||
/>
|
||||
|
||||
<AccountPasswordSubForm
|
||||
v-if="authStore.authUser.pk !== account.pk"
|
||||
ref="passwordForm"
|
||||
v-model="account"
|
||||
:editing="editing"
|
||||
:form-errors="formErrors"
|
||||
/>
|
||||
<v-alert v-else type="info" variant="tonal" class="py-2">
|
||||
{{ $gettext('You can update your password from the Account section') }}
|
||||
</v-alert>
|
||||
|
||||
<v-switch
|
||||
v-model="account.is_active"
|
||||
@@ -51,9 +55,12 @@ import AccountPasswordSubForm from './AccountPasswordSubForm.vue'
|
||||
import EmailField from '@/components/tools/EmailField.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useAuthStore } from '@/stores'
|
||||
import rules from '@/plugins/rules'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, default: null },
|
||||
editing: { type: Boolean, default: false },
|
||||
|
||||
@@ -122,7 +122,7 @@ function cleanQuota(value) {
|
||||
|
||||
function cleanMessageLimit(value) {
|
||||
if (value === '') {
|
||||
delete form.value.mailbox.message_limit
|
||||
form.value.mailbox.message_limit = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
<template v-if="withPasswordCheck">
|
||||
<label class="m-label">{{ $gettext('Current password') }}</label>
|
||||
<v-text-field
|
||||
v-model="account.currentPassword"
|
||||
v-model="account.password"
|
||||
autocomplete="new-password"
|
||||
variant="outlined"
|
||||
type="password"
|
||||
density="compact"
|
||||
validate-on="submit"
|
||||
:rules="disabled ? [] : [rules.required, validPassword]"
|
||||
:rules="disabled ? [] : [rules.required]"
|
||||
:disabled="disabled"
|
||||
:error-messages="props.formErrors ? props.formErrors.password : []"
|
||||
/>
|
||||
</template>
|
||||
<v-row justify="center">
|
||||
@@ -32,7 +33,7 @@
|
||||
class="ml-6"
|
||||
density="compact"
|
||||
>
|
||||
<span class="text-white mr-4">{{ account.password }}</span>
|
||||
<span class="text-white mr-4">{{ account.new_password }}</span>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="white"
|
||||
@@ -48,7 +49,7 @@
|
||||
</v-row>
|
||||
<template v-if="!account.random_password">
|
||||
<v-text-field
|
||||
v-model="account.password"
|
||||
v-model="account.new_password"
|
||||
variant="outlined"
|
||||
:label="$gettext('Password')"
|
||||
type="password"
|
||||
@@ -65,9 +66,7 @@
|
||||
density="compact"
|
||||
:disabled="disabled"
|
||||
:rules="isRuleActive ? [rules.required, passwordConfirmationRules] : []"
|
||||
:error-messages="
|
||||
formErrors && formErrors.value ? formErrors.value.password : []
|
||||
"
|
||||
:error-messages="props.formErrors ? props.formErrors.new_password : []"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
@@ -76,7 +75,6 @@
|
||||
<script setup lang="js">
|
||||
import { useBusStore } from '@/stores'
|
||||
import accountsApi from '@/api/accounts'
|
||||
import accountApi from '@/api/account'
|
||||
import { computed } from 'vue'
|
||||
import rules from '@/plugins/rules'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
@@ -98,10 +96,10 @@ const isRuleActive = computed(
|
||||
)
|
||||
|
||||
const passwordConfirmationRules = (value) =>
|
||||
value === account.value.password || $gettext('Password mismatch')
|
||||
value === account.value.new_password || $gettext('Password mismatch')
|
||||
|
||||
function copyPassword() {
|
||||
navigator.clipboard.writeText(account.value.password).then(() => {
|
||||
navigator.clipboard.writeText(account.value.new_password).then(() => {
|
||||
busStore.displayNotification({
|
||||
msg: $gettext('Password copied to clipboard'),
|
||||
})
|
||||
@@ -111,21 +109,11 @@ function copyPassword() {
|
||||
function updatePassword(value) {
|
||||
if (value) {
|
||||
accountsApi.getRandomPassword().then((resp) => {
|
||||
account.value.password = resp.data.password
|
||||
account.value.new_password = resp.data.password
|
||||
})
|
||||
} else {
|
||||
account.value.password = null
|
||||
account.value.new_password = null
|
||||
account.value.password_confirmation = null
|
||||
}
|
||||
}
|
||||
|
||||
async function validPassword(value) {
|
||||
if (value) {
|
||||
return accountApi
|
||||
.checkPassword(value)
|
||||
.then(() => true)
|
||||
.catch(() => $gettext('Invalid password'))
|
||||
}
|
||||
return true
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<v-text-field
|
||||
v-model="alias.address"
|
||||
:label="$gettext('Generated address')"
|
||||
variant="outlined"
|
||||
readonly
|
||||
>
|
||||
<template #append-inner>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { computed } from 'vue'
|
||||
import { useAuthStore } from '@/stores'
|
||||
|
||||
export function usePermissions() {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const canSetRole = computed(() => {
|
||||
return ['Resellers', 'SuperAdmins'].includes(authStore.authUser.role)
|
||||
})
|
||||
|
||||
const canAddDomain = computed(() => {
|
||||
return ['Resellers', 'SuperAdmins'].includes(authStore.authUser.role)
|
||||
})
|
||||
|
||||
return {
|
||||
canSetRole,
|
||||
canAddDomain,
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,10 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
})
|
||||
}
|
||||
|
||||
async function updatePassword(data) {
|
||||
return accountApi.setPassword(data)
|
||||
}
|
||||
|
||||
function finalizeTFASetup(pinCode) {
|
||||
return accountApi.finalizeTFASetup(pinCode).then((response) => {
|
||||
const cookie = Cookies.withAttributes({ sameSite: 'strict' })
|
||||
@@ -198,6 +202,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
login,
|
||||
$reset,
|
||||
updateAccount,
|
||||
updatePassword,
|
||||
finalizeTFASetup,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<DomainList>
|
||||
<template #extraActions>
|
||||
<v-menu offset-y>
|
||||
<v-menu v-if="canAddDomain" offset-y>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
color="primary"
|
||||
@@ -28,6 +28,7 @@
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-btn
|
||||
v-if="canAddDomain"
|
||||
class="mr-2"
|
||||
:title="$gettext('Import domains and aliases from CSV file')"
|
||||
variant="flat"
|
||||
@@ -82,6 +83,7 @@
|
||||
import { ref } from 'vue'
|
||||
import domainApi from '@/api/domains'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { usePermissions } from '@/composables/permissions'
|
||||
import DomainAliasForm from '@/components/admin/domains/DomainAliasForm'
|
||||
import DomainCreationForm from '@/components/admin/domains/DomainCreationForm'
|
||||
import DomainList from '@/components/admin/domains/DomainList'
|
||||
@@ -93,6 +95,7 @@ const showDomainWizard = ref(false)
|
||||
const showImportForm = ref(false)
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const { canAddDomain } = usePermissions()
|
||||
|
||||
const importForm = ref()
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useAccountsStore } from '@/stores'
|
||||
import { usePermissions } from '@/composables/permissions'
|
||||
import parametersApi from '@/api/parameters'
|
||||
import AccountAliases from '@/components/admin/identities/AccountAliases.vue'
|
||||
import AccountSenderAddresses from '@/components/admin/identities/AccountSenderAddresses.vue'
|
||||
@@ -52,6 +53,7 @@ import ResourcesView from '@/components/tools/ResourcesView.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const accountsStore = useAccountsStore()
|
||||
const { canSetRole } = usePermissions()
|
||||
|
||||
const account = computed(() => {
|
||||
if (accountsStore.accounts[route.params.id] !== undefined) {
|
||||
@@ -62,9 +64,11 @@ const account = computed(() => {
|
||||
})
|
||||
const limitsConfig = ref({})
|
||||
|
||||
parametersApi.getGlobalApplication('limits').then((resp) => {
|
||||
limitsConfig.value = resp.data
|
||||
})
|
||||
if (canSetRole.value) {
|
||||
parametersApi.getGlobalApplication('limits').then((resp) => {
|
||||
limitsConfig.value = resp.data
|
||||
})
|
||||
}
|
||||
|
||||
function refreshAccount() {
|
||||
accountsStore.getAccount(route.params.id)
|
||||
|
||||
@@ -352,12 +352,9 @@ class IdentitySerializer(serializers.Serializer):
|
||||
identity = serializers.CharField()
|
||||
name_or_rcpt = serializers.CharField()
|
||||
tags = TagSerializer(many=True)
|
||||
possible_actions = serializers.SerializerMethodField()
|
||||
|
||||
def __init__(self, instance=None, data=..., **kwargs):
|
||||
super().__init__(instance, data, **kwargs)
|
||||
self.fields["possible_actions"] = serializers.SerializerMethodField()
|
||||
|
||||
def get_possible_actions(self, identity):
|
||||
def get_possible_actions(self, identity) -> list[IdPossibleActionsSerializer]:
|
||||
if not isinstance(identity, core_models.User):
|
||||
# Return empty action list if identity type is an alias
|
||||
# (not used for now)
|
||||
@@ -478,21 +475,31 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer):
|
||||
fields = tuple(
|
||||
field
|
||||
for field in v1_serializers.WritableAccountSerializer.Meta.fields
|
||||
if field != "random_password"
|
||||
if field not in ["random_password", "domains"]
|
||||
) + ("aliases",)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not param_tools.get_global_parameter("enable_admin_limits", app="limits"):
|
||||
return
|
||||
self.fields["resources"] = WritableResourceSerializer(many=True, required=False)
|
||||
del self.fields["domains"]
|
||||
if self.context["request"].user.role in ["Resellers", "SuperAdmins"]:
|
||||
self.fields["resources"] = WritableResourceSerializer(
|
||||
many=True, required=False
|
||||
)
|
||||
|
||||
def validate_aliases(self, value):
|
||||
"""Check if required domains are locals and user can access them."""
|
||||
aliases = []
|
||||
existing_aliases = (
|
||||
self.instance.mailbox.alias_addresses if self.instance else []
|
||||
)
|
||||
for alias in value:
|
||||
localpart, domain = models.validate_alias_address(
|
||||
alias, self.context["request"].user, ignore_existing=True
|
||||
alias,
|
||||
self.context["request"].user,
|
||||
ignore_existing=True,
|
||||
check_limits=alias not in existing_aliases,
|
||||
)
|
||||
aliases.append({"localpart": localpart, "domain": domain})
|
||||
return aliases
|
||||
@@ -609,7 +616,6 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer):
|
||||
"""Update account and associated objects."""
|
||||
mailbox_data = validated_data.pop("mailbox", None)
|
||||
password = validated_data.pop("password", None)
|
||||
domains = validated_data.pop("domains", [])
|
||||
aliases = validated_data.pop("aliases", None)
|
||||
for key, value in validated_data.items():
|
||||
setattr(instance, key, value)
|
||||
@@ -657,7 +663,6 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer):
|
||||
allocate_resources_from_user(limit, owner, resource["max_value"])
|
||||
limit.max_value = resource["max_value"]
|
||||
limit.save()
|
||||
self.set_permissions(instance, domains)
|
||||
return instance
|
||||
|
||||
|
||||
|
||||
@@ -217,8 +217,8 @@ class IdentityViewSet(GetThrottleViewsetMixin, viewsets.ViewSet):
|
||||
"""Viewset for identities."""
|
||||
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = None
|
||||
|
||||
@extend_schema(responses=serializers.IdentitySerializer(many=True))
|
||||
def list(self, request, **kwargs):
|
||||
"""Return all identities."""
|
||||
serializer = serializers.IdentitySerializer(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import hashlib
|
||||
import random
|
||||
from typing import Optional
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
@@ -27,8 +28,13 @@ from .mailbox import Mailbox
|
||||
|
||||
|
||||
def validate_alias_address(
|
||||
address, creator, internal=False, instance=None, ignore_existing=False
|
||||
):
|
||||
address: str,
|
||||
creator,
|
||||
internal: bool = False,
|
||||
instance: Optional["Alias"] = None,
|
||||
ignore_existing: bool = False,
|
||||
check_limits: bool = True,
|
||||
) -> tuple[str, Domain]:
|
||||
"""Check if the given alias address can be created by creator."""
|
||||
local_part, domain = split_mailbox(address.lower())
|
||||
domain = Domain.objects.filter(name=domain).first()
|
||||
@@ -41,7 +47,7 @@ def validate_alias_address(
|
||||
condition = alias.exists() and not ignore_existing
|
||||
if condition:
|
||||
raise AliasExists(alias.first().pk)
|
||||
if instance is None:
|
||||
if instance is None and check_limits:
|
||||
try:
|
||||
# Check creator limits
|
||||
core_signals.can_create_object.send(
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
tonio@homer.148277:1749538104
|
||||
Reference in New Issue
Block a user