UNPKG

botframework-connector

Version:

Bot Connector is autorest generated connector client.

385 lines 25.9 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ParameterizedBotFrameworkAuthentication = void 0; const botframework_schema_1 = require("botframework-schema"); const authenticationConstants_1 = require("./authenticationConstants"); const authenticationError_1 = require("./authenticationError"); const botFrameworkAuthentication_1 = require("./botFrameworkAuthentication"); const connectorFactoryImpl_1 = require("./connectorFactoryImpl"); const botFrameworkClientImpl_1 = require("./botFrameworkClientImpl"); const claimsIdentity_1 = require("./claimsIdentity"); const emulatorValidation_1 = require("./emulatorValidation"); const jwtTokenExtractor_1 = require("./jwtTokenExtractor"); const jwtTokenValidation_1 = require("./jwtTokenValidation"); const skillValidation_1 = require("./skillValidation"); const tokenValidationParameters_1 = require("./tokenValidationParameters"); const userTokenClientImpl_1 = require("./userTokenClientImpl"); const aseChannelValidation_1 = require("./aseChannelValidation"); function getAppId(claimsIdentity) { var _a, _b; // For requests from channel App Id is in Audience claim of JWT token. For emulator it is in AppId claim. For // unauthenticated requests we have anonymous claimsIdentity provided auth is disabled. // For Activities coming from Emulator AppId claim contains the Bot's AAD AppId. return ((_b = (_a = claimsIdentity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AudienceClaim)) !== null && _a !== void 0 ? _a : claimsIdentity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AppIdClaim)) !== null && _b !== void 0 ? _b : undefined); } /** * @internal * Parameterized [BotFrameworkAuthentication](xref:botframework-connector.BotFrameworkAuthentication) used to authenticate Bot Framework Protocol network calls within this environment. */ class ParameterizedBotFrameworkAuthentication extends botFrameworkAuthentication_1.BotFrameworkAuthentication { /** * @param validateAuthority The validate authority value to use. * @param toChannelFromBotLoginUrl The to Channel from bot login url. * @param toChannelFromBotOAuthScope The to Channel from bot oauth scope. * @param toBotFromChannelTokenIssuer The to bot from Channel Token Issuer. * @param oAuthUrl The OAuth url. * @param toBotFromChannelOpenIdMetadataUrl The to bot from Channel Open Id Metadata url. * @param toBotFromEmulatorOpenIdMetadataUrl The to bot from Emulator Open Id Metadata url. * @param callerId The callerId set on an authenticated [Activities](xref:botframework-schema.Activity). * @param credentialsFactory The [ServiceClientCredentialsFactory](xref:botframework-connector.ServiceClientCredentialsFactory) to use to create credentials. * @param authConfiguration The [AuthenticationConfiguration](xref:botframework-connector.AuthenticationConfiguration) to use. * @param botFrameworkClientFetch The fetch to use in BotFrameworkClient. * @param connectorClientOptions The [ConnectorClientOptions](xref:botframework-connector.ConnectorClientOptions) to use when creating ConnectorClients. */ constructor(validateAuthority, toChannelFromBotLoginUrl, toChannelFromBotOAuthScope, toBotFromChannelTokenIssuer, oAuthUrl, toBotFromChannelOpenIdMetadataUrl, toBotFromEmulatorOpenIdMetadataUrl, callerId, credentialsFactory, authConfiguration, botFrameworkClientFetch, connectorClientOptions = {}) { super(); this.validateAuthority = validateAuthority; this.toChannelFromBotLoginUrl = toChannelFromBotLoginUrl; this.toChannelFromBotOAuthScope = toChannelFromBotOAuthScope; this.toBotFromChannelTokenIssuer = toBotFromChannelTokenIssuer; this.oAuthUrl = oAuthUrl; this.toBotFromChannelOpenIdMetadataUrl = toBotFromChannelOpenIdMetadataUrl; this.toBotFromEmulatorOpenIdMetadataUrl = toBotFromEmulatorOpenIdMetadataUrl; this.callerId = callerId; this.credentialsFactory = credentialsFactory; this.authConfiguration = authConfiguration; this.botFrameworkClientFetch = botFrameworkClientFetch; this.connectorClientOptions = connectorClientOptions; } /** * Gets the originating audience from Bot OAuth scope. * * @returns The originating audience. */ getOriginatingAudience() { return this.toChannelFromBotOAuthScope; } /** * @param authHeader The http auth header received in the skill request. * @returns The identity validation result. */ authenticateChannelRequest(authHeader) { return __awaiter(this, void 0, void 0, function* () { if (!authHeader.trim()) { const isAuthDisabled = yield this.credentialsFactory.isAuthenticationDisabled(); if (!isAuthDisabled) { throw new authenticationError_1.AuthenticationError('Unauthorized Access. Request is not authorized', botframework_schema_1.StatusCodes.UNAUTHORIZED); } // In the scenario where auth is disabled, we still want to have the isAuthenticated flag set in the // ClaimsIdentity. To do this requires adding in an empty claim. Since ChannelServiceHandler calls are // always a skill callback call, we set the skill claim too. return skillValidation_1.SkillValidation.createAnonymousSkillClaim(); } return this.JwtTokenValidation_validateAuthHeader(authHeader, 'unknown', null); }); } /** * Validate Bot Framework Protocol requests. * * @param activity The inbound Activity. * @param authHeader The http auth header received in the skill request. * @returns Promise with AuthenticateRequestResult. */ authenticateRequest(activity, authHeader) { return __awaiter(this, void 0, void 0, function* () { const claimsIdentity = yield this.JwtTokenValidation_authenticateRequest(activity, authHeader); const outboundAudience = skillValidation_1.SkillValidation.isSkillClaim(claimsIdentity.claims) ? jwtTokenValidation_1.JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims) : this.toChannelFromBotOAuthScope; const callerId = yield this.generateCallerId(this.credentialsFactory, claimsIdentity, this.callerId); const connectorFactory = new connectorFactoryImpl_1.ConnectorFactoryImpl(getAppId(claimsIdentity), this.toChannelFromBotOAuthScope, this.toChannelFromBotLoginUrl, this.validateAuthority, this.credentialsFactory, this.connectorClientOptions); return { audience: outboundAudience, callerId, claimsIdentity, connectorFactory, }; }); } /** * Validate Bot Framework Protocol requests. * * @param authHeader The http auth header received in the skill request. * @param channelIdHeader The channel Id HTTP header. * @returns Promise with AuthenticateRequestResult. */ authenticateStreamingRequest(authHeader, channelIdHeader) { return __awaiter(this, void 0, void 0, function* () { if (!(channelIdHeader === null || channelIdHeader === void 0 ? void 0 : channelIdHeader.trim()) && !(yield this.credentialsFactory.isAuthenticationDisabled())) { throw new authenticationError_1.AuthenticationError("'channelIdHeader' required.", botframework_schema_1.StatusCodes.UNAUTHORIZED); } const claimsIdentity = yield this.JwtTokenValidation_validateAuthHeader(authHeader, channelIdHeader, null); const outboundAudience = skillValidation_1.SkillValidation.isSkillClaim(claimsIdentity.claims) ? jwtTokenValidation_1.JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims) : this.toChannelFromBotOAuthScope; const callerId = yield this.generateCallerId(this.credentialsFactory, claimsIdentity, this.callerId); return { audience: outboundAudience, callerId, claimsIdentity }; }); } /** * Creates the appropriate UserTokenClient instance. * * @param claimsIdentity The inbound Activity's ClaimsIdentity. * @returns Promise with UserTokenClient instance. */ createUserTokenClient(claimsIdentity) { return __awaiter(this, void 0, void 0, function* () { const appId = getAppId(claimsIdentity); const credentials = yield this.credentialsFactory.createCredentials(appId, this.toChannelFromBotOAuthScope, this.toChannelFromBotLoginUrl, this.validateAuthority); return new userTokenClientImpl_1.UserTokenClientImpl(appId, credentials, this.oAuthUrl, this.connectorClientOptions); }); } /** * Creates a ConnectorFactory that can be used to create ConnectorClients that can use credentials from this particular Cloud Environment. * * @param claimsIdentity The inbound Activity's ClaimsIdentity. * @returns A ConnectorFactory. */ createConnectorFactory(claimsIdentity) { return new connectorFactoryImpl_1.ConnectorFactoryImpl(getAppId(claimsIdentity), this.toChannelFromBotOAuthScope, this.toChannelFromBotLoginUrl, this.validateAuthority, this.credentialsFactory, this.connectorClientOptions); } /** * Creates a BotFrameworkClient used for calling Skills. * * @returns A BotFrameworkClient instance to call Skills. */ createBotFrameworkClient() { return new botFrameworkClientImpl_1.BotFrameworkClientImpl(this.credentialsFactory, this.toChannelFromBotLoginUrl, this.botFrameworkClientFetch, this.connectorClientOptions); } JwtTokenValidation_authenticateRequest(activity, authHeader) { var _a; return __awaiter(this, void 0, void 0, function* () { if (!authHeader.trim()) { const isAuthDisabled = yield this.credentialsFactory.isAuthenticationDisabled(); if (!isAuthDisabled) { throw new authenticationError_1.AuthenticationError('Unauthorized Access. Request is not authorized', botframework_schema_1.StatusCodes.UNAUTHORIZED); } // Check if the activity is for a skill call and is coming from the Emulator. if (activity.channelId === botframework_schema_1.Channels.Emulator && ((_a = activity.recipient) === null || _a === void 0 ? void 0 : _a.role) === botframework_schema_1.RoleTypes.Skill) { return skillValidation_1.SkillValidation.createAnonymousSkillClaim(); } // In the scenario where Auth is disabled, we still want to have the // IsAuthenticated flag set in the ClaimsIdentity. To do this requires // adding in an empty claim. return new claimsIdentity_1.ClaimsIdentity([], authenticationConstants_1.AuthenticationConstants.AnonymousAuthType); } const claimsIdentity = yield this.JwtTokenValidation_validateAuthHeader(authHeader, activity.channelId, activity.serviceUrl); return claimsIdentity; }); } JwtTokenValidation_validateAuthHeader(authHeader, channelId, serviceUrl = '') { return __awaiter(this, void 0, void 0, function* () { const identity = yield this.JwtTokenValidation_authenticateToken(authHeader, channelId, serviceUrl); yield this.JwtTokenValidation_validateClaims(identity.claims); return identity; }); } JwtTokenValidation_validateClaims(claims = []) { return __awaiter(this, void 0, void 0, function* () { if (this.authConfiguration.validateClaims) { // Call the validation method if defined (it should throw an exception if the validation fails) yield this.authConfiguration.validateClaims(claims); } else if (skillValidation_1.SkillValidation.isSkillClaim(claims)) { // Skill claims must be validated using AuthenticationConfiguration validateClaims throw new authenticationError_1.AuthenticationError('Unauthorized Access. Request is not authorized. Skill Claims require validation.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } }); } JwtTokenValidation_authenticateToken(authHeader, channelId, serviceUrl) { return __awaiter(this, void 0, void 0, function* () { if (aseChannelValidation_1.AseChannelValidation.isTokenFromAseChannel(channelId)) { return aseChannelValidation_1.AseChannelValidation.authenticateAseChannelToken(authHeader); } if (skillValidation_1.SkillValidation.isSkillToken(authHeader)) { return this.SkillValidation_authenticateChannelToken(authHeader, channelId); } if (emulatorValidation_1.EmulatorValidation.isTokenFromEmulator(authHeader)) { return this.EmulatorValidation_authenticateEmulatorToken(authHeader, channelId); } // Handle requests from BotFramework Channels return this.ChannelValidation_authenticateChannelToken(authHeader, serviceUrl, channelId); }); } SkillValidation_authenticateChannelToken(authHeader, channelId) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { // Add allowed token issuers from configuration. const verifyOptions = Object.assign(Object.assign({}, tokenValidationParameters_1.ToBotFromBotOrEmulatorTokenValidationParameters), { issuer: [ ...tokenValidationParameters_1.ToBotFromBotOrEmulatorTokenValidationParameters.issuer, ...((_a = this.authConfiguration.validTokenIssuers) !== null && _a !== void 0 ? _a : []), ] }); const tokenExtractor = new jwtTokenExtractor_1.JwtTokenExtractor(verifyOptions, this.toBotFromEmulatorOpenIdMetadataUrl, authenticationConstants_1.AuthenticationConstants.AllowedSigningAlgorithms, (_b = this.connectorClientOptions) === null || _b === void 0 ? void 0 : _b.proxySettings); const parts = authHeader.split(' '); const identity = yield tokenExtractor.getIdentity(parts[0], parts[1], channelId, this.authConfiguration.requiredEndorsements); yield this.SkillValidation_ValidateIdentity(identity); return identity; }); } SkillValidation_ValidateIdentity(identity) { return __awaiter(this, void 0, void 0, function* () { if (!identity) { // No valid identity. Not Authorized. throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Invalid identity', botframework_schema_1.StatusCodes.UNAUTHORIZED); } if (!identity.isAuthenticated) { // The token is in some way invalid. Not Authorized. throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Token not authenticated', botframework_schema_1.StatusCodes.UNAUTHORIZED); } const versionClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.VersionClaim); if (!versionClaim) { // No version claim throw new authenticationError_1.AuthenticationError(`SkillValidation.validateIdentity(): '${authenticationConstants_1.AuthenticationConstants.VersionClaim}' claim is required on skill Tokens.`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } // Look for the "aud" claim, but only if issued from the Bot Framework const audienceClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AudienceClaim); if (!audienceClaim) { // Claim is not present or doesn't have a value. Not Authorized. throw new authenticationError_1.AuthenticationError(`SkillValidation.validateIdentity(): '${authenticationConstants_1.AuthenticationConstants.AudienceClaim}' claim is required on skill Tokens.`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } if (!(yield this.credentialsFactory.isValidAppId(audienceClaim))) { // The AppId is not valid. Not Authorized. throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Invalid audience.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } const appId = jwtTokenValidation_1.JwtTokenValidation.getAppIdFromClaims(identity.claims); if (!appId) { // Invalid appId throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Invalid appId.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } }); } EmulatorValidation_authenticateEmulatorToken(authHeader, channelId) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { // Add allowed token issuers from configuration. const verifyOptions = Object.assign(Object.assign({}, tokenValidationParameters_1.ToBotFromBotOrEmulatorTokenValidationParameters), { issuer: [ ...tokenValidationParameters_1.ToBotFromBotOrEmulatorTokenValidationParameters.issuer, ...((_a = this.authConfiguration.validTokenIssuers) !== null && _a !== void 0 ? _a : []), ] }); const tokenExtractor = new jwtTokenExtractor_1.JwtTokenExtractor(verifyOptions, this.toBotFromEmulatorOpenIdMetadataUrl, authenticationConstants_1.AuthenticationConstants.AllowedSigningAlgorithms, (_b = this.connectorClientOptions) === null || _b === void 0 ? void 0 : _b.proxySettings); const identity = yield tokenExtractor.getIdentityFromAuthHeader(authHeader, channelId, this.authConfiguration.requiredEndorsements); if (!identity) { // No valid identity. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. No valid identity.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } if (!identity.isAuthenticated) { // The token is in some way invalid. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. Is not authenticated', botframework_schema_1.StatusCodes.UNAUTHORIZED); } // Now check that the AppID in the claimset matches // what we're looking for. Note that in a multi-tenant bot, this value // comes from developer code that may be reaching out to a service, hence the // Async validation. const versionClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.VersionClaim); if (versionClaim === null) { throw new authenticationError_1.AuthenticationError('Unauthorized. "ver" claim is required on Emulator Tokens.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } let appId = ''; // The Emulator, depending on Version, sends the AppId via either the // appid claim (Version 1) or the Authorized Party claim (Version 2). if (!versionClaim || versionClaim === '1.0') { // either no Version or a version of "1.0" means we should look for // the claim in the "appid" claim. const appIdClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AppIdClaim); if (!appIdClaim) { // No claim around AppID. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. "appid" claim is required on Emulator Token version "1.0".', botframework_schema_1.StatusCodes.UNAUTHORIZED); } appId = appIdClaim; } else if (versionClaim === '2.0') { // Emulator, "2.0" puts the AppId in the "azp" claim. const appZClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AuthorizedParty); if (!appZClaim) { // No claim around AppID. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. "azp" claim is required on Emulator Token version "2.0".', botframework_schema_1.StatusCodes.UNAUTHORIZED); } appId = appZClaim; } else { // Unknown Version. Not Authorized. throw new authenticationError_1.AuthenticationError(`Unauthorized. Unknown Emulator Token version "${versionClaim}".`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } if (!(yield this.credentialsFactory.isValidAppId(appId))) { throw new authenticationError_1.AuthenticationError(`Unauthorized. Invalid AppId passed on token: ${appId}`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } return identity; }); } ChannelValidation_authenticateChannelToken(authHeader, serviceUrl, channelId) { var _a; return __awaiter(this, void 0, void 0, function* () { const tokenValidationParameters = this.ChannelValidation_GetTokenValidationParameters(); const tokenExtractor = new jwtTokenExtractor_1.JwtTokenExtractor(tokenValidationParameters, this.toBotFromChannelOpenIdMetadataUrl, authenticationConstants_1.AuthenticationConstants.AllowedSigningAlgorithms, (_a = this.connectorClientOptions) === null || _a === void 0 ? void 0 : _a.proxySettings); const identity = yield tokenExtractor.getIdentityFromAuthHeader(authHeader, channelId, this.authConfiguration.requiredEndorsements); return this.governmentChannelValidation_ValidateIdentity(identity, serviceUrl); }); } ChannelValidation_GetTokenValidationParameters() { return { issuer: [this.toBotFromChannelTokenIssuer], audience: undefined, clockTolerance: 5 * 60, ignoreExpiration: false, }; } governmentChannelValidation_ValidateIdentity(identity, serviceUrl) { return __awaiter(this, void 0, void 0, function* () { if (!identity) { // No valid identity. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. No valid identity.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } if (!identity.isAuthenticated) { // The token is in some way invalid. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. Is not authenticated', botframework_schema_1.StatusCodes.UNAUTHORIZED); } // Now check that the AppID in the claimset matches // what we're looking for. Note that in a multi-tenant bot, this value // comes from developer code that may be reaching out to a service, hence the // Async validation. // Look for the "aud" claim, but only if issued from the Bot Framework if (identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.IssuerClaim) !== this.toBotFromChannelTokenIssuer) { // The relevant Audiance Claim MUST be present. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. Issuer Claim MUST be present.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } // The AppId from the claim in the token must match the AppId specified by the developer. // In this case, the token is destined for the app, so we find the app ID in the audience claim. const audClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AudienceClaim); if (!(yield this.credentialsFactory.isValidAppId(audClaim || ''))) { // The AppId is not valid or not present. Not Authorized. throw new authenticationError_1.AuthenticationError(`Unauthorized. Invalid AppId passed on token: ${audClaim}`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } if (serviceUrl) { const serviceUrlClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.ServiceUrlClaim); if (serviceUrlClaim !== serviceUrl) { // Claim must match. Not Authorized. throw new authenticationError_1.AuthenticationError('Unauthorized. ServiceUrl claim do not match.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } } return identity; }); } } exports.ParameterizedBotFrameworkAuthentication = ParameterizedBotFrameworkAuthentication; //# sourceMappingURL=parameterizedBotFrameworkAuthentication.js.map