diff --git a/package.json b/package.json index ba18f77af..b2419bb24 100644 --- a/package.json +++ b/package.json @@ -90,9 +90,9 @@ "scripts": { "docs": "typedoc", "lint": "eslint src test --ext .js,.ts && tsc", - "test": "mocha test/unit test/unit/token test/unit/tracking-buffer", + "test": "mocha test/unit test/unit/authentication test/unit/token test/unit/tracking-buffer", "test-integration": "mocha test/integration/", - "test-all": "mocha test/unit/ test/unit/token/ test/unit/tracking-buffer test/integration/", + "test-all": "mocha test/unit/ test/unit/authentication test/unit/token/ test/unit/tracking-buffer test/integration/", "build": "rimraf lib && babel src --out-dir lib --extensions .js,.ts", "prepublish": "npm run build", "semantic-release": "semantic-release" diff --git a/src/authentication/authentication-types.ts b/src/authentication/authentication-types.ts new file mode 100644 index 000000000..436d9d7ea --- /dev/null +++ b/src/authentication/authentication-types.ts @@ -0,0 +1,84 @@ +import { AzureActiveDirectoryAccessTokenAuthenticationType } from './azure-active-directory-access-token'; +import { AzureActiveDirectoryMsiAppServiceAuthenticationType } from './azure-active-directory-msi-app-service'; +import { AzureActiveDirectoryMsiVmAuthenticationType } from './azure-active-directory-msi-vm'; +import { AzureActiveDirectoryPasswordAuthenticationType } from './azure-active-directory-password'; +import { AzureActiveDirectoryServicePrincipalSecretType } from './azure-active-directory-service-principal-secret'; +import { DefaultAuthenticationType } from './default'; +import { NtlmAuthenticationType } from './ntlm'; + +export const SupportedAuthenticationTypes = Object.freeze([ + AzureActiveDirectoryAccessTokenAuthenticationType, + AzureActiveDirectoryMsiAppServiceAuthenticationType, + AzureActiveDirectoryMsiVmAuthenticationType, + AzureActiveDirectoryPasswordAuthenticationType, + AzureActiveDirectoryServicePrincipalSecretType, + DefaultAuthenticationType, + NtlmAuthenticationType, +]); + +/** + * Authentication types that requires FedAuth request + */ +export type FedAuthAuthenticationType = typeof AzureActiveDirectoryPasswordAuthenticationType | + typeof AzureActiveDirectoryMsiVmAuthenticationType | + typeof AzureActiveDirectoryMsiAppServiceAuthenticationType | + typeof AzureActiveDirectoryServicePrincipalSecretType; + +/** + * Active Directory Authentication types + */ +export type AADAuthenticationType = FedAuthAuthenticationType + | typeof AzureActiveDirectoryAccessTokenAuthenticationType + +/** + * All authentication types + */ +export type AuthenticationType = AADAuthenticationType + | typeof DefaultAuthenticationType + | typeof NtlmAuthenticationType; + + +/** + * Checks if authentication type requires FedAuth request + * + * @param {AuthenticationType} authenticationType + * @return {boolean} + */ +export function isSupportedAuthenticationType(authenticationType: AuthenticationType): boolean { + return SupportedAuthenticationTypes.includes(authenticationType); +} + +/** + * Checks if authentication type requires FedAuth request + * + * @param {AuthenticationType} authenticationType + * @return {boolean} + */ +export function isFedAuthAuthenticationType(authenticationType: AuthenticationType): boolean { + switch (authenticationType) { + case AzureActiveDirectoryMsiAppServiceAuthenticationType: + case AzureActiveDirectoryMsiVmAuthenticationType: + case AzureActiveDirectoryPasswordAuthenticationType: + case AzureActiveDirectoryServicePrincipalSecretType: + return true; + + default: + return false; + } +} + +/** + * Checks if authentication type is AAD Authentication + * + * @param {AuthenticationType} authenticationType + * @return {boolean} + */ +export function isAADAuthenticationType(authenticationType: AuthenticationType): boolean { + switch (authenticationType) { + case AzureActiveDirectoryAccessTokenAuthenticationType: + return true; + + default: + return isFedAuthAuthenticationType(authenticationType); + } +} diff --git a/src/authentication/authentication.ts b/src/authentication/authentication.ts new file mode 100644 index 000000000..55ea9d6d0 --- /dev/null +++ b/src/authentication/authentication.ts @@ -0,0 +1,23 @@ +import { AzureActiveDirectoryAccessTokenAuthentication } from './azure-active-directory-access-token'; +import { AzureActiveDirectoryServicePrincipalSecret } from './azure-active-directory-service-principal-secret'; +import { AzureActiveDirectoryPasswordAuthentication } from './azure-active-directory-password'; +import { AzureActiveDirectoryMsiVmAuthentication } from './azure-active-directory-msi-vm'; +import { AzureActiveDirectoryMsiAppServiceAuthentication } from './azure-active-directory-msi-app-service'; +import { DefaultAuthentication } from './default'; +import { NtlmAuthentication } from './ntlm'; + +/** + * FedAuth authentications + */ +export type FedAuthAuthentication = AzureActiveDirectoryPasswordAuthentication + | AzureActiveDirectoryMsiVmAuthentication + | AzureActiveDirectoryMsiAppServiceAuthentication + | AzureActiveDirectoryServicePrincipalSecret; + +/** + * All supported authentications + */ +export type Authentication = FedAuthAuthentication + | AzureActiveDirectoryAccessTokenAuthentication + | DefaultAuthentication + | NtlmAuthentication; diff --git a/src/authentication/azure-active-directory-access-token.ts b/src/authentication/azure-active-directory-access-token.ts new file mode 100644 index 000000000..b29efec83 --- /dev/null +++ b/src/authentication/azure-active-directory-access-token.ts @@ -0,0 +1,39 @@ +export const AzureActiveDirectoryAccessTokenAuthenticationType = 'azure-active-directory-access-token'; + +export interface AzureActiveDirectoryAccessTokenAuthentication { + type: typeof AzureActiveDirectoryAccessTokenAuthenticationType; + options: { + /** + * A user need to provide `token` which they retrived else where + * to forming the connection. + */ + token: string; + }; +} + +/** + * @param {AzureActiveDirectoryAccessTokenAuthentication} authentication + * @throws {TypeError} + */ +export function validateAADAccessTokenOptions(authentication: AzureActiveDirectoryAccessTokenAuthentication): void { + const { options } = authentication; + + if (typeof options.token !== 'string') { + throw new TypeError('The "config.authentication.options.token" property must be of type string.'); + } +} + +/** + * @param {AzureActiveDirectoryAccessTokenAuthentication} authentication + * @return {AzureActiveDirectoryAccessTokenAuthentication} + */ +export function parseAADAccessTokenOptions(authentication: AzureActiveDirectoryAccessTokenAuthentication): AzureActiveDirectoryAccessTokenAuthentication { + const { options } = authentication; + + return { + type: AzureActiveDirectoryAccessTokenAuthenticationType, + options: { + token: options.token, + }, + }; +} diff --git a/src/authentication/azure-active-directory-msi-app-service.ts b/src/authentication/azure-active-directory-msi-app-service.ts new file mode 100644 index 000000000..97a4e5ef5 --- /dev/null +++ b/src/authentication/azure-active-directory-msi-app-service.ts @@ -0,0 +1,61 @@ +export const AzureActiveDirectoryMsiAppServiceAuthenticationType = 'azure-active-directory-msi-app-service'; + +export interface AzureActiveDirectoryMsiAppServiceAuthentication { + type: typeof AzureActiveDirectoryMsiAppServiceAuthenticationType; + options: { + /** + * If you user want to connect to an Azure app service using a specific client account + * they need to provide `clientId` asscoiate to their created idnetity. + * + * This is optional for retrieve token from azure web app service + */ + clientId?: string; + /** + * A msi app service environment need to provide `msiEndpoint` for retriving the accesstoken. + */ + msiEndpoint?: string; + /** + * A msi app service environment need to provide `msiSecret` for retriving the accesstoken. + */ + msiSecret?: string; + }; +} + + +/** + * @param {AzureActiveDirectoryMsiAppServiceAuthentication} authentication + * @throws {TypeError} + */ +export function validateAADMsiAppServiceOptions(authentication: AzureActiveDirectoryMsiAppServiceAuthentication): void { + const { options } = authentication; + + if (options.clientId !== undefined && typeof options.clientId !== 'string') { + throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); + } + + if (options.msiEndpoint !== undefined && typeof options.msiEndpoint !== 'string') { + throw new TypeError('The "config.authentication.options.msiEndpoint" property must be of type string.'); + } + + if (options.msiSecret !== undefined && typeof options.msiSecret !== 'string') { + throw new TypeError('The "config.authentication.options.msiSecret" property must be of type string.'); + } + +} + +/** + * @param {AzureActiveDirectoryMsiAppServiceAuthentication} authentication + * @return {AzureActiveDirectoryMsiAppServiceAuthentication} + */ +export function parseAADMsiAppServiceOptions(authentication: AzureActiveDirectoryMsiAppServiceAuthentication): AzureActiveDirectoryMsiAppServiceAuthentication { + const { options } = authentication; + + return { + type: AzureActiveDirectoryMsiAppServiceAuthenticationType, + options: { + clientId: options.clientId, + msiEndpoint: options.msiEndpoint, + msiSecret: options.msiSecret, + }, + }; +} diff --git a/src/authentication/azure-active-directory-msi-vm.ts b/src/authentication/azure-active-directory-msi-vm.ts new file mode 100644 index 000000000..f255a6a5b --- /dev/null +++ b/src/authentication/azure-active-directory-msi-vm.ts @@ -0,0 +1,52 @@ +export const AzureActiveDirectoryMsiVmAuthenticationType = 'azure-active-directory-msi-vm'; + +export interface AzureActiveDirectoryMsiVmAuthentication { + type: typeof AzureActiveDirectoryMsiVmAuthenticationType; + options: { + /** + * If you user want to connect to an Azure app service using a specific client account + * they need to provide `clientId` asscoiate to their created idnetity. + * + * This is optional for retrieve token from azure web app service + */ + clientId?: string; + /** + * A user need to provide `msiEndpoint` for retriving the accesstoken. + */ + msiEndpoint?: string; + }; +} + + +/** + * @param {AzureActiveDirectoryMsiVmAuthentication} authentication + * @throws {TypeError} + */ +export function validateAADMsiVmOptions(authentication: AzureActiveDirectoryMsiVmAuthentication): void { + const { options } = authentication; + + if (options.clientId !== undefined && typeof options.clientId !== 'string') { + throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); + } + + if (options.msiEndpoint !== undefined && typeof options.msiEndpoint !== 'string') { + throw new TypeError('The "config.authentication.options.msiEndpoint" property must be of type string.'); + } + +} + +/** + * @param {AzureActiveDirectoryMsiVmAuthentication} authentication + * @return {AzureActiveDirectoryMsiVmAuthentication} + */ +export function parseAADMsiVmOptions(authentication: AzureActiveDirectoryMsiVmAuthentication): AzureActiveDirectoryMsiVmAuthentication { + const { options } = authentication; + + return { + type: AzureActiveDirectoryMsiVmAuthenticationType, + options: { + clientId: options.clientId, + msiEndpoint: options.msiEndpoint, + }, + }; +} diff --git a/src/authentication/azure-active-directory-password.ts b/src/authentication/azure-active-directory-password.ts new file mode 100644 index 000000000..4f832752d --- /dev/null +++ b/src/authentication/azure-active-directory-password.ts @@ -0,0 +1,59 @@ +export const AzureActiveDirectoryPasswordAuthenticationType = 'azure-active-directory-password'; + +export interface AzureActiveDirectoryPasswordAuthentication { + type: typeof AzureActiveDirectoryPasswordAuthenticationType; + + options: { + /** + * A user need to provide `userName` asscoiate to their account. + */ + userName: string; + /** + * A user need to provide `password` asscoiate to their account. + */ + password: string; + + /** + * Optional parameter for specific Azure tenant ID + */ + domain: string; + }; +} + + +/** + * @param {AzureActiveDirectoryPasswordAuthentication} authentication + * @throws {TypeError} + */ +export function validateAADPasswordOptions(authentication: AzureActiveDirectoryPasswordAuthentication): void { + const { options } = authentication; + + if (options.userName !== undefined && typeof options.userName !== 'string') { + throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); + } + + if (options.password !== undefined && typeof options.password !== 'string') { + throw new TypeError('The "config.authentication.options.password" property must be of type string.'); + } + + if (options.domain !== undefined && typeof options.domain !== 'string') { + throw new TypeError('The "config.authentication.options.domain" property must be of type string.'); + } +} + +/** + * @param {AzureActiveDirectoryPasswordAuthentication} authentication + * @return {AzureActiveDirectoryPasswordAuthentication} + */ +export function parseAADPasswordOptions(authentication: AzureActiveDirectoryPasswordAuthentication): AzureActiveDirectoryPasswordAuthentication { + const { options } = authentication; + + return { + type: AzureActiveDirectoryPasswordAuthenticationType, + options: { + userName: options.userName, + password: options.password, + domain: options.domain, + }, + }; +} diff --git a/src/authentication/azure-active-directory-service-principal-secret.ts b/src/authentication/azure-active-directory-service-principal-secret.ts new file mode 100644 index 000000000..e20a7563c --- /dev/null +++ b/src/authentication/azure-active-directory-service-principal-secret.ts @@ -0,0 +1,58 @@ +export const AzureActiveDirectoryServicePrincipalSecretType = 'azure-active-directory-service-principal-secret'; + +export interface AzureActiveDirectoryServicePrincipalSecret { + type: typeof AzureActiveDirectoryServicePrincipalSecretType; + options: { + /** + * Application (`client`) ID from your registered Azure application + */ + clientId: string; + /** + * The created `client secret` for this registered Azure application + */ + clientSecret: string; + /** + * Directory (`tenant`) ID from your registered Azure application + */ + tenantId: string; + }; +} + + +/** + * @param {AzureActiveDirectoryServicePrincipalSecret} authentication + * @throws {TypeError} + */ +export function validateAADServicePrincipalSecretOptions(authentication: AzureActiveDirectoryServicePrincipalSecret): void { + const { options } = authentication; + + if (typeof options.clientId !== 'string') { + throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); + } + + if (typeof options.clientSecret !== 'string') { + throw new TypeError('The "config.authentication.options.clientSecret" property must be of type string.'); + } + + if (typeof options.tenantId !== 'string') { + throw new TypeError('The "config.authentication.options.tenantId" property must be of type string.'); + } + +} + +/** + * @param {AzureActiveDirectoryServicePrincipalSecret} authentication + * @return {AzureActiveDirectoryServicePrincipalSecret} + */ +export function parseAADServicePrincipalSecretOptions(authentication: AzureActiveDirectoryServicePrincipalSecret): AzureActiveDirectoryServicePrincipalSecret { + const { options } = authentication; + + return { + type: AzureActiveDirectoryServicePrincipalSecretType, + options: { + clientId: options.clientId, + clientSecret: options.clientSecret, + tenantId: options.tenantId, + }, + }; +} diff --git a/src/authentication/default.ts b/src/authentication/default.ts new file mode 100644 index 000000000..3b6d03ca4 --- /dev/null +++ b/src/authentication/default.ts @@ -0,0 +1,62 @@ +export const DefaultAuthenticationType = 'default'; + +export interface DefaultAuthentication { + type: typeof DefaultAuthenticationType; + options: { + /** + * User name to use for sql server login. + */ + userName?: string; + /** + * Password to use for sql server login. + */ + password?: string; + }; +} + + +/** + * @param {DefaultAuthentication} authentication + * @throws {TypeError} + */ +export function validateDefaultOptions(authentication: DefaultAuthentication): void { + const { options } = authentication; + + if (options.userName !== undefined && typeof options.userName !== 'string') { + throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); + } + + if (options.password !== undefined && typeof options.password !== 'string') { + throw new TypeError('The "config.authentication.options.password" property must be of type string.'); + } + +} + +/** + * @param {DefaultAuthentication} authentication + * @return {DefaultAuthentication} + */ +export function parseDefaultOptions(authentication: DefaultAuthentication): DefaultAuthentication { + const { options } = authentication; + + return { + type: DefaultAuthenticationType, + options: { + userName: options.userName, + password: options.password, + }, + }; +} + +/** + * @return {DefaultAuthentication} + */ +export function parseDefaultAnonymousOptions(): DefaultAuthentication { + return { + type: DefaultAuthenticationType, + options: { + userName: undefined, + password: undefined, + }, + }; +} diff --git a/src/authentication/ntlm.ts b/src/authentication/ntlm.ts new file mode 100644 index 000000000..34420d929 --- /dev/null +++ b/src/authentication/ntlm.ts @@ -0,0 +1,58 @@ +export const NtlmAuthenticationType = 'ntlm'; + +export interface NtlmAuthentication { + type: typeof NtlmAuthenticationType; + options: { + /** + * User name from your windows account. + */ + userName: string; + /** + * Password from your windows account. + */ + password: string; + /** + * Once you set domain for ntlm authentication type, driver will connect to SQL Server using domain login. + * + * This is necessary for forming a connection using ntlm type + */ + domain: string; + }; +} + +/** + * @param {NtlmAuthentication} authentication + * @throws {TypeError} + */ +export function validateNtlmOptions(authentication: NtlmAuthentication): void { + const { options } = authentication; + + if (typeof options.domain !== 'string') { + throw new TypeError('The "config.authentication.options.domain" property must be of type string.'); + } + + if (options.userName !== undefined && typeof options.userName !== 'string') { + throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); + } + + if (options.password !== undefined && typeof options.password !== 'string') { + throw new TypeError('The "config.authentication.options.password" property must be of type string.'); + } +} + +/** + * @param {NtlmAuthentication} authentication + * @return {NtlmAuthentication} + */ +export function parseNtlmOptions(authentication: NtlmAuthentication): NtlmAuthentication { + const { options } = authentication; + + return { + type: NtlmAuthenticationType, + options: { + userName: options.userName, + password: options.password, + domain: options.domain && options.domain.toUpperCase(), + }, + }; +} diff --git a/src/connection.ts b/src/connection.ts index f7fa2423e..27f53bdad 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -8,12 +8,12 @@ import { createSecureContext, SecureContext, SecureContextOptions } from 'tls'; import { Readable } from 'readable-stream'; import { - loginWithVmMSI, + ApplicationTokenCredentials, loginWithAppServiceMSI, - UserTokenCredentials, - MSIVmTokenCredentials, + loginWithVmMSI, MSIAppServiceTokenCredentials, - ApplicationTokenCredentials + MSIVmTokenCredentials, + UserTokenCredentials, } from '@azure/ms-rest-nodeauth'; import BulkLoad, { Options as BulkLoadOptions, Callback as BulkLoadCallback } from './bulk-load'; @@ -45,6 +45,56 @@ import depd from 'depd'; import { MemoryCache } from 'adal-node'; import AbortController from 'node-abort-controller'; +import { Authentication, FedAuthAuthentication } from './authentication/authentication'; +import { + AuthenticationType, + isAADAuthenticationType, + isFedAuthAuthenticationType, + isSupportedAuthenticationType, + SupportedAuthenticationTypes, +} from './authentication/authentication-types'; +import { + NtlmAuthentication, + NtlmAuthenticationType, + parseNtlmOptions, + validateNtlmOptions, +} from './authentication/ntlm'; +import { + AzureActiveDirectoryPasswordAuthentication, + AzureActiveDirectoryPasswordAuthenticationType, + parseAADPasswordOptions, + validateAADPasswordOptions, +} from './authentication/azure-active-directory-password'; +import { + AzureActiveDirectoryAccessTokenAuthentication, + AzureActiveDirectoryAccessTokenAuthenticationType, + parseAADAccessTokenOptions, + validateAADAccessTokenOptions, +} from './authentication/azure-active-directory-access-token'; +import { + AzureActiveDirectoryMsiVmAuthentication, + AzureActiveDirectoryMsiVmAuthenticationType, + parseAADMsiVmOptions, + validateAADMsiVmOptions, +} from './authentication/azure-active-directory-msi-vm'; +import { + AzureActiveDirectoryMsiAppServiceAuthentication, + AzureActiveDirectoryMsiAppServiceAuthenticationType, + parseAADMsiAppServiceOptions, + validateAADMsiAppServiceOptions, +} from './authentication/azure-active-directory-msi-app-service'; +import { + AzureActiveDirectoryServicePrincipalSecret, + AzureActiveDirectoryServicePrincipalSecretType, + parseAADServicePrincipalSecretOptions, + validateAADServicePrincipalSecretOptions, +} from './authentication/azure-active-directory-service-principal-secret'; +import { + DefaultAuthentication, + parseDefaultAnonymousOptions, + parseDefaultOptions, + validateDefaultOptions, +} from './authentication/default'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const deprecate = depd('tedious'); @@ -196,133 +246,13 @@ const DEFAULT_LANGUAGE = 'us_english'; */ const DEFAULT_DATEFORMAT = 'mdy'; -interface AzureActiveDirectoryMsiAppServiceAuthentication { - type: 'azure-active-directory-msi-app-service'; - options: { - /** - * If you user want to connect to an Azure app service using a specific client account - * they need to provide `clientId` asscoiate to their created idnetity. - * - * This is optional for retrieve token from azure web app service - */ - clientId?: string; - /** - * A msi app service environment need to provide `msiEndpoint` for retriving the accesstoken. - */ - msiEndpoint?: string; - /** - * A msi app service environment need to provide `msiSecret` for retriving the accesstoken. - */ - msiSecret?: string; - }; -} - -interface AzureActiveDirectoryMsiVmAuthentication { - type: 'azure-active-directory-msi-vm'; - options: { - /** - * If you user want to connect to an Azure app service using a specific client account - * they need to provide `clientId` asscoiate to their created idnetity. - * - * This is optional for retrieve token from azure web app service - */ - clientId?: string; - /** - * A user need to provide `msiEndpoint` for retriving the accesstoken. - */ - msiEndpoint?: string; - }; -} - -interface AzureActiveDirectoryAccessTokenAuthentication { - type: 'azure-active-directory-access-token'; - options: { - /** - * A user need to provide `token` which they retrived else where - * to forming the connection. - */ - token: string; - }; -} - -interface AzureActiveDirectoryPasswordAuthentication { - type: 'azure-active-directory-password'; - options: { - /** - * A user need to provide `userName` asscoiate to their account. - */ - userName: string; - /** - * A user need to provide `password` asscoiate to their account. - */ - password: string; - - /** - * Optional parameter for specific Azure tenant ID - */ - domain: string; - }; -} - -interface AzureActiveDirectoryServicePrincipalSecret { - type: 'azure-active-directory-service-principal-secret'; - options: { - /** - * Application (`client`) ID from your registered Azure application - */ - clientId: string; - /** - * The created `client secret` for this registered Azure application - */ - clientSecret: string; - /** - * Directory (`tenant`) ID from your registered Azure application - */ - tenantId: string; - }; -} - -interface NtlmAuthentication { - type: 'ntlm'; - options: { - /** - * User name from your windows account. - */ - userName: string; - /** - * Password from your windows account. - */ - password: string; - /** - * Once you set domain for ntlm authentication type, driver will connect to SQL Server using domain login. - * - * This is necessary for forming a connection using ntlm type - */ - domain: string; - }; -} - -interface DefaultAuthentication { - type: 'default'; - options: { - /** - * User name to use for sql server login. - */ - userName?: string; - /** - * Password to use for sql server login. - */ - password?: string; - }; -} - interface ErrorWithCode extends Error { code?: string; } interface InternalConnectionConfig { server: string; - authentication: DefaultAuthentication | NtlmAuthentication | AzureActiveDirectoryPasswordAuthentication | AzureActiveDirectoryMsiAppServiceAuthentication | AzureActiveDirectoryMsiVmAuthentication | AzureActiveDirectoryAccessTokenAuthentication | AzureActiveDirectoryServicePrincipalSecret; + authentication: Authentication; options: InternalConnectionOptions; } @@ -400,16 +330,6 @@ interface State { }; } -type Authentication = DefaultAuthentication | - NtlmAuthentication | - AzureActiveDirectoryPasswordAuthentication | - AzureActiveDirectoryMsiAppServiceAuthentication | - AzureActiveDirectoryMsiVmAuthentication | - AzureActiveDirectoryAccessTokenAuthentication | - AzureActiveDirectoryServicePrincipalSecret; - -type AuthenticationType = Authentication['type']; - export interface ConnectionConfiguration { /** * Hostname to connect to. @@ -1057,146 +977,46 @@ class Connection extends EventEmitter { throw new TypeError('The "config.authentication.type" property must be of type string.'); } - if (type !== 'default' && type !== 'ntlm' && type !== 'azure-active-directory-password' && type !== 'azure-active-directory-access-token' && type !== 'azure-active-directory-msi-vm' && type !== 'azure-active-directory-msi-app-service' && type !== 'azure-active-directory-service-principal-secret') { - throw new TypeError('The "type" property must one of "default", "ntlm", "azure-active-directory-password", "azure-active-directory-access-token", "azure-active-directory-msi-vm" or "azure-active-directory-msi-app-service" or "azure-active-directory-service-principal-secret".'); + if (!isSupportedAuthenticationType(type)) { + throw new TypeError(`The "type" property must one of "${SupportedAuthenticationTypes.join('", "')}".`); } if (typeof options !== 'object' || options === null) { throw new TypeError('The "config.authentication.options" property must be of type object.'); } - if (type === 'ntlm') { - if (typeof options.domain !== 'string') { - throw new TypeError('The "config.authentication.options.domain" property must be of type string.'); - } - - if (options.userName !== undefined && typeof options.userName !== 'string') { - throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); - } - - if (options.password !== undefined && typeof options.password !== 'string') { - throw new TypeError('The "config.authentication.options.password" property must be of type string.'); - } - - authentication = { - type: 'ntlm', - options: { - userName: options.userName, - password: options.password, - domain: options.domain && options.domain.toUpperCase() - } - }; - } else if (type === 'azure-active-directory-password') { - if (options.userName !== undefined && typeof options.userName !== 'string') { - throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); - } - - if (options.password !== undefined && typeof options.password !== 'string') { - throw new TypeError('The "config.authentication.options.password" property must be of type string.'); - } - - authentication = { - type: 'azure-active-directory-password', - options: { - userName: options.userName, - password: options.password, - domain: options.domain, - } - }; - } else if (type === 'azure-active-directory-access-token') { - if (typeof options.token !== 'string') { - throw new TypeError('The "config.authentication.options.token" property must be of type string.'); - } - - authentication = { - type: 'azure-active-directory-access-token', - options: { - token: options.token - } - }; - } else if (type === 'azure-active-directory-msi-vm') { - if (options.clientId !== undefined && typeof options.clientId !== 'string') { - throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); - } - - if (options.msiEndpoint !== undefined && typeof options.msiEndpoint !== 'string') { - throw new TypeError('The "config.authentication.options.msiEndpoint" property must be of type string.'); - } - - authentication = { - type: 'azure-active-directory-msi-vm', - options: { - clientId: options.clientId, - msiEndpoint: options.msiEndpoint - } - }; - } else if (type === 'azure-active-directory-msi-app-service') { - if (options.clientId !== undefined && typeof options.clientId !== 'string') { - throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); - } - - if (options.msiEndpoint !== undefined && typeof options.msiEndpoint !== 'string') { - throw new TypeError('The "config.authentication.options.msiEndpoint" property must be of type string.'); - } - - if (options.msiSecret !== undefined && typeof options.msiSecret !== 'string') { - throw new TypeError('The "config.authentication.options.msiSecret" property must be of type string.'); - } - - authentication = { - type: 'azure-active-directory-msi-app-service', - options: { - clientId: options.clientId, - msiEndpoint: options.msiEndpoint, - msiSecret: options.msiSecret - } - }; - } else if (type === 'azure-active-directory-service-principal-secret') { - if (typeof options.clientId !== 'string') { - throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); - } - - if (typeof options.clientSecret !== 'string') { - throw new TypeError('The "config.authentication.options.clientSecret" property must be of type string.'); - } - - if (typeof options.tenantId !== 'string') { - throw new TypeError('The "config.authentication.options.tenantId" property must be of type string.'); - } - - authentication = { - type: 'azure-active-directory-service-principal-secret', - options: { - clientId: options.clientId, - clientSecret: options.clientSecret, - tenantId: options.tenantId - } - }; - } else { - if (options.userName !== undefined && typeof options.userName !== 'string') { - throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); - } - - if (options.password !== undefined && typeof options.password !== 'string') { - throw new TypeError('The "config.authentication.options.password" property must be of type string.'); - } - - authentication = { - type: 'default', - options: { - userName: options.userName, - password: options.password - } - }; + switch (type) { + case NtlmAuthenticationType: + validateNtlmOptions(config.authentication as NtlmAuthentication); + authentication = parseNtlmOptions(config.authentication as NtlmAuthentication); + break; + case AzureActiveDirectoryPasswordAuthenticationType : + validateAADPasswordOptions(config.authentication as AzureActiveDirectoryPasswordAuthentication); + authentication = parseAADPasswordOptions(config.authentication as AzureActiveDirectoryPasswordAuthentication); + break; + case AzureActiveDirectoryAccessTokenAuthenticationType : + validateAADAccessTokenOptions(config.authentication as AzureActiveDirectoryAccessTokenAuthentication); + authentication = parseAADAccessTokenOptions(config.authentication as AzureActiveDirectoryAccessTokenAuthentication); + break; + case AzureActiveDirectoryMsiVmAuthenticationType : + validateAADMsiVmOptions(config.authentication as AzureActiveDirectoryMsiVmAuthentication); + authentication = parseAADMsiVmOptions(config.authentication as AzureActiveDirectoryMsiVmAuthentication); + break; + case AzureActiveDirectoryMsiAppServiceAuthenticationType : + validateAADMsiAppServiceOptions(config.authentication as AzureActiveDirectoryMsiAppServiceAuthentication); + authentication = parseAADMsiAppServiceOptions(config.authentication as AzureActiveDirectoryMsiAppServiceAuthentication); + break; + case AzureActiveDirectoryServicePrincipalSecretType : + validateAADServicePrincipalSecretOptions(config.authentication as AzureActiveDirectoryServicePrincipalSecret); + authentication = parseAADServicePrincipalSecretOptions(config.authentication as AzureActiveDirectoryServicePrincipalSecret); + break; + default : + validateDefaultOptions(config.authentication as DefaultAuthentication); + authentication = parseDefaultOptions(config.authentication as DefaultAuthentication); + break; } } else { - authentication = { - type: 'default', - options: { - userName: undefined, - password: undefined - } - }; + authentication = parseDefaultAnonymousOptions(); } this.config = { @@ -2517,7 +2337,7 @@ class Connection extends EventEmitter { const { authentication } = this.config; switch (authentication.type) { - case 'azure-active-directory-password': + case AzureActiveDirectoryPasswordAuthenticationType: payload.fedAuth = { type: 'ADAL', echo: this.fedAuthRequired, @@ -2525,7 +2345,7 @@ class Connection extends EventEmitter { }; break; - case 'azure-active-directory-access-token': + case AzureActiveDirectoryAccessTokenAuthenticationType: payload.fedAuth = { type: 'SECURITYTOKEN', echo: this.fedAuthRequired, @@ -2533,9 +2353,9 @@ class Connection extends EventEmitter { }; break; - case 'azure-active-directory-msi-vm': - case 'azure-active-directory-msi-app-service': - case 'azure-active-directory-service-principal-secret': + case AzureActiveDirectoryMsiVmAuthenticationType: + case AzureActiveDirectoryMsiAppServiceAuthenticationType: + case AzureActiveDirectoryServicePrincipalSecretType: payload.fedAuth = { type: 'ADAL', echo: this.fedAuthRequired, @@ -2543,7 +2363,7 @@ class Connection extends EventEmitter { }; break; - case 'ntlm': + case NtlmAuthenticationType: payload.sspi = createNTLMRequest({ domain: authentication.options.domain }); break; @@ -3270,7 +3090,7 @@ Connection.prototype.STATE = { this.sendLogin7Packet(); const { authentication } = this.config; - if (authentication.type === 'ntlm') { + if (authentication.type === NtlmAuthenticationType) { this.transitionTo(this.STATE.SENT_LOGIN7_WITH_NTLM); } else { this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN); @@ -3339,9 +3159,9 @@ Connection.prototype.STATE = { const { authentication } = this.config; - if (authentication.type === 'azure-active-directory-password' || authentication.type === 'azure-active-directory-msi-vm' || authentication.type === 'azure-active-directory-msi-app-service' || authentication.type === 'azure-active-directory-service-principal-secret') { + if (isFedAuthAuthenticationType(authentication.type)) { this.transitionTo(this.STATE.SENT_LOGIN7_WITH_FEDAUTH); - } else if (authentication.type === 'ntlm') { + } else if (authentication.type === NtlmAuthenticationType) { this.transitionTo(this.STATE.SENT_LOGIN7_WITH_NTLM); } else { this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN); @@ -3362,7 +3182,8 @@ Connection.prototype.STATE = { }, featureExtAck: function(token) { const { authentication } = this.config; - if (authentication.type === 'azure-active-directory-password' || authentication.type === 'azure-active-directory-access-token' || authentication.type === 'azure-active-directory-msi-vm' || authentication.type === 'azure-active-directory-msi-app-service' || authentication.type === 'azure-active-directory-service-principal-secret') { + + if (isAADAuthenticationType(authentication.type)) { if (token.fedAuth === undefined) { this.loginError = ConnectionError('Did not receive Active Directory authentication acknowledgement'); this.loggedIn = false; @@ -3484,7 +3305,7 @@ Connection.prototype.STATE = { const fedAuthInfoToken = this.fedAuthInfoToken; if (fedAuthInfoToken && fedAuthInfoToken.stsurl && fedAuthInfoToken.spn) { - const authentication = this.config.authentication as AzureActiveDirectoryPasswordAuthentication | AzureActiveDirectoryMsiVmAuthentication | AzureActiveDirectoryMsiAppServiceAuthentication | AzureActiveDirectoryServicePrincipalSecret; + const authentication = this.config.authentication as FedAuthAuthentication; const getToken = (callback: (error: Error | null, token?: string) => void) => { const getTokenFromCredentials = (err: Error | undefined, credentials?: UserTokenCredentials | MSIAppServiceTokenCredentials | MSIVmTokenCredentials | ApplicationTokenCredentials) => { @@ -3497,7 +3318,7 @@ Connection.prototype.STATE = { }, callback); }; - if (authentication.type === 'azure-active-directory-password') { + if (authentication.type === AzureActiveDirectoryPasswordAuthenticationType) { const credentials = new UserTokenCredentials( '7f98cb04-cd1e-40df-9140-3bf7e2cea4db', authentication.options.domain ?? 'common', @@ -3509,20 +3330,20 @@ Connection.prototype.STATE = { ); getTokenFromCredentials(undefined, credentials); - } else if (authentication.type === 'azure-active-directory-msi-vm') { + } else if (authentication.type === AzureActiveDirectoryMsiVmAuthenticationType) { loginWithVmMSI({ clientId: authentication.options.clientId, msiEndpoint: authentication.options.msiEndpoint, resource: fedAuthInfoToken.spn }, getTokenFromCredentials); - } else if (authentication.type === 'azure-active-directory-msi-app-service') { + } else if (authentication.type === AzureActiveDirectoryMsiAppServiceAuthenticationType) { loginWithAppServiceMSI({ msiEndpoint: authentication.options.msiEndpoint, msiSecret: authentication.options.msiSecret, resource: fedAuthInfoToken.spn, clientId: authentication.options.clientId }, getTokenFromCredentials); - } else if (authentication.type === 'azure-active-directory-service-principal-secret') { + } else if (authentication.type === AzureActiveDirectoryServicePrincipalSecretType) { const credentials = new ApplicationTokenCredentials( authentication.options.clientId, authentication.options.tenantId, // domain diff --git a/test/unit/authentication/authentication-types.js b/test/unit/authentication/authentication-types.js new file mode 100644 index 000000000..a0a2ad7f6 --- /dev/null +++ b/test/unit/authentication/authentication-types.js @@ -0,0 +1,123 @@ +const { isAADAuthenticationType } = require('../../../src/authentication/authentication-types'); +const { isFedAuthAuthenticationType } = require('../../../src/authentication/authentication-types'); +const { isSupportedAuthenticationType } = require('../../../src/authentication/authentication-types'); +const assert = require('chai').assert; + +describe('AuthenticationTypes', () => { + + describe('isSupportedAuthenticationType', () => { + it('should accept azure-active-directory-access-token type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('azure-active-directory-access-token'), true); + done(); + }); + + it('should accept azure-active-directory-msi-app-service type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('azure-active-directory-msi-app-service'), true); + done(); + }); + + it('should accept azure-active-directory-msi-vm type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('azure-active-directory-msi-vm'), true); + done(); + }); + + it('should accept azure-active-directory-password type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('azure-active-directory-password'), true); + done(); + }); + + it('should accept azure-active-directory-service-principal-secret type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('azure-active-directory-service-principal-secret'), true); + done(); + }); + + it('should accept default type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('default'), true); + done(); + }); + + it('should accept ntlm type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('ntlm'), true); + done(); + }); + + it('shouldn\'t accept unknown type', (done) => { + assert.strictEqual(isSupportedAuthenticationType('unknown'), false); + done(); + }); + }); + + describe('isFedAuthAuthenticationType', () => { + it('should accept azure-active-directory-msi-app-service type', (done) => { + assert.strictEqual(isFedAuthAuthenticationType('azure-active-directory-msi-app-service'), true); + done(); + }); + + it('should accept azure-active-directory-msi-vm type', (done) => { + assert.strictEqual(isFedAuthAuthenticationType('azure-active-directory-msi-vm'), true); + done(); + }); + + it('should accept azure-active-directory-password type', (done) => { + assert.strictEqual(isFedAuthAuthenticationType('azure-active-directory-password'), true); + done(); + }); + + it('should accept azure-active-directory-service-principal-secret type', (done) => { + assert.strictEqual(isFedAuthAuthenticationType('azure-active-directory-service-principal-secret'), true); + done(); + }); + + it('shouldn\'t accept azure-active-directory-access-token type', (done) => { + assert.strictEqual(isFedAuthAuthenticationType('azure-active-directory-access-token'), false); + done(); + }); + + it('shouldn\'t accept default type', (done) => { + assert.strictEqual(isFedAuthAuthenticationType('default'), false); + done(); + }); + + it('shouldn\'t accept ntlm type', (done) => { + assert.strictEqual(isFedAuthAuthenticationType('ntlm'), false); + done(); + }); + }); + + describe('isAADAuthenticationType', () => { + it('should accept azure-active-directory-msi-app-service type', (done) => { + assert.strictEqual(isAADAuthenticationType('azure-active-directory-msi-app-service'), true); + done(); + }); + + it('should accept azure-active-directory-msi-vm type', (done) => { + assert.strictEqual(isAADAuthenticationType('azure-active-directory-msi-vm'), true); + done(); + }); + + it('should accept azure-active-directory-password type', (done) => { + assert.strictEqual(isAADAuthenticationType('azure-active-directory-password'), true); + done(); + }); + + it('should accept azure-active-directory-service-principal-secret type', (done) => { + assert.strictEqual(isAADAuthenticationType('azure-active-directory-service-principal-secret'), true); + done(); + }); + + it('should accept azure-active-directory-access-token type', (done) => { + assert.strictEqual(isAADAuthenticationType('azure-active-directory-access-token'), true); + done(); + }); + + it('shouldn\'t accept default type', (done) => { + assert.strictEqual(isAADAuthenticationType('default'), false); + done(); + }); + + it('shouldn\'t accept ntlm type', (done) => { + assert.strictEqual(isAADAuthenticationType('ntlm'), false); + done(); + }); + }); +}); diff --git a/test/unit/authentication/azure-active-directory-access-token.js b/test/unit/authentication/azure-active-directory-access-token.js new file mode 100644 index 000000000..5fd6851f7 --- /dev/null +++ b/test/unit/authentication/azure-active-directory-access-token.js @@ -0,0 +1,73 @@ +const { parseAADAccessTokenOptions } = require('../../../src/authentication/azure-active-directory-access-token'); +const { validateAADAccessTokenOptions } = require('../../../src/authentication/azure-active-directory-access-token'); +const assert = require('chai').assert; + +describe('AzureActiveDirectoryAccessTokenAuthentication', () => { + let authentication; + + beforeEach(() => { + authentication = { type: 'azure-active-directory-access-token', options: {} }; + }); + + describe('validateAADAccessTokenOptions', () => { + it('should not throw an error with valid token', (done) => { + authentication.options.token = 'token'; + + assert.doesNotThrow(() => { + validateAADAccessTokenOptions(authentication); + }); + + done(); + }); + + it('should throw an error without token', (done) => { + assert.throws(() => { + validateAADAccessTokenOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string token', (done) => { + authentication.options.token = {}; + + assert.throws(() => { + validateAADAccessTokenOptions(authentication); + }); + + done(); + }); + + }); + + describe('parseAADAccessTokenOptions', () => { + + + it('should not throw an error with token', (done) => { + authentication.options.token = 'token'; + + const parseResults = parseAADAccessTokenOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.token, authentication.options.token); + + done(); + }); + + + it('should not throw an error with valid username and password and additional options property', (done) => { + authentication.options.token = 'token'; + authentication.options.additional = 'property'; + + const parseResults = parseAADAccessTokenOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.token, authentication.options.token); + assert.strictEqual(parseResults.options.additional, undefined); + + done(); + }); + + }); + +}); diff --git a/test/unit/authentication/azure-active-directory-msi-app-service.js b/test/unit/authentication/azure-active-directory-msi-app-service.js new file mode 100644 index 000000000..7f7177bf8 --- /dev/null +++ b/test/unit/authentication/azure-active-directory-msi-app-service.js @@ -0,0 +1,123 @@ +const { parseAADMsiAppServiceOptions } = require('../../../src/authentication/azure-active-directory-msi-app-service'); +const { validateAADMsiAppServiceOptions } = require('../../../src/authentication/azure-active-directory-msi-app-service'); +const assert = require('chai').assert; + +describe('AzureActiveDirectoryMsiAppServiceAuthentication', () => { + let authentication; + + beforeEach(() => { + authentication = { type: 'azure-active-directory-msi-app-service', options: {} }; + }); + + describe('validateAADMsiAppServiceOptions', () => { + + it('should not throw an error without options', (done) => { + assert.doesNotThrow(() => { + validateAADMsiAppServiceOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string clientId', (done) => { + authentication.options.clientId = {}; + + assert.throws(() => { + validateAADMsiAppServiceOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string msiEndpoint', (done) => { + authentication.options.msiEndpoint = {}; + + assert.throws(() => { + validateAADMsiAppServiceOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string msiSecret', (done) => { + authentication.options.msiSecret = {}; + + assert.throws(() => { + validateAADMsiAppServiceOptions(authentication); + }); + + done(); + }); + + + it('should not throw an error with string clientId', (done) => { + authentication.options.clientId = 'clientId'; + + assert.doesNotThrow(() => { + validateAADMsiAppServiceOptions(authentication); + }); + + done(); + }); + + it('should not throw an error with string msiEndpoint', (done) => { + authentication.options.msiEndpoint = 'msiEndpoint'; + + assert.doesNotThrow(() => { + validateAADMsiAppServiceOptions(authentication); + }); + + done(); + }); + + it('should not throw an error with string msiSecret', (done) => { + authentication.options.msiSecret = 'msiSecret'; + + assert.doesNotThrow(() => { + validateAADMsiAppServiceOptions(authentication); + }); + + done(); + }); + + + }); + + describe('parseAADMsiAppServiceOptions', () => { + + it('should not throw an error with clientId, msiEndpoint, msiSecret', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.msiEndpoint = 'msiEndpoint'; + authentication.options.msiSecret = 'msiSecret'; + + const parseResults = parseAADMsiAppServiceOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.clientId, authentication.options.clientId); + assert.strictEqual(parseResults.options.msiEndpoint, authentication.options.msiEndpoint); + assert.strictEqual(parseResults.options.msiSecret, authentication.options.msiSecret); + + done(); + }); + + + it('should not throw an error with valid username and password and additional options property', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.msiEndpoint = 'msiEndpoint'; + authentication.options.msiSecret = 'msiSecret'; + authentication.options.additional = 'additional'; + + const parseResults = parseAADMsiAppServiceOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.clientId, authentication.options.clientId); + assert.strictEqual(parseResults.options.msiEndpoint, authentication.options.msiEndpoint); + assert.strictEqual(parseResults.options.msiSecret, authentication.options.msiSecret); + assert.strictEqual(parseResults.options.additional, undefined); + + done(); + }); + + }); + +}); diff --git a/test/unit/authentication/azure-active-directory-msi-vm.js b/test/unit/authentication/azure-active-directory-msi-vm.js new file mode 100644 index 000000000..713ddf57b --- /dev/null +++ b/test/unit/authentication/azure-active-directory-msi-vm.js @@ -0,0 +1,97 @@ +const { parseAADMsiVmOptions } = require('../../../src/authentication/azure-active-directory-msi-vm'); +const { validateAADMsiVmOptions } = require('../../../src/authentication/azure-active-directory-msi-vm'); +const assert = require('chai').assert; + +describe('AzureActiveDirectoryMsiVmAuthentication', () => { + let authentication; + + beforeEach(() => { + authentication = { type: 'azure-active-directory-msi-vm', options: {} }; + }); + + describe('validateAADMsiVmOptions', () => { + + it('should not throw an error without options', (done) => { + assert.doesNotThrow(() => { + validateAADMsiVmOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string clientId', (done) => { + authentication.options.clientId = {}; + + assert.throws(() => { + validateAADMsiVmOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string msiEndpoint', (done) => { + authentication.options.msiEndpoint = {}; + + assert.throws(() => { + validateAADMsiVmOptions(authentication); + }); + + done(); + }); + + it('should not throw an error with string clientId', (done) => { + authentication.options.clientId = 'clientId'; + + assert.doesNotThrow(() => { + validateAADMsiVmOptions(authentication); + }); + + done(); + }); + + it('should not throw an error with string msiEndpoint', (done) => { + authentication.options.msiEndpoint = 'msiEndpoint'; + + assert.doesNotThrow(() => { + validateAADMsiVmOptions(authentication); + }); + + done(); + }); + + }); + + describe('parseAADMsiVmOptions', () => { + + it('should not throw an error with clientId, msiEndpoint, msiSecret', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.msiEndpoint = 'msiEndpoint'; + + const parseResults = parseAADMsiVmOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.clientId, authentication.options.clientId); + assert.strictEqual(parseResults.options.msiEndpoint, authentication.options.msiEndpoint); + + done(); + }); + + + it('should not throw an error with valid username and password and additional options property', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.msiEndpoint = 'msiEndpoint'; + authentication.options.additional = 'additional'; + + const parseResults = parseAADMsiVmOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.clientId, authentication.options.clientId); + assert.strictEqual(parseResults.options.msiEndpoint, authentication.options.msiEndpoint); + assert.strictEqual(parseResults.options.additional, undefined); + + done(); + }); + + }); + +}); diff --git a/test/unit/authentication/azure-active-directory-password.js b/test/unit/authentication/azure-active-directory-password.js new file mode 100644 index 000000000..eee1facd5 --- /dev/null +++ b/test/unit/authentication/azure-active-directory-password.js @@ -0,0 +1,136 @@ +const { validateAADPasswordOptions } = require('../../../src/authentication/azure-active-directory-password'); +const { parseAADPasswordOptions } = require('../../../src/authentication/azure-active-directory-password'); +const assert = require('chai').assert; + +describe('AzureActiveDirectoryPasswordAuthentication', () => { + let authentication; + + beforeEach(() => { + authentication = { type: 'azure-active-directory-password', options: {} }; + }); + + describe('validateAADPasswordOptions', () => { + + it('should not throw an error with valid username and password and domain', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + assert.doesNotThrow(() => { + validateAADPasswordOptions(authentication); + }); + + done(); + }); + + it('should not throw an error without password', (done) => { + authentication.options.userName = 'username'; + authentication.options.domain = 'domain.tld'; + + assert.doesNotThrow(() => { + validateAADPasswordOptions(authentication); + }); + + done(); + }); + + + it('should not throw an error without username', (done) => { + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + assert.doesNotThrow(() => { + validateAADPasswordOptions(authentication); + }); + + done(); + }); + + + it('should not throw an error without domain', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + + assert.doesNotThrow(() => { + validateAADPasswordOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string domain', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = {}; + + assert.throws(() => { + validateAADPasswordOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string username', (done) => { + authentication.options.userName = {}; + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + assert.throws(() => { + validateAADPasswordOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string password', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = {}; + authentication.options.domain = 'domain.tld'; + + assert.throws(() => { + validateAADPasswordOptions(authentication); + }); + + done(); + }); + + }); + + describe('parseAADPasswordOptions', () => { + + it('should not throw an error with valid username and password', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + const parseResults = parseAADPasswordOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.password, authentication.options.password); + assert.strictEqual(parseResults.options.userName, authentication.options.userName); + assert.strictEqual(parseResults.options.domain, authentication.options.domain); + + done(); + }); + + + it('should not throw an error with valid username and password and additional options property', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = 'domain'; + authentication.options.additional = 'property'; + + const parseResults = parseAADPasswordOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.password, authentication.options.password); + assert.strictEqual(parseResults.options.userName, authentication.options.userName); + assert.strictEqual(parseResults.options.domain, authentication.options.domain); + assert.strictEqual(parseResults.options.additional, undefined); + + done(); + }); + + }); + +}); diff --git a/test/unit/authentication/azure-active-directory-service-principal-secret.js b/test/unit/authentication/azure-active-directory-service-principal-secret.js new file mode 100644 index 000000000..2c566ab77 --- /dev/null +++ b/test/unit/authentication/azure-active-directory-service-principal-secret.js @@ -0,0 +1,137 @@ +const { validateAADServicePrincipalSecretOptions } = require('../../../src/authentication/azure-active-directory-service-principal-secret'); +const { parseAADServicePrincipalSecretOptions } = require('../../../src/authentication/azure-active-directory-service-principal-secret'); +const assert = require('chai').assert; + +describe('AzureActiveDirectoryServicePrincipalSecret', () => { + let authentication; + + beforeEach(() => { + authentication = { type: 'azure-active-directory-service-principal-secret', options: {} }; + }); + + describe('validateAADServicePrincipalSecretOptions', () => { + + it('should not throw an error with valid clientId and clientSecret and tenantId', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.clientSecret = 'clientSecret'; + authentication.options.tenantId = 'tenantId'; + + assert.doesNotThrow(() => { + validateAADServicePrincipalSecretOptions(authentication); + }); + + done(); + }); + + it('should throw an error without clientId', (done) => { + authentication.options.clientSecret = 'clientSecret'; + authentication.options.tenantId = 'tenantId'; + + assert.throws(() => { + validateAADServicePrincipalSecretOptions(authentication); + }); + + done(); + }); + + + it('should throw an error without clientSecret', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.tenantId = 'tenantId'; + + assert.throws(() => { + validateAADServicePrincipalSecretOptions(authentication); + }); + + done(); + }); + + + it('should throw an error without tenantId', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.clientSecret = 'clientSecret'; + + assert.throws(() => { + validateAADServicePrincipalSecretOptions(authentication); + }); + + done(); + }); + + it('should throw an error without non-string clientId', (done) => { + authentication.options.clientId = {}; + authentication.options.clientSecret = 'clientSecret'; + authentication.options.tenantId = 'tenantId'; + + assert.throws(() => { + validateAADServicePrincipalSecretOptions(authentication); + }); + + done(); + }); + + it('should throw an error without non-string clientSecret', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.clientSecret = {}; + authentication.options.tenantId = 'tenantId'; + + + assert.throws(() => { + validateAADServicePrincipalSecretOptions(authentication); + }); + + done(); + }); + + it('should throw an error without non-string tenantId', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.clientSecret = 'clientSecret'; + authentication.options.tenantId = {}; + + assert.throws(() => { + validateAADServicePrincipalSecretOptions(authentication); + }); + + done(); + }); + + }); + + describe('parseAADServicePrincipalSecretOptions', () => { + + it('should not throw an error with valid username and password', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.clientSecret = 'clientSecret'; + authentication.options.tenantId = 'tenantId'; + + const parseResults = parseAADServicePrincipalSecretOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.clientId, authentication.options.clientId); + assert.strictEqual(parseResults.options.clientSecret, authentication.options.clientSecret); + assert.strictEqual(parseResults.options.tenantId, authentication.options.tenantId); + + done(); + }); + + + it('should not throw an error with valid username and password and additional options property', (done) => { + authentication.options.clientId = 'clientId'; + authentication.options.clientSecret = 'clientSecret'; + authentication.options.tenantId = 'tenantId'; + authentication.options.additional = 'property'; + + const parseResults = parseAADServicePrincipalSecretOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.clientId, authentication.options.clientId); + assert.strictEqual(parseResults.options.clientSecret, authentication.options.clientSecret); + assert.strictEqual(parseResults.options.tenantId, authentication.options.tenantId); + assert.strictEqual(parseResults.options.additional, undefined); + + done(); + }); + + }); + +}); diff --git a/test/unit/authentication/default.js b/test/unit/authentication/default.js new file mode 100644 index 000000000..38e433b38 --- /dev/null +++ b/test/unit/authentication/default.js @@ -0,0 +1,100 @@ +const { validateDefaultOptions, parseDefaultOptions } = require('../../../src/authentication/default'); +const assert = require('chai').assert; + +describe('DefaultAuthentication', () => { + let authentication; + + beforeEach(() => { + authentication = { type: 'default', options: {} }; + }); + + describe('validateDefaultOptions', () => { + it('should not throw an error with valid username and password', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + + assert.doesNotThrow(() => { + validateDefaultOptions(authentication); + }); + + done(); + }); + + it('should not throw an error with undefined username and password', (done) => { + assert.doesNotThrow(() => { + validateDefaultOptions(authentication); + }); + + done(); + }); + + + it('should throw an error with null username and password', (done) => { + authentication.options.userName = null; + authentication.options.password = null; + + assert.throws(() => { + validateDefaultOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string userName', (done) => { + authentication.options.userName = {}; + authentication.options.password = 'password'; + + assert.throws(() => { + validateDefaultOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string password', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = {}; + + assert.throws(() => { + validateDefaultOptions(authentication); + }); + + done(); + }); + + }); + + describe('parseDefaultOptions', () => { + + it('should not throw an error with valid username and password', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + + const parseResults = parseDefaultOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.password, authentication.options.password); + assert.strictEqual(parseResults.options.userName, authentication.options.userName); + + done(); + }); + + + it('should not throw an error with valid username and password and additional options property', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.additional = 'property'; + + const parseResults = parseDefaultOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.password, authentication.options.password); + assert.strictEqual(parseResults.options.userName, authentication.options.userName); + assert.strictEqual(parseResults.options.additional, undefined); + + done(); + }); + + }); + +}); diff --git a/test/unit/authentication/ntlm.js b/test/unit/authentication/ntlm.js new file mode 100644 index 000000000..2e3ed49b7 --- /dev/null +++ b/test/unit/authentication/ntlm.js @@ -0,0 +1,134 @@ +const { validateNtlmOptions, parseNtlmOptions } = require('../../../src/authentication/ntlm'); +const assert = require('chai').assert; + +describe('NtlmAuthentication', () => { + let authentication; + + beforeEach(() => { + authentication = { type: 'ntlm', options: {} }; + }); + + describe('validateNtlmOptions', () => { + it('should not throw an error with valid username and password and domain', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + assert.doesNotThrow(() => { + validateNtlmOptions(authentication); + }); + + done(); + }); + + it('should not throw an error without password', (done) => { + authentication.options.userName = 'username'; + authentication.options.domain = 'domain.tld'; + + assert.doesNotThrow(() => { + validateNtlmOptions(authentication); + }); + + done(); + }); + + + it('should not throw an error without username', (done) => { + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + assert.doesNotThrow(() => { + validateNtlmOptions(authentication); + }); + + done(); + }); + + + it('should throw an error without domain', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + + assert.throws(() => { + validateNtlmOptions(authentication); + }); + + done(); + }); + + it('should throw an error without non-string domain', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = {}; + + assert.throws(() => { + validateNtlmOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string username', (done) => { + authentication.options.userName = {}; + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + assert.throws(() => { + validateNtlmOptions(authentication); + }); + + done(); + }); + + it('should throw an error with non-string password', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = {}; + authentication.options.domain = 'domain.tld'; + + assert.throws(() => { + validateNtlmOptions(authentication); + }); + + done(); + }); + + }); + + describe('parseDefaultOptions', () => { + + it('should not throw an error with valid username and password', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = 'domain.tld'; + + const parseResults = parseNtlmOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.password, authentication.options.password); + assert.strictEqual(parseResults.options.userName, authentication.options.userName); + assert.strictEqual(parseResults.options.domain, authentication.options.domain.toUpperCase()); + + done(); + }); + + + it('should not throw an error with valid username and password and additional options property', (done) => { + authentication.options.userName = 'username'; + authentication.options.password = 'password'; + authentication.options.domain = 'domain'; + authentication.options.additional = 'property'; + + const parseResults = parseNtlmOptions(authentication); + + assert.strictEqual(parseResults.type, authentication.type); + assert.strictEqual(parseResults.options.password, authentication.options.password); + assert.strictEqual(parseResults.options.userName, authentication.options.userName); + assert.strictEqual(parseResults.options.domain, authentication.options.domain.toUpperCase()); + assert.strictEqual(parseResults.options.additional, undefined); + + done(); + }); + + }); + +});