Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Communication] - Authentication - SmsClient auth using token credential #13260

Merged
merged 11 commits into from
Jan 19, 2021
3 changes: 3 additions & 0 deletions sdk/communication/communication-sms/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 1.0.0-beta.4 (Unreleased)

### Added

- `SmsClient` added a constructor that supports `TokenCredential`.

## 1.0.0-beta.3 (2020-11-16)

Expand Down
10 changes: 10 additions & 0 deletions sdk/communication/communication-sms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ const connectionString = `endpoint=<Host>;accessKey=<Base64-Encoded-Key>`;
const client = new SmsClient(connectionString);
```

### Using a `TokenCredential`

```typescript
import { DefaultAzureCredential } from "@azure/identity";
import { CommunicationIdentityClient } from "@azure/communication-administration";

const credential = new DefaultAzureCredential();
const client = new CommunicationIdentityClient("<Host>", credential);
```

## Sending SMS

```typescript
Expand Down
6 changes: 5 additions & 1 deletion sdk/communication/communication-sms/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ module.exports = function(config) {
envPreprocessor: [
"AZURE_COMMUNICATION_LIVETEST_CONNECTION_STRING",
"AZURE_PHONE_NUMBER",
"TEST_MODE"
"TEST_MODE",
"COMMUNICATION_ENDPOINT",
"AZURE_CLIENT_ID",
"AZURE_CLIENT_SECRET",
"AZURE_TENANT_ID"
],

// test results reporter to use
Expand Down
1 change: 1 addition & 0 deletions sdk/communication/communication-sms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"devDependencies": {
"@azure/dev-tool": "^1.0.0",
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
"@azure/identity": "^1.1.0",
"@azure/test-utils-recorder": "^1.0.0",
"@microsoft/api-extractor": "7.7.11",
"@rollup/plugin-commonjs": "11.0.2",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { KeyCredential } from '@azure/core-auth';
import { OperationOptions } from '@azure/core-http';
import { PipelineOptions } from '@azure/core-http';
import { RestResponse } from '@azure/core-http';
import { TokenCredential } from '@azure/core-auth';

// @public
export interface SendOptions extends OperationOptions {
Expand All @@ -25,6 +26,7 @@ export interface SendRequest {
export class SmsClient {
constructor(connectionString: string, options?: SmsClientOptions);
constructor(url: string, credential: KeyCredential, options?: SmsClientOptions);
constructor(url: string, credential: TokenCredential, options?: SmsClientOptions);
send(sendRequest: SendRequest, options?: SendOptions): Promise<RestResponse>;
}

Expand Down
18 changes: 13 additions & 5 deletions sdk/communication/communication-sms/src/smsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// Licensed under the MIT license.

import {
createCommunicationAccessKeyCredentialPolicy,
parseClientArguments,
isKeyCredential
isKeyCredential,
createCommunicationAuthPolicy
} from "@azure/communication-common";
import { KeyCredential } from "@azure/core-auth";
import { KeyCredential, TokenCredential } from "@azure/core-auth";
import {
RestResponse,
PipelineOptions,
Expand Down Expand Up @@ -87,9 +87,17 @@ export class SmsClient {
*/
constructor(url: string, credential: KeyCredential, options?: SmsClientOptions);

/**
* Initializes a new instance of the SmsClient class using a TokenCredential.
* @param url The endpoint of the service (ex: https://contoso.eastus.communications.azure.net).
* @param credential TokenCredential that is used to authenticate requests to the service.
* @param options Optional. Options to configure the HTTP pipeline.
*/
constructor(url: string, credential: TokenCredential, options?: SmsClientOptions);

constructor(
connectionStringOrUrl: string,
credentialOrOptions?: KeyCredential | SmsClientOptions,
credentialOrOptions?: KeyCredential | TokenCredential | SmsClientOptions,
maybeOptions: SmsClientOptions = {}
) {
const { url, credential } = parseClientArguments(connectionStringOrUrl, credentialOrOptions);
Expand All @@ -115,7 +123,7 @@ export class SmsClient {
}
};

const authPolicy = createCommunicationAccessKeyCredentialPolicy(credential as KeyCredential);
const authPolicy = createCommunicationAuthPolicy(credential);
const pipeline = createPipelineFromOptions(internalPipelineOptions, authPolicy);

this.api = new SmsApiClient(url, pipeline);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: separating the test utils from the tests looks way cleaner

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated and included a utils folder and new file called recordedClient.ts like the admin folder.

// Licensed under the MIT license.

import { record, Recorder, env } from "@azure/test-utils-recorder";
import { SendRequest, SmsClient } from "../src/smsClient";
import { assert } from "chai";
import { isNode } from "@azure/core-http";
import * as dotenv from "dotenv";
import { createCredential, recorderConfiguration } from "./utils/recordedClient";

if (isNode) {
dotenv.config();
}

describe("SmsClientWithToken [Playback/Live]", async () => {
let recorder: Recorder;

beforeEach(async function() {
recorder = record(this, recorderConfiguration);
});

afterEach(async function() {
if (!this.currentTest?.isPending()) {
await recorder.stop();
}
});

it("successfully issues a token for a user", async function() {
const credential = createCredential();

if (!credential) {
this.skip();
}

const endpoint = env.COMMUNICATION_ENDPOINT;
const fromNumber = env.AZURE_PHONE_NUMBER;
const toNumber = env.AZURE_PHONE_NUMBER;

const smsClient = new SmsClient(endpoint, credential);
const sendRequest: SendRequest = {
from: fromNumber,
to: [toNumber],
message: "test message"
};

const response = await smsClient.send(sendRequest);
assert.equal(response._response.status, 200);
}).timeout(5000);
});
35 changes: 35 additions & 0 deletions sdk/communication/communication-sms/test/utils/recordedClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DefaultAzureCredential, TokenCredential } from "@azure/identity";
import { isPlaybackMode, RecorderEnvironmentSetup } from "@azure/test-utils-recorder";

export const recorderConfiguration: RecorderEnvironmentSetup = {
replaceableVariables: {
AZURE_COMMUNICATION_LIVETEST_CONNECTION_STRING: "endpoint=https://endpoint/;accesskey=banana",
AZURE_PHONE_NUMBER: "+18005551234",
COMMUNICATION_ENDPOINT: "https://endpoint/",
AZURE_CLIENT_ID: "SomeClientId",
AZURE_CLIENT_SECRET: "SomeClientSecret",
AZURE_TENANT_ID: "SomeTenantId"
},
customizationsOnRecordings: [
(recording: string): string => recording.replace(/(https:\/\/)([^\/',]*)/, "$1endpoint"),
(recording: string): string =>
recording.replace(/"messageId"\s?:\s?"[^"]*"/g, `"messageId":"Sanitized"`)
],
queryParametersToSkip: []
};

export function createCredential(): TokenCredential | undefined {
if (isPlaybackMode()) {
return {
getToken: async (_scopes) => {
return { token: "testToken", expiresOnTimestamp: 11111 };
}
};
} else {
try {
return new DefaultAzureCredential();
} catch {
return undefined;
}
}
}