]> source.dussan.org Git - nextcloud-server.git/commitdiff
WIP5
authorLouis Chemineau <louis@chmn.me>
Wed, 18 Sep 2024 13:43:25 +0000 (15:43 +0200)
committerLouis Chemineau <louis@chmn.me>
Thu, 3 Oct 2024 13:45:20 +0000 (15:45 +0200)
Signed-off-by: Louis Chemineau <louis@chmn.me>
apps/user_ldap/lib/Controller/ConfigAPIController.php
apps/user_ldap/src/components/SettingsTabs/AdvancedTab.vue
apps/user_ldap/src/components/SettingsTabs/ExpertTab.vue
apps/user_ldap/src/components/SettingsTabs/LoginTab.vue
apps/user_ldap/src/components/SettingsTabs/ServerTab.vue
apps/user_ldap/src/services/ldapConfigService.ts
apps/user_ldap/src/services/wizardUtils.ts [deleted file]
apps/user_ldap/src/store/config.ts
apps/user_ldap/src/views/Settings.vue

index 8ce2486c153abbf5eb2da4c3ab0f77db13b193b3..2459cc016ed51993adbb922d0d800729ad6d43fb 100644 (file)
@@ -12,7 +12,6 @@ use OCA\User_LDAP\Configuration;
 use OCA\User_LDAP\ConnectionFactory;
 use OCA\User_LDAP\Helper;
 use OCA\User_LDAP\Settings\Admin;
-use OCP\AppFramework\Http;
 use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
 use OCP\AppFramework\Http\DataResponse;
 use OCP\AppFramework\OCS\OCSBadRequestException;
index dfb9ec4dfe4991e4c8b6b67f6a6a26752697b694..4a984c44d8d1ad97a87dbcf351f1619e9807373a 100644 (file)
@@ -4,8 +4,8 @@
  -->
 <template>
        <fieldset class="ldap-wizard__advanced">
-               <summary class="ldap-wizard__advanced__section">
-                       <h3>{{ t('user_ldap', 'Connection Settings') }}</h3>
+               <details name="ldap-wizard__advanced__section" class="ldap-wizard__advanced__section">
+                       <summary><h3>{{ t('user_ldap', 'Connection Settings') }}</h3></summary>
 
                        <NcCheckboxRadioSwitch :checked="ldapConfig.ldapConfigurationActive === '1'"
                                :aria-label="t('user_ldap', 'When unchecked, this configuration will be skipped.')"
                                :label="t('user_ldap', 'Cache Time-To-Live')"
                                :value="ldapConfig.ldapCacheTTL"
                                :helper-text="t('user_ldap', 'in seconds. A change empties the cache.')" />
-               </summary>
+               </details>
 
-               <summary class="ldap-wizard__advanced__section">
-                       <h3>{{ t('user_ldap', 'Directory Settings') }}</h3>
+               <details name="ldap-wizard__advanced__section" class="ldap-wizard__advanced__section">
+                       <summary><h3>{{ t('user_ldap', 'Directory Settings') }}</h3></summary>
 
                        <NcTextField autocomplete="off"
                                :value.sync="ldapConfig.ldapUserDisplayName"
                                :label="t('user_ldap', 'Default password policy DN')"
                                :value.sync="ldapConfig.ldapDefaultPPolicyDN"
                                :helper-text="t('user_ldap', 'The DN of a default password policy that will be used for password expiry handling. Works only when LDAP password changes per user are enabled and is only supported by OpenLDAP. Leave empty to disable password expiry handling.')" />
-               </summary>
+               </details>
 
-               <summary class="ldap-wizard__advanced__section">
-                       <h3>{{ t('user_ldap', 'Special Attributes') }}</h3>
+               <details name="ldap-wizard__advanced__section" class="ldap-wizard__advanced__section">
+                       <summary><h3>{{ t('user_ldap', 'Special Attributes') }}</h3></summary>
 
                        <NcTextField autocomplete="off"
                                :value.sync="ldapConfig.ldapQuotaAttribute"
                                :label="t('user_ldap', '`$home` Placeholder Field')"
                                :value.sync="ldapConfig.ldapExtStorageHomeAttribute"
                                :helper-text="t('user_ldap', '$home in an external storage configuration will be replaced with the value of the specified attribute')" />
-               </summary>
+               </details>
 
-               <summary class="ldap-wizard__advanced__section">
-                       <h3>{{ t('user_ldap', 'User Profile Attributes') }}</h3>
+               <details name="ldap-wizard__advanced__section" class="ldap-wizard__advanced__section">
+                       <summary><h3>{{ t('user_ldap', 'User Profile Attributes') }}</h3></summary>
 
                        <NcTextField autocomplete="off"
                                :label="t('user_ldap', 'Phone Field')"
                                :label="t('user_ldap', 'Birthdate Field')"
                                :value.sync="ldapConfig.ldapAttributeBirthDate"
                                :helper-text="t('user_ldap', 'User profile Date of birth will be set from the specified attribute')" />
-               </summary>
+               </details>
        </fieldset>
 </template>
 
@@ -249,6 +249,24 @@ const instanceName = 'TODO'
                display: flex;
                flex-direction: column;
                gap: 8px;
+
+               summary {
+
+                       h3 {
+                               margin: 0;
+                               display: inline;
+                               cursor: pointer;
+                               color: var(--color-text-lighter);
+                               font-size: 16px;
+
+                       }
+               }
+
+               &:hover, &[open] {
+                       h3 {
+                               color: var(--color-text-light);
+                       }
+               }
        }
 }
 </style>
index bc407f00b5f0c4472e86abc7cbea108f29f5e6d8..60d04ac332a326beb61decb6e45e24f91310fba0 100644 (file)
                <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 id="ldap_action_clear_user_mappings" type="button" name="ldap_action_clear_user_mappings">
+                       <NcButton @click="console.log('TODO')">
                                {{ t('user_ldap', 'Clear Username-LDAP User Mapping') }}
                        </NcButton>
-                       <NcButton id="ldap_action_clear_group_mappings" type="button" name="ldap_action_clear_group_mappings">
+                       <NcButton @click="console.log('TODO')">
                                {{ t('user_ldap', 'Clear Groupname-LDAP Group Mapping') }}
                        </NcButton>
                </div>
index ed6de013b7377d680b8ec6352119e7a87f8c2442..2ec27a969bdcb3f054808adb2264c13d9f4b3cc2 100644 (file)
@@ -7,9 +7,9 @@
                {{ 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.ldapAgentName === '1'"
+                       <NcCheckboxRadioSwitch :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.ldapAgentName = $event ? '1' : '0'">
+                               @update:checked="ldapConfig.ldapLoginFilterUsername = $event ? '1' : '0'">
                                {{ t('user_ldap', 'LDAP/AD Username') }}
                        </NcCheckboxRadioSwitch>
 
index 0d9722d9b585e25386e6b3a951aa4b894d32693a..cef01eaa67e7364c2864fe44b769bb45e2c6edb5 100644 (file)
@@ -29,7 +29,7 @@
                                        :placeholder="t('user_ldap', 'Port')"
                                        type="number"
                                        autocomplete="off" />
-                               <NcButton @click="detectPort">
+                               <NcButton :disabled="currentWizardActions.includes('guessPortAndTLS')" @click="guessPortAndTLS">
                                        {{ t('user_ldap', 'Detect Port') }}
                                </NcButton>
                        </div>
                                :placeholder="t('user_ldap', 'One Base DN per line')"
                                :helper-text="t('user_ldap', 'You can specify Base DN for users and groups in the Advanced tab')" />
 
-                       <NcButton @click="detectBaseDN">
+                       <NcButton :disabled="currentWizardActions.includes('guessBaseDN')" @click="guessBaseDN">
                                {{ t('user_ldap', 'Detect Base DN') }}
                        </NcButton>
-                       <NcButton @click="testBaseDN">
+                       <NcButton :disabled="currentWizardActions.includes('countInBaseDN')" @click="countInBaseDN">
                                {{ t('user_ldap', 'Test Base DN') }}
                        </NcButton>
                </div>
 
                <div class="ldap-wizard__server__line">
-                       <NcCheckboxRadioSwitch :checked.sync="advancedAdmin"
-                               :aria-label="t('user_ldap', 'Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge.')">
+                       <NcCheckboxRadioSwitch :checked="ldapConfig.ldapExperiencedAdmin === '1'"
+                               :aria-label="t('user_ldap', 'Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge.')"
+                               @update:checked="ldapConfig.ldapExperiencedAdmin = $event ? '1' : '0'">
                                {{ t('user_ldap', 'Manually enter LDAP filters (recommended for large directories)') }}
                        </NcCheckboxRadioSwitch>
                </div>
@@ -87,8 +88,7 @@ import { t } from '@nextcloud/l10n'
 import { NcButton, NcTextField, NcTextArea, NcCheckboxRadioSwitch } from '@nextcloud/vue'
 
 import { useLDAPConfigStore } from '../../store/config'
-
-const ldapConfigStore = useLDAPConfigStore()
+import { callWizard } from '../../services/ldapConfigService'
 
 const { ldapConfigId } = defineProps({
        ldapConfigId: {
@@ -97,31 +97,42 @@ const { ldapConfigId } = defineProps({
        },
 })
 
-const ldapConfig = computed(() => ldapConfigStore.ldapConfigs[ldapConfigId])
+const ldapConfigStore = useLDAPConfigStore()
 
-// TODO: use this
-const advancedAdmin = ref(false)
+const ldapConfig = computed(() => ldapConfigStore.ldapConfigs[ldapConfigId])
+const usersCount = ref<number|undefined>(undefined)
+const currentWizardActions = ref<string[]>([])
 
 /**
  *
  */
-async function detectPort() {
-       // TODO
+async function guessPortAndTLS() {
+       currentWizardActions.value.push('guessPortAndTLS')
+       const { changes: { ldap_port: ldapPort } } = await callWizard('guessPortAndTLS', ldapConfigId)
+       ldapConfig.value.ldapPort = ldapPort
+       currentWizardActions.value.splice(currentWizardActions.value.indexOf('guessPortAndTLS'), 1)
 }
 
 /**
  *
  */
-async function detectBaseDN() {
-       // TODO
+async function guessBaseDN() {
+       currentWizardActions.value.push('guessBaseDN')
+       const { changes: { ldap_base: ldapBase } } = await callWizard('guessBaseDN', ldapConfigId)
+       ldapConfig.value.ldapBase = ldapBase
+       currentWizardActions.value.splice(currentWizardActions.value.indexOf('guessPortAndTLS'), 1)
 }
 
 /**
  *
  */
-async function testBaseDN() {
-       // TODO
+async function countInBaseDN() {
+       currentWizardActions.value.push('countInBaseDN')
+       const { changes: { ldap_test_base: ldapTestBase } } = await callWizard('countInBaseDN', ldapConfigId)
+       usersCount.value = ldapTestBase
+       currentWizardActions.value.splice(currentWizardActions.value.indexOf('guessPortAndTLS'), 1)
 }
+
 </script>
 <style lang="scss" scoped>
 .ldap-wizard__server {
index ba053d56f3e403e49336bbdc6f73ffcd8053e3f8..cc6fd7ef75b6d940db8fb272f172437b0cddd9ce 100644 (file)
@@ -6,19 +6,50 @@
 import path from 'path'
 
 import axios from '@nextcloud/axios'
-import { getAppRootUrl } from '@nextcloud/router'
+import { getAppRootUrl, generateOcsUrl } from '@nextcloud/router'
 
 import type { LDAPConfig } from '../models'
 
-const API_ENDPOINT = path.join(getAppRootUrl('user_ldap'), '/api/v1/config')
+const APP_URL = getAppRootUrl('user_ldap')
+const AJAX_ENDPOINT = path.join(APP_URL, '/ajax')
+
+const OCS_APP_URL = generateOcsUrl('apps/user_ldap')
+const CONFIG_API_ENDPOINT = path.join(OCS_APP_URL, '/api/v1/config')
+
+console.log(OCS_APP_URL)
+console.log(CONFIG_API_ENDPOINT)
+
+type WizardAction =
+       'guessPortAndTLS' |
+       'guessBaseDN' |
+       'detectEmailAttribute' |
+       'detectUserDisplayNameAttribute' |
+       'determineGroupMemberAssoc' |
+       'determineUserObjectClasses' |
+       'determineGroupObjectClasses' |
+       'determineGroupsForUsers' |
+       'determineGroupsForGroups' |
+       'determineAttributes' |
+       'getUserListFilter' |
+       'getUserLoginFilter' |
+       'getGroupFilter' |
+       'countUsers' |
+       'countGroups' |
+       'countInBaseDN' |
+       'testLoginName'
 
 /**
  *
  * @param config
  */
-export async function createConfig(config: LDAPConfig): Promise<{configId: string, config: LDAPConfig}> {
-       const response = await axios.post(API_ENDPOINT, config)
-       return { configId: response.data.configId as string, config: response.data.config as LDAPConfig }
+export async function createConfig(
+       config: LDAPConfig,
+): Promise<{ configId: string; config: LDAPConfig }> {
+       const response = await axios.post(CONFIG_API_ENDPOINT, config)
+       return {
+               configId: response.data.configId as string,
+               config: response.data.config as LDAPConfig,
+       }
 }
 
 /**
@@ -26,8 +57,14 @@ export async function createConfig(config: LDAPConfig): Promise<{configId: strin
  * @param configId
  * @param config
  */
-export async function updateConfig(configId: string, config: LDAPConfig): Promise<LDAPConfig> {
-       const response = await axios.put(path.join(API_ENDPOINT, configId), config)
+export async function updateConfig(
+       configId: string,
+       config: LDAPConfig,
+): Promise<LDAPConfig> {
+       const response = await axios.put(
+               path.join(CONFIG_API_ENDPOINT, configId),
+               config,
+       )
        return response.data as LDAPConfig
 }
 
@@ -36,6 +73,59 @@ export async function updateConfig(configId: string, config: LDAPConfig): Promis
  * @param configId
  */
 export async function deleteConfig(configId: string): Promise<boolean> {
-       await axios.delete(path.join(API_ENDPOINT, configId))
+       await axios.delete(path.join(CONFIG_API_ENDPOINT, configId))
        return true
 }
+
+/**
+ * Starts a configuration test.
+ * @param configId
+ */
+export async function testConfiguration(configId: string) {
+       const response = await axios.post(
+               path.join(AJAX_ENDPOINT, 'testConfiguration.php'),
+               undefined,
+               {
+                       params: {
+                               ldap_serverconfig_chooser: configId,
+                       },
+               },
+       )
+
+       return response.data // TODO: check response content
+}
+
+/**
+ *
+ * @param subject
+ */
+export async function clearMapping(subject: 'user' | 'group') {
+       const response = await axios.post(
+               path.join(AJAX_ENDPOINT, 'clearMappings.php'),
+               undefined,
+               {
+                       data: {
+                               ldap_clear_mapping: subject,
+                       },
+               },
+       )
+
+       return response.data // TODO: check response content
+}
+
+/**
+ * Calls the wizard endpoint.
+ * @param action
+ * @param configId
+ */
+export async function callWizard(action: WizardAction, configId: string) {
+       const params = new FormData()
+       params.set('action', action)
+       params.set('ldap_serverconfig_chooser', configId)
+       const response = await axios.post(
+               path.join(AJAX_ENDPOINT, 'wizard.php'),
+               params,
+       )
+
+       return response.data
+}
diff --git a/apps/user_ldap/src/services/wizardUtils.ts b/apps/user_ldap/src/services/wizardUtils.ts
deleted file mode 100644 (file)
index 79c6ee6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-               /**
-                * starts a configuration test on the Nextcloud server
-                */
-               requestConfigurationTest: function() {
-                       var url = OC.generateUrl('apps/user_ldap/ajax/testConfiguration.php');
-                       var params = OC.buildQueryString({ldap_serverconfig_chooser: this.configID});
-                       var model = this;
-                       $.post(url, params, function(result) { model._processTestResult(model, result) });
-                       //TODO: make sure only one test is running at a time
-               },
\ No newline at end of file
index 6ada1184072f46e6898f62d583230e2e26b7499d..6af71740744dd2d56c394f4b8922c9064b7ff233 100644 (file)
@@ -3,60 +3,60 @@
  * SPDX-License-Identifier: AGPL-3.0-or-later
  */
 import { defineStore } from 'pinia'
-import Vue from 'vue'
+import Vue, { ref } from 'vue'
 
 import { loadState } from '@nextcloud/initial-state'
 
 import { createConfig, deleteConfig, updateConfig } from '../services/ldapConfigService'
 import type { LDAPConfig } from '../models'
 
-/**
- *
- * @param {...any} args
- */
-export function useLDAPConfigStore(...args) {
-       const store = defineStore('ldapconfig', {
-               state: () => ({
-                       ldapConfigs: loadState('user_ldap', 'ldapConfigs') as Record<string, LDAPConfig>,
-                       defaultLdapConfig: loadState('user_ldap', 'ldapDefaultConfig') as LDAPConfig,
-               }),
-
-               actions: {
-                       async create() {
-                               const { configId, config } = await createConfig({ ...this.defaultLdapConfig })
-                               Vue.set(this.ldapConfigs, configId, config)
-                       },
-
-                       async copy(fromConfigId: string) {
-                               const { configId, config } = await createConfig({ ...this.ldapConfigs[fromConfigId] })
-                               Vue.set(this.ldapConfigs, configId, config)
-                       },
-
-                       async remove(configId: string) {
-                               const result = await deleteConfig(configId)
-                               if (result === true) {
-                                       Vue.delete(this.ldapConfigs, configId)
-                               }
-                       },
-
-                       async update(configId: string, config: LDAPConfig) {
-                               config = await updateConfig(configId, config)
-                               Vue.set(this.ldapConfigs, configId, config)
-                       },
-
-                       async detectPort() {
-                                // TODO
-                       },
-
-                       async detectBaseDN() {
-                                // TODO
-                       },
-
-                       async testBaseDN() {
-                                // TODO
-                       },
-               },
-       })
-
-       return store(...args)
-}
+export const useLDAPConfigStore = defineStore('ldapconfig', () => {
+       const ldapConfigs = ref(loadState('user_ldap', 'ldapConfigs') as Record<string, LDAPConfig>)
+       const defaultLdapConfig = ref(loadState('user_ldap', 'ldapDefaultConfig') as LDAPConfig)
+
+       /**
+        *
+        */
+       async function create() {
+               const { configId, config } = await createConfig({ ...defaultLdapConfig.value })
+               Vue.set(ldapConfigs, configId, config)
+       }
+
+       /**
+        *
+        * @param fromConfigId
+        */
+       async function copy(fromConfigId: string) {
+               const { configId, config } = await createConfig({ ...ldapConfigs[fromConfigId] })
+               Vue.set(ldapConfigs, configId, config)
+       }
+
+       /**
+        *
+        * @param configId
+        */
+       async function remove(configId: string) {
+               const result = await deleteConfig(configId)
+               if (result === true) {
+                       Vue.delete(ldapConfigs, configId)
+               }
+       }
+
+       /**
+        *
+        * @param configId
+        * @param config
+        */
+       async function update(configId: string) {
+               const config = await updateConfig(configId, ldapConfigs[configId])
+               Vue.set(ldapConfigs, configId, config)
+       }
+
+       return {
+               ldapConfigs,
+               create,
+               copy,
+               remove,
+               update,
+       }
+})
index 1b8df25cb4ccdc69b07e7f8281fb9c60c392d696..6a4c4493b111239e53becf1934b2b74725ce3056 100644 (file)
@@ -64,7 +64,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue'
+import { computed, ref } from 'vue'
 
 import Plus from 'vue-material-design-icons/Plus.vue'
 
@@ -105,6 +105,11 @@ const selectOptions = Object.entries(ldapConfigStore.ldapConfigs).map(([configId
        label: `${configId}: ${config.ldapHost}`,
 }))
 
+ldapConfigStore.$subscribe((mutation, state) => {
+       ldapConfigStore.update(selectedConfigId.value)
+       console.log('mutation', mutation, state)
+})
+
 </script>
 <style lang="scss" scoped>
 .ldap-wizard {