Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

Commit

Permalink
[BotBuilder-Skills][TypeScript] Update with latest changes (#1263)
Browse files Browse the repository at this point in the history
* Add dependency and temporal registry

* Add new classes

* Add auth methods

* Implement skillHttpAdapter

* Fix issue with skillDialog

* Fix TSLint issues

* Add WebSocket implementation

* Add WebSocket implementation

* Fix TSLint issues

* Latest changes

* Fix npm-shrinkwrap.json
  • Loading branch information
enzocano authored and lzc850612 committed May 4, 2019
1 parent 771d393 commit f75e103
Show file tree
Hide file tree
Showing 38 changed files with 2,462 additions and 1,370 deletions.
22 changes: 13 additions & 9 deletions lib/typescript/botbuilder-skills/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@
},
"dependencies": {
"@azure/ms-rest-js": "1.2.6",
"botbuilder": "^4.3.4",
"botbuilder-ai": "^4.3.4",
"botbuilder-azure": "^4.3.4",
"botbuilder-core": "^4.3.4",
"botbuilder-dialogs": "^4.3.4",
"botbuilder-solutions": "^4.3.4",
"botframework-config": "^4.3.4",
"botframework-connector": "^4.3.4",
"botframework-schema": "^4.3.4",
"botbuilder": "4.3.4",
"botbuilder-ai": "4.3.4",
"botbuilder-azure": "4.3.4",
"botbuilder-core": "4.3.4",
"botbuilder-dialogs": "4.3.4",
"botbuilder-solutions": "4.3.4",
"botframework-config": "4.3.4",
"botframework-connector": "4.3.4",
"botframework-schema": "4.3.4",
"i18n": "^0.8.3",
"jsonwebtoken": "^8.5.1",
"jwks-rsa": "^1.4.0",
"microsoft-bot-protocol": "^0.0.2",
"microsoft-bot-protocol-websocket": "^0.0.2",
"p-queue": "^4.0.0",
"uuid": "^3.3.2"
},
Expand Down
13 changes: 13 additions & 0 deletions lib/typescript/botbuilder-skills/src/activityHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

import { InvokeResponse, TurnContext } from 'botbuilder';
import { Activity } from 'botframework-schema';

export type BotCallbackHandler = (turnContext: TurnContext) => Promise<void>;

export interface IActivityHandler {
processActivity(activity: Activity, callback: BotCallbackHandler): Promise<InvokeResponse>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
* Licensed under the MIT License.
*/

export interface ISkillWhitelist {
skillWhiteList: string[];
export interface IAuthenticationProvider {
authenticate(authHeader: string): Promise<boolean>;
}
6 changes: 3 additions & 3 deletions lib/typescript/botbuilder-skills/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/

export * from './jwtClaimAuthProvider';
export * from './authenticationProvider';
export * from './microsoftAppCredentialsEx';
export * from './skillAuthProvider';
export * from './skillWhitelist';
export * from './msJWTAuthenticationProvider';
export * from './serviceClientCredentials';
54 changes: 0 additions & 54 deletions lib/typescript/botbuilder-skills/src/auth/jwtClaimAuthProvider.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import { MicrosoftAppCredentials } from 'botframework-connector';

export class MicrosoftAppCredentialsEx extends MicrosoftAppCredentials {
constructor(appId: string, password: string, oauthScope: string) {
constructor(appId: string, password: string, oauthScope?: string) {
super(appId, password);
this.oAuthScope = oauthScope;
if (oauthScope) {
this.oAuthScope = oauthScope;
}

this.oAuthEndpoint = 'https://login.microsoftonline.com/microsoft.com';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

// Extends verify definitions to be compatible with callback handler to resolve signingKey
declare module 'jsonwebtoken' {
export type signingKeyResolver = (headers: jwks.Headers, cb: (err: Error, signingKey: string) => void) => void;

export function verify(
token: string,
secretOrPublicKey: signingKeyResolver,
callback?: VerifyCallback
): void;
}

import { HttpOperationResponse, ServiceClient } from '@azure/ms-rest-js';
import { signingKeyResolver, verify } from 'jsonwebtoken';
import * as jwks from 'jwks-rsa';
import { IAuthenticationProvider } from './authenticationProvider';

export class MsJWTAuthenticationProvider implements IAuthenticationProvider {
private readonly openIdMetadataUrl: string = 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration';
private readonly jwtIssuer: string = 'https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0';
private readonly httpClient: ServiceClient;
private readonly appId: string;

constructor(appId: string) {
this.httpClient = new ServiceClient();
this.appId = appId;
}

public async authenticate(authHeader: string): Promise<boolean> {
try {
const token: string = authHeader.includes(' ') ? authHeader.split(' ')[1] : authHeader;

const jwksInfo: HttpOperationResponse = await this.httpClient.sendRequest({
method: 'GET',
url: this.openIdMetadataUrl
});

const jwksUri: string = <string>jwksInfo.parsedBody.jwks_uri;
const jwksClient: jwks.JwksClient = jwks({ jwksUri: jwksUri });

const getKey: signingKeyResolver = (headers: jwks.Headers, cb: (err: Error, signingKey: string) => void): void => {
jwksClient.getSigningKey(headers.kid, (err: Error, key: jwks.Jwk) => {
cb(err, key.publicKey || key.rsaPublicKey || '');
});
};

// tslint:disable-next-line:typedef
const decoder: Promise<{[key: string]: Object}> = new Promise((resolve, reject) => {
verify(token, getKey, (err: Error, decodedObj: Object) => {
if (err) { reject(err); }
const result: {[key: string]: Object} = <{[key: string]: Object}>decodedObj;
resolve(result);
});
});

const decoded: { [key: string]: Object } = await decoder;

return decoded.appid === this.appId;
} catch (error) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

import { WebResource } from '@azure/ms-rest-js';

export interface IServiceClientCredentials {
getToken(forceRefresh?: boolean): Promise<string>;
processHttpRequest(request: WebResource): Promise<void>;
}
10 changes: 0 additions & 10 deletions lib/typescript/botbuilder-skills/src/auth/skillAuthProvider.ts

This file was deleted.

8 changes: 8 additions & 0 deletions lib/typescript/botbuilder-skills/src/http/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

export * from './skillHttpAdapter';
export * from './skillHttpBotAdapter';
export * from './skillHttpTransport';
104 changes: 104 additions & 0 deletions lib/typescript/botbuilder-skills/src/http/skillHttpAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { BotFrameworkAdapter, BotFrameworkAdapterSettings, BotTelemetryClient, InvokeResponse,
Severity, TurnContext, WebRequest, WebResponse } from 'botbuilder';
import { TelemetryExtensions } from 'botbuilder-solutions';
import { Activity } from 'botframework-schema';
import { IActivityHandler } from '../activityHandler';
import { IAuthenticationProvider } from '../auth';
import { SkillHttpBotAdapter } from './skillHttpBotAdapter';

/**
* This adapter is responsible for accepting a bot-to-bot call over http transport.
* It'll perform the following tasks:
* 1. Authentication.
* 2. Call SkillHttpBotAdapter to process the incoming activity.
*/
export class SkillHttpAdapter extends BotFrameworkAdapter {
private readonly authHeaderName: string = 'Authorization';

private readonly botAdapter: IActivityHandler;
private readonly authenticationProvider?: IAuthenticationProvider;
private readonly telemetryClient?: BotTelemetryClient;

constructor(
botAdapter: SkillHttpBotAdapter,
authenticationProvider?: IAuthenticationProvider,
telemetryClient?: BotTelemetryClient,
config?: Partial<BotFrameworkAdapterSettings>
) {
super(config);
this.botAdapter = botAdapter;
this.authenticationProvider = authenticationProvider;
this.telemetryClient = telemetryClient;
}

// tslint:disable-next-line:no-any
public async processActivity(req: WebRequest, res: WebResponse, logic: (context: TurnContext) => Promise<any>): Promise<void> {
if (this.authenticationProvider) {
// grab the auth header from the inbound http request
const headers: { [header: string]: string | string[] | undefined } = req.headers;
const authHeader: string = <string> headers[this.authHeaderName];
const authenticated: boolean = await this.authenticationProvider.authenticate(authHeader);

if (!authenticated) {
res.status(401);
res.end();

return;
}
}

// deserialize the incoming Activity
const activity: Activity = await parseRequest(req);

if (this.telemetryClient) {
const message: string = `SkillHttpAdapter: Processing incoming activity. Activity id: ${activity.id}`;
TelemetryExtensions.trackTraceEx(this.telemetryClient, message, Severity.Information, activity);
}

// process the inbound activity with the bot
const invokeResponse: InvokeResponse = await this.botAdapter.processActivity(activity, logic);

// write the response, potentially serializing the InvokeResponse
res.status(invokeResponse.status);
if (invokeResponse.body) {
res.send(invokeResponse.body);
}

res.end();
}
}

function parseRequest(req: WebRequest): Promise<Activity> {
// tslint:disable-next-line:typedef
return new Promise((resolve, reject): void => {
function returnActivity(activity: Activity): void {
if (typeof activity !== 'object') { throw new Error(`BotFrameworkAdapter.parseRequest(): invalid request body.`); }
if (typeof activity.type !== 'string') { throw new Error(`BotFrameworkAdapter.parseRequest(): missing activity type.`); }
if (typeof activity.timestamp === 'string') { activity.timestamp = new Date(activity.timestamp); }
if (typeof activity.localTimestamp === 'string') { activity.localTimestamp = new Date(activity.localTimestamp); }
if (typeof activity.expiration === 'string') { activity.expiration = new Date(activity.expiration); }
resolve(activity);
}

if (req.body) {
try {
returnActivity(req.body);
} catch (err) {
reject(err);
}
} else {
let requestData: string = '';
req.on('data', (chunk: string) => {
requestData += chunk;
});
req.on('end', () => {
try {
req.body = JSON.parse(requestData);
returnActivity(req.body);
} catch (err) {
reject(err);
}
});
}
});
}
Loading

0 comments on commit f75e103

Please sign in to comment.