Files
Hugo Häggmark 2b8c74de2e i18n: removes useTranslate hook (#106556)
* i18n: removes useTranslate hook

* chore: fix duplicate imports

* chore: fix import sorting and hook dependencies
2025-06-12 11:03:52 +02:00

934 lines
31 KiB
TypeScript

import { validate as uuidValidate } from 'uuid';
import { SelectableValue } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { TextLink } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { ServerDiscoveryField } from './components/ServerDiscoveryField';
import { FieldData, SSOProvider, SSOSettingsField } from './types';
import { isSelectableValue, isSelectableValueArray } from './utils/guards';
import { isUrlValid } from './utils/url';
type Section = Record<
SSOProvider['provider'],
Array<{
name: string;
id: string;
hidden?: boolean;
fields: SSOSettingsField[];
}>
>;
export const getSectionFields = (): Section => {
const generalSettingsLabel = t('auth-config.fields.section-general-settings', 'General settings');
const userMappingLabel = t('auth-config.fields.section-user-mapping', 'User mapping');
const extraSecurityLabel = t('auth-config.fields.section-extra-security', 'Extra security measures');
return {
azuread: [
{
name: generalSettingsLabel,
id: 'general',
fields: [
'name',
'clientAuthentication',
'clientId',
'clientSecret',
'managedIdentityClientId',
'federatedCredentialAudience',
'scopes',
'authUrl',
'tokenUrl',
'allowSignUp',
'autoLogin',
'signoutRedirectUrl',
],
},
{
name: userMappingLabel,
id: 'user',
fields: ['roleAttributeStrict', 'orgMapping', 'allowAssignGrafanaAdmin', 'skipOrgRoleSync'],
},
{
name: extraSecurityLabel,
id: 'extra',
fields: [
'allowedOrganizations',
'allowedDomains',
'allowedGroups',
'forceUseGraphApi',
'usePkce',
'useRefreshToken',
'tlsSkipVerifyInsecure',
'tlsClientCert',
'tlsClientKey',
'tlsClientCa',
'workloadIdentityTokenFile',
],
},
],
generic_oauth: [
{
name: generalSettingsLabel,
id: 'general',
fields: [
'name',
'clientId',
'clientSecret',
'authStyle',
'scopes',
'serverDiscoveryUrl',
'authUrl',
'tokenUrl',
'apiUrl',
'allowSignUp',
'autoLogin',
'signoutRedirectUrl',
],
},
{
name: userMappingLabel,
id: 'user',
fields: [
'nameAttributePath',
'loginAttributePath',
'emailAttributeName',
'emailAttributePath',
'idTokenAttributeName',
'roleAttributePath',
'roleAttributeStrict',
'orgMapping',
'orgAttributePath',
'allowAssignGrafanaAdmin',
'skipOrgRoleSync',
],
},
{
name: extraSecurityLabel,
id: 'extra',
fields: [
'allowedOrganizations',
'allowedDomains',
'defineAllowedGroups',
{ name: 'allowedGroups', dependsOn: 'defineAllowedGroups' },
{ name: 'groupsAttributePath', dependsOn: 'defineAllowedGroups' },
'defineAllowedTeamsIds',
{ name: 'teamIds', dependsOn: 'defineAllowedTeamsIds' },
{ name: 'teamsUrl', dependsOn: 'defineAllowedTeamsIds' },
{ name: 'teamIdsAttributePath', dependsOn: 'defineAllowedTeamsIds' },
'usePkce',
'useRefreshToken',
'tlsSkipVerifyInsecure',
'tlsClientCert',
'tlsClientKey',
'tlsClientCa',
],
},
],
google: [
{
name: generalSettingsLabel,
id: 'general',
fields: ['name', 'clientId', 'clientSecret', 'scopes', 'allowSignUp', 'autoLogin', 'signoutRedirectUrl'],
},
{
name: userMappingLabel,
id: 'user',
fields: [
'roleAttributePath',
'roleAttributeStrict',
'orgMapping',
'allowAssignGrafanaAdmin',
'skipOrgRoleSync',
],
},
{
name: extraSecurityLabel,
id: 'extra',
fields: [
'validateHd',
'hostedDomain',
'allowedDomains',
'allowedGroups',
'usePkce',
'useRefreshToken',
'tlsSkipVerifyInsecure',
'tlsClientCert',
'tlsClientKey',
'tlsClientCa',
],
},
],
github: [
{
name: generalSettingsLabel,
id: 'general',
fields: ['name', 'clientId', 'clientSecret', 'scopes', 'allowSignUp', 'autoLogin', 'signoutRedirectUrl'],
},
{
name: userMappingLabel,
id: 'user',
fields: [
'roleAttributePath',
'roleAttributeStrict',
'orgMapping',
'allowAssignGrafanaAdmin',
'skipOrgRoleSync',
],
},
{
name: extraSecurityLabel,
id: 'extra',
fields: [
'allowedOrganizations',
'allowedDomains',
'teamIds',
'usePkce',
'useRefreshToken',
'tlsSkipVerifyInsecure',
'tlsClientCert',
'tlsClientKey',
'tlsClientCa',
],
},
],
gitlab: [
{
name: generalSettingsLabel,
id: 'general',
fields: ['name', 'clientId', 'clientSecret', 'scopes', 'allowSignUp', 'autoLogin', 'signoutRedirectUrl'],
},
{
name: userMappingLabel,
id: 'user',
fields: [
'roleAttributePath',
'roleAttributeStrict',
'orgMapping',
'allowAssignGrafanaAdmin',
'skipOrgRoleSync',
],
},
{
name: extraSecurityLabel,
id: 'extra',
fields: [
'allowedDomains',
'allowedGroups',
'usePkce',
'useRefreshToken',
'tlsSkipVerifyInsecure',
'tlsClientCert',
'tlsClientKey',
'tlsClientCa',
],
},
],
okta: [
{
name: generalSettingsLabel,
id: 'general',
fields: [
'name',
'clientId',
'clientSecret',
'scopes',
'authUrl',
'tokenUrl',
'apiUrl',
'allowSignUp',
'autoLogin',
'signoutRedirectUrl',
],
},
{
name: userMappingLabel,
id: 'user',
fields: [
'roleAttributePath',
'roleAttributeStrict',
'orgMapping',
'orgAttributePath',
'allowAssignGrafanaAdmin',
'skipOrgRoleSync',
],
},
{
name: extraSecurityLabel,
id: 'extra',
fields: [
'allowedDomains',
'allowedGroups',
'usePkce',
'useRefreshToken',
'tlsSkipVerifyInsecure',
'tlsClientCert',
'tlsClientKey',
'tlsClientCa',
],
},
],
};
};
// These field names should not be translated because they refer to specific technical terminology.
// We put them in variables so they can be referred to in otherwise translated descriptions and not
// risk being translated.
const clientIDLabel = 'Client ID';
const clientSecretLabel = 'Client secret';
const scopesLabel = 'Scopes';
const openIDConnectDiscoveryLabel = 'OpenID Connect Discovery URL';
const authURLLabel = 'Auth URL';
const tokenURLLabel = 'Token URL';
const apiURLLabel = 'API URL';
const jmesPathLabel = 'JMESPath';
const workloadIdentityLabel = 'Workload identity';
/**
* List all the fields that can be used in the form
*/
export function fieldMap(provider: string): Record<string, FieldData> {
const orgMappingLabel = t('auth-config.fields.organization-mapping-label', 'Organization mapping');
const orgAttributePathLabel = t(
'auth-config.fields.organization-attribute-path-label',
'Organization attribute path'
);
const teamsURLLabel = t('auth-config.fields.teams-url-label', 'Teams URL');
const teamIDsAttributePathLabel = t('auth-config.fields.team-ids-attribute-path-label', 'Team IDs attribute path');
const allowedGroupsLabel = t('auth-config.fields.allowed-groups-label', 'Allowed groups');
const groupsAttributePathLabel = t('auth-config.fields.groups-attribute-path-label', 'Groups attribute path');
const teamIDsLabel = t('auth-config.fields.team-ids-label', 'Team IDs');
const allowedDomainsLabel = t('auth-config.fields.allowed-domains-label', 'Allowed domains');
return {
clientAuthentication: {
label: t('auth-config.fields.client-authentication-label', 'Client authentication'),
type: 'select',
description: t(
'auth-config.fields.client-authentication-description',
'The client authentication method used to authenticate to the token endpoint.'
),
multi: false,
options: clientAuthenticationOptions(provider),
defaultValue: { value: 'none', label: t('auth-config.field-map.label.none', 'None') },
validation: {
required: true,
message: t('auth-config.fields.required', 'This field is required'),
},
},
clientId: {
label: clientIDLabel,
type: 'text',
description: t('auth-config.fields.client-id-description', 'The {{ clientIDLabel }} of your OAuth2 app.', {
clientIDLabel,
}),
validation: {
required: true,
message: t('auth-config.fields.required', 'This field is required'),
},
},
clientSecret: {
label: clientSecretLabel,
type: 'secret',
description: t(
'auth-config.fields.client-secret-description',
'The {{ clientSecretLabel }} of your OAuth2 app.',
{
clientSecretLabel,
}
),
},
managedIdentityClientId: {
label: t('auth-config.fields.managed-identity-client-id-label', 'FIC managed identity client ID'),
type: 'text',
description: t(
'auth-config.fields.managed-identity-client-id-description',
'The managed identity client ID of the federated identity credential of your OAuth2 app.'
),
},
federatedCredentialAudience: {
label: t('auth-config.fields.federated-credential-audience-label', 'FIC audience'),
type: 'text',
description: t(
'auth-config.fields.federated-credential-audience-description',
'The audience of the federated identity credential of your OAuth2 app.'
),
},
workloadIdentityTokenFile: {
label: t('auth-config.fields.workload-identity-token-file-label', '{{ workloadIdentityLabel }} token file', {
workloadIdentityLabel,
}),
type: 'text',
description: t(
'auth-config.fields.workload-identity-token-file-description',
'The file path to the token file used to authenticate to the OAuth2 provider. This is only required when client authentication is set to "workload_identity". Defaults to /var/run/secrets/azure/tokens/azure-identity-token.'
),
validation: {
validate: (value, formValues) => {
let clientAuth = formValues.clientAuthentication;
if (isSelectableValue<string>(clientAuth)) {
clientAuth = clientAuth.value;
}
if (clientAuth === 'workload_identity') {
return !!value;
}
return true;
},
message: t(
'auth-config.fields.workload-identity-token-file-required',
'This field must be set when client authentication is set to "Workload identity".'
),
},
},
allowedOrganizations: {
label: t('auth-config.fields.allowed-organizations-label', 'Allowed organizations'),
type: 'select',
description: t(
'auth-config.fields.allowed-organizations-description',
'List of comma- or space-separated organizations. The user should be a member \nof at least one organization to log in.'
),
multi: true,
allowCustomValue: true,
options: [],
placeholder: t(
'auth-config.fields.allowed-organizations-placeholder',
'Enter organizations (my-team, myteam...) and press Enter to add'
),
},
allowedDomains: {
label: allowedDomainsLabel,
type: 'select',
description: t(
'auth-config.fields.allowed-domains-description',
'List of comma- or space-separated domains. The user should belong to at least \none domain to log in.'
),
multi: true,
allowCustomValue: true,
options: [],
},
authUrl: {
label: authURLLabel,
type: 'text',
description: t('auth-config.fields.auth-url-description', 'The authorization endpoint of your OAuth2 provider.'),
validation: {
required: true,
validate: (value) => {
return isUrlValid(value);
},
message: t('auth-config.fields.auth-url-required', 'This field is required and must be a valid URL.'),
},
},
authStyle: {
label: t('auth-config.fields.auth-style-label', 'Auth style'),
type: 'select',
description: t(
'auth-config.fields.auth-style-description',
'It determines how "{{ clientIDLabel }}" and "{{ clientSecretLabel }}" are sent to Oauth2 provider. Default is AutoDetect.',
{ clientIDLabel, clientSecretLabel }
),
multi: false,
options: [
/* eslint-disable @grafana/i18n/no-untranslated-strings */
{ value: 'AutoDetect', label: 'AutoDetect' },
{ value: 'InParams', label: 'InParams' },
{ value: 'InHeader', label: 'InHeader' },
],
defaultValue: { value: 'AutoDetect', label: 'AutoDetect' },
/* eslint-enable @grafana/i18n/no-untranslated-strings */
},
tokenUrl: {
label: tokenURLLabel,
type: 'text',
description: t('auth-config.fields.token-url-description', 'The token endpoint of your OAuth2 provider.'),
validation: {
required: true,
validate: (value) => {
return isUrlValid(value);
},
message: t('auth-config.fields.token-url-required', 'This field is required and must be a valid URL.'),
},
},
scopes: {
label: scopesLabel,
type: 'select',
description: t(
'auth-config.fields.scopes-description',
'List of comma- or space-separated OAuth2 {{ scopesLabel }}.',
{
scopesLabel,
}
),
multi: true,
allowCustomValue: true,
options: [],
},
allowedGroups: {
label: allowedGroupsLabel,
type: 'select',
description: (
<>
<Trans i18nKey="auth-config.fields.allowed-groups-description">
List of comma- or space-separated groups. The user should be a member of at least one group to log in.
</Trans>{' '}
{provider === 'generic_oauth' &&
t(
'auth-config.fields.allowed-groups-description-oauth',
'If you configure "{{ allowedGroupsLabel }}", you must also configure "{{ groupsAttributePathLabel }}".',
{ allowedGroupsLabel, groupsAttributePathLabel }
)}
</>
),
multi: true,
allowCustomValue: true,
options: [],
validation:
provider === 'azuread'
? {
validate: (value) => {
if (typeof value === 'string') {
return uuidValidate(value);
}
if (isSelectableValueArray(value)) {
return value.every((v) => v?.value && uuidValidate(v.value));
}
return true;
},
message: t(
'auth-config.fields.allowed-groups-object-ids',
'{{ allowedGroupsLabel }} must be {{ objectIDsField }}.',
{
objectIDsField: 'Object IDs',
}
),
}
: undefined,
},
apiUrl: {
label: apiURLLabel,
type: 'text',
description: (
<Trans i18nKey="auth-config.fields.api-url-description">
The user information endpoint of your OAuth2 provider. Information returned by this endpoint must be
compatible with{' '}
<TextLink href={'https://connect2id.com/products/server/docs/api/userinfo'} external variant={'bodySmall'}>
OpenID UserInfo
</TextLink>
.
</Trans>
),
validation: {
required: false,
validate: (value) => {
if (typeof value !== 'string') {
return false;
}
if (value.length) {
return isUrlValid(value);
}
return true;
},
message: t('auth-config.fields.api-url-required', 'This field must be a valid URL if set.'),
},
},
roleAttributePath: {
label: t('auth-config.fields.role-attribute-path-label', 'Role attribute path'),
description: t(
'auth-config.fields.role-attribute-path-description',
'{{ jmesPathLabel }} expression to use for Grafana role lookup.',
{ jmesPathLabel }
),
type: 'text',
validation: {
required: false,
},
},
name: {
label: t('auth-config.fields.display-name-label', 'Display name'),
description: t(
'auth-config.fields.display-name-description',
'Will be displayed on the login page as "Sign in with ...". Helpful if you use more than one identity providers or SSO protocols.'
),
type: 'text',
},
allowSignUp: {
label: t('auth-config.fields.allow-sign-up-label', 'Allow sign up'),
description: t(
'auth-config.fields.allow-sign-up-description',
'If not enabled, only existing Grafana users can log in using OAuth.'
),
type: 'switch',
},
autoLogin: {
label: t('auth-config.fields.auto-login-label', 'Auto login'),
description: t('auth-config.fields.auto-login-description', 'Log in automatically, skipping the login screen.'),
type: 'switch',
},
signoutRedirectUrl: {
label: t('auth-config.fields.signout-redirect-url-label', 'Sign out redirect URL'),
description: t(
'auth-config.fields.signout-redirect-url-description',
'The URL to redirect the user to after signing out from Grafana.'
),
type: 'text',
validation: {
required: false,
},
},
emailAttributeName: {
label: t('auth-config.fields.email-attribute-name-label', 'Email attribute name'),
description: t(
'auth-config.fields.email-attribute-name-description',
'Name of the key to use for user email lookup within the attributes map of OAuth2 ID token.'
),
type: 'text',
},
emailAttributePath: {
label: t('auth-config.fields.email-attribute-path-label', 'Email attribute path'),
description: t(
'auth-config.fields.email-attribute-path-description',
'JMESPath expression to use for user email lookup from the user information.'
),
type: 'text',
},
nameAttributePath: {
label: t('auth-config.fields.name-attribute-path-label', 'Name attribute path'),
description: t(
'auth-config.fields.name-attribute-path-description',
"JMESPath expression to use for user name lookup from the user ID token. \nThis name will be used as the user's display name."
),
type: 'text',
},
loginAttributePath: {
label: t('auth-config.fields.login-attribute-path-label', 'Login attribute path'),
description: t(
'auth-config.fields.login-attribute-path-description',
'JMESPath expression to use for user login lookup from the user ID token.'
),
type: 'text',
},
idTokenAttributeName: {
label: t('auth-config.fields.id-token-attribute-name-label', 'ID token attribute name'),
description: t(
'auth-config.fields.id-token-attribute-name-description',
'The name of the key used to extract the ID token from the returned OAuth2 token.'
),
type: 'text',
},
roleAttributeStrict: {
label: t('auth-config.fields.role-attribute-strict-label', 'Role attribute strict mode'),
description: t(
'auth-config.fields.role-attribute-strict-description',
'If enabled, denies user login if the Grafana role cannot be extracted using Role attribute path.'
),
type: 'switch',
},
allowAssignGrafanaAdmin: {
label: t('auth-config.fields.allow-assign-grafana-admin-label', 'Allow assign Grafana admin'),
description: t(
'auth-config.fields.allow-assign-grafana-admin-description',
'If enabled, it will automatically sync the Grafana server administrator role.'
),
type: 'switch',
hidden: !contextSrv.isGrafanaAdmin,
},
skipOrgRoleSync: {
label: t('auth-config.fields.skip-org-role-sync-label', 'Skip organization role sync'),
description: t(
'auth-config.fields.skip-org-role-sync-description',
"Prevent synchronizing users' organization roles from your IdP."
),
type: 'switch',
},
orgMapping: {
label: orgMappingLabel,
description: orgMappingDescription(provider),
type: 'select',
hidden: !contextSrv.isGrafanaAdmin,
multi: true,
allowCustomValue: true,
options: [],
placeholder: t(
'auth-config.fields.organization-mapping-placeholder',
'Enter mappings (my-team:1:Viewer...) and press Enter to add'
),
},
orgAttributePath: {
label: orgAttributePathLabel,
description: t(
'auth-config.fields.organization-attribute-path-description',
'JMESPath expression to use for organization lookup. If you configure "{{ orgMappingLabel }}", you must also configure "{{ orgAttributePathLabel }}".',
{ orgMappingLabel, orgAttributePathLabel }
),
type: 'text',
hidden: !(['generic_oauth', 'okta'].includes(provider) && contextSrv.isGrafanaAdmin),
},
defineAllowedGroups: {
label: t('auth-config.fields.define-allowed-groups-label', 'Define allowed groups'),
type: 'switch',
},
defineAllowedTeamsIds: {
label: t('auth-config.fields.define-allowed-teams-ids-label', 'Define allowed teams IDs'),
type: 'switch',
},
forceUseGraphApi: {
label: t('auth-config.fields.force-use-graph-api-label', 'Force use Graph API'),
description: t(
'auth-config.fields.force-use-graph-api-description',
"If enabled, Grafana will fetch the users' groups using the Microsoft Graph API."
),
type: 'checkbox',
},
usePkce: {
label: t('auth-config.fields.use-pkce-label', 'Use PKCE'),
description: (
<Trans i18nKey="auth-config.fields.use-pkce-description">
If enabled, Grafana will use{' '}
<TextLink external variant={'bodySmall'} href={'https://datatracker.ietf.org/doc/html/rfc7636'}>
Proof Key for Code Exchange (PKCE)
</TextLink>{' '}
with the OAuth2 Authorization Code Grant.
</Trans>
),
type: 'checkbox',
},
useRefreshToken: {
label: t('auth-config.fields.use-refresh-token-label', 'Use refresh token'),
description: t(
'auth-config.fields.use-refresh-token-description',
'If enabled, Grafana will fetch a new access token using the refresh token provided by the OAuth2 provider.'
),
type: 'checkbox',
},
tlsClientCa: {
label: t('auth-config.fields.tls-client-ca-label', 'TLS client CA'),
description: t(
'auth-config.fields.tls-client-ca-description',
'The file path to the trusted certificate authority list. Is not applicable on Grafana Cloud.'
),
type: 'text',
hidden: !config.localFileSystemAvailable,
},
tlsClientCert: {
label: t('auth-config.fields.tls-client-cert-label', 'TLS client cert'),
description: t(
'auth-config.fields.tls-client-cert-description',
'The file path to the certificate. Is not applicable on Grafana Cloud.'
),
type: 'text',
hidden: !config.localFileSystemAvailable,
},
tlsClientKey: {
label: t('auth-config.fields.tls-client-key-label', 'TLS client key'),
description: t(
'auth-config.fields.tls-client-key-description',
'The file path to the key. Is not applicable on Grafana Cloud.'
),
type: 'text',
hidden: !config.localFileSystemAvailable,
},
tlsSkipVerifyInsecure: {
label: t('auth-config.fields.tls-skip-verify-label', 'TLS skip verify'),
description: t(
'auth-config.fields.tls-skip-verify-description',
'If enabled, the client accepts any certificate presented by the server and any host \nname in that certificate. You should only use this for testing, because this mode leaves \nSSL/TLS susceptible to man-in-the-middle attacks.'
),
type: 'switch',
},
groupsAttributePath: {
label: groupsAttributePathLabel,
description: t(
'auth-config.fields.groups-attribute-path-description',
'JMESPath expression to use for user group lookup. If you configure "{{ allowedGroupsLabel }}", \nyou must also configure "{{ groupsAttributePathLabel }}".',
{ allowedGroupsLabel, groupsAttributePathLabel }
),
type: 'text',
},
teamsUrl: {
label: teamsURLLabel,
description: (
<>
<Trans i18nKey="auth-config.fields.teams-url-description">
The URL used to query for Team IDs. If not set, the default value is /teams.
</Trans>{' '}
{provider === 'generic_oauth' &&
t(
'auth-config.fields.teams-url-description-oauth',
'If you configure "{{ teamsURLLabel }}", you must also configure "{{ teamIDsAttributePathLabel }}".',
{ teamsURLLabel, teamIDsAttributePathLabel }
)}
</>
),
type: 'text',
validation: {
validate: (value, formValues) => {
let result = true;
if (formValues.teamIds.length) {
result = !!value;
}
if (typeof value === 'string' && value.length) {
result = isUrlValid(value);
}
return result;
},
message: t(
'auth-config.fields.teams-url-required',
'This field must be set if "{{ teamIDsLabel }}" are configured and must be a valid URL.',
{ teamIDsLabel }
),
},
},
teamIdsAttributePath: {
label: teamIDsAttributePathLabel,
description: t(
'auth-config.fields.team-ids-attribute-path-description',
'The JMESPath expression to use for Grafana Team ID lookup within the results returned by the "{{ teamsURLLabel }}" endpoint.',
{ teamsURLLabel }
),
type: 'text',
validation: {
validate: (value, formValues) => {
if (formValues.teamIds.length) {
return !!value;
}
return true;
},
message: t(
'auth-config.fields.team-ids-attribute-path-required',
'This field must be set if "{{ teamIDsLabel }}" are configured.',
{ teamIDsLabel }
),
},
},
teamIds: {
label: teamIDsLabel,
type: 'select',
description: (
<>
{provider === 'github'
? t('auth-config.fields.team-ids-github', 'Integer list of Team IDs.')
: t('auth-config.fields.team-ids-other', 'String list of Team IDs.')}{' '}
<Trans i18nKey="auth-config.fields.team-ids-description">
If set, the user must be a member of one of the given teams to log in.
</Trans>{' '}
{provider === 'generic_oauth' &&
t(
'auth-config.fields.team-ids-description-oauth',
'If you configure "{{ teamIDsLabel }}", you must also configure "{{ teamsURLLabel }}" and "{{ teamIDsAttributePathLabel }}".',
{ teamIDsLabel, teamsURLLabel, teamIDsAttributePathLabel }
)}
</>
),
multi: true,
allowCustomValue: true,
options: [],
placeholder: t('auth-config.fields.team-ids-placeholder', 'Enter Team IDs and press Enter to add'),
validation:
provider === 'github'
? {
validate: (value) => {
if (typeof value === 'string') {
return isNumeric(value);
}
if (isSelectableValueArray(value)) {
return value.every((v) => v?.value && isNumeric(v.value));
}
return true;
},
message: t('auth-config.fields.team-ids-numbers', 'Team IDs must be numbers.'),
}
: undefined,
},
hostedDomain: {
label: t('auth-config.fields.hosted-domain-label', 'Hosted domain'),
description: t(
'auth-config.fields.hosted-domain-description',
'The domain under which Grafana is hosted and accessible.'
),
type: 'text',
},
validateHd: {
label: t('auth-config.fields.validate-hosted-domain-label', 'Validate hosted domain'),
description: t(
'auth-config.fields.validate-hosted-domain-description',
'If enabled, Grafana will match the Hosted Domain retrieved from the Google ID Token against the "{{ allowedDomainsLabel }}" list specified by the user.',
{ allowedDomainsLabel }
),
type: 'checkbox',
},
serverDiscoveryUrl: {
label: openIDConnectDiscoveryLabel,
description: t(
'auth-config.fields.server-discovery-url-description',
'The .well-known/openid-configuration endpoint for your IdP. The info extracted from this URL will be used to populate the "{{ authURLLabel }}", "{{ tokenURLLabel }}" and "{{ apiURLLabel }}" fields.',
{ authURLLabel, tokenURLLabel, apiURLLabel }
),
type: 'custom',
content: (setValue) => <ServerDiscoveryField setValue={setValue} />,
},
};
}
// Check if a string contains only numeric values
function isNumeric(value: string) {
return /^-?\d+$/.test(value);
}
function orgMappingDescription(provider: string): string {
switch (provider) {
case 'azuread':
return t(
'auth-config.fields.org-mapping-description-azuread',
'List of "<GroupID>:<OrgIdOrName>:<Role>" mappings.'
);
case 'github':
return t(
'auth-config.fields.org-mapping-description-github',
'List of "<GitHubTeamName>:<OrgIdOrName>:<Role>" mappings.'
);
case 'gitlab':
return t(
'auth-config.fields.org-mapping-description-gitlab',
'List of "<GitlabGroupName>:<OrgIdOrName>:<Role>" mappings.'
);
case 'google':
return t(
'auth-config.fields.org-mapping-description-google',
'List of "<GoogleGroupName>:<OrgIdOrName>:<Role>" mappings.'
);
default:
// Generic OAuth, Okta
return t(
'auth-config.fields.org-mapping-description-generic',
'List of "<ExternalName>:<OrgIdOrName>:<Role>" mappings.'
);
}
}
function clientAuthenticationOptions(provider: string): Array<SelectableValue<string>> {
// Other options are purposefully not translated
/* eslint-disable @grafana/i18n/no-untranslated-strings */
switch (provider) {
case 'azuread':
return [
{ value: 'none', label: t('auth-config.fields.client-authentication-none', 'None') },
{ value: 'client_secret_post', label: 'Client secret' },
{ value: 'managed_identity', label: 'Managed identity' },
{ value: 'workload_identity', label: 'Workload identity' },
];
// Other providers ...
default:
return [
{ value: 'none', label: 'None' },
{ value: 'client_secret_post', label: 'Client secret' },
];
}
/* eslint-enable @grafana/i18n/no-untranslated-strings */
}