From 54247fdd1b41fc52431a697c520474a149ebd8e5 Mon Sep 17 00:00:00 2001 From: Ramya Rao Date: Mon, 2 Nov 2020 11:48:34 -0800 Subject: [PATCH] Core AMQP cleanup for v2 (#12210) --- sdk/core/core-amqp/CHANGELOG.md | 35 ++- sdk/core/core-amqp/package.json | 1 - sdk/core/core-amqp/review/core-amqp.api.md | 164 +---------- .../core-amqp/src/ConnectionContextBase.ts | 18 -- sdk/core/core-amqp/src/auth/iotSas.ts | 27 -- sdk/core/core-amqp/src/cbs.ts | 7 +- .../src/connectionConfig/connectionConfig.ts | 9 +- .../iothubConnectionConfig.ts | 127 --------- sdk/core/core-amqp/src/index.ts | 18 -- sdk/core/core-amqp/src/util/utils.ts | 43 --- sdk/core/core-amqp/test/config.spec.ts | 261 +----------------- sdk/core/core-amqp/test/context.spec.ts | 11 +- sdk/eventhub/event-hubs/package.json | 3 + .../event-hubs/review/event-hubs.api.md | 2 +- .../event-hubs/src/connectionContext.ts | 17 +- .../event-hubs/src/diagnostics/messageSpan.ts | 2 +- .../event-hubs/src/eventHubConsumerClient.ts | 4 +- .../event-hubs/src/eventHubProducerClient.ts | 2 +- .../src}/eventhubConnectionConfig.ts | 2 +- .../src/eventhubSharedKeyCredential.ts} | 10 +- sdk/eventhub/event-hubs/src/index.ts | 3 +- sdk/eventhub/event-hubs/src/linkEntity.ts | 12 +- .../event-hubs/src/managementClient.ts | 3 +- sdk/eventhub/event-hubs/test/auth.spec.ts | 9 +- sdk/eventhub/event-hubs/test/config.spec.ts | 79 ++++++ .../test/diagnostics/messageSpan.spec.ts | 2 +- .../test/sharedKeyCredential.spec.ts} | 47 ++-- sdk/servicebus/service-bus/package.json | 2 + .../service-bus/review/service-bus.api.md | 2 +- .../service-bus/src/connectionContext.ts | 11 +- .../service-bus/src/constructorHelpers.ts | 10 +- .../service-bus/src/core/linkEntity.ts | 6 +- sdk/servicebus/service-bus/src/index.ts | 2 +- .../src/serviceBusAtomManagementClient.ts | 8 +- .../service-bus/src/serviceBusClient.ts | 3 +- .../src/servicebusSharedKeyCredential.ts | 126 +++++++++ .../src/util/sasServiceClientCredentials.ts | 3 +- .../service-bus/test/atomManagement.spec.ts | 3 +- sdk/servicebus/service-bus/test/auth.spec.ts | 2 +- .../test/internal/sharedKeyCredential.spec.ts | 58 ++++ .../test/internal/unittestUtils.ts | 3 +- .../test/streamingReceiver.spec.ts | 2 +- 42 files changed, 400 insertions(+), 759 deletions(-) delete mode 100644 sdk/core/core-amqp/src/auth/iotSas.ts delete mode 100644 sdk/core/core-amqp/src/connectionConfig/iothubConnectionConfig.ts rename sdk/{core/core-amqp/src/connectionConfig => eventhub/event-hubs/src}/eventhubConnectionConfig.ts (99%) rename sdk/{core/core-amqp/src/auth/sas.ts => eventhub/event-hubs/src/eventhubSharedKeyCredential.ts} (94%) create mode 100644 sdk/eventhub/event-hubs/test/config.spec.ts rename sdk/{core/core-amqp/test/tokenProvider.spec.ts => eventhub/event-hubs/test/sharedKeyCredential.spec.ts} (56%) create mode 100644 sdk/servicebus/service-bus/src/servicebusSharedKeyCredential.ts create mode 100644 sdk/servicebus/service-bus/test/internal/sharedKeyCredential.spec.ts diff --git a/sdk/core/core-amqp/CHANGELOG.md b/sdk/core/core-amqp/CHANGELOG.md index f86071f1eea..e4bfc8cc3cb 100644 --- a/sdk/core/core-amqp/CHANGELOG.md +++ b/sdk/core/core-amqp/CHANGELOG.md @@ -4,8 +4,39 @@ - `AmqpAnnotatedMessage` interface that closely represents the AMQP annotated message from the [AMQP spec](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format) has been added. New `AmqpMessageHeaders` and `AmqpMessageProperties` interfaces(properties with camelCasing) have been added in the place of re-exports from "rhea" library(properties with snake_casing). [PR 12091](https://github.com/Azure/azure-sdk-for-js/pull/12091) -- `Message` from "rhea" library which was being exported as `AmqpMessage` is removed. - [PR 12169](https://github.com/Azure/azure-sdk-for-js/pull/12169) + +### Breaking changes + +We are cleaning the public API surface by + +- removing exports that are either not used by either `@azure/event-hubs` and `@azure/service-bus` packages (which are the two main consumers of this package) + - AsyncLockOptions + - executePromisesSequentially + - Func + - getNewAsyncLock + - isNode + - randomNumberFromInterval + - Timeout +- moving the clases/methods/interfaces that are very specific to Event Hubs/Service Bus to their corresponding packages. + - SharedKeyCredential + - As part of this move the `negotiateClaim()` method now takes the token string directly instead of the `AccessToken` object. + - EventHubConnectionConfig +- avoid re-exporting things from `rhea-promise` and `@azure/core-auth` + - Dictionary + - isAmqpError + - Message + - TokenCredential + - isTokenCredential + - AccessToken +- removing all IotHub related artifacts. These existed to support the IotHub support we had in Event Hubs v2 which has since been removed in Event Hubs v5 for a better separation of concerns + - IotHubConnectionConfig + - IotHubConnectionStringModel + - IotSharedKeyCredential + - isIotHubConnectionString +- removing all Event Hubs, Storage and Service Bus interfaces meant to be used with the `parseConnectionString()` method + - ServiceBusConnectionStringModel + - StorageConnectionStringModel + - EventHubsConnectionStringModel ## 1.1.7 (2020-10-28) diff --git a/sdk/core/core-amqp/package.json b/sdk/core/core-amqp/package.json index a05a07157b3..941e1c9aff9 100644 --- a/sdk/core/core-amqp/package.json +++ b/sdk/core/core-amqp/package.json @@ -69,7 +69,6 @@ }, "dependencies": { "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.1.3", "@azure/logger": "^1.0.0", "@types/async-lock": "^1.1.0", "@types/is-buffer": "^2.0.0", diff --git a/sdk/core/core-amqp/review/core-amqp.api.md b/sdk/core/core-amqp/review/core-amqp.api.md index fbac4d42015..943a2f4ccb4 100644 --- a/sdk/core/core-amqp/review/core-amqp.api.md +++ b/sdk/core/core-amqp/review/core-amqp.api.md @@ -5,13 +5,9 @@ ```ts import { AbortSignalLike } from '@azure/abort-controller'; -import { AccessToken } from '@azure/core-auth'; import { AmqpError } from 'rhea-promise'; import AsyncLock from 'async-lock'; import { Connection } from 'rhea-promise'; -import { Dictionary } from 'rhea-promise'; -import { isAmqpError } from 'rhea-promise'; -import { isTokenCredential } from '@azure/core-auth'; import { Message } from 'rhea-promise'; import { MessageHeader } from 'rhea-promise'; import { MessageProperties } from 'rhea-promise'; @@ -21,11 +17,8 @@ import { ReqResLink } from 'rhea-promise'; import { Sender } from 'rhea-promise'; import { SenderOptions } from 'rhea-promise'; import { Session } from 'rhea-promise'; -import { TokenCredential } from '@azure/core-auth'; import { WebSocketImpl } from 'rhea-promise'; -export { AccessToken } - // @public export interface AmqpAnnotatedMessage { applicationProperties?: { @@ -89,14 +82,6 @@ export const AmqpMessageProperties: { export { AsyncLock } -// @public -export interface AsyncLockOptions { - domainReentrant?: boolean; - maxPending?: number; - Promise?: any; - timeout?: number; -} - // @public export class CbsClient { constructor(connection: Connection, connectionLock: string); @@ -106,7 +91,7 @@ export class CbsClient { readonly connectionLock: string; readonly endpoint: string; init(): Promise; - negotiateClaim(audience: string, tokenObject: AccessToken, tokenType: TokenType): Promise; + negotiateClaim(audience: string, token: string, tokenType: TokenType): Promise; remove(): void; readonly replyTo: string; } @@ -250,7 +235,6 @@ export interface ConnectionContextBase { dataTransformer: DataTransformer; negotiateClaimLock: string; refreshConnection: () => void; - readonly tokenCredential: SharedKeyCredential | TokenCredential; wasConnectionCloseCalled: boolean; } @@ -392,7 +376,6 @@ export interface CreateConnectionContextBaseParameters { dataTransformer?: DataTransformer; isEntityPathRequired?: boolean; operationTimeoutInMs?: number; - tokenCredential?: SharedKeyCredential | TokenCredential; } // @public @@ -413,8 +396,6 @@ export const defaultLock: AsyncLock; // @public export function delay(delayInMs: number, abortSignal?: AbortSignalLike, abortErrorMsg?: string, value?: T): Promise; -export { Dictionary } - // @public export enum ErrorNameConditionMapper { AddressAlreadyInUseError = "com.microsoft:address-already-in-use", @@ -464,98 +445,12 @@ export enum ErrorNameConditionMapper { UnauthorizedError = "amqp:unauthorized-access" } -// @public -export interface EventHubConnectionConfig extends ConnectionConfig { - entityPath: string; - getManagementAddress(): string; - getManagementAudience(): string; - getReceiverAddress(partitionId: string | number, consumergroup?: string): string; - getReceiverAudience(partitionId: string | number, consumergroup?: string): string; - getSenderAddress(partitionId?: string | number): string; - getSenderAudience(partitionId?: string | number): string; -} - -// @public -export const EventHubConnectionConfig: { - create(connectionString: string, path?: string | undefined): EventHubConnectionConfig; - createFromConnectionConfig(config: ConnectionConfig): EventHubConnectionConfig; - validate(config: EventHubConnectionConfig): void; -}; - -// @public -export interface EventHubConnectionStringModel { - // (undocumented) - [x: string]: any; - // (undocumented) - Endpoint: string; - // (undocumented) - EntityPath?: string; - // (undocumented) - SharedAccessKey: string; - // (undocumented) - SharedAccessKeyName: string; -} - -// @public (undocumented) -export function executePromisesSequentially(promiseFactories: Array, kickstart?: any): Promise; - -// @public -export type Func = (a: T) => V; - -// @public -export function getNewAsyncLock(options?: AsyncLockOptions): AsyncLock; - -// @public (undocumented) -export interface IotHubConnectionConfig { - connectionString: string; - deviceId?: string; - entityPath: string; - host: string; - hostName: string; - sharedAccessKey: string; - sharedAccessKeyName: string; -} - -// @public -export const IotHubConnectionConfig: { - create(connectionString: string, path?: string | undefined): IotHubConnectionConfig; - validate(config: IotHubConnectionConfig): void; - convertToEventHubConnectionConfig(iotHubConfig: IotHubConnectionConfig): EventHubConnectionConfig; -}; - -// @public -export interface IotHubConnectionStringModel { - // (undocumented) - DeviceId?: string; - // (undocumented) - HostName: string; - // (undocumented) - SharedAccessKey: string; - // (undocumented) - SharedAccessKeyName: string; -} - -// @public -export class IotSharedKeyCredential extends SharedKeyCredential { - getToken(audience: string): AccessToken; -} - -export { isAmqpError } - -// @public -export function isIotHubConnectionString(connectionString: string): boolean; - // @public export function isMessagingError(error: Error | MessagingError): error is MessagingError; -// @public -export const isNode: boolean; - // @public export function isSystemError(err: any): err is NetworkSystemError; -export { isTokenCredential } - // @public export const logger: import("@azure/logger").AzureLogger; @@ -605,9 +500,6 @@ export type ParsedOutput = { [P in keyof T]: T[P]; }; -// @public -export function randomNumberFromInterval(min: number, max: number): number; - // @public export class RequestResponseLink implements ReqResLink { constructor(session: Session, sender: Sender, receiver: Receiver); @@ -685,44 +577,6 @@ export interface SendRequestOptions { timeoutInMs?: number; } -// @public -export interface ServiceBusConnectionStringModel { - // (undocumented) - [x: string]: any; - // (undocumented) - Endpoint: string; - // (undocumented) - EntityPath?: string; - // (undocumented) - SharedAccessKey: string; - // (undocumented) - SharedAccessKeyName: string; -} - -// @public -export class SharedKeyCredential { - constructor(keyName: string, key: string); - protected _createToken(expiry: number, audience: string, hashInput?: string | Buffer): AccessToken; - static fromConnectionString(connectionString: string): SharedKeyCredential; - getToken(audience: string): AccessToken; - key: string; - keyName: string; -} - -// @public -export interface StorageConnectionStringModel { - // (undocumented) - [x: string]: any; - // (undocumented) - AccountKey: string; - // (undocumented) - AccountName: string; - // (undocumented) - DefaultEndpointsProtocol: string; - // (undocumented) - EndpointSuffix: string; -} - // @public export enum SystemErrorConditionMapper { // (undocumented) @@ -747,22 +601,6 @@ export enum SystemErrorConditionMapper { ETIMEDOUT = "com.microsoft:timeout" } -// @public -export class Timeout { - // (undocumented) - clear(): void; - // (undocumented) - set(t: number, value?: T): Promise; - // (undocumented) - static set(t: number, value?: T): Promise; - // (undocumented) - wrap(promise: Promise, t: number, value?: T): Promise; - // (undocumented) - static wrap(promise: Promise, t: number, value?: T): Promise; -} - -export { TokenCredential } - // @public export enum TokenType { CbsTokenTypeJwt = "jwt", diff --git a/sdk/core/core-amqp/src/ConnectionContextBase.ts b/sdk/core/core-amqp/src/ConnectionContextBase.ts index 454cae3d93a..f77a3a25896 100644 --- a/sdk/core/core-amqp/src/ConnectionContextBase.ts +++ b/sdk/core/core-amqp/src/ConnectionContextBase.ts @@ -4,9 +4,7 @@ import { Connection, ConnectionOptions, generate_uuid } from "rhea-promise"; import { CbsClient } from "./cbs"; import { DataTransformer, DefaultDataTransformer } from "./dataTransformer"; -import { TokenCredential } from "@azure/core-auth"; import { ConnectionConfig } from "./connectionConfig/connectionConfig"; -import { SharedKeyCredential } from "./auth/sas"; import { Constants } from "./util/constants"; import { getFrameworkInfo, getPlatformInfo } from "./util/runtimeInfo"; @@ -32,11 +30,6 @@ export interface ConnectionContextBase { * acquire the lock for negotiating cbs claim by an entity on that connection. */ negotiateClaimLock: string; - /** - * @property {SharedKeyCredential | TokenCredential} tokenCredential The credential to be used for getting tokens - * for authentication for the EventHub client. - */ - readonly tokenCredential: SharedKeyCredential | TokenCredential; /** * @property {Connection} connection The underlying AMQP connection. */ @@ -102,11 +95,6 @@ export interface CreateConnectionContextBaseParameters { * the AMQP connection. */ connectionProperties: ConnectionProperties; - /** - * @property {SharedKeyCredential | TokenCredential} [tokenCredential] The credential to be used for Authentication. - * Default value: SharedKeyCredentials. - */ - tokenCredential?: SharedKeyCredential | TokenCredential; /** * @property {DataTransformer} [dataTransformer] The datatransformer to be used for encoding and * decoding messages. Default value: DefaultDataTransformer @@ -190,12 +178,6 @@ export const ConnectionContextBase = { connectionId: connection.id, cbsSession: new CbsClient(connection, connectionLock), config: parameters.config, - tokenCredential: - parameters.tokenCredential || - new SharedKeyCredential( - parameters.config.sharedAccessKeyName, - parameters.config.sharedAccessKey - ), dataTransformer: parameters.dataTransformer || new DefaultDataTransformer(), refreshConnection() { const connection = new Connection(connectionOptions); diff --git a/sdk/core/core-amqp/src/auth/iotSas.ts b/sdk/core/core-amqp/src/auth/iotSas.ts deleted file mode 100644 index e7fbab8e929..00000000000 --- a/sdk/core/core-amqp/src/auth/iotSas.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { SharedKeyCredential } from "./sas"; -import { AccessToken } from "@azure/core-auth"; -import { Buffer } from "buffer"; - -/** - * @class IotSharedKeyCredential - * @ignore - * Defines the IotSharedKeyCredential for IotHub. - */ -export class IotSharedKeyCredential extends SharedKeyCredential { - /** - * Gets the sas token for the specified audience for IotHub. - * @ignore - * @param {string} [audience] - The audience for which the token is desired. If not - * provided then the Endpoint from the connection string will be applied. - */ - getToken(audience: string): AccessToken { - return this._createToken( - Math.floor(Date.now() / 1000) + 3600, - audience, - Buffer.from(this.key, "base64") - ); - } -} diff --git a/sdk/core/core-amqp/src/cbs.ts b/sdk/core/core-amqp/src/cbs.ts index a7bee1ec2f6..14efe456041 100644 --- a/sdk/core/core-amqp/src/cbs.ts +++ b/sdk/core/core-amqp/src/cbs.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import { TokenType } from "./auth/token"; -import { AccessToken } from "@azure/core-auth"; import { Message as RheaMessage, Connection, @@ -181,18 +180,18 @@ export class CbsClient { * * - **ManagementClient** * - `"sb://.servicebus.windows.net//$management"`. - * @param {TokenInfo} tokenObject The token object that needs to be sent in the put-token request. + * @param {string} token The token that needs to be sent in the put-token request. * @return {Promise} Returns a Promise that resolves when $cbs authentication is successful * and rejects when an error occurs during $cbs authentication. */ async negotiateClaim( audience: string, - tokenObject: AccessToken, + token: string, tokenType: TokenType ): Promise { try { const request: RheaMessage = { - body: tokenObject.token, + body: token, message_id: generate_uuid(), reply_to: this.replyTo, to: this.endpoint, diff --git a/sdk/core/core-amqp/src/connectionConfig/connectionConfig.ts b/sdk/core/core-amqp/src/connectionConfig/connectionConfig.ts index 011b46c15b4..057fca85a08 100644 --- a/sdk/core/core-amqp/src/connectionConfig/connectionConfig.ts +++ b/sdk/core/core-amqp/src/connectionConfig/connectionConfig.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { ServiceBusConnectionStringModel, parseConnectionString } from "../util/utils"; +import { parseConnectionString } from "../util/utils"; import { WebSocketImpl } from "rhea-promise"; /** @@ -83,7 +83,12 @@ export const ConnectionConfig = { create(connectionString: string, path?: string): ConnectionConfig { connectionString = String(connectionString); - const parsedCS = parseConnectionString(connectionString); + const parsedCS = parseConnectionString<{ + Endpoint: string; + SharedAccessKeyName: string; + SharedAccessKey: string; + EntityPath?: string; + }>(connectionString); if (!parsedCS.Endpoint) { throw new TypeError("Missing Endpoint in Connection String."); } diff --git a/sdk/core/core-amqp/src/connectionConfig/iothubConnectionConfig.ts b/sdk/core/core-amqp/src/connectionConfig/iothubConnectionConfig.ts deleted file mode 100644 index 1462ed66a5f..00000000000 --- a/sdk/core/core-amqp/src/connectionConfig/iothubConnectionConfig.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { ConnectionConfig } from "./connectionConfig"; -import { EventHubConnectionConfig } from "./eventhubConnectionConfig"; -import { IotHubConnectionStringModel, parseConnectionString } from "../util/utils"; - -/** - * @ignore - */ -export interface IotHubConnectionConfig { - /** - * @property {string} endpoint - The iothub endpoint `".azure-devices.net"`. - */ - hostName: string; - /** - * @property {string} host - The host `""`. - */ - host: string; - /** - * @property {string} connectionString - The IotHub connection string. - */ - connectionString: string; - /** - * @property {string} entityPath - The name/path of the entity to which the connection needs to happen. - */ - entityPath: string; - /** - * @property {string} sharedAccessKeyName - The name of the access key. - */ - sharedAccessKeyName: string; - /** - * @property {string} sharedAccessKey - The secret value of the access key. - */ - sharedAccessKey: string; - /** - * @property {string} [deviceId] - The unique device identifier. - */ - deviceId?: string; -} - -/** - * @module IotHubConnectionConfig - * @ignore - */ -export const IotHubConnectionConfig = { - /** - * Creates the connection config. - * @ignore - * @param {string} connectionString - The event hub connection string - * @param {string} [path] - The name/path of the entity (hub name) to which the connection needs to happen - */ - create(connectionString: string, path?: string): IotHubConnectionConfig { - connectionString = String(connectionString); - - const parsedCS = parseConnectionString(connectionString); - if (!path) { - path = "messages/events"; - } - const result: IotHubConnectionConfig = { - connectionString: connectionString, - hostName: parsedCS.HostName, - host: parsedCS && parsedCS.HostName ? parsedCS.HostName.split(".")[0] : "", - entityPath: path, - sharedAccessKeyName: parsedCS.SharedAccessKeyName, - sharedAccessKey: parsedCS.SharedAccessKey, - deviceId: parsedCS.DeviceId - }; - return result; - }, - - /** - * Validates the properties of connection config. - * @ignore - * @param {ConnectionConfig} config The connection config to be validated. - */ - validate(config: IotHubConnectionConfig): void { - if (!config) { - throw new TypeError("Missing configuration"); - } - - if (!config.hostName) { - throw new TypeError("Missing 'hostName' in configuration"); - } - config.hostName = String(config.hostName); - - if (!config.entityPath) { - throw new TypeError("Missing 'entityPath' in configuration"); - } - config.entityPath = String(config.entityPath); - - if (!config.sharedAccessKeyName) { - throw new TypeError("Missing 'sharedAccessKeyName' in configuration"); - } - config.sharedAccessKeyName = String(config.sharedAccessKeyName); - - if (!config.sharedAccessKey) { - throw new TypeError("Missing 'sharedAccessKey' in configuration"); - } - config.sharedAccessKey = String(config.sharedAccessKey); - - if (config.deviceId) { - config.deviceId = String(config.deviceId); - } - }, - - /** - * Convert iothub connection config to eventhub connection config. - * @ignore - * @param {IotHubConnectionConfig} iotHubConfig - */ - convertToEventHubConnectionConfig( - iotHubConfig: IotHubConnectionConfig - ): EventHubConnectionConfig { - IotHubConnectionConfig.validate(iotHubConfig); - const config: ConnectionConfig = { - sharedAccessKey: iotHubConfig.sharedAccessKey, - sharedAccessKeyName: iotHubConfig.sharedAccessKeyName, - entityPath: iotHubConfig.entityPath, - host: iotHubConfig.hostName, - // `sb://` prefix to match with the endpoint in the connection string from the portal - endpoint: `sb://${iotHubConfig.hostName}/`, - connectionString: iotHubConfig.connectionString - }; - return EventHubConnectionConfig.createFromConnectionConfig(config); - } -}; diff --git a/sdk/core/core-amqp/src/index.ts b/sdk/core/core-amqp/src/index.ts index d21841677a9..f5663ad8ca5 100644 --- a/sdk/core/core-amqp/src/index.ts +++ b/sdk/core/core-amqp/src/index.ts @@ -7,13 +7,8 @@ export { RequestResponseLink, SendRequestOptions } from "./requestResponseLink"; export { retry, RetryOptions, RetryConfig, RetryOperationType, RetryMode } from "./retry"; export { DataTransformer, DefaultDataTransformer } from "./dataTransformer"; export { TokenType } from "./auth/token"; -export { AccessToken, TokenCredential, isTokenCredential } from "@azure/core-auth"; -export { SharedKeyCredential } from "./auth/sas"; -export { IotSharedKeyCredential } from "./auth/iotSas"; export { ConnectionConfig, ConnectionConfigOptions } from "./connectionConfig/connectionConfig"; -export { EventHubConnectionConfig } from "./connectionConfig/eventhubConnectionConfig"; -export { IotHubConnectionConfig } from "./connectionConfig/iothubConnectionConfig"; export { CbsClient, CbsResponse } from "./cbs"; export { Constants } from "./util/constants"; @@ -24,7 +19,6 @@ export { ConnectionProperties, CreateConnectionContextBaseParameters } from "./ConnectionContextBase"; -export { Dictionary, isAmqpError } from "rhea-promise"; export { MessagingError, MessageErrorCodes, @@ -40,22 +34,10 @@ export { } from "./errors"; export { delay, - Timeout, - EventHubConnectionStringModel, - executePromisesSequentially, parseConnectionString, - IotHubConnectionStringModel, - StorageConnectionStringModel, defaultLock, - Func, ParsedOutput, - getNewAsyncLock, - AsyncLockOptions, - ServiceBusConnectionStringModel, - isIotHubConnectionString, - randomNumberFromInterval, AsyncLock, - isNode, WebSocketOptions } from "./util/utils"; export { AmqpAnnotatedMessage } from "./amqpAnnotatedMessage"; diff --git a/sdk/core/core-amqp/src/util/utils.ts b/sdk/core/core-amqp/src/util/utils.ts index 526b5e869f1..cb4fc2657c0 100644 --- a/sdk/core/core-amqp/src/util/utils.ts +++ b/sdk/core/core-amqp/src/util/utils.ts @@ -57,49 +57,6 @@ export interface WebSocketOptions { export const isNode = !!process && !!process.version && !!process.versions && !!process.versions.node; -/** - * Describes the servicebus connection string model. - */ -export interface ServiceBusConnectionStringModel { - Endpoint: string; - SharedAccessKeyName: string; - SharedAccessKey: string; - EntityPath?: string; - [x: string]: any; -} - -/** - * Describes the eventhub connection string model. - */ -export interface EventHubConnectionStringModel { - Endpoint: string; - SharedAccessKeyName: string; - SharedAccessKey: string; - EntityPath?: string; - [x: string]: any; -} - -/** - * Describes the storage connection string model. - */ -export interface StorageConnectionStringModel { - DefaultEndpointsProtocol: string; - AccountName: string; - AccountKey: string; - EndpointSuffix: string; - [x: string]: any; -} - -/** - * Describes the iothub connection string model. - */ -export interface IotHubConnectionStringModel { - HostName: string; - SharedAccessKeyName: string; - SharedAccessKey: string; - DeviceId?: string; -} - /** * Defines an object with possible properties defined in T. * @type ParsedOutput diff --git a/sdk/core/core-amqp/test/config.spec.ts b/sdk/core/core-amqp/test/config.spec.ts index ff22ed1981d..a87030602e3 100644 --- a/sdk/core/core-amqp/test/config.spec.ts +++ b/sdk/core/core-amqp/test/config.spec.ts @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { ConnectionConfig, EventHubConnectionConfig, IotHubConnectionConfig } from "../src"; +import { ConnectionConfig } from "../src"; import * as chai from "chai"; import { isSharedAccessSignature } from "../src/connectionConfig/connectionConfig"; -import { SharedAccessSignatureCredential } from "../src/auth/sas"; const should = chai.should(); describe("ConnectionConfig", function() { @@ -197,241 +196,6 @@ describe("ConnectionConfig", function() { }); }); - describe("EventHub", function() { - it("should fail if connection config does not contain path and the connectionstring also does not contain EntityPath", function(done) { - const connectionString = - "Endpoint=sb://hostname.servicebus.windows.net/;SharedAccessKeyName=sakName;SharedAccessKey=sak"; - try { - EventHubConnectionConfig.create(connectionString); - done(new Error("Should not have reached here.")); - } catch (err) { - err.message.should.match(/Either provide "path" or the "connectionString".*/gi); - } - done(); - }); - - it("should correctly populate config properties from an EventHubs connection string and the helper methods should work as expected", function(done) { - const config = EventHubConnectionConfig.create( - "Endpoint=sb://hostname.servicebus.windows.net/;SharedAccessKeyName=sakName;SharedAccessKey=sak;EntityPath=ep" - ); - config.should.have.property("host").that.equals("hostname.servicebus.windows.net"); - config.should.have.property("sharedAccessKeyName").that.equals("sakName"); - config.should.have.property("sharedAccessKey").that.equals("sak"); - config.should.have.property("entityPath").that.equals("ep"); - - config.getManagementAddress().should.equal("ep/$management"); - config.getSenderAddress().should.equal("ep"); - config.getSenderAddress("0").should.equal("ep/Partitions/0"); - config.getSenderAddress(0).should.equal("ep/Partitions/0"); - config.getReceiverAddress("0").should.equal("ep/ConsumerGroups/$default/Partitions/0"); - config.getReceiverAddress(0).should.equal("ep/ConsumerGroups/$default/Partitions/0"); - config.getReceiverAddress("0", "cg").should.equal("ep/ConsumerGroups/cg/Partitions/0"); - config.getReceiverAddress(0, "cg").should.equal("ep/ConsumerGroups/cg/Partitions/0"); - - config - .getManagementAudience() - .should.equal("sb://hostname.servicebus.windows.net/ep/$management"); - config.getSenderAudience().should.equal("sb://hostname.servicebus.windows.net/ep"); - config - .getSenderAudience("0") - .should.equal("sb://hostname.servicebus.windows.net/ep/Partitions/0"); - config - .getSenderAudience(0) - .should.equal("sb://hostname.servicebus.windows.net/ep/Partitions/0"); - config - .getReceiverAudience("0") - .should.equal( - "sb://hostname.servicebus.windows.net/ep/ConsumerGroups/$default/Partitions/0" - ); - config - .getReceiverAudience(0) - .should.equal( - "sb://hostname.servicebus.windows.net/ep/ConsumerGroups/$default/Partitions/0" - ); - config - .getReceiverAudience("0", "cg") - .should.equal("sb://hostname.servicebus.windows.net/ep/ConsumerGroups/cg/Partitions/0"); - config - .getReceiverAudience(0, "cg") - .should.equal("sb://hostname.servicebus.windows.net/ep/ConsumerGroups/cg/Partitions/0"); - done(); - }); - - it("requires that Endpoint be present in the connection string", (done) => { - const connectionString = `Endpoint=sb://a`; - - should.throw(() => { - EventHubConnectionConfig.create(connectionString); - }, /must contain EntityPath/); - - done(); - }); - }); - - describe("IotHub", function() { - const iotString = - "HostName=someiot.azure-devices.net;SharedAccessKeyName=sakName;SharedAccessKey=sak;DeviceId=device-1234"; - it("should correctly create an iothub connection config from an iothub connectionstring", function(done) { - const config = IotHubConnectionConfig.create(iotString); - config.should.have.property("hostName").that.equals("someiot.azure-devices.net"); - config.should.have.property("host").that.equals("someiot"); - config.should.have.property("sharedAccessKeyName").that.equals("sakName"); - config.should.have.property("sharedAccessKey").that.equals("sak"); - config.should.have.property("entityPath").that.equals("messages/events"); - done(); - }); - - it("populates path from the path argument if provided", function(done) { - const config = IotHubConnectionConfig.create(iotString, "abc"); - config.should.have.property("entityPath").that.equals("abc"); - done(); - }); - - it("converts an IotHubConnectionConfig to an EventHubConnectionConfig", function(done) { - const config = IotHubConnectionConfig.create(iotString); - const ehConfig = IotHubConnectionConfig.convertToEventHubConnectionConfig(config); - ehConfig.should.have.property("endpoint").that.equals("sb://someiot.azure-devices.net/"); - ehConfig.should.have.property("host").that.equals("someiot.azure-devices.net"); - ehConfig.should.have.property("sharedAccessKeyName").that.equals("sakName"); - ehConfig.should.have.property("sharedAccessKey").that.equals("sak"); - ehConfig.should.have.property("entityPath").that.equals("messages/events"); - - ehConfig.getManagementAddress().should.equal("messages/events/$management"); - ehConfig.getSenderAddress().should.equal("messages/events"); - ehConfig.getSenderAddress("0").should.equal("messages/events/Partitions/0"); - ehConfig.getSenderAddress(0).should.equal("messages/events/Partitions/0"); - ehConfig - .getReceiverAddress("0") - .should.equal("messages/events/ConsumerGroups/$default/Partitions/0"); - ehConfig - .getReceiverAddress(0) - .should.equal("messages/events/ConsumerGroups/$default/Partitions/0"); - ehConfig - .getReceiverAddress("0", "cg") - .should.equal("messages/events/ConsumerGroups/cg/Partitions/0"); - ehConfig - .getReceiverAddress(0, "cg") - .should.equal("messages/events/ConsumerGroups/cg/Partitions/0"); - - ehConfig - .getManagementAudience() - .should.equal("sb://someiot.azure-devices.net/messages/events/$management"); - ehConfig.getSenderAudience().should.equal("sb://someiot.azure-devices.net/messages/events"); - ehConfig - .getSenderAudience("0") - .should.equal("sb://someiot.azure-devices.net/messages/events/Partitions/0"); - ehConfig - .getSenderAudience(0) - .should.equal("sb://someiot.azure-devices.net/messages/events/Partitions/0"); - ehConfig - .getReceiverAudience("0") - .should.equal( - "sb://someiot.azure-devices.net/messages/events/ConsumerGroups/$default/Partitions/0" - ); - ehConfig - .getReceiverAudience(0) - .should.equal( - "sb://someiot.azure-devices.net/messages/events/ConsumerGroups/$default/Partitions/0" - ); - ehConfig - .getReceiverAudience("0", "cg") - .should.equal( - "sb://someiot.azure-devices.net/messages/events/ConsumerGroups/cg/Partitions/0" - ); - ehConfig - .getReceiverAudience(0, "cg") - .should.equal( - "sb://someiot.azure-devices.net/messages/events/ConsumerGroups/cg/Partitions/0" - ); - done(); - }); - - describe("Throws error if required connection config properties are not present", function() { - const connectionString = ` - Endpoint=sb://someiot.azure-devices.net/; - SharedAccessKeyName=sakName; - SharedAccessKey=sakName; - EntityPath=messages/events; - `; - - it("requires that connection config be present", (done) => { - should.throw(() => { - IotHubConnectionConfig.validate(undefined as any); - }, /Missing configuration/); - - done(); - }); - - it("requires that hostName be present in the connection config", (done) => { - const config: IotHubConnectionConfig = { - connectionString: connectionString, - hostName: "", - host: "someiot", - sharedAccessKeyName: "sakName", - sharedAccessKey: "sak", - entityPath: "messages/events" - }; - - should.throw(() => { - IotHubConnectionConfig.validate(config); - }, /Missing 'hostName'/); - - done(); - }); - - it("requires that host be present in the connection config", (done) => { - const config: IotHubConnectionConfig = { - connectionString: connectionString, - hostName: "someiot.azure-devices.net", - host: "someiot", - sharedAccessKeyName: "sakName", - sharedAccessKey: "sak", - entityPath: "" - }; - - should.throw(() => { - IotHubConnectionConfig.validate(config); - }, /Missing 'entityPath'/); - - done(); - }); - - it("requires that sharedAccessKeyName be present in the connection config", (done) => { - const config: IotHubConnectionConfig = { - connectionString: connectionString, - hostName: "someiot.azure-devices.net", - host: "someiot", - sharedAccessKeyName: "", - sharedAccessKey: "sak", - entityPath: "messages/events" - }; - - should.throw(() => { - IotHubConnectionConfig.validate(config); - }, /Missing 'sharedAccessKeyName'/); - - done(); - }); - - it("requires that sharedAccessKey be present in the connection config", (done) => { - const config: IotHubConnectionConfig = { - connectionString: connectionString, - hostName: "someiot.azure-devices.net", - host: "someiot", - sharedAccessKeyName: "sakName", - sharedAccessKey: "", - entityPath: "messages/events" - }; - - should.throw(() => { - IotHubConnectionConfig.validate(config); - }, /Missing 'sharedAccessKey'/); - - done(); - }); - }); - }); - describe("SharedAccessSignature", () => { [ "Endpoint=hello;SharedAccessSignature=SharedAccessSignature sr=&sig=someb64=&se=&skn=", @@ -461,28 +225,5 @@ describe("ConnectionConfig", function() { connectionString: "Endpoint=hello;SharedAccessSignature=SharedAccessSignature hellosig" }); }); - - it("SharedAccessSignatureCredential", () => { - const sasCred = new SharedAccessSignatureCredential("SharedAccessSignature se="); - const accessToken = sasCred.getToken("audience isn't used"); - - should.equal( - accessToken.token, - "SharedAccessSignature se=", - "SAS URI we were constructed with should just be returned verbatim without interpretation (and the audience is ignored)" - ); - - should.equal( - accessToken.expiresOnTimestamp, - 0, - "SAS URI always returns 0 for expiry (ignoring what's in the SAS token)" - ); - - // these just exist because we're a SharedKeyCredential but we don't currently - // parse any attributes out (they're available but we've carved out a spot so - // they're not needed.) - should.equal(sasCred.key, ""); - should.equal(sasCred.keyName, ""); - }); }); }); diff --git a/sdk/core/core-amqp/test/context.spec.ts b/sdk/core/core-amqp/test/context.spec.ts index f0fc3c18c62..4c51c56b491 100644 --- a/sdk/core/core-amqp/test/context.spec.ts +++ b/sdk/core/core-amqp/test/context.spec.ts @@ -3,13 +3,7 @@ import * as chai from "chai"; const should = chai.should(); -import { - CbsClient, - ConnectionConfig, - ConnectionContextBase, - DefaultDataTransformer, - SharedKeyCredential -} from "../src"; +import { CbsClient, ConnectionConfig, ConnectionContextBase, DefaultDataTransformer } from "../src"; import { Connection } from "rhea-promise"; import { isNode } from "../src/util/utils"; @@ -32,10 +26,8 @@ describe("ConnectionContextBase", function() { should.exist(context.connectionId); should.exist(context.connectionLock); should.exist(context.negotiateClaimLock); - should.exist(context.tokenCredential); should.exist(context.dataTransformer); context.wasConnectionCloseCalled.should.equal(false); - context.tokenCredential.should.instanceOf(SharedKeyCredential); context.connection.should.instanceOf(Connection); context.connection.options.properties!.product.should.equal("MSJSClient"); context.connection.options.properties!["user-agent"].should.equal("/js-amqp-client"); @@ -141,7 +133,6 @@ describe("ConnectionContextBase", function() { should.exist(context.connectionId); should.exist(context.connectionLock); should.exist(context.negotiateClaimLock); - should.exist(context.tokenCredential); should.exist(context.dataTransformer); context.wasConnectionCloseCalled.should.equal(false); context.cbsSession.should.instanceOf(CbsClient); diff --git a/sdk/eventhub/event-hubs/package.json b/sdk/eventhub/event-hubs/package.json index c3a1443e6bd..5abfc5cbfa7 100644 --- a/sdk/eventhub/event-hubs/package.json +++ b/sdk/eventhub/event-hubs/package.json @@ -91,9 +91,12 @@ "@azure/core-amqp": "2.0.0-beta.1", "@azure/core-asynciterator-polyfill": "^1.0.0", "@azure/core-tracing": "1.0.0-preview.9", + "@azure/core-auth": "^1.1.3", "@azure/logger": "^1.0.0", "@opentelemetry/api": "^0.10.2", "buffer": "^5.2.1", + "is-buffer": "^2.0.3", + "jssha": "^3.1.0", "process": "^0.11.10", "rhea-promise": "^1.0.0", "tslib": "^2.0.0", diff --git a/sdk/eventhub/event-hubs/review/event-hubs.api.md b/sdk/eventhub/event-hubs/review/event-hubs.api.md index ed3f94ed4d4..0e7f4c00a95 100644 --- a/sdk/eventhub/event-hubs/review/event-hubs.api.md +++ b/sdk/eventhub/event-hubs/review/event-hubs.api.md @@ -10,7 +10,7 @@ import { OperationTracingOptions } from '@azure/core-tracing'; import { RetryOptions } from '@azure/core-amqp'; import { Span } from '@opentelemetry/api'; import { SpanContext } from '@opentelemetry/api'; -import { TokenCredential } from '@azure/core-amqp'; +import { TokenCredential } from '@azure/core-auth'; import { WebSocketImpl } from 'rhea-promise'; import { WebSocketOptions } from '@azure/core-amqp'; diff --git a/sdk/eventhub/event-hubs/src/connectionContext.ts b/sdk/eventhub/event-hubs/src/connectionContext.ts index 5aabcaa3a84..870397c890f 100644 --- a/sdk/eventhub/event-hubs/src/connectionContext.ts +++ b/sdk/eventhub/event-hubs/src/connectionContext.ts @@ -13,17 +13,15 @@ import { ConnectionContextBase, Constants, CreateConnectionContextBaseParameters, - EventHubConnectionConfig, - SharedKeyCredential, - TokenCredential, - isTokenCredential, parseConnectionString, - EventHubConnectionStringModel, ConnectionConfig } from "@azure/core-amqp"; +import { TokenCredential, isTokenCredential } from "@azure/core-auth"; import { ManagementClient, ManagementClientOptions } from "./managementClient"; import { EventHubClientOptions } from "./models/public"; import { Connection, ConnectionEvents, Dictionary, EventContext, OnAmqpEvent } from "rhea-promise"; +import { EventHubConnectionConfig } from "./eventhubConnectionConfig"; +import { SharedKeyCredential } from "./eventhubSharedKeyCredential"; /** * @internal @@ -37,6 +35,11 @@ export interface ConnectionContext extends ConnectionContextBase { * parsing the connection string. */ readonly config: EventHubConnectionConfig; + /** + * @property {SharedKeyCredential | TokenCredential} [tokenCredential] The credential to be used for Authentication. + * Default value: SharedKeyCredentials. + */ + tokenCredential: SharedKeyCredential | TokenCredential; /** * @property wasConnectionCloseCalled Indicates whether the close() method was * called on theconnection object. @@ -163,7 +166,6 @@ export namespace ConnectionContext { const parameters: CreateConnectionContextBaseParameters = { config: config, - tokenCredential: tokenCredential, // re-enabling this will be a post-GA discussion. // dataTransformer: options.dataTransformer, isEntityPathRequired: true, @@ -175,6 +177,7 @@ export namespace ConnectionContext { }; // Let us create the base context and then add EventHub specific ConnectionContext properties. const connectionContext = ConnectionContextBase.create(parameters) as ConnectionContext; + connectionContext.tokenCredential = tokenCredential; connectionContext.wasConnectionCloseCalled = false; connectionContext.senders = {}; connectionContext.receivers = {}; @@ -450,7 +453,7 @@ export function createConnectionContext( hostOrConnectionString = String(hostOrConnectionString); if (!isTokenCredential(credentialOrOptions)) { - const parsedCS = parseConnectionString(hostOrConnectionString); + const parsedCS = parseConnectionString<{ EntityPath?: string }>(hostOrConnectionString); if ( !(parsedCS.EntityPath || (typeof eventHubNameOrOptions === "string" && eventHubNameOrOptions)) ) { diff --git a/sdk/eventhub/event-hubs/src/diagnostics/messageSpan.ts b/sdk/eventhub/event-hubs/src/diagnostics/messageSpan.ts index ad15fae72bb..5d858125ed9 100644 --- a/sdk/eventhub/event-hubs/src/diagnostics/messageSpan.ts +++ b/sdk/eventhub/event-hubs/src/diagnostics/messageSpan.ts @@ -3,7 +3,7 @@ import { getTracer } from "@azure/core-tracing"; import { Span, SpanContext, SpanKind } from "@opentelemetry/api"; -import { EventHubConnectionConfig } from "@azure/core-amqp"; +import { EventHubConnectionConfig } from "../eventhubConnectionConfig"; /** * @internal diff --git a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts index 5d1a15769ca..0f21dc72ea4 100644 --- a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts +++ b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts @@ -11,7 +11,7 @@ import { } from "./models/public"; import { InMemoryCheckpointStore } from "./inMemoryCheckpointStore"; import { CheckpointStore, EventProcessor, FullEventProcessorOptions } from "./eventProcessor"; -import { Constants, TokenCredential } from "@azure/core-amqp"; +import { Constants } from "@azure/core-amqp"; import { logger } from "./log"; import { @@ -19,7 +19,7 @@ import { Subscription, SubscriptionEventHandlers } from "./eventHubConsumerClientModels"; -import { isTokenCredential } from "@azure/core-amqp"; +import { TokenCredential, isTokenCredential } from "@azure/core-auth"; import { EventHubProperties, PartitionProperties } from "./managementClient"; import { PartitionGate } from "./impl/partitionGate"; import { v4 as uuid } from "uuid"; diff --git a/sdk/eventhub/event-hubs/src/eventHubProducerClient.ts b/sdk/eventhub/event-hubs/src/eventHubProducerClient.ts index fb3d5735dc2..ab6dfc9ce76 100644 --- a/sdk/eventhub/event-hubs/src/eventHubProducerClient.ts +++ b/sdk/eventhub/event-hubs/src/eventHubProducerClient.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { isTokenCredential, TokenCredential } from "@azure/core-amqp"; +import { isTokenCredential, TokenCredential } from "@azure/core-auth"; import { getTracer } from "@azure/core-tracing"; import { CanonicalCode, Link, Span, SpanContext, SpanKind } from "@opentelemetry/api"; import { ConnectionContext, createConnectionContext } from "./connectionContext"; diff --git a/sdk/core/core-amqp/src/connectionConfig/eventhubConnectionConfig.ts b/sdk/eventhub/event-hubs/src/eventhubConnectionConfig.ts similarity index 99% rename from sdk/core/core-amqp/src/connectionConfig/eventhubConnectionConfig.ts rename to sdk/eventhub/event-hubs/src/eventhubConnectionConfig.ts index 109f90d5cea..0d9a446dc53 100644 --- a/sdk/core/core-amqp/src/connectionConfig/eventhubConnectionConfig.ts +++ b/sdk/eventhub/event-hubs/src/eventhubConnectionConfig.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. /* eslint-disable eqeqeq */ -import { ConnectionConfig } from "./connectionConfig"; +import { ConnectionConfig } from "@azure/core-amqp"; /** * Describes the connection config object that is created after parsing an EventHub connection diff --git a/sdk/core/core-amqp/src/auth/sas.ts b/sdk/eventhub/event-hubs/src/eventhubSharedKeyCredential.ts similarity index 94% rename from sdk/core/core-amqp/src/auth/sas.ts rename to sdk/eventhub/event-hubs/src/eventhubSharedKeyCredential.ts index 88fa6815686..fe7c1f9deb8 100644 --- a/sdk/core/core-amqp/src/auth/sas.ts +++ b/sdk/eventhub/event-hubs/src/eventhubSharedKeyCredential.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { ServiceBusConnectionStringModel, parseConnectionString } from "../util/utils"; +import { parseConnectionString } from "@azure/core-amqp"; import { AccessToken } from "@azure/core-auth"; import { Buffer } from "buffer"; import isBuffer from "is-buffer"; @@ -78,9 +78,11 @@ export class SharedKeyCredential { * @param {string} connectionString - The EventHub/ServiceBus connection string */ static fromConnectionString(connectionString: string): SharedKeyCredential { - const parsed = parseConnectionString< - ServiceBusConnectionStringModel & { SharedAccessSignature: string } - >(connectionString); + const parsed = parseConnectionString<{ + SharedAccessSignature: string; + SharedAccessKeyName: string; + SharedAccessKey: string; + }>(connectionString); if (parsed.SharedAccessSignature == null) { return new SharedKeyCredential(parsed.SharedAccessKeyName, parsed.SharedAccessKey); diff --git a/sdk/eventhub/event-hubs/src/index.ts b/sdk/eventhub/event-hubs/src/index.ts index 0cddd15b1fd..0a2f784099c 100644 --- a/sdk/eventhub/event-hubs/src/index.ts +++ b/sdk/eventhub/event-hubs/src/index.ts @@ -35,5 +35,6 @@ export { EventDataBatch, TryAddOptions } from "./eventDataBatch"; export { Checkpoint } from "./partitionProcessor"; export { CheckpointStore, PartitionOwnership } from "./eventProcessor"; export { CloseReason } from "./models/public"; -export { MessagingError, RetryOptions, TokenCredential, WebSocketOptions } from "@azure/core-amqp"; +export { MessagingError, RetryOptions, WebSocketOptions } from "@azure/core-amqp"; +export { TokenCredential } from "@azure/core-auth"; export { logger } from "./log"; diff --git a/sdk/eventhub/event-hubs/src/linkEntity.ts b/sdk/eventhub/event-hubs/src/linkEntity.ts index 93855e7b8ef..f4262fa8f5b 100644 --- a/sdk/eventhub/event-hubs/src/linkEntity.ts +++ b/sdk/eventhub/event-hubs/src/linkEntity.ts @@ -2,16 +2,12 @@ // Licensed under the MIT license. import { v4 as uuid } from "uuid"; -import { - AccessToken, - Constants, - SharedKeyCredential, - TokenType, - defaultLock -} from "@azure/core-amqp"; +import { Constants, TokenType, defaultLock } from "@azure/core-amqp"; +import { AccessToken } from "@azure/core-auth"; import { ConnectionContext } from "./connectionContext"; import { AwaitableSender, Receiver } from "rhea-promise"; import { logger } from "./log"; +import { SharedKeyCredential } from "../src/eventhubSharedKeyCredential"; /** * @ignore @@ -175,7 +171,7 @@ export class LinkEntity { this.address ); await defaultLock.acquire(this._context.negotiateClaimLock, () => { - return this._context.cbsSession.negotiateClaim(this.audience, tokenObject, tokenType); + return this._context.cbsSession.negotiateClaim(this.audience, tokenObject.token, tokenType); }); logger.verbose( "[%s] Negotiated claim for %s '%s' with with address: %s", diff --git a/sdk/eventhub/event-hubs/src/managementClient.ts b/sdk/eventhub/event-hubs/src/managementClient.ts index 5f64a4514c8..d1d05a4573e 100644 --- a/sdk/eventhub/event-hubs/src/managementClient.ts +++ b/sdk/eventhub/event-hubs/src/managementClient.ts @@ -9,7 +9,6 @@ import { RetryOperationType, RetryOptions, SendRequestOptions, - SharedKeyCredential, defaultLock, retry, translate @@ -33,6 +32,8 @@ import { OperationNames } from "./models/private"; import { Span, SpanContext, SpanKind, CanonicalCode } from "@opentelemetry/api"; import { getParentSpan, OperationOptions } from "./util/operationOptions"; import { getTracer } from "@azure/core-tracing"; +import { SharedKeyCredential } from "../src/eventhubSharedKeyCredential"; + /** * Describes the runtime information of an Event Hub. */ diff --git a/sdk/eventhub/event-hubs/test/auth.spec.ts b/sdk/eventhub/event-hubs/test/auth.spec.ts index a67bc42cdb1..51268c8bb39 100644 --- a/sdk/eventhub/event-hubs/test/auth.spec.ts +++ b/sdk/eventhub/event-hubs/test/auth.spec.ts @@ -1,15 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - parseConnectionString, - ServiceBusConnectionStringModel, - SharedKeyCredential -} from "@azure/core-amqp"; +import { parseConnectionString } from "@azure/core-amqp"; import { EventHubConsumerClient } from "../src/eventHubConsumerClient"; import { EnvVarKeys, getEnvVars } from "./utils/testUtils"; import chai from "chai"; import { EventHubProducerClient } from "../src"; +import { SharedKeyCredential } from "../src/eventhubSharedKeyCredential"; const should = chai.should(); const env = getEnvVars(); @@ -18,7 +15,7 @@ describe("Authentication via SAS", () => { const service = { connectionString: env[EnvVarKeys.EVENTHUB_CONNECTION_STRING], path: env[EnvVarKeys.EVENTHUB_NAME], - fqdn: parseConnectionString( + fqdn: parseConnectionString<{ Endpoint: string }>( env[EnvVarKeys.EVENTHUB_CONNECTION_STRING] ).Endpoint.replace(/\/+$/, "") }; diff --git a/sdk/eventhub/event-hubs/test/config.spec.ts b/sdk/eventhub/event-hubs/test/config.spec.ts new file mode 100644 index 00000000000..541a45573cd --- /dev/null +++ b/sdk/eventhub/event-hubs/test/config.spec.ts @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { EventHubConnectionConfig } from "../src/eventhubConnectionConfig"; +import chai from "chai"; +const should = chai.should(); + +describe("ConnectionConfig", function() { + describe("EventHub", function() { + it("should fail if connection config does not contain path and the connectionstring also does not contain EntityPath", function(done) { + const connectionString = + "Endpoint=sb://hostname.servicebus.windows.net/;SharedAccessKeyName=sakName;SharedAccessKey=sak"; + try { + EventHubConnectionConfig.create(connectionString); + done(new Error("Should not have reached here.")); + } catch (err) { + err.message.should.match(/Either provide "path" or the "connectionString".*/gi); + } + done(); + }); + + it("should correctly populate config properties from an EventHubs connection string and the helper methods should work as expected", function(done) { + const config = EventHubConnectionConfig.create( + "Endpoint=sb://hostname.servicebus.windows.net/;SharedAccessKeyName=sakName;SharedAccessKey=sak;EntityPath=ep" + ); + config.should.have.property("host").that.equals("hostname.servicebus.windows.net"); + config.should.have.property("sharedAccessKeyName").that.equals("sakName"); + config.should.have.property("sharedAccessKey").that.equals("sak"); + config.should.have.property("entityPath").that.equals("ep"); + + config.getManagementAddress().should.equal("ep/$management"); + config.getSenderAddress().should.equal("ep"); + config.getSenderAddress("0").should.equal("ep/Partitions/0"); + config.getSenderAddress(0).should.equal("ep/Partitions/0"); + config.getReceiverAddress("0").should.equal("ep/ConsumerGroups/$default/Partitions/0"); + config.getReceiverAddress(0).should.equal("ep/ConsumerGroups/$default/Partitions/0"); + config.getReceiverAddress("0", "cg").should.equal("ep/ConsumerGroups/cg/Partitions/0"); + config.getReceiverAddress(0, "cg").should.equal("ep/ConsumerGroups/cg/Partitions/0"); + + config + .getManagementAudience() + .should.equal("sb://hostname.servicebus.windows.net/ep/$management"); + config.getSenderAudience().should.equal("sb://hostname.servicebus.windows.net/ep"); + config + .getSenderAudience("0") + .should.equal("sb://hostname.servicebus.windows.net/ep/Partitions/0"); + config + .getSenderAudience(0) + .should.equal("sb://hostname.servicebus.windows.net/ep/Partitions/0"); + config + .getReceiverAudience("0") + .should.equal( + "sb://hostname.servicebus.windows.net/ep/ConsumerGroups/$default/Partitions/0" + ); + config + .getReceiverAudience(0) + .should.equal( + "sb://hostname.servicebus.windows.net/ep/ConsumerGroups/$default/Partitions/0" + ); + config + .getReceiverAudience("0", "cg") + .should.equal("sb://hostname.servicebus.windows.net/ep/ConsumerGroups/cg/Partitions/0"); + config + .getReceiverAudience(0, "cg") + .should.equal("sb://hostname.servicebus.windows.net/ep/ConsumerGroups/cg/Partitions/0"); + done(); + }); + + it("requires that Endpoint be present in the connection string", (done) => { + const connectionString = `Endpoint=sb://a`; + + should.throw(() => { + EventHubConnectionConfig.create(connectionString); + }, /must contain EntityPath/); + + done(); + }); + }); +}); diff --git a/sdk/eventhub/event-hubs/test/diagnostics/messageSpan.spec.ts b/sdk/eventhub/event-hubs/test/diagnostics/messageSpan.spec.ts index 4de4a061a1e..25baad171f2 100644 --- a/sdk/eventhub/event-hubs/test/diagnostics/messageSpan.spec.ts +++ b/sdk/eventhub/event-hubs/test/diagnostics/messageSpan.spec.ts @@ -5,7 +5,7 @@ import chai from "chai"; import { createMessageSpan } from "../../src/diagnostics/messageSpan"; import { TraceFlags, SpanContext } from "@opentelemetry/api"; import { TestTracer, setTracer, getTracer } from "@azure/core-tracing"; -import { EventHubConnectionConfig } from "@azure/core-amqp"; +import { EventHubConnectionConfig } from "../../src/eventhubConnectionConfig"; const should = chai.should(); const assert = chai.assert; diff --git a/sdk/core/core-amqp/test/tokenProvider.spec.ts b/sdk/eventhub/event-hubs/test/sharedKeyCredential.spec.ts similarity index 56% rename from sdk/core/core-amqp/test/tokenProvider.spec.ts rename to sdk/eventhub/event-hubs/test/sharedKeyCredential.spec.ts index 46d5aa5d404..4ead88a9ee6 100644 --- a/sdk/core/core-amqp/test/tokenProvider.spec.ts +++ b/sdk/eventhub/event-hubs/test/sharedKeyCredential.spec.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import * as chai from "chai"; -chai.should(); -import debugModule from "debug"; -const debug = debugModule("azure:core-amqp:token-spec"); -import { IotSharedKeyCredential, SharedKeyCredential } from "../src"; +import chai from "chai"; +const should = chai.should(); +import { + SharedKeyCredential, + SharedAccessSignatureCredential +} from "../src/eventhubSharedKeyCredential"; describe("SharedKeyCredential", function(): void { it("should work as expected with required parameters", async function(): Promise { @@ -13,9 +14,7 @@ describe("SharedKeyCredential", function(): void { const key = "importantValue"; const tokenProvider = new SharedKeyCredential(keyName, key); const now = Math.floor(Date.now() / 1000) + 3600; - debug(">>> now: %d", now); const tokenInfo = tokenProvider.getToken("myaudience"); - debug(">>> Token Info is: %O", tokenInfo); tokenInfo.token.should.match( /SharedAccessSignature sr=myaudience&sig=(.*)&se=\d{10}&skn=myKeyName/g ); @@ -28,28 +27,32 @@ describe("SharedKeyCredential", function(): void { "Endpoint=sb://hostname.servicebus.windows.net/;SharedAccessKeyName=sakName;SharedAccessKey=sak;EntityPath=ep"; const tokenProvider = SharedKeyCredential.fromConnectionString(cs); const now = Math.floor(Date.now() / 1000) + 3600; - debug(">>> now: %d", now); const tokenInfo = tokenProvider.getToken("sb://hostname.servicebus.windows.net/"); - debug(">>> Token Info is: %O", tokenInfo); tokenInfo.token.should.match( /SharedAccessSignature sr=sb%3A%2F%2Fhostname.servicebus.windows.net%2F&sig=(.*)&se=\d{10}&skn=sakName/g ); tokenInfo.expiresOnTimestamp.should.equal(now); }); -}); + it("SharedAccessSignatureCredential", () => { + const sasCred = new SharedAccessSignatureCredential("SharedAccessSignature se="); + const accessToken = sasCred.getToken("audience isn't used"); -describe("IotSharedKeyCredential", function(): void { - it("should work as expected with required parameters", async function(): Promise { - const keyName = "myKeyName"; - const key = "importantValue"; - const tokenProvider = new IotSharedKeyCredential(keyName, key); - const now = Math.floor(Date.now() / 1000) + 3600; - debug(">>> now: %d", now); - const tokenInfo = tokenProvider.getToken("myaudience"); - debug(">>> Token Info is: %O", tokenInfo); - tokenInfo.token.should.match( - /SharedAccessSignature sr=myaudience&sig=(.*)&se=\d{10}&skn=myKeyName/g + should.equal( + accessToken.token, + "SharedAccessSignature se=", + "SAS URI we were constructed with should just be returned verbatim without interpretation (and the audience is ignored)" ); - tokenInfo.expiresOnTimestamp.should.equal(now); + + should.equal( + accessToken.expiresOnTimestamp, + 0, + "SAS URI always returns 0 for expiry (ignoring what's in the SAS token)" + ); + + // these just exist because we're a SharedKeyCredential but we don't currently + // parse any attributes out (they're available but we've carved out a spot so + // they're not needed.) + should.equal(sasCred.key, ""); + should.equal(sasCred.keyName, ""); }); }); diff --git a/sdk/servicebus/service-bus/package.json b/sdk/servicebus/service-bus/package.json index 920592cfe7c..293724c51c7 100644 --- a/sdk/servicebus/service-bus/package.json +++ b/sdk/servicebus/service-bus/package.json @@ -102,12 +102,14 @@ "@azure/core-http": "^1.2.0", "@azure/core-tracing": "1.0.0-preview.9", "@azure/core-paging": "^1.1.1", + "@azure/core-auth": "^1.1.3", "@azure/logger": "^1.0.0", "@opentelemetry/api": "^0.10.2", "@types/is-buffer": "^2.0.0", "@types/long": "^4.0.0", "buffer": "^5.2.1", "is-buffer": "^2.0.3", + "jssha": "^3.1.0", "long": "^4.0.0", "process": "^0.11.10", "tslib": "^2.0.0", diff --git a/sdk/servicebus/service-bus/review/service-bus.api.md b/sdk/servicebus/service-bus/review/service-bus.api.md index feae64ba70d..bb4c6db016e 100644 --- a/sdk/servicebus/service-bus/review/service-bus.api.md +++ b/sdk/servicebus/service-bus/review/service-bus.api.md @@ -20,7 +20,7 @@ import { RetryOptions } from '@azure/core-amqp'; import { ServiceClient } from '@azure/core-http'; import { Span } from '@opentelemetry/api'; import { SpanContext } from '@opentelemetry/api'; -import { TokenCredential } from '@azure/core-amqp'; +import { TokenCredential } from '@azure/core-auth'; import { TokenType } from '@azure/core-amqp'; import { UserAgentOptions } from '@azure/core-http'; import { WebSocketImpl } from 'rhea-promise'; diff --git a/sdk/servicebus/service-bus/src/connectionContext.ts b/sdk/servicebus/service-bus/src/connectionContext.ts index 4c9526dd9e7..ecec9edc9db 100644 --- a/sdk/servicebus/service-bus/src/connectionContext.ts +++ b/sdk/servicebus/service-bus/src/connectionContext.ts @@ -8,10 +8,9 @@ import { ConnectionContextBase, Constants, CreateConnectionContextBaseParameters, - SharedKeyCredential, - TokenCredential, delay } from "@azure/core-amqp"; +import { TokenCredential } from "@azure/core-auth"; import { ServiceBusClientOptions } from "./constructorHelpers"; import { Connection, ConnectionEvents, EventContext, OnAmqpEvent } from "rhea-promise"; import { MessageSender } from "./core/messageSender"; @@ -20,6 +19,7 @@ import { MessageReceiver } from "./core/messageReceiver"; import { ManagementClient } from "./core/managementClient"; import { formatUserAgentPrefix } from "./util/utils"; import { getRuntimeInfo } from "./util/runtimeInfo"; +import { SharedKeyCredential } from "./servicebusSharedKeyCredential"; /** * @internal @@ -28,6 +28,11 @@ import { getRuntimeInfo } from "./util/runtimeInfo"; * tokenCredential, senders, receivers, etc. about the ServiceBus client. */ export interface ConnectionContext extends ConnectionContextBase { + /** + * @property {SharedKeyCredential | TokenCredential} [tokenCredential] The credential to be used for Authentication. + * Default value: SharedKeyCredentials. + */ + tokenCredential: SharedKeyCredential | TokenCredential; /** * @property A map of active Service Bus Senders with sender name as key. */ @@ -141,7 +146,6 @@ export namespace ConnectionContext { )} ${getRuntimeInfo()}`; const parameters: CreateConnectionContextBaseParameters = { config: config, - tokenCredential: tokenCredential, // re-enabling this will be a post-GA discussion similar to event-hubs. // dataTransformer: options.dataTransformer, isEntityPathRequired: false, @@ -153,6 +157,7 @@ export namespace ConnectionContext { }; // Let us create the base context and then add ServiceBus specific ConnectionContext properties. const connectionContext = ConnectionContextBase.create(parameters) as ConnectionContext; + connectionContext.tokenCredential = tokenCredential; connectionContext.senders = {}; connectionContext.messageReceivers = {}; connectionContext.messageSessions = {}; diff --git a/sdk/servicebus/service-bus/src/constructorHelpers.ts b/sdk/servicebus/service-bus/src/constructorHelpers.ts index b1350bac337..99c7d1186e8 100644 --- a/sdk/servicebus/service-bus/src/constructorHelpers.ts +++ b/sdk/servicebus/service-bus/src/constructorHelpers.ts @@ -1,15 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - ConnectionConfig, - RetryOptions, - SharedKeyCredential, - TokenCredential, - WebSocketOptions -} from "@azure/core-amqp"; +import { ConnectionConfig, RetryOptions, WebSocketOptions } from "@azure/core-amqp"; +import { TokenCredential } from "@azure/core-auth"; import { ConnectionContext } from "./connectionContext"; import { UserAgentOptions } from "@azure/core-http"; +import { SharedKeyCredential } from "./servicebusSharedKeyCredential"; /** * Describes the options that can be provided while creating the ServiceBusClient. diff --git a/sdk/servicebus/service-bus/src/core/linkEntity.ts b/sdk/servicebus/service-bus/src/core/linkEntity.ts index 9da82056024..e8edf1edd9c 100644 --- a/sdk/servicebus/service-bus/src/core/linkEntity.ts +++ b/sdk/servicebus/service-bus/src/core/linkEntity.ts @@ -2,14 +2,13 @@ // Licensed under the MIT license. import { - AccessToken, Constants, - SharedKeyCredential, TokenType, defaultLock, RequestResponseLink, MessagingError } from "@azure/core-amqp"; +import { AccessToken } from "@azure/core-auth"; import { ConnectionContext } from "../connectionContext"; import { AwaitableSender, @@ -22,6 +21,7 @@ import { import { getUniqueName, StandardAbortMessage } from "../util/utils"; import { AbortError, AbortSignalLike } from "@azure/abort-controller"; import { ServiceBusLogger } from "../log"; +import { SharedKeyCredential } from "../servicebusSharedKeyCredential"; /** * @internal @@ -444,7 +444,7 @@ export abstract class LinkEntity { this.checkIfConnectionReady(); - return this._context.cbsSession.negotiateClaim(this.audience, tokenObject, tokenType); + return this._context.cbsSession.negotiateClaim(this.audience, tokenObject.token, tokenType); }); this._logger.verbose( "%s Negotiated claim for %s '%s' with with address: %s", diff --git a/sdk/servicebus/service-bus/src/index.ts b/sdk/servicebus/service-bus/src/index.ts index 2aaca6658f8..7ca67cc0c6b 100644 --- a/sdk/servicebus/service-bus/src/index.ts +++ b/sdk/servicebus/service-bus/src/index.ts @@ -10,10 +10,10 @@ export { MessageErrorCodes, MessagingError, RetryOptions, - TokenCredential, TokenType, WebSocketOptions } from "@azure/core-amqp"; +export { TokenCredential } from "@azure/core-auth"; export { OperationOptions } from "@azure/core-http"; export { Delivery, WebSocketImpl } from "rhea-promise"; export { ServiceBusClientOptions } from "./constructorHelpers"; diff --git a/sdk/servicebus/service-bus/src/serviceBusAtomManagementClient.ts b/sdk/servicebus/service-bus/src/serviceBusAtomManagementClient.ts index 3b5652ab146..b361fb9d3bf 100644 --- a/sdk/servicebus/service-bus/src/serviceBusAtomManagementClient.ts +++ b/sdk/servicebus/service-bus/src/serviceBusAtomManagementClient.ts @@ -1,12 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - Constants as AMQPConstants, - isTokenCredential, - parseConnectionString, - TokenCredential -} from "@azure/core-amqp"; +import { Constants as AMQPConstants, parseConnectionString } from "@azure/core-amqp"; +import { TokenCredential, isTokenCredential } from "@azure/core-auth"; import { bearerTokenAuthenticationPolicy, createPipelineFromOptions, diff --git a/sdk/servicebus/service-bus/src/serviceBusClient.ts b/sdk/servicebus/service-bus/src/serviceBusClient.ts index 9959671fff7..ea91dc6596b 100644 --- a/sdk/servicebus/service-bus/src/serviceBusClient.ts +++ b/sdk/servicebus/service-bus/src/serviceBusClient.ts @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { TokenCredential, isTokenCredential, ConnectionConfig } from "@azure/core-amqp"; +import { ConnectionConfig } from "@azure/core-amqp"; +import { TokenCredential, isTokenCredential } from "@azure/core-auth"; import { ServiceBusClientOptions, createConnectionContextForConnectionString, diff --git a/sdk/servicebus/service-bus/src/servicebusSharedKeyCredential.ts b/sdk/servicebus/service-bus/src/servicebusSharedKeyCredential.ts new file mode 100644 index 00000000000..fe7c1f9deb8 --- /dev/null +++ b/sdk/servicebus/service-bus/src/servicebusSharedKeyCredential.ts @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { parseConnectionString } from "@azure/core-amqp"; +import { AccessToken } from "@azure/core-auth"; +import { Buffer } from "buffer"; +import isBuffer from "is-buffer"; +import jssha from "jssha"; + +/** + * @class SharedKeyCredential + * Defines the SharedKeyCredential . + */ +export class SharedKeyCredential { + /** + * @property {string} keyName - The name of the EventHub/ServiceBus key. + */ + keyName: string; + + /** + * @property {string} key - The secret value associated with the above EventHub/ServiceBus key. + */ + key: string; + + /** + * Initializes a new instance of SharedKeyCredential + * @constructor + * @param {string} keyName - The name of the EventHub/ServiceBus key. + * @param {string} key - The secret value associated with the above EventHub/ServiceBus key + */ + constructor(keyName: string, key: string) { + this.keyName = keyName; + this.key = key; + } + + /** + * Gets the sas token for the specified audience + * @param {string} [audience] - The audience for which the token is desired. + */ + getToken(audience: string): AccessToken { + return this._createToken(Math.floor(Date.now() / 1000) + 3600, audience); + } + + /** + * Creates the sas token based on the provided information + * @param {string | number} expiry - The time period in unix time after which the token will expire. + * @param {string} [audience] - The audience for which the token is desired. + * @param {string | Buffer} [hashInput] The input to be provided to hmac to create the hash. + */ + protected _createToken( + expiry: number, + audience: string, + hashInput?: string | Buffer + ): AccessToken { + audience = encodeURIComponent(audience); + const keyName = encodeURIComponent(this.keyName); + const stringToSign = audience + "\n" + expiry; + hashInput = hashInput || this.key; + let shaObj: any; + if (isBuffer(hashInput)) { + shaObj = new jssha("SHA-256", "ARRAYBUFFER"); + shaObj.setHMACKey(hashInput, "ARRAYBUFFER"); + shaObj.update(Buffer.from(stringToSign)); + } else { + shaObj = new jssha("SHA-256", "TEXT"); + shaObj.setHMACKey(hashInput, "TEXT"); + shaObj.update(stringToSign); + } + const sig = encodeURIComponent(shaObj.getHMAC("B64")); + return { + token: `SharedAccessSignature sr=${audience}&sig=${sig}&se=${expiry}&skn=${keyName}`, + expiresOnTimestamp: expiry + }; + } + + /** + * Creates a token provider from the EventHub/ServiceBus connection string; + * @param {string} connectionString - The EventHub/ServiceBus connection string + */ + static fromConnectionString(connectionString: string): SharedKeyCredential { + const parsed = parseConnectionString<{ + SharedAccessSignature: string; + SharedAccessKeyName: string; + SharedAccessKey: string; + }>(connectionString); + + if (parsed.SharedAccessSignature == null) { + return new SharedKeyCredential(parsed.SharedAccessKeyName, parsed.SharedAccessKey); + } else { + return new SharedAccessSignatureCredential(parsed.SharedAccessSignature); + } + } +} + +/** + * A credential that takes a SharedAccessSignature: + * `SharedAccessSignature sr=&sig=&se=&skn=` + * + * @internal + * @ignore + */ +export class SharedAccessSignatureCredential extends SharedKeyCredential { + private _accessToken: AccessToken; + + /** + * @param sharedAccessSignature A shared access signature of the form + * `SharedAccessSignature sr=&sig=&se=&skn=` + */ + constructor(sharedAccessSignature: string) { + super("", ""); + + this._accessToken = { + token: sharedAccessSignature, + expiresOnTimestamp: 0 + }; + } + + /** + * Retrieve a valid token for authenticaton. + * + * @param _audience Not applicable in SharedAccessSignatureCredential as the token is not re-generated at every invocation of the method + */ + getToken(_audience: string): AccessToken { + return this._accessToken; + } +} diff --git a/sdk/servicebus/service-bus/src/util/sasServiceClientCredentials.ts b/sdk/servicebus/service-bus/src/util/sasServiceClientCredentials.ts index 496546d59e1..2a65d44c906 100644 --- a/sdk/servicebus/service-bus/src/util/sasServiceClientCredentials.ts +++ b/sdk/servicebus/service-bus/src/util/sasServiceClientCredentials.ts @@ -4,7 +4,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { AccessToken, SharedKeyCredential } from "@azure/core-amqp"; +import { AccessToken } from "@azure/core-auth"; +import { SharedKeyCredential } from "../servicebusSharedKeyCredential"; import { HttpHeaders, ServiceClientCredentials, WebResource } from "@azure/core-http"; import { generateKey } from "./crypto"; diff --git a/sdk/servicebus/service-bus/test/atomManagement.spec.ts b/sdk/servicebus/service-bus/test/atomManagement.spec.ts index 453a4e8907d..acef133466a 100644 --- a/sdk/servicebus/service-bus/test/atomManagement.spec.ts +++ b/sdk/servicebus/service-bus/test/atomManagement.spec.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { isNode } from "@azure/core-amqp"; import { PageSettings } from "@azure/core-paging"; import { DefaultAzureCredential } from "@azure/identity"; import chai from "chai"; @@ -14,7 +13,7 @@ import { RuleProperties, CreateRuleOptions } from "../src/serializers/ruleResour import { CreateSubscriptionOptions } from "../src/serializers/subscriptionResourceSerializer"; import { CreateTopicOptions } from "../src/serializers/topicResourceSerializer"; import { ServiceBusAdministrationClient } from "../src/serviceBusAtomManagementClient"; -import { EntityStatus, EntityAvailabilityStatus } from "../src/util/utils"; +import { EntityStatus, EntityAvailabilityStatus, isNode } from "../src/util/utils"; import { EnvVarNames, getEnvVars } from "./utils/envVarUtils"; import { recreateQueue, recreateSubscription, recreateTopic } from "./utils/managementUtils"; import { EntityNames } from "./utils/testUtils"; diff --git a/sdk/servicebus/service-bus/test/auth.spec.ts b/sdk/servicebus/service-bus/test/auth.spec.ts index d10e9743dec..b05d4b1fbe8 100644 --- a/sdk/servicebus/service-bus/test/auth.spec.ts +++ b/sdk/servicebus/service-bus/test/auth.spec.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { SharedKeyCredential } from "@azure/core-amqp"; +import { SharedKeyCredential } from "../src/servicebusSharedKeyCredential"; import chai from "chai"; import { ServiceBusClient, ServiceBusReceiver, parseServiceBusConnectionString } from "../src"; import { getEnvVars } from "./utils/envVarUtils"; diff --git a/sdk/servicebus/service-bus/test/internal/sharedKeyCredential.spec.ts b/sdk/servicebus/service-bus/test/internal/sharedKeyCredential.spec.ts new file mode 100644 index 00000000000..0eb72118a4e --- /dev/null +++ b/sdk/servicebus/service-bus/test/internal/sharedKeyCredential.spec.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import chai from "chai"; +const should = chai.should(); +import { + SharedKeyCredential, + SharedAccessSignatureCredential +} from "../../src/servicebusSharedKeyCredential"; + +describe("SharedKeyCredential", function(): void { + it("should work as expected with required parameters", async function(): Promise { + const keyName = "myKeyName"; + const key = "importantValue"; + const tokenProvider = new SharedKeyCredential(keyName, key); + const now = Math.floor(Date.now() / 1000) + 3600; + const tokenInfo = tokenProvider.getToken("myaudience"); + tokenInfo.token.should.match( + /SharedAccessSignature sr=myaudience&sig=(.*)&se=\d{10}&skn=myKeyName/g + ); + tokenInfo.expiresOnTimestamp.should.equal(now); + }); + it("should work as expected when created from a connection string", async function(): Promise< + void + > { + const cs = + "Endpoint=sb://hostname.servicebus.windows.net/;SharedAccessKeyName=sakName;SharedAccessKey=sak;EntityPath=ep"; + const tokenProvider = SharedKeyCredential.fromConnectionString(cs); + const now = Math.floor(Date.now() / 1000) + 3600; + const tokenInfo = tokenProvider.getToken("sb://hostname.servicebus.windows.net/"); + tokenInfo.token.should.match( + /SharedAccessSignature sr=sb%3A%2F%2Fhostname.servicebus.windows.net%2F&sig=(.*)&se=\d{10}&skn=sakName/g + ); + tokenInfo.expiresOnTimestamp.should.equal(now); + }); + it("SharedAccessSignatureCredential", () => { + const sasCred = new SharedAccessSignatureCredential("SharedAccessSignature se="); + const accessToken = sasCred.getToken("audience isn't used"); + + should.equal( + accessToken.token, + "SharedAccessSignature se=", + "SAS URI we were constructed with should just be returned verbatim without interpretation (and the audience is ignored)" + ); + + should.equal( + accessToken.expiresOnTimestamp, + 0, + "SAS URI always returns 0 for expiry (ignoring what's in the SAS token)" + ); + + // these just exist because we're a SharedKeyCredential but we don't currently + // parse any attributes out (they're available but we've carved out a spot so + // they're not needed.) + should.equal(sasCred.key, ""); + should.equal(sasCred.keyName, ""); + }); +}); diff --git a/sdk/servicebus/service-bus/test/internal/unittestUtils.ts b/sdk/servicebus/service-bus/test/internal/unittestUtils.ts index 78ffb89dca2..8a35bc44e0e 100644 --- a/sdk/servicebus/service-bus/test/internal/unittestUtils.ts +++ b/sdk/servicebus/service-bus/test/internal/unittestUtils.ts @@ -8,7 +8,8 @@ import { ReceiverEvents, ReceiverOptions } from "rhea-promise"; -import { DefaultDataTransformer, AccessToken, Constants } from "@azure/core-amqp"; +import { DefaultDataTransformer, Constants } from "@azure/core-amqp"; +import { AccessToken } from "@azure/core-auth"; import { EventEmitter } from "events"; import { getUniqueName } from "../../src/util/utils"; import { Link } from "rhea-promise/typings/lib/link"; diff --git a/sdk/servicebus/service-bus/test/streamingReceiver.spec.ts b/sdk/servicebus/service-bus/test/streamingReceiver.spec.ts index 1f664f326ec..1abbbc5f79e 100644 --- a/sdk/servicebus/service-bus/test/streamingReceiver.spec.ts +++ b/sdk/servicebus/service-bus/test/streamingReceiver.spec.ts @@ -18,7 +18,7 @@ import { getRandomTestClientTypeWithNoSessions } from "./utils/testutils2"; import { getDeliveryProperty } from "./utils/misc"; -import { isNode } from "@azure/core-amqp"; +import { isNode } from "../src/util/utils"; import { verifyMessageCount } from "./utils/managementUtils"; import sinon from "sinon";