Skip to content

Commit

Permalink
Allowing for a SAS key to be directly specified in a connection string.
Browse files Browse the repository at this point in the history
  • Loading branch information
richardpark-msft committed Aug 31, 2020
1 parent 970ff62 commit bd8deac
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 5 deletions.
1 change: 1 addition & 0 deletions sdk/core/core-amqp/review/core-amqp.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ export class SharedKeyCredential {
getToken(audience: string): AccessToken;
key: string;
keyName: string;
renewable: boolean;
}

// @public
Expand Down
101 changes: 99 additions & 2 deletions sdk/core/core-amqp/src/auth/sas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +36,7 @@ export class SharedKeyCredential {
constructor(keyName: string, key: string) {
this.keyName = keyName;
this.key = key;
this.renewable = true;
}

/**
Expand Down Expand Up @@ -78,7 +84,98 @@ export class SharedKeyCredential {
* @param {string} connectionString - The EventHub/ServiceBus connection string
*/
static fromConnectionString(connectionString: string): SharedKeyCredential {
const parsed = parseConnectionString<ServiceBusConnectionStringModel>(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=<resource>&sig=<signature>&se=<expiry>&skn=<keyname>`
* or
* `sr=<resource>&sig=<signature>&se=<expiry>&skn=<keyname>`
*
* @internal
* @ignore
*/
export class SharedAccessSignatureCredential extends SharedKeyCredential {
private _accessToken: AccessToken;

/**
* @param sharedAccessSignature A shared access signature of the form `sr=<resource>&sig=<signature>&se=<expiry>&skn=<keyname>`
*/
constructor(sharedAccessSignature: string) {
super("", "");

const {
keyName,
expiry: expiresOnTimestamp,
token
} = SharedAccessSignatureCredential.extractSasProperties(sharedAccessSignature);

this.keyName = keyName;
this.key = "<none>";
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
};
}
}
7 changes: 6 additions & 1 deletion sdk/servicebus/service-bus/src/constructorHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
7 changes: 5 additions & 2 deletions sdk/servicebus/service-bus/src/core/linkEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,11 @@ export abstract class LinkEntity<LinkT extends Receiver | AwaitableSender | Requ
if (this._context.tokenCredential instanceof SharedKeyCredential) {
tokenObject = this._context.tokenCredential.getToken(this.audience);
tokenType = TokenType.CbsTokenTypeSas;
// renew sas token in every 45 minutess
this._tokenTimeout = (3600 - 900) * 1000;

if (this._context.tokenCredential.renewable) {
// renew sas token in every 45 minutes
this._tokenTimeout = (3600 - 900) * 1000;
}
} else {
const aadToken = await this._context.tokenCredential.getToken(Constants.aadServiceBusScope);
if (!aadToken) {
Expand Down

0 comments on commit bd8deac

Please sign in to comment.