]> source.dussan.org Git - nextcloud-server.git/commitdiff
WIP8
authorLouis Chemineau <louis@chmn.me>
Mon, 23 Sep 2024 10:45:17 +0000 (12:45 +0200)
committerLouis Chemineau <louis@chmn.me>
Thu, 3 Oct 2024 13:45:22 +0000 (15:45 +0200)
Signed-off-by: Louis Chemineau <louis@chmn.me>
apps/user_ldap/src/components/SettingsTabs/AdvancedTab.vue
apps/user_ldap/src/components/SettingsTabs/ExpertTab.vue
apps/user_ldap/src/components/SettingsTabs/GroupsTab.vue
apps/user_ldap/src/components/SettingsTabs/LoginTab.vue
apps/user_ldap/src/components/SettingsTabs/ServerTab.vue
apps/user_ldap/src/components/SettingsTabs/UsersTab.vue
apps/user_ldap/src/components/WizardControls.vue
apps/user_ldap/src/services/ldapConfigService.ts
apps/user_ldap/src/store/configs.ts
apps/user_ldap/src/views/Settings.vue

index d9e40c2faef7c5d6ebf2a5274411878c338b9d50..70635d76c527bd9b00984033fa8329740915e6f4 100644 (file)
@@ -4,7 +4,7 @@
  -->
 <template>
        <fieldset class="ldap-wizard__advanced">
-               <details name="ldap-wizard__advanced__section" class="ldap-wizard__advanced__section">
+               <details open="true" name="ldap-wizard__advanced__section" class="ldap-wizard__advanced__section">
                        <summary><h3>{{ t('user_ldap', 'Connection Settings') }}</h3></summary>
 
                        <NcCheckboxRadioSwitch :checked="ldapConfig.ldapConfigurationActive === '1'"
@@ -223,13 +223,14 @@ import { storeToRefs } from 'pinia'
 
 import { t } from '@nextcloud/l10n'
 import { NcTextField, NcTextArea, NcCheckboxRadioSwitch } from '@nextcloud/vue'
+import { getCapabilities } from '@nextcloud/capabilities'
 
 import { useLDAPConfigsStore } from '../../store/configs'
 
 const ldapConfigsStore = useLDAPConfigsStore()
 const { selectedConfig: ldapConfig } = storeToRefs(ldapConfigsStore)
 
-const instanceName = 'TODO'
+const instanceName = (getCapabilities() as { theming: { name:string } }).theming.name
 </script>
 <style lang="scss" scoped>
 .ldap-wizard__advanced {
@@ -240,9 +241,16 @@ const instanceName = 'TODO'
        &__section {
                display: flex;
                flex-direction: column;
-               gap: 8px;
+               border: 1px solid var(--color-text-lighter);
+               border-radius: var(--border-radius);
+               padding: 8px;
+
+               & > * {
+                       margin-top: 12px !important;
+               }
 
                summary {
+                       margin-top: 0 !important;
 
                        h3 {
                                margin: 0;
index f2cbf6f8813e3c9431240f1d82452ce71e34cabe..b1896208cadfc95d7337c499994bdc6e6747ca5e 100644 (file)
                                :label="t('user_ldap', 'UUID Attribute for Groups')"
                                :value.sync="ldapConfig.ldapExpertUUIDGroupAttr" />
                </div>
-
-               <div class="ldap-wizard__expert__line">
-                       <strong>{{ t('user_ldap', 'Username-LDAP User Mapping') }}</strong>
-                       {{ t('user_ldap', 'Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage.') }}
-                       <NcButton @click="console.log('TODO')">
-                               {{ t('user_ldap', 'Clear Username-LDAP User Mapping') }}
-                       </NcButton>
-                       <NcButton @click="console.log('TODO')">
-                               {{ t('user_ldap', 'Clear Groupname-LDAP Group Mapping') }}
-                       </NcButton>
-               </div>
        </fieldset>
 </template>
 
@@ -43,7 +32,7 @@
 import { storeToRefs } from 'pinia'
 
 import { t } from '@nextcloud/l10n'
-import { NcTextField, NcButton } from '@nextcloud/vue'
+import { NcTextField } from '@nextcloud/vue'
 
 import { useLDAPConfigsStore } from '../../store/configs'
 
index 2e30a796e2edf6f96ac4654193ae41012d32a81c..c99e4f763437e4acaf09baeb2391ab3323a994df 100644 (file)
                </div>
 
                <div class="ldap-wizard__groups__line ldap-wizard__groups__groups-filter">
-                       <NcCheckboxRadioSwitch :checked.sync="editGroupsFilter">
+                       <NcCheckboxRadioSwitch :checked="ldapConfig.ldapGroupFilterMode === '1'"
+                               @update:checked="toggleFilterMode">
                                {{ t('user_name', 'Edit LDAP Query') }}
                        </NcCheckboxRadioSwitch>
 
-                       <div v-if="!editGroupsFilter">
+                       <div v-if="ldapConfig.ldapGroupFilterMode === '0'">
                                <label>{{ t('user_name', 'LDAP Filter:') }}</label>
-                               <span>{{ ldapConfig.ldapGroupFilter }}</span>
+                               <code>{{ ldapConfig.ldapGroupFilter }}</code>
                        </div>
                        <div v-else>
                                <NcTextArea :value.sync="ldapConfig.ldapGroupFilter"
@@ -81,18 +82,19 @@ import { storeToRefs } from 'pinia'
 
 import { t } from '@nextcloud/l10n'
 import { NcButton, NcTextArea, NcCheckboxRadioSwitch, NcSelect } from '@nextcloud/vue'
+import { getCapabilities } from '@nextcloud/capabilities'
 
 import { useLDAPConfigsStore } from '../../store/configs'
+import { showEnableAutomaticFilterInfo } from '../../services/ldapConfigService'
 import { useWizardStore } from '../../store/wizard'
 
 const ldapConfigsStore = useLDAPConfigsStore()
 const wizardStore = useWizardStore()
 const { selectedConfig: ldapConfig } = storeToRefs(ldapConfigsStore)
 
-const instanceName = 'TODO'
+const instanceName = (getCapabilities() as { theming: { name:string } }).theming.name
 
 const groupsCount = ref<number|undefined>(undefined)
-const editGroupsFilter = ref(false)
 const allowUserFilterGroupsSelection = ref(false)
 
 /**
@@ -102,6 +104,18 @@ async function countGroups() {
        const { changes: { ldap_test_base: ldapTestBase } } = await wizardStore.callWizardAction('countGroups')
        groupsCount.value = ldapTestBase
 }
+
+/**
+ *
+ * @param value
+ */
+async function toggleFilterMode(value: boolean) {
+       if (value) {
+               ldapConfig.value.ldapGroupFilterMode = '1'
+       } else {
+               ldapConfig.value.ldapGroupFilterMode = await showEnableAutomaticFilterInfo()
+       }
+}
 </script>
 <style lang="scss" scoped>
 .ldap-wizard__groups {
@@ -121,6 +135,12 @@ async function countGroups() {
        &__groups-filter {
                display: flex;
                flex-direction: column;
+
+               code {
+                       background-color: var(--color-background-dark);
+                       padding: 4px;
+                       border-radius: 4px;
+               }
        }
 
        &__groups-count-check {
index d7539b16ef214e26804de50db2c2877c9e073bd3..b62b7e87761848a663ffc22c0b4d2b87ab66d09a 100644 (file)
@@ -7,32 +7,36 @@
                {{ t('user_ldap', 'When logging in, {instanceName} will find the user based on the following attributes:', { instanceName }) }}
 
                <div class="ldap-wizard__login__line ldap-wizard__login__login-attributes">
-                       <NcCheckboxRadioSwitch :checked="ldapConfig.ldapLoginFilterUsername === '1'"
+                       <NcCheckboxRadioSwitch :disabled="ldapConfig.ldapLoginFilterMode === '1'"
+                               :checked="ldapConfig.ldapLoginFilterUsername === '1'"
                                :aria-label="t('user_ldap', 'Allows login against the LDAP/AD username, which is either `uid` or `sAMAccountName` and will be detected.')"
                                @update:checked="ldapConfig.ldapLoginFilterUsername = $event ? '1' : '0'">
                                {{ t('user_ldap', 'LDAP/AD Username') }}
                        </NcCheckboxRadioSwitch>
 
-                       <NcCheckboxRadioSwitch :checked="ldapConfig.ldapLoginFilterEmail === '1'"
+                       <NcCheckboxRadioSwitch :disabled="ldapConfig.ldapLoginFilterMode === '1'"
+                               :checked="ldapConfig.ldapLoginFilterEmail === '1'"
                                :aria-label="t('user_ldap', 'Allows login against an email attribute. `mail` and `mailPrimaryAddress` allowed.')"
                                @update:checked="ldapConfig.ldapLoginFilterEmail = $event ? '1' : '0'">
                                {{ t('user_ldap', 'LDAP/AD Email Address') }}
                        </NcCheckboxRadioSwitch>
 
                        <NcSelect v-model="ldapConfig.ldapLoginFilterAttributes"
+                               :disabled="ldapConfig.ldapLoginFilterMode === '1'"
                                :options="['TODO']"
                                :input-label="t('user_ldap', 'Other Attributes:')"
                                :multiple="true" />
                </div>
 
                <div class="ldap-wizard__login__line ldap-wizard__login__user-login-filter">
-                       <NcCheckboxRadioSwitch :checked.sync="editUserLoginFilter">
+                       <NcCheckboxRadioSwitch :checked="ldapConfig.ldapLoginFilterMode === '1'"
+                               @update:checked="toggleFilterMode">
                                {{ t('user_name', 'Edit LDAP Query') }}
                        </NcCheckboxRadioSwitch>
 
-                       <div v-if="!editUserLoginFilter">
+                       <div v-if="ldapConfig.ldapLoginFilterMode === '0'">
                                <label>{{ t('user_name', 'LDAP Filter:') }}</label>
-                               <span>{{ ldapConfig.ldapLoginFilter }}</span>
+                               <code>{{ ldapConfig.ldapLoginFilter }}</code>
                        </div>
                        <div v-else>
                                <NcTextArea :value.sync="ldapConfig.ldapLoginFilter"
@@ -43,8 +47,8 @@
 
                <div class="ldap-wizard__login__line">
                        <NcTextField :value.sync="testUsername"
-                               :helper-text="t('user_ldap', 'Attempts to receive a DN for the given loginname and the current login filter')"
-                               :placeholder="t('user_ldap', 'Test Loginname')"
+                               :helper-text="t('user_ldap', 'Attempts to receive a DN for the given login name and the current login filter')"
+                               :placeholder="t('user_ldap', 'Test Login name')"
                                autocomplete="off" />
 
                        <NcButton :disabled="enableVerifyButton"
@@ -61,25 +65,45 @@ import { storeToRefs } from 'pinia'
 
 import { t } from '@nextcloud/l10n'
 import { NcButton, NcTextField, NcTextArea, NcCheckboxRadioSwitch, NcSelect } from '@nextcloud/vue'
+import { getCapabilities } from '@nextcloud/capabilities'
 
 import { useLDAPConfigsStore } from '../../store/configs'
 import { useWizardStore } from '../../store/wizard'
+import { showError, showSuccess } from '@nextcloud/dialogs'
+import { showEnableAutomaticFilterInfo } from '../../services/ldapConfigService'
 
 const ldapConfigsStore = useLDAPConfigsStore()
 const wizardStore = useWizardStore()
 
 const { selectedConfig: ldapConfig } = storeToRefs(ldapConfigsStore)
 
-const instanceName = 'TODO'
-const testUsername = ref('TODO')
+const instanceName = (getCapabilities() as { theming: { name:string } }).theming.name
+const testUsername = ref('')
 const enableVerifyButton = ref(false)
-const editUserLoginFilter = ref(false)
 
 /**
  *
  */
 async function verifyLoginName() {
-       const { changes: { ldap_test_base: ldapTestBase } } = await wizardStore.callWizardAction('testLoginName', { testUsername: testUsername.value })
+       const { changes: { ldap_test_loginname: testLoginName, ldap_test_effective_filter: testEffectiveFilter } } = await wizardStore.callWizardAction('testLoginName', { ldap_test_loginname: testUsername.value })
+
+       if (testLoginName === 1) {
+               showSuccess(t('user_ldap', 'User found and settings verified.'))
+       } else {
+               showError(t('user_ldap', 'User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command-line validation): {filter}', { filter: testEffectiveFilter }))
+       }
+}
+
+/**
+ *
+ * @param value
+ */
+async function toggleFilterMode(value: boolean) {
+       if (value) {
+               ldapConfig.value.ldapLoginFilterMode = '1'
+       } else {
+               ldapConfig.value.ldapLoginFilterMode = await showEnableAutomaticFilterInfo()
+       }
 }
 </script>
 <style lang="scss" scoped>
@@ -95,6 +119,7 @@ async function verifyLoginName() {
        &__line {
                display: flex;
                align-items: start;
+               gap: 8px;
        }
 
        &__login-attributes {
@@ -105,6 +130,12 @@ async function verifyLoginName() {
        &__user-login-filter {
                display: flex;
                flex-direction: column;
+
+               code {
+                       background-color: var(--color-background-dark);
+                       padding: 4px;
+                       border-radius: 4px;
+               }
        }
 }
 </style>
index 133f626e93d08fc4f80215366258d27ad6e21e3d..f785dbe38c6f3c315f9f992c37fe886f1e4ec81c 100644 (file)
@@ -11,7 +11,8 @@
                                        <ContentCopy :size="20" />
                                </template>
                        </NcButton>
-                       <NcButton :aria-label="t('user_ldap', 'Delete the current configuration')"
+                       <NcButton type="error"
+                               :aria-label="t('user_ldap', 'Delete the current configuration')"
                                @click="() => ldapConfigsStore.removeConfig(ldapConfigId)">
                                <template #icon>
                                        <Delete :size="20" />
index 9c4cc700e49282d8712cd7e95f5fe8207a5c53ed..44c2f6264432bafcd735cb1dfb5f4859d7b68c81 100644 (file)
@@ -8,6 +8,7 @@
 
                <div class="ldap-wizard__users__line ldap-wizard__users__user-filter-object-class">
                        <NcSelect v-model="ldapConfig.ldapUserFilterObjectclass"
+                               :disabled="ldapConfig.ldapUserFilterMode === '1'"
                                class="ldap-wizard__users__user-filter-object-class__select"
                                :disable="editUserFilter"
                                :options="['TODO']"
@@ -29,6 +30,7 @@
 
                        <NcSelect v-model="ldapConfig.ldapUserFilterGroups"
                                class="ldap-wizard__users__user-filter-groups__select"
+                               :disabled="ldapConfig.ldapUserFilterMode === '1'"
                                :options="['TODO']"
                                :disable="allowUserFilterGroupsSelection"
                                :input-label="t('user_name', 'Only these object classes:')"
@@ -39,6 +41,7 @@
                <div class="ldap-wizard__users__line">
                        <p class="ldapManyGroupsSupport hidden">
                                <select class="ldapGroupList ldapGroupListAvailable"
+                                       :disabled="ldapConfig.ldapUserFilterMode === '1'"
                                        :multiple="true"
                                        aria-describedby="ldapGroupListAvailable_instructions"
                                        :title="t('user_name', 'Available groups')" />
@@ -53,6 +56,7 @@
                        </span>
 
                        <select class="ldapGroupList ldapGroupListSelected"
+                               :disabled="ldapConfig.ldapUserFilterMode === '1'"
                                :multiple="true"
                                aria-describedby="ldapGroupListSelected_instructions"
                                :title="t('user_name', 'Selected groups')" />
                </div>
 
                <div class="ldap-wizard__users__line ldap-wizard__users__user-filter">
-                       <NcCheckboxRadioSwitch :checked.sync="editUserFilter">
+                       <NcCheckboxRadioSwitch :checked="ldapConfig.ldapUserFilterMode === '1'"
+                               @update:checked="toggleFilterMode">
                                {{ t('user_name', 'Edit LDAP Query') }}
                        </NcCheckboxRadioSwitch>
 
-                       <div v-if="!editUserFilter">
+                       <div v-if="ldapConfig.ldapUserFilterMode === '0'">
                                <label>{{ t('user_name', 'LDAP Filter:') }}</label>
-                               <span>{{ ldapConfig.ldapUserFilter }}</span>
+                               <code>{{ ldapConfig.ldapUserFilter }}</code>
                        </div>
                        <div v-else>
                                <NcTextArea :value.sync="ldapConfig.ldapUserFilter"
@@ -94,9 +99,11 @@ import { storeToRefs } from 'pinia'
 
 import { t } from '@nextcloud/l10n'
 import { NcButton, NcTextArea, NcCheckboxRadioSwitch, NcSelect } from '@nextcloud/vue'
+import { getCapabilities } from '@nextcloud/capabilities'
 
 import { useLDAPConfigsStore } from '../../store/configs'
 import { useWizardStore } from '../../store/wizard'
+import { showEnableAutomaticFilterInfo } from '../../services/ldapConfigService'
 
 const wizardStore = useWizardStore()
 const ldapConfigsStore = useLDAPConfigsStore()
@@ -104,9 +111,8 @@ const { selectedConfig: ldapConfig } = storeToRefs(ldapConfigsStore)
 
 const usersCount = ref<number|undefined>(undefined)
 
-const editUserFilter = ref(false)
 const allowUserFilterGroupsSelection = ref(true) // TODO
-const instanceName = 'TODO'
+const instanceName = (getCapabilities() as { theming: { name:string } }).theming.name
 
 /**
  *
@@ -115,6 +121,18 @@ async function countUsers() {
        const { changes: { ldap_test_base: ldapTestBase } } = await wizardStore.callWizardAction('countUsers')
        usersCount.value = ldapTestBase
 }
+
+/**
+ *
+ * @param value
+ */
+async function toggleFilterMode(value: boolean) {
+       if (value) {
+               ldapConfig.value.ldapUserFilterMode = '1'
+       } else {
+               ldapConfig.value.ldapUserFilterMode = await showEnableAutomaticFilterInfo()
+       }
+}
 </script>
 <style lang="scss" scoped>
 .ldap-wizard__users {
@@ -150,6 +168,12 @@ async function countUsers() {
        &__user-filter {
                display: flex;
                flex-direction: column;
+
+               code {
+                       background-color: var(--color-background-dark);
+                       padding: 4px;
+                       border-radius: 4px;
+               }
        }
 
        &__user-count-check {
index affc75f31be537d4d9d476030955ac67c970d909..ec655a37c8083c53dbe62d4f0cdcc4b17a66cfde 100644 (file)
@@ -7,7 +7,8 @@
                <!-- TODO -->
                <span class="ldap_config_state_indicator" /> <span class="ldap_config_state_indicator_sign" />
 
-               <NcButton href="https://docs.nextcloud.com/server/stable/go.php?to=admin-ldap"
+               <NcButton type="tertiary"
+                       href="https://docs.nextcloud.com/server/stable/go.php?to=admin-ldap"
                        target="_blank"
                        rel="noreferrer noopener">
                        <template #icon>
@@ -16,7 +17,7 @@
                        <span>{{ t('user_ldap', 'Help') }}</span>
                </NcButton>
 
-               <NcButton :disabled="loading" @click="testSelectedConfig">
+               <NcButton type="primary" :disabled="loading" @click="testSelectedConfig">
                        {{ t('user_ldap', 'Test Configuration') }}
                </NcButton>
        </div>
index 28915d7a0507d991fe2bc40cea085d559e30af44..06ea614390878cd033a081a5880ead9cbcc2b137 100644 (file)
@@ -9,8 +9,9 @@ import axios, { AxiosError, type AxiosResponse } from '@nextcloud/axios'
 import { getAppRootUrl, generateOcsUrl } from '@nextcloud/router'
 
 import type { LDAPConfig } from '../models'
-import { showError, showSuccess } from '@nextcloud/dialogs'
+import { DialogSeverity, getDialogBuilder, showError, showSuccess } from '@nextcloud/dialogs'
 import type { OCSResponse } from '@nextcloud/typings/ocs'
+import { t } from '@nextcloud/l10n'
 
 const AJAX_ENDPOINT = path.join(getAppRootUrl('user_ldap'), '/ajax')
 
@@ -75,7 +76,7 @@ export async function deleteConfig(configId: string): Promise<boolean> {
                await axios.delete(generateOcsUrl('apps/user_ldap/api/v1/config/{configId}', { configId }))
        } catch (error) {
                const errorResponse = (error as AxiosError<OCSResponse>).response
-               showError(errorResponse?.data.ocs.meta.message || 'Fail to delete config')
+               showError(errorResponse?.data.ocs.meta.message || t('user_ldap', 'Fail to delete config'))
        }
 
        return true
@@ -116,7 +117,11 @@ export async function clearMapping(subject: 'user' | 'group') {
                params,
        )
 
-       return response.data // TODO: check response content
+       if (response.data.status === 'success') {
+               showSuccess(t('user_ldap', 'Mapping cleared'))
+       } else {
+               showError(t('user_ldap', 'Failed to clear mapping'))
+       }
 }
 
 /**
@@ -141,8 +146,36 @@ export async function callWizard(action: WizardAction, configId: string, extraPa
 
        if (response.data.status === 'error') {
                showError(response.data.message)
-               throw new Error('Error when calling wizard.php')
+               throw new Error(t('user_ldap', 'Error when calling wizard.php'))
        }
 
        return response.data
 }
+
+/**
+ *
+ * @param value
+ */
+export async function showEnableAutomaticFilterInfo(): Promise<'0'|'1'> {
+       return new Promise((resolve) => {
+               const dialog = getDialogBuilder(t('user_ldap', 'Mode switch'))
+                       .setText(t('user_ldap', 'Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?'))
+                       .addButton({
+                               label: t('user_ldap', 'No'),
+                               callback() {
+                                       dialog.hide()
+                                       resolve('1')
+                               },
+                       })
+                       .addButton({
+                               label: t('user_ldap', 'Yes'),
+                               callback() {
+                                       resolve('0')
+                               },
+                       })
+                       .setSeverity(DialogSeverity.Info)
+                       .build()
+
+               dialog.show()
+       })
+}
index fee13565624c829473280b3123d30a77b27c58f0..49d0205f2a7b390e2617f6cb76cdeacee85d2a5b 100644 (file)
@@ -7,7 +7,7 @@ import Vue, { computed, ref } from 'vue'
 
 import { loadState } from '@nextcloud/initial-state'
 
-import { createConfig, deleteConfig, getConfig, updateConfig } from '../services/ldapConfigService'
+import { createConfig, deleteConfig, getConfig } from '../services/ldapConfigService'
 import type { LDAPConfig } from '../models'
 
 export const useLDAPConfigsStore = defineStore('ldap-configs', () => {
index 9392ccef6697518fa1211f1ddee9f110ea7193e7..f062b77830718dbb3106d59b833b13397cc8e72f 100644 (file)
@@ -18,7 +18,7 @@
                                        {{ `${configId}: ${ldapConfigs[configId].ldapHost}` }}
                                </template>
                        </NcSelect>
-                       <NcButton :label="t('user_ldap','Create New Config')" @click="ldapConfigsStore.create">
+                       <NcButton :label="t('user_ldap','Create New Config')" class="ldap-wizard__config-selection__create-button" @click="ldapConfigsStore.create">
                                <template #icon>
                                        <Plus :size="20" />
                                </template>
 
                        <WizardControls class="ldap-wizard__controls" />
                </div>
+
+               <div class="ldap-wizard__clear-mapping">
+                       <strong>{{ t('user_ldap', 'Username-LDAP User Mapping') }}</strong>
+                       {{ t('user_ldap', 'Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage.') }}
+
+                       <div class="ldap-wizard__clear-mapping__buttons">
+                               <NcButton type="error" :disabled="clearMappingLoading" @click="requestClearMapping('user')">
+                                       {{ t('user_ldap', 'Clear Username-LDAP User Mapping') }}
+                               </NcButton>
+                               <NcButton type="error" :disabled="clearMappingLoading" @click="requestClearMapping('group')">
+                                       {{ t('user_ldap', 'Clear Groupname-LDAP Group Mapping') }}
+                               </NcButton>
+                       </div>
+               </div>
        </form>
 </template>
 
@@ -82,7 +96,7 @@ import ExpertTab from '../components/SettingsTabs/ExpertTab.vue'
 import AdvancedTab from '../components/SettingsTabs/AdvancedTab.vue'
 import WizardControls from '../components/WizardControls.vue'
 import { useLDAPConfigsStore } from '../store/configs'
-import { updateConfig } from '../services/ldapConfigService'
+import { clearMapping, updateConfig } from '../services/ldapConfigService'
 import { storeToRefs } from 'pinia'
 
 const ldapModuleInstalled = loadState('user_ldap', 'ldapModuleInstalled')
@@ -103,6 +117,7 @@ const ldapConfigsStore = useLDAPConfigsStore()
 const { ldapConfigs, selectedConfigId, selectedConfig } = storeToRefs(ldapConfigsStore)
 
 const selectedTab = ref('server')
+const clearMappingLoading = ref(false)
 
 ldapConfigsStore.$subscribe(async () => {
        if (selectedConfigId === undefined) {
@@ -111,6 +126,19 @@ ldapConfigsStore.$subscribe(async () => {
 
        await updateConfig(selectedConfigId.value, selectedConfig.value)
 })
+
+/**
+ *
+ * @param subject
+ */
+async function requestClearMapping(subject: 'user'|'group') {
+       try {
+               clearMappingLoading.value = true
+               await clearMapping(subject)
+       } finally {
+               clearMappingLoading.value = false
+       }
+}
 </script>
 <style lang="scss" scoped>
 .ldap-wizard {
@@ -119,9 +147,13 @@ ldapConfigsStore.$subscribe(async () => {
 
        &__config-selection {
                display: flex;
-               align-items: bottom;
+               align-items: end;
                margin-bottom: 8px;
                gap: 16px;
+
+               &__create-button {
+                       margin-bottom: 4px;
+               }
        }
 
        &__tab-selection-container {
@@ -140,7 +172,7 @@ ldapConfigsStore.$subscribe(async () => {
        }
 
        &__tab-container {
-               border: 1px solid gray;
+               border: 1px solid var(--color-text-light);
                border-radius: var(--border-radius);
                padding: 0 16px 16px 16px;
        }
@@ -148,5 +180,16 @@ ldapConfigsStore.$subscribe(async () => {
        &__controls {
                margin-top: 16px;
        }
+
+       &__clear-mapping {
+               padding: 16px;
+
+               &__buttons {
+                       display: flex;
+                       margin-top: 8px;
+                       gap: 16px;
+                       justify-content: end;
+               }
+       }
 }
 </style>