From 09ebba139104816ea675e6e7958e479c7c3de6d2 Mon Sep 17 00:00:00 2001 From: singhk97 <115390646+singhk97@users.noreply.github.com> Date: Wed, 7 Feb 2024 15:30:16 -0800 Subject: [PATCH] [C#] feat: Add enableSso property to toggle SSO in user auth scenarios (#1236) ## Linked issues closes: #1194 #991 ## Details * Corresponding JS PR: #1232 for implementation details. ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --------- Co-authored-by: Corina <14900841+corinagum@users.noreply.github.com> --- .../Bot/OAuthBotAuthenticationTests.cs | 113 ++++++++++++++++++ ...essageExtensionsAuthenticationBaseTests.cs | 5 + .../OAuthMessageExtensionsTests.cs | 47 ++++++++ .../OAuthAuthenticationTests.cs | 83 +++++++++++++ .../Bot/OAuthBotAuthentication.cs | 62 +++++++++- .../MessageExtensionsAuthenticationBase.cs | 23 ++-- .../OAuthMessageExtensionsAuthentication.cs | 17 ++- ...TeamsSsoMessageExtensionsAuthentication.cs | 5 + .../Authentication/OAuthAuthentication.cs | 33 +++-- .../Authentication/OAuthSettings.cs | 5 + dotnet/samples/06.auth.oauth.bot/Program.cs | 1 + .../06.auth.oauth.messageExtension/Program.cs | 4 +- .../authentication/OAuthBotAuthentication.ts | 3 +- 13 files changed, 369 insertions(+), 32 deletions(-) create mode 100644 dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs create mode 100644 dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/OAuthMessageExtensionsTests.cs create mode 100644 dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs new file mode 100644 index 000000000..df183e5b4 --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/Bot/OAuthBotAuthenticationTests.cs @@ -0,0 +1,113 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Tests.TestUtils; +using Moq; + +namespace Microsoft.Teams.AI.Tests.Application.Authentication.Bot +{ + internal class TestOAuthBotAuthentication : OAuthBotAuthentication + { + public TestOAuthBotAuthentication(Application app, OAuthSettings oauthSettings, string settingName, IStorage? storage = null) : base(app, oauthSettings, settingName, storage) + { + } + + protected override Task GetSignInResourceAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken = default) + { + return Task.FromResult(new SignInResource() + { + SignInLink = "signInLink", + TokenExchangeResource = new TokenExchangeResource() + { + Id = "id", + Uri = "uri" + }, + TokenPostResource = new TokenPostResource() + { + SasUrl = "sasUrl", + } + }); + } + } + + public class OAuthBotAuthenticationTests + { + [Fact] + public async void Test_CreateOAuthCard_WithSSOEnabled() + { + // Arrange + IActivity sentActivity; + var testAdapter = new SimpleAdapter((activity) => sentActivity = activity); + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TestApplication(new() { Adapter = testAdapter }); + var authSettings = new OAuthSettings() { + ConnectionName = "connectionName", + Title = "title", + Text = "text", + EnableSso = true + }; + + var botAuth = new TestOAuthBotAuthentication(app, authSettings, "connectionName"); + + // Act + var result = await botAuth.CreateOAuthCard(turnContext); + + // Assert + var card = result.Content as OAuthCard; + Assert.NotNull(card); + Assert.Equal(card.Text, authSettings.Text); + Assert.Equal(card.ConnectionName, authSettings.ConnectionName); + Assert.Equal(card.Buttons[0].Title, authSettings.Title); + Assert.Equal(card.Buttons[0].Text, authSettings.Text); + Assert.Equal(card.Buttons[0].Type, "signin"); + Assert.Equal(card.Buttons[0].Value, "signInLink"); + Assert.NotNull(card.TokenExchangeResource); + Assert.Equal(card.TokenExchangeResource.Id, "id"); + Assert.Equal(card.TokenExchangeResource.Uri, "uri"); + Assert.NotNull(card.TokenPostResource); + Assert.Equal(card.TokenPostResource.SasUrl, "sasUrl"); + } + + [Fact] + public async void Test_CreateOAuthCard_WithoutSSO() + { + // Arrange + IActivity sentActivity; + var testAdapter = new SimpleAdapter((activity) => sentActivity = activity); + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TestApplication(new() { Adapter = testAdapter }); + var authSettings = new OAuthSettings() + { + ConnectionName = "connectionName", + Title = "title", + Text = "text", + EnableSso = false + }; + + var botAuth = new TestOAuthBotAuthentication(app, authSettings, "connectionName"); + + // Act + var result = await botAuth.CreateOAuthCard(turnContext); + + // Assert + var card = result.Content as OAuthCard; + Assert.NotNull(card); + Assert.Equal(card.Text, authSettings.Text); + Assert.Equal(card.ConnectionName, authSettings.ConnectionName); + Assert.Equal(card.Buttons[0].Title, authSettings.Title); + Assert.Equal(card.Buttons[0].Text, authSettings.Text); + Assert.Equal(card.Buttons[0].Type, "signin"); + Assert.Equal(card.Buttons[0].Value, "signInLink"); + Assert.Null(card.TokenExchangeResource); + Assert.NotNull(card.TokenPostResource); + Assert.Equal(card.TokenPostResource.SasUrl, "sasUrl"); + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs index e4060cc54..b68ff544f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBaseTests.cs @@ -40,6 +40,11 @@ public override Task HandleSsoTokenExchange(ITurnContext context) } return Task.FromResult(_tokenExchangeResponse); } + + public override bool IsSsoSignIn(ITurnContext context) + { + return context.Activity.Name == MessageExtensionsInvokeNames.QUERY_INVOKE_NAME; + } } internal sealed class TokenExchangeRequest diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/OAuthMessageExtensionsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/OAuthMessageExtensionsTests.cs new file mode 100644 index 000000000..b5d8ff539 --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/MessageExtensions/OAuthMessageExtensionsTests.cs @@ -0,0 +1,47 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; +using Microsoft.Teams.AI.Tests.TestUtils; + +namespace Microsoft.Teams.AI.Tests.Application.Authentication.MessageExtensions +{ + public class OAuthMessageExtensionsTests + { + [Fact] + public void Test_isSsoSignIn_False() + { + // Arrange + var turnContext = new TurnContext(new NotImplementedAdapter(), new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/query", + }); + var oauthSettings = new OAuthSettings() { EnableSso = false }; + var messageExtensionsAuth = new OAuthMessageExtensionsAuthentication(oauthSettings); + + // Act + var result = messageExtensionsAuth.IsSsoSignIn(turnContext); + + // Assert + Assert.False(result); + } + + [Fact] + public void Test_isSsoSignIn_True() + { + // Arrange + var turnContext = new TurnContext(new NotImplementedAdapter(), new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/query", + }); + var oauthSettings = new OAuthSettings() { EnableSso = true }; + var messageExtensionsAuth = new OAuthMessageExtensionsAuthentication(oauthSettings); + + // Act + var result = messageExtensionsAuth.IsSsoSignIn(turnContext); + + // Assert + Assert.True(result); + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs new file mode 100644 index 000000000..e82d81294 --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/Authentication/OAuthAuthenticationTests.cs @@ -0,0 +1,83 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Tests.TestUtils; + +namespace Microsoft.Teams.AI.Tests.Application.Authentication +{ + public class OAuthAuthenticationTests + { + [Fact] + public async void Test_IsUserSignedIn_ReturnsTokenString() + { + // Arrange + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + var oauthSettings = new OAuthSettings() { ConnectionName = "connectionName" }; + var app = new TestApplication(new() + { + Adapter = new SimpleAdapter() + }); + var tokenResponse = new TokenResponse() + { + Token = "validToken", + Expiration = "validExpiration", + ConnectionName = "connectionName", + }; + var auth = new TestOAuthAuthentication(tokenResponse, app, "name", oauthSettings, null); + + + // Act + var result = await auth.IsUserSignedInAsync(turnContext); + + // Assert + Assert.NotNull(result); + Assert.True(result == "validToken"); + } + + [Fact] + public async void Test_IsUserSignedIn_ReturnsNull() + { + // Arrange + var turnContext = TurnStateConfig.CreateConfiguredTurnContext(); + var oauthSettings = new OAuthSettings() { ConnectionName = "connectionName" }; + var app = new TestApplication(new() + { + Adapter = new SimpleAdapter() + }); + var tokenResponse = new TokenResponse() + { + Token = "", // Empty token + Expiration = "", + ConnectionName = "connectionName", + }; + var auth = new TestOAuthAuthentication(tokenResponse, app, "name", oauthSettings, null); + + + // Act + var result = await auth.IsUserSignedInAsync(turnContext); + + // Assert + Assert.Null(result); + } + } + + public class TestOAuthAuthentication : OAuthAuthentication + { + private TokenResponse _tokenResponse; + + internal TestOAuthAuthentication(TokenResponse tokenResponse, Application app, string name, OAuthSettings settings, IStorage? storage) : base(settings, new OAuthMessageExtensionsAuthentication(settings), new OAuthBotAuthentication(app, settings, name, storage)) + { + _tokenResponse = tokenResponse; + } + + internal TestOAuthAuthentication(TokenResponse tokenResponse, OAuthSettings settings, OAuthMessageExtensionsAuthentication messageExtensionAuth, OAuthBotAuthentication botAuthentication) : base(settings, messageExtensionAuth, botAuthentication) + { + _tokenResponse = tokenResponse; + } + + protected override Task GetUserToken(ITurnContext context, string connectionName, CancellationToken cancellationToken = default) + { + return Task.FromResult(_tokenResponse); + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/Bot/OAuthBotAuthentication.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/Bot/OAuthBotAuthentication.cs index 8e197cc98..92727d825 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/Bot/OAuthBotAuthentication.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/Bot/OAuthBotAuthentication.cs @@ -1,5 +1,6 @@ using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; using Microsoft.Teams.AI.Application.Authentication.Bot; using Microsoft.Teams.AI.State; @@ -11,19 +12,22 @@ namespace Microsoft.Teams.AI internal class OAuthBotAuthentication : BotAuthenticationBase where TState : TurnState, new() { - private OAuthPrompt _oauthPrompt; + private readonly OAuthPrompt _oauthPrompt; + private readonly OAuthSettings _oauthSettings; /// /// Initializes the class /// /// The application instance - /// The OAuth prompt settings + /// The OAuth prompt settings /// The name of current authentication handler /// The storage to save turn state - public OAuthBotAuthentication(Application app, OAuthPromptSettings oauthPromptSettings, string settingName, IStorage? storage = null) : base(app, settingName, storage) + public OAuthBotAuthentication(Application app, OAuthSettings oauthSettings, string settingName, IStorage? storage = null) : base(app, settingName, storage) { + this._oauthSettings = oauthSettings; + // Create OAuthPrompt - this._oauthPrompt = new OAuthPrompt("OAuthPrompt", oauthPromptSettings); + this._oauthPrompt = new OAuthPrompt("OAuthPrompt", this._oauthSettings); // Handles deduplication of token exchange event when using SSO with Bot Authentication app.Adapter.Use(new FilteredTeamsSSOTokenExchangeMiddleware(storage ?? new MemoryStorage(), settingName)); @@ -57,7 +61,14 @@ public override async Task RunDialog(ITurnContext context, TSt DialogTurnResult results = await dialogContext.ContinueDialogAsync(cancellationToken); if (results.Status == DialogTurnStatus.Empty) { - results = await dialogContext.BeginDialogAsync(this._oauthPrompt.Id, null, cancellationToken); + Attachment card = await this.CreateOAuthCard(context, cancellationToken); + Activity messageActivity = (Activity)MessageFactory.Attachment(card); + PromptOptions options = new() + { + Prompt = messageActivity, + }; + + results = await dialogContext.BeginDialogAsync(this._oauthPrompt.Id, options, cancellationToken); } return results; } @@ -66,8 +77,47 @@ private async Task CreateDialogContextAsync(ITurnContext context, { IStatePropertyAccessor accessor = new TurnStateProperty(state, "conversation", dialogStateProperty); DialogSet dialogSet = new(accessor); - dialogSet.Add(_oauthPrompt); + dialogSet.Add(this._oauthPrompt); return await dialogSet.CreateContextAsync(context, cancellationToken); } + + public async Task CreateOAuthCard(ITurnContext context, CancellationToken cancellationToken = default) + { + SignInResource signInResource = await GetSignInResourceAsync(context, this._oauthSettings.ConnectionName, cancellationToken); + string? link = signInResource.SignInLink; + TokenExchangeResource? tokenExchangeResource = null; + + if (this._oauthSettings.EnableSso == true) + { + tokenExchangeResource = signInResource.TokenExchangeResource; + } + + return new Attachment + { + ContentType = OAuthCard.ContentType, + Content = new OAuthCard + { + Text = this._oauthSettings.Text, + ConnectionName = this._oauthSettings.ConnectionName, + Buttons = new[] + { + new CardAction + { + Title = this._oauthSettings.Title, + Text = this._oauthSettings.Text, + Type = "signin", + Value = link + }, + }, + TokenExchangeResource = tokenExchangeResource, + TokenPostResource = signInResource.TokenPostResource + }, + }; + } + + protected async virtual Task GetSignInResourceAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken = default) + { + return await UserTokenClientWrapper.GetSignInResourceAsync(context, this._oauthSettings.ConnectionName, cancellationToken); + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBase.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBase.cs index 5c398fefd..391d66f3f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBase.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/MessageExtensionsAuthenticationBase.cs @@ -43,7 +43,7 @@ internal abstract class MessageExtensionsAuthenticationBase // Token exchange failed, asks user to sign in and consent. - Activity response = new() + Activity activity = new() { Type = ActivityTypesEx.InvokeResponse, Value = new InvokeResponse @@ -51,7 +51,7 @@ internal abstract class MessageExtensionsAuthenticationBase Status = 412 } }; - await context.SendActivityAsync(response); + await context.SendActivityAsync(activity); return null; } @@ -63,10 +63,10 @@ internal abstract class MessageExtensionsAuthenticationBase { try { - TokenResponse response = await HandleUserSignIn(context, state.ToString()); - if (!string.IsNullOrEmpty(response.Token)) + TokenResponse tokenResponse = await HandleUserSignIn(context, state.ToString()); + if (!string.IsNullOrEmpty(tokenResponse.Token)) { - return response.Token; + return tokenResponse.Token; } } catch @@ -80,9 +80,9 @@ internal abstract class MessageExtensionsAuthenticationBase string signInLink = await GetSignInLink(context); // Do 'silentAuth' if this is a composeExtension/query request otherwise do normal `auth` flow. - string authType = context.Activity.Name == MessageExtensionsInvokeNames.QUERY_INVOKE_NAME ? "silentAuth" : "auth"; + string authType = IsSsoSignIn(context) ? "silentAuth" : "auth"; - MessagingExtensionResponse resposne = new() + MessagingExtensionResponse response = new() { ComposeExtension = new MessagingExtensionResult { @@ -101,7 +101,7 @@ internal abstract class MessageExtensionsAuthenticationBase }, }; - await context.SendActivityAsync(ActivityUtilities.CreateInvokeResponseActivity(resposne), cancellationToken); + await context.SendActivityAsync(ActivityUtilities.CreateInvokeResponseActivity(response), cancellationToken); return null; } @@ -141,5 +141,12 @@ public virtual bool IsValidActivity(ITurnContext context) /// The turn context /// The sign in link public abstract Task GetSignInLink(ITurnContext context); + + /// + /// Should sign in using SSO flow. + /// + /// The turn context. + /// A boolean indicating if the sign-in should use SSO flow. + public abstract bool IsSsoSignIn(ITurnContext context); } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/OAuthMessageExtensionsAuthentication.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/OAuthMessageExtensionsAuthentication.cs index 070a9beac..dccc94558 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/OAuthMessageExtensionsAuthentication.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/OAuthMessageExtensionsAuthentication.cs @@ -9,11 +9,11 @@ namespace Microsoft.Teams.AI /// internal class OAuthMessageExtensionsAuthentication : MessageExtensionsAuthenticationBase { - private string _oauthConnectionName; + private readonly OAuthSettings _oauthSettings; - public OAuthMessageExtensionsAuthentication(string oauthConnectionName) + public OAuthMessageExtensionsAuthentication(OAuthSettings oauthSettings) { - _oauthConnectionName = oauthConnectionName; + _oauthSettings = oauthSettings; } /// @@ -23,7 +23,7 @@ public OAuthMessageExtensionsAuthentication(string oauthConnectionName) /// The sign in link public override async Task GetSignInLink(ITurnContext context) { - SignInResource signInResource = await UserTokenClientWrapper.GetSignInResourceAsync(context, _oauthConnectionName); + SignInResource signInResource = await UserTokenClientWrapper.GetSignInResourceAsync(context, _oauthSettings.ConnectionName); return signInResource.SignInLink; } @@ -35,7 +35,7 @@ public override async Task GetSignInLink(ITurnContext context) /// The token response if successfully verified the magic code public override async Task HandleUserSignIn(ITurnContext context, string magicCode) { - return await UserTokenClientWrapper.GetUserTokenAsync(context, _oauthConnectionName, magicCode); + return await UserTokenClientWrapper.GetUserTokenAsync(context, _oauthSettings.ConnectionName, magicCode); } /// @@ -49,10 +49,15 @@ public override async Task HandleSsoTokenExchange(ITurnContext co TokenExchangeRequest? tokenExchangeRequest = value["authentication"]?.ToObject(); if (tokenExchangeRequest != null && !string.IsNullOrEmpty(tokenExchangeRequest.Token)) { - return await UserTokenClientWrapper.ExchangeTokenAsync(context, _oauthConnectionName, tokenExchangeRequest); + return await UserTokenClientWrapper.ExchangeTokenAsync(context, _oauthSettings.ConnectionName, tokenExchangeRequest); } return new TokenResponse(); } + + public override bool IsSsoSignIn(ITurnContext context) + { + return context.Activity.Name == MessageExtensionsInvokeNames.QUERY_INVOKE_NAME && _oauthSettings.EnableSso == true; + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/TeamsSsoMessageExtensionsAuthentication.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/TeamsSsoMessageExtensionsAuthentication.cs index 154914353..7b5f44f94 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/TeamsSsoMessageExtensionsAuthentication.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/MessageExtensions/TeamsSsoMessageExtensionsAuthentication.cs @@ -83,6 +83,11 @@ public override async Task HandleSsoTokenExchange(ITurnContext co return new TokenResponse(); } + public override bool IsSsoSignIn(ITurnContext context) + { + return context.Activity.Name == MessageExtensionsInvokeNames.QUERY_INVOKE_NAME; + } + public override bool IsValidActivity(ITurnContext context) { return base.IsValidActivity(context) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthAuthentication.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthAuthentication.cs index bd9364ad6..732101aac 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthAuthentication.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthAuthentication.cs @@ -1,9 +1,11 @@ using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Schema; using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.State; +using System.Runtime.CompilerServices; +using System.Threading; +[assembly: InternalsVisibleTo("Microsoft.Teams.AI.Tests")] namespace Microsoft.Teams.AI { /// @@ -12,9 +14,9 @@ namespace Microsoft.Teams.AI public class OAuthAuthentication : IAuthentication where TState : TurnState, new() { - private OAuthPromptSettings _settings; - private OAuthMessageExtensionsAuthentication? _messageExtensionAuth; - private OAuthBotAuthentication? _botAuthentication; + private readonly OAuthSettings _settings; + private readonly OAuthMessageExtensionsAuthentication? _messageExtensionAuth; + private readonly OAuthBotAuthentication? _botAuthentication; /// /// Initializes the class @@ -23,11 +25,21 @@ public class OAuthAuthentication : IAuthentication /// The authentication name. /// The settings to initialize the class /// The storage to use. - public OAuthAuthentication(Application app, string name, OAuthSettings settings, IStorage? storage) + public OAuthAuthentication(Application app, string name, OAuthSettings settings, IStorage? storage) : this(settings, new OAuthMessageExtensionsAuthentication(settings), new OAuthBotAuthentication(app, settings, name, storage)) + { + } + + /// + /// Initializes the class + /// + /// The settings to initialize the class + /// The bot authentication instance + /// The message extension authentication instance + internal OAuthAuthentication(OAuthSettings settings, OAuthMessageExtensionsAuthentication messageExtensionAuth, OAuthBotAuthentication botAuthentication) { _settings = settings; - _messageExtensionAuth = new OAuthMessageExtensionsAuthentication(_settings.ConnectionName); - _botAuthentication = new OAuthBotAuthentication(app, _settings, name, storage); + _messageExtensionAuth = messageExtensionAuth; + _botAuthentication = botAuthentication; } /// @@ -38,7 +50,7 @@ public OAuthAuthentication(Application app, string name, OAuthSettings s /// The token if the user is signed. Otherwise null. public async Task IsUserSignedInAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { - TokenResponse tokenResponse = await UserTokenClientWrapper.GetUserTokenAsync(turnContext, _settings.ConnectionName, "", cancellationToken); + TokenResponse tokenResponse = await GetUserToken(turnContext, _settings.ConnectionName, cancellationToken); if (tokenResponse != null && tokenResponse.Token != string.Empty) { @@ -120,5 +132,10 @@ public async Task SignOutUserAsync(ITurnContext context, TState state, Cancellat await UserTokenClientWrapper.SignoutUserAsync(context, _settings.ConnectionName, cancellationToken); } + + protected virtual async Task GetUserToken(ITurnContext context, string connectionName, CancellationToken cancellationToken = default) + { + return await UserTokenClientWrapper.GetUserTokenAsync(context, connectionName, "", cancellationToken); + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthSettings.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthSettings.cs index 5abb45841..0f40b05e2 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthSettings.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/Authentication/OAuthSettings.cs @@ -11,5 +11,10 @@ public class OAuthSettings : OAuthPromptSettings /// The token exchange uri for SSO in adaptive card auth scenario. /// public string? TokenExchangeUri { get; set; } + + /// + /// Set to `true` to enable SSO when authenticating using Azure Active Directory (AAD). + /// + public bool? EnableSso { get; set; } } } diff --git a/dotnet/samples/06.auth.oauth.bot/Program.cs b/dotnet/samples/06.auth.oauth.bot/Program.cs index fd8a23a4e..32db36773 100644 --- a/dotnet/samples/06.auth.oauth.bot/Program.cs +++ b/dotnet/samples/06.auth.oauth.bot/Program.cs @@ -44,6 +44,7 @@ Title = "Sign In", Text = "Please sign in to use the bot.", EndOnInvalidMessage = true, + EnableSso = true, } ); diff --git a/dotnet/samples/06.auth.oauth.messageExtension/Program.cs b/dotnet/samples/06.auth.oauth.messageExtension/Program.cs index 96c022236..d86097cdc 100644 --- a/dotnet/samples/06.auth.oauth.messageExtension/Program.cs +++ b/dotnet/samples/06.auth.oauth.messageExtension/Program.cs @@ -52,8 +52,8 @@ Title = "Sign In", Text = "Please sign in to use the bot.", EndOnInvalidMessage = true, - } - ); + EnableSso = true, + }); Application app = new ApplicationBuilder() .WithStorage(storage) diff --git a/js/packages/teams-ai/src/authentication/OAuthBotAuthentication.ts b/js/packages/teams-ai/src/authentication/OAuthBotAuthentication.ts index b92ad624d..64d8014e3 100644 --- a/js/packages/teams-ai/src/authentication/OAuthBotAuthentication.ts +++ b/js/packages/teams-ai/src/authentication/OAuthBotAuthentication.ts @@ -123,12 +123,11 @@ export class OAuthBotAuthentication extends BotAuthent */ private async createOAuthCard(context: TurnContext): Promise { const signInResource = await getSignInResource(context, this._oauthSettings); - let link = signInResource.signInLink; + const link = signInResource.signInLink; let tokenExchangeResource; if (this._oauthSettings.enableSso == true) { tokenExchangeResource = signInResource.tokenExchangeResource; - link = undefined; } return CardFactory.oauthCard(