Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ease Middleware by allowing injection and minor auth sample cleanup #56

Merged
merged 4 commits into from
Jan 21, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -184,27 +184,17 @@ private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt
{
var cardActionType = ActionTypes.Signin;
var signInResource = await GetTokenClient(turnContext).GetSignInResourceAsync(ConnectionName, turnContext.Activity, null, cancellationToken).ConfigureAwait(false);
var value = signInResource.SignInLink;

// use the SignInLink when
// in speech channel or
// bot is a skill or
// an extra OAuthAppCredentials is being passed in
/*
if (turnContext.Activity.IsFromStreamingConnection()
|| (turnContext.TurnState.Get<ClaimsIdentity>(ChannelAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && ClaimsHelpers.IsBotClaim(botIdentity.Claims)))
{
if (turnContext.Activity.ChannelId == Channels.Emulator)
{
cardActionType = ActionTypes.OpenUrl;
}
}
*/

string value;
if ((ShowSignInLink != null && ShowSignInLink == false) ||
(ShowSignInLink == null && !ChannelRequiresSignInLink(turnContext.Activity.ChannelId)))
{
value = null;
}
else
{
value = signInResource.SignInLink;
}

prompt.Attachments.Add(new Attachment
{
@@ -213,16 +203,16 @@ private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt
{
Text = Text,
ConnectionName = ConnectionName,
Buttons = new[]
{
Buttons =
[
new CardAction
{
Title = Title,
Text = Text,
Type = cardActionType,
Value = value
},
},
],
TokenExchangeResource = signInResource.TokenExchangeResource,
TokenPostResource = signInResource.TokenPostResource
},
11 changes: 10 additions & 1 deletion src/libraries/Hosting/AspNetCore/CloudAdapter.cs
Original file line number Diff line number Diff line change
@@ -42,11 +42,20 @@ public CloudAdapter(
IChannelServiceClientFactory channelServiceClientFactory,
IActivityTaskQueue activityTaskQueue,
ILogger<IBotHttpAdapter> logger = null,
bool async = true) : base(channelServiceClientFactory, logger)
bool async = true,
params Core.Interfaces.IMiddleware[] middlewares) : base(channelServiceClientFactory, logger)
{
_activityTaskQueue = activityTaskQueue ?? throw new ArgumentNullException(nameof(activityTaskQueue));
_async = async;

if (middlewares != null)
{
foreach (var middleware in middlewares)
{
Use(middleware);
}
}

OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
41 changes: 16 additions & 25 deletions src/samples/AuthenticationBot/AuthBot.cs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
using Microsoft.Agents.BotBuilder;
using Microsoft.Agents.Core.Interfaces;
using Microsoft.Agents.Core.Models;
using Microsoft.Agents.Storage;
using Microsoft.Agents.State;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@@ -20,33 +20,20 @@ public class AuthBot : ActivityHandler
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
private readonly OAuthFlow _flow;
private readonly IStorage _storage;
private readonly ConversationState _conversationState;
private FlowState _state;

public AuthBot(IConfiguration configuration, IStorage storage, ILogger<AuthBot> logger)
public AuthBot(IConfiguration configuration, ConversationState conversationState, ILogger<AuthBot> logger)
{
_logger = logger ?? NullLogger<AuthBot>.Instance;
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
_flow = new OAuthFlow("Sign In", "Please sign in", _configuration["ConnectionName"], 30000, null);
}

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
// Read OAuthFlow state for this conversation
var stateKey = GetStorageKey(turnContext);
var items = await _storage.ReadAsync([stateKey], cancellationToken);
_state = items.TryGetValue(stateKey, out object value) ? (FlowState)value : new FlowState();

await base.OnTurnAsync(turnContext, cancellationToken);

// Store any changes to the OAuthFlow state after the turn is complete.
items[stateKey] = _state;
await _storage.WriteAsync(items, cancellationToken);
}

protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
// Display a welcome message
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
@@ -116,6 +103,17 @@ protected override async Task OnSignInInvokeAsync(ITurnContext<IInvokeActivity>
await OnContinueFlow(turnContext, cancellationToken);
}

protected override async Task OnTurnBeginAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
_state = await _conversationState.GetPropertyAsync(turnContext, "flowState", () => new FlowState(), cancellationToken);
}

protected override async Task OnTurnEndAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}

private async Task<TokenResponse> OnContinueFlow(ITurnContext turnContext, CancellationToken cancellationToken)
{
TokenResponse tokenResponse = null;
@@ -140,13 +138,6 @@ private async Task<TokenResponse> OnContinueFlow(ITurnContext turnContext, Cance
_state.FlowStarted = false;
return tokenResponse;
}

private static string GetStorageKey(ITurnContext turnContext)
{
var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId");
var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id");
return $"{channelId}/conversations/{conversationId}/flowState";
}
}

class FlowState
1 change: 1 addition & 0 deletions src/samples/AuthenticationBot/AuthenticationBot.csproj
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@

<ItemGroup>
<ProjectReference Include="..\..\libraries\Authentication\Authentication.Msal\Microsoft.Agents.Authentication.Msal.csproj" />
<ProjectReference Include="..\..\libraries\Core\Microsoft.Agents.State\Microsoft.Agents.State.csproj" />
<ProjectReference Include="..\..\libraries\Hosting\AspNetCore\Microsoft.Agents.Hosting.AspNetCore.csproj" />
<ProjectReference Include="..\..\libraries\Storage\Microsoft.Agents.Storage\Microsoft.Agents.Storage.csproj" />
</ItemGroup>
11 changes: 11 additions & 0 deletions src/samples/AuthenticationBot/Program.cs
Original file line number Diff line number Diff line change
@@ -9,6 +9,9 @@
using Microsoft.Extensions.Logging;
using Microsoft.Agents.Hosting.AspNetCore;
using AuthenticationBot;
using Microsoft.Agents.Core.Interfaces;
using Microsoft.Agents.BotBuilder.Teams;
using Microsoft.Agents.State;

var builder = WebApplication.CreateBuilder(args);

@@ -24,9 +27,17 @@
// Add basic bot functionality
builder.AddBot<AuthBot>();

builder.Services.AddSingleton<IMiddleware[]>((sp) =>
{
return [new TeamsSSOTokenExchangeMiddleware(sp.GetService<IStorage>(), builder.Configuration["ConnectionName"])];
});

// Add IStorage for turn state persistence
builder.Services.AddSingleton<IStorage, MemoryStorage>();

// Create the Conversation state.
builder.Services.AddSingleton<ConversationState>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
45 changes: 22 additions & 23 deletions src/samples/EvalClient/EvalClient.csproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<Project Sdk="Microsoft.NET.Sdk.Worker">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
@@ -16,20 +15,20 @@
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Identity.Client" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" />
</ItemGroup>

</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libraries\Client\Microsoft.Agents.Client\Microsoft.Agents.Client.csproj" />
<ProjectReference Include="..\..\libraries\Client\Microsoft.Agents.CopilotStudio.Client\Microsoft.Agents.CopilotStudio.Client.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\Evaluation Dataset.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\Evaluation Dataset.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
3 changes: 2 additions & 1 deletion src/samples/EvalClient/EvaluationService.cs
Original file line number Diff line number Diff line change
@@ -4,10 +4,11 @@
using System.Globalization;
using CsvHelper.Configuration;
using Microsoft.Agents.CopilotStudio.Client;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.AI;
using Microsoft.Agents.Core.Models;

#nullable disable

namespace EvalClient;

/// <summary>
5 changes: 2 additions & 3 deletions src/samples/EvalClient/Program.cs
Original file line number Diff line number Diff line change
@@ -3,13 +3,12 @@
// See https://aka.ms/new-console-template for more information
using System.ClientModel;
using Microsoft.Agents.CopilotStudio.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using EvalClient;

#nullable disable

// Setup the Direct To Engine client example.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
Original file line number Diff line number Diff line change
@@ -18,12 +18,11 @@ namespace BotConversationSsoQuickstart.Bots
// 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>(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger) : TeamsActivityHandler where T : Dialog
public class DialogBot<T>(ConversationState conversationState, T dialog, ILogger<DialogBot<T>> logger) : TeamsActivityHandler where T : Dialog
{
protected readonly BotState _conversationState = conversationState;
protected readonly Dialog _dialog = dialog;
protected readonly ILogger _logger = logger;
protected readonly BotState _userState = userState;

/// <summary>
/// Handle when a message is addressed to the bot.
@@ -47,7 +46,6 @@ protected override async Task OnTurnEndAsync(ITurnContext turnContext, Cancellat
{
// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -14,8 +14,8 @@
namespace BotConversationSsoQuickstart.Bots
{
// This bot is derived (view DialogBot<T>) from the TeamsActivityHandler class currently included as part of this sample.
public class TeamsBot<T>(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
: DialogBot<T>(conversationState, userState, dialog, logger) where T : Dialog
public class TeamsBot<T>(ConversationState conversationState, T dialog, ILogger<DialogBot<T>> logger)
: DialogBot<T>(conversationState, dialog, logger) where T : Dialog
{
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
12 changes: 8 additions & 4 deletions src/samples/Teams/bot-conversation-sso-quickstart/Program.cs
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Agents.BotBuilder.Teams;
using Microsoft.Agents.Core.Interfaces;

var builder = WebApplication.CreateBuilder(args);

@@ -27,13 +29,15 @@
// Add basic bot functionality
builder.AddBot<TeamsBot<MainDialog>, TeamsSSOAdapter>();

builder.Services.AddSingleton<IMiddleware[]>((sp) =>
{
return [new TeamsSSOTokenExchangeMiddleware(sp.GetService<IStorage>(), builder.Configuration["ConnectionName"])];
});

// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
builder.Services.AddSingleton<IStorage, MemoryStorage>();

// Create the User state. (Used in this bot's Dialog implementation.)
builder.Services.AddSingleton<UserState>();

// Create the Conversation state. (Used by the Dialog system itself.)
// Create the Conversation state.
builder.Services.AddSingleton<ConversationState>();

// The Dialog that will be run by the bot.
Original file line number Diff line number Diff line change
@@ -6,11 +6,9 @@
using Microsoft.Agents.State;
using Microsoft.Agents.Hosting.AspNetCore;
using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue;
using Microsoft.Agents.BotBuilder.Teams;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Agents.BotBuilder;
using Microsoft.Agents.Storage;
using Microsoft.Agents.Core.Interfaces;

namespace BotConversationSsoQuickstart
{
@@ -19,20 +17,14 @@ public class TeamsSSOAdapter : CloudAdapter
public TeamsSSOAdapter(
IChannelServiceClientFactory channelServiceClientFactory,
IActivityTaskQueue activityTaskQueue,
IConfiguration configuration,
ILogger<IBotHttpAdapter> logger,
IStorage storage,
ConversationState conversationState)
: base(channelServiceClientFactory, activityTaskQueue, logger)
ConversationState conversationState,
params IMiddleware[] middlewares)
: base(channelServiceClientFactory, activityTaskQueue, logger, middlewares: middlewares)
{
base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"]));

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}");

// Uncomment below commented line for local debugging..
Loading