import { validate as uuidValidate } from 'uuid'; import { SelectableValue } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { t } from '@grafana/i18n/internal'; 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 { 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(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: ( <> List of comma- or space-separated groups. The user should be a member of at least one group to log in. {' '} {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: ( The user information endpoint of your OAuth2 provider. Information returned by this endpoint must be compatible with{' '} OpenID UserInfo . ), 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: ( If enabled, Grafana will use{' '} Proof Key for Code Exchange (PKCE) {' '} with the OAuth2 Authorization Code Grant. ), 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: ( <> The URL used to query for Team IDs. If not set, the default value is /teams. {' '} {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.')}{' '} If set, the user must be a member of one of the given teams to log in. {' '} {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) => , }, }; } // 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 "::" mappings.' ); case 'github': return t( 'auth-config.fields.org-mapping-description-github', 'List of "::" mappings.' ); case 'gitlab': return t( 'auth-config.fields.org-mapping-description-gitlab', 'List of "::" mappings.' ); case 'google': return t( 'auth-config.fields.org-mapping-description-google', 'List of "::" mappings.' ); default: // Generic OAuth, Okta return t( 'auth-config.fields.org-mapping-description-generic', 'List of "::" mappings.' ); } } function clientAuthenticationOptions(provider: string): Array> { // 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 */ }