@@ -10,7 +10,7 @@ import { STATUS_CODES } from 'http';
1010import * as os from 'os' ;
1111
1212import { Activity , ActivityTypes , BotAdapter , BotCallbackHandlerKey , ChannelAccount , ConversationAccount , ConversationParameters , ConversationReference , ConversationsResult , IUserTokenProvider , ResourceResponse , TokenResponse , TurnContext } from 'botbuilder-core' ;
13- import { AuthenticationConstants , ChannelValidation , ConnectorClient , EmulatorApiClient , GovernmentConstants , GovernmentChannelValidation , JwtTokenValidation , MicrosoftAppCredentials , AppCredentials , CertificateAppCredentials , SimpleCredentialProvider , TokenApiClient , TokenStatus , TokenApiModels } from 'botframework-connector' ;
13+ import { AuthenticationConfiguration , AuthenticationConstants , ChannelValidation , ClaimsIdentity , ConnectorClient , EmulatorApiClient , GovernmentConstants , GovernmentChannelValidation , JwtTokenValidation , MicrosoftAppCredentials , AppCredentials , CertificateAppCredentials , SimpleCredentialProvider , TokenApiClient , TokenStatus , TokenApiModels , SkillValidation } from 'botframework-connector' ;
1414import { INodeBuffer , INodeSocket , IReceiveRequest , ISocket , IStreamingTransportServer , NamedPipeServer , NodeWebSocketFactory , NodeWebSocketFactoryBase , RequestHandler , StreamingResponse , WebSocketServer } from 'botframework-streaming' ;
1515
1616import { StreamingHttpClient , TokenResolver } from './streaming' ;
@@ -155,6 +155,11 @@ export interface BotFrameworkAdapterSettings {
155155 * Optional. Certificate key to authenticate the appId against AAD.
156156 */
157157 certificatePrivateKey ?: string ;
158+
159+ /**
160+ * Optional. Used to require specific endorsements and verify claims. Recommended for Skills.
161+ */
162+ authConfig ?: AuthenticationConfiguration ;
158163}
159164
160165/**
@@ -232,6 +237,8 @@ export const INVOKE_RESPONSE_KEY: symbol = Symbol('invokeResponse');
232237 * ```
233238 */
234239export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvider , RequestHandler {
240+ public static readonly BotIdentityKey : string = 'BotIdentity' ;
241+ public static readonly ConnectorClientKey : string = 'ConnectorClient' ;
235242 protected readonly credentials : AppCredentials ;
236243 protected readonly credentialsProvider : SimpleCredentialProvider ;
237244 protected readonly settings : BotFrameworkAdapterSettings ;
@@ -243,6 +250,8 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
243250 private streamingServer : IStreamingTransportServer ;
244251 private webSocketFactory : NodeWebSocketFactoryBase ;
245252
253+ private authConfiguration : AuthenticationConfiguration ;
254+
246255 /**
247256 * Creates a new instance of the [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) class.
248257 *
@@ -280,6 +289,8 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
280289 this . settings . channelService = this . settings . channelService || process . env [ AuthenticationConstants . ChannelService ] ;
281290 this . settings . openIdMetadata = this . settings . openIdMetadata || process . env [ AuthenticationConstants . BotOpenIdMetadataKey ] ;
282291
292+ this . authConfiguration = this . settings . authConfig || new AuthenticationConfiguration ( ) ;
293+
283294 if ( this . settings . openIdMetadata ) {
284295 ChannelValidation . OpenIdMetadataEndpoint = this . settings . openIdMetadata ;
285296 GovernmentChannelValidation . OpenIdMetadataEndpoint = this . settings . openIdMetadata ;
@@ -667,8 +678,7 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
667678 *
668679 * @returns The [TokenStatus](xref:botframework-connector.TokenStatus) objects retrieved.
669680 */
670- public async getTokenStatus ( context : TurnContext , userId ?: string , includeFilter ?: string ) : Promise < TokenStatus [ ] >
671- {
681+ public async getTokenStatus ( context : TurnContext , userId ?: string , includeFilter ?: string ) : Promise < TokenStatus [ ] > {
672682 if ( ! userId && ( ! context . activity . from || ! context . activity . from . id ) ) {
673683 throw new Error ( `BotFrameworkAdapter.getTokenStatus(): missing from or from.id` ) ;
674684 }
@@ -785,9 +795,14 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
785795 const authHeader : string = req . headers . authorization || req . headers . Authorization || '' ;
786796 await this . authenticateRequest ( request , authHeader ) ;
787797
798+ const identity = await this . authenticateRequestInternal ( request , authHeader ) ;
799+
788800 // Process received activity
789801 status = 500 ;
790802 const context : TurnContext = this . createContext ( request ) ;
803+ context . turnState . set ( BotFrameworkAdapter . BotIdentityKey , identity ) ;
804+ const connectorClient = await this . createConnectorClientWithIdentity ( request . serviceUrl , identity ) ;
805+ context . turnState . set ( BotFrameworkAdapter . ConnectorClientKey , connectorClient ) ;
791806 context . turnState . set ( BotCallbackHandlerKey , logic ) ;
792807 await this . runMiddleware ( context , logic ) ;
793808
@@ -968,25 +983,87 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
968983 * Override this in a derived class to create a mock connector client for unit testing.
969984 */
970985 public createConnectorClient ( serviceUrl : string ) : ConnectorClient {
971- if ( BotFrameworkAdapter . isStreamingServiceUrl ( serviceUrl ) ) {
986+ return this . createConnectorClientInternal ( serviceUrl , this . credentials ) ;
987+ }
988+
989+ /**
990+ * Create a ConnectorClient with a ClaimsIdentity.
991+ * @remarks
992+ * If the ClaimsIdentity contains the claims for a Skills request, create a ConnectorClient for use with Skills.
993+ * @param serviceUrl
994+ * @param identity ClaimsIdentity
995+ */
996+ public async createConnectorClientWithIdentity ( serviceUrl : string , identity : ClaimsIdentity ) : Promise < ConnectorClient > {
997+ if ( ! identity ) {
998+ throw new Error ( 'BotFrameworkAdapter.createConnectorClientWithScope(): invalid identity parameter.' ) ;
999+ }
1000+
1001+ const botAppId = identity . getClaimValue ( AuthenticationConstants . AudienceClaim ) ||
1002+ identity . getClaimValue ( AuthenticationConstants . AppIdClaim ) ;
1003+
1004+ // Anonymous claims and non-skill claims should fall through without modifying the scope.
1005+ let credentials : AppCredentials = this . credentials ;
1006+
1007+ // If the request is for skills, we need to create an AppCredentials instance with
1008+ // the correct scope for communication between the caller and the skill.
1009+ if ( botAppId && SkillValidation . isSkillClaim ( identity . claims ) ) {
1010+ const scope = JwtTokenValidation . getAppIdFromClaims ( identity . claims ) ;
1011+ if ( this . credentials . oAuthScope === scope ) {
1012+ // Do nothing, the current credentials and its scope are valid for the skill.
1013+ // i.e. the adatper instance is pre-configured to talk with one skill.
1014+ } else {
1015+ // Since the scope is different, we will create a new instance of the AppCredentials
1016+ // so this.credentials.oAuthScope isn't overridden.
1017+ credentials = await this . buildCredentials ( botAppId , scope ) ;
1018+
1019+ if ( JwtTokenValidation . isGovernment ( this . settings . channelService ) ) {
1020+ credentials . oAuthEndpoint = GovernmentConstants . ToChannelFromBotLoginUrl ;
1021+ // Not sure that this code is correct because the scope was set earlier.
1022+ credentials . oAuthScope = GovernmentConstants . ToChannelFromBotOAuthScope ;
1023+ }
1024+ }
1025+ }
1026+
1027+ const client : ConnectorClient = this . createConnectorClientInternal ( serviceUrl , credentials ) ;
1028+ return client ;
1029+ }
9721030
1031+ /**
1032+ * @private
1033+ * @param serviceUrl The client's service URL.
1034+ * @param credentials AppCredentials instance to construct the ConnectorClient with.
1035+ */
1036+ private createConnectorClientInternal ( serviceUrl : string , credentials : AppCredentials ) : ConnectorClient {
1037+ if ( BotFrameworkAdapter . isStreamingServiceUrl ( serviceUrl ) ) {
9731038 // Check if we have a streaming server. Otherwise, requesting a connector client
9741039 // for a non-existent streaming connection results in an error
9751040 if ( ! this . streamingServer ) {
9761041 throw new Error ( `Cannot create streaming connector client for serviceUrl ${ serviceUrl } without a streaming connection. Call 'useWebSocket' or 'useNamedPipe' to start a streaming connection.` )
9771042 }
9781043
9791044 return new ConnectorClient (
980- this . credentials ,
1045+ credentials ,
9811046 {
9821047 baseUri : serviceUrl ,
9831048 userAgent : USER_AGENT ,
9841049 httpClient : new StreamingHttpClient ( this . streamingServer )
9851050 } ) ;
9861051 }
9871052
988- const client : ConnectorClient = new ConnectorClient ( this . credentials , { baseUri : serviceUrl , userAgent : USER_AGENT } ) ;
989- return client ;
1053+ return new ConnectorClient ( credentials , { baseUri : serviceUrl , userAgent : USER_AGENT } ) ;
1054+ }
1055+
1056+ /**
1057+ *
1058+ * @remarks
1059+ * @param appId
1060+ * @param oAuthScope
1061+ */
1062+ protected async buildCredentials ( appId : string , oAuthScope : string = '' ) : Promise < AppCredentials > {
1063+ // There is no cache for AppCredentials in JS as opposed to C#.
1064+ // Instead of retrieving an AppCredentials from the Adapter instance, generate a new one
1065+ const appPassword = await this . credentialsProvider . getAppPassword ( appId ) ;
1066+ return new MicrosoftAppCredentials ( appId , appPassword , oAuthScope ) ;
9901067 }
9911068
9921069 /**
@@ -998,22 +1075,37 @@ export class BotFrameworkAdapter extends BotAdapter implements IUserTokenProvide
9981075 * Override this in a derived class to create a mock OAuth API client for unit testing.
9991076 */
10001077 protected createTokenApiClient ( serviceUrl : string ) : TokenApiClient {
1001- const client = new TokenApiClient ( this . credentials , { baseUri : serviceUrl , userAgent : USER_AGENT } ) ;
1078+ const client = new TokenApiClient ( this . credentials , { baseUri : serviceUrl , userAgent : USER_AGENT } ) ;
10021079 return client ;
10031080 }
10041081
10051082 /**
1006- * Allows for the overriding of authentication in unit tests.
1083+ * Allows for the overriding of authentication in unit tests.
1084+ * @param request Received request.
1085+ * @param authHeader Received authentication header.
1086+ */
1087+ protected async authenticateRequest ( request : Partial < Activity > , authHeader : string ) : Promise < void > {
1088+ const claims = await this . authenticateRequestInternal ( request , authHeader ) ;
1089+ if ( ! claims . isAuthenticated ) { throw new Error ( 'Unauthorized Access. Request is not authorized' ) ; }
1090+ }
1091+
1092+ /**
1093+ * @ignore
1094+ * @private
1095+ * Returns the actual ClaimsIdentity from the JwtTokenValidation.authenticateRequest() call.
1096+ * @remarks
1097+ * This method is used instead of authenticateRequest() in processActivity() to obtain the ClaimsIdentity for caching in the TurnContext.turnState.
1098+ *
10071099 * @param request Received request.
10081100 * @param authHeader Received authentication header.
10091101 */
1010- protected async authenticateRequest ( request : Partial < Activity > , authHeader : string ) : Promise < void > {
1011- const claims = await JwtTokenValidation . authenticateRequest (
1102+ private authenticateRequestInternal ( request : Partial < Activity > , authHeader : string ) : Promise < ClaimsIdentity > {
1103+ return JwtTokenValidation . authenticateRequest (
10121104 request as Activity , authHeader ,
10131105 this . credentialsProvider ,
1014- this . settings . channelService
1106+ this . settings . channelService ,
1107+ this . authConfiguration
10151108 ) ;
1016- if ( ! claims . isAuthenticated ) { throw new Error ( 'Unauthorized Access. Request is not authorized' ) ; }
10171109 }
10181110
10191111 /**
0 commit comments