The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/features/admin/ldap/LdapSettingsPage.tsx

350 lines
12 KiB

import { css } from '@emotion/css';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { connect } from 'react-redux';
import { AppEvents, GrafanaTheme2, NavModelItem } from '@grafana/data';
import { getBackendSrv, getAppEvents } from '@grafana/runtime';
import { useStyles2, Alert, Box, Button, Field, Input, Stack, Text, TextLink } from '@grafana/ui';
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 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 [mapKeyCertConfigured, setMapKeyCertConfigured] = useState<MapKeyCertConfigured>({
// values
rootCaCertValue: false,
clientCertValue: false,
clientKeyCertValue: false,
// paths
rootCaCertPath: false,
clientCertPath: false,
clientKeyCertPath: false,
});
const methods = useForm<LdapPayload>({ defaultValues: emptySettings });
const { getValues, handleSubmit, register, reset } = methods;
const styles = useStyles2(getStyles);
useEffect(() => {
async function init() {
const payload = await getBackendSrv().get<LdapPayload>('/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;
}
const serverConfig = payload.settings.config.servers[0];
setMapKeyCertConfigured({
rootCaCertValue: serverConfig.root_ca_cert_value?.length > 0,
clientCertValue: serverConfig.client_cert_value !== '',
clientKeyCertValue: serverConfig.client_key_value !== '',
rootCaCertPath: serverConfig.root_ca_cert !== '',
clientCertPath: serverConfig.client_cert !== '',
clientKeyCertPath: serverConfig.client_key !== '',
});
reset(payload);
setIsLoading(false);
}
init();
}, [reset]);
/**
* Display warning if the feature flag is disabled
*/
if (!config.featureToggles.ssoSettingsLDAP) {
return (
<Alert title="invalid configuration">
<Trans i18nKey="ldap-settings-page.alert.feature-flag-disabled">
This page is only accessible by enabling the <strong>ssoSettingsLDAP</strong> feature flag.
</Trans>
</Alert>
);
}
/**
* 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')],
});
} 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 submitAndEnableLdapSettings = (payload: LdapPayload) => {
payload.settings.enabled = true;
putPayload(payload);
};
const saveForm = () => {
putPayload(getValues());
};
const discardForm = async () => {
try {
setIsLoading(true);
await getBackendSrv().delete('/api/v1/sso-settings/ldap');
const payload = await getBackendSrv().get<LdapPayload>('/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-update', 'Error updating LDAP settings')],
});
return;
}
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: [t('ldap-settings-page.alert.discard-success', 'LDAP settings discarded')],
});
reset(payload);
} catch (error) {
appEvents.publish({
type: AppEvents.alertError.name,
payload: [t('ldap-settings-page.alert.error-saving', 'Error saving LDAP settings')],
});
} finally {
setIsLoading(false);
}
};
const documentation = (
<TextLink
href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/ldap/"
external
>
<Trans i18nKey="ldap-settings-page.documentation">documentation</Trans>
</TextLink>
);
const subTitle = (
<Trans i18nKey="ldap-settings-page.subtitle">
The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in
our {documentation}.
</Trans>
);
return (
<Page navId="authentication" pageNav={pageNav} subTitle={subTitle}>
<Page.Contents>
<FormProvider {...methods}>
<form onSubmit={handleSubmit(submitAndEnableLdapSettings, onErrors)}>
{isLoading && <Loader />}
{!isLoading && (
<section className={styles.form}>
<h3>
<Trans i18nKey="ldap-settings-page.title">Basic Settings</Trans>
</h3>
<Field
label={t('ldap-settings-page.host.label', 'Server host')}
description={t(
'ldap-settings-page.host.description',
'Hostname or IP address of the LDAP server you wish to connect to.'
)}
>
<Input
id="host"
placeholder={t('ldap-settings-page.host.placeholder', 'example: 127.0.0.1')}
type="text"
{...register('settings.config.servers.0.host', { required: true })}
/>
</Field>
<Field
label={t('ldap-settings-page.bind-dn.label', 'Bind DN')}
description={t(
'ldap-settings-page.bind-dn.description',
'Distinguished name of the account used to bind and authenticate to the LDAP server.'
)}
>
<Input
id="bind-dn"
placeholder={t('ldap-settings-page.bind-dn.placeholder', 'example: cn=admin,dc=grafana,dc=org')}
type="text"
{...register('settings.config.servers.0.bind_dn')}
/>
</Field>
<Field label={t('ldap-settings-page.bind-password.label', 'Bind password')}>
<Input
id="bind-password"
type="text"
{...register('settings.config.servers.0.bind_password', { required: false })}
/>
</Field>
<Field
label={t('ldap-settings-page.search_filter.label', 'Search filter*')}
description={t(
'ldap-settings-page.search_filter.description',
'LDAP search filter used to locate specific entries within the directory.'
)}
>
<Input
id="search_filter"
placeholder={t('ldap-settings-page.search_filter.placeholder', 'example: cn=%s')}
type="text"
{...register('settings.config.servers.0.search_filter', { required: true })}
/>
</Field>
<Field
label={t('ldap-settings-page.search-base-dns.label', 'Search base DNS *')}
description={t(
'ldap-settings-page.search-base-dns.description',
'An array of base dns to search through; separate by commas or spaces.'
)}
>
<Input
id="search-base-dns"
placeholder={t('ldap-settings-page.search-base-dns.placeholder', 'example: "dc=grafana.dc=org"')}
type="text"
{...register('settings.config.servers.0.search_base_dns', { required: true })}
/>
</Field>
<Box borderColor="strong" borderStyle="solid" padding={2} width={68}>
<Stack alignItems={'center'} direction={'row'} gap={2} justifyContent={'space-between'}>
<Stack alignItems={'start'} direction={'column'}>
<Text element="h2">
<Trans i18nKey="ldap-settings-page.advanced-settings-section.title">Advanced Settings</Trans>
</Text>
<Text>
<Trans i18nKey="ldap-settings-page.advanced-settings-section.subtitle">
Mappings, extra security measures, and more.
</Trans>
</Text>
</Stack>
<Button variant="secondary" onClick={() => setIsDrawerOpen(true)}>
<Trans i18nKey="ldap-settings-page.advanced-settings-section.edit.button">Edit</Trans>
</Button>
</Stack>
</Box>
<Box display={'flex'} gap={2} marginTop={5}>
<Stack alignItems={'center'} gap={2}>
<Button type={'submit'}>
<Trans i18nKey="ldap-settings-page.buttons-section.save-and-enable.button">Save and enable</Trans>
</Button>
<Button variant="secondary" onClick={saveForm}>
<Trans i18nKey="ldap-settings-page.buttons-section.save.button">Save</Trans>
</Button>
<Button variant="secondary" onClick={discardForm}>
<Trans i18nKey="ldap-settings-page.buttons-section.discard.button">Discard</Trans>
</Button>
</Stack>
</Box>
</section>
)}
{isDrawerOpen && (
<LdapDrawerComponent
onClose={() => setIsDrawerOpen(false)}
mapKeyCertConfigured={mapKeyCertConfigured}
setMapKeyCertConfigured={setMapKeyCertConfigured}
/>
)}
</form>
</FormProvider>
</Page.Contents>
</Page>
);
};
function getStyles(theme: GrafanaTheme2) {
return {
form: css({
width: theme.spacing(68),
}),
};
}
export default connector(LdapSettingsPage);