TokenProviders - Adding many features, including interactive login (#167)
* -Added InteractiveLoginProvider -Restructured Token Providers to support more features and avoid code duplication -Restructured connection builder to be more type safe and less verbose * -Added more tests (with many more to come) -More robust options -Add toString for connection string builder * Added no cred login and aad federated security * -Added init for callback provider -Added tests for certificate * Added tests for all connection builders and fixed bugs * Added more tests and context * Added more tests * Added tests to complete the coverage * Error fixes * Update security.ts * Changed order back to avoid breaking change * Rename to fit with other sdks
This commit is contained in:
Родитель
adaced569e
Коммит
b0e5a096a7
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<KustoConnectionStringBuilder, string | boolean | undefined>;
|
||||
|
||||
// This type gurantess that we don't have properties in KeywordMapping that don't exist in KustoConnectionStringBuilder
|
||||
type KeywordMappingRecordType = Partial<Record<KcsbMappedKeys, MappingType>>;
|
||||
|
||||
const KeywordMapping: KeywordMappingRecordType = Object.freeze<Readonly<KeywordMappingRecordType>>({
|
||||
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<string>;
|
||||
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<KustoConnectionStringBuilder, boolean | undefined>] = kvp[1].trim().toLowerCase() === "true";
|
||||
} else {
|
||||
this[mappingTypeName as KeyOfType<KustoConnectionStringBuilder, string | undefined>] = 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<string>) {
|
||||
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;
|
|
@ -0,0 +1,6 @@
|
|||
export class KustoAuthenticationError extends Error {
|
||||
constructor(message: string, public inner: Error | undefined, public tokenProviderName: string, public context: Record<string, any>) {
|
||||
super(message);
|
||||
this.name = "KustoAuthenticationError";
|
||||
}
|
||||
}
|
|
@ -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<string> {
|
||||
const token = await this.tokenProvider.acquireToken();
|
||||
return `${token.tokenType} ${token.accessToken}`;
|
||||
async getAuthHeader(): Promise<string | null> {
|
||||
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;
|
||||
export default AadHelper;
|
||||
|
|
|
@ -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<TokenResponse>;
|
||||
|
||||
context(): Record<string, any> {
|
||||
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<TokenResponse> {
|
||||
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<TokenResponse> {
|
||||
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<AuthenticationResult | null>;
|
||||
authorityUri!: string;
|
||||
|
||||
protected constructor(kustoUri: string, authorityId?: string) {
|
||||
abstract initClient(): void;
|
||||
|
||||
abstract acquireMsalToken(): Promise<TokenType | null>;
|
||||
|
||||
protected constructor(kustoUri: string, authorityId: string) {
|
||||
super(kustoUri);
|
||||
this.initialized = false;
|
||||
this.authorityId = authorityId;
|
||||
}
|
||||
|
||||
async acquireToken(): Promise<TokenResponse> {
|
||||
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<string, any> {
|
||||
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<TokenType | null> {
|
||||
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<string, any> {
|
||||
let base: Record<string, any> = { ...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<string, any> {
|
||||
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<AuthenticationResult | null> {
|
||||
async acquireMsalToken(): Promise<TokenType | null> {
|
||||
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<string, any> {
|
||||
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<AuthenticationResult | null> {
|
||||
async acquireMsalToken(): Promise<TokenType | null> {
|
||||
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<AuthenticationResult | null> {
|
||||
acquireMsalToken(): Promise<TokenType | null> {
|
||||
return this.msalClient.acquireTokenByClientCredential({ scopes: this.scopes });
|
||||
}
|
||||
|
||||
context(): Record<string, any> {
|
||||
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<AuthenticationResult | null> {
|
||||
acquireMsalToken(): Promise<TokenType | null> {
|
||||
return this.msalClient.acquireTokenByClientCredential({ scopes: this.scopes });
|
||||
}
|
||||
|
||||
context(): Record<string, any> {
|
||||
return { ...super.context(), clientId: this.appClientId, thumbprint: this.certThumbprint };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export type KeyOfType<T, V> = keyof {
|
||||
[P in keyof T as T[P] extends V? P: never]: unknown
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<Record<keyof KustoConnectionStringBuilder, unknown>>,
|
||||
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`
|
||||
)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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], []);
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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")));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче