diff --git a/sdk/core/core-amqp/review/core-amqp.api.md b/sdk/core/core-amqp/review/core-amqp.api.md index 7c09f88d2739..e8d4c4468931 100644 --- a/sdk/core/core-amqp/review/core-amqp.api.md +++ b/sdk/core/core-amqp/review/core-amqp.api.md @@ -684,6 +684,7 @@ export class SharedKeyCredential { getToken(audience: string): AccessToken; key: string; keyName: string; + renewable: boolean; } // @public diff --git a/sdk/core/core-amqp/src/auth/sas.ts b/sdk/core/core-amqp/src/auth/sas.ts index 432b234b3b70..f14888d049ba 100644 --- a/sdk/core/core-amqp/src/auth/sas.ts +++ b/sdk/core/core-amqp/src/auth/sas.ts @@ -22,6 +22,11 @@ export class SharedKeyCredential { */ key: string; + /** + * @property {boolean} renewable - Whether the credential is renewable. + */ + renewable: boolean; + /** * Initializes a new instance of SharedKeyCredential * @constructor @@ -31,6 +36,7 @@ export class SharedKeyCredential { constructor(keyName: string, key: string) { this.keyName = keyName; this.key = key; + this.renewable = true; } /** @@ -78,7 +84,98 @@ export class SharedKeyCredential { * @param {string} connectionString - The EventHub/ServiceBus connection string */ static fromConnectionString(connectionString: string): SharedKeyCredential { - const parsed = parseConnectionString(connectionString); - return new SharedKeyCredential(parsed.SharedAccessKeyName, parsed.SharedAccessKey); + const parsed = parseConnectionString< + ServiceBusConnectionStringModel & { SharedAccessSignature: 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 of two forms: + * `SharedAccessSignature sr=&sig=&se=&skn=` + * or + * `sr=&sig=&se=&skn=` + * + * @internal + * @ignore + */ +export class SharedAccessSignatureCredential extends SharedKeyCredential { + private _accessToken: AccessToken; + + /** + * @param sharedAccessSignature A shared access signature of the form `sr=&sig=&se=&skn=` + */ + constructor(sharedAccessSignature: string) { + super("", ""); + + const { + keyName, + expiry: expiresOnTimestamp, + token + } = SharedAccessSignatureCredential.extractSasProperties(sharedAccessSignature); + + this.keyName = keyName; + this.key = ""; + this._accessToken = { + token, + expiresOnTimestamp + }; + + this.renewable = false; + } + + /** + * Retrieve a valid token for authenticaton. + * + * @param _audience Not used for SAS tokens. + */ + getToken(_audience: string): AccessToken { + return this._accessToken; + } + + private static extractSasProperties( + sharedAccessSignature: string + ): { + expiry: number; + keyName: string; + token: string; + } { + let expiry: number | undefined; + let keyName: string | undefined; + + sharedAccessSignature = sharedAccessSignature.replace(/^SharedAccessSignature\s+/, ""); + + for (const part of sharedAccessSignature.split("&")) { + const [key, value] = part.split("="); + + if (expiry != null && keyName != null) { + break; + } + + switch (key) { + case "se": + expiry = Number(decodeURIComponent(value)); + break; + case "skn": + keyName = decodeURIComponent(value); + break; + } + } + + if (expiry == null || keyName == null) { + throw new Error("Invalid se/skn field."); + } + + return { + token: `SharedAccessSignature ${sharedAccessSignature}`, + expiry, + keyName + }; } } diff --git a/sdk/servicebus/service-bus/src/constructorHelpers.ts b/sdk/servicebus/service-bus/src/constructorHelpers.ts index da549a52b543..48b1de5cec89 100644 --- a/sdk/servicebus/service-bus/src/constructorHelpers.ts +++ b/sdk/servicebus/service-bus/src/constructorHelpers.ts @@ -48,7 +48,12 @@ export function createConnectionContextForConnectionString( config.webSocketEndpointPath = "$servicebus/websocket"; config.webSocketConstructorOptions = options?.webSocketOptions?.webSocketConstructorOptions; - const credential = new SharedKeyCredential(config.sharedAccessKeyName, config.sharedAccessKey); + const credential = SharedKeyCredential.fromConnectionString(connectionString); + + // TODO: silly workaround for purely SAS credentials. + config.sharedAccessKey = config.sharedAccessKey ?? credential.key; + config.sharedAccessKeyName = config.sharedAccessKeyName ?? credential.keyName; + validate(config); return ConnectionContext.create(config, credential, options); } diff --git a/sdk/servicebus/service-bus/src/core/linkEntity.ts b/sdk/servicebus/service-bus/src/core/linkEntity.ts index 8d49a9b914a8..607465a4d27e 100644 --- a/sdk/servicebus/service-bus/src/core/linkEntity.ts +++ b/sdk/servicebus/service-bus/src/core/linkEntity.ts @@ -437,8 +437,11 @@ export abstract class LinkEntity