Ported skillbot to cloud adapter
This commit is contained in:
Gabo Gilabert 2022-01-10 15:23:26 -05:00
Родитель b3cee90a37
Коммит 52feafa2b6
6 изменённых файлов: 63 добавлений и 36 удалений

Просмотреть файл

@ -127,6 +127,8 @@ namespace Microsoft.BotBuilderSamples.RootBot.Dialogs
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)
{
@ -134,14 +136,12 @@ namespace Microsoft.BotBuilderSamples.RootBot.Dialogs
return await stepContext.BeginDialogAsync(nameof(SsoSignInDialog), null, cancellationToken);
case "logout from the root bot":
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,25 +5,24 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
namespace Microsoft.BotBuilderSamples.SkillBot.Bots
{
public class ChildBot<T> : ActivityHandler
public class SkillBot<T> : ActivityHandler
where T : Dialog
{
private readonly ConversationState _conversationState;
private readonly Dialog _dialog;
private readonly Dialog _mainDialog;
public ChildBot(Dialog dialog, ConversationState conversationState)
public SkillBot(ConversationState conversationState, T mainDialog)
{
_dialog = dialog;
_conversationState = conversationState;
_mainDialog = mainDialog;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);

Просмотреть файл

@ -19,8 +19,8 @@ namespace Microsoft.BotBuilderSamples.SkillBot.Dialogs
public ActivityRouterDialog(IConfiguration configuration)
: base(nameof(ActivityRouterDialog))
{
AddDialog(new SsoSkillDialog(configuration));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { ProcessActivityAsync }));
AddDialog(new SsoSkillDialog(configuration.GetSection("ConnectionName")?.Value));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] {ProcessActivityAsync}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);

Просмотреть файл

@ -8,8 +8,8 @@ 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.BotBuilderSamples.SkillBot.Dialogs
{
@ -17,10 +17,10 @@ namespace Microsoft.BotBuilderSamples.SkillBot.Dialogs
{
private readonly string _connectionName;
public SsoSkillDialog(IConfiguration configuration)
public SsoSkillDialog(string connectionName)
: base(nameof(SsoSkillDialog))
{
_connectionName = configuration.GetSection("ConnectionName")?.Value;
_connectionName = connectionName;
if (string.IsNullOrWhiteSpace(_connectionName))
{
throw new ArgumentException("\"ConnectionName\" is not set in configuration");
@ -58,11 +58,13 @@ namespace Microsoft.BotBuilderSamples.SkillBot.Dialogs
private async Task<List<Choice>> GetPromptChoicesAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var promptChoices = new List<Choice>();
var adapter = (IUserTokenProvider)stepContext.Context.Adapter;
// Try to get the token for the current user to determine if it is logged in or not.
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);
// Present different choices depending on the user's sign in status.
var token = await adapter.GetUserTokenAsync(stepContext.Context, _connectionName, null, cancellationToken);
var promptChoices = new List<Choice>();
if (token == null)
{
promptChoices.Add(new Choice("Login to the skill"));
@ -81,6 +83,8 @@ namespace Microsoft.BotBuilderSamples.SkillBot.Dialogs
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)
{
@ -90,14 +94,12 @@ namespace Microsoft.BotBuilderSamples.SkillBot.Dialogs
case "logout from the skill":
// This will just clear the token from the skill.
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);
@ -110,6 +112,7 @@ namespace Microsoft.BotBuilderSamples.SkillBot.Dialogs
return await stepContext.NextAsync(cancellationToken: cancellationToken);
case "end":
// Ends the interaction with the skill.
return new DialogTurnResult(DialogTurnStatus.Complete);
default:

Просмотреть файл

@ -10,7 +10,6 @@ using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder.TraceExtensions;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Microsoft.BotBuilderSamples.SkillBot
@ -23,13 +22,13 @@ namespace Microsoft.BotBuilderSamples.SkillBot
/// <see cref="SendActivitiesAsync"/> to save the state of the conversation when a OAuthPrompt is sent to the parent.
/// This prepares the bot to receive the activity send by the TokenExchangeSkillHandler.
/// </remarks>
public class SsoSkillAdapterWithErrorHandler : BotFrameworkHttpAdapter
public class SsoSkillAdapterWithErrorHandler : CloudAdapter
{
private readonly ConversationState _conversationState;
private readonly ILogger _logger;
public SsoSkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState)
: base(configuration, credentialProvider, authConfig, logger: logger)
public SsoSkillAdapterWithErrorHandler(BotFrameworkAuthentication auth, ConversationState conversationState, ILogger<IBotFrameworkHttpAdapter> logger)
: base(auth, logger)
{
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@ -109,7 +108,7 @@ namespace Microsoft.BotBuilderSamples.SkillBot
{
// Delete the conversationState for the current conversation to prevent the
// bot from getting stuck in a error-loop caused by being in a bad state.
// ConversationState should be thought of as similar to "cookie-state" for a web page.
// ConversationState should be thought of as similar to "cookie-state" for a Web page.
await _conversationState.DeleteAsync(turnContext);
}
catch (Exception ex)

Просмотреть файл

@ -2,11 +2,10 @@
// Licensed under the MIT License.
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.Dialogs;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.BotBuilderSamples.SkillBot.Bots;
@ -32,12 +31,37 @@ namespace Microsoft.BotBuilderSamples.SkillBot
services.AddControllers()
.AddNewtonsoftJson();
// Configure credentials.
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
// Register AuthConfiguration to enable custom claim validation.
var allowedCallers = new List<string>(Configuration.GetSection("AllowedCallers").Get<string[]>());
services.AddSingleton(sp => new AuthenticationConfiguration {ClaimsValidator = new AllowedCallersClaimsValidator(allowedCallers)});
services.AddSingleton(sp =>
{
var allowedCallers = new List<string>(sp.GetService<IConfiguration>().GetSection("AllowedCallers").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))
{
// 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
};
});
// Create the Bot Framework Authentication to be used with the Bot Adapter.
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
// Create the Bot Framework Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, SsoSkillAdapterWithErrorHandler>();
@ -47,10 +71,12 @@ namespace Microsoft.BotBuilderSamples.SkillBot
// Create the Conversation state. (Used by the Dialog system itself.)
services.AddSingleton<ConversationState>();
services.AddSingleton<Dialog, ActivityRouterDialog>();
// The Dialog that will be run by the bot.
services.AddSingleton<ActivityRouterDialog>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, ChildBot<ActivityRouterDialog>>();
services.AddTransient<IBot, SkillBot<ActivityRouterDialog>>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.