diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17136f0..3807bcc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,9 @@ jobs: npm i npm run lint npm run testPipeline + env: + AUTO_TEST: true + LOGIN_TEST: true - working-directory: ./azure-kusto-ingest run: | npm i @@ -38,6 +41,8 @@ jobs: TENANT_ID: "72f988bf-86f1-41af-91ab-2d7cd011db47" ENGINE_CONNECTION_STRING: "https://sdkse2etest.eastus.kusto.windows.net" DM_CONNECTION_STRING: "https://ingest-sdkse2etest.eastus.kusto.windows.net" + AUTO_TEST: true + LOGIN_TEST: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 - name: Publish Unit Test Results diff --git a/azure-kusto-data/source/client.ts b/azure-kusto-data/source/client.ts index 8c04a1b..bf1b8ca 100644 --- a/azure-kusto-data/source/client.ts +++ b/azure-kusto-data/source/client.ts @@ -141,7 +141,10 @@ export class KustoClient { headers["x-ms-client-request-id"] = clientRequestId || clientRequestPrefix + `${uuidv4()}`; - headers.Authorization = await this.aadHelper._getAuthHeader(); + const authHeader = await this.aadHelper.getAuthHeader(); + if (authHeader != null) { + headers.Authorization = authHeader; + } return this._doRequest(endpoint, executionType, headers, payloadContent, timeout, properties); } diff --git a/azure-kusto-data/source/connectionBuilder.ts b/azure-kusto-data/source/connectionBuilder.ts index 87e20a1..2d6e0a2 100644 --- a/azure-kusto-data/source/connectionBuilder.ts +++ b/azure-kusto-data/source/connectionBuilder.ts @@ -2,87 +2,111 @@ // Licensed under the MIT License. import { DeviceCodeResponse } from "@azure/msal-common"; +import { KeyOfType } from "./typeUtilts"; interface MappingType { - propName: string, mappedTo: string, - validNames: string[] + validNames: string[], + isSecret?: boolean, + isBool?: boolean, } -const KeywordMapping: { [name: string]: MappingType } = Object.freeze({ +type KcsbMappedKeys = KeyOfType; + +// This type gurantess that we don't have properties in KeywordMapping that don't exist in KustoConnectionStringBuilder +type KeywordMappingRecordType = Partial>; + +const KeywordMapping: KeywordMappingRecordType = Object.freeze>({ dataSource: { - propName: "dataSource", mappedTo: "Data Source", - validNames: ["data source", "addr", "address", "network address", "server"] + validNames: ["data source", "addr", "address", "network address", "server"], + }, + aadFederatedSecurity: { + mappedTo: "AAD Federated Security", + validNames: ["aad federated security", "federated security", "federated", "fed", "aadfed"], + isBool: true, }, aadUserId: { - propName: "aadUserId", mappedTo: "AAD User ID", - validNames: ["aad user id"] + validNames: ["aad user id"], }, password: { - propName: "password", mappedTo: "Password", - validNames: ["password", "pwd"] + validNames: ["password", "pwd"], + isSecret: true, }, applicationClientId: { - propName: "applicationClientId", mappedTo: "Application Client Id", - validNames: ["application client id", "appclientid"] + validNames: ["application client id", "appclientid"], }, applicationKey: { - propName: "applicationKey", mappedTo: "Application Key", - validNames: ["application key", "appkey"] + validNames: ["application key", "appkey"], + isSecret: true, }, applicationCertificatePrivateKey: { - propName: "applicationCertificatePrivateKey", - mappedTo: "application Certificate PrivateKey", - validNames: ["application Certificate PrivateKey"] + mappedTo: "Application Certificate PrivateKey", + validNames: ["Application Certificate PrivateKey"], + isSecret: true, }, applicationCertificateThumbprint: { - propName: "applicationCertificateThumbprint", mappedTo: "Application Certificate Thumbprint", - validNames: ["application certificate thumbprint"] + validNames: ["application certificate thumbprint", "AppCert"], }, applicationCertificateX5c: { - propName: "applicationCertificateX5c", mappedTo: "Application Certificate x5c", - validNames: ["application certificate x5c"] + validNames: ["application certificate x5c", "Application Certificate Send Public Certificate", "Application Certificate SendX5c", "SendX5c"], }, authorityId: { - propName: "authorityId", mappedTo: "Authority Id", - validNames: ["authority id", "authorityid", "authority", "tenantid", "tenant", "tid"] - } + validNames: ["authority id", "authorityid", "authority", "tenantid", "tenant", "tid"], + }, }); -const getPropName = (key: string): string => { +const getPropName = (key: string): [string, MappingType] => { const _key = key.trim().toLowerCase(); for (const keyword of Object.keys(KeywordMapping)) { - const k = KeywordMapping[keyword]; - if (k.validNames.indexOf(_key) >= 0) { - return k.propName; + const k = KeywordMapping[keyword as KcsbMappedKeys]; + if (!k) { + continue; + } + if (k.validNames.map(n => n.trim().toLowerCase()).indexOf(_key) >= 0) { + return [keyword, k]; } } - throw new Error(key); + throw new Error("Failed to get prop: " + key); }; + export class KustoConnectionStringBuilder { - [prop: string]: string | boolean | ((info: DeviceCodeResponse) => void) | undefined; + static readonly SecretReplacement = "****"; + // tslint:disable-next-line:no-console + static defaultDeviceCallback: (response: DeviceCodeResponse) => void = (response) => console.log(response.message); + dataSource?: string; + aadFederatedSecurity?: boolean; aadUserId?: string; password?: string; applicationClientId?: string; + msiClientId?: string; applicationKey?: string; applicationCertificatePrivateKey?: string; applicationCertificateThumbprint?: string; - authorityId?: string; + applicationCertificateX5c?: string; + authorityId: string = "common"; deviceCodeCallback?: (response: DeviceCodeResponse) => void; + tokenProvider?: () => Promise; + loginHint?: string; + timeoutMs?: number; + accessToken?: string; + useDeviceCodeAuth?: boolean; + useUserPromptAuth?: boolean; + useAzLoginAuth?: boolean; + useManagedIdentityAuth?: boolean; constructor(connectionString: string) { - if (!connectionString || connectionString.trim().length === 0) throw new Error("Missing connection string"); + if (connectionString.trim().length === 0) throw new Error("Missing connection string"); if (connectionString.endsWith("/") || connectionString.endsWith("\\")) { connectionString = connectionString.slice(0, -1); @@ -92,90 +116,165 @@ export class KustoConnectionStringBuilder { connectionString = "Data Source=" + connectionString; } - this[KeywordMapping.authorityId.propName] = "common"; - const params = connectionString.split(";"); for (const item of params) { const kvp = item.split("="); - this[getPropName(kvp[0])] = kvp[1].trim(); + const [mappingTypeName, mappingType] = getPropName(kvp[0]); + if (mappingType.isBool) { + this[mappingTypeName as KeyOfType] = kvp[1].trim().toLowerCase() === "true"; + } else { + this[mappingTypeName as KeyOfType] = kvp[1]?.trim(); + } } } + + toString(removeSecrets: boolean = true): string { + return Object.entries(KeywordMapping).map(([key, mappingType]) => { + const value = this[key as KcsbMappedKeys]; + if (!mappingType || value === undefined) { + return ""; + } + if (mappingType.isSecret && removeSecrets) { + return `${mappingType.mappedTo}=${KustoConnectionStringBuilder.SecretReplacement}`; + } + + return `${mappingType.mappedTo}=${value.toString()}`; + }).filter(x => x !== "").join(";"); + } + + static fromExisting(other: KustoConnectionStringBuilder): KustoConnectionStringBuilder { return Object.assign({}, other); } static withAadUserPasswordAuthentication(connectionString: string, userId: string, password: string, authorityId?: string) { - if (!userId || userId.trim().length === 0) throw new Error("Invalid user"); - if (!password || password.trim().length === 0) throw new Error("Invalid password"); + if (userId.trim().length === 0) throw new Error("Invalid user"); + if (password.trim().length === 0) throw new Error("Invalid password"); const kcsb = new KustoConnectionStringBuilder(connectionString); - kcsb[KeywordMapping.aadUserId.propName] = userId; - kcsb[KeywordMapping.password.propName] = password; - kcsb[KeywordMapping.authorityId.propName] = authorityId || "common"; + kcsb.aadFederatedSecurity = true; + kcsb.aadUserId = userId; + kcsb.password = password; + if (authorityId) { + kcsb.authorityId = authorityId; + } return kcsb; } static withAadApplicationKeyAuthentication(connectionString: string, aadAppId: string, appKey: string, authorityId?: string) { - if (!aadAppId || aadAppId.trim().length === 0) throw new Error("Invalid app id"); - if (!appKey || appKey.trim().length === 0) throw new Error("Invalid app key"); + if (aadAppId.trim().length === 0) throw new Error("Invalid app id"); + if (appKey.trim().length === 0) throw new Error("Invalid app key"); const kcsb = new KustoConnectionStringBuilder(connectionString); - kcsb[KeywordMapping.applicationClientId.propName] = aadAppId; - kcsb[KeywordMapping.applicationKey.propName] = appKey; - kcsb[KeywordMapping.authorityId.propName] = authorityId || "common"; + kcsb.aadFederatedSecurity = true; + kcsb.applicationClientId = aadAppId; + kcsb.applicationKey = appKey; + if (authorityId) { + kcsb.authorityId = authorityId; + } + return kcsb; } - static withAadApplicationCertificateAuthentication(connectionString: string, aadAppId: string, applicationCertificatePrivateKey: string, applicationCertificateThumbprint: string, authorityId?: string, applicationCertificateX5c?: string) { - if (!aadAppId || aadAppId.trim().length === 0) throw new Error("Invalid app id"); - if (!applicationCertificatePrivateKey || applicationCertificatePrivateKey.trim().length === 0) throw new Error("Invalid certificate"); - if (!applicationCertificateThumbprint || applicationCertificateThumbprint.trim().length === 0) throw new Error("Invalid thumbprint"); + static withAadApplicationCertificateAuthentication( + connectionString: string, + aadAppId: string, + applicationCertificatePrivateKey: string, + applicationCertificateThumbprint: string, + authorityId?: string, + applicationCertificateX5c?: string, + ) { + if (aadAppId.trim().length === 0) throw new Error("Invalid app id"); + if (applicationCertificatePrivateKey.trim().length === 0) throw new Error("Invalid certificate"); + if (applicationCertificateThumbprint.trim().length === 0) throw new Error("Invalid thumbprint"); const kcsb = new KustoConnectionStringBuilder(connectionString); - kcsb[KeywordMapping.applicationClientId.propName] = aadAppId; - kcsb[KeywordMapping.applicationCertificatePrivateKey.propName] = applicationCertificatePrivateKey; - kcsb[KeywordMapping.applicationCertificateThumbprint.propName] = applicationCertificateThumbprint; - kcsb[KeywordMapping.applicationCertificateX5c.propName] = applicationCertificateX5c; - kcsb[KeywordMapping.authorityId.propName] = authorityId || "common"; + kcsb.aadFederatedSecurity = true; + kcsb.applicationClientId = aadAppId; + kcsb.applicationCertificatePrivateKey = applicationCertificatePrivateKey; + kcsb.applicationCertificateThumbprint = applicationCertificateThumbprint; + kcsb.applicationCertificateX5c = applicationCertificateX5c; + + if (authorityId) { + kcsb.authorityId = authorityId; + } return kcsb; } - static withAadDeviceAuthentication(connectionString: string, authorityId: string = "common", deviceCodeCallback?: (response: DeviceCodeResponse) => void) { + static withAadDeviceAuthentication(connectionString: string, authorityId: string = "common", deviceCodeCallback: (response: DeviceCodeResponse) => void = KustoConnectionStringBuilder.defaultDeviceCallback) { const kcsb = new KustoConnectionStringBuilder(connectionString); - kcsb[KeywordMapping.authorityId.propName] = authorityId; + kcsb.aadFederatedSecurity = true; + kcsb.authorityId = authorityId; kcsb.deviceCodeCallback = deviceCodeCallback; + kcsb.useDeviceCodeAuth = true return kcsb; } - static withAadManagedIdentities(connectionString: string, msiClientId?: string) { + static withAadManagedIdentities(connectionString: string, msiClientId?: string, authorityId?: string, timeoutMs?: number) { const kcsb = new KustoConnectionStringBuilder(connectionString); + kcsb.aadFederatedSecurity = true; + if (authorityId) { + kcsb.authorityId = authorityId; + } kcsb.msiClientId = msiClientId; - kcsb.managedIdentity = true; + kcsb.timeoutMs = timeoutMs; + kcsb.useManagedIdentityAuth = true; return kcsb; } - static withAzLoginIdentity(connectionString: string) { + static withAzLoginIdentity(connectionString: string, authorityId?: string, timeoutMs?: number,) { const kcsb = new KustoConnectionStringBuilder(connectionString); + kcsb.aadFederatedSecurity = true; + + kcsb.useAzLoginAuth = true; + if (authorityId) { + kcsb.authorityId = authorityId; + } + kcsb.timeoutMs = timeoutMs; - kcsb.azLoginIdentity = true; return kcsb; } - static withAccessToken(connectionString: string, accessToken?: string) { + static withAccessToken(connectionString: string, accessToken: string) { const kcsb = new KustoConnectionStringBuilder(connectionString); + kcsb.aadFederatedSecurity = true; kcsb.accessToken = accessToken; return kcsb; } + + static withTokenProvider(connectionString: string, tokenProvider: () => Promise) { + const kcsb = new KustoConnectionStringBuilder(connectionString); + kcsb.aadFederatedSecurity = true; + + kcsb.tokenProvider = tokenProvider; + + return kcsb; + } + + static withUserPrompt(connectionString: string, authorityId?: string, clientId?: string, timeoutMs?: number, loginHint?: string) { + const kcsb = new KustoConnectionStringBuilder(connectionString); + kcsb.aadFederatedSecurity = true; + + kcsb.useUserPromptAuth = true; + if (authorityId) { + kcsb.authorityId = authorityId; + } + kcsb.loginHint = loginHint; + kcsb.applicationClientId = clientId; + kcsb.timeoutMs = timeoutMs; + + return kcsb; + } } export default KustoConnectionStringBuilder; \ No newline at end of file diff --git a/azure-kusto-data/source/errors.ts b/azure-kusto-data/source/errors.ts new file mode 100644 index 0000000..f45f983 --- /dev/null +++ b/azure-kusto-data/source/errors.ts @@ -0,0 +1,6 @@ +export class KustoAuthenticationError extends Error { + constructor(message: string, public inner: Error | undefined, public tokenProviderName: string, public context: Record) { + super(message); + this.name = "KustoAuthenticationError"; + } +} \ No newline at end of file diff --git a/azure-kusto-data/source/security.ts b/azure-kusto-data/source/security.ts index 4457af2..f807ec1 100644 --- a/azure-kusto-data/source/security.ts +++ b/azure-kusto-data/source/security.ts @@ -3,9 +3,10 @@ import KustoConnectionStringBuilder from "./connectionBuilder"; import "./tokenProvider"; import * as TokenProvider from "./tokenProvider"; +import { KustoAuthenticationError } from "./errors"; export class AadHelper { - tokenProvider: TokenProvider.TokenProviderBase; + tokenProvider?: TokenProvider.TokenProviderBase; constructor(kcsb: KustoConnectionStringBuilder) { if (!kcsb.dataSource) { @@ -19,26 +20,35 @@ export class AadHelper { } else if (!!kcsb.applicationClientId && !!kcsb.applicationCertificateThumbprint && !!kcsb.applicationCertificatePrivateKey) { this.tokenProvider = new TokenProvider.ApplicationCertificateTokenProvider(kcsb.dataSource, kcsb.applicationClientId, kcsb.applicationCertificateThumbprint, kcsb.applicationCertificatePrivateKey, kcsb.applicationCertificateX5c as string | undefined, kcsb.authorityId); - } else if (kcsb.managedIdentity) { - this.tokenProvider = new TokenProvider.MsiTokenProvider(kcsb.dataSource, kcsb.msiClientId as string | undefined); - } else if (kcsb.azLoginIdentity) { - this.tokenProvider = new TokenProvider.AzCliTokenProvider(kcsb.dataSource); + } else if (kcsb.useManagedIdentityAuth) { + this.tokenProvider = new TokenProvider.MsiTokenProvider(kcsb.dataSource, kcsb.authorityId, kcsb.msiClientId, kcsb.timeoutMs); + } else if (kcsb.useAzLoginAuth) { + this.tokenProvider = new TokenProvider.AzCliTokenProvider(kcsb.dataSource, kcsb.authorityId, undefined, kcsb.timeoutMs); } else if (kcsb.accessToken) { this.tokenProvider = new TokenProvider.BasicTokenProvider(kcsb.dataSource, kcsb.accessToken as string); - } else { - let callback = kcsb.deviceCodeCallback; - if (!callback) { - // tslint:disable-next-line:no-console - callback = (response) => console.log(response.message); + } else if (kcsb.useUserPromptAuth) { + this.tokenProvider = new TokenProvider.UserPromptProvider(kcsb.dataSource, kcsb.authorityId, kcsb.applicationClientId, kcsb.timeoutMs, kcsb.loginHint); + } else if (kcsb.tokenProvider) { + this.tokenProvider = new TokenProvider.CallbackTokenProvider(kcsb.dataSource, kcsb.tokenProvider); + } else if (kcsb.useDeviceCodeAuth){ + if (kcsb.deviceCodeCallback === undefined) { + throw new KustoAuthenticationError("Device code authentication requires a callback function", undefined, TokenProvider.DeviceLoginTokenProvider.name, {}); } - this.tokenProvider = new TokenProvider.DeviceLoginTokenProvider(kcsb.dataSource, callback); + this.tokenProvider = new TokenProvider.DeviceLoginTokenProvider(kcsb.dataSource, kcsb.deviceCodeCallback, kcsb.authorityId); } } - async _getAuthHeader(): Promise { - const token = await this.tokenProvider.acquireToken(); - return `${token.tokenType} ${token.accessToken}`; + async getAuthHeader(): Promise { + if (!this.tokenProvider) { + return null; + } + try { + const token = await this.tokenProvider.acquireToken(); + return `${token.tokenType} ${token.accessToken}`; + } catch (e) { + throw new KustoAuthenticationError(e instanceof Error ? e.message : `${e}`, e instanceof Error ? e : undefined, this.tokenProvider.constructor.name, this.tokenProvider.context()); + } } } -export default AadHelper; \ No newline at end of file +export default AadHelper; diff --git a/azure-kusto-data/source/tokenProvider.ts b/azure-kusto-data/source/tokenProvider.ts index 739af9c..2934a03 100644 --- a/azure-kusto-data/source/tokenProvider.ts +++ b/azure-kusto-data/source/tokenProvider.ts @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { AuthenticationResult, PublicClientApplication, ConfidentialClientApplication } from "@azure/msal-node"; +import { ConfidentialClientApplication, PublicClientApplication } from "@azure/msal-node"; import { DeviceCodeResponse } from "@azure/msal-common"; -import { ManagedIdentityCredential, AzureCliCredential} from "@azure/identity"; -import { CloudSettings, CloudInfo } from "./cloudSettings" +import { AzureCliCredential, InteractiveBrowserCredential, ManagedIdentityCredential, TokenCredentialOptions, } from "@azure/identity"; +import { CloudInfo, CloudSettings } from "./cloudSettings" +import { TokenCredential } from "@azure/core-auth"; // We want all the Token Providers in this file /* tslint:disable:max-classes-per-file */ @@ -13,6 +14,12 @@ export declare type TokenResponse = { accessToken: string; } +interface TokenType { + tokenType: string, + accessToken: string +} + + const BEARER_TYPE = "Bearer"; /** @@ -25,10 +32,14 @@ export abstract class TokenProviderBase { abstract acquireToken(): Promise; + context(): Record { + return {}; + } + protected constructor(kustoUri: string) { this.kustoUri = kustoUri; if (kustoUri != null) { - const suffix = this.kustoUri.endsWith("/") ? ".default" : "/.default"; + const suffix = (!this.kustoUri.endsWith("/") ? "/" : "") + ".default"; this.scopes = [kustoUri + suffix]; } } @@ -67,64 +78,26 @@ export class CallbackTokenProvider extends TokenProviderBase { } } - -// MSI Token Provider obtains a token from the MSI endpoint -// The args parameter is a dictionary conforming with the ManagedIdentityCredential initializer API arguments -export class MsiTokenProvider extends TokenProviderBase { - clientId?: string; - managedIdentityCredential!: ManagedIdentityCredential; - - constructor(kustoUri: string, clientId?: string) { - super(kustoUri); - this.clientId = clientId; - } - - async acquireToken(): Promise { - if (this.managedIdentityCredential == null) { - this.managedIdentityCredential = this.clientId ? new ManagedIdentityCredential(this.clientId) : new ManagedIdentityCredential(); - } - const msiToken = await this.managedIdentityCredential.getToken(this.kustoUri); - return { tokenType: BEARER_TYPE, accessToken: msiToken.token }; - } -} - -/** - * AzCli Token Provider obtains a refresh token from the AzCli cache and uses it to authenticate with MSAL - */ -export class AzCliTokenProvider extends TokenProviderBase { - azureCliCredentials!: AzureCliCredential; - - constructor(kustoUri: string) { - super(kustoUri); - } - - async acquireToken(): Promise { - if (this.azureCliCredentials == null) { - this.azureCliCredentials = new AzureCliCredential(); - } - const response = await this.azureCliCredentials.getToken(this.scopes); - return { tokenType: BEARER_TYPE, accessToken: response.token }; - } -} - /** * Acquire a token from MSAL */ abstract class MsalTokenProvider extends TokenProviderBase { cloudInfo!: CloudInfo; - authorityId?: string; + authorityId: string; initialized: boolean; - abstract initClient(): void; - abstract acquireMsalToken(): Promise; + authorityUri!: string; - protected constructor(kustoUri: string, authorityId?: string) { + abstract initClient(): void; + + abstract acquireMsalToken(): Promise; + + protected constructor(kustoUri: string, authorityId: string) { super(kustoUri); this.initialized = false; this.authorityId = authorityId; } async acquireToken(): Promise { - let token; if (!this.initialized) { if (this.cloudInfo == null) { this.cloudInfo = await CloudSettings.getInstance().getCloudInfoForCluster(this.kustoUri); @@ -133,17 +106,119 @@ abstract class MsalTokenProvider extends TokenProviderBase { resourceUri = resourceUri.replace(".kusto.", ".kustomfa.") } this.scopes = [resourceUri + "/.default"] + this.authorityUri = CloudSettings.getAuthorityUri(this.cloudInfo, this.authorityId); this.initClient(); } this.initialized = true; } - token = await this.acquireMsalToken(); + const token = await this.acquireMsalToken(); if (token) { return { tokenType: token.tokenType, accessToken: token.accessToken } } throw new Error("Failed to get token from msal"); } + + context(): Record { + return { ...super.context(), kustoUri: this.kustoUri, authorityId: this.authorityId }; + } +} + +export abstract class AzureIdentityProvider extends MsalTokenProvider { + private credential!: TokenCredential; + protected authorityHost!: string; + + constructor(kustoUri: string, authorityId: string, protected clientId?: string, private timeoutMs?: number) { + super(kustoUri, authorityId); + } + + initClient(): void { + this.authorityHost = this.cloudInfo.LoginEndpoint; + this.credential = this.getCredential(); + } + + getCommonOptions(): { authorityHost: string; clientId: string | undefined; tenantId: string } { + return { + authorityHost: this.authorityHost, + tenantId: this.authorityId, + clientId: this.clientId, + } + } + + async acquireMsalToken(): Promise { + const response = await this.credential.getToken(this.scopes, { + requestOptions: { + timeout: this.timeoutMs + }, + tenantId: this.authorityId + }); + if (response === null) { + throw new Error("Failed to get token from msal"); + } + return { tokenType: BEARER_TYPE, accessToken: response.token }; + } + + context(): Record { + let base: Record = { ...super.context(), kustoUri: this.kustoUri, authorityId: this.authorityId }; + if (this.clientId) { + base = { ...base, clientId: this.clientId }; + } + if (this.timeoutMs) { + base = { ...base, timeoutMs: this.timeoutMs }; + } + + return base; + } + + abstract getCredential(): TokenCredential; +} + +/** + * MSI Token Provider obtains a token from the MSI endpoint + * The args parameter is a dictionary conforming with the ManagedIdentityCredential initializer API arguments + */ +export class MsiTokenProvider extends AzureIdentityProvider { + getCredential(): TokenCredential { + const options: TokenCredentialOptions = this.getCommonOptions(); + return this.clientId ? new ManagedIdentityCredential(this.clientId, options) : new ManagedIdentityCredential(options); + } +} + +/** + * AzCli Token Provider obtains a refresh token from the AzCli cache and uses it to authenticate with MSAL + */ +export class AzCliTokenProvider extends AzureIdentityProvider { + getCredential(): TokenCredential { + return new AzureCliCredential(this.getCommonOptions()); + } +} + +/** + * UserPromptProvider will pop up a login prompt to acquire a token. + */ +export class UserPromptProvider extends AzureIdentityProvider { + // The default port is 80, which can lead to permission errors, so we'll choose another port + readonly BrowserPort = 23145; + + constructor(kustoUri: string, authorityId: string, clientId?: string, timeoutMs?: number, private loginHint?: string) { + super(kustoUri, authorityId, clientId, timeoutMs); + } + + getCredential(): TokenCredential { + return new InteractiveBrowserCredential({ + ...this.getCommonOptions(), + loginHint: this.loginHint, + redirectUri: `http://localhost:${this.BrowserPort}/` + }); + } + + context(): Record { + let base = super.context(); + if (this.loginHint) { + base = { ...base, loginHint: this.loginHint }; + } + return base; + } } /** @@ -155,7 +230,7 @@ export class UserPassTokenProvider extends MsalTokenProvider { homeAccountId?: string; msalClient!: PublicClientApplication; - constructor(kustoUri: string, userName: string, password: string, authorityId?: string) { + constructor(kustoUri: string, userName: string, password: string, authorityId: string) { super(kustoUri, authorityId); this.userName = userName; this.password = password; @@ -165,13 +240,13 @@ export class UserPassTokenProvider extends MsalTokenProvider { const clientConfig = { auth: { clientId: this.cloudInfo.KustoClientAppId, - authority: CloudSettings.getAuthorityUri(this.cloudInfo, this.authorityId), + authority: this.authorityUri, } }; this.msalClient = new PublicClientApplication(clientConfig); } - async acquireMsalToken(): Promise { + async acquireMsalToken(): Promise { let token = null; if (this.homeAccountId != null) { const account = await this.msalClient.getTokenCache().getAccountByHomeId(this.homeAccountId) @@ -185,6 +260,10 @@ export class UserPassTokenProvider extends MsalTokenProvider { } return token; } + + context(): Record { + return { ...super.context(), userName: this.userName, homeAccountId: this.homeAccountId }; + } } /** @@ -195,7 +274,7 @@ export class DeviceLoginTokenProvider extends MsalTokenProvider { homeAccountId?: string; msalClient!: PublicClientApplication; - constructor(kustoUri: string, deviceCodeCallback: (response: DeviceCodeResponse) => void, authorityId?: string) { + constructor(kustoUri: string, deviceCodeCallback: (response: DeviceCodeResponse) => void, authorityId: string) { super(kustoUri, authorityId); this.deviceCodeCallback = deviceCodeCallback; } @@ -204,13 +283,13 @@ export class DeviceLoginTokenProvider extends MsalTokenProvider { const clientConfig = { auth: { clientId: this.cloudInfo.KustoClientAppId, - authority: CloudSettings.getAuthorityUri(this.cloudInfo, this.authorityId), + authority: this.authorityUri, }, }; this.msalClient = new PublicClientApplication(clientConfig); } - async acquireMsalToken(): Promise { + async acquireMsalToken(): Promise { let token = null; if (this.homeAccountId != null) { const account = await this.msalClient.getTokenCache().getAccountByHomeId(this.homeAccountId) @@ -234,7 +313,7 @@ export class ApplicationKeyTokenProvider extends MsalTokenProvider { appKey: string; msalClient!: ConfidentialClientApplication; - constructor(kustoUri: string, appClientId: string, appKey: string, authorityId?: string) { + constructor(kustoUri: string, appClientId: string, appKey: string, authorityId: string) { super(kustoUri, authorityId); this.appClientId = appClientId; this.appKey = appKey; @@ -245,15 +324,19 @@ export class ApplicationKeyTokenProvider extends MsalTokenProvider { auth: { clientId: this.appClientId, clientSecret: this.appKey, - authority: CloudSettings.getAuthorityUri(this.cloudInfo, this.authorityId), + authority: this.authorityUri, } }; this.msalClient = new ConfidentialClientApplication(clientConfig); } - acquireMsalToken(): Promise { + acquireMsalToken(): Promise { return this.msalClient.acquireTokenByClientCredential({ scopes: this.scopes }); } + + context(): Record { + return { ...super.context(), clientId: this.appClientId }; + } } /** @@ -268,7 +351,7 @@ export class ApplicationCertificateTokenProvider extends MsalTokenProvider { msalClient!: ConfidentialClientApplication; constructor(kustoUri: string, appClientId: string, certThumbprint: string, certPrivateKey: string, certX5c?: string, authorityId?: string) { - super(kustoUri, authorityId); + super(kustoUri, authorityId!); this.appClientId = appClientId; this.certThumbprint = certThumbprint; this.certPrivateKey = certPrivateKey; @@ -279,7 +362,7 @@ export class ApplicationCertificateTokenProvider extends MsalTokenProvider { const clientConfig = { auth: { clientId: this.appClientId, - authority: CloudSettings.getAuthorityUri(this.cloudInfo, this.authorityId), + authority: this.authorityUri, clientCertificate: { thumbprint: this.certThumbprint, privateKey: this.certPrivateKey, @@ -290,7 +373,11 @@ export class ApplicationCertificateTokenProvider extends MsalTokenProvider { this.msalClient = new ConfidentialClientApplication(clientConfig); } - acquireMsalToken(): Promise { + acquireMsalToken(): Promise { return this.msalClient.acquireTokenByClientCredential({ scopes: this.scopes }); } + + context(): Record { + return { ...super.context(), clientId: this.appClientId, thumbprint: this.certThumbprint }; + } } diff --git a/azure-kusto-data/source/typeUtilts.ts b/azure-kusto-data/source/typeUtilts.ts new file mode 100644 index 0000000..f1da615 --- /dev/null +++ b/azure-kusto-data/source/typeUtilts.ts @@ -0,0 +1,4 @@ + +export type KeyOfType = keyof { + [P in keyof T as T[P] extends V? P: never]: unknown +} \ No newline at end of file diff --git a/azure-kusto-data/test/clientTest.ts b/azure-kusto-data/test/clientTest.ts index 45f8fa8..315309e 100644 --- a/azure-kusto-data/test/clientTest.ts +++ b/azure-kusto-data/test/clientTest.ts @@ -107,7 +107,7 @@ describe("KustoClient", () => { const clientRequestProps = new ClientRequestProperties(); const timeoutMs = moment.duration(2.51, "minutes").asMilliseconds(); clientRequestProps.setTimeout(timeoutMs); - client.aadHelper._getAuthHeader = () => { return Promise.resolve("MockToken") }; + client.aadHelper.getAuthHeader = () => { return Promise.resolve("MockToken") }; client._doRequest = (_endpoint, _executionType, _headers, payload, timeout) => { const payloadObj = JSON.parse(payload); assert.strictEqual(payloadObj.properties.Options.servertimeout, "00:02:30.6"); @@ -125,7 +125,7 @@ describe("KustoClient", () => { const clientRequestProps = new ClientRequestProperties(); const timeoutMs = moment.duration(2.51, "minutes").asMilliseconds(); clientRequestProps.setClientTimeout(timeoutMs); - client.aadHelper._getAuthHeader = () => { return Promise.resolve("MockToken") }; + client.aadHelper.getAuthHeader = () => { return Promise.resolve("MockToken") }; client._doRequest = (_endpoint, _executionType, _headers, payload, timeout) => { JSON.parse(payload); assert.strictEqual(timeout, timeoutMs); @@ -139,7 +139,7 @@ describe("KustoClient", () => { const url = "https://cluster.kusto.windows.net"; const client = new KustoClient(url); - client.aadHelper._getAuthHeader = () => { return Promise.resolve("MockToken") }; + client.aadHelper.getAuthHeader = () => { return Promise.resolve("MockToken") }; client._doRequest = (_endpoint, _executionType, _headers, _payload, timeout) => { assert.strictEqual(timeout, moment.duration(4.5, "minutes").asMilliseconds()); return Promise.resolve(new KustoResponseDataSetV2([])); @@ -151,7 +151,7 @@ describe("KustoClient", () => { it("default timeout for admin", async () => { const url = "https://cluster.kusto.windows.net"; const client = new KustoClient(url); - client.aadHelper._getAuthHeader = () => { return Promise.resolve("MockToken") }; + client.aadHelper.getAuthHeader = () => { return Promise.resolve("MockToken") }; client._doRequest = (_endpoint, _executionType, _headers, _payload, timeout) => { assert.strictEqual(timeout, moment.duration(10.5, "minutes").asMilliseconds()); return Promise.resolve(new KustoResponseDataSetV2([])); @@ -171,7 +171,7 @@ describe("KustoClient", () => { clientRequestProps.clientRequestId = clientRequestId; clientRequestProps.application = application; clientRequestProps.user = user; - client.aadHelper._getAuthHeader = () => { return Promise.resolve("MockToken") }; + client.aadHelper.getAuthHeader = () => { return Promise.resolve("MockToken") }; client._doRequest = (_endpoint, _executionType, headers) => { assert.strictEqual(headers["x-ms-client-request-id"], clientRequestId); assert.strictEqual(headers["x-ms-app"], application); @@ -186,7 +186,7 @@ describe("KustoClient", () => { const url = "https://cluster.kusto.windows.net"; const client = new KustoClient(url); - client.aadHelper._getAuthHeader = () => { return Promise.resolve("MockToken") }; + client.aadHelper.getAuthHeader = () => { return Promise.resolve("MockToken") }; client._doRequest = (endpoint, executionType, _headers, _payload, _timeout, _properties) => { assert.strictEqual(endpoint, `${url}/v1/rest/query`); assert.strictEqual(executionType, ExecutionType.QueryV1); diff --git a/azure-kusto-data/test/connectionBuilderTest.ts b/azure-kusto-data/test/connectionBuilderTest.ts index 9d5e1ad..fcf547f 100644 --- a/azure-kusto-data/test/connectionBuilderTest.ts +++ b/azure-kusto-data/test/connectionBuilderTest.ts @@ -1,11 +1,129 @@ +/* tslint:disable:no-console */ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import assert from "assert"; import { v4 as uuidv4 } from 'uuid'; -import {KustoConnectionStringBuilder} from "../source/connectionBuilder"; +import { KustoConnectionStringBuilder } from "../source/connectionBuilder"; +import { DeviceCodeResponse } from "@azure/msal-common"; + +function doComparsion( + kcsbs: KustoConnectionStringBuilder[], + expectedProperties: Partial>, + expectedToString: string, + expectedToStringWithSecrets: string +) { + for (const [i, kcsb] of kcsbs.entries()) { + console.log(`Checking connection string #${i} - ${kcsb.toString(false)}`); + + const clone = KustoConnectionStringBuilder.fromExisting(kcsb); + + const emptyFields = [ + "aadUserId", + "applicationClientId", + "password", + "msiClientId", + "applicationKey", + "applicationCertificatePrivateKey", + "applicationCertificateThumbprint", + "applicationCertificateX5c", + "deviceCodeCallback", + "loginHint", + "timeoutMs", + "accessToken", + "isAzLoginIdentity", + "isManagedIdentity", + "isInteractiveLogin", + "isDeviceCode" + ]; + + for (const entry of Object.entries(expectedProperties)) { + const [key, value] = entry; + + const kcsbEntry = kcsb[key as keyof KustoConnectionStringBuilder]; + + if (typeof kcsbEntry === "function") { + assert.notStrictEqual(kcsbEntry, undefined, `${key} is not defined`); + continue; + } + assert.strictEqual(kcsbEntry, value, `${key} is not equal to ${value}`); + assert.strictEqual(clone[key as keyof KustoConnectionStringBuilder], value, `${key} is not equal to ${value} in clone`); + } + + for (const field of emptyFields.filter(f => !(f in expectedProperties))) { + assert.strictEqual(kcsb[field as keyof KustoConnectionStringBuilder], undefined, `${field} should be undefined`); + assert.strictEqual(clone[field as keyof KustoConnectionStringBuilder], undefined, `${field} should be undefined in clone`); + } + + assert.strictEqual( + kcsb.toString(), + expectedToString + ) + assert.strictEqual( + kcsb.toString(false), + expectedToStringWithSecrets + ) + } +} describe("KustoConnectionStringBuilder", () => { + describe("validation tests", () => { + it("throws when empty connection string is provided", () => { + assert.throws(() => new KustoConnectionStringBuilder(" "), Error, "Missing connection string"); + }); + + it("removes trailing dashes from data source", () => { + const kcsbForward = new KustoConnectionStringBuilder("https://test.kusto.windows.net/"); + assert.strictEqual(kcsbForward.dataSource, "https://test.kusto.windows.net"); + const kcsbBack = new KustoConnectionStringBuilder("https://test.kusto.windows.net\\"); + assert.strictEqual(kcsbBack.dataSource, "https://test.kusto.windows.net"); + }); + + it("throws when user or password is empty", () => { + assert.throws( + () => KustoConnectionStringBuilder.withAadUserPasswordAuthentication("https://test.kusto.windows.net/", " ", "password"), + Error, + "Invalid user" + ); + assert.throws( + () => KustoConnectionStringBuilder.withAadUserPasswordAuthentication("https://test.kusto.windows.net/", "user", " "), + Error, + "Invalid password" + ); + }); + + it("throws when appId or appKey is empty", () => { + assert.throws( + () => KustoConnectionStringBuilder.withAadApplicationKeyAuthentication("https://test.kusto.windows.net/", " ", "password"), + Error, + "Invalid app id" + ); + assert.throws(() => KustoConnectionStringBuilder.withAadApplicationKeyAuthentication( + "https://test.kusto.windows.net/", + "53e12945-98b5-4d5c-9465-fd6b6edf848e", + " " + ), Error, "Invalid app key"); + }); + + it("throws when certificate values are empty", () => { + assert.throws( + () => KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication("https://test.kusto.windows.net/", " ", "private", "thumb"), + Error, + "Invalid app id" + ); + assert.throws( + () => KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication("https://test.kusto.windows.net/", "53e12945-98b5-4d5c-9465-fd6b6edf848e", " ", "thumb"), + Error, + "Invalid app certificate" + ); + assert.throws( + () => KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication("https://test.kusto.windows.net/", "53e12945-98b5-4d5c-9465-fd6b6edf848e", "private", " "), + Error, + "Invalid app thumbprint" + ); + }); + }); + describe("#constructor(connectionString)", () => { it("from string with no creds", () => { const kcsbs = [ @@ -13,74 +131,472 @@ describe("KustoConnectionStringBuilder", () => { new KustoConnectionStringBuilder("data Source=localhost"), new KustoConnectionStringBuilder("Addr=localhost"), new KustoConnectionStringBuilder("Addr = localhost"), - KustoConnectionStringBuilder.withAadDeviceAuthentication("localhost", "common"), ]; - for (const kcsb of kcsbs) { - assert.strictEqual(kcsb.dataSource, "localhost"); - assert.strictEqual(kcsb.authorityId, "common"); - const emptyFields = ["aadUserId", "password", "applicationClientId", "applicationKey"]; - for (const field of emptyFields) { - assert.strictEqual(kcsb[field], undefined); - } - } + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: "common" + }, + "Data Source=localhost;Authority Id=common", + "Data Source=localhost;Authority Id=common" + ); }); - it("from string with username auth", () => { + + describe("from username auth", () => { const expectedUser = "test"; const expectedPassword = "Pa$$w0rd"; - const kcsbs = [ - new KustoConnectionStringBuilder(`localhost;AAD User ID=${expectedUser};password=${expectedPassword}`), - new KustoConnectionStringBuilder(`Data Source=localhost ; AaD User ID=${expectedUser}; Password =${expectedPassword}`), - new KustoConnectionStringBuilder(` Addr = localhost ; AAD User ID = ${expectedUser} ; Pwd =${expectedPassword}`), - new KustoConnectionStringBuilder(`Network Address = localhost; AAD User iD = ${expectedUser} ; Pwd = ${expectedPassword} `), - KustoConnectionStringBuilder.withAadUserPasswordAuthentication("localhost", expectedUser, expectedPassword), - ]; - const kcsb1 = new KustoConnectionStringBuilder("Server=localhost"); - kcsb1.aadUserId = expectedUser; - kcsb1.password = expectedPassword; - kcsbs.push(kcsb1); - for (const kcsb of kcsbs) { - assert.strictEqual(kcsb.dataSource, "localhost"); - assert.strictEqual(kcsb.aadUserId, expectedUser); - assert.strictEqual(kcsb.password, expectedPassword); - assert.strictEqual(kcsb.authorityId, "common"); - const emptyFields = ["applicationClientId", "applicationKey"]; - for (const field of emptyFields) { - assert.strictEqual(kcsb[field], undefined); - } - } + it("without authority id", () => { + const kcsbs = [ + new KustoConnectionStringBuilder(`localhost;AAD User ID=${expectedUser};password=${expectedPassword};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Data Source=localhost ; AaD User ID=${expectedUser}; Password =${expectedPassword};AAD Federated Security=True`), + new KustoConnectionStringBuilder(` Addr = localhost ; AAD User ID = ${expectedUser} ; Pwd =${expectedPassword};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Network Address = localhost; AAD User iD = ${expectedUser} ; Pwd = ${expectedPassword};AAD Federated Security=True `), + KustoConnectionStringBuilder.withAadUserPasswordAuthentication("localhost", expectedUser, expectedPassword), + ]; + const kcsb1 = new KustoConnectionStringBuilder("Server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.aadUserId = expectedUser; + kcsb1.password = expectedPassword; + kcsbs.push(kcsb1); + + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: "common", + aadUserId: expectedUser, + password: expectedPassword, + aadFederatedSecurity: true + }, + `Data Source=localhost;AAD Federated Security=true;AAD User ID=${expectedUser};Password=****;Authority Id=common`, + `Data Source=localhost;AAD Federated Security=true;AAD User ID=${expectedUser};Password=${expectedPassword};Authority Id=common` + ); + }); + + it("with authority id", () => { + const expectedAuthorityId = "test-authority"; + + const kcsbs = [ + new KustoConnectionStringBuilder(`localhost;AAD User ID=${expectedUser};password=${expectedPassword};Authority Id=${expectedAuthorityId};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Data Source=localhost ; AaD User ID=${expectedUser}; Password =${expectedPassword};authority=${expectedAuthorityId};AAD Federated Security=True`), + new KustoConnectionStringBuilder(` Addr = localhost ; AAD User ID = ${expectedUser} ; Pwd =${expectedPassword};tenantid=${expectedAuthorityId};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Network Address = localhost; AAD User iD = ${expectedUser} ; Pwd = ${expectedPassword};tid=${expectedAuthorityId};AAD Federated Security=True `), + KustoConnectionStringBuilder.withAadUserPasswordAuthentication("localhost", expectedUser, expectedPassword, expectedAuthorityId), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("Server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.aadUserId = expectedUser; + kcsb1.password = expectedPassword; + kcsb1.authorityId = expectedAuthorityId; + kcsbs.push(kcsb1); + + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: expectedAuthorityId, + aadUserId: expectedUser, + password: expectedPassword, + aadFederatedSecurity: true + }, + `Data Source=localhost;AAD Federated Security=true;AAD User ID=${expectedUser};Password=****;Authority Id=${expectedAuthorityId}`, + `Data Source=localhost;AAD Federated Security=true;AAD User ID=${expectedUser};Password=${expectedPassword};Authority Id=${expectedAuthorityId}` + ); + }) }); - it("from string with app auth", () => { - const uuid = uuidv4(); - const key = "key of application"; + describe("from app key auth", () => { + const expectedUuid = uuidv4(); + const expectedKey = "key of application"; + it("without authority id", () => { + const kcsbs = [ + new KustoConnectionStringBuilder(`localhost;Application client Id=${expectedUuid};application Key=${expectedKey};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Data Source=localhost ; Application Client Id=${expectedUuid}; Appkey =${expectedKey};AAD Federated Security=True`), + new KustoConnectionStringBuilder(` Addr = localhost ; AppClientId = ${expectedUuid} ; AppKey =${expectedKey};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Network Address = localhost; AppClientId = ${expectedUuid} ; AppKey =${expectedKey};AAD Federated Security=True`), + KustoConnectionStringBuilder.withAadApplicationKeyAuthentication("localhost", expectedUuid, expectedKey) + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.applicationClientId = expectedUuid; + kcsb1.applicationKey = expectedKey; + kcsbs.push(kcsb1); + + doComparsion( + kcsbs, + { + dataSource: "localhost", + applicationClientId: expectedUuid, + applicationKey: expectedKey, + aadFederatedSecurity: true + }, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${expectedUuid};Application Key=****;Authority Id=common`, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${expectedUuid};Application Key=${expectedKey};Authority Id=common` + ); + }) + + it("with authority id", () => { + const expectedAuthorityId = "test-authority"; + + const kcsbs = [ + new KustoConnectionStringBuilder(`localhost;Application client Id=${expectedUuid};application Key=${expectedKey};Authority Id=${expectedAuthorityId};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Data Source=localhost ; Application Client Id=${expectedUuid}; Appkey =${expectedKey};authority=${expectedAuthorityId};AAD Federated Security=True`), + new KustoConnectionStringBuilder(` Addr = localhost ; AppClientId = ${expectedUuid} ; AppKey =${expectedKey};tenantid=${expectedAuthorityId};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`Network Address = localhost; AppClientId = ${expectedUuid} ; AppKey =${expectedKey};tid=${expectedAuthorityId};AAD Federated Security=True `), + KustoConnectionStringBuilder.withAadApplicationKeyAuthentication("localhost", expectedUuid, expectedKey, expectedAuthorityId) + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.applicationClientId = expectedUuid; + kcsb1.applicationKey = expectedKey; + kcsb1.authorityId = expectedAuthorityId; + kcsbs.push(kcsb1); + + doComparsion( + kcsbs, + { + dataSource: "localhost", + applicationClientId: expectedUuid, + applicationKey: expectedKey, + aadFederatedSecurity: true + }, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${expectedUuid};Application Key=****;Authority Id=${expectedAuthorityId}`, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${expectedUuid};Application Key=${expectedKey};Authority Id=${expectedAuthorityId}` + ); + }) + }); + + describe("from certificate auth", () => { + const appId = uuidv4(); + const privateKey = "some private key"; + const thumbPrint = "thumbprint"; + const expectedAuthorityId = "test-authority"; + const cert5xc = "5xc"; + + it("with authority id", () => { + const kcsbs = [ + new KustoConnectionStringBuilder(`localhost;Application client Id=${appId};application Certificate PrivateKey=${privateKey};application certificate thumbprint=${thumbPrint};Authority Id=${expectedAuthorityId};application certificate x5c=${cert5xc};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`localhost;AppClientId=${appId};Application Certificate PrivateKey=${privateKey};appcert=${thumbPrint};Authority Id=${expectedAuthorityId};SendX5c=${cert5xc};AAD Federated Security=True`), + KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication( + "localhost", + appId, + privateKey, + thumbPrint, + expectedAuthorityId, + cert5xc, + ) + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.applicationClientId = appId; + kcsb1.authorityId = expectedAuthorityId; + kcsb1.applicationCertificatePrivateKey = privateKey; + kcsb1.applicationCertificateThumbprint = thumbPrint; + kcsb1.applicationCertificateX5c = cert5xc; + kcsbs.push(kcsb1); + + doComparsion( + kcsbs, + { + dataSource: "localhost", + applicationClientId: appId, + applicationCertificatePrivateKey: privateKey, + applicationCertificateThumbprint: thumbPrint, + authorityId: expectedAuthorityId, + applicationCertificateX5c: cert5xc, + aadFederatedSecurity: true + }, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${appId};Application Certificate PrivateKey=****;Application Certificate Thumbprint=${thumbPrint};Application Certificate x5c=${cert5xc};Authority Id=${expectedAuthorityId}`, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${appId};Application Certificate PrivateKey=${privateKey};Application Certificate Thumbprint=${thumbPrint};Application Certificate x5c=${cert5xc};Authority Id=${expectedAuthorityId}` + ); + }) + + it("without authority id", () => { + const kcsbs = [ + new KustoConnectionStringBuilder(`localhost;Application client Id=${appId};application Certificate PrivateKey=${privateKey};application certificate thumbprint=${thumbPrint};application certificate x5c=${cert5xc};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`localhost;AppClientId=${appId};Application Certificate PrivateKey=${privateKey};appcert=${thumbPrint};SendX5c=${cert5xc};AAD Federated Security=True`), + KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication( + "localhost", + appId, + privateKey, + thumbPrint, + "common", + cert5xc, + ) + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.applicationClientId = appId; + kcsb1.applicationCertificatePrivateKey = privateKey; + kcsb1.applicationCertificateThumbprint = thumbPrint; + kcsb1.applicationCertificateX5c = cert5xc; + kcsbs.push(kcsb1); + + doComparsion( + kcsbs, + { + dataSource: "localhost", + applicationClientId: appId, + applicationCertificatePrivateKey: privateKey, + applicationCertificateThumbprint: thumbPrint, + authorityId: "common", + applicationCertificateX5c: cert5xc, + aadFederatedSecurity: true + }, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${appId};Application Certificate PrivateKey=****;Application Certificate Thumbprint=${thumbPrint};Application Certificate x5c=${cert5xc};Authority Id=common`, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${appId};Application Certificate PrivateKey=${privateKey};Application Certificate Thumbprint=${thumbPrint};Application Certificate x5c=${cert5xc};Authority Id=common` + ); + }) + + it("without 3xc", () => { + const kcsbs = [ + new KustoConnectionStringBuilder(`localhost;Application client Id=${appId};application Certificate PrivateKey=${privateKey};application certificate thumbprint=${thumbPrint};AAD Federated Security=True`), + new KustoConnectionStringBuilder(`localhost;AppClientId=${appId};Application Certificate PrivateKey=${privateKey};appcert=${thumbPrint};AAD Federated Security=True`), + KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication( + "localhost", + appId, + privateKey, + thumbPrint, + "common", + ) + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.applicationClientId = appId; + kcsb1.applicationCertificatePrivateKey = privateKey; + kcsb1.applicationCertificateThumbprint = thumbPrint; + kcsbs.push(kcsb1); + + doComparsion( + kcsbs, + { + dataSource: "localhost", + applicationClientId: appId, + applicationCertificatePrivateKey: privateKey, + applicationCertificateThumbprint: thumbPrint, + authorityId: "common", + aadFederatedSecurity: true + }, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${appId};Application Certificate PrivateKey=****;Application Certificate Thumbprint=${thumbPrint};Authority Id=common`, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${appId};Application Certificate PrivateKey=${privateKey};Application Certificate Thumbprint=${thumbPrint};Authority Id=common` + ); + }) + }); + + it("from aad device auth", () => { const kcsbs = [ - new KustoConnectionStringBuilder(`localhost;Application client Id=${uuid};application Key=${key}`), - new KustoConnectionStringBuilder(`Data Source=localhost ; Application Client Id=${uuid}; Appkey =${key}`), - new KustoConnectionStringBuilder(` Addr = localhost ; AppClientId = ${uuid} ; AppKey =${key}`), - new KustoConnectionStringBuilder(`Network Address = localhost; AppClientId = ${uuid} ; AppKey =${key}`), - KustoConnectionStringBuilder.withAadApplicationKeyAuthentication("localhost", uuid, key) + KustoConnectionStringBuilder.withAadDeviceAuthentication("localhost", "common"), + KustoConnectionStringBuilder.withAadDeviceAuthentication("localhost", "common", (res) => res), ]; const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); - kcsb1.applicationClientId = uuid; - kcsb1.applicationKey = key; - kcsbs.push(kcsb1); + kcsb1.aadFederatedSecurity = true; + kcsb1.useDeviceCodeAuth = true; + kcsb1.deviceCodeCallback = (res) => res; - for (const kcsb of kcsbs) { - assert.strictEqual(kcsb.dataSource, "localhost"); - assert.strictEqual(kcsb.applicationClientId, uuid); - assert.strictEqual(kcsb.applicationKey, key); - assert.strictEqual(kcsb.authorityId, "common"); - const emptyFields = ["aadUserId", "password"]; - for (const field of emptyFields) { - assert.strictEqual(kcsb[field], undefined); - } - } + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: "common", + useDeviceCodeAuth: true, + aadFederatedSecurity: true, + deviceCodeCallback: (res: DeviceCodeResponse) => res + }, + "Data Source=localhost;AAD Federated Security=true;Authority Id=common", + "Data Source=localhost;AAD Federated Security=true;Authority Id=common" + ); + }); + + describe("from msi auth", () => { + it("without clientId and timeout", () => { + const kcsbs = [ + KustoConnectionStringBuilder.withAadManagedIdentities("localhost"), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.useManagedIdentityAuth = true; + + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: "common", + useManagedIdentityAuth: true, + aadFederatedSecurity: true, + }, + "Data Source=localhost;AAD Federated Security=true;Authority Id=common", + "Data Source=localhost;AAD Federated Security=true;Authority Id=common" + ) + }); + + it("with clientId and timeout", () => { + const msiClientId = "clientId"; + const timeoutMs = 10; + const kcsbs = [ + KustoConnectionStringBuilder.withAadManagedIdentities("localhost", msiClientId, "common", timeoutMs), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.useManagedIdentityAuth = true; + kcsb1.msiClientId = msiClientId; + kcsb1.timeoutMs = timeoutMs; + + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: "common", + useManagedIdentityAuth: true, + aadFederatedSecurity: true, + msiClientId, + timeoutMs + }, + "Data Source=localhost;AAD Federated Security=true;Authority Id=common", + "Data Source=localhost;AAD Federated Security=true;Authority Id=common" + ) + }); + }); + + describe("from az cli", () => { + const timeout = 10000; + const authorityId = "common"; + const kcsbs = [ + KustoConnectionStringBuilder.withAzLoginIdentity("localhost", authorityId, timeout), + KustoConnectionStringBuilder.withAzLoginIdentity("localhost", undefined, timeout), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.useAzLoginAuth = true; + kcsb1.authorityId = authorityId; + kcsb1.timeoutMs = timeout; + + doComparsion( + kcsbs, + { + dataSource: "localhost", + aadFederatedSecurity: true, + authorityId, + timeoutMs: timeout, + useAzLoginAuth: true, + }, + "Data Source=localhost;AAD Federated Security=true;Authority Id=common", + "Data Source=localhost;AAD Federated Security=true;Authority Id=common" + ); + }); + + describe("from access token", () => { + const token = "some_token"; + const kcsbs = [ + KustoConnectionStringBuilder.withAccessToken("localhost", token), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.accessToken = token; + + doComparsion( + kcsbs, + { + dataSource: "localhost", + aadFederatedSecurity: true, + accessToken: token, + }, + "Data Source=localhost;AAD Federated Security=true;Authority Id=common", + "Data Source=localhost;AAD Federated Security=true;Authority Id=common" + ); + }); + + describe("from token provider", () => { + const tokenProvider = () => Promise.resolve("some_token"); + const kcsbs = [ + KustoConnectionStringBuilder.withTokenProvider("localhost", tokenProvider), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.tokenProvider = tokenProvider; + + doComparsion( + kcsbs, + { + dataSource: "localhost", + aadFederatedSecurity: true, + tokenProvider, + }, + "Data Source=localhost;AAD Federated Security=true;Authority Id=common", + "Data Source=localhost;AAD Federated Security=true;Authority Id=common" + ); + }); + + describe("interactive login", () => { + it("without optional params", () => { + const kcsbs = [ + KustoConnectionStringBuilder.withUserPrompt("localhost", "common"), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.useUserPromptAuth = true; + + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: "common", + useUserPromptAuth: true, + aadFederatedSecurity: true, + }, + "Data Source=localhost;AAD Federated Security=true;Authority Id=common", + "Data Source=localhost;AAD Federated Security=true;Authority Id=common" + ) + }); + + it("with optional params", () => { + const clientId = "clientId"; + const loginHint = "myUser"; + const timeoutMs = 10; + const kcsbs = [ + KustoConnectionStringBuilder.withUserPrompt("localhost", "common", clientId, timeoutMs, loginHint), + ]; + + const kcsb1 = new KustoConnectionStringBuilder("server=localhost"); + kcsb1.aadFederatedSecurity = true; + kcsb1.useUserPromptAuth = true; + kcsb1.applicationClientId = clientId; + kcsb1.timeoutMs = timeoutMs; + kcsb1.loginHint = loginHint; + + doComparsion( + kcsbs, + { + dataSource: "localhost", + authorityId: "common", + useUserPromptAuth: true, + aadFederatedSecurity: true, + applicationClientId: clientId, + timeoutMs, + loginHint + }, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${clientId};Authority Id=common`, + `Data Source=localhost;AAD Federated Security=true;Application Client Id=${clientId};Authority Id=common` + ) + }); }); }); }); diff --git a/azure-kusto-data/test/data/testUtils.ts b/azure-kusto-data/test/data/testUtils.ts new file mode 100644 index 0000000..de79e9c --- /dev/null +++ b/azure-kusto-data/test/data/testUtils.ts @@ -0,0 +1,13 @@ +const loginTestEnv = "LOGIN_TEST"; +const autoTestEnv = "AUTO_TEST"; + +export const testIfEnv = (definedEnv: string[], undefinedEnv: string[]) => { + if (definedEnv.filter(x => process.env[x]).length === definedEnv.length && undefinedEnv.filter(x => !process.env[x]).length === undefinedEnv.length) { + return it; + } else { + return it.skip; + } +}; + +export const manualLoginTest = (...definedEnv: string[]) => testIfEnv([loginTestEnv, ...definedEnv], [autoTestEnv]); +export const loginTest = (...definedEnv: string[]) => testIfEnv([loginTestEnv, ...definedEnv], []); \ No newline at end of file diff --git a/azure-kusto-data/test/securityTest.ts b/azure-kusto-data/test/securityTest.ts new file mode 100644 index 0000000..7d2ced3 --- /dev/null +++ b/azure-kusto-data/test/securityTest.ts @@ -0,0 +1,231 @@ +import { KustoConnectionStringBuilder } from "../index"; +import AadHelper from "../source/security"; +import { CloudSettings } from "../source/cloudSettings"; +import assert from "assert"; +import { ServerError } from "@azure/msal-node"; +import { KustoAuthenticationError } from "../source/errors"; +import { CredentialUnavailableError } from "@azure/identity"; +import { loginTest, manualLoginTest } from "./data/testUtils"; + + +describe("test errors", () => { + before(() => { + CloudSettings.getInstance().cloudCache["https://somecluster.kusto.windows.net"] = CloudSettings.getInstance().defaultCloudInfo; + }); + + it("no data source", async () => { + const kcsb = new KustoConnectionStringBuilder("test"); + kcsb.dataSource = ""; + + assert.throws(() => new AadHelper(kcsb), Error, "Invalid string builder - missing dataSource"); + }); + + it("test user pass", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const username = "username@microsoft.com"; + const kcsb = KustoConnectionStringBuilder.withAadUserPasswordAuthentication(cluster, username, "password", "organizations"); + + const helper = new AadHelper(kcsb); + try { + await helper.getAuthHeader(); + assert.fail("should throw unauthorized exception"); + } catch (e: unknown) { + assert.ok(e instanceof KustoAuthenticationError); + assert.ok(e.inner instanceof ServerError); + assert.strictEqual(e.tokenProviderName, "UserPassTokenProvider") + assert.strictEqual(e.context.userName, username); + } + }).timeout(10000); + + it("test app key", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const appId = "86f7361f-15b7-4f10-aef5-3ce66ac73766"; + const key = "private_key"; + const kcsb = KustoConnectionStringBuilder.withAadApplicationKeyAuthentication(cluster, appId, key, "organizations"); + + const helper = new AadHelper(kcsb); + try { + await helper.getAuthHeader(); + assert.fail("should throw unauthorized exception"); + } catch (e: unknown) { + assert.ok(e instanceof KustoAuthenticationError); + assert.ok(e.inner instanceof ServerError); + assert.strictEqual(e.tokenProviderName, "ApplicationKeyTokenProvider") + assert.strictEqual(e.context.clientId, appId); + } + }).timeout(10000); + + it("test app certificate", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const appId = "86f7361f-15b7-4f10-aef5-3ce66ac73766"; + const thumb = "thumb"; + const privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp\n" + + "wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5\n" + + "1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh\n" + + "3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2\n" + + "pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX\n" + + "GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il\n" + + "AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF\n" + + "L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k\n" + + "X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl\n" + + "U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ\n" + + "37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=\n" + + "-----END RSA PRIVATE KEY-----"; + const kcsb = KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication(cluster, appId, privateKey, thumb, "organizations"); + + const helper = new AadHelper(kcsb); + try { + await helper.getAuthHeader(); + assert.fail("should throw unauthorized exception"); + } catch (e: unknown) { + assert.ok(e instanceof KustoAuthenticationError); + assert.ok(e.inner instanceof ServerError); + assert.strictEqual(e.tokenProviderName, "ApplicationCertificateTokenProvider") + assert.strictEqual(e.context.clientId, appId); + assert.strictEqual(e.context.thumbprint, thumb); + } + }).timeout(10000); + + it("device code without function", () => { + const kcsb = new KustoConnectionStringBuilder("https://somecluster.kusto.windows.net"); + kcsb.aadFederatedSecurity = true; + kcsb.authorityId = "common"; + kcsb.useDeviceCodeAuth = true + + assert.throws(() => new AadHelper(kcsb), KustoAuthenticationError, "Device code authentication is not supported without a function"); + }); + + it("test msi", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const clientId = "86f7361f-15b7-4f10-aef5-3ce66ac73766"; + const kcsb = KustoConnectionStringBuilder.withAadManagedIdentities(cluster, clientId, "organizations", 1); + + const helper = new AadHelper(kcsb); + try { + await helper.getAuthHeader(); + assert.fail("should throw unauthorized exception"); + } catch (e: unknown) { + assert.ok(e instanceof KustoAuthenticationError); + assert.ok(e.inner instanceof CredentialUnavailableError); + assert.strictEqual(e.tokenProviderName, "MsiTokenProvider") + assert.strictEqual(e.context.clientId, clientId); + } + }).timeout(10000); +}); + +describe("Test providers", () => { + before(() => { + CloudSettings.getInstance().cloudCache["https://somecluster.kusto.windows.net"] = CloudSettings.getInstance().defaultCloudInfo; + }); + + it("test null", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const kcsb = new KustoConnectionStringBuilder(cluster); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.strictEqual(token, null); + }); + + it("test access token", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const kcsb = KustoConnectionStringBuilder.withAccessToken(cluster, "somekey"); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.strictEqual(token, "Bearer somekey"); + }); + + it("test callback token provider", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const kcsb = KustoConnectionStringBuilder.withTokenProvider(cluster, () => Promise.resolve("somekey")); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.strictEqual(token, "Bearer somekey"); + }); + + loginTest("APP_ID", "TENANT_ID", "APP_KEY")("test app key token provider", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const kcsb = KustoConnectionStringBuilder.withAadApplicationKeyAuthentication( + cluster, + process.env.APP_ID!, + process.env.APP_KEY!, + process.env.TENANT_ID!, + ); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.notStrictEqual(token, null); + }).timeout(10000); + + manualLoginTest("APP_ID", "TENANT_ID", "CERT_THUMBPRINT", "CERT_PUBLIC", "CERT_PEM")("test app certificate token provider", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const kcsb = KustoConnectionStringBuilder.withAadApplicationCertificateAuthentication( + cluster, + process.env.APP_KEY!, + process.env.CERT_PEM!, + process.env.CERT_THUMBPRINT!, + process.env.CERT_PUBLIC! + ); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.notStrictEqual(token, null); + }).timeout(10000); + + manualLoginTest("APP_ID", "USER_NAME", "USER_PASS")("test user pass provider", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + const kcsb = KustoConnectionStringBuilder.withAadUserPasswordAuthentication( + cluster, + process.env.USER_NAME!, + process.env.USER_PASS!, + process.env.TENANT_ID!, + ); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.notStrictEqual(token, null); + }).timeout(10000); + + manualLoginTest()("test az login", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + + const kcsb = KustoConnectionStringBuilder.withAzLoginIdentity(cluster, "organizations"); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.notStrictEqual(token, null); + }).timeout(30000); + + manualLoginTest()("test device code", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + + const kcsb = KustoConnectionStringBuilder.withAadDeviceAuthentication(cluster, "organizations"); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.notStrictEqual(token, null); + }).timeout(30000); + + manualLoginTest()("test user prompt", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + + const kcsb = KustoConnectionStringBuilder.withUserPrompt(cluster, "organizations"); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.notStrictEqual(token, null); + }).timeout(30000); + + manualLoginTest("TEST_MSI")("test msi", async () => { + const cluster = "https://somecluster.kusto.windows.net"; + + const kcsb = KustoConnectionStringBuilder.withAadManagedIdentities(cluster, undefined,"organizations"); + + const helper = new AadHelper(kcsb); + const token = await helper.getAuthHeader(); + assert.notStrictEqual(token, null); + }).timeout(30000); +}); diff --git a/azure-kusto-data/test/tokenProviderTest.ts b/azure-kusto-data/test/tokenProviderTest.ts index 24f38c8..aa8ea72 100644 --- a/azure-kusto-data/test/tokenProviderTest.ts +++ b/azure-kusto-data/test/tokenProviderTest.ts @@ -27,7 +27,7 @@ describe("CloudInfo", () => { } assert.strictEqual(provider.scopes[0], "https://fakeurl.kusto.windows.net/.default"); - }); + }).timeout(5000); it("mfa off", async () => { const fakeUri2 = "https://fakeurl2.kusto.windows.net" @@ -49,6 +49,6 @@ describe("CloudInfo", () => { } assert.strictEqual(provider.scopes[0], "https://fakeurl.kustomfa.windows.net/.default"); - }); + }).timeout(5000); }); }); diff --git a/azure-kusto-ingest/test/managedStreamingIngestClientTest.ts b/azure-kusto-ingest/test/managedStreamingIngestClientTest.ts index d25d993..1498d44 100644 --- a/azure-kusto-ingest/test/managedStreamingIngestClientTest.ts +++ b/azure-kusto-ingest/test/managedStreamingIngestClientTest.ts @@ -195,7 +195,7 @@ describe("ManagedStreamingIngestClient", () => { describe("helper methods", () => { it("should be able to create a ManagedStreamingIngestClient from a DM URI", () => { - const client = KustoManagedStreamingIngestClient.fromDmConnectionString(KustoConnectionStringBuilder.withAccessToken("https://ingest-dummy.kusto.windows.net")); + const client = KustoManagedStreamingIngestClient.fromDmConnectionString(new KustoConnectionStringBuilder("https://ingest-dummy.kusto.windows.net")); assert.strictEqual((client as any).queuedIngestClient.resourceManager.kustoClient.connectionString.dataSource, "https://ingest-dummy.kusto.windows.net"); @@ -203,10 +203,10 @@ describe("ManagedStreamingIngestClient", () => { "https://dummy.kusto.windows.net"); }); it("should fail when trying to create a ManagedStreamingIngestClient from an invalid DM URI", () => { - assert.throws(() => KustoManagedStreamingIngestClient.fromDmConnectionString(KustoConnectionStringBuilder.withAccessToken("https://dummy.kusto.windows.net"))); + assert.throws(() => KustoManagedStreamingIngestClient.fromDmConnectionString(new KustoConnectionStringBuilder("https://dummy.kusto.windows.net"))); }); it("should be able to create a ManagedStreamingIngestClient from an Engine URI", () => { - const client = KustoManagedStreamingIngestClient.fromEngineConnectionString(KustoConnectionStringBuilder.withAccessToken("https://dummy.kusto.windows.net")); + const client = KustoManagedStreamingIngestClient.fromEngineConnectionString(new KustoConnectionStringBuilder("https://dummy.kusto.windows.net")); assert.strictEqual((client as any).queuedIngestClient.resourceManager.kustoClient.connectionString.dataSource, "https://ingest-dummy.kusto.windows.net"); @@ -214,7 +214,7 @@ describe("ManagedStreamingIngestClient", () => { "https://dummy.kusto.windows.net"); }); it("should fail when trying to create a ManagedStreamingIngestClient from an invalid Engine URI", () => { - assert.throws(() => KustoManagedStreamingIngestClient.fromEngineConnectionString(KustoConnectionStringBuilder.withAccessToken("https://ingest-dummy.kusto.windows.net"))); + assert.throws(() => KustoManagedStreamingIngestClient.fromEngineConnectionString(new KustoConnectionStringBuilder("https://ingest-dummy.kusto.windows.net"))); }); }); });