diff --git a/sdk/communication/communication-sms/CHANGELOG.md b/sdk/communication/communication-sms/CHANGELOG.md index 879480bd153f..fd458ea59bf2 100644 --- a/sdk/communication/communication-sms/CHANGELOG.md +++ b/sdk/communication/communication-sms/CHANGELOG.md @@ -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) diff --git a/sdk/communication/communication-sms/README.md b/sdk/communication/communication-sms/README.md index 97854f64f694..cd51cb456bb4 100644 --- a/sdk/communication/communication-sms/README.md +++ b/sdk/communication/communication-sms/README.md @@ -46,6 +46,16 @@ const connectionString = `endpoint=;accessKey=`; 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("", credential); +``` + ## Sending SMS ```typescript diff --git a/sdk/communication/communication-sms/karma.conf.js b/sdk/communication/communication-sms/karma.conf.js index 0077d4527ea8..2b83171a27a1 100644 --- a/sdk/communication/communication-sms/karma.conf.js +++ b/sdk/communication/communication-sms/karma.conf.js @@ -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 diff --git a/sdk/communication/communication-sms/package.json b/sdk/communication/communication-sms/package.json index fd1179c0557a..68a280a9c275 100644 --- a/sdk/communication/communication-sms/package.json +++ b/sdk/communication/communication-sms/package.json @@ -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", diff --git a/sdk/communication/communication-sms/recordings/browsers/smsclientwithtoken_playbacklive/recording_successfully_issues_a_token_for_a_user.json b/sdk/communication/communication-sms/recordings/browsers/smsclientwithtoken_playbacklive/recording_successfully_issues_a_token_for_a_user.json new file mode 100644 index 000000000000..b61e924072eb --- /dev/null +++ b/sdk/communication/communication-sms/recordings/browsers/smsclientwithtoken_playbacklive/recording_successfully_issues_a_token_for_a_user.json @@ -0,0 +1,22 @@ +{ + "recordings": [ + { + "method": "POST", + "url": "https://endpoint/sms", + "query": { + "api-version": "2020-07-20-preview1" + }, + "requestBody": "{\"from\":\"+18005551234\",\"to\":[\"+18005551234\"],\"message\":\"test message\",\"sendSmsOptions\":{}}", + "status": 200, + "response": "{\"messageId\":\"Sanitized\"}", + "responseHeaders": { + "content-type": "application/json; charset=utf-8" + } + } + ], + "uniqueTestInfo": { + "uniqueName": {}, + "newDate": {} + }, + "hash": "6224dd5c700595ada28152d48052a1a6" +} diff --git a/sdk/communication/communication-sms/recordings/node/smsclient/recording_sends_a_sms_message.js b/sdk/communication/communication-sms/recordings/node/smsclient/recording_sends_a_sms_message.js index 4d905d97026b..9bb2cbc260bd 100644 --- a/sdk/communication/communication-sms/recordings/node/smsclient/recording_sends_a_sms_message.js +++ b/sdk/communication/communication-sms/recordings/node/smsclient/recording_sends_a_sms_message.js @@ -1,6 +1,6 @@ let nock = require('nock'); -module.exports.hash = "cf38d97837dc5da79554291c769ff666"; +module.exports.hash = "e9ca3e20f65ddb447cc01a00b7a442a1"; module.exports.testInfo = {"uniqueName":{},"newDate":{}} @@ -12,12 +12,14 @@ nock('https://endpoint', {"encodedQueryParams":true}) 'chunked', 'Content-Type', 'application/json; charset=utf-8', + 'Request-Context', + 'appId=', 'MS-CV', - 'RCYfn94WuEexo/lBvdpMjA.0', + 'oLB+DpQkGkCgc7OqurGsyw.0', 'X-Processing-Time', - '739ms', + '482ms', 'X-Azure-Ref', - '0XTraXwAAAADA+2QKjs9VSYiiJ/IjcS/3QkVSMzBFREdFMDQyMAA5ZmM3YjUxOS1hOGNjLTRmODktOTM1ZS1jOTE0OGFlMDllODE=', + '0NhsHYAAAAADQ+P8dHsTtQbtueR1XFl5pRVdSMzBFREdFMDUyMQA5ZmM3YjUxOS1hOGNjLTRmODktOTM1ZS1jOTE0OGFlMDllODE=', 'Date', - 'Wed, 16 Dec 2020 16:48:29 GMT' + 'Tue, 19 Jan 2021 17:47:34 GMT' ]); diff --git a/sdk/communication/communication-sms/recordings/node/smsclientwithtoken_playbacklive/recording_successfully_issues_a_token_for_a_user.js b/sdk/communication/communication-sms/recordings/node/smsclientwithtoken_playbacklive/recording_successfully_issues_a_token_for_a_user.js new file mode 100644 index 000000000000..017558c08c3a --- /dev/null +++ b/sdk/communication/communication-sms/recordings/node/smsclientwithtoken_playbacklive/recording_successfully_issues_a_token_for_a_user.js @@ -0,0 +1,58 @@ +let nock = require('nock'); + +module.exports.hash = "60565f8dbf8144b9e086c949385f4c3e"; + +module.exports.testInfo = {"uniqueName":{},"newDate":{}} + +nock('https://endpoint', {"encodedQueryParams":true}) + .post('/SomeTenantId/oauth2/v2.0/token', "response_type=token&grant_type=client_credentials&client_id=SomeClientId&client_secret=SomeClientSecret&scope=https%3A%2F%2Fcommunication.azure.com%2F%2F.default") + .reply(200, {"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiJodHRwczovL2NvbW11bmljYXRpb24uYXp1cmUuY29tLyIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0Ny8iLCJpYXQiOjE2MTEwNzgxNTUsIm5iZiI6MTYxMTA3ODE1NSwiZXhwIjoxNjExMTY0ODU1LCJhaW8iOiJFMkpnWU5DVlpRbVc0UDFZWjF0dTg2N1ZhRVlZQUE9PSIsImFwcGlkIjoiNmM4MTgxYzctOTFhNi00ZTJlLTg0ODAtZDU0MDIxYWM0YzRiIiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3LyIsIm9pZCI6IjNlMGM3MTRmLTk3YWMtNGQ2My1hZWFmLTE0YmFhNWUwNjRjYiIsInJoIjoiMC5BUm9BdjRqNWN2R0dyMEdScXkxODBCSGJSOGVCZ1d5bWtTNU9oSURWUUNHc1RFc2FBQUEuIiwic3ViIjoiM2UwYzcxNGYtOTdhYy00ZDYzLWFlYWYtMTRiYWE1ZTA2NGNiIiwidGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3IiwidXRpIjoiaWlLZll0UmJwVVNpdXRla1VRUDNBQSIsInZlciI6IjEuMCJ9.PvG5oxPxhGau80ci-edmA7zaiK6JZDQifqmkO-0__LQ5_-KWAU3I1U8pnUvgKz-J9oEMV6ZTRECncwv4EBa6gEM7vayhG7nAa9pOvPNPKIR_TdlCWeOvZzrQa8ysUKaak4M6emy8hV5eRtNLERTbDdGbFueO7lr1APSb0ybLaVHSy_2uLdVD9XEPNryqSWPKHH9S8xPPRdOWwXongKGUsRPwgbVoCNq7pPndnp7T1NIBIkb9bo_n3C_h5-M_dvGxtX6CceS1qQwV2z-2NV2QnF9Al7zoLe7LSODEwB7W2U4HhPPmIiJXAJs3lUpZJGlYhCkbKCmM-qXuEbqIkT0hIg"}, [ + 'Cache-Control', + 'no-store, no-cache', + 'Pragma', + 'no-cache', + 'Content-Length', + '1327', + 'Content-Type', + 'application/json; charset=utf-8', + 'Expires', + '-1', + 'Strict-Transport-Security', + 'max-age=31536000; includeSubDomains', + 'X-Content-Type-Options', + 'nosniff', + 'P3P', + 'CP="DSP CUR OTPi IND OTRi ONL FIN"', + 'x-ms-request-id', + '629f228a-5bd4-44a5-a2ba-d7a45103f700', + 'x-ms-ests-server', + '2.1.11397.13 - SCUS ProdSlices', + 'Set-Cookie', + 'fpc=Aj_-729LM0JIjWYKy_lo-FhWyo4SAQAAADYSmdcOAAAA; expires=Thu, 18-Feb-2021 17:47:35 GMT; path=/; secure; HttpOnly; SameSite=None', + 'Set-Cookie', + 'x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly', + 'Set-Cookie', + 'stsservicecookie=estsfd; path=/; secure; samesite=none; httponly', + 'Date', + 'Tue, 19 Jan 2021 17:47:34 GMT' +]); + +nock('https://endpoint', {"encodedQueryParams":true}) + .post('/sms', {"from":"+18005551234","to":["+18005551234"],"message":"test message","sendSmsOptions":{}}) + .query(true) + .reply(200, {"messageId":"Sanitized"}, [ + 'Transfer-Encoding', + 'chunked', + 'Content-Type', + 'application/json; charset=utf-8', + 'Request-Context', + 'appId=', + 'MS-CV', + 'j84YORT5f0+sj7XSC2NdIw.0', + 'X-Processing-Time', + '300ms', + 'X-Azure-Ref', + '0NxsHYAAAAADYX1clhOIrT57fR6Ej4vuARVdSMzBFREdFMDUyMQA5ZmM3YjUxOS1hOGNjLTRmODktOTM1ZS1jOTE0OGFlMDllODE=', + 'Date', + 'Tue, 19 Jan 2021 17:47:35 GMT' +]); diff --git a/sdk/communication/communication-sms/review/communication-sms.api.md b/sdk/communication/communication-sms/review/communication-sms.api.md index a9a21013f372..798e7920e8b4 100644 --- a/sdk/communication/communication-sms/review/communication-sms.api.md +++ b/sdk/communication/communication-sms/review/communication-sms.api.md @@ -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 { @@ -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; } diff --git a/sdk/communication/communication-sms/src/smsClient.ts b/sdk/communication/communication-sms/src/smsClient.ts index 0a1177880e52..81e39235acd7 100644 --- a/sdk/communication/communication-sms/src/smsClient.ts +++ b/sdk/communication/communication-sms/src/smsClient.ts @@ -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, @@ -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); @@ -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); diff --git a/sdk/communication/communication-sms/test/smsClientWithToken.spec.ts b/sdk/communication/communication-sms/test/smsClientWithToken.spec.ts new file mode 100644 index 000000000000..820d2a745f47 --- /dev/null +++ b/sdk/communication/communication-sms/test/smsClientWithToken.spec.ts @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// 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); +}); diff --git a/sdk/communication/communication-sms/test/utils/recordedClient.ts b/sdk/communication/communication-sms/test/utils/recordedClient.ts new file mode 100644 index 000000000000..7d8898c381b8 --- /dev/null +++ b/sdk/communication/communication-sms/test/utils/recordedClient.ts @@ -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; + } + } +}