• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / botbuilder-js / 7249230892

18 Dec 2023 02:09PM UTC coverage: 84.539% (-0.09%) from 84.629%
7249230892

Pull #4589

github

web-flow
Merge c8030b61d into f3db3e98b
Pull Request #4589: fix: Add ASE channel validation

9985 of 13088 branches covered (0.0%)

Branch coverage included in aggregate %.

62 of 90 new or added lines in 13 files covered. (68.89%)

99 existing lines in 4 files now uncovered.

20416 of 22873 relevant lines covered (89.26%)

7171.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.42
/libraries/botframework-connector/src/auth/aseChannelValidation.ts
1
/**
2
 * @module botframework-connector
3
 */
4
/**
5
 * Copyright (c) Microsoft Corporation. All rights reserved.
6
 * Licensed under the MIT License.
7
 */
8

9
/* eslint-disable @typescript-eslint/no-namespace */
10

11
import { ClaimsIdentity } from './claimsIdentity';
12
import { AuthenticationConstants } from './authenticationConstants';
2✔
13
import { AuthenticationConfiguration } from './authenticationConfiguration';
2✔
14
import { GovernmentConstants } from './governmentConstants';
2✔
15
import { ICredentialProvider } from './credentialProvider';
16
import { JwtTokenExtractor } from './jwtTokenExtractor';
2✔
17
import { JwtTokenValidation } from './jwtTokenValidation';
2✔
18
import { AuthenticationError } from './authenticationError';
2✔
19
import { SimpleCredentialProvider } from './credentialProvider';
2✔
20
import { StatusCodes } from 'botframework-schema';
2✔
21
import { BetweenBotAndAseChannelTokenValidationParameters } from './tokenValidationParameters';
2✔
22

23
/**
24
 * @deprecated Use `ConfigurationBotFrameworkAuthentication` instead to perform AseChannel validation.
25
 * Validates and Examines JWT tokens from the Bot Framework AseChannel
26
 */
27
export namespace AseChannelValidation {
2✔
28
    const ChannelId = "AseChannel";
2✔
29
    let _creadentialProvider: ICredentialProvider;
30
    let _channelService: string;
31
    export let MetadataUrl: string;
32

33
    export function init(configuration: any)
2✔
34
    {
35
        let appId = configuration.MicrosoftAppId;
12✔
36
        let tenantId = configuration.MicrosoftAppTenantId;
12✔
37
        _channelService = configuration.ChannelService;
12✔
38
        MetadataUrl =  _channelService !== undefined && JwtTokenValidation.isGovernment(_channelService)
12✔
39
            ? GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl
12✔
40
            : AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl;
41

42
        _creadentialProvider = new SimpleCredentialProvider(appId, "");
12✔
43

44
        let tenantIds: string[] = [
12✔
45
            tenantId,
46
            "f8cdef31-a31e-4b4a-93e4-5f571e91255a", // US Gov MicrosoftServices.onmicrosoft.us
47
            "d6d49420-f39b-4df7-a1dc-d59a935871db" // Public botframework.com
48
        ];
49
        let validIssuers: string[] = [];
12✔
50
        tenantIds.forEach((tmpId: string) => {
12✔
51
            validIssuers.push(`https://sts.windows.net/${tmpId}/`); // Auth Public/US Gov, 1.0 token
36✔
52
            validIssuers.push(`https://login.microsoftonline.com/${tmpId}/v2.0`); // Auth Public, 2.0 token
36✔
53
            validIssuers.push(`https://login.microsoftonline.us/${tmpId}/v2.0`); // Auth for US Gov, 2.0 token
36✔
54
        })
55
        BetweenBotAndAseChannelTokenValidationParameters.issuer = validIssuers;
12✔
56
    }
57

58
    /**
59
     * Determines if a given Auth header is from the Bot Framework AseChannel
60
     *
61
     * @param  {string} channelId.
62
     * @returns {boolean} True, if the token was issued by the AseChannel. Otherwise, false.
63
     */
64
    export function isTokenFromAseChannel(channelId: string): boolean {
2✔
65
        return channelId === ChannelId;
36✔
66
    }
67

68
    /**
69
     * Validate the incoming Auth Header as a token sent from the Bot Framework AseChannel.
70
     * A token issued by the Bot Framework will FAIL this check. Only AseChannel tokens will pass.
71
     *
72
     * @param  {string} authHeader The raw HTTP header in the format: "Bearer [longString]"
73
     * @param  {ICredentialProvider} credentials The user defined set of valid credentials, such as the AppId.
74
     * @param  {string} channelService The channelService value that distinguishes public Azure from US Government Azure.
75
     * @param  {AuthenticationConfiguration} authConfig The authentication configuration.
76
     * @returns {Promise<ClaimsIdentity>} A valid ClaimsIdentity.
77
     */
78
    export async function authenticateAseChannelToken(
2✔
79
        authHeader: string,
80
        authConfig: AuthenticationConfiguration = new AuthenticationConfiguration()
×
81
    ): Promise<ClaimsIdentity> {
82

NEW
83
        const tokenExtractor: JwtTokenExtractor = new JwtTokenExtractor(
×
84
            BetweenBotAndAseChannelTokenValidationParameters,
85
            MetadataUrl,
86
            AuthenticationConstants.AllowedSigningAlgorithms
87
        );
88

NEW
89
        const identity: ClaimsIdentity = await tokenExtractor.getIdentityFromAuthHeader(
×
90
            authHeader,
91
            ChannelId,
92
            authConfig.requiredEndorsements
93
        );
NEW
94
        if (!identity) {
×
95
            // No valid identity. Not Authorized.
NEW
96
            throw new AuthenticationError('Unauthorized. No valid identity.', StatusCodes.UNAUTHORIZED);
×
97
        }
98

NEW
99
        if (!identity.isAuthenticated) {
×
100
            // The token is in some way invalid. Not Authorized.
NEW
101
            throw new AuthenticationError('Unauthorized. Is not authenticated', StatusCodes.UNAUTHORIZED);
×
102
        }
103

104
        // Now check that the AppID in the claimset matches
105
        // what we're looking for. Note that in a multi-tenant bot, this value
106
        // comes from developer code that may be reaching out to a service, hence the
107
        // Async validation.
NEW
108
        const versionClaim: string = identity.getClaimValue(AuthenticationConstants.VersionClaim);
×
NEW
109
        if (versionClaim === null) {
×
NEW
110
            throw new AuthenticationError(
×
111
                'Unauthorized. "ver" claim is required on AseChannel Tokens.',
112
                StatusCodes.UNAUTHORIZED
113
            );
114
        }
115

NEW
116
        let appId = '';
×
117

118
        // The AseChannel, depending on Version, sends the AppId via either the
119
        // appid claim (Version 1) or the Authorized Party claim (Version 2).
NEW
120
        if (!versionClaim || versionClaim === '1.0') {
×
121
            // either no Version or a version of "1.0" means we should look for
122
            // the claim in the "appid" claim.
NEW
123
            const appIdClaim: string = identity.getClaimValue(AuthenticationConstants.AppIdClaim);
×
NEW
124
            if (!appIdClaim) {
×
125
                // No claim around AppID. Not Authorized.
NEW
126
                throw new AuthenticationError(
×
127
                    'Unauthorized. "appid" claim is required on AseChannel Token version "1.0".',
128
                    StatusCodes.UNAUTHORIZED
129
                );
130
            }
131

NEW
132
            appId = appIdClaim;
×
NEW
133
        } else if (versionClaim === '2.0') {
×
134
            // AseChannel, "2.0" puts the AppId in the "azp" claim.
NEW
135
            const appZClaim: string = identity.getClaimValue(AuthenticationConstants.AuthorizedParty);
×
NEW
136
            if (!appZClaim) {
×
137
                // No claim around AppID. Not Authorized.
NEW
138
                throw new AuthenticationError(
×
139
                    'Unauthorized. "azp" claim is required on AseChannel Token version "2.0".',
140
                    StatusCodes.UNAUTHORIZED
141
                );
142
            }
143

NEW
144
            appId = appZClaim;
×
145
        } else {
146
            // Unknown Version. Not Authorized.
NEW
147
            throw new AuthenticationError(
×
148
                `Unauthorized. Unknown AseChannel Token version "${versionClaim}".`,
149
                StatusCodes.UNAUTHORIZED
150
            );
151
        }
152

NEW
153
        if (!(await _creadentialProvider.isValidAppId(appId))) {
×
NEW
154
            throw new AuthenticationError(
×
155
                `Unauthorized. Invalid AppId passed on token: ${appId}`,
156
                StatusCodes.UNAUTHORIZED
157
            );
158
        }
159

NEW
160
        return identity;
×
161
    }
162
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc