From 85392de2e7714f8f20007e77256eb57d1b9e21c8 Mon Sep 17 00:00:00 2001 From: Younjin Song Date: Tue, 10 Dec 2024 03:32:27 -0800 Subject: [PATCH] Azure: Unify credentials in frontend for MSSQL (#96357) * init * test fix --- .../mssql/azureauth/AzureAuth.test.ts | 153 +++++++++------- .../mssql/azureauth/AzureAuth.testMocks.ts | 41 ++--- .../mssql/azureauth/AzureAuthSettings.tsx | 15 +- .../mssql/azureauth/AzureCredentials.ts | 21 --- .../mssql/azureauth/AzureCredentialsConfig.ts | 171 ++---------------- .../mssql/azureauth/AzureCredentialsForm.tsx | 164 ++++++++++------- public/app/plugins/datasource/mssql/types.ts | 37 +--- 7 files changed, 228 insertions(+), 374 deletions(-) delete mode 100644 public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts index ae8d516966e..2d1a75fa02f 100644 --- a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts +++ b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts @@ -1,92 +1,115 @@ -import { AzureAuthType, AzureCloud, AzureCredentialsType, ConcealedSecretType } from '../types'; +import { + AzureCredentials, + AzureCloud, + ConcealedSecret, + AzureClientSecretCredentials, + instanceOfAzureCredential, + updateDatasourceCredentials, +} from '@grafana/azure-sdk'; +import { config } from '@grafana/runtime'; import { - configWithManagedIdentityEnabled, - configWithManagedIdentityDisabled, dataSourceSettingsWithMsiCredentials, dataSourceSettingsWithClientSecretOnServer, dataSourceSettingsWithClientSecretInSecureJSONData, } from './AzureAuth.testMocks'; -import { getDefaultCredentials, getSecret, getCredentials, updateCredentials } from './AzureCredentialsConfig'; +import { getDefaultCredentials, getCredentials } from './AzureCredentialsConfig'; // NOTE: @ts-ignores are used to ignore the type errors that are thrown when passing in the mocks. // This is because the mocks are partials of the actual types, so the types are not complete. -export const CLIENT_SECRET_SYMBOL: ConcealedSecretType = Symbol('Concealed client secret'); +export const CLIENT_SECRET_SYMBOL: ConcealedSecret = Symbol('Concealed client secret'); export const CLIENT_SECRET_STRING = 'XXXX-super-secret-secret-XXXX'; +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), // Keep the rest of the actual module +})); + describe('AzureAuth', () => { + beforeEach(() => { + jest.resetModules(); + }); + describe('AzureCredentialsConfig', () => { it('`getDefaultCredentials()` should return the correct credentials based on whether the managed identity is enabled', () => { - const resultForManagedIdentityEnabled = getDefaultCredentials(true, AzureCloud.Public); - const resultForManagedIdentityDisabled = getDefaultCredentials(false, AzureCloud.Public); + jest.mocked(config).azure.managedIdentityEnabled = true; + const resultForManagedIdentityEnabled = getDefaultCredentials(); + + jest.mocked(config).azure.managedIdentityEnabled = false; + const resultForManagedIdentityDisabled = getDefaultCredentials(); expect(resultForManagedIdentityEnabled).toEqual({ authType: 'msi' }); expect(resultForManagedIdentityDisabled).toEqual({ authType: 'clientsecret', azureCloud: 'AzureCloud' }); }); - it("`getSecret()` should correctly return the client secret if it's not concealed", () => { - const resultFromServerSideSecret = getSecret(false, CLIENT_SECRET_STRING); - expect(resultFromServerSideSecret).toBe(CLIENT_SECRET_STRING); - - const resultFromSecureJSONDataSecret = typeof getSecret(true, ''); - expect(resultFromSecureJSONDataSecret).toBe('symbol'); - }); - describe('getCredentials()', () => { it('should return the correct managed identity credentials', () => { - // If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`. + // If `dataSourceSettings.authType === 'msi'` && `config.azure.managedIdentityEnabled === true`. + jest.mocked(config).azure.managedIdentityEnabled = true; const resultForManagedIdentityEnabled = getCredentials( // @ts-ignore - dataSourceSettingsWithMsiCredentials, - configWithManagedIdentityEnabled + dataSourceSettingsWithMsiCredentials ); - expect(resultForManagedIdentityEnabled).toEqual({ authType: AzureAuthType.MSI }); + expect(resultForManagedIdentityEnabled).toEqual({ authType: 'msi' }); - // If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`. + // If `dataSourceSettings.authType === 'msi'` but `config.azure.managedIdentityEnabled !== true`. // Default to basic client secret credentials. + jest.mocked(config).azure.managedIdentityEnabled = false; const resultForManagedIdentityEnabledInJSONButDisabledInConfig = getCredentials( // @ts-ignore - dataSourceSettingsWithMsiCredentials, - configWithManagedIdentityDisabled + dataSourceSettingsWithMsiCredentials ); expect(resultForManagedIdentityEnabledInJSONButDisabledInConfig).toEqual({ - authType: AzureAuthType.CLIENT_SECRET, + authType: 'clientsecret', azureCloud: 'AzureCloud', }); }); it('should return the correct client secret credentials', () => { const basicExpectedResult = { - authType: AzureAuthType.CLIENT_SECRET, + authType: 'clientsecret', azureCloud: 'AzureCloud', tenantId: 'XXXX-tenant-id-XXXX', clientId: 'XXXX-client-id-XXXX', }; - // If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`, + // If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == true`, // i.e. the client secret is stored on the server. + jest.mocked(config).azure.managedIdentityEnabled = false; const resultForClientSecretCredentialsOnServer = getCredentials( // @ts-ignore - dataSourceSettingsWithClientSecretOnServer, - configWithManagedIdentityDisabled + dataSourceSettingsWithClientSecretOnServer ); // Here we test the properties separately because the client secret is a symbol, // and since JS symobls are unique, we test via the `typeof` operator. - expect(resultForClientSecretCredentialsOnServer.authType).toEqual(AzureAuthType.CLIENT_SECRET); - expect(resultForClientSecretCredentialsOnServer.azureCloud).toEqual('AzureCloud'); - expect(resultForClientSecretCredentialsOnServer.tenantId).toEqual('XXXX-tenant-id-XXXX'); - expect(resultForClientSecretCredentialsOnServer.clientId).toEqual('XXXX-client-id-XXXX'); - expect(typeof resultForClientSecretCredentialsOnServer.clientSecret).toEqual('symbol'); + expect(resultForClientSecretCredentialsOnServer.authType).toEqual('clientsecret'); + expect( + instanceOfAzureCredential( + 'clientsecret', + resultForClientSecretCredentialsOnServer + ) + ).toEqual(true); + expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).azureCloud).toEqual( + 'AzureCloud' + ); + expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).tenantId).toEqual( + 'XXXX-tenant-id-XXXX' + ); + expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).clientId).toEqual( + 'XXXX-client-id-XXXX' + ); + expect(typeof (resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).clientSecret).toEqual( + 'symbol' + ); - // If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`, + // If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == false`, // i.e. the client secret is stored in the secureJson. + jest.mocked(config).azure.managedIdentityEnabled = false; const resultForClientSecretCredentialsInSecureJSON = getCredentials( // @ts-ignore - dataSourceSettingsWithClientSecretInSecureJSONData, - configWithManagedIdentityDisabled + dataSourceSettingsWithClientSecretInSecureJSONData ); expect(resultForClientSecretCredentialsInSecureJSON).toEqual({ ...basicExpectedResult, @@ -97,66 +120,68 @@ describe('AzureAuth', () => { describe('updateCredentials()', () => { it('should update the credentials for managed service identity correctly', () => { - // If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`. - const resultForMsiCredentials = updateCredentials( + // If `dataSourceSettings.authType === 'msi'` && `config.azure.managedIdentityEnabled === true`. + jest.mocked(config).azure.managedIdentityEnabled = true; + const resultForMsiCredentials = updateDatasourceCredentials( // @ts-ignore dataSourceSettingsWithMsiCredentials, - configWithManagedIdentityEnabled, { - authType: AzureAuthType.MSI, + authType: 'msi', } ); expect(resultForMsiCredentials).toEqual({ jsonData: { azureCredentials: { authType: 'msi' } } }); - // If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`. + // If `dataSourceSettings.authType === 'msi'` but `config.azure.managedIdentityEnabled !== true`. + jest.mocked(config).azure.managedIdentityEnabled = false; expect(() => - updateCredentials( + updateDatasourceCredentials( // @ts-ignore dataSourceSettingsWithMsiCredentials, - configWithManagedIdentityDisabled, { - authType: AzureAuthType.MSI, + authType: 'msi', } ) ).toThrow('Managed Identity authentication is not enabled in Grafana config.'); }); it('should update the credentials for client secret correctly', () => { - const basicClientSecretCredentials: AzureCredentialsType = { - authType: AzureAuthType.CLIENT_SECRET, - azureCloud: 'AzureCloud', + const basicClientSecretCredentials: AzureCredentials = { + authType: 'clientsecret', + azureCloud: AzureCloud.Public, tenantId: 'XXXX-tenant-id-XXXX', clientId: 'XXXX-client-id-XXXX', }; - // If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`. - const resultForClientSecretCredentials1 = updateCredentials( + // If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == true`. + jest.mocked(config).azure.managedIdentityEnabled = false; + const resultForClientSecretCredentials1 = updateDatasourceCredentials( // @ts-ignore dataSourceSettingsWithClientSecretOnServer, - configWithManagedIdentityDisabled, basicClientSecretCredentials ); - expect(resultForClientSecretCredentials1).toEqual({ - jsonData: { - azureCredentials: { ...basicClientSecretCredentials }, - }, - secureJsonData: { azureClientSecret: undefined }, - secureJsonFields: { azureClientSecret: false }, + + expect(resultForClientSecretCredentials1.jsonData.azureCredentials).toEqual(basicClientSecretCredentials); + expect(resultForClientSecretCredentials1.secureJsonData).toEqual({ azureClientSecret: undefined }); + expect(resultForClientSecretCredentials1.secureJsonFields).toEqual({ + azureClientSecret: false, + clientSecret: false, }); - // If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`. - const resultForClientSecretCredentials2 = updateCredentials( + // If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == false`. + jest.mocked(config).azure.managedIdentityEnabled = false; + const resultForClientSecretCredentials2 = updateDatasourceCredentials( // @ts-ignore dataSourceSettingsWithClientSecretInSecureJSONData, - configWithManagedIdentityDisabled, { ...basicClientSecretCredentials, clientSecret: 'XXXX-super-secret-secret-XXXX' } ); - expect(resultForClientSecretCredentials2).toEqual({ - jsonData: { - azureCredentials: { ...basicClientSecretCredentials }, - }, - secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' }, - secureJsonFields: { azureClientSecret: false }, + + expect(resultForClientSecretCredentials2.jsonData.azureCredentials).toEqual(basicClientSecretCredentials); + expect(resultForClientSecretCredentials2.secureJsonData).toEqual({ + azureClientSecret: 'XXXX-super-secret-secret-XXXX', + }); + expect(resultForClientSecretCredentials2.secureJsonFields).toEqual({ + azureClientSecret: false, + clientSecret: false, }); }); }); diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts index 8c4fda9035e..137a41cc909 100644 --- a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts +++ b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts @@ -1,8 +1,6 @@ -import { DataSourceSettings } from '@grafana/data'; +import { AzureDataSourceSettings } from '@grafana/azure-sdk'; import { GrafanaBootConfig } from '@grafana/runtime'; -import { AzureAuthSecureJSONDataType, AzureAuthJSONDataType, AzureAuthType } from '../types'; - export const configWithManagedIdentityEnabled: Partial = { azure: { managedIdentityEnabled: true, @@ -24,31 +22,22 @@ export const configWithManagedIdentityDisabled: Partial = { }, }; -export const dataSourceSettingsWithMsiCredentials: Partial< - DataSourceSettings -> = { - jsonData: { azureCredentials: { authType: AzureAuthType.MSI } }, -}; - -const basicJSONData = { - jsonData: { - azureCredentials: { - authType: AzureAuthType.CLIENT_SECRET, - tenantId: 'XXXX-tenant-id-XXXX', - clientId: 'XXXX-client-id-XXXX', - }, - }, +export const dataSourceSettingsWithMsiCredentials: Partial = { + jsonData: { azureCredentials: { authType: 'msi' } }, }; // Will return symbol as the secret is concealed -export const dataSourceSettingsWithClientSecretOnServer: Partial< - DataSourceSettings -> = { ...basicJSONData, secureJsonFields: { azureClientSecret: true } }; - +export const dataSourceSettingsWithClientSecretOnServer: Partial = { + jsonData: { + azureCredentials: { authType: 'clientsecret', clientId: 'XXXX-client-id-XXXX', tenantId: 'XXXX-tenant-id-XXXX' }, + }, + secureJsonFields: { azureClientSecret: true }, +}; // Will return the secret as a string from the secureJsonData -export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial< - DataSourceSettings -> = { - ...basicJSONData, - secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX', password: undefined }, +export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial = { + jsonData: { + azureCredentials: { authType: 'clientsecret', clientId: 'XXXX-client-id-XXXX', tenantId: 'XXXX-tenant-id-XXXX' }, + }, + secureJsonFields: { azureClientSecret: false }, + secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' }, }; diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx b/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx index 2213abff501..ba93261c515 100644 --- a/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx +++ b/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx @@ -1,24 +1,25 @@ import { useMemo } from 'react'; import { useEffectOnce } from 'react-use'; +import { AzureCredentials, AzureCloud, updateDatasourceCredentials } from '@grafana/azure-sdk'; +import { SelectableValue } from '@grafana/data'; import { config } from '@grafana/runtime'; import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types'; -import { AzureCredentialsType } from '../types'; - -import { KnownAzureClouds } from './AzureCredentials'; -import { getCredentials, updateCredentials } from './AzureCredentialsConfig'; +import { getCredentials } from './AzureCredentialsConfig'; import { AzureCredentialsForm } from './AzureCredentialsForm'; +export const KnownAzureClouds: Array> = [{ value: AzureCloud.Public, label: 'Azure' }]; + export const AzureAuthSettings = (props: HttpSettingsBaseProps) => { const { dataSourceConfig: dsSettings, onChange } = props; const managedIdentityEnabled = config.azure.managedIdentityEnabled; const azureEntraPasswordCredentialsEnabled = config.azure.azureEntraPasswordCredentialsEnabled; - const credentials = useMemo(() => getCredentials(dsSettings, config), [dsSettings]); + const credentials = useMemo(() => getCredentials(dsSettings), [dsSettings]); - const onCredentialsChange = (credentials: AzureCredentialsType): void => { - onChange(updateCredentials(dsSettings, config, credentials)); + const onCredentialsChange = (credentials: AzureCredentials): void => { + onChange(updateDatasourceCredentials(dsSettings, credentials)); }; // The auth type needs to be set on the first load of the data source diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts b/public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts deleted file mode 100644 index 123feb53bab..00000000000 --- a/public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { SelectableValue } from '@grafana/data'; - -import { AzureCredentialsType, AzureAuthType } from '../types'; - -export enum AzureCloud { - Public = 'AzureCloud', - None = '', -} - -export const KnownAzureClouds: Array> = [{ value: AzureCloud.Public, label: 'Azure' }]; - -export function isCredentialsComplete(credentials: AzureCredentialsType): boolean { - switch (credentials.authType) { - case AzureAuthType.MSI: - return true; - case AzureAuthType.CLIENT_SECRET: - return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret); - case AzureAuthType.AD_PASSWORD: - return !!(credentials.clientId && credentials.password && credentials.userId); - } -} diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts index acb493712ff..435edadbf34 100644 --- a/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts +++ b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts @@ -1,167 +1,26 @@ -import { DataSourceSettings } from '@grafana/data'; -import { GrafanaBootConfig } from '@grafana/runtime'; - import { - AzureCloud, - AzureCredentialsType, - ConcealedSecretType, - AzureAuthSecureJSONDataType, - AzureAuthJSONDataType, - AzureAuthType, -} from '../types'; + AzureCredentials, + AzureDataSourceSettings, + getDatasourceCredentials, + getDefaultAzureCloud, +} from '@grafana/azure-sdk'; +import { config } from '@grafana/runtime'; -export const getDefaultCredentials = (managedIdentityEnabled: boolean, cloud: string): AzureCredentialsType => { - if (managedIdentityEnabled) { - return { authType: AzureAuthType.MSI }; +export const getDefaultCredentials = (): AzureCredentials => { + if (config.azure.managedIdentityEnabled) { + return { authType: 'msi' }; } else { - return { authType: AzureAuthType.CLIENT_SECRET, azureCloud: cloud }; + return { authType: 'clientsecret', azureCloud: getDefaultAzureCloud() }; } }; -export const getSecret = ( - storedServerSide: boolean, - secret: string | symbol | undefined -): undefined | string | ConcealedSecretType => { - const concealedSecret: ConcealedSecretType = Symbol('Concealed client secret'); - if (storedServerSide) { - // The secret is concealed server side, so return the symbol - return concealedSecret; - } else { - return typeof secret === 'string' && secret.length > 0 ? secret : undefined; +export const getCredentials = (dsSettings: AzureDataSourceSettings): AzureCredentials => { + const credentials = getDatasourceCredentials(dsSettings); + if (credentials) { + return credentials; } -}; - -export const getCredentials = ( - dsSettings: DataSourceSettings, - bootConfig: GrafanaBootConfig -): AzureCredentialsType => { - // JSON data - const credentials = dsSettings.jsonData?.azureCredentials; - - // Secure JSON data/fields - const clientSecretStoredServerSide = dsSettings.secureJsonFields?.azureClientSecret; - const clientSecret = dsSettings.secureJsonData?.azureClientSecret; - const passwordStoredServerSide = dsSettings.secureJsonFields?.password; - const password = dsSettings.secureJsonData?.password; - - // BootConfig data - const managedIdentityEnabled = !!bootConfig.azure?.managedIdentityEnabled; - const cloud = bootConfig.azure?.cloud || AzureCloud.Public; // If no credentials saved, then return empty credentials // of type based on whether the managed identity enabled - if (!credentials) { - return getDefaultCredentials(managedIdentityEnabled, cloud); - } - - switch (credentials.authType) { - case AzureAuthType.MSI: - if (managedIdentityEnabled) { - return { - authType: AzureAuthType.MSI, - }; - } else { - // If authentication type is managed identity but managed identities were disabled in Grafana config, - // then we should fallback to an empty app registration (client secret) configuration - return { - authType: AzureAuthType.CLIENT_SECRET, - azureCloud: cloud, - }; - } - case AzureAuthType.CLIENT_SECRET: - return { - authType: AzureAuthType.CLIENT_SECRET, - azureCloud: credentials.azureCloud || cloud, - tenantId: credentials.tenantId, - clientId: credentials.clientId, - clientSecret: getSecret(clientSecretStoredServerSide, clientSecret), - }; - case AzureAuthType.AD_PASSWORD: - return { - authType: AzureAuthType.AD_PASSWORD, - userId: credentials.userId, - clientId: credentials.clientId, - password: getSecret(passwordStoredServerSide, password), - }; - } -}; - -export const updateCredentials = ( - dsSettings: DataSourceSettings, - bootConfig: GrafanaBootConfig, - credentials: AzureCredentialsType -): DataSourceSettings => { - // BootConfig data - const managedIdentityEnabled = !!bootConfig.azure?.managedIdentityEnabled; - const cloud = bootConfig.azure?.cloud || AzureCloud.Public; - - switch (credentials.authType) { - case AzureAuthType.MSI: - if (!managedIdentityEnabled) { - throw new Error('Managed Identity authentication is not enabled in Grafana config.'); - } - - dsSettings = { - ...dsSettings, - jsonData: { - ...dsSettings.jsonData, - azureCredentials: { - authType: AzureAuthType.MSI, - }, - }, - }; - - return dsSettings; - - case AzureAuthType.CLIENT_SECRET: - dsSettings = { - ...dsSettings, - jsonData: { - ...dsSettings.jsonData, - azureCredentials: { - authType: AzureAuthType.CLIENT_SECRET, - azureCloud: credentials.azureCloud || cloud, - tenantId: credentials.tenantId, - clientId: credentials.clientId, - }, - }, - secureJsonData: { - ...dsSettings.secureJsonData, - azureClientSecret: - typeof credentials.clientSecret === 'string' && credentials.clientSecret.length > 0 - ? credentials.clientSecret - : undefined, - }, - secureJsonFields: { - ...dsSettings.secureJsonFields, - azureClientSecret: typeof credentials.clientSecret === 'symbol', - }, - }; - - return dsSettings; - - case AzureAuthType.AD_PASSWORD: - return { - ...dsSettings, - jsonData: { - ...dsSettings.jsonData, - azureCredentials: { - authType: AzureAuthType.AD_PASSWORD, - userId: credentials.userId, - clientId: credentials.clientId, - }, - }, - secureJsonData: { - ...dsSettings.secureJsonData, - password: - typeof credentials.password === 'string' && credentials.password.length > 0 - ? credentials.password - : undefined, - }, - secureJsonFields: { - ...dsSettings.secureJsonFields, - password: typeof credentials.password === 'symbol', - }, - }; - } + return getDefaultCredentials(); }; diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx index 442f75bb7fa..ba2124a3c38 100644 --- a/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx +++ b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx @@ -1,16 +1,15 @@ import { ChangeEvent } from 'react'; +import { AzureCredentials, AzureAuthType } from '@grafana/azure-sdk'; import { SelectableValue } from '@grafana/data'; import { Button, Field, Select, Input } from '@grafana/ui/src/components'; -import { AzureCredentialsType, AzureAuthType } from '../types'; - export interface Props { managedIdentityEnabled: boolean; azureEntraPasswordCredentialsEnabled: boolean; - credentials: AzureCredentialsType; + credentials: AzureCredentials; azureCloudOptions?: SelectableValue[]; - onCredentialsChange: (updatedCredentials: AzureCredentialsType) => void; + onCredentialsChange: (updatedCredentials: AzureCredentials) => void; disabled?: boolean; } @@ -26,9 +25,89 @@ export const AzureCredentialsForm = (props: Props) => { const onAuthTypeChange = (selected: SelectableValue) => { if (onCredentialsChange) { - const updated: AzureCredentialsType = { + const updated: AzureCredentials = { ...credentials, - authType: selected.value || AzureAuthType.MSI, + authType: selected.value || 'msi', + }; + onCredentialsChange(updated); + } + }; + + const onAzureCloudChange = (selected: SelectableValue) => { + if (credentials.authType === 'clientsecret') { + const updated: AzureCredentials = { + ...credentials, + azureCloud: selected.value, + }; + onCredentialsChange(updated); + } + }; + + const onTenantIdChange = (event: ChangeEvent) => { + if (credentials.authType === 'clientsecret') { + const updated: AzureCredentials = { + ...credentials, + tenantId: event.target.value, + }; + onCredentialsChange(updated); + } + }; + + const onClientIdChange = (event: ChangeEvent) => { + if (credentials.authType === 'clientsecret' || credentials.authType === 'ad-password') { + const updated: AzureCredentials = { + ...credentials, + clientId: event.target.value, + }; + onCredentialsChange(updated); + } + }; + + const onClientSecretChange = (event: ChangeEvent) => { + if (credentials.authType === 'clientsecret') { + const updated: AzureCredentials = { + ...credentials, + clientSecret: event.target.value, + }; + onCredentialsChange(updated); + } + }; + + const onClientSecretReset = () => { + if (credentials.authType === 'clientsecret') { + const updated: AzureCredentials = { + ...credentials, + clientSecret: '', + }; + onCredentialsChange(updated); + } + }; + + const onUserIdChange = (event: ChangeEvent) => { + if (credentials.authType === 'ad-password') { + const updated: AzureCredentials = { + ...credentials, + userId: event.target.value, + }; + onCredentialsChange(updated); + } + }; + + const onPasswordChange = (event: ChangeEvent) => { + if (credentials.authType === 'ad-password') { + const updated: AzureCredentials = { + ...credentials, + password: event.target.value, + }; + onCredentialsChange(updated); + } + }; + + const onPasswordReset = () => { + if (credentials.authType === 'ad-password') { + const updated: AzureCredentials = { + ...credentials, + password: '', }; onCredentialsChange(updated); } @@ -36,33 +115,23 @@ export const AzureCredentialsForm = (props: Props) => { const authTypeOptions: Array> = [ { - value: AzureAuthType.CLIENT_SECRET, + value: 'clientsecret', label: 'App Registration', }, ]; if (managedIdentityEnabled) { authTypeOptions.push({ - value: AzureAuthType.MSI, + value: 'msi', label: 'Managed Identity', }); } if (azureEntraPasswordCredentialsEnabled) { authTypeOptions.push({ - value: AzureAuthType.AD_PASSWORD, + value: 'ad-password', label: 'Azure Entra Password', }); } - const onInputChange = ({ property, value }: { property: keyof AzureCredentialsType; value: string }) => { - if (onCredentialsChange) { - const updated: AzureCredentialsType = { - ...credentials, - [property]: value, - }; - onCredentialsChange(updated); - } - }; - return (
{ disabled={disabled} /> - {credentials.authType === AzureAuthType.CLIENT_SECRET && ( + {credentials.authType === 'clientsecret' && ( <> {azureCloudOptions && ( ) => { - const value = event.target.value; - onInputChange({ property: 'userId', value }); - }} + onChange={onUserIdChange} disabled={disabled} aria-label="User ID" /> @@ -205,10 +252,7 @@ export const AzureCredentialsForm = (props: Props) => { ) => { - const value = event.target.value; - onInputChange({ property: 'clientId', value }); - }} + onChange={onClientIdChange} disabled={disabled} aria-label="Application Client ID" /> @@ -224,14 +268,7 @@ export const AzureCredentialsForm = (props: Props) => { data-testid={'password'} width={45} /> -
@@ -248,10 +285,7 @@ export const AzureCredentialsForm = (props: Props) => { width={45} aria-label="Password" value={credentials.password || ''} - onChange={(event: ChangeEvent) => { - const value = event.target.value; - onInputChange({ property: 'password', value }); - }} + onChange={onPasswordChange} id="password" disabled={disabled} /> diff --git a/public/app/plugins/datasource/mssql/types.ts b/public/app/plugins/datasource/mssql/types.ts index 2397a0d31b6..4562abb7623 100644 --- a/public/app/plugins/datasource/mssql/types.ts +++ b/public/app/plugins/datasource/mssql/types.ts @@ -1,4 +1,4 @@ -import { DataSourceJsonData } from '@grafana/data'; +import { AzureCredentials } from '@grafana/azure-sdk'; import { SQLOptions } from '@grafana/sql'; import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types'; @@ -17,37 +17,13 @@ export enum MSSQLEncryptOptions { false = 'false', true = 'true', } - -export enum AzureCloud { - Public = 'AzureCloud', - None = '', -} - -export type ConcealedSecretType = symbol; - -export enum AzureAuthType { - MSI = 'msi', - CLIENT_SECRET = 'clientsecret', - AD_PASSWORD = 'ad-password', -} - -export interface AzureCredentialsType { - authType: AzureAuthType; - azureCloud?: string; - tenantId?: string; - clientId?: string; - clientSecret?: string | ConcealedSecretType; - userId?: string; - password?: string | ConcealedSecretType; -} - export interface MssqlOptions extends SQLOptions { authenticationType?: MSSQLAuthenticationType; encrypt?: MSSQLEncryptOptions; sslRootCertFile?: string; serverName?: string; connectionTimeout?: number; - azureCredentials?: AzureCredentialsType; + azureCredentials?: AzureCredentials; keytabFilePath?: string; credentialCache?: string; credentialCacheLookupFile?: string; @@ -60,15 +36,6 @@ export interface MssqlSecureOptions { password?: string; } -export type AzureAuthJSONDataType = DataSourceJsonData & { - azureCredentials: AzureCredentialsType; -}; - -export type AzureAuthSecureJSONDataType = { - azureClientSecret: undefined | string | ConcealedSecretType; - password: undefined | string | ConcealedSecretType; -}; - export type AzureAuthConfigType = { azureAuthIsSupported: boolean; azureAuthSettingsUI: (props: HttpSettingsBaseProps) => JSX.Element;