import { css } from '@emotion/css'; import { useEffect, useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; import { connect } from 'react-redux'; import { AppEvents, GrafanaTheme2, NavModelItem } from '@grafana/data'; import { getBackendSrv, getAppEvents, locationService, reportInteraction } from '@grafana/runtime'; import { useStyles2, Alert, Box, Button, Field, IconButton, Input, LinkButton, Menu, Stack, Text, TextLink, Dropdown, MultiSelect, SecretInput, } from '@grafana/ui'; import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt'; import { Page } from 'app/core/components/Page/Page'; import config from 'app/core/config'; import { t, Trans } from 'app/core/internationalization'; import { Loader } from 'app/features/plugins/admin/components/Loader'; import { LdapPayload, MapKeyCertConfigured, StoreState } from 'app/types'; import { LdapDrawerComponent } from './LdapDrawer'; const appEvents = getAppEvents(); const mapStateToProps = (state: StoreState) => ({ ldapSsoSettings: state.ldap.ldapSsoSettings, }); const mapDispatchToProps = {}; const connector = connect(mapStateToProps, mapDispatchToProps); const pageNav: NavModelItem = { text: 'LDAP', icon: 'shield', id: 'LDAP', }; const serverConfig = 'settings.config.servers.0'; const isOptionDefined = (option: string | undefined) => option !== undefined && option !== ''; const emptySettings: LdapPayload = { id: '', provider: '', source: '', settings: { activeSyncEnabled: false, allowSignUp: false, config: { servers: [ { attributes: {}, bind_dn: '', bind_password: '', client_cert: '', client_cert_value: '', client_key: '', client_key_value: '', group_mappings: [], group_search_base_dns: [], group_search_filter: '', group_search_filter_user_attribute: '', host: '', min_tls_version: '', port: 389, root_ca_cert: '', root_ca_cert_value: [], search_base_dns: [], search_filter: '', skip_org_role_sync: false, ssl_skip_verify: false, start_tls: false, timeout: 10, tls_ciphers: [], tls_skip_verify: false, use_ssl: false, }, ], }, enabled: false, skipOrgRoleSync: false, syncCron: '', }, }; export const LdapSettingsPage = () => { const [isLoading, setIsLoading] = useState(true); const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isBindPasswordConfigured, setBindPasswordConfigured] = useState(false); const [mapKeyCertConfigured, setMapKeyCertConfigured] = useState({ clientKeyCertValue: false, clientKeyCertPath: false, }); const methods = useForm({ defaultValues: emptySettings }); const { control, formState: { isDirty, errors }, getValues, setValue, handleSubmit, register, reset, watch, } = methods; const styles = useStyles2(getStyles); useEffect(() => { async function init() { const payload = await getSettings(); let serverConfig = emptySettings.settings.config.servers[0]; if (payload.settings.config.servers?.length > 0) { serverConfig = payload.settings.config.servers[0]; } setMapKeyCertConfigured({ clientKeyCertValue: isOptionDefined(serverConfig.client_key_value), clientKeyCertPath: isOptionDefined(serverConfig.client_key), }); setBindPasswordConfigured(isOptionDefined(serverConfig.bind_password)); reset(payload); setIsLoading(false); } init(); }, [reset]); /** * Display warning if the feature flag is disabled */ if (!config.featureToggles.ssoSettingsLDAP) { return ( This page is only accessible by enabling the ssoSettingsLDAP feature flag. ); } /** * Fetches the settings from the backend * @returns Promise */ const getSettings = async () => { try { const payload = await getBackendSrv().get('/api/v1/sso-settings/ldap'); if (!payload || !payload.settings || !payload.settings.config) { appEvents.publish({ type: AppEvents.alertError.name, payload: [t('ldap-settings-page.alert.error-fetching', 'Error fetching LDAP settings')], }); return emptySettings; } return payload; } catch (error) { appEvents.publish({ type: AppEvents.alertError.name, payload: [t('ldap-settings-page.alert.error-fetching', 'Error fetching LDAP settings')], }); return emptySettings; } }; /** * Save payload to the backend * @param payload LdapPayload */ const putPayload = async (payload: LdapPayload) => { try { const result = await getBackendSrv().put('/api/v1/sso-settings/ldap', payload); if (result) { appEvents.publish({ type: AppEvents.alertError.name, payload: [t('ldap-settings-page.alert.error-saving', 'Error saving LDAP settings')], }); } appEvents.publish({ type: AppEvents.alertSuccess.name, payload: [t('ldap-settings-page.alert.saved', 'LDAP settings saved')], }); reset(await getSettings()); // Delay redirect so the form state can update setTimeout(() => { locationService.push(`/admin/authentication`); }, 300); } catch (error) { appEvents.publish({ type: AppEvents.alertError.name, payload: [t('ldap-settings-page.alert.error-saving', 'Error saving LDAP settings')], }); } }; const onErrors = () => { appEvents.publish({ type: AppEvents.alertError.name, payload: [t('ldap-settings-page.alert.error-validate-form', 'Error validating LDAP settings')], }); }; /** * Button's Actions */ const submitFormAndToggleSettings = async (payload: LdapPayload) => { payload.settings.enabled = !payload.settings.enabled; await putPayload(payload); reportInteraction('authentication_ldap_enabled'); }; const saveForm = async () => { await putPayload(getValues()); reportInteraction('authentication_ldap_saved'); }; const deleteLDAPConfig = async () => { try { setIsLoading(true); await getBackendSrv().delete('/api/v1/sso-settings/ldap'); const payload = await getSettings(); appEvents.publish({ type: AppEvents.alertSuccess.name, payload: [t('ldap-settings-page.alert.discard-success', 'LDAP settings discarded')], }); reset(payload); reportInteraction('authentication_ldap_deleted'); setTimeout(() => { locationService.push(`/admin/authentication`); }, 300); } catch (error) { appEvents.publish({ type: AppEvents.alertError.name, payload: [t('ldap-settings-page.alert.error-saving', 'Error saving LDAP settings')], }); } finally { setIsLoading(false); } }; const onDiscard = () => { reportInteraction('authentication_ldap_abandoned'); }; const isInvalidField = (field: string) => { const err = errors?.settings?.config?.servers?.[0]; return typeof err === 'object' && field in err; }; const subTitle = ( The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in our{' '} documentation . ); const disabledFormAlert = ( Your LDAP configuration is not working because the basic login form is currently disabled. Please enable the login form to use LDAP authentication. You can enable it on the Authentication page under “Auth settings”. ); return ( {config.disableLoginForm && disabledFormAlert}
{isLoading && } {!isLoading && (

Basic Settings

{ setValue(`${serverConfig}.bind_password`, ''); setBindPasswordConfigured(false); }} value={watch(`${serverConfig}.bind_password`)} onChange={({ currentTarget: { value } }) => setValue(`${serverConfig}.bind_password`, value)} /> !!value?.length }} name={`${serverConfig}.search_base_dns`} control={control} render={({ field: { onChange, ref, ...field } }) => ( onChange(v.map(({ value }) => String(value)))} /> )} /> Advanced Settings Mappings, extra security measures, and more. {!watch('settings.enabled') && ( )} {watch('settings.enabled') && ( )} Discard } placement="bottom-start" >
)} {isDrawerOpen && ( setIsDrawerOpen(false)} mapKeyCertConfigured={mapKeyCertConfigured} setMapKeyCertConfigured={setMapKeyCertConfigured} /> )}
); }; function getStyles(theme: GrafanaTheme2) { return { form: css({ width: theme.spacing(68), }), multiSelect: css({ 'div:last-of-type > svg': { display: 'none', }, }), }; } export default connector(LdapSettingsPage);