-->
<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'"
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 {
&__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;
: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>
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'
</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"
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)
/**
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 {
&__groups-filter {
display: flex;
flex-direction: column;
+
+ code {
+ background-color: var(--color-background-dark);
+ padding: 4px;
+ border-radius: 4px;
+ }
}
&__groups-count-check {
{{ 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"
<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"
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>
&__line {
display: flex;
align-items: start;
+ gap: 8px;
}
&__login-attributes {
&__user-login-filter {
display: flex;
flex-direction: column;
+
+ code {
+ background-color: var(--color-background-dark);
+ padding: 4px;
+ border-radius: 4px;
+ }
}
}
</style>
<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" />
<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']"
<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:')"
<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')" />
</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"
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()
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
/**
*
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 {
&__user-filter {
display: flex;
flex-direction: column;
+
+ code {
+ background-color: var(--color-background-dark);
+ padding: 4px;
+ border-radius: 4px;
+ }
}
&__user-count-check {
<!-- 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>
<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>
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')
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
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'))
+ }
}
/**
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()
+ })
+}
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', () => {
{{ `${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>
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')
const { ldapConfigs, selectedConfigId, selectedConfig } = storeToRefs(ldapConfigsStore)
const selectedTab = ref('server')
+const clearMappingLoading = ref(false)
ldapConfigsStore.$subscribe(async () => {
if (selectedConfigId === undefined) {
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 {
&__config-selection {
display: flex;
- align-items: bottom;
+ align-items: end;
margin-bottom: 8px;
gap: 16px;
+
+ &__create-button {
+ margin-bottom: 4px;
+ }
}
&__tab-selection-container {
}
&__tab-container {
- border: 1px solid gray;
+ border: 1px solid var(--color-text-light);
border-radius: var(--border-radius);
padding: 0 16px 16px 16px;
}
&__controls {
margin-top: 16px;
}
+
+ &__clear-mapping {
+ padding: 16px;
+
+ &__buttons {
+ display: flex;
+ margin-top: 8px;
+ gap: 16px;
+ justify-content: end;
+ }
+ }
}
</style>