SSO LDAP: Add Group Mappings field (#92993)

* Add GroupMApping component

* Add remove/add buttons

* Implement role fields

* Change value as number for org ids

* Add i18n extracts

* Use conditional for GrafanaAdmin switch
This commit is contained in:
linoman 2024-09-06 13:42:33 +02:00 committed by GitHub
parent 20f7e11987
commit 8ea374b773
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 149 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import { useFormContext } from 'react-hook-form';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import {
useStyles2,
Button,
CollapsableSection,
Divider,
Drawer,
@ -25,6 +26,8 @@ import {
import { t, Trans } from 'app/core/internationalization';
import { LdapPayload, MapKeyCertConfigured } from 'app/types';
import { GroupMappingComponent } from './LdapGroupMapping';
interface Props {
onClose: () => void;
mapKeyCertConfigured: MapKeyCertConfigured;
@ -90,6 +93,23 @@ export const LdapDrawerComponent = ({
</Trans>
);
const onAddGroupMapping = () => {
setValue(`${serverConfig}.group_mappings`, [
...getValues(`${serverConfig}.group_mappings`),
{
group_dn: '',
org_id: 1,
org_role: 'Viewer',
grafana_admin: false,
},
]);
};
const onRemoveGroupMapping = (index: number) => {
const groupMappings = getValues(`${serverConfig}.group_mappings`);
setValue(`${serverConfig}.group_mappings`, [...groupMappings.slice(0, index), ...groupMappings.slice(index + 1)]);
};
return (
<Drawer title={t('ldap-drawer.title', 'Advanced settings')} onClose={onClose}>
<CollapsableSection label={t('ldap-drawer.misc-section.label', 'Misc')} isOpen={true}>
@ -204,7 +224,13 @@ export const LdapDrawerComponent = ({
{...register(`${serverConfig}.group_search_filter_user_attribute`)}
/>
</Field>
{watch('settings.config.servers.0.group_mappings')?.map((_, i) => {
return <GroupMappingComponent key={i} groupMappingIndex={i} onRemove={() => onRemoveGroupMapping(i)} />;
})}
<Divider />
<Button className={styles.button} variant="secondary" icon="plus" onClick={() => onAddGroupMapping()}>
<Trans i18nKey="ldap-drawer.group-mapping-section.add.button">Add group mapping</Trans>
</Button>
</CollapsableSection>
<CollapsableSection
label={t('ldap-drawer.extra-security-section.label', 'Extra security measures')}
@ -420,5 +446,8 @@ function getStyles(theme: GrafanaTheme2) {
sectionLabel: css({
fontSize: theme.typography.size.lg,
}),
button: css({
marginBottom: theme.spacing(4),
}),
};
}

View File

@ -0,0 +1,74 @@
import { useFormContext } from 'react-hook-form';
import { SelectableValue } from '@grafana/data';
import { Box, Button, Field, Input, RadioButtonGroup, Switch } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization';
import { LdapPayload, OrgRole } from 'app/types';
const roleOptions: Array<SelectableValue<string>> = Object.keys(OrgRole).map((key) => {
return { label: key, value: key };
});
interface GroupMappingProps {
onRemove: () => void;
groupMappingIndex: number;
}
export const GroupMappingComponent = ({ groupMappingIndex, onRemove }: GroupMappingProps) => {
const { getValues, register, setValue } = useFormContext<LdapPayload>();
return (
<Box borderColor="strong" borderStyle="solid" padding={2} marginBottom={2}>
<Field
htmlFor="group-dn"
label={t('ldap-drawer.group-mapping.group-dn.label', 'Group DN')}
description={t(
'ldap-drawer.group-mapping.group-dn.description',
'The name of the key used to extract the ID token from the returned OAuth2 token.'
)}
>
<Input id="group-dn" {...register(`settings.config.servers.0.group_mappings.${groupMappingIndex}.group_dn`)} />
</Field>
<Field label={t('ldap-drawer.group-mapping.org-role.label', 'Org role *')}>
<RadioButtonGroup
id={`org-role-${groupMappingIndex}`}
options={roleOptions}
value={getValues(`settings.config.servers.0.group_mappings.${groupMappingIndex}.org_role`)}
onChange={(v) => setValue(`settings.config.servers.0.group_mappings.${groupMappingIndex}.org_role`, v)}
/>
</Field>
<Field
htmlFor="org-id"
label={t('ldap-drawer.group-mapping.org-id.label', 'Org ID')}
description={t(
'ldap-drawer.group-mapping.org-id.description',
'The Grafana organization database id. Default org (ID 1) will be used if left out'
)}
>
<Input
id="org-id"
type="number"
{...register(`settings.config.servers.0.group_mappings.${groupMappingIndex}.org_id`, { valueAsNumber: true })}
/>
</Field>
{contextSrv.isGrafanaAdmin && (
<Field
htmlFor="grafana-admin"
label={t('ldap-drawer.group-mapping.grafana-admin.label', 'Grafana Admin')}
description={t(
'ldap-drawer.group-mapping.grafana-admin.description',
'If enabled, all users from this group will be Grafana Admins'
)}
>
<Switch
id="grafana-admin"
{...register(`settings.config.servers.0.group_mappings.${groupMappingIndex}.grafana_admin`)}
/>
</Field>
)}
<Button variant="secondary" fill="outline" icon="trash-alt" onClick={onRemove}>
<Trans i18nKey="ldap-drawer.group-mapping.remove.button">Remove group mapping</Trans>
</Button>
</Box>
);
};

View File

@ -1045,7 +1045,30 @@
"use-ssl-label": "Use SSL",
"use-ssl-tooltip": "For a complete list of supported ciphers and TLS versions, refer to: {\n <TextLink style={{ fontSize: 'inherit' }} href=\"https://go.dev/src/crypto/tls/cipher_suites.go\" external>\n https://go.dev/src/crypto/tls/cipher_suites.go\n </TextLink>}"
},
"group-mapping": {
"grafana-admin": {
"description": "If enabled, all users from this group will be Grafana Admins",
"label": "Grafana Admin"
},
"group-dn": {
"description": "The name of the key used to extract the ID token from the returned OAuth2 token.",
"label": "Group DN"
},
"org-id": {
"description": "The Grafana organization database id. Default org (ID 1) will be used if left out",
"label": "Org ID"
},
"org-role": {
"label": "Org role *"
},
"remove": {
"button": "Remove group mapping"
}
},
"group-mapping-section": {
"add": {
"button": "Add group mapping"
},
"description": "Map LDAP groups to Grafana org roles",
"group-search-base-dns-description": "Separate by commas or spaces",
"group-search-base-dns-label": "Group search base DNS",

View File

@ -1045,7 +1045,30 @@
"use-ssl-label": "Ůşę ŜŜĿ",
"use-ssl-tooltip": "Főř ä čőmpľęŧę ľįşŧ őƒ şūppőřŧęđ čįpĥęřş äʼnđ ŦĿŜ vęřşįőʼnş, řęƒęř ŧő: {\n <ŦęχŧĿįʼnĸ şŧyľę={{ fontSize: 'inherit' }} ĥřęƒ=\"ĥŧŧpş://ģő.đęv/şřč/čřypŧő/ŧľş/čįpĥęř_şūįŧęş.ģő\" ęχŧęřʼnäľ>\n ĥŧŧpş://ģő.đęv/şřč/čřypŧő/ŧľş/čįpĥęř_şūįŧęş.ģő\n </ŦęχŧĿįʼnĸ>}"
},
"group-mapping": {
"grafana-admin": {
"description": "Ĩƒ ęʼnäþľęđ, äľľ ūşęřş ƒřőm ŧĥįş ģřőūp ŵįľľ þę Ğřäƒäʼnä Åđmįʼnş",
"label": "Ğřäƒäʼnä Åđmįʼn"
},
"group-dn": {
"description": "Ŧĥę ʼnämę őƒ ŧĥę ĸęy ūşęđ ŧő ęχŧřäčŧ ŧĥę ĨĐ ŧőĸęʼn ƒřőm ŧĥę řęŧūřʼnęđ ØÅūŧĥ2 ŧőĸęʼn.",
"label": "Ğřőūp ĐŃ"
},
"org-id": {
"description": "Ŧĥę Ğřäƒäʼnä őřģäʼnįžäŧįőʼn đäŧäþäşę įđ. Đęƒäūľŧ őřģ (ĨĐ 1) ŵįľľ þę ūşęđ įƒ ľęƒŧ őūŧ",
"label": "Øřģ ĨĐ"
},
"org-role": {
"label": "Øřģ řőľę *"
},
"remove": {
"button": "Ŗęmővę ģřőūp mäppįʼnģ"
}
},
"group-mapping-section": {
"add": {
"button": "Åđđ ģřőūp mäppįʼnģ"
},
"description": "Mäp ĿĐÅP ģřőūpş ŧő Ğřäƒäʼnä őřģ řőľęş",
"group-search-base-dns-description": "Ŝępäřäŧę þy čőmmäş őř şpäčęş",
"group-search-base-dns-label": "Ğřőūp şęäřčĥ þäşę ĐŃŜ",