diff --git a/libraries/botbuilder-core/tests/transcriptUtilities.js b/libraries/botbuilder-core/tests/transcriptUtilities.js index 2ac8da67d4..b8b5caf114 100644 --- a/libraries/botbuilder-core/tests/transcriptUtilities.js +++ b/libraries/botbuilder-core/tests/transcriptUtilities.js @@ -172,9 +172,13 @@ const decompressZip = (inputPath, outputPath, callback) => { .pipe(unzip.Parse()) .on('entry', (entry) => { if (entry.type === 'File' && entry.path.includes(zipTranscriptsRelativePath)) { - const fileExtractPath = path.join(outputPath, entry.path); - ensureDirectoryExists(fileExtractPath); - entry.pipe(fs.createWriteStream(fileExtractPath)).on('error', console.log); + if (entry.path.indexOf('..') == -1) { + const fileExtractPath = path.join(outputPath, entry.path); + ensureDirectoryExists(fileExtractPath); + entry.pipe(fs.createWriteStream(fileExtractPath)).on('error', console.log); + } else { + console.warn(`Skipping file ${entry.path} as it contains '..' in its path.`); + } } else { entry.autodrain(); } diff --git a/libraries/botbuilder/src/channelServiceHandler.ts b/libraries/botbuilder/src/channelServiceHandler.ts index d58d6bc9f4..5ff7c672b5 100644 --- a/libraries/botbuilder/src/channelServiceHandler.ts +++ b/libraries/botbuilder/src/channelServiceHandler.ts @@ -43,26 +43,27 @@ export class ChannelServiceHandler extends ChannelServiceHandlerBase { } protected async authenticate(authHeader: string): Promise { - if (!authHeader) { - const isAuthDisabled = await this.credentialProvider.isAuthenticationDisabled(); - if (!isAuthDisabled) { - throw new StatusCodeError(StatusCodes.UNAUTHORIZED); - } + const isAuthDisabled = await this.credentialProvider.isAuthenticationDisabled(); + if (isAuthDisabled) { // 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.createAnonymousSkillClaim(); - } + } else { + if (!authHeader) { + throw new StatusCodeError(StatusCodes.UNAUTHORIZED); + } - return JwtTokenValidation.validateAuthHeader( - authHeader, - this.credentialProvider, - this.channelService, - 'unknown', - undefined, - this.authConfig, - ); + return JwtTokenValidation.validateAuthHeader( + authHeader, + this.credentialProvider, + this.channelService, + 'unknown', + undefined, + this.authConfig, + ); + } } } diff --git a/libraries/botbuilder/tests/channelServiceHandler.test.js b/libraries/botbuilder/tests/channelServiceHandler.test.js index a6ae97d439..952152b6fe 100644 --- a/libraries/botbuilder/tests/channelServiceHandler.test.js +++ b/libraries/botbuilder/tests/channelServiceHandler.test.js @@ -14,7 +14,7 @@ const { const AUTH_HEADER = 'Bearer HelloWorld'; const AUTH_CONFIG = new AuthenticationConfiguration(); -const CREDENTIALS = new SimpleCredentialProvider('', ''); +const CREDENTIALS = new SimpleCredentialProvider('appId', 'appSecret'); const ACTIVITY = { id: 'testId', type: ActivityTypes.Message }; class NoAuthHandler extends ChannelServiceHandler { diff --git a/libraries/botbuilder/tests/cloudAdapter.test.js b/libraries/botbuilder/tests/cloudAdapter.test.js index 5e27b7958b..b90c366f1c 100644 --- a/libraries/botbuilder/tests/cloudAdapter.test.js +++ b/libraries/botbuilder/tests/cloudAdapter.test.js @@ -213,8 +213,8 @@ describe('CloudAdapter', function () { const claimsValidators = allowedCallersClaimsValidator(['*']); const authConfig = new AuthenticationConfiguration([], claimsValidators, validTokenIssuers); const credentialsFactory = new ConfigurationServiceClientCredentialFactory({ - MicrosoftAppId: '', - MicrosoftAppPassword: '', + MicrosoftAppId: 'app-id', + MicrosoftAppPassword: 'app-password', MicrosoftAppType: '', MicrosoftAppTenantId: '', }); diff --git a/libraries/botframework-connector/src/auth/jwtTokenValidation.ts b/libraries/botframework-connector/src/auth/jwtTokenValidation.ts index 3b58821dc8..3d8d991cb0 100644 --- a/libraries/botframework-connector/src/auth/jwtTokenValidation.ts +++ b/libraries/botframework-connector/src/auth/jwtTokenValidation.ts @@ -47,16 +47,9 @@ export namespace JwtTokenValidation { authConfig = new AuthenticationConfiguration(); } - // eslint-disable-next-line prettier/prettier - if (!authHeader.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty header as part of that. - const isAuthDisabled = await credentials.isAuthenticationDisabled(); - if (!isAuthDisabled) { - throw new AuthenticationError( - 'Unauthorized Access. Request is not authorized', - StatusCodes.UNAUTHORIZED, - ); - } + const isAuthDisabled = await credentials.isAuthenticationDisabled(); + if (isAuthDisabled) { // Check if the activity is for a skill call and is coming from the Emulator. if ( activity.channelId === Channels.Emulator && @@ -70,18 +63,25 @@ export namespace JwtTokenValidation { // IsAuthenticated flag set in the ClaimsIdentity. To do this requires // adding in an empty claim. return new ClaimsIdentity([], AuthenticationConstants.AnonymousAuthType); - } + } else { + if (!authHeader.trim()) { + throw new AuthenticationError( + 'Unauthorized Access. Request is not authorized', + StatusCodes.UNAUTHORIZED, + ); + } - const claimsIdentity: ClaimsIdentity = await validateAuthHeader( - authHeader, - credentials, - channelService, - activity.channelId, - activity.serviceUrl, - authConfig, - ); + const claimsIdentity: ClaimsIdentity = await validateAuthHeader( + authHeader, + credentials, + channelService, + activity.channelId, + activity.serviceUrl, + authConfig, + ); - return claimsIdentity; + return claimsIdentity; + } } /** @@ -153,8 +153,7 @@ export namespace JwtTokenValidation { } if (isPublicAzure(channelService)) { - // eslint-disable-next-line prettier/prettier - if (serviceUrl.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty serviceUrl as part of that. + if (isValidServiceURL(serviceUrl)) { return await ChannelValidation.authenticateChannelTokenWithServiceUrl( authHeader, credentials, @@ -167,8 +166,7 @@ export namespace JwtTokenValidation { } if (isGovernment(channelService)) { - // eslint-disable-next-line prettier/prettier - if (serviceUrl.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty serviceUrl as part of that. + if (isValidServiceURL(serviceUrl)) { return await GovernmentChannelValidation.authenticateChannelTokenWithServiceUrl( authHeader, credentials, @@ -181,8 +179,7 @@ export namespace JwtTokenValidation { } // Otherwise use Enterprise Channel Validation - // eslint-disable-next-line prettier/prettier - if (serviceUrl.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty serviceUrl as part of that. + if (isValidServiceURL(serviceUrl)) { return await EnterpriseChannelValidation.authenticateChannelTokenWithServiceUrl( authHeader, credentials, @@ -270,6 +267,18 @@ export namespace JwtTokenValidation { return !channelService || channelService.length === 0; } + function isValidServiceURL(serviceUrl: string): boolean { + const trimmedUrl = serviceUrl.trim(); + const absoluteUrl = + trimmedUrl.startsWith('http') || trimmedUrl.startsWith('wss') ? trimmedUrl : `https://${trimmedUrl}`; + try { + const newUri = new URL(absoluteUrl); + return !!newUri; + } catch { + return false; + } + } + /** * Determine whether or not a channel service is government * diff --git a/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts b/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts index 4b5ddb5288..bd55318115 100644 --- a/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts +++ b/libraries/botframework-connector/src/auth/parameterizedBotFrameworkAuthentication.ts @@ -85,22 +85,17 @@ export class ParameterizedBotFrameworkAuthentication extends BotFrameworkAuthent * @returns The identity validation result. */ async authenticateChannelRequest(authHeader: string): Promise { - if (!authHeader.trim()) { - const isAuthDisabled = await this.credentialsFactory.isAuthenticationDisabled(); - if (!isAuthDisabled) { + if (await this.credentialsFactory.isAuthenticationDisabled()) { + return SkillValidation.createAnonymousSkillClaim(); + } else { + if (!authHeader.trim()) { throw new AuthenticationError( 'Unauthorized Access. Request is not authorized', 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.createAnonymousSkillClaim(); + return this.JwtTokenValidation_validateAuthHeader(authHeader, 'unknown', null); } - - return this.JwtTokenValidation_validateAuthHeader(authHeader, 'unknown', null); } /** @@ -213,15 +208,7 @@ export class ParameterizedBotFrameworkAuthentication extends BotFrameworkAuthent activity: Partial, authHeader: string, ): Promise { - if (!authHeader.trim()) { - const isAuthDisabled = await this.credentialsFactory.isAuthenticationDisabled(); - if (!isAuthDisabled) { - throw new AuthenticationError( - 'Unauthorized Access. Request is not authorized', - StatusCodes.UNAUTHORIZED, - ); - } - + if (await this.credentialsFactory.isAuthenticationDisabled()) { // Check if the activity is for a skill call and is coming from the Emulator. if (activity.channelId === Channels.Emulator && activity.recipient?.role === RoleTypes.Skill) { return SkillValidation.createAnonymousSkillClaim(); @@ -231,15 +218,21 @@ export class ParameterizedBotFrameworkAuthentication extends BotFrameworkAuthent // IsAuthenticated flag set in the ClaimsIdentity. To do this requires // adding in an empty claim. return new ClaimsIdentity([], AuthenticationConstants.AnonymousAuthType); - } - - const claimsIdentity: ClaimsIdentity = await this.JwtTokenValidation_validateAuthHeader( - authHeader, - activity.channelId, - activity.serviceUrl, - ); + } else { + if (!authHeader.trim()) { + throw new AuthenticationError( + 'Unauthorized Access. Request is not authorized', + StatusCodes.UNAUTHORIZED, + ); + } + const claimsIdentity: ClaimsIdentity = await this.JwtTokenValidation_validateAuthHeader( + authHeader, + activity.channelId, + activity.serviceUrl, + ); - return claimsIdentity; + return claimsIdentity; + } } private async JwtTokenValidation_validateAuthHeader(