Skip to content

Commit 766b84f

Browse files
committed
Updated MSSQL Grafana Datasource
1 parent 23aa5c5 commit 766b84f

File tree

19 files changed

+324
-407
lines changed

19 files changed

+324
-407
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { getSchema } from './MSSqlMetaQuery';
2+
3+
describe('getSchema', () => {
4+
const database = 'foo';
5+
const table = 'bar';
6+
const schema = getSchema(database, table);
7+
it('should escapte database names', () => {
8+
expect(schema).toContain(`USE [${database}]`);
9+
});
10+
});

Source/Applications/openHistorian/openHistorian/Grafana/public/app/plugins/datasource/mssql/MSSqlMetaQuery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function getSchemaAndName(database?: string) {
1010

1111
export function getSchema(database?: string, table?: string) {
1212
return `
13-
USE ${database}
13+
USE [${database}]
1414
SELECT COLUMN_NAME as 'column',DATA_TYPE as 'type'
1515
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='${table}';`;
1616
}

Source/Applications/openHistorian/openHistorian/Grafana/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,115 @@
1-
import { AzureAuthType, AzureCloud, AzureCredentialsType, ConcealedSecretType } from '../types';
1+
import {
2+
AzureCredentials,
3+
AzureCloud,
4+
ConcealedSecret,
5+
AzureClientSecretCredentials,
6+
instanceOfAzureCredential,
7+
updateDatasourceCredentials,
8+
} from '@grafana/azure-sdk';
9+
import { config } from '@grafana/runtime';
210

311
import {
4-
configWithManagedIdentityEnabled,
5-
configWithManagedIdentityDisabled,
612
dataSourceSettingsWithMsiCredentials,
713
dataSourceSettingsWithClientSecretOnServer,
814
dataSourceSettingsWithClientSecretInSecureJSONData,
915
} from './AzureAuth.testMocks';
10-
import { getDefaultCredentials, getSecret, getCredentials, updateCredentials } from './AzureCredentialsConfig';
16+
import { getDefaultCredentials, getCredentials } from './AzureCredentialsConfig';
1117

1218
// NOTE: @ts-ignores are used to ignore the type errors that are thrown when passing in the mocks.
1319
// This is because the mocks are partials of the actual types, so the types are not complete.
1420

15-
export const CLIENT_SECRET_SYMBOL: ConcealedSecretType = Symbol('Concealed client secret');
21+
export const CLIENT_SECRET_SYMBOL: ConcealedSecret = Symbol('Concealed client secret');
1622

1723
export const CLIENT_SECRET_STRING = 'XXXX-super-secret-secret-XXXX';
1824

25+
jest.mock('@grafana/runtime', () => ({
26+
...jest.requireActual('@grafana/runtime'), // Keep the rest of the actual module
27+
}));
28+
1929
describe('AzureAuth', () => {
30+
beforeEach(() => {
31+
jest.resetModules();
32+
});
33+
2034
describe('AzureCredentialsConfig', () => {
2135
it('`getDefaultCredentials()` should return the correct credentials based on whether the managed identity is enabled', () => {
22-
const resultForManagedIdentityEnabled = getDefaultCredentials(true, AzureCloud.Public);
23-
const resultForManagedIdentityDisabled = getDefaultCredentials(false, AzureCloud.Public);
36+
jest.mocked(config).azure.managedIdentityEnabled = true;
37+
const resultForManagedIdentityEnabled = getDefaultCredentials();
38+
39+
jest.mocked(config).azure.managedIdentityEnabled = false;
40+
const resultForManagedIdentityDisabled = getDefaultCredentials();
2441

2542
expect(resultForManagedIdentityEnabled).toEqual({ authType: 'msi' });
2643
expect(resultForManagedIdentityDisabled).toEqual({ authType: 'clientsecret', azureCloud: 'AzureCloud' });
2744
});
2845

29-
it("`getSecret()` should correctly return the client secret if it's not concealed", () => {
30-
const resultFromServerSideSecret = getSecret(false, CLIENT_SECRET_STRING);
31-
expect(resultFromServerSideSecret).toBe(CLIENT_SECRET_STRING);
32-
33-
const resultFromSecureJSONDataSecret = typeof getSecret(true, '');
34-
expect(resultFromSecureJSONDataSecret).toBe('symbol');
35-
});
36-
3746
describe('getCredentials()', () => {
3847
it('should return the correct managed identity credentials', () => {
39-
// If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`.
48+
// If `dataSourceSettings.authType === 'msi'` && `config.azure.managedIdentityEnabled === true`.
49+
jest.mocked(config).azure.managedIdentityEnabled = true;
4050
const resultForManagedIdentityEnabled = getCredentials(
4151
// @ts-ignore
42-
dataSourceSettingsWithMsiCredentials,
43-
configWithManagedIdentityEnabled
52+
dataSourceSettingsWithMsiCredentials
4453
);
45-
expect(resultForManagedIdentityEnabled).toEqual({ authType: AzureAuthType.MSI });
54+
expect(resultForManagedIdentityEnabled).toEqual({ authType: 'msi' });
4655

47-
// If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`.
56+
// If `dataSourceSettings.authType === 'msi'` but `config.azure.managedIdentityEnabled !== true`.
4857
// Default to basic client secret credentials.
58+
jest.mocked(config).azure.managedIdentityEnabled = false;
4959
const resultForManagedIdentityEnabledInJSONButDisabledInConfig = getCredentials(
5060
// @ts-ignore
51-
dataSourceSettingsWithMsiCredentials,
52-
configWithManagedIdentityDisabled
61+
dataSourceSettingsWithMsiCredentials
5362
);
5463
expect(resultForManagedIdentityEnabledInJSONButDisabledInConfig).toEqual({
55-
authType: AzureAuthType.CLIENT_SECRET,
64+
authType: 'clientsecret',
5665
azureCloud: 'AzureCloud',
5766
});
5867
});
5968

6069
it('should return the correct client secret credentials', () => {
6170
const basicExpectedResult = {
62-
authType: AzureAuthType.CLIENT_SECRET,
71+
authType: 'clientsecret',
6372
azureCloud: 'AzureCloud',
6473
tenantId: 'XXXX-tenant-id-XXXX',
6574
clientId: 'XXXX-client-id-XXXX',
6675
};
6776

68-
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`,
77+
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == true`,
6978
// i.e. the client secret is stored on the server.
79+
jest.mocked(config).azure.managedIdentityEnabled = false;
7080
const resultForClientSecretCredentialsOnServer = getCredentials(
7181
// @ts-ignore
72-
dataSourceSettingsWithClientSecretOnServer,
73-
configWithManagedIdentityDisabled
82+
dataSourceSettingsWithClientSecretOnServer
7483
);
7584

7685
// Here we test the properties separately because the client secret is a symbol,
7786
// and since JS symobls are unique, we test via the `typeof` operator.
78-
expect(resultForClientSecretCredentialsOnServer.authType).toEqual(AzureAuthType.CLIENT_SECRET);
79-
expect(resultForClientSecretCredentialsOnServer.azureCloud).toEqual('AzureCloud');
80-
expect(resultForClientSecretCredentialsOnServer.tenantId).toEqual('XXXX-tenant-id-XXXX');
81-
expect(resultForClientSecretCredentialsOnServer.clientId).toEqual('XXXX-client-id-XXXX');
82-
expect(typeof resultForClientSecretCredentialsOnServer.clientSecret).toEqual('symbol');
87+
expect(resultForClientSecretCredentialsOnServer.authType).toEqual('clientsecret');
88+
expect(
89+
instanceOfAzureCredential<AzureClientSecretCredentials>(
90+
'clientsecret',
91+
resultForClientSecretCredentialsOnServer
92+
)
93+
).toEqual(true);
94+
expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).azureCloud).toEqual(
95+
'AzureCloud'
96+
);
97+
expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).tenantId).toEqual(
98+
'XXXX-tenant-id-XXXX'
99+
);
100+
expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).clientId).toEqual(
101+
'XXXX-client-id-XXXX'
102+
);
103+
expect(typeof (resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).clientSecret).toEqual(
104+
'symbol'
105+
);
83106

84-
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`,
107+
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == false`,
85108
// i.e. the client secret is stored in the secureJson.
109+
jest.mocked(config).azure.managedIdentityEnabled = false;
86110
const resultForClientSecretCredentialsInSecureJSON = getCredentials(
87111
// @ts-ignore
88-
dataSourceSettingsWithClientSecretInSecureJSONData,
89-
configWithManagedIdentityDisabled
112+
dataSourceSettingsWithClientSecretInSecureJSONData
90113
);
91114
expect(resultForClientSecretCredentialsInSecureJSON).toEqual({
92115
...basicExpectedResult,
@@ -97,66 +120,68 @@ describe('AzureAuth', () => {
97120

98121
describe('updateCredentials()', () => {
99122
it('should update the credentials for managed service identity correctly', () => {
100-
// If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`.
101-
const resultForMsiCredentials = updateCredentials(
123+
// If `dataSourceSettings.authType === 'msi'` && `config.azure.managedIdentityEnabled === true`.
124+
jest.mocked(config).azure.managedIdentityEnabled = true;
125+
const resultForMsiCredentials = updateDatasourceCredentials(
102126
// @ts-ignore
103127
dataSourceSettingsWithMsiCredentials,
104-
configWithManagedIdentityEnabled,
105128
{
106-
authType: AzureAuthType.MSI,
129+
authType: 'msi',
107130
}
108131
);
109132
expect(resultForMsiCredentials).toEqual({ jsonData: { azureCredentials: { authType: 'msi' } } });
110133

111-
// If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`.
134+
// If `dataSourceSettings.authType === 'msi'` but `config.azure.managedIdentityEnabled !== true`.
135+
jest.mocked(config).azure.managedIdentityEnabled = false;
112136
expect(() =>
113-
updateCredentials(
137+
updateDatasourceCredentials(
114138
// @ts-ignore
115139
dataSourceSettingsWithMsiCredentials,
116-
configWithManagedIdentityDisabled,
117140
{
118-
authType: AzureAuthType.MSI,
141+
authType: 'msi',
119142
}
120143
)
121144
).toThrow('Managed Identity authentication is not enabled in Grafana config.');
122145
});
123146

124147
it('should update the credentials for client secret correctly', () => {
125-
const basicClientSecretCredentials: AzureCredentialsType = {
126-
authType: AzureAuthType.CLIENT_SECRET,
127-
azureCloud: 'AzureCloud',
148+
const basicClientSecretCredentials: AzureCredentials = {
149+
authType: 'clientsecret',
150+
azureCloud: AzureCloud.Public,
128151
tenantId: 'XXXX-tenant-id-XXXX',
129152
clientId: 'XXXX-client-id-XXXX',
130153
};
131154

132-
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`.
133-
const resultForClientSecretCredentials1 = updateCredentials(
155+
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == true`.
156+
jest.mocked(config).azure.managedIdentityEnabled = false;
157+
const resultForClientSecretCredentials1 = updateDatasourceCredentials(
134158
// @ts-ignore
135159
dataSourceSettingsWithClientSecretOnServer,
136-
configWithManagedIdentityDisabled,
137160
basicClientSecretCredentials
138161
);
139-
expect(resultForClientSecretCredentials1).toEqual({
140-
jsonData: {
141-
azureCredentials: { ...basicClientSecretCredentials },
142-
},
143-
secureJsonData: { azureClientSecret: undefined },
144-
secureJsonFields: { azureClientSecret: false },
162+
163+
expect(resultForClientSecretCredentials1.jsonData.azureCredentials).toEqual(basicClientSecretCredentials);
164+
expect(resultForClientSecretCredentials1.secureJsonData).toEqual({ azureClientSecret: undefined });
165+
expect(resultForClientSecretCredentials1.secureJsonFields).toEqual({
166+
azureClientSecret: false,
167+
clientSecret: false,
145168
});
146169

147-
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`.
148-
const resultForClientSecretCredentials2 = updateCredentials(
170+
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == false`.
171+
jest.mocked(config).azure.managedIdentityEnabled = false;
172+
const resultForClientSecretCredentials2 = updateDatasourceCredentials(
149173
// @ts-ignore
150174
dataSourceSettingsWithClientSecretInSecureJSONData,
151-
configWithManagedIdentityDisabled,
152175
{ ...basicClientSecretCredentials, clientSecret: 'XXXX-super-secret-secret-XXXX' }
153176
);
154-
expect(resultForClientSecretCredentials2).toEqual({
155-
jsonData: {
156-
azureCredentials: { ...basicClientSecretCredentials },
157-
},
158-
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' },
159-
secureJsonFields: { azureClientSecret: false },
177+
178+
expect(resultForClientSecretCredentials2.jsonData.azureCredentials).toEqual(basicClientSecretCredentials);
179+
expect(resultForClientSecretCredentials2.secureJsonData).toEqual({
180+
azureClientSecret: 'XXXX-super-secret-secret-XXXX',
181+
});
182+
expect(resultForClientSecretCredentials2.secureJsonFields).toEqual({
183+
azureClientSecret: false,
184+
clientSecret: false,
160185
});
161186
});
162187
});
Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { DataSourceSettings } from '@grafana/data';
1+
import { AzureDataSourceSettings } from '@grafana/azure-sdk';
22
import { GrafanaBootConfig } from '@grafana/runtime';
33

4-
import { AzureAuthSecureJSONDataType, AzureAuthJSONDataType, AzureAuthType } from '../types';
5-
64
export const configWithManagedIdentityEnabled: Partial<GrafanaBootConfig> = {
75
azure: {
86
managedIdentityEnabled: true,
@@ -24,31 +22,22 @@ export const configWithManagedIdentityDisabled: Partial<GrafanaBootConfig> = {
2422
},
2523
};
2624

27-
export const dataSourceSettingsWithMsiCredentials: Partial<
28-
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
29-
> = {
30-
jsonData: { azureCredentials: { authType: AzureAuthType.MSI } },
25+
export const dataSourceSettingsWithMsiCredentials: Partial<AzureDataSourceSettings> = {
26+
jsonData: { azureCredentials: { authType: 'msi' } },
3127
};
3228

33-
const basicJSONData = {
29+
// Will return symbol as the secret is concealed
30+
export const dataSourceSettingsWithClientSecretOnServer: Partial<AzureDataSourceSettings> = {
3431
jsonData: {
35-
azureCredentials: {
36-
authType: AzureAuthType.CLIENT_SECRET,
37-
tenantId: 'XXXX-tenant-id-XXXX',
38-
clientId: 'XXXX-client-id-XXXX',
39-
},
32+
azureCredentials: { authType: 'clientsecret', clientId: 'XXXX-client-id-XXXX', tenantId: 'XXXX-tenant-id-XXXX' },
4033
},
34+
secureJsonFields: { azureClientSecret: true },
4135
};
42-
43-
// Will return symbol as the secret is concealed
44-
export const dataSourceSettingsWithClientSecretOnServer: Partial<
45-
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
46-
> = { ...basicJSONData, secureJsonFields: { azureClientSecret: true } };
47-
4836
// Will return the secret as a string from the secureJsonData
49-
export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial<
50-
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
51-
> = {
52-
...basicJSONData,
53-
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX', password: undefined },
37+
export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial<AzureDataSourceSettings> = {
38+
jsonData: {
39+
azureCredentials: { authType: 'clientsecret', clientId: 'XXXX-client-id-XXXX', tenantId: 'XXXX-tenant-id-XXXX' },
40+
},
41+
secureJsonFields: { azureClientSecret: false },
42+
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' },
5443
};

Source/Applications/openHistorian/openHistorian/Grafana/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import { useMemo } from 'react';
22
import { useEffectOnce } from 'react-use';
33

4+
import { AzureCredentials, AzureCloud, updateDatasourceCredentials } from '@grafana/azure-sdk';
5+
import { SelectableValue } from '@grafana/data';
46
import { config } from '@grafana/runtime';
57
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
68

7-
import { AzureCredentialsType } from '../types';
8-
9-
import { KnownAzureClouds } from './AzureCredentials';
10-
import { getCredentials, updateCredentials } from './AzureCredentialsConfig';
9+
import { getCredentials } from './AzureCredentialsConfig';
1110
import { AzureCredentialsForm } from './AzureCredentialsForm';
1211

12+
export const KnownAzureClouds: Array<SelectableValue<AzureCloud>> = [{ value: AzureCloud.Public, label: 'Azure' }];
13+
1314
export const AzureAuthSettings = (props: HttpSettingsBaseProps) => {
1415
const { dataSourceConfig: dsSettings, onChange } = props;
1516
const managedIdentityEnabled = config.azure.managedIdentityEnabled;
1617
const azureEntraPasswordCredentialsEnabled = config.azure.azureEntraPasswordCredentialsEnabled;
1718

18-
const credentials = useMemo(() => getCredentials(dsSettings, config), [dsSettings]);
19+
const credentials = useMemo(() => getCredentials(dsSettings), [dsSettings]);
1920

20-
const onCredentialsChange = (credentials: AzureCredentialsType): void => {
21-
onChange(updateCredentials(dsSettings, config, credentials));
21+
const onCredentialsChange = (credentials: AzureCredentials): void => {
22+
onChange(updateDatasourceCredentials(dsSettings, credentials));
2223
};
2324

2425
// The auth type needs to be set on the first load of the data source

Source/Applications/openHistorian/openHistorian/Grafana/public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)