Merge branch 'main' into southworks/add/deploy-auth-type-bots
This commit is contained in:
Коммит
162a661268
|
@ -374,4 +374,3 @@ Bots/JavaScript/.yarn/*
|
|||
!Bots/JavaScript/.yarn/plugins
|
||||
!Bots/JavaScript/.yarn/sdks
|
||||
!Bots/JavaScript/.yarn/versions
|
||||
Bots/JavaScript/.pnp.*
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Settings;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ComposerSkillBotDotNet.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.ComposerSkillBotDotNet.Controllers
|
||||
{
|
||||
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
|
||||
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ComposerSkillBotDotNet.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.ComposerSkillBotDotNet.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that handles skill replies to the bot.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace ComposerSkillBotDotNet
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.ComposerSkillBotDotNet
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
@ -30,4 +30,4 @@ namespace ComposerSkillBotDotNet
|
|||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions;
|
||||
using Microsoft.Bot.Builder.LanguageGeneration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace ComposerSkillBotDotNet
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.ComposerSkillBotDotNet
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
|
|
@ -120,6 +120,33 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"$kind": "Microsoft.OnEventActivity",
|
||||
"$designer": {
|
||||
"id": "7kJtr3",
|
||||
"name": "Event received (Event activity)"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"$kind": "Microsoft.IfCondition",
|
||||
"$designer": {
|
||||
"id": "FpQfAQ"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"$kind": "Microsoft.SetProperty",
|
||||
"$designer": {
|
||||
"id": "ulNzSl"
|
||||
},
|
||||
"property": "dialog.token",
|
||||
"value": "turn.activity.value.token"
|
||||
}
|
||||
],
|
||||
"condition": "=turn.activity.name==tokens/response"
|
||||
}
|
||||
],
|
||||
"condition": "=not(empty(turn.activity.value))"
|
||||
}
|
||||
],
|
||||
"generator": "AuthDialog.lg",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# SendActivity_kNHtMW()
|
||||
[Activity
|
||||
Text = ${SendActivity_kNHtMW_text()}
|
||||
InputHint = ignoringInput
|
||||
]
|
||||
|
||||
# SendActivity_kNHtMW_text()
|
||||
|
@ -10,6 +11,7 @@
|
|||
# ConfirmInput_Prompt_WxLXxu()
|
||||
[Activity
|
||||
Text = ${ConfirmInput_Prompt_WxLXxu_text()}
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# ConfirmInput_Prompt_WxLXxu_text()
|
||||
|
@ -24,6 +26,7 @@
|
|||
# SendActivity_oj6Ert()
|
||||
[Activity
|
||||
Text = ${SendActivity_oj6Ert_text()}
|
||||
InputHint = ignoringInput
|
||||
]
|
||||
|
||||
# SendActivity_oj6Ert_text()
|
||||
|
@ -31,6 +34,7 @@
|
|||
# ConfirmInput_Prompt_pBqV4a()
|
||||
[Activity
|
||||
Text = ${ConfirmInput_Prompt_pBqV4a_text()}
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# ConfirmInput_Prompt_pBqV4a_text()
|
||||
|
@ -38,6 +42,8 @@
|
|||
# SendActivity_ooIaKS()
|
||||
[Activity
|
||||
Text = ${SendActivity_ooIaKS_text()}
|
||||
Speak = Here is your token:
|
||||
InputHint = ignoringInput
|
||||
]
|
||||
|
||||
# SendActivity_ooIaKS_text()
|
||||
|
|
|
@ -1,107 +1,108 @@
|
|||
[import](common.lg)
|
||||
> !# @strict = false
|
||||
> !# @namespace = cards
|
||||
> !# @exports = UnsupportedChannelCards
|
||||
|
||||
#UnsupportedChannelCards(channel)
|
||||
- IF:${channel == 'emulator'}
|
||||
- AdaptiveCardTeamsTaskModule, AdaptiveUpdate, O365, TeamsFileConsent
|
||||
- ELSEIF:${channel == 'directline'}
|
||||
- AdaptiveUpdate
|
||||
- ELSEIF:${channel == 'telegram'}
|
||||
- AdaptiveCardBotAction, AdaptiveCardTeamsTaskModule, AdaptiveCardSubmitAction, List, TeamsFileConsent
|
||||
- ELSE:
|
||||
-
|
||||
|
||||
# ChoiceInput_Prompt_CQChcR()
|
||||
[Activity
|
||||
Text = ${ChoiceInput_Prompt_CQChcR_text()}
|
||||
]
|
||||
|
||||
# ChoiceInput_Prompt_CQChcR_text()
|
||||
- What card do you want?
|
||||
# SendActivity_FNEXZh()
|
||||
-${HeroCard()}
|
||||
# SendActivity_lEVbJV()
|
||||
-${ThumbnailCard()}
|
||||
# SendActivity_RTN27M()
|
||||
-${SigninCard()}
|
||||
# SendActivity_MKkvvu()
|
||||
- ${AnimationCard()}
|
||||
# SendActivity_uTm0n2()
|
||||
- ${VideoCard()}
|
||||
# SendActivity_QuTjbR()
|
||||
- ${AudioCard()}
|
||||
# SendActivity_mxKi3X()
|
||||
-${CarouselTemplate()}
|
||||
# SendActivity_72oOOw()
|
||||
-${ListTemplate()}
|
||||
# SendActivity_JsAcV8()
|
||||
-${ReceiptCard()}
|
||||
# SendActivity_pYKkwT()
|
||||
[Activity
|
||||
Text = ${SendActivity_pYKkwT_text()}
|
||||
]
|
||||
|
||||
# SendActivity_pYKkwT_text()
|
||||
- I received an activity with this data in the value field ${turn.activity.value}
|
||||
# SendActivity_muQ5dx()
|
||||
[Activity
|
||||
Text = ${SendActivity_muQ5dx_text()}
|
||||
]
|
||||
|
||||
# SendActivity_muQ5dx_text()
|
||||
- ${dialog.CardType} cards are not supported in the ${turn.activity.channelId} channel.
|
||||
# SendActivity_xQaXd3()
|
||||
- ${AdaptiveCardBotAction()}
|
||||
# SendActivity_gi7Il1()
|
||||
- ${AdaptiveCardTaskModule()}
|
||||
# SendActivity_wdjazJ()
|
||||
- ${AdaptiveCardSubmitAction()}
|
||||
# SendActivity_CQMkYP()
|
||||
- ${HeroCard()}
|
||||
# SendActivity_yPmKNE()
|
||||
- ${ThumbnailCard()}
|
||||
# SendActivity_cRm57W()
|
||||
- ${ReceiptCard()}
|
||||
# SendActivity_jFdVpH()
|
||||
- ${SigninCard()}
|
||||
# SendActivity_WqKTFa()
|
||||
- ${CarouselTemplate()}
|
||||
# SendActivity_d764md()
|
||||
- ${ListTemplate()}
|
||||
# SendActivity_sAEgJI()
|
||||
- ${AnimationCard()}
|
||||
# SendActivity_LWdTvT()
|
||||
- ${AudioCard()}
|
||||
# SendActivity_jLEgbL()
|
||||
- ${VideoCard()}
|
||||
# SendActivity_Mvport()
|
||||
- ${TeamsFileConsent()}
|
||||
# SendActivity_0TKIF9()
|
||||
[Activity
|
||||
Text = ${SendActivity_0TKIF9_text()}
|
||||
]
|
||||
|
||||
# SendActivity_0TKIF9_text()
|
||||
- Update activity is not supported in the ${turn.activity.channelId} channel.
|
||||
# SendActivity_QwGHzg()
|
||||
[Activity
|
||||
Text = ${SendActivity_QwGHzg_text()}
|
||||
]
|
||||
|
||||
# SendActivity_QwGHzg_text()
|
||||
- I received an activity with this data in the text field ${turn.activity.text} and this data in the value field ${turn.activity.value}
|
||||
# SendActivity_73NchW()
|
||||
- ${UpdateCardTemplate('Update card', 'Update Card Action', 'Update card title', 'Update card text', '')}
|
||||
# UpdateActivity_Activity_Y72Ndw()
|
||||
- ${UpdateCardTemplate('Newly updated card', `Update count - ${user.UpdateCount}`, 'Update card', 'UpdateCardAction', '')}
|
||||
# SendActivity_nB1AD7()
|
||||
- ${O365Card()}
|
||||
# ChoiceInput_UnrecognizedPrompt_CQChcR()
|
||||
[Activity
|
||||
Text = ${ChoiceInput_UnrecognizedPrompt_CQChcR_text()}
|
||||
]
|
||||
|
||||
# ChoiceInput_UnrecognizedPrompt_CQChcR_text()
|
||||
[import](common.lg)
|
||||
> !# @strict = false
|
||||
> !# @namespace = cards
|
||||
> !# @exports = UnsupportedChannelCards
|
||||
|
||||
#UnsupportedChannelCards(channel)
|
||||
- IF:${channel == 'emulator'}
|
||||
- AdaptiveCardTeamsTaskModule, AdaptiveUpdate, O365, TeamsFileConsent
|
||||
- ELSEIF:${channel == 'directline'}
|
||||
- AdaptiveUpdate
|
||||
- ELSEIF:${channel == 'telegram'}
|
||||
- AdaptiveCardBotAction, AdaptiveCardTeamsTaskModule, AdaptiveCardSubmitAction, List, TeamsFileConsent
|
||||
- ELSE:
|
||||
-
|
||||
|
||||
# ChoiceInput_Prompt_CQChcR()
|
||||
[Activity
|
||||
Text = ${ChoiceInput_Prompt_CQChcR_text()}
|
||||
Speak = ${ChoiceInput_Prompt_CQChcR_text()}
|
||||
]
|
||||
|
||||
# ChoiceInput_Prompt_CQChcR_text()
|
||||
- What card do you want?
|
||||
# SendActivity_FNEXZh()
|
||||
-${HeroCard()}
|
||||
# SendActivity_lEVbJV()
|
||||
-${ThumbnailCard()}
|
||||
# SendActivity_RTN27M()
|
||||
-${SigninCard()}
|
||||
# SendActivity_MKkvvu()
|
||||
- ${AnimationCard()}
|
||||
# SendActivity_uTm0n2()
|
||||
- ${VideoCard()}
|
||||
# SendActivity_QuTjbR()
|
||||
- ${AudioCard()}
|
||||
# SendActivity_mxKi3X()
|
||||
-${CarouselTemplate()}
|
||||
# SendActivity_72oOOw()
|
||||
-${ListTemplate()}
|
||||
# SendActivity_JsAcV8()
|
||||
-${ReceiptCard()}
|
||||
# SendActivity_pYKkwT()
|
||||
[Activity
|
||||
Text = ${SendActivity_pYKkwT_text()}
|
||||
]
|
||||
|
||||
# SendActivity_pYKkwT_text()
|
||||
- I received an activity with this data in the value field ${turn.activity.value}
|
||||
# SendActivity_muQ5dx()
|
||||
[Activity
|
||||
Text = ${SendActivity_muQ5dx_text()}
|
||||
]
|
||||
|
||||
# SendActivity_muQ5dx_text()
|
||||
- ${dialog.CardType} cards are not supported in the ${turn.activity.channelId} channel.
|
||||
# SendActivity_xQaXd3()
|
||||
- ${AdaptiveCardBotAction()}
|
||||
# SendActivity_gi7Il1()
|
||||
- ${AdaptiveCardTaskModule()}
|
||||
# SendActivity_wdjazJ()
|
||||
- ${AdaptiveCardSubmitAction()}
|
||||
# SendActivity_CQMkYP()
|
||||
- ${HeroCard()}
|
||||
# SendActivity_yPmKNE()
|
||||
- ${ThumbnailCard()}
|
||||
# SendActivity_cRm57W()
|
||||
- ${ReceiptCard()}
|
||||
# SendActivity_jFdVpH()
|
||||
- ${SigninCard()}
|
||||
# SendActivity_WqKTFa()
|
||||
- ${CarouselTemplate()}
|
||||
# SendActivity_d764md()
|
||||
- ${ListTemplate()}
|
||||
# SendActivity_sAEgJI()
|
||||
- ${AnimationCard()}
|
||||
# SendActivity_LWdTvT()
|
||||
- ${AudioCard()}
|
||||
# SendActivity_jLEgbL()
|
||||
- ${VideoCard()}
|
||||
# SendActivity_Mvport()
|
||||
- ${TeamsFileConsent()}
|
||||
# SendActivity_0TKIF9()
|
||||
[Activity
|
||||
Text = ${SendActivity_0TKIF9_text()}
|
||||
]
|
||||
|
||||
# SendActivity_0TKIF9_text()
|
||||
- Update activity is not supported in the ${turn.activity.channelId} channel.
|
||||
# SendActivity_QwGHzg()
|
||||
[Activity
|
||||
Text = ${SendActivity_QwGHzg_text()}
|
||||
]
|
||||
|
||||
# SendActivity_QwGHzg_text()
|
||||
- I received an activity with this data in the text field ${turn.activity.text} and this data in the value field ${turn.activity.value}
|
||||
# SendActivity_73NchW()
|
||||
- ${UpdateCardTemplate('Update card', 'Update Card Action', 'Update card title', 'Update card text', '')}
|
||||
# UpdateActivity_Activity_Y72Ndw()
|
||||
- ${UpdateCardTemplate('Newly updated card', `Update count - ${user.UpdateCount}`, 'Update card', 'UpdateCardAction', '')}
|
||||
# SendActivity_nB1AD7()
|
||||
- ${O365Card()}
|
||||
# ChoiceInput_UnrecognizedPrompt_CQChcR()
|
||||
[Activity
|
||||
Text = ${ChoiceInput_UnrecognizedPrompt_CQChcR_text()}
|
||||
]
|
||||
|
||||
# ChoiceInput_UnrecognizedPrompt_CQChcR_text()
|
||||
- Got: ${turn.activity}\n\nWhat card do you want?
|
|
@ -3,6 +3,7 @@
|
|||
# AttachmentInput_Prompt_JKtU0T()
|
||||
[Activity
|
||||
Text = ${AttachmentInput_Prompt_JKtU0T_text()}
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# AttachmentInput_Prompt_JKtU0T_text()
|
||||
|
@ -10,6 +11,7 @@
|
|||
# AttachmentInput_UnrecognizedPrompt_JKtU0T()
|
||||
[Activity
|
||||
Text = ${AttachmentInput_UnrecognizedPrompt_JKtU0T_text()}
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# AttachmentInput_UnrecognizedPrompt_JKtU0T_text()
|
||||
|
@ -17,11 +19,11 @@
|
|||
# SendActivity_ZBBqzI()
|
||||
[Activity
|
||||
Text = ${SendActivity_ZBBqzI_text()}
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# SendActivity_ZBBqzI_text()
|
||||
- ```Attachment "${dialog.file.Name}" has been received.
|
||||
File content: ${dialog.fileContent}```
|
||||
- ```Attachment "${dialog.file.Name}" has been received.\r\nFile content: ${dialog.fileContent}```
|
||||
# ConfirmInput_Prompt_9WovlS()
|
||||
[Activity
|
||||
Text = ${ConfirmInput_Prompt_9WovlS_text()}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
# SendActivity_uWZAVE()
|
||||
[Activity
|
||||
Text = ${SendActivity_uWZAVE_text()}
|
||||
InputHint = ignoringInput
|
||||
Attachments = ${AttachmentTemplate('Files/architecture-resize.png', 'image/png', `${conversation.HostUrl}/images/architecture-resize.png`)}
|
||||
]
|
||||
# SendActivity_uWZAVE_text()
|
||||
|
@ -43,6 +44,7 @@
|
|||
# SendActivity_4HOnje()
|
||||
[Activity
|
||||
Text = ${SendActivity_4HOnje_text()}
|
||||
InputHint = ignoringInput
|
||||
Attachments = ${AttachmentTemplate('Files/architecture-resize.png', 'image/png', `data:image/png;base64,${base64(GetFile())}`)}
|
||||
]
|
||||
|
||||
|
|
|
@ -69,12 +69,14 @@
|
|||
[Activity
|
||||
Attachments = ${HeroCard()} | ${HeroCard()} | ${HeroCard()}
|
||||
AttachmentLayout = carousel
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# ListTemplate
|
||||
[Activity
|
||||
Attachments = ${HeroCard()} | ${HeroCard()} | ${HeroCard()}
|
||||
AttachmentLayout = list
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# AnimationCard
|
||||
|
@ -122,16 +124,22 @@
|
|||
# AdaptiveCardBotAction
|
||||
[Activity
|
||||
Attachments = ${json(adaptivecardbotactionjson())}
|
||||
AttachmentLayout = list
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# AdaptiveCardSubmitAction
|
||||
[Activity
|
||||
Attachments = ${json(adaptivecardsubmitactionjson())}
|
||||
AttachmentLayout = list
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# AdaptiveCardTaskModule
|
||||
[Activity
|
||||
Attachments = ${json(adaptivecardtaskmodulejson())}
|
||||
AttachmentLayout = list
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# TeamsFileConsent
|
||||
|
@ -142,6 +150,8 @@
|
|||
# O365Card
|
||||
[Activity
|
||||
Attachments = ${json(o365cardjson())}
|
||||
AttachmentLayout = list
|
||||
InputHint = acceptingInput
|
||||
]
|
||||
|
||||
# adaptivecardbotactionjson()
|
||||
|
@ -282,11 +292,242 @@
|
|||
# o365cardjson()
|
||||
- ```
|
||||
{
|
||||
"type": "MessageCard",
|
||||
"type": "application/vnd.microsoft.teams.card.o365connector",
|
||||
"context": "http://schema.org/extensions",
|
||||
"summary": "John Doe commented on Trello",
|
||||
"title": "Project Tango",
|
||||
"sections": [],
|
||||
"potentialAction": []
|
||||
"summary": "O365 card summary",
|
||||
"title": "card title",
|
||||
"text": "card text",
|
||||
"themeColor": "#E67A9E",
|
||||
"sections": [
|
||||
{
|
||||
"title": "**section title**",
|
||||
"text": "section text",
|
||||
"activityTitle": "activity title",
|
||||
"activitySubtitle": "activity subtitle",
|
||||
"activityText": "activity text",
|
||||
"activityImage": "http://connectorsdemo.azurewebsites.net/images/MSC12_Oscar_002.jpg",
|
||||
"activityImageType": "avatar",
|
||||
"markdown": true,
|
||||
"facts": [
|
||||
{
|
||||
"name": "Fact name 1",
|
||||
"value": "Fact value 1"
|
||||
},
|
||||
{
|
||||
"name": "Fact name 2",
|
||||
"value": "Fact value 2"
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"image": "http://connectorsdemo.azurewebsites.net/images/MicrosoftSurface_024_Cafe_OH-06315_VS_R1c.jpg",
|
||||
"title": "image 1"
|
||||
},
|
||||
{
|
||||
"image": "http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg",
|
||||
"title": "image 2"
|
||||
},
|
||||
{
|
||||
"image": "http://connectorsdemo.azurewebsites.net/images/WIN12_Anthony_02.jpg",
|
||||
"title": "image 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"potentialAction": [
|
||||
{
|
||||
"@type": "ActionCard",
|
||||
"inputs": [
|
||||
{
|
||||
"@type": "MultichoiceInput",
|
||||
"choices": [
|
||||
{
|
||||
"display": "Choice 1"
|
||||
},
|
||||
{
|
||||
"display": "Choice 2"
|
||||
},
|
||||
{
|
||||
"display": "Choice 3"
|
||||
}
|
||||
],
|
||||
"style": "expanded",
|
||||
"isMultiSelect": true,
|
||||
"id": "list-1",
|
||||
"isRequired": true,
|
||||
"title": "Pick multiple options"
|
||||
},
|
||||
{
|
||||
"@type": "MultichoiceInput",
|
||||
"choices": [
|
||||
{
|
||||
"display": "Choice 4"
|
||||
},
|
||||
{
|
||||
"display": "Choice 5"
|
||||
},
|
||||
{
|
||||
"display": "Choice 6"
|
||||
}
|
||||
],
|
||||
"style": "compact",
|
||||
"isMultiSelect": true,
|
||||
"id": "list-2",
|
||||
"isRequired": true,
|
||||
"title": "Pick multiple options"
|
||||
},
|
||||
{
|
||||
"@type": "MultichoiceInput",
|
||||
"choices": [
|
||||
{
|
||||
"display": "Choice a",
|
||||
"value": "a"
|
||||
},
|
||||
{
|
||||
"display": "Choice b",
|
||||
"value": "b"
|
||||
},
|
||||
{
|
||||
"display": "Choice c",
|
||||
"value": "c"
|
||||
}
|
||||
],
|
||||
"style": "expanded",
|
||||
"isMultiSelect": false,
|
||||
"id": "list-3",
|
||||
"isRequired": false,
|
||||
"title": "Pick an option"
|
||||
},
|
||||
{
|
||||
"@type": "MultichoiceInput",
|
||||
"choices": [
|
||||
{
|
||||
"display": "Choice x",
|
||||
"value": "x"
|
||||
},
|
||||
{
|
||||
"display": "Choice y",
|
||||
"value": "y"
|
||||
},
|
||||
{
|
||||
"display": "Choice z",
|
||||
"value": "z"
|
||||
}
|
||||
],
|
||||
"style": "compact",
|
||||
"isMultiSelect": false,
|
||||
"id": "list-4",
|
||||
"isRequired": false,
|
||||
"title": "Pick an option"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"@type": "HttpPOST",
|
||||
"name": "Send",
|
||||
"@id": "card-1-btn-1"
|
||||
}
|
||||
],
|
||||
"name": "Multiple Choice",
|
||||
"@id": "card-1"
|
||||
},
|
||||
{
|
||||
"@type": "ActionCard",
|
||||
"inputs": [
|
||||
{
|
||||
"@type": "TextInput",
|
||||
"isMultiline": true,
|
||||
"id": "text-1",
|
||||
"isRequired": false,
|
||||
"title": "multiline, no maxLength"
|
||||
},
|
||||
{
|
||||
"@type": "TextInput",
|
||||
"isMultiline": false,
|
||||
"id": "text-2",
|
||||
"isRequired": false,
|
||||
"title": "single line, no maxLength"
|
||||
},
|
||||
{
|
||||
"@type": "TextInput",
|
||||
"isMultiline": true,
|
||||
"id": "text-3",
|
||||
"isRequired": true,
|
||||
"title": "multiline, max len = 10, isRequired"
|
||||
},
|
||||
{
|
||||
"@type": "TextInput",
|
||||
"isMultiline": false,
|
||||
"id": "text-4",
|
||||
"isRequired": true,
|
||||
"title": "single line, max len = 10, isRequired"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"@type": "HttpPOST",
|
||||
"name": "Send",
|
||||
"@id": "card-2-btn-1"
|
||||
}
|
||||
],
|
||||
"name": "Text Input",
|
||||
"@id": "card-2"
|
||||
},
|
||||
{
|
||||
"@type": "ActionCard",
|
||||
"inputs": [
|
||||
{
|
||||
"@type": "DateInput",
|
||||
"includeTime": true,
|
||||
"id": "date-1",
|
||||
"isRequired": true,
|
||||
"title": "date with time"
|
||||
},
|
||||
{
|
||||
"@type": "DateInput",
|
||||
"includeTime": false,
|
||||
"id": "date-2",
|
||||
"isRequired": false,
|
||||
"title": "date only"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"@type": "HttpPOST",
|
||||
"name": "Send",
|
||||
"@id": "card-3-btn-1"
|
||||
}
|
||||
],
|
||||
"name": "Date Input",
|
||||
"@id": "card-3"
|
||||
},
|
||||
{
|
||||
"@type": "ViewAction",
|
||||
"name": "View Action"
|
||||
},
|
||||
{
|
||||
"@type": "OpenUri",
|
||||
"targets": [
|
||||
{
|
||||
"os": "default",
|
||||
"uri": "http://microsoft.com"
|
||||
},
|
||||
{
|
||||
"os": "iOS",
|
||||
"uri": "http://microsoft.com"
|
||||
},
|
||||
{
|
||||
"os": "android",
|
||||
"uri": "http://microsoft.com"
|
||||
},
|
||||
{
|
||||
"os": "windows",
|
||||
"uri": "http://microsoft.com"
|
||||
}
|
||||
],
|
||||
"name": "Open Uri",
|
||||
"@id": "open-uri"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
|
@ -10,7 +10,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3
|
||||
{
|
||||
/// <summary>
|
||||
/// Web Api Controller to intersect HTTP operations when the Action Invoker is triggered to capture exceptions and send it to the bot as an Activity.
|
||||
|
|
|
@ -6,7 +6,7 @@ using Newtonsoft.Json.Serialization;
|
|||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3
|
||||
{
|
||||
public static class WebApiConfig
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ using System.Linq;
|
|||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Authentication
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample claims validator that loads an allowed list from configuration if present
|
||||
|
|
|
@ -5,7 +5,7 @@ using Microsoft.Bot.Connector.SkillAuthentication;
|
|||
using System.Configuration;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Authentication
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3.Authentication
|
||||
{
|
||||
public class CustomSkillAuthenticationConfiguration : AuthenticationConfiguration
|
||||
{
|
||||
|
|
|
@ -10,11 +10,11 @@ using System.Web.Http;
|
|||
using Autofac;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Internals;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3.Authentication;
|
||||
using Microsoft.Bot.Connector;
|
||||
using Microsoft.Bot.Connector.SkillAuthentication;
|
||||
using Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Authentication;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3
|
||||
{
|
||||
// Specify which type provides the authentication configuration to allow for validation for skills.
|
||||
[SkillBotAuthentication(AuthenticationConfigurationProviderType = typeof(CustomSkillAuthenticationConfiguration))]
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.Dialogs
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3.Dialogs
|
||||
{
|
||||
[Serializable]
|
||||
public class RootDialog : IDialog<object>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3.WebApiApplication" Language="C#" %>
|
||||
<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3.WebApiApplication" Language="C#" %>
|
||||
|
|
|
@ -9,7 +9,7 @@ using Autofac;
|
|||
using Microsoft.Bot.Connector;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBotv3
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotv3
|
||||
{
|
||||
public class WebApiApplication : System.Web.HttpApplication
|
||||
{
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot.Bots
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot.Bots
|
||||
{
|
||||
public class EchoBot : ActivityHandler
|
||||
{
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot implementation at runtime.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot
|
||||
{
|
||||
public class SkillAdapterWithErrorHandler : CloudAdapter
|
||||
{
|
||||
|
|
|
@ -1,85 +1,97 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.EchoSkillBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.EchoSkillBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private const string CallersConfigKey = "AllowedCallers";
|
||||
|
||||
public Startup(IConfiguration config)
|
||||
{
|
||||
Configuration = config;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to add services to the container.
|
||||
/// </summary>
|
||||
/// <param name="services">Method to add services to the container.</param>
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot.Bots;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private const string CallersConfigKey = "AllowedCallers";
|
||||
|
||||
public Startup(IConfiguration config)
|
||||
{
|
||||
Configuration = config;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to add services to the container.
|
||||
/// </summary>
|
||||
/// <param name="services">Method to add services to the container.</param>
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Register AuthConfiguration to enable custom claim validation.
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
// AllowedCallers is the setting in the appsettings.json file that consists of the list of parent bot IDs that are allowed to access the skill.
|
||||
// To add a new parent bot, simply edit the AllowedCallers and add the parent bot's Microsoft app ID to the list.
|
||||
// In this sample, we allow all callers if AllowedCallers contains an "*".
|
||||
var callersSection = Configuration.GetSection(CallersConfigKey);
|
||||
var callers = callersSection.Get<string[]>();
|
||||
if (callers == null)
|
||||
var allowedCallers = new List<string>(sp.GetService<IConfiguration>().GetSection(CallersConfigKey).Get<string[]>());
|
||||
|
||||
var claimsValidator = new AllowedCallersClaimsValidator(allowedCallers);
|
||||
|
||||
// If TenantId is specified in config, add the tenant as a valid JWT token issuer for Bot to Skill conversation.
|
||||
// The token issuer for MSI and single tenant scenarios will be the tenant where the bot is registered.
|
||||
var validTokenIssuers = new List<string>();
|
||||
var tenantId = sp.GetService<IConfiguration>().GetSection(MicrosoftAppCredentials.MicrosoftAppTenantIdKey)?.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
throw new ArgumentNullException($"\"{CallersConfigKey}\" not found in configuration.");
|
||||
// For SingleTenant/MSI auth, the JWT tokens will be issued from the bot's home tenant.
|
||||
// Therefore, these issuers need to be added to the list of valid token issuers for authenticating activity requests.
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV2, tenantId));
|
||||
}
|
||||
|
||||
return new AuthenticationConfiguration { ClaimsValidator = new AllowedCallersClaimsValidator(callers) };
|
||||
return new AuthenticationConfiguration
|
||||
{
|
||||
ClaimsValidator = claimsValidator,
|
||||
ValidTokenIssuers = validTokenIssuers
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
|
||||
|
||||
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
|
||||
|
||||
// Create the Bot Framework Adapter with error handling enabled.
|
||||
services.AddSingleton<IBotFrameworkHttpAdapter, SkillAdapterWithErrorHandler>();
|
||||
|
||||
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
|
||||
services.AddTransient<IBot, EchoBot>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
/// </summary>
|
||||
/// <param name="app">The application request pipeline to be configured.</param>
|
||||
/// <param name="env">The web hosting environment.</param>
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
services.AddSingleton<IBotFrameworkHttpAdapter, SkillAdapterWithErrorHandler>();
|
||||
|
||||
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
|
||||
services.AddTransient<IBot, EchoBot>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
/// </summary>
|
||||
/// <param name="app">The application request pipeline to be configured.</param>
|
||||
/// <param name="env">The web hosting environment.</param>
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"MicrosoftAppType": "",
|
||||
"MicrosoftAppId": "",
|
||||
"MicrosoftAppPassword": "",
|
||||
"MicrosoftAppTenantId": "",
|
||||
"ChannelService": "",
|
||||
// This is a comma separate list with the App IDs that will have access to the skill.
|
||||
// This setting is used in AllowedCallersClaimsValidator.
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Settings;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EchoSkillBotComposer.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotComposer.Controllers
|
||||
{
|
||||
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
|
||||
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EchoSkillBotComposer.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotComposer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that handles skill replies to the bot.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace EchoSkillBotComposer
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotComposer
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
@ -30,4 +30,4 @@ namespace EchoSkillBotComposer
|
|||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions;
|
||||
|
@ -6,7 +6,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace EchoSkillBotComposer
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotComposer
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
|
|
@ -3,21 +3,21 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30621.155
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleHostBot", "SimpleHostBot\SimpleHostBot.csproj", "{AAE978F8-D22B-41E8-B445-872FF4194713}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot", "SimpleHostBot\Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.csproj", "{AAE978F8-D22B-41E8-B445-872FF4194713}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoSkillBot", "EchoSkillBot\EchoSkillBot.csproj", "{692F26DD-F7BA-49F3-AC6D-73047C1E5D61}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot", "EchoSkillBot\Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot.csproj", "{692F26DD-F7BA-49F3-AC6D-73047C1E5D61}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaterfallHostBot", "WaterfallHostBot\WaterfallHostBot.csproj", "{15A946BE-39F9-4945-9895-0019ED3392FC}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot", "WaterfallHostBot\Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.csproj", "{15A946BE-39F9-4945-9895-0019ED3392FC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaterfallSkillBot", "WaterfallSkillBot\WaterfallSkillBot.csproj", "{E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot", "WaterfallSkillBot\Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.csproj", "{E3BECBEF-E41F-48D1-9EEB-A4D7E1CD34E3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoSkillBot-v3", "EchoSkillBot-v3\EchoSkillBot-v3.csproj", "{41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot-v3", "EchoSkillBot-v3\Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBot-v3.csproj", "{41BC9547-FD9E-40E1-B1D9-C3F25BC9A2F3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleHostBotComposer", "SimpleHostBotComposer\SimpleHostBotComposer.csproj", "{FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBotComposer", "SimpleHostBotComposer\Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBotComposer.csproj", "{FDC53B3A-0E15-4FDF-A587-05C8F90BC2B6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoSkillBotComposer", "EchoSkillBotComposer\EchoSkillBotComposer.csproj", "{9BA47CF9-7D90-4B5C-A9FA-01797A435D53}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotComposer", "EchoSkillBotComposer\Microsoft.Bot.Builder.FunctionalTestsBots.EchoSkillBotComposer.csproj", "{9BA47CF9-7D90-4B5C-A9FA-01797A435D53}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComposerSkillBotDotNet", "ComposerSkillBotDotNet\ComposerSkillBotDotNet.csproj", "{925E0E3F-194D-4E56-AE78-6FF6D49B7E7E}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTestsBots.ComposerSkillBotDotNet", "ComposerSkillBotDotNet\Microsoft.Bot.Builder.FunctionalTestsBots.ComposerSkillBotDotNet.csproj", "{925E0E3F-194D-4E56-AE78-6FF6D49B7E7E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComposerHostBotDotNet", "ComposerHostBotDotNet\ComposerHostBotDotNet.csproj", "{E3FCDDD4-553D-44BA-8B2F-82BA778261BC}"
|
||||
EndProject
|
|
@ -4,44 +4,40 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Bots;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot
|
||||
{
|
||||
public class AdapterWithErrorHandler : CloudAdapter
|
||||
{
|
||||
private readonly BotFrameworkAuthentication _auth;
|
||||
private readonly ConversationState _conversationState;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger _logger;
|
||||
private readonly SkillHttpClient _skillClient;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdapterWithErrorHandler"/> class to handle errors.
|
||||
/// </summary>
|
||||
/// <param name="botFrameworkAuthentication">The cloud environment for the bot.</param>
|
||||
/// <param name="auth">The cloud environment for the bot.</param>
|
||||
/// <param name="configuration">The configuration properties.</param>
|
||||
/// <param name="logger">An instance of a logger.</param>
|
||||
/// <param name="conversationState">A state management object for the conversation.</param>
|
||||
/// <param name="skillClient">The HTTP client for the skills.</param>
|
||||
/// <param name="skillsConfig">The skills configuration.</param>
|
||||
public AdapterWithErrorHandler(BotFrameworkAuthentication botFrameworkAuthentication, IConfiguration configuration, ILogger<CloudAdapter> logger, ConversationState conversationState = null, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null)
|
||||
: base(botFrameworkAuthentication, logger)
|
||||
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, IConfiguration configuration, ILogger<CloudAdapter> logger, ConversationState conversationState = null, SkillsConfiguration skillsConfig = null)
|
||||
: base(auth, logger)
|
||||
{
|
||||
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_conversationState = conversationState;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_skillClient = skillClient;
|
||||
_skillsConfig = skillsConfig;
|
||||
|
||||
OnTurnError = HandleTurnErrorAsync;
|
||||
|
@ -104,7 +100,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot
|
|||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
private async Task EndSkillConversationAsync(ITurnContext turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_conversationState == null || _skillClient == null || _skillsConfig == null)
|
||||
if (_conversationState == null || _skillsConfig == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -123,7 +119,9 @@ namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot
|
|||
endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true);
|
||||
|
||||
await _conversationState.SaveChangesAsync(turnContext, true, cancellationToken);
|
||||
await _skillClient.PostActivityAsync(botId, activeSkill, _skillsConfig.SkillHostEndpoint, (Activity)endOfConversation, cancellationToken);
|
||||
|
||||
using var client = _auth.CreateBotFrameworkClient();
|
||||
await client.PostActivityAsync(botId, activeSkill.AppId, activeSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, endOfConversation.Conversation.Id, (Activity)endOfConversation, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -1,242 +1,259 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Dialogs;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots
|
||||
{
|
||||
public class HostBot : ActivityHandler
|
||||
{
|
||||
public const string DeliveryModePropertyName = "deliveryModeProperty";
|
||||
public const string ActiveSkillPropertyName = "activeSkillProperty";
|
||||
|
||||
private readonly IStatePropertyAccessor<string> _deliveryModeProperty;
|
||||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
|
||||
private readonly IStatePropertyAccessor<DialogState> _dialogStateProperty;
|
||||
private readonly string _botId;
|
||||
private readonly ConversationState _conversationState;
|
||||
private readonly SkillHttpClient _skillClient;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
private readonly Dialog _dialog;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HostBot"/> class.
|
||||
/// </summary>
|
||||
/// <param name="conversationState">A state management object for the conversation.</param>
|
||||
/// <param name="skillsConfig">The skills configuration.</param>
|
||||
/// <param name="skillClient">The HTTP client for the skills.</param>
|
||||
/// <param name="configuration">The configuration properties.</param>
|
||||
/// <param name="dialog">The dialog to use.</param>
|
||||
public HostBot(ConversationState conversationState, SkillsConfiguration skillsConfig, SkillHttpClient skillClient, IConfiguration configuration, SetupDialog dialog)
|
||||
{
|
||||
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
|
||||
_skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig));
|
||||
_skillClient = skillClient ?? throw new ArgumentNullException(nameof(skillClient));
|
||||
_dialog = dialog ?? throw new ArgumentNullException(nameof(dialog));
|
||||
_dialogStateProperty = _conversationState.CreateProperty<DialogState>("DialogState");
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
_botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
|
||||
// Create state properties to track the delivery mode and active skill.
|
||||
_deliveryModeProperty = conversationState.CreateProperty<string>(DeliveryModePropertyName);
|
||||
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>(ActiveSkillPropertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Forward all activities except EndOfConversation to the active skill.
|
||||
if (turnContext.Activity.Type != ActivityTypes.EndOfConversation)
|
||||
{
|
||||
// Try to get the active skill
|
||||
var activeSkill = await _activeSkillProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
|
||||
if (activeSkill != null)
|
||||
{
|
||||
var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
|
||||
// Send the activity to the skill
|
||||
await SendToSkillAsync(turnContext, deliveryMode, activeSkill, cancellationToken);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnTurnAsync(turnContext, cancellationToken);
|
||||
|
||||
// Save any state changes that might have occurred during the turn.
|
||||
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a message activity.
|
||||
/// </summary>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_skillsConfig.Skills.ContainsKey(turnContext.Activity.Text))
|
||||
{
|
||||
var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
var selectedSkill = _skillsConfig.Skills[turnContext.Activity.Text];
|
||||
var v3Bots = new List<string> { "EchoSkillBotDotNetV3", "EchoSkillBotJSV3" };
|
||||
|
||||
if (selectedSkill != null && deliveryMode == DeliveryModes.ExpectReplies && v3Bots.Contains(selectedSkill.Id))
|
||||
{
|
||||
var message = MessageFactory.Text("V3 Bots do not support 'expectReplies' delivery mode.");
|
||||
await turnContext.SendActivityAsync(message, cancellationToken);
|
||||
|
||||
// Forget delivery mode and skill invocation.
|
||||
await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
|
||||
// Restart setup dialog
|
||||
await _conversationState.DeleteAsync(turnContext, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes an end of conversation activity.
|
||||
/// </summary>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
protected override async Task OnEndOfConversationActivityAsync(ITurnContext<IEndOfConversationActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
await EndConversation((Activity)turnContext.Activity, turnContext, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a member added event.
|
||||
/// </summary>
|
||||
/// <param name="membersAdded">The list of members added to the conversation.</param>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var member in membersAdded)
|
||||
{
|
||||
if (member.Id != turnContext.Activity.Recipient.Id)
|
||||
{
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome!"), cancellationToken);
|
||||
await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears storage variables and sends the end of conversation activities.
|
||||
/// </summary>
|
||||
/// <param name="activity">End of conversation activity.</param>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
private async Task EndConversation(Activity activity, ITurnContext turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// Forget delivery mode and skill invocation.
|
||||
await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
|
||||
// Show status message, text and value returned by the skill
|
||||
var eocActivityMessage = $"Received {ActivityTypes.EndOfConversation}.\n\nCode: {activity.Code}.";
|
||||
if (!string.IsNullOrWhiteSpace(activity.Text))
|
||||
{
|
||||
eocActivityMessage += $"\n\nText: {activity.Text}";
|
||||
}
|
||||
|
||||
if (activity.Value != null)
|
||||
{
|
||||
eocActivityMessage += $"\n\nValue: {JsonConvert.SerializeObject(activity.Value)}";
|
||||
}
|
||||
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text(eocActivityMessage), cancellationToken);
|
||||
|
||||
// We are back at the host.
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Back in the host bot."), cancellationToken);
|
||||
|
||||
// Restart setup dialog.
|
||||
await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken);
|
||||
|
||||
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an activity to the skill bot.
|
||||
/// </summary>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="deliveryMode">The delivery mode to use when communicating to the skill.</param>
|
||||
/// <param name="targetSkill">The skill that will receive the activity.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
private async Task SendToSkillAsync(ITurnContext turnContext, string deliveryMode, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
|
||||
{
|
||||
// NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
|
||||
// will have access to current accurate state.
|
||||
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken);
|
||||
|
||||
if (deliveryMode == DeliveryModes.ExpectReplies)
|
||||
{
|
||||
// Clone activity and update its delivery mode.
|
||||
var activity = JsonConvert.DeserializeObject<Activity>(JsonConvert.SerializeObject(turnContext.Activity));
|
||||
activity.DeliveryMode = deliveryMode;
|
||||
|
||||
// Route the activity to the skill.
|
||||
var expectRepliesResponse = await _skillClient.PostActivityAsync<ExpectedReplies>(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, activity, cancellationToken);
|
||||
|
||||
// Check response status.
|
||||
if (!expectRepliesResponse.IsSuccessStatusCode())
|
||||
{
|
||||
throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {expectRepliesResponse.Status}). \r\n {expectRepliesResponse.Body}");
|
||||
}
|
||||
|
||||
// Route response activities back to the channel.
|
||||
var responseActivities = expectRepliesResponse.Body?.Activities;
|
||||
|
||||
foreach (var responseActivity in responseActivities)
|
||||
{
|
||||
if (responseActivity.Type == ActivityTypes.EndOfConversation)
|
||||
{
|
||||
await EndConversation(responseActivity, turnContext, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await turnContext.SendActivityAsync(responseActivity, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Route the activity to the skill.
|
||||
var response = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, (Activity)turnContext.Activity, cancellationToken);
|
||||
|
||||
// Check response status
|
||||
if (!response.IsSuccessStatusCode())
|
||||
{
|
||||
throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Dialogs;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Bots
|
||||
{
|
||||
public class HostBot : ActivityHandler
|
||||
{
|
||||
public const string DeliveryModePropertyName = "deliveryModeProperty";
|
||||
public const string ActiveSkillPropertyName = "activeSkillProperty";
|
||||
|
||||
private readonly IStatePropertyAccessor<string> _deliveryModeProperty;
|
||||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
|
||||
private readonly IStatePropertyAccessor<DialogState> _dialogStateProperty;
|
||||
private readonly string _botId;
|
||||
private readonly BotFrameworkAuthentication _auth;
|
||||
private readonly ConversationState _conversationState;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
private readonly SkillConversationIdFactoryBase _conversationIdFactory;
|
||||
private readonly Dialog _dialog;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HostBot"/> class.
|
||||
/// </summary>
|
||||
/// <param name="auth">The cloud environment for the bot.</param>
|
||||
/// <param name="conversationState">A state management object for the conversation.</param>
|
||||
/// <param name="skillsConfig">The skills configuration.</param>
|
||||
/// <param name="configuration">The configuration properties.</param>
|
||||
/// <param name="conversationIdFactory">The conversation id factory.</param>
|
||||
/// <param name="dialog">The dialog to use.</param>
|
||||
public HostBot(BotFrameworkAuthentication auth, ConversationState conversationState, SkillsConfiguration skillsConfig, SkillConversationIdFactoryBase conversationIdFactory, IConfiguration configuration, SetupDialog dialog)
|
||||
{
|
||||
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
|
||||
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
|
||||
_skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig));
|
||||
_conversationIdFactory = conversationIdFactory ?? throw new ArgumentNullException(nameof(conversationIdFactory));
|
||||
_dialog = dialog ?? throw new ArgumentNullException(nameof(dialog));
|
||||
_dialogStateProperty = _conversationState.CreateProperty<DialogState>("DialogState");
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
_botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
|
||||
// Create state properties to track the delivery mode and active skill.
|
||||
_deliveryModeProperty = conversationState.CreateProperty<string>(DeliveryModePropertyName);
|
||||
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>(ActiveSkillPropertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Forward all activities except EndOfConversation to the active skill.
|
||||
if (turnContext.Activity.Type != ActivityTypes.EndOfConversation)
|
||||
{
|
||||
// Try to get the active skill
|
||||
var activeSkill = await _activeSkillProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
|
||||
if (activeSkill != null)
|
||||
{
|
||||
var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
|
||||
// Send the activity to the skill
|
||||
await SendToSkillAsync(turnContext, deliveryMode, activeSkill, cancellationToken);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnTurnAsync(turnContext, cancellationToken);
|
||||
|
||||
// Save any state changes that might have occurred during the turn.
|
||||
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a message activity.
|
||||
/// </summary>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_skillsConfig.Skills.ContainsKey(turnContext.Activity.Text))
|
||||
{
|
||||
var deliveryMode = await _deliveryModeProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
var selectedSkill = _skillsConfig.Skills[turnContext.Activity.Text];
|
||||
var v3Bots = new List<string> { "EchoSkillBotDotNetV3", "EchoSkillBotJSV3" };
|
||||
|
||||
if (selectedSkill != null && deliveryMode == DeliveryModes.ExpectReplies && v3Bots.Contains(selectedSkill.Id))
|
||||
{
|
||||
var message = MessageFactory.Text("V3 Bots do not support 'expectReplies' delivery mode.");
|
||||
await turnContext.SendActivityAsync(message, cancellationToken);
|
||||
|
||||
// Forget delivery mode and skill invocation.
|
||||
await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
|
||||
// Restart setup dialog
|
||||
await _conversationState.DeleteAsync(turnContext, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes an end of conversation activity.
|
||||
/// </summary>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
protected override async Task OnEndOfConversationActivityAsync(ITurnContext<IEndOfConversationActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
await EndConversation((Activity)turnContext.Activity, turnContext, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a member added event.
|
||||
/// </summary>
|
||||
/// <param name="membersAdded">The list of members added to the conversation.</param>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var member in membersAdded)
|
||||
{
|
||||
if (member.Id != turnContext.Activity.Recipient.Id)
|
||||
{
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome!"), cancellationToken);
|
||||
await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears storage variables and sends the end of conversation activities.
|
||||
/// </summary>
|
||||
/// <param name="activity">End of conversation activity.</param>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
private async Task EndConversation(Activity activity, ITurnContext turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// Forget delivery mode and skill invocation.
|
||||
await _deliveryModeProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken);
|
||||
|
||||
// Show status message, text and value returned by the skill
|
||||
var eocActivityMessage = $"Received {ActivityTypes.EndOfConversation}.\n\nCode: {activity.Code}.";
|
||||
if (!string.IsNullOrWhiteSpace(activity.Text))
|
||||
{
|
||||
eocActivityMessage += $"\n\nText: {activity.Text}";
|
||||
}
|
||||
|
||||
if (activity.Value != null)
|
||||
{
|
||||
eocActivityMessage += $"\n\nValue: {JsonConvert.SerializeObject(activity.Value)}";
|
||||
}
|
||||
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text(eocActivityMessage), cancellationToken);
|
||||
|
||||
// We are back at the host.
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Back in the host bot."), cancellationToken);
|
||||
|
||||
// Restart setup dialog.
|
||||
await _dialog.RunAsync(turnContext, _dialogStateProperty, cancellationToken);
|
||||
|
||||
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an activity to the skill bot.
|
||||
/// </summary>
|
||||
/// <param name="turnContext">Context for the current turn of conversation.</param>
|
||||
/// <param name="deliveryMode">The delivery mode to use when communicating to the skill.</param>
|
||||
/// <param name="targetSkill">The skill that will receive the activity.</param>
|
||||
/// <param name="cancellationToken">CancellationToken propagates notifications that operations should be cancelled.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
||||
private async Task SendToSkillAsync(ITurnContext turnContext, string deliveryMode, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
|
||||
{
|
||||
// NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
|
||||
// will have access to current accurate state.
|
||||
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken);
|
||||
|
||||
// Route the activity to the skill.
|
||||
using var client = _auth.CreateBotFrameworkClient();
|
||||
|
||||
// Create a conversationId to interact with the skill and send the activity
|
||||
var options = new SkillConversationIdFactoryOptions
|
||||
{
|
||||
FromBotOAuthScope = turnContext.TurnState.Get<string>(BotAdapter.OAuthScopeKey),
|
||||
FromBotId = _botId,
|
||||
Activity = turnContext.Activity,
|
||||
BotFrameworkSkill = targetSkill
|
||||
};
|
||||
|
||||
var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(options, cancellationToken);
|
||||
|
||||
if (deliveryMode == DeliveryModes.ExpectReplies)
|
||||
{
|
||||
// Clone activity and update its delivery mode.
|
||||
var activity = JsonConvert.DeserializeObject<Activity>(JsonConvert.SerializeObject(turnContext.Activity));
|
||||
activity.DeliveryMode = deliveryMode;
|
||||
|
||||
// route the activity to the skill
|
||||
var expectRepliesResponse = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, skillConversationId, activity, cancellationToken);
|
||||
|
||||
// Check response status.
|
||||
if (!expectRepliesResponse.IsSuccessStatusCode())
|
||||
{
|
||||
throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {expectRepliesResponse.Status}). \r\n {expectRepliesResponse.Body}");
|
||||
}
|
||||
|
||||
// Route response activities back to the channel.
|
||||
var response = expectRepliesResponse.Body as JObject;
|
||||
var activities = response["activities"];
|
||||
var responseActivities = activities.ToObject<IList<Activity>>();
|
||||
|
||||
foreach (var responseActivity in responseActivities)
|
||||
{
|
||||
if (responseActivity.Type == ActivityTypes.EndOfConversation)
|
||||
{
|
||||
await EndConversation(responseActivity, turnContext, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await turnContext.SendActivityAsync(responseActivity, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Route the activity to the skill.
|
||||
var response = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, skillConversationId, turnContext.Activity, cancellationToken);
|
||||
|
||||
// Check response status
|
||||
if (!response.IsSuccessStatusCode())
|
||||
{
|
||||
throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot implementation at runtime.
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that handles skill replies to the bot.
|
||||
/// This example uses the <see cref="SkillHandler"/> that is registered as a <see cref="ChannelServiceHandler"/> in startup.cs.
|
||||
/// This example uses the <see cref="CloudSkillHandler"/> that is registered as a <see cref="ChannelServiceHandlerBase"/> in startup.cs.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/skills")]
|
||||
|
@ -19,8 +18,8 @@ namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Controllers
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkillController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handler">The skill handler registered as ChannelServiceHandler.</param>
|
||||
public SkillController(ChannelServiceHandler handler)
|
||||
/// <param name="handler">The skill handler registered as ChannelServiceHandlerBase.</param>
|
||||
public SkillController(ChannelServiceHandlerBase handler)
|
||||
: base(handler)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -6,14 +6,13 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Choices;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Bots;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Dialogs
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// The setup dialog for this bot.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that loads Skills information from configuration.
|
||||
|
|
|
@ -1,104 +1,123 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Bots;
|
||||
using Microsoft.BotFrameworkFunctionalTests.SimpleHostBot.Dialogs;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.SimpleHostBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration config)
|
||||
{
|
||||
Configuration = config;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to add services to the container.
|
||||
/// </summary>
|
||||
/// <param name="services">The collection of services to add to the container.</param>
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Register the skills configuration class
|
||||
services.AddSingleton<SkillsConfiguration>();
|
||||
|
||||
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
|
||||
|
||||
services.AddSingleton(sp => new AuthenticationConfiguration
|
||||
{
|
||||
ClaimsValidator = new AllowedSkillsClaimsValidator(
|
||||
(from skill in sp.GetService<SkillsConfiguration>().Skills.Values select skill.AppId).ToList())
|
||||
});
|
||||
|
||||
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
|
||||
|
||||
// Register the Bot Framework Adapter with error handling enabled.
|
||||
// Note: some classes use the base BotAdapter so we add an extra registration that pulls the same instance.
|
||||
services.AddSingleton<CloudAdapter, AdapterWithErrorHandler>();
|
||||
services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
services.AddSingleton<BotAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
|
||||
// Register the skills client and skills request handler.
|
||||
services.AddSingleton<SkillConversationIdFactoryBase, SkillConversationIdFactory>();
|
||||
services.AddHttpClient<SkillHttpClient>();
|
||||
services.AddSingleton<ChannelServiceHandler, SkillHandler>();
|
||||
|
||||
// Register the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
|
||||
services.AddSingleton<IStorage, MemoryStorage>();
|
||||
|
||||
// Register Conversation state (used by the Dialog system itself).
|
||||
services.AddSingleton<ConversationState>();
|
||||
|
||||
// Create SetupDialog
|
||||
services.AddSingleton<SetupDialog>();
|
||||
|
||||
// Register the bot as a transient. In this case the ASP Controller is expecting an IBot.
|
||||
services.AddTransient<IBot, HostBot>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Configuration["ChannelService"]))
|
||||
{
|
||||
// Register a ConfigurationChannelProvider -- this is only for Azure Gov.
|
||||
services.AddSingleton<IChannelProvider, ConfigurationChannelProvider>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
/// </summary>
|
||||
/// <param name="app">The application request pipeline to be configured.</param>
|
||||
/// <param name="env">The web hosting environment.</param>
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Bots;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot.Dialogs;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration config)
|
||||
{
|
||||
Configuration = config;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to add services to the container.
|
||||
/// </summary>
|
||||
/// <param name="services">The collection of services to add to the container.</param>
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Register the skills configuration class
|
||||
services.AddSingleton<SkillsConfiguration>();
|
||||
|
||||
// Register AuthConfiguration to enable custom claim validation.
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
var allowedSkills = sp.GetService<SkillsConfiguration>().Skills.Values.Select(s => s.AppId).ToList();
|
||||
|
||||
var claimsValidator = new AllowedSkillsClaimsValidator(allowedSkills);
|
||||
|
||||
// If TenantId is specified in config, add the tenant as a valid JWT token issuer for Bot to Skill conversation.
|
||||
// The token issuer for MSI and single tenant scenarios will be the tenant where the bot is registered.
|
||||
var validTokenIssuers = new List<string>();
|
||||
var tenantId = sp.GetService<IConfiguration>().GetSection(MicrosoftAppCredentials.MicrosoftAppTenantIdKey)?.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
// For SingleTenant/MSI auth, the JWT tokens will be issued from the bot's home tenant.
|
||||
// Therefore, these issuers need to be added to the list of valid token issuers for authenticating activity requests.
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV2, tenantId));
|
||||
}
|
||||
|
||||
return new AuthenticationConfiguration
|
||||
{
|
||||
ClaimsValidator = claimsValidator,
|
||||
ValidTokenIssuers = validTokenIssuers
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
|
||||
|
||||
// Register the Bot Framework Adapter with error handling enabled.
|
||||
// Note: some classes use the base BotAdapter so we add an extra registration that pulls the same instance.
|
||||
services.AddSingleton<CloudAdapter, AdapterWithErrorHandler>();
|
||||
services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
services.AddSingleton<BotAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
|
||||
// Register the skills conversation ID factory, the client and the request handler.
|
||||
services.AddSingleton<SkillConversationIdFactoryBase, SkillConversationIdFactory>();
|
||||
services.AddSingleton<ChannelServiceHandlerBase, CloudSkillHandler>();
|
||||
|
||||
// Register the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
|
||||
services.AddSingleton<IStorage, MemoryStorage>();
|
||||
|
||||
// Register Conversation state (used by the Dialog system itself).
|
||||
services.AddSingleton<ConversationState>();
|
||||
|
||||
// Create SetupDialog
|
||||
services.AddSingleton<SetupDialog>();
|
||||
|
||||
// Register the bot as a transient. In this case the ASP Controller is expecting an IBot.
|
||||
services.AddTransient<IBot, HostBot>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Configuration["ChannelService"]))
|
||||
{
|
||||
// Register a ConfigurationChannelProvider -- this is only for Azure Gov.
|
||||
services.AddSingleton<IChannelProvider, ConfigurationChannelProvider>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
/// </summary>
|
||||
/// <param name="app">The application request pipeline to be configured.</param>
|
||||
/// <param name="env">The web hosting environment.</param>
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"MicrosoftAppType": "",
|
||||
"MicrosoftAppId": "",
|
||||
"MicrosoftAppPassword": "",
|
||||
"MicrosoftAppTenantId": "",
|
||||
"ChannelService": "",
|
||||
"SkillHostEndpoint": "http://localhost:35000/api/skills/",
|
||||
"BotFrameworkSkills": [
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Settings;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SimpleHostBotComposer.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBotComposer.Controllers
|
||||
{
|
||||
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
|
||||
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SimpleHostBotComposer.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBotComposer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that handles skill replies to the bot.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace SimpleHostBotComposer
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBotComposer
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
@ -30,4 +30,4 @@ namespace SimpleHostBotComposer
|
|||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime.Extensions;
|
||||
|
@ -6,7 +6,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace SimpleHostBotComposer
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.SimpleHostBotComposer
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
|
|
@ -4,35 +4,33 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Dialogs;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Middleware;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Middleware;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot
|
||||
{
|
||||
public class AdapterWithErrorHandler : CloudAdapter
|
||||
{
|
||||
private readonly BotFrameworkAuthentication _auth;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ConversationState _conversationState;
|
||||
private readonly ILogger _logger;
|
||||
private readonly SkillHttpClient _skillClient;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
|
||||
public AdapterWithErrorHandler(BotFrameworkAuthentication botFrameworkAuthentication, IConfiguration configuration, ILogger<CloudAdapter> logger, ConversationState conversationState, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null)
|
||||
: base(botFrameworkAuthentication, logger)
|
||||
{
|
||||
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, IConfiguration configuration, ILogger<CloudAdapter> logger, ConversationState conversationState, SkillsConfiguration skillsConfig = null)
|
||||
: base(auth, logger)
|
||||
{
|
||||
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_skillClient = skillClient;
|
||||
_skillsConfig = skillsConfig;
|
||||
|
||||
OnTurnError = HandleTurnError;
|
||||
|
@ -77,7 +75,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
|||
|
||||
private async Task EndSkillConversationAsync(ITurnContext turnContext)
|
||||
{
|
||||
if (_skillClient == null || _skillsConfig == null)
|
||||
if (_skillsConfig == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -97,7 +95,8 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
|||
endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true);
|
||||
|
||||
await _conversationState.SaveChangesAsync(turnContext, true);
|
||||
await _skillClient.PostActivityAsync(botId, activeSkill, _skillsConfig.SkillHostEndpoint, (Activity)endOfConversation, CancellationToken.None);
|
||||
using var client = _auth.CreateBotFrameworkClient();
|
||||
await client.PostActivityAsync(botId, activeSkill.AppId, activeSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, endOfConversation.Conversation.Id, (Activity)endOfConversation, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -5,12 +5,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Bots
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Bots
|
||||
{
|
||||
public class RootBot<T> : ActivityHandler
|
||||
where T : Dialog
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot
|
||||
{
|
||||
internal class ComponentSettings
|
||||
{
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Controllers
|
||||
{
|
||||
// This ASP Controller is created to handle a request. Dependency injection will provide the Adapter and IBot
|
||||
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
|
||||
|
|
|
@ -4,17 +4,16 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that handles skill replies to the bot.
|
||||
/// This example uses the <see cref="SkillHandler"/> that is registered as a <see cref="ChannelServiceHandler"/> in startup.cs.
|
||||
/// This example uses the <see cref="CloudSkillHandler"/> that is registered as a <see cref="ChannelServiceHandlerBase"/> in startup.cs.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/skills")]
|
||||
|
@ -22,7 +21,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Controllers
|
|||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SkillController(ChannelServiceHandler handler, ILogger<SkillController> logger)
|
||||
public SkillController(ChannelServiceHandlerBase handler, ILogger<SkillController> logger)
|
||||
: base(handler)
|
||||
{
|
||||
_logger = logger;
|
||||
|
|
|
@ -6,19 +6,17 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Choices;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Dialogs.Sso;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs.Sso;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// The main dialog for this bot. It uses a <see cref="SkillDialog"/> to call skills.
|
||||
|
@ -32,24 +30,21 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs
|
|||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
|
||||
private readonly string _deliveryMode = $"{typeof(MainDialog).FullName}.DeliveryMode";
|
||||
private readonly string _selectedSkillKey = $"{typeof(MainDialog).FullName}.SelectedSkillKey";
|
||||
private readonly BotFrameworkAuthentication _auth;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
// Dependency injection uses this constructor to instantiate MainDialog.
|
||||
public MainDialog(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, SkillsConfiguration skillsConfig, IConfiguration configuration)
|
||||
public MainDialog(BotFrameworkAuthentication auth, ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillsConfiguration skillsConfig, IConfiguration configuration)
|
||||
: base(nameof(MainDialog))
|
||||
{
|
||||
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
var botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
|
||||
_skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig));
|
||||
|
||||
if (skillClient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(skillClient));
|
||||
}
|
||||
|
||||
if (conversationState == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(conversationState));
|
||||
|
@ -62,7 +57,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs
|
|||
AddDialog(new TangentDialog());
|
||||
|
||||
// Create and add SkillDialog instances for the configured skills.
|
||||
AddSkillDialogs(conversationState, conversationIdFactory, skillClient, skillsConfig, botId);
|
||||
AddSkillDialogs(conversationState, conversationIdFactory, skillsConfig, botId);
|
||||
|
||||
// Add ChoicePrompt to render available delivery modes.
|
||||
AddDialog(new ChoicePrompt("DeliveryModePrompt"));
|
||||
|
@ -304,7 +299,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs
|
|||
}
|
||||
|
||||
// Helper method that creates and adds SkillDialog instances for the configured skills.
|
||||
private void AddSkillDialogs(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, SkillsConfiguration skillsConfig, string botId)
|
||||
private void AddSkillDialogs(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillsConfiguration skillsConfig, string botId)
|
||||
{
|
||||
foreach (var skillInfo in _skillsConfig.Skills.Values)
|
||||
{
|
||||
|
@ -313,7 +308,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs
|
|||
{
|
||||
BotId = botId,
|
||||
ConversationIdFactory = conversationIdFactory,
|
||||
SkillClient = skillClient,
|
||||
SkillClient = _auth.CreateBotFrameworkClient(),
|
||||
SkillHostEndpoint = skillsConfig.SkillHostEndpoint,
|
||||
ConversationState = conversationState,
|
||||
Skill = skillInfo
|
||||
|
|
|
@ -5,13 +5,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Choices;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs.Sso
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Dialogs.Sso
|
||||
{
|
||||
/// <summary>
|
||||
/// Helps prepare the host for SSO operations and provides helpers to check the status and invoke the skill.
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs.Sso
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Dialogs.Sso
|
||||
{
|
||||
public class SsoSignInDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple waterfall dialog used to test triggering tangents from <see cref="MainDialog"/>.
|
||||
|
|
|
@ -5,12 +5,11 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Middleware
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses an ILogger instance to log user and bot messages. It filters out ContinueConversation events coming from skill responses.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Skills
|
||||
{
|
||||
public class EchoSkill : SkillDefinition
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends <see cref="BotFrameworkSkill"/> and provides methods to return the actions and the begin activity to start a skill.
|
||||
|
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Skills
|
||||
{
|
||||
public class TeamsSkill : SkillDefinition
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Skills
|
||||
{
|
||||
public class WaterfallSkill : SkillDefinition
|
||||
{
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Skills;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Skills;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that loads Skills information from configuration.
|
||||
|
|
|
@ -1,103 +1,124 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Bots;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot.Dialogs;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson();
|
||||
|
||||
// Register the skills configuration class.
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Bots;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot.Dialogs;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson();
|
||||
|
||||
// Register the skills configuration class.
|
||||
services.AddSingleton<SkillsConfiguration>();
|
||||
|
||||
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
|
||||
|
||||
services.AddSingleton(sp => new AuthenticationConfiguration
|
||||
// Register AuthConfiguration to enable custom claim validation.
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
ClaimsValidator = new AllowedSkillsClaimsValidator(
|
||||
(from skill in sp.GetService<SkillsConfiguration>().Skills.Values select skill.AppId).ToList())
|
||||
var allowedSkills = sp.GetService<SkillsConfiguration>().Skills.Values.Select(skill => skill.AppId).ToList();
|
||||
var claimsValidator = new AllowedSkillsClaimsValidator(allowedSkills);
|
||||
|
||||
// If TenantId is specified in config, add the tenant as a valid JWT token issuer for Bot to Skill conversation.
|
||||
// The token issuer for MSI and single tenant scenarios will be the tenant where the bot is registered.
|
||||
var validTokenIssuers = new List<string>();
|
||||
var tenantId = sp.GetService<IConfiguration>().GetSection(MicrosoftAppCredentials.MicrosoftAppTenantIdKey)?.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
// For SingleTenant/MSI auth, the JWT tokens will be issued from the bot's home tenant.
|
||||
// Therefore, these issuers need to be added to the list of valid token issuers for authenticating activity requests.
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV2, tenantId));
|
||||
}
|
||||
|
||||
return new AuthenticationConfiguration
|
||||
{
|
||||
ClaimsValidator = claimsValidator,
|
||||
ValidTokenIssuers = validTokenIssuers
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
|
||||
|
||||
// Register the Bot Framework Adapter with error handling enabled.
|
||||
// Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so
|
||||
// register the same adapter instance for both types.
|
||||
services.AddSingleton<CloudAdapter, AdapterWithErrorHandler>();
|
||||
services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
services.AddSingleton<BotAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
|
||||
// Register the skills conversation ID factory, the client and the request handler.
|
||||
services.AddSingleton<SkillConversationIdFactoryBase, SkillConversationIdFactory>();
|
||||
services.AddHttpClient<SkillHttpClient>();
|
||||
// Create the Bot Framework Authentication to be used with the Bot Adapter.
|
||||
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
|
||||
|
||||
services.AddSingleton<ChannelServiceHandler, TokenExchangeSkillHandler>();
|
||||
|
||||
// Register the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
|
||||
services.AddSingleton<IStorage, MemoryStorage>();
|
||||
|
||||
// Register Conversation state (used by the Dialog system itself).
|
||||
services.AddSingleton<ConversationState>();
|
||||
services.AddSingleton<UserState>();
|
||||
|
||||
// Register the MainDialog that will be run by the bot.
|
||||
services.AddSingleton<MainDialog>();
|
||||
|
||||
// Register the bot as a transient. In this case the ASP Controller is expecting an IBot.
|
||||
services.AddTransient<IBot, RootBot<MainDialog>>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Uncomment this to support HTTPS.
|
||||
// app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseWebSockets();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Register the Bot Framework Adapter with error handling enabled.
|
||||
// Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so
|
||||
// register the same adapter instance for both types.
|
||||
services.AddSingleton<CloudAdapter, AdapterWithErrorHandler>();
|
||||
services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
services.AddSingleton<BotAdapter>(sp => sp.GetService<CloudAdapter>());
|
||||
|
||||
// Register the skills conversation ID factory, the client and the request handler.
|
||||
services.AddSingleton<SkillConversationIdFactoryBase, SkillConversationIdFactory>();
|
||||
services.AddSingleton<ChannelServiceHandlerBase, CloudSkillHandler>();
|
||||
|
||||
services.AddSingleton<ChannelServiceHandlerBase, TokenExchangeSkillHandler>();
|
||||
|
||||
// Register the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
|
||||
services.AddSingleton<IStorage, MemoryStorage>();
|
||||
|
||||
// Register Conversation state (used by the Dialog system itself).
|
||||
services.AddSingleton<ConversationState>();
|
||||
services.AddSingleton<UserState>();
|
||||
|
||||
// Register the MainDialog that will be run by the bot.
|
||||
services.AddSingleton<MainDialog>();
|
||||
|
||||
// Register the bot as a transient. In this case the ASP Controller is expecting an IBot.
|
||||
services.AddTransient<IBot, RootBot<MainDialog>>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Uncomment this to support HTTPS.
|
||||
// app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseWebSockets();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,20 +8,20 @@ using System.Security.Claims;
|
|||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallHostBot
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="SkillHandler"/> specialized to support SSO Token exchanges.
|
||||
/// A <see cref="CloudSkillHandler"/> specialized to support SSO Token exchanges.
|
||||
/// </summary>
|
||||
public class TokenExchangeSkillHandler : SkillHandler
|
||||
public class TokenExchangeSkillHandler : CloudSkillHandler
|
||||
{
|
||||
private const string WaterfallSkillBot = "WaterfallSkillBot";
|
||||
private const string ComposerSkillBot = "ComposerSkillBot";
|
||||
|
@ -32,36 +32,31 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
|||
private readonly SkillConversationIdFactoryBase _conversationIdFactory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly AuthenticationConfiguration _authConfig;
|
||||
private readonly BotFrameworkAuthentication _botAuth;
|
||||
private readonly BotFrameworkAuthentication _auth;
|
||||
|
||||
public TokenExchangeSkillHandler(
|
||||
BotAdapter adapter,
|
||||
IBot bot,
|
||||
IConfiguration configuration,
|
||||
ICredentialProvider credentialProvider,
|
||||
SkillConversationIdFactoryBase conversationIdFactory,
|
||||
AuthenticationConfiguration authConfig,
|
||||
BotFrameworkAuthentication botAuth,
|
||||
BotFrameworkAuthentication auth,
|
||||
SkillsConfiguration skillsConfig,
|
||||
IChannelProvider channelProvider = null,
|
||||
ILogger<TokenExchangeSkillHandler> logger = null)
|
||||
: base(adapter, bot, conversationIdFactory, credentialProvider, authConfig, channelProvider, logger)
|
||||
: base(adapter, bot, conversationIdFactory, auth, logger)
|
||||
{
|
||||
_adapter = adapter;
|
||||
|
||||
_configuration = configuration;
|
||||
_botAuth = botAuth;
|
||||
_authConfig = authConfig;
|
||||
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
|
||||
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
|
||||
_conversationIdFactory = conversationIdFactory;
|
||||
_skillsConfig = skillsConfig ?? new SkillsConfiguration(configuration);
|
||||
_configuration = configuration;
|
||||
|
||||
_botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
_logger = logger;
|
||||
_logger = logger ?? NullLogger<TokenExchangeSkillHandler>.Instance;
|
||||
}
|
||||
|
||||
protected override async Task<ResourceResponse> OnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (await InterceptOAuthCards(claimsIdentity, activity).ConfigureAwait(false))
|
||||
if (await InterceptOAuthCards(claimsIdentity, activity, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
@ -71,7 +66,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
|||
|
||||
protected override async Task<ResourceResponse> OnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (await InterceptOAuthCards(claimsIdentity, activity).ConfigureAwait(false))
|
||||
if (await InterceptOAuthCards(claimsIdentity, activity, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
@ -91,60 +86,60 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
|||
return _skillsConfig.Skills.Values.FirstOrDefault(s => string.Equals(s.AppId, appId, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
private async Task<bool> InterceptOAuthCards(ClaimsIdentity claimsIdentity, Activity activity)
|
||||
private async Task<bool> InterceptOAuthCards(ClaimsIdentity claimsIdentity, Activity activity, CancellationToken cancellationToken)
|
||||
{
|
||||
var oauthCardAttachment = activity.Attachments?.FirstOrDefault(a => a?.ContentType == OAuthCard.ContentType);
|
||||
if (oauthCardAttachment != null)
|
||||
if (oauthCardAttachment == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetSkill = GetCallingSkill(claimsIdentity);
|
||||
if (targetSkill == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var oauthCard = ((JObject)oauthCardAttachment.Content).ToObject<OAuthCard>();
|
||||
if (string.IsNullOrWhiteSpace(oauthCard?.TokenExchangeResource?.Uri))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using var tokenClient = await _auth.CreateUserTokenClientAsync(claimsIdentity, cancellationToken).ConfigureAwait(false);
|
||||
using var context = new TurnContext(_adapter, activity);
|
||||
context.TurnState.Add<IIdentity>(BotAdapter.BotIdentityKey, claimsIdentity);
|
||||
|
||||
// We need to know what connection name to use for the token exchange so we figure that out here
|
||||
var connectionName = targetSkill.Id.Contains(WaterfallSkillBot) || targetSkill.Id.Contains(ComposerSkillBot) ? _configuration.GetSection("SsoConnectionName").Value : _configuration.GetSection("SsoConnectionNameTeams").Value;
|
||||
|
||||
// AAD token exchange
|
||||
try
|
||||
{
|
||||
var targetSkill = GetCallingSkill(claimsIdentity);
|
||||
if (targetSkill != null)
|
||||
var result = await tokenClient.ExchangeTokenAsync(
|
||||
activity.Recipient.Id,
|
||||
connectionName,
|
||||
activity.ChannelId,
|
||||
new TokenExchangeRequest { Uri = oauthCard.TokenExchangeResource.Uri },
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(result?.Token))
|
||||
{
|
||||
var oauthCard = ((JObject)oauthCardAttachment.Content).ToObject<OAuthCard>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(oauthCard?.TokenExchangeResource?.Uri))
|
||||
{
|
||||
using (var context = new TurnContext(_adapter, activity))
|
||||
{
|
||||
context.TurnState.Add<IIdentity>("BotIdentity", claimsIdentity);
|
||||
|
||||
// We need to know what connection name to use for the token exchange so we figure that out here
|
||||
var connectionName = targetSkill.Id.Contains(WaterfallSkillBot) || targetSkill.Id.Contains(ComposerSkillBot) ? _configuration.GetSection("SsoConnectionName").Value : _configuration.GetSection("SsoConnectionNameTeams").Value;
|
||||
|
||||
// AAD token exchange
|
||||
try
|
||||
{
|
||||
var tokenClient = await _botAuth.CreateUserTokenClientAsync(claimsIdentity, CancellationToken.None).ConfigureAwait(false);
|
||||
var result = await tokenClient.ExchangeTokenAsync(
|
||||
activity.Recipient.Id,
|
||||
connectionName,
|
||||
activity.ChannelId,
|
||||
new TokenExchangeRequest { Uri = oauthCard.TokenExchangeResource.Uri },
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(result?.Token))
|
||||
{
|
||||
// If token above is null, then SSO has failed and hence we return false.
|
||||
// If not, send an invoke to the skill with the token.
|
||||
return await SendTokenExchangeInvokeToSkillAsync(activity, oauthCard.TokenExchangeResource.Id, result.Token, oauthCard.ConnectionName, targetSkill, default).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Show oauth card if token exchange fails.
|
||||
_logger.LogWarning("Unable to exchange token.", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If token above is null, then SSO has failed and hence we return false.
|
||||
// If not, send an invoke to the skill with the token.
|
||||
return await SendTokenExchangeInvokeToSkill(activity, oauthCard.TokenExchangeResource.Id, result.Token, oauthCard.ConnectionName, targetSkill, default).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
// Show oauth card if token exchange fails.
|
||||
_logger.LogWarning("Unable to exchange token.", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> SendTokenExchangeInvokeToSkillAsync(Activity incomingActivity, string id, string token, string connectionName, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
|
||||
private async Task<bool> SendTokenExchangeInvokeToSkill(Activity incomingActivity, string id, string token, string connectionName, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
|
||||
{
|
||||
var activity = incomingActivity.CreateReply();
|
||||
activity.Type = ActivityTypes.Invoke;
|
||||
|
@ -158,13 +153,14 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallHostBot
|
|||
|
||||
var skillConversationReference = await _conversationIdFactory.GetSkillConversationReferenceAsync(incomingActivity.Conversation.Id, cancellationToken).ConfigureAwait(false);
|
||||
activity.Conversation = skillConversationReference.ConversationReference.Conversation;
|
||||
activity.ServiceUrl = skillConversationReference.ConversationReference.ServiceUrl;
|
||||
|
||||
// route the activity to the skill
|
||||
using var client = _botAuth.CreateBotFrameworkClient();
|
||||
var response = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, incomingActivity.Conversation.Id, activity, cancellationToken);
|
||||
|
||||
// Check response status: true if success, false if failure
|
||||
return response.Status >= 200 && response.Status <= 299;
|
||||
using var client = _auth.CreateBotFrameworkClient();
|
||||
var response = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, incomingActivity.Conversation.Id, activity, cancellationToken);
|
||||
|
||||
// Check response status: true if success, false if failure
|
||||
return response.IsSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
},
|
||||
"AllowedHosts": "*",
|
||||
|
||||
"MicrosoftAppType": "",
|
||||
"MicrosoftAppId": "",
|
||||
"MicrosoftAppPassword": "",
|
||||
"MicrosoftAppTenantId": "",
|
||||
"SsoConnectionName": "",
|
||||
"SsoConnectionNameTeams": "",
|
||||
"SkillHostEndpoint": "http://localhost:35020/api/skills/",
|
||||
|
|
|
@ -6,11 +6,10 @@ using System.Collections.Generic;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Bots
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Bots
|
||||
{
|
||||
public class SkillBot<T> : ActivityHandler
|
||||
where T : Dialog
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Controllers
|
||||
{
|
||||
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
|
||||
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using System.IO;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Controllers
|
||||
{
|
||||
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
|
||||
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
|
||||
|
|
|
@ -8,13 +8,12 @@ using System.Security.Claims;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Proactive;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Controllers
|
||||
{
|
||||
[Route("api/notify")]
|
||||
[ApiController]
|
||||
|
|
|
@ -4,22 +4,21 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Controllers
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that handles skill replies to the bot.
|
||||
/// This example uses the <see cref="SkillHandler"/> that is registered as a <see cref="ChannelServiceHandler"/> in startup.cs.
|
||||
/// This example uses the <see cref="CloudSkillHandler"/> that is registered as a <see cref="ChannelServiceHandlerBase"/> in startup.cs.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/skills")]
|
||||
public class SkillController : ChannelServiceController
|
||||
{
|
||||
public SkillController(ChannelServiceHandler handler)
|
||||
public SkillController(ChannelServiceHandlerBase handler)
|
||||
: base(handler)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -6,25 +6,24 @@ using System.Collections.Concurrent;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Auth;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Cards;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Delete;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.FileUpload;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.MessageWithAttachment;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Proactive;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Sso;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Update;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Auth;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Delete;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.FileUpload;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.MessageWithAttachment;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Update;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A root dialog that can route activities sent to the skill to different sub-dialogs.
|
||||
|
@ -32,10 +31,13 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs
|
|||
public class ActivityRouterDialog : ComponentDialog
|
||||
{
|
||||
private static readonly string _echoSkill = "EchoSkill";
|
||||
private readonly BotFrameworkAuthentication _auth;
|
||||
|
||||
public ActivityRouterDialog(IConfiguration configuration, IHttpContextAccessor httpContextAccessor, ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, ConcurrentDictionary<string, ContinuationParameters> continuationParametersStore)
|
||||
public ActivityRouterDialog(BotFrameworkAuthentication auth, IConfiguration configuration, IHttpContextAccessor httpContextAccessor, ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, ConcurrentDictionary<string, ContinuationParameters> continuationParametersStore)
|
||||
: base(nameof(ActivityRouterDialog))
|
||||
{
|
||||
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
|
||||
|
||||
AddDialog(new CardDialog(httpContextAccessor));
|
||||
AddDialog(new MessageWithAttachmentDialog(new Uri($"{httpContextAccessor.HttpContext.Request.Scheme}://{httpContextAccessor.HttpContext.Request.Host.Value}")));
|
||||
AddDialog(new WaitForProactiveDialog(httpContextAccessor, continuationParametersStore));
|
||||
|
@ -45,7 +47,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs
|
|||
AddDialog(new DeleteDialog());
|
||||
AddDialog(new UpdateDialog());
|
||||
|
||||
AddDialog(CreateEchoSkillDialog(conversationState, conversationIdFactory, skillClient, configuration));
|
||||
AddDialog(CreateEchoSkillDialog(conversationState, conversationIdFactory, configuration));
|
||||
|
||||
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { ProcessActivityAsync }));
|
||||
|
||||
|
@ -53,7 +55,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs
|
|||
InitialDialogId = nameof(WaterfallDialog);
|
||||
}
|
||||
|
||||
private static SkillDialog CreateEchoSkillDialog(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, IConfiguration configuration)
|
||||
private SkillDialog CreateEchoSkillDialog(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, IConfiguration configuration)
|
||||
{
|
||||
var botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
|
||||
|
@ -69,14 +71,13 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs
|
|||
{
|
||||
BotId = botId,
|
||||
ConversationIdFactory = conversationIdFactory,
|
||||
SkillClient = skillClient,
|
||||
SkillClient = _auth.CreateBotFrameworkClient(),
|
||||
SkillHostEndpoint = new Uri(skillHostEndpoint),
|
||||
ConversationState = conversationState,
|
||||
Skill = skillInfo
|
||||
};
|
||||
var echoSkillDialog = new SkillDialog(skillDialogOptions);
|
||||
var echoSkillDialog = new SkillDialog(skillDialogOptions, _echoSkill);
|
||||
|
||||
echoSkillDialog.Id = _echoSkill;
|
||||
return echoSkillDialog;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Auth
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Auth
|
||||
{
|
||||
public class AuthDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ using Microsoft.Bot.Schema;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Cards
|
||||
{
|
||||
public static class AdaptiveCardExtensions
|
||||
{
|
||||
|
|
|
@ -9,7 +9,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using AdaptiveCards;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Choices;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
@ -17,7 +16,7 @@ using Microsoft.Bot.Schema.Teams;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Cards
|
||||
{
|
||||
public class CardDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Cards
|
||||
{
|
||||
public enum CardOptions
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ using Microsoft.Bot.Schema;
|
|||
using Microsoft.Bot.Schema.Teams;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Cards
|
||||
{
|
||||
public static class CardSampleHelper
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Cards
|
||||
{
|
||||
public static class ChannelSupportedCards
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Cards
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Cards
|
||||
{
|
||||
public class SampleData
|
||||
{
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Delete
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Delete
|
||||
{
|
||||
public class DeleteDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -5,11 +5,10 @@ using System.IO;
|
|||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.FileUpload
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.FileUpload
|
||||
{
|
||||
public class FileUploadDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -6,12 +6,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Choices;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.MessageWithAttachment
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.MessageWithAttachment
|
||||
{
|
||||
public class MessageWithAttachmentDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using System.Security.Principal;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Proactive
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the information needed to resume a conversation when a proactive message arrives.
|
||||
|
|
|
@ -7,11 +7,10 @@ using System.Security.Principal;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Proactive
|
||||
{
|
||||
public class WaitForProactiveDialog : Dialog
|
||||
{
|
||||
|
|
|
@ -5,13 +5,13 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Choices;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Sso
|
||||
{
|
||||
public class SsoSkillDialog : ComponentDialog
|
||||
{
|
||||
|
@ -54,8 +54,9 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso
|
|||
private async Task<List<Choice>> GetPromptChoicesAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var promptChoices = new List<Choice>();
|
||||
var adapter = (IUserTokenProvider)stepContext.Context.Adapter;
|
||||
var token = await adapter.GetUserTokenAsync(stepContext.Context, _connectionName, null, cancellationToken);
|
||||
var userId = stepContext.Context.Activity?.From?.Id;
|
||||
var userTokenClient = stepContext.Context.TurnState.Get<UserTokenClient>();
|
||||
var token = await userTokenClient.GetUserTokenAsync(userId, _connectionName, stepContext.Context.Activity?.ChannelId, null, cancellationToken);
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
|
@ -75,6 +76,8 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso
|
|||
private async Task<DialogTurnResult> HandleActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var action = ((FoundChoice)stepContext.Result).Value.ToLowerInvariant();
|
||||
var userId = stepContext.Context.Activity?.From?.Id;
|
||||
var userTokenClient = stepContext.Context.TurnState.Get<UserTokenClient>();
|
||||
|
||||
switch (action)
|
||||
{
|
||||
|
@ -82,14 +85,12 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso
|
|||
return await stepContext.BeginDialogAsync(nameof(SsoSkillSignInDialog), null, cancellationToken);
|
||||
|
||||
case "logout":
|
||||
var adapter = (IUserTokenProvider)stepContext.Context.Adapter;
|
||||
await adapter.SignOutUserAsync(stepContext.Context, _connectionName, cancellationToken: cancellationToken);
|
||||
await userTokenClient.SignOutUserAsync(userId, _connectionName, stepContext.Context.Activity?.ChannelId, cancellationToken);
|
||||
await stepContext.Context.SendActivityAsync("You have been signed out.", cancellationToken: cancellationToken);
|
||||
return await stepContext.NextAsync(cancellationToken: cancellationToken);
|
||||
|
||||
case "show token":
|
||||
var tokenProvider = (IUserTokenProvider)stepContext.Context.Adapter;
|
||||
var token = await tokenProvider.GetUserTokenAsync(stepContext.Context, _connectionName, null, cancellationToken);
|
||||
var token = await userTokenClient.GetUserTokenAsync(userId, _connectionName, stepContext.Context.Activity?.ChannelId, null, cancellationToken);
|
||||
if (token == null)
|
||||
{
|
||||
await stepContext.Context.SendActivityAsync("User has no cached token.", cancellationToken: cancellationToken);
|
||||
|
|
|
@ -5,9 +5,8 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Sso
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Sso
|
||||
{
|
||||
public class SsoSkillSignInDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Update
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Update
|
||||
{
|
||||
public class UpdateDialog : ComponentDialog
|
||||
{
|
||||
|
|
|
@ -6,10 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Middleware
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// A middleware that ensures conversation state is saved when an OAuthCard is returned by the skill.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Middleware;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Middleware;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot
|
||||
{
|
||||
public class SkillAdapterWithErrorHandler : CloudAdapter
|
||||
{
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Bots;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs;
|
||||
using Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot.Dialogs.Proactive;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Bots;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs;
|
||||
using Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot.Dialogs.Proactive;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot
|
||||
namespace Microsoft.Bot.Builder.FunctionalTestsBots.WaterfallSkillBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
@ -37,21 +37,33 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot
|
|||
services.AddControllers()
|
||||
.AddNewtonsoftJson();
|
||||
|
||||
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
|
||||
|
||||
// Register AuthConfiguration to enable custom claim validation.
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
// AllowedCallers is the setting in the appsettings.json file that consists of the list of parent bot IDs that are allowed to access the skill.
|
||||
// To add a new parent bot, simply edit the AllowedCallers and add the parent bot's Microsoft app ID to the list.
|
||||
// In this sample, we allow all callers if AllowedCallers contains an "*".
|
||||
var callersSection = Configuration.GetSection(CallersConfigKey);
|
||||
var callers = callersSection.Get<string[]>();
|
||||
if (callers == null)
|
||||
var allowedCallers = new List<string>(sp.GetService<IConfiguration>().GetSection(CallersConfigKey).Get<string[]>());
|
||||
|
||||
var claimsValidator = new AllowedCallersClaimsValidator(allowedCallers);
|
||||
|
||||
// If TenantId is specified in config, add the tenant as a valid JWT token issuer for Bot to Skill conversation.
|
||||
// The token issuer for MSI and single tenant scenarios will be the tenant where the bot is registered.
|
||||
var validTokenIssuers = new List<string>();
|
||||
var tenantId = sp.GetService<IConfiguration>().GetSection(MicrosoftAppCredentials.MicrosoftAppTenantIdKey)?.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
throw new ArgumentNullException($"\"{CallersConfigKey}\" not found in configuration.");
|
||||
// For SingleTenant/MSI auth, the JWT tokens will be issued from the bot's home tenant.
|
||||
// Therefore, these issuers need to be added to the list of valid token issuers for authenticating activity requests.
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV1, tenantId));
|
||||
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV2, tenantId));
|
||||
}
|
||||
|
||||
return new AuthenticationConfiguration { ClaimsValidator = new AllowedCallersClaimsValidator(callers) };
|
||||
return new AuthenticationConfiguration
|
||||
{
|
||||
ClaimsValidator = claimsValidator,
|
||||
ValidTokenIssuers = validTokenIssuers
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
|
||||
|
@ -65,9 +77,7 @@ namespace Microsoft.BotFrameworkFunctionalTests.WaterfallSkillBot
|
|||
// Register the skills conversation ID factory, the client and the request handler.
|
||||
services.AddSingleton<SkillConversationIdFactoryBase, SkillConversationIdFactory>();
|
||||
|
||||
services.AddHttpClient<SkillHttpClient>();
|
||||
|
||||
services.AddSingleton<ChannelServiceHandler, SkillHandler>();
|
||||
services.AddSingleton<ChannelServiceHandlerBase, CloudSkillHandler>();
|
||||
|
||||
// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
|
||||
services.AddSingleton<IStorage, MemoryStorage>();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"MicrosoftAppType": "",
|
||||
"MicrosoftAppId": "",
|
||||
"MicrosoftAppPassword": "",
|
||||
"MicrosoftAppTenantId": "",
|
||||
"ConnectionName": "TestOAuthProvider",
|
||||
"SsoConnectionName": "",
|
||||
"ChannelService": "",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
BotBuilderVersion=~4.15.0
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
# Ignore web.config files generated to deploy JS to IIS on Windows
|
||||
**/web.config
|
||||
**/web.config
|
||||
Utils/temp
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,249 @@
|
|||
import { URL, fileURLToPath, pathToFileURL } from 'url';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import moduleExports, { Module } from 'module';
|
||||
|
||||
var PathType;
|
||||
(function(PathType2) {
|
||||
PathType2[PathType2["File"] = 0] = "File";
|
||||
PathType2[PathType2["Portable"] = 1] = "Portable";
|
||||
PathType2[PathType2["Native"] = 2] = "Native";
|
||||
})(PathType || (PathType = {}));
|
||||
const npath = Object.create(path);
|
||||
const ppath = Object.create(path.posix);
|
||||
npath.cwd = () => process.cwd();
|
||||
ppath.cwd = () => toPortablePath(process.cwd());
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path.posix.resolve(...segments);
|
||||
} else {
|
||||
return path.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
const contains = function(pathUtils, from, to) {
|
||||
from = pathUtils.normalize(from);
|
||||
to = pathUtils.normalize(to);
|
||||
if (from === to)
|
||||
return `.`;
|
||||
if (!from.endsWith(pathUtils.sep))
|
||||
from = from + pathUtils.sep;
|
||||
if (to.startsWith(from)) {
|
||||
return to.slice(from.length);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
npath.contains = (from, to) => contains(npath, from, to);
|
||||
ppath.contains = (from, to) => contains(ppath, from, to);
|
||||
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
|
||||
const UNC_WINDOWS_PATH_REGEXP = /^\\\\(\.\\)?(.*)$/;
|
||||
const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
|
||||
const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
|
||||
function fromPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
let portablePathMatch, uncPortablePathMatch;
|
||||
if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
|
||||
p = portablePathMatch[1];
|
||||
else if (uncPortablePathMatch = p.match(UNC_PORTABLE_PATH_REGEXP))
|
||||
p = `\\\\${uncPortablePathMatch[1] ? `.\\` : ``}${uncPortablePathMatch[2]}`;
|
||||
else
|
||||
return p;
|
||||
return p.replace(/\//g, `\\`);
|
||||
}
|
||||
function toPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
let windowsPathMatch, uncWindowsPathMatch;
|
||||
if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
|
||||
p = `/${windowsPathMatch[1]}`;
|
||||
else if (uncWindowsPathMatch = p.match(UNC_WINDOWS_PATH_REGEXP))
|
||||
p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
|
||||
return p.replace(/\\/g, `/`);
|
||||
}
|
||||
|
||||
const builtinModules = new Set(Module.builtinModules || Object.keys(process.binding(`natives`)));
|
||||
const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request);
|
||||
function readPackageScope(checkPath) {
|
||||
const rootSeparatorIndex = checkPath.indexOf(npath.sep);
|
||||
let separatorIndex;
|
||||
do {
|
||||
separatorIndex = checkPath.lastIndexOf(npath.sep);
|
||||
checkPath = checkPath.slice(0, separatorIndex);
|
||||
if (checkPath.endsWith(`${npath.sep}node_modules`))
|
||||
return false;
|
||||
const pjson = readPackage(checkPath + npath.sep);
|
||||
if (pjson) {
|
||||
return {
|
||||
data: pjson,
|
||||
path: checkPath
|
||||
};
|
||||
}
|
||||
} while (separatorIndex > rootSeparatorIndex);
|
||||
return false;
|
||||
}
|
||||
function readPackage(requestPath) {
|
||||
const jsonPath = npath.resolve(requestPath, `package.json`);
|
||||
if (!fs.existsSync(jsonPath))
|
||||
return null;
|
||||
return JSON.parse(fs.readFileSync(jsonPath, `utf8`));
|
||||
}
|
||||
|
||||
async function tryReadFile(path2) {
|
||||
try {
|
||||
return await fs.promises.readFile(path2, `utf8`);
|
||||
} catch (error) {
|
||||
if (error.code === `ENOENT`)
|
||||
return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
function tryParseURL(str) {
|
||||
try {
|
||||
return new URL(str);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function getFileFormat(filepath) {
|
||||
var _a;
|
||||
const ext = path.extname(filepath);
|
||||
switch (ext) {
|
||||
case `.mjs`: {
|
||||
return `module`;
|
||||
}
|
||||
case `.cjs`: {
|
||||
return `commonjs`;
|
||||
}
|
||||
case `.wasm`: {
|
||||
throw new Error(`Unknown file extension ".wasm" for ${filepath}`);
|
||||
}
|
||||
case `.json`: {
|
||||
throw new Error(`Unknown file extension ".json" for ${filepath}`);
|
||||
}
|
||||
case `.js`: {
|
||||
const pkg = readPackageScope(filepath);
|
||||
if (pkg) {
|
||||
return (_a = pkg.data.type) != null ? _a : `commonjs`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getFormat$1(resolved, context, defaultGetFormat) {
|
||||
const url = tryParseURL(resolved);
|
||||
if ((url == null ? void 0 : url.protocol) !== `file:`)
|
||||
return defaultGetFormat(resolved, context, defaultGetFormat);
|
||||
const format = getFileFormat(fileURLToPath(url));
|
||||
if (format) {
|
||||
return {
|
||||
format
|
||||
};
|
||||
}
|
||||
return defaultGetFormat(resolved, context, defaultGetFormat);
|
||||
}
|
||||
|
||||
async function getSource$1(urlString, context, defaultGetSource) {
|
||||
const url = tryParseURL(urlString);
|
||||
if ((url == null ? void 0 : url.protocol) !== `file:`)
|
||||
return defaultGetSource(urlString, context, defaultGetSource);
|
||||
return {
|
||||
source: await fs.promises.readFile(fileURLToPath(url), `utf8`)
|
||||
};
|
||||
}
|
||||
|
||||
async function load$1(urlString, context, defaultLoad) {
|
||||
const url = tryParseURL(urlString);
|
||||
if ((url == null ? void 0 : url.protocol) !== `file:`)
|
||||
return defaultLoad(urlString, context, defaultLoad);
|
||||
const filePath = fileURLToPath(url);
|
||||
const format = getFileFormat(filePath);
|
||||
if (!format)
|
||||
return defaultLoad(urlString, context, defaultLoad);
|
||||
return {
|
||||
format,
|
||||
source: await fs.promises.readFile(filePath, `utf8`)
|
||||
};
|
||||
}
|
||||
|
||||
const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:node:)?(?:@[^/]+\/)?[^/]+)\/*(.*|)$/;
|
||||
async function resolve$1(originalSpecifier, context, defaultResolver) {
|
||||
var _a;
|
||||
const {findPnpApi} = moduleExports;
|
||||
if (!findPnpApi || isBuiltinModule(originalSpecifier))
|
||||
return defaultResolver(originalSpecifier, context, defaultResolver);
|
||||
let specifier = originalSpecifier;
|
||||
const url = tryParseURL(specifier);
|
||||
if (url) {
|
||||
if (url.protocol !== `file:`)
|
||||
return defaultResolver(originalSpecifier, context, defaultResolver);
|
||||
specifier = fileURLToPath(specifier);
|
||||
}
|
||||
const {parentURL, conditions = []} = context;
|
||||
const issuer = parentURL ? fileURLToPath(parentURL) : process.cwd();
|
||||
const pnpapi = (_a = findPnpApi(issuer)) != null ? _a : url ? findPnpApi(specifier) : null;
|
||||
if (!pnpapi)
|
||||
return defaultResolver(originalSpecifier, context, defaultResolver);
|
||||
const dependencyNameMatch = specifier.match(pathRegExp);
|
||||
let allowLegacyResolve = false;
|
||||
if (dependencyNameMatch) {
|
||||
const [, dependencyName, subPath] = dependencyNameMatch;
|
||||
if (subPath === ``) {
|
||||
const resolved = pnpapi.resolveToUnqualified(`${dependencyName}/package.json`, issuer);
|
||||
if (resolved) {
|
||||
const content = await tryReadFile(resolved);
|
||||
if (content) {
|
||||
const pkg = JSON.parse(content);
|
||||
allowLegacyResolve = pkg.exports == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = pnpapi.resolveRequest(specifier, issuer, {
|
||||
conditions: new Set(conditions),
|
||||
extensions: allowLegacyResolve ? void 0 : []
|
||||
});
|
||||
if (!result)
|
||||
throw new Error(`Resolving '${specifier}' from '${issuer}' failed`);
|
||||
return {
|
||||
url: pathToFileURL(result).href
|
||||
};
|
||||
}
|
||||
|
||||
const binding = process.binding(`fs`);
|
||||
const originalfstat = binding.fstat;
|
||||
const ZIP_FD = 2147483648;
|
||||
binding.fstat = function(...args) {
|
||||
const [fd, useBigint, req] = args;
|
||||
if ((fd & ZIP_FD) !== 0 && useBigint === false && req === void 0) {
|
||||
try {
|
||||
const stats = fs.fstatSync(fd);
|
||||
return new Float64Array([
|
||||
stats.dev,
|
||||
stats.mode,
|
||||
stats.nlink,
|
||||
stats.uid,
|
||||
stats.gid,
|
||||
stats.rdev,
|
||||
stats.blksize,
|
||||
stats.ino,
|
||||
stats.size,
|
||||
stats.blocks
|
||||
]);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return originalfstat.apply(this, args);
|
||||
};
|
||||
|
||||
const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
|
||||
const hasConsolidatedHooks = major > 16 || major === 16 && minor >= 12;
|
||||
const resolve = resolve$1;
|
||||
const getFormat = hasConsolidatedHooks ? void 0 : getFormat$1;
|
||||
const getSource = hasConsolidatedHooks ? void 0 : getSource$1;
|
||||
const load = hasConsolidatedHooks ? load$1 : void 0;
|
||||
|
||||
export { getFormat, getSource, load, resolve };
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче