Enhance the sample to include Oauth Bot using FIC. (#4020)

This commit is contained in:
Kartik Parihar 2024-10-04 18:34:01 +05:30 коммит произвёл GitHub
Родитель baf38f484d
Коммит 84fab35230
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
13 изменённых файлов: 330 добавлений и 126 удалений

86
.github/workflows/ci-dotnet-samples.yml поставляемый
Просмотреть файл

@ -1,86 +0,0 @@
name: ci-dotnet-samples
env:
ROOT_FOLDER: BotBuilder-Samples/samples/
on:
workflow_dispatch:
pull_request:
branches:
- main
paths:
- "samples/**/*.cs"
jobs:
generate:
name: detect and generate bot matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v3
- name: git diff
uses: technote-space/get-diff-action@v4
with:
PATTERNS: samples/**/*.cs
ABSOLUTE: true
- name: generate matrix
id: set-matrix
shell: pwsh
if: env.GIT_DIFF
run: |
function UpSearchFolder {
param ([String] $path, [String] $file)
while ($path -and !(Test-Path (Join-Path $path $file))) {
$path = Split-Path $path -Parent
}
return $path
}
$paths = @("${{ env.GIT_DIFF_FILTERED }}" -replace "'", "" -split " ")
$rootFolder = "${{ env.ROOT_FOLDER }}"
$result = $paths | ForEach-Object { UpSearchFolder -path $_ -file "*.csproj" } | Get-Unique | ForEach-Object {
$folder = $_
return @{
name = $folder.Substring($folder.IndexOf($rootFolder) + $rootFolder.Length);
folder = $folder;
}
}
"Generated matrix:"
ConvertTo-Json @($result)
$matrix = ConvertTo-Json -Compress @($result)
echo "::set-output name=matrix::$($matrix)"
build:
needs: generate
runs-on: ubuntu-latest
strategy:
matrix:
include: ${{fromJSON(needs.generate.outputs.matrix)}}
fail-fast: false
name: bot - ${{ matrix.name }}
steps:
- uses: actions/checkout@v3
- name: use .net 8.0.x
uses: actions/setup-dotnet@v2
with:
dotnet-version: "8.0.x"
- name: dotnet restore
run: dotnet restore
working-directory: ${{ matrix.folder }}
- name: dotnet build
run: dotnet build --configuration Release --no-restore --nologo --clp:NoSummary
working-directory: ${{ matrix.folder }}

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

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthFederatedCredBot", "AuthFederatedCredBot.csproj", "{0D47DE58-8F94-4E24-9265-77074A356DBE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0D47DE58-8F94-4E24-9265-77074A356DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D47DE58-8F94-4E24-9265-77074A356DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D47DE58-8F94-4E24-9265-77074A356DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D47DE58-8F94-4E24-9265-77074A356DBE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B623C9C4-F8D5-4AAC-AA92-FC01B93080ED}
EndGlobalSection
EndGlobal

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

@ -1,5 +1,8 @@
// Generated with Bot Builder V4 SDK Template for Visual Studio CoreBot v4.22.0
// Copyright (c) Microsoft Corporation. All rights reserved.
// 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;
@ -9,18 +12,36 @@ namespace Microsoft.BotBuilderSamples
{
public class AdapterWithErrorHandler : CloudAdapter
{
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger)
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger, ConversationState conversationState = default)
: base(auth, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
// NOTE: In production environment, you should consider logging this to
// Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
// to add telemetry capture to your bot.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
// Send a message to the user
await turnContext.SendActivityAsync("The bot encountered an error or bug.");
await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code.");
if (conversationState != null)
{
try
{
// 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" in a Web pages.
await conversationState.DeleteAsync(turnContext);
}
catch (Exception e)
{
logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}");
}
}
// Send a trace activity, which will be displayed in the Bot Framework Emulator
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
};

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

@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
<PackageReference Include="Microsoft.Bot.Builder.Dialogs" Version="4.22.8" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.22.8" />
</ItemGroup>

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

@ -0,0 +1,39 @@
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.22.0
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
namespace Microsoft.BotBuilderSamples.Bots
{
public class AuthBotFIC<T> : DialogBot<T> where T : Dialog
{
public AuthBotFIC(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
: base(conversationState, userState, dialog, logger)
{
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot using Federated Credentials. Type anything to get logged in. Type 'logout' to sign-out."), cancellationToken);
}
}
}
protected override async Task OnTokenResponseEventAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Token Response Event Activity.");
// Run the Dialog with the new Token Response Event Activity.
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
}

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

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
namespace Microsoft.BotBuilderSamples
{
// This IBot implementation can run any type of Dialog. The use of type parameterization is to allows multiple different bots
// to be run at different endpoints within the same project. This can be achieved by defining distinct Controller types
// each with dependency on distinct IBot types, this way ASP Dependency Injection can glue everything together without ambiguity.
// The ConversationState is used by the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation,
// and the requirement is that all BotState objects are saved at the end of a turn.
public class DialogBot<T> : ActivityHandler where T : Dialog
{
protected readonly BotState ConversationState;
protected readonly Dialog Dialog;
protected readonly ILogger Logger;
protected readonly BotState UserState;
public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Dialog = dialog;
Logger = logger;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occurred during the turn.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
// Run the Dialog with the new message Activity.
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
}

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

@ -1,31 +0,0 @@
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.22.0
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
namespace Microsoft.BotBuilderSamples.Bots
{
public class EchoBot : ActivityHandler
{
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var replyText = $"Echo: {turnContext.Activity.Text}";
await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var welcomeText = "Hello and welcome to Echo Bot Using Federated Identity Credentials !!";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
}
}
}
}
}

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

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
namespace Microsoft.BotBuilderSamples
{
public class LogoutDialog : ComponentDialog
{
public LogoutDialog(string id, string connectionName)
: base(id)
{
ConnectionName = connectionName;
}
protected string ConnectionName { get; }
protected override async Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken))
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();
if (text == "logout")
{
// The UserTokenClient encapsulates the authentication processes.
var userTokenClient = innerDc.Context.TurnState.Get<UserTokenClient>();
await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false);
await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken);
return await innerDc.CancelAllDialogsAsync(cancellationToken);
}
}
return null;
}
}
}

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

@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Microsoft.BotBuilderSamples
{
public class MainDialog : LogoutDialog
{
protected readonly ILogger Logger;
public MainDialog(IConfiguration configuration, ILogger<MainDialog> logger)
: base(nameof(MainDialog), configuration["ConnectionName"])
{
Logger = logger;
AddDialog(new OAuthPrompt(
nameof(OAuthPrompt),
new OAuthPromptSettings
{
ConnectionName = ConnectionName,
Text = "Please Sign In",
Title = "Sign In",
Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5)
}));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
PromptStepAsync,
LoginStepAsync,
DisplayTokenPhase1Async,
DisplayTokenPhase2Async,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
}
private async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Get the token from the previous step. Note that we could also have gotten the
// token directly from the prompt itself. There is an example of this in the next method.
var tokenResponse = (TokenResponse)stepContext.Result;
if (tokenResponse != null)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("You are now logged in."), cancellationToken);
return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your token?") }, cancellationToken);
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> DisplayTokenPhase1Async(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank you."), cancellationToken);
var result = (bool)stepContext.Result;
if (result)
{
// Call the prompt again because we need the token. The reasons for this are:
// 1. If the user is already logged in we do not need to store the token locally in the bot and worry
// about refreshing it. We can always just call the prompt again to get the token.
// 2. We never know how long it will take a user to respond. By the time the
// user responds the token may have expired. The user would then be prompted to login again.
//
// There is no reason to store the token locally in the bot because we can always just call
// the OAuth prompt to get the token or get a new token if needed.
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), cancellationToken: cancellationToken);
}
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> DisplayTokenPhase2Async(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var tokenResponse = (TokenResponse)stepContext.Result;
if (tokenResponse != null)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Here is your token {tokenResponse.Token}"), cancellationToken);
}
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
}
}

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

@ -20,7 +20,7 @@ This bot has been created using [Bot Framework](https://dev.botframework.com/),
## To try this sample
- In a terminal, navigate to `EchoFICBot`
- In a terminal, navigate to `AuthFederatedCredBot`
```bash
# change into project folder
@ -32,7 +32,7 @@ This bot has been created using [Bot Framework](https://dev.botframework.com/),
- Launch Visual Studio
- File -> Open -> Project/Solution
- Navigate to `samples/csharp_dotnetcore/86.bot-authentication-fic` folder
- Select `EchoFICBot.csproj` file
- Select `AuthFederatedCredBot.csproj` file
- Create an user assigned managed identity.
- Record the client ID of the managed identity and add the same to appsettings.json.
@ -82,7 +82,7 @@ This bot has been created using [Bot Framework](https://dev.botframework.com/),
- Launch Visual Studio
- File -> Open -> Project/Solution
- Navigate to `86.bot-authentication-fic` folder
- Select `EchoFICBot.csproj` file
- Select `AuthFederatedCredBot.csproj` file
- Press `F5` to run the project
## Deploy the bot to Azure

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

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.BotBuilderSamples.Bots;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -41,10 +42,29 @@ namespace Microsoft.BotBuilderSamples
// Create the Bot Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, Bots.EchoBot>();
// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
services.AddSingleton<IStorage, MemoryStorage>();
// Create the User state. (Used in this bot's Dialog implementation.)
services.AddSingleton<UserState>();
// Create the Conversation state. (Used by the Dialog system itself.)
services.AddSingleton<ConversationState>();
// The Dialog that will be run by the bot.
services.AddSingleton<MainDialog>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, AuthBotFIC<MainDialog>>();
services.AddHttpClient().AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

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

@ -2,5 +2,6 @@
"MicrosoftAppType": "",
"MicrosoftAppId": "",
"MicrosoftAppClientId": "",
"MicrosoftAppTenantId": ""
"MicrosoftAppTenantId": "",
"ConnectionName": ""
}

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

@ -86,7 +86,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthCertificateBot", "84.bo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthSNIBot", "85.bot-authentication-sni\AuthSNIBot.csproj", "{95AA9E48-B2A4-40B2-B3D2-DA4B3C1AD652}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoFICBot", "86.bot-authentication-fic\EchoFICBot.csproj", "{14316F6B-1488-449C-B56E-C55A4236A059}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthFederatedCredBot", "86.bot-authentication-fic\AuthFederatedCredBot.csproj", "{14316F6B-1488-449C-B56E-C55A4236A059}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution