botframework-connector
Version:
Bot Connector is autorest generated connector client.
385 lines • 25.9 kB
JavaScript
"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