diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/.gitignore b/dotnet/samples/06.auth.teamsSSO.messageExtension/.gitignore new file mode 100644 index 0000000000..986967299e --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/.gitignore @@ -0,0 +1,26 @@ +# VS +.vs + +# User-specific files +*.user + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Teams Toolkit +env/.env.*.user +appPackage/build +build +.deployment +# Teams Toolkit Local Development +appsettings.Development.json +env/.env.local diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/AdapterWithErrorHandler.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..984f40bf78 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/AdapterWithErrorHandler.cs @@ -0,0 +1,29 @@ +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; + +namespace MessageExtensionAuth +{ + public class AdapterWithErrorHandler : CloudAdapter + { + public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger logger) + : 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 : {message}", exception.Message); + + // Send a message to the user + await turnContext.SendActivityAsync($"The bot encountered an unhandled error: {exception.Message}"); + await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); + + // Send a trace activity + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Config.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/Config.cs new file mode 100644 index 0000000000..eca6861ca0 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Config.cs @@ -0,0 +1,13 @@ +namespace MessageExtensionAuth +{ + public class ConfigOptions + { + public string BOT_ID { get; set; } + public string BOT_PASSWORD { get; set; } + public string BOT_DOMAIN { get; set; } + public string AAD_APP_CLIENT_ID { get; set; } + public string AAD_APP_CLIENT_SECRET { get; set; } + public string AAD_APP_TENANT_ID { get; set; } + public string AAD_APP_OAUTH_AUTHORITY_HOST { get; set; } + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Controllers/BotController.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/Controllers/BotController.cs new file mode 100644 index 0000000000..62eeb1e48a --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Controllers/BotController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace MessageExtensionAuth.Controllers +{ + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly CloudAdapter _adapter; + private readonly IBot _bot; + + public BotController(CloudAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + [HttpPost] + public async Task PostAsync(CancellationToken cancellationToken = default) + { + await _adapter.ProcessAsync + ( + Request, + Response, + _bot, + cancellationToken + ); + } + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj new file mode 100644 index 0000000000..827569aaef --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj @@ -0,0 +1,40 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + None + + + + diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.sln b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.sln new file mode 100644 index 0000000000..67db64c316 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33906.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessageExtensionAuth", "MessageExtensionAuth.csproj", "{2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.Build.0 = Release|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {436748E9-F64E-4E1D-9BEE-1AE35954FA4F} + EndGlobalSection +EndGlobal diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Model/CardPackage.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/Model/CardPackage.cs new file mode 100644 index 0000000000..40bb4165d0 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Model/CardPackage.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; + +namespace MessageExtensionAuth.Model +{ + /// + /// The strongly typed NuGet package model for Adaptive Card + /// + public class CardPackage + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("version")] + public string? Version { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("tags")] + public string? Tags { get; set; } + + [JsonProperty("authors")] + public string? Authors { get; set; } + + [JsonProperty("owners")] + public string? Owners { get; set; } + + [JsonProperty("licenseUrl")] + public string? LicenseUrl { get; set; } + + [JsonProperty("projectUrl")] + public string? ProjectUrl { get; set; } + + [JsonProperty("nugetUrl")] + public string? NuGetUrl { get; set; } + + public static CardPackage Create(Package package) + { + return new CardPackage + { + Id = package.Id ?? string.Empty, + Version = package.Version ?? string.Empty, + Description = package.Description ?? string.Empty, + Tags = package.Tags == null ? string.Empty : string.Join(", ", package.Tags), + Authors = package.Authors == null ? string.Empty : string.Join(", ", package.Authors), + Owners = package.Owners == null ? string.Empty : string.Join(", ", package.Owners), + LicenseUrl = package.LicenseUrl ?? string.Empty, + ProjectUrl = package.ProjectUrl ?? string.Empty, + NuGetUrl = $"https://www.nuget.org/packages/{package.Id}" + }; + } + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Model/Package.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/Model/Package.cs new file mode 100644 index 0000000000..5ea928d691 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Model/Package.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; + +namespace MessageExtensionAuth.Model +{ + /// + /// The strongly typed NuGet package search result + /// + public class Package + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("version")] + public string? Version { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("tags")] + public string[]? Tags { get; set; } + + [JsonProperty("authors")] + public string[]? Authors { get; set; } + + [JsonProperty("owners")] + public string[]? Owners { get; set; } + + [JsonProperty("iconUrl")] + public string? IconUrl { get; set; } + + [JsonProperty("licenseUrl")] + public string? LicenseUrl { get; set; } + + [JsonProperty("projectUrl")] + public string? ProjectUrl { get; set; } + + [JsonProperty("packageTypes")] + public object[]? PackageTypes { get; set; } + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Program.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/Program.cs new file mode 100644 index 0000000000..de4b23c975 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Program.cs @@ -0,0 +1,212 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema.Teams; +using Microsoft.Identity.Client; +using Microsoft.Teams.AI; +using Microsoft.Teams.AI.State; +using Microsoft.Identity.Web; +using MessageExtensionAuth; +using MessageExtensionAuth.Model; +using Microsoft.Bot.Schema; +using AdaptiveCards.Templating; +using AdaptiveCards; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600)); +builder.Services.AddHttpContextAccessor(); + +// Prepare Configuration for ConfigurationBotFrameworkAuthentication +var config = builder.Configuration.Get()!; +builder.Configuration["MicrosoftAppType"] = "MultiTenant"; +builder.Configuration["MicrosoftAppId"] = config.BOT_ID; +builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD; + +// Create the Bot Framework Authentication to be used with the Bot Adapter. +builder.Services.AddSingleton(); + +// Create the Cloud Adapter with error handling enabled. +// Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so +// register the same adapter instance for all types. +builder.Services.AddSingleton(); +builder.Services.AddSingleton(sp => sp.GetService()!); +builder.Services.AddSingleton(sp => sp.GetService()!); + +// Create singleton instances for bot application +builder.Services.AddSingleton(); + +builder.Services.AddSingleton(); + +builder.Services.AddSingleton(sp => +{ + IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.AAD_APP_CLIENT_ID) + .WithClientSecret(config.AAD_APP_CLIENT_SECRET) + .WithTenantId(config.AAD_APP_TENANT_ID) + .WithLegacyCacheCompatibility(false) + .Build(); + app.AddInMemoryTokenCache(); // For development purpose only, use distributed cache in production environment + return app; +}); + +// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. +builder.Services.AddTransient(sp => +{ + IStorage storage = sp.GetService()!; + IConfidentialClientApplication msal = sp.GetService(); + string signInLink = $"https://{config.BOT_DOMAIN}/auth-start.html"; + ApplicationOptions applicationOptions = new() + { + Storage = storage, + Authentication = new AuthenticationOptions( + new Dictionary>() + { + { "graph", new TeamsSsoAuthentication(new TeamsSsoSettings(new string[]{"User.Read"}, signInLink, msal)) } + } + ) + }; + + Application app = new(applicationOptions); + + Utilities utilities = sp.GetService()!; + + string packageCardFilePath = Path.Combine(".", "Resources", "PackageCard.json"); + + // Listen for search actions + app.MessageExtensions.OnQuery("searchCmd", async (ITurnContext turnContext, TurnState turnState, Query> query, CancellationToken cancellationToken) => + { + string text = (string)query.Parameters["queryText"]; + int count = query.Count; + + if (text == "profile") + { + string token = turnState.Temp.AuthTokens["graph"]; + if (string.IsNullOrEmpty(token)) + { + throw new Exception("No auth token found in state. Authentication failed."); + } + + SimpleGraphClient client = new SimpleGraphClient(token); + + var profile = await client.GetMyProfile(); + var photoUri = await client.GetMyPhoto(); + + ThumbnailCard heroCard = new ThumbnailCard + { + Text = $"{profile.DisplayName}", + Images = new List { new CardImage(photoUri) }, + }; + + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(HeroCard.ContentType, null, heroCard); + MessagingExtensionResult result = new MessagingExtensionResult("list", "result", new[] { attachment }); + + return result; + } + + Package[] packages = await utilities.SearchPackages(text, count, cancellationToken); + + // Format search results + List attachments = packages.Select(package => new MessagingExtensionAttachment + { + ContentType = HeroCard.ContentType, + Content = new HeroCard + { + Title = package.Id, + Text = package.Description + }, + Preview = new HeroCard + { + Title = package.Id, + Text = package.Description, + Tap = new CardAction + { + Type = "invoke", + Value = package + } + }.ToAttachment() + }).ToList(); + + return new MessagingExtensionResult + { + Type = "result", + AttachmentLayout = "list", + Attachments = attachments + }; + }); + + // Listen for item tap + app.MessageExtensions.OnSelectItem(async (ITurnContext turnContext, TurnState turnState, object item, CancellationToken cancellationToken) => + { + JObject? obj = item as JObject; + CardPackage package = CardPackage.Create(obj!.ToObject()!); + string cardTemplate = await File.ReadAllTextAsync(packageCardFilePath, cancellationToken)!; + string cardContent = new AdaptiveCardTemplate(cardTemplate).Expand(package); + MessagingExtensionAttachment attachment = new() + { + ContentType = AdaptiveCard.ContentType, + Content = JsonConvert.DeserializeObject(cardContent) + }; + + return new MessagingExtensionResult + { + Type = "result", + AttachmentLayout = "list", + Attachments = new List { attachment } + }; + }); + + // Handles when the user clicks the Messaging Extension "Sign Out" command. + app.MessageExtensions.OnFetchTask("signOutCommand", async (context, state, cancellationToken) => + { + if (app.Authentication != null) + { + await app.Authentication.SignOutUserAsync(context, state, cancellationToken: cancellationToken); + } + + return new TaskModuleResponse + { + Task = new TaskModuleContinueResponse + { + Value = new TaskModuleTaskInfo + { + Card = new Attachment + { + Content = new AdaptiveCard(new AdaptiveSchemaVersion("1.0")) + { + Body = new List() { new AdaptiveTextBlock() { Text = "You have been signed out." } }, + Actions = new List() { new AdaptiveSubmitAction() { Title = "Close" } }, + }, + ContentType = AdaptiveCard.ContentType, + }, + Height = 200, + Width = 400, + Title = "Adaptive Card: Inputs", + }, + } + }; + }); + + // Handles the 'Close' button on the confirmation Task Module after the user signs out. + app.MessageExtensions.OnSubmitAction("signOutCommand", (context, state, data, cancellationToken) => + { + return Task.FromResult(new MessagingExtensionActionResponse()); + }); + + return app; +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseStaticFiles(); +app.UseRouting(); +app.MapControllers(); + +app.Run(); diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Properties/launchSettings.json b/dotnet/samples/06.auth.teamsSSO.messageExtension/Properties/launchSettings.json new file mode 100644 index 0000000000..3d5e8ea4b9 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "profiles": { + // Debug project within Teams + "Microsoft Teams (browser)": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "https://teams.microsoft.com/", + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "hotReloadProfile": "aspnetcore" + } + //// Uncomment following profile to debug project only (without launching Teams) + //, + //"Start Project (not in Teams)": { + // "commandName": "Project", + // "dotnetRunMessages": true, + // "applicationUrl": "https://localhost:7130;http://localhost:5130", + // "environmentVariables": { + // "ASPNETCORE_ENVIRONMENT": "Development" + // }, + // "hotReloadProfile": "aspnetcore" + //} + } +} \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/README.md b/dotnet/samples/06.auth.teamsSSO.messageExtension/README.md new file mode 100644 index 0000000000..e002b3ff94 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/README.md @@ -0,0 +1,35 @@ +## Summary + +This sample shows how to incorporate a basic Message Extension app with SSO into a Microsoft Teams application using [Bot Framework](https://dev.botframework.com) and the Teams AI SDK. Users can search nuget.org for packages. + +This sample depends on Teams SSO and gives you more flexibility on how to configure AAD, like using a client certificate. There is no need to create an OAuth Connection in Azure Bot Service to run this sample. + +## Set up instructions + +All the samples in the C# .NET SDK can be set up in the same way. You can find the step by step instructions here: + [Setup Instructions](../README.md). + +## Interacting with the Message Extension + +You can interact with this app by selecting its app icon in the chat compose area. This opens a dialog that allows you to search NuGet for a package. Selecting a package will output an Adaptive Card with its description to the chat. +If you type `profile` in the Message Extension's search box to show the current user's profile in search result. + + +Here's a sample search result: + +![Sample search](assets/search.png) + +And after selecting it outputs an Adaptive Card. + +![Adaptive Card](assets/card.png) + +## Deploy to Azure + +You can use Teams Toolkit for Visual Studio or CLI to host the bot in Azure. The sample includes Bicep templates in the `/infra` directory which are used by the tools to create resources in Azure. + +You can find deployment instructions [here](../README.md#deploy-to-azure). + +## Further reading + +- [Teams Toolkit overview](https://aka.ms/vs-teams-toolkit-getting-started) +- [How Microsoft Teams bots work](https://learn.microsoft.com/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=csharp) \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Resources/PackageCard.json b/dotnet/samples/06.auth.teamsSSO.messageExtension/Resources/PackageCard.json new file mode 100644 index 0000000000..a907b097e4 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Resources/PackageCard.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "size": "Medium", + "weight": "Bolder", + "text": "${id}" + }, + { + "type": "FactSet", + "facts": [ + { + "title": "Version", + "value": "${version}" + }, + { + "title": "Description", + "value": "${description}" + }, + { + "title": "Tags", + "value": "${tags}" + }, + { + "title": "Authors", + "value": "${authors}" + }, + { + "title": "Owners", + "value": "${owners}" + } + ] + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "NuGet", + "url": "${nugetUrl}" + }, + { + "type": "Action.OpenUrl", + "title": "Project", + "url": "${projectUrl}" + }, + { + "type": "Action.OpenUrl", + "title": "License", + "url": "${licenseUrl}" + } + ] +} \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/SimpleGraphClient.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/SimpleGraphClient.cs new file mode 100644 index 0000000000..5686c74776 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/SimpleGraphClient.cs @@ -0,0 +1,54 @@ +using System.Net.Http.Headers; +using Microsoft.Graph; + +namespace MessageExtensionAuth +{ + public class SimpleGraphClient + { + private readonly string _token; + + public SimpleGraphClient(string token) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentNullException(nameof(token)); + } + + _token = token; + } + + //Fetching user's profile + public async Task GetMyProfile() + { + var graphClient = GetAuthenticatedClient(); + return await graphClient.Me.Request().GetAsync(); + } + + public async Task GetMyPhoto() + { + var graphClient = GetAuthenticatedClient(); + var photo = await graphClient.Me.Photo.Content.Request().GetAsync(); + var memoryStream = new MemoryStream(); + await photo.CopyToAsync(memoryStream); + var photoBytes = memoryStream.ToArray(); + var photoBase64 = Convert.ToBase64String(photoBytes); + var photoUri = $"data:image/png;base64,{photoBase64}"; + return photoUri; + } + + // Get an Authenticated Microsoft Graph client using the token issued to the user. + private GraphServiceClient GetAuthenticatedClient() + { + var graphClient = new GraphServiceClient( + new DelegateAuthenticationProvider( + requestMessage => + { + // Append the access token to the request. + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", _token); + + return Task.CompletedTask; + })); + return graphClient; + } + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/Utilities.cs b/dotnet/samples/06.auth.teamsSSO.messageExtension/Utilities.cs new file mode 100644 index 0000000000..09800383b2 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/Utilities.cs @@ -0,0 +1,51 @@ +using MessageExtensionAuth.Model; +using Newtonsoft.Json.Linq; +using System.Collections.Specialized; +using System.Web; + +namespace MessageExtensionAuth +{ + /// + /// Defines the activity handlers. + /// + public class Utilities + { + private readonly HttpClient _httpClient; + + public Utilities(IHttpClientFactory httpClientFactory) + { + _httpClient = httpClientFactory.CreateClient("WebClient"); + } + + + public async Task SearchPackages(string text, int size, CancellationToken cancellationToken) + { + // Call NuGet Search API + NameValueCollection query = HttpUtility.ParseQueryString(string.Empty); + query["q"] = text; + query["take"] = size.ToString(); + string queryString = query.ToString()!; + string responseContent; + try + { + responseContent = await _httpClient.GetStringAsync($"https://azuresearch-usnc.nuget.org/query?{queryString}", cancellationToken); + } + catch (Exception) + { + throw; + } + + if (!string.IsNullOrWhiteSpace(responseContent)) + { + JObject responseObj = JObject.Parse(responseContent); + return responseObj["data"]? + .Select(obj => obj.ToObject()!)? + .ToArray() ?? Array.Empty(); + } + else + { + return Array.Empty(); + } + } + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/aad.manifest.json b/dotnet/samples/06.auth.teamsSSO.messageExtension/aad.manifest.json new file mode 100644 index 0000000000..984ab49ed8 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/aad.manifest.json @@ -0,0 +1,101 @@ +{ + "id": "${{AAD_APP_OBJECT_ID}}", + "appId": "${{AAD_APP_CLIENT_ID}}", + "name": "ME-Auth-aad", + "accessTokenAcceptedVersion": 2, + "signInAudience": "AzureADMyOrg", + "optionalClaims": { + "idToken": [], + "accessToken": [ + { + "name": "idtyp", + "source": null, + "essential": false, + "additionalProperties": [] + } + ], + "saml2Token": [] + }, + "requiredResourceAccess": [ + { + "resourceAppId": "Microsoft Graph", + "resourceAccess": [ + { + "id": "User.Read", + "type": "Scope" + } + ] + } + ], + "oauth2Permissions": [ + { + "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.", + "adminConsentDisplayName": "Teams can access app's web APIs", + "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}", + "isEnabled": true, + "type": "User", + "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have", + "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf", + "value": "access_as_user" + } + ], + "preAuthorizedApplications": [ + { + "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "00000002-0000-0ff1-ce00-000000000000", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4765445b-32c6-49b0-83e6-1d93765276ca", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4345a7b9-9a63-4910-a426-35363201d503", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + } + ], + "identifierUris": [ + "api://botid-${{BOT_ID}}" + ], + "replyUrlsWithType": [ + { + "url": "https://${{BOT_DOMAIN}}/auth-end.html", + "type": "Web" + } + ] +} \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/color.png b/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/color.png new file mode 100644 index 0000000000..4ab1585881 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67c7c063ba4dc41c977080c1f1fa17c897e1c72ec4a6412ed5e681b5d4cb9680 +size 1066 diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/manifest.json b/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/manifest.json new file mode 100644 index 0000000000..5f884e9083 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/manifest.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.package.name", + "name": { + "short": "ME-Auth-${{TEAMSFX_ENV}}", + "full": "ME-Auth" + }, + "developer": { + "name": "Microsoft", + "mpnId": "", + "websiteUrl": "https://microsoft.com", + "privacyUrl": "https://privacy.microsoft.com/privacystatement", + "termsOfUseUrl": "https://www.microsoft.com/legal/terms-of-use" + }, + "description": { + "short": "Example message extension with SSO based authentication", + "full": "Example message extension with SSO based authentication" + }, + "icons": { + "outline": "outline.png", + "color": "color.png" + }, + "accentColor": "#FFFFFF", + "staticTabs": [], + "bots": [], + "composeExtensions": [ + { + "botId": "${{BOT_ID}}", + "canUpdateConfiguration": true, + "commands": [ + { + "id": "searchCmd", + "description": "NuGet Search", + "title": "Search", + "initialRun": false, + "parameters": [ + { + "name": "queryText", + "description": "Enter your search query", + "title": "Query" + } + ] + }, + { + "id": "signOutCommand", + "type": "action", + "title": "Sign Out", + "description": "Sign out from authenticated services.", + "initialRun": false, + "fetchTask": true, + "context": [ + "commandBox", + "compose" + ], + "parameters": [ + { + "name": "param", + "title": "param", + "description": "" + } + ] + } + ] + } + ], + "validDomains": [ + "${{BOT_DOMAIN}}" + ], + "webApplicationInfo": { + "id": "${{BOT_ID}}", + "resource": "api://botid-${{BOT_ID}}" + } +} \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/outline.png b/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/outline.png new file mode 100644 index 0000000000..458549f6df --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/appPackage/outline.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1ddc76f79027d9c0300689721649ce1f1950271a5fc4ca50ae56545228fb566 +size 249 diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/appsettings.json b/dotnet/samples/06.auth.teamsSSO.messageExtension/appsettings.json new file mode 100644 index 0000000000..6d2093d204 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/appsettings.json @@ -0,0 +1,17 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "BOT_ID": "", + "BOT_PASSWORD": "", + "BOT_DOMAIN": "", + "AAD_APP_CLIENT_ID": "", + "AAD_APP_CLIENT_SECRET": "", + "AAD_APP_TENANT_ID": "", + "AAD_APP_OAUTH_AUTHORITY_HOST": "" +} \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/assets/card.png b/dotnet/samples/06.auth.teamsSSO.messageExtension/assets/card.png new file mode 100644 index 0000000000..5b14c31454 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/assets/card.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7b2e6efed3d95ee3889355b25086506b230a9771aebc6ddc7d53aec3d5fbd86 +size 76643 diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/assets/search.png b/dotnet/samples/06.auth.teamsSSO.messageExtension/assets/search.png new file mode 100644 index 0000000000..29dd60e8fe --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/assets/search.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a01969943e05285eda97bfd8ddc06f8ed1026b27d2c21459cace72adcd887bf1 +size 61027 diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/env/.env.dev b/dotnet/samples/06.auth.teamsSSO.messageExtension/env/.env.dev new file mode 100644 index 0000000000..c42bc16fb3 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/env/.env.dev @@ -0,0 +1,23 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +TEAMS_APP_TENANT_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= +AAD_APP_CLIENT_ID= +AAD_APP_OBJECT_ID= +AAD_APP_TENANT_ID= +AAD_APP_OAUTH_AUTHORITY= +AAD_APP_OAUTH_AUTHORITY_HOST= +AAD_APP_ACCESS_AS_USER_PERMISSION_ID= \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/azure.bicep b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/azure.bicep new file mode 100644 index 0000000000..0b9c3de8dc --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/azure.bicep @@ -0,0 +1,81 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location + +param aadAppClientId string +param aadAppTenantId string +param aadAppOauthAuthorityHost string +@secure() +param aadAppClientSecret string + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + ftpsState: 'FtpsOnly' + } + } +} + +resource webAppSettings 'Microsoft.Web/sites/config@2021-02-01' = { + name: '${webAppName}/appsettings' + properties: { + WEBSITE_RUN_FROM_PACKAGE: '1' + BOT_ID: botAadAppClientId + BOT_PASSWORD: botAadAppClientSecret + BOT_DOMAIN: webApp.properties.defaultHostName + AAD_APP_CLIENT_ID: aadAppClientId + AAD_APP_CLIENT_SECRET: aadAppClientSecret + AAD_APP_TENANT_ID: aadAppTenantId + AAD_APP_OAUTH_AUTHORITY_HOST: aadAppOauthAuthorityHost + RUNNING_ON_AZURE: '1' + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/azure.parameters.json b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/azure.parameters.json new file mode 100644 index 0000000000..9f0f18b507 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/azure.parameters.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "ME-Auth${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "ME-Auth" + }, + "aadAppClientId": { + "value": "${{AAD_APP_CLIENT_ID}}" + }, + "aadAppClientSecret": { + "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}" + }, + "aadAppTenantId": { + "value": "${{AAD_APP_TENANT_ID}}" + }, + "aadAppOauthAuthorityHost": { + "value": "${{AAD_APP_OAUTH_AUTHORITY_HOST}}" + } + } +} \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/botRegistration/README.md b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/botRegistration/README.md new file mode 100644 index 0000000000..e912ea21e0 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/botRegistration/README.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/botRegistration/azurebot.bicep b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..ab67c7a56b --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/teamsapp.local.yml b/dotnet/samples/06.auth.teamsSSO.messageExtension/teamsapp.local.yml new file mode 100644 index 0000000000..b8007c20c4 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/teamsapp.local.yml @@ -0,0 +1,120 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json +# +# The teamsapp.local.yml composes automation tasks for Teams Toolkit when running locally. +# This file is used when Debugging (F5) from Visual Studio or with the TeamsFx CLI commands. +# i.e. `teamsfx provision --env local` or `teamsfx deploy --env local`. +# +# You can customize this file. +# Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. +# Visit https://aka.ms/teamsfx-actions for details on actions +version: 1.1.0 + +# Defines what the `provision` lifecycle step does with Teams Toolkit. +# Runs during 'Teams Toolkit -> Prepare Teams App Dependencies' or run manually using `teamsfx provision --env local`. +provision: + + # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + - uses: aadApp/create + with: + # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + name: ME-Auth-aad + # If the value is false, the action will not generate client secret for you + generateClientSecret: true + # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + signInAudience: "AzureADMyOrg" + # Write the information of created resources into environment file for the specified environment variable(s). + writeToEnvironmentFile: + clientId: AAD_APP_CLIENT_ID + # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + clientSecret: SECRET_AAD_APP_CLIENT_SECRET + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Automates the creation of a Teams app registration and saves the App ID to an environment file. + - uses: teamsApp/create + with: + name: ME-Auth-${{TEAMSFX_ENV}} + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Automates the creation an Azure AD app registration which is required for a bot. + # The Bot ID (AAD app client ID) and Bot Password (AAD app client secret) are saved to an environment file. + - uses: botAadApp/create + with: + name: ME-Auth-${{TEAMSFX_ENV}} + writeToEnvironmentFile: + botId: BOT_ID + botPassword: SECRET_BOT_PASSWORD + + # Automates the creation and configuration of a Bot Framework registration which is required for a bot. + # This configures the bot to use the Azure AD app registration created in the previous step. + # Teams Toolkit uses the Visual Studio Dev Tunnel URL and updates BOT_ENDPOINT when debugging (F5). + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: ME-Auth + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + - uses: aadApp/update + with: + # Relative path to this file. Environment variables in manifest will be replaced before apply to AAD app + manifestPath: ./aad.manifest.json + outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Provides the Teams Toolkit .env file values to the apps runtime settings so they can be accessed in source code. + - uses: file/createOrUpdateJsonFile + with: + target: ./appsettings.Development.json + content: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + BOT_DOMAIN: ${{BOT_DOMAIN}} + AAD_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} + AAD_APP_CLIENT_SECRET: ${{SECRET_AAD_APP_CLIENT_SECRET}} + AAD_APP_TENANT_ID: ${{AAD_APP_TENANT_ID}} + AAD_APP_OAUTH_AUTHORITY_HOST: ${{AAD_APP_OAUTH_AUTHORITY_HOST}} + + # Optional: Automates schema and error checking of the Teams app manifest and outputs the results in the console. + - uses: teamsApp/validateManifest + with: + manifestPath: ./appPackage/manifest.json + + # Automates the creation of a Teams app package (.zip). + - uses: teamsApp/zipAppPackage + with: + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + + # Optional: Automates an app package check for errors that would prevent the app from being published and reports any problems. + - uses: teamsApp/validateAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Automates updating the Teams app manifest in Teams Developer Portal using the App ID from the mainfest file. + # This action ensures that any manifest changes are reflected when launching the app again in Teams. + - uses: teamsApp/update + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Provides the debug profile to launch the app in Teams when debugging (F5) from Visual Studio. + - uses: file/createOrUpdateJsonFile + with: + target: ./Properties/launchSettings.json + content: + profiles: + Microsoft Teams (browser): + commandName: "Project" + dotnetRunMessages: true + launchBrowser: true + launchUrl: "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" + applicationUrl: "http://localhost:5130" + environmentVariables: + ASPNETCORE_ENVIRONMENT: "Development" + hotReloadProfile: "aspnetcore" diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/teamsapp.yml b/dotnet/samples/06.auth.teamsSSO.messageExtension/teamsapp.yml new file mode 100644 index 0000000000..76631595e8 --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/teamsapp.yml @@ -0,0 +1,108 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json +# +# The teamsapp.yml composes automation tasks for Teams Toolkit when running other environment configurations. +# This file is used when selecting the Provision, Deploy menu items in the Teams Toolkit menu for Visual Studio +# or with the TeamsFx CLI commands. +# i.e. `teamsfx provision --env {environment name}` or `teamsfx deploy --env {environment name}`. +# +# You can customize this file. +# Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. +# Visit https://aka.ms/teamsfx-actions for details on actions +version: 1.1.0 + +environmentFolderPath: ./env + +# Defines what the `provision` lifecycle step does with Teams Toolkit. +# Runs with the Provision menu or CLI using `teamsfx provision --env {environment name}`. +provision: + # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + - uses: aadApp/create + with: + # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + name: ME-Auth-aad + # If the value is false, the action will not generate client secret for you + generateClientSecret: true + # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + signInAudience: "AzureADMyOrg" + # Write the information of created resources into environment file for the specified environment variable(s). + writeToEnvironmentFile: + clientId: AAD_APP_CLIENT_ID + # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + clientSecret: SECRET_AAD_APP_CLIENT_SECRET + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Automates the creation of a Teams app registration and saves the App ID to an environment file. + - uses: teamsApp/create + with: + name: ME-Auth-${{TEAMSFX_ENV}} + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Automates the creation an Azure AD app registration which is required for a bot. + # The Bot ID (AAD app client ID) and Bot Password (AAD app client secret) are saved to an environment file. + - uses: botAadApp/create + with: + name: ME-Auth-${{TEAMSFX_ENV}} + writeToEnvironmentFile: + botId: BOT_ID + botPassword: SECRET_BOT_PASSWORD + + # Automates the creation of infrastructure defined in ARM templates to host the bot. + # The created resource IDs are saved to an environment file. + - uses: arm/deploy + with: + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep + parameters: ./infra/azure.parameters.json + deploymentName: Create-resources-SearchCommand-${{TEAMSFX_ENV}} + bicepCliVersion: v0.9.1 + + # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + - uses: aadApp/update + with: + # Relative path to this file. Environment variables in manifest will be replaced before apply to AAD app + manifestPath: ./aad.manifest.json + outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Optional: Automates schema and error checking of the Teams app manifest and outputs the results in the console. + - uses: teamsApp/validateManifest + with: + manifestPath: ./appPackage/manifest.json + + # Automates creating a final app package (.zip) by replacing any variables in the manifest.json file for the current environment. + - uses: teamsApp/zipAppPackage + with: + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + + # Optional: Automates an app package check for errors that would prevent the app from being published and reports any problems. + - uses: teamsApp/validateAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Automates updating the Teams app manifest in Teams Developer Portal using the App ID from the mainfest file. + # This action ensures that any manifest changes are reflected when launching the app again in Teams. + - uses: teamsApp/update + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Defines what the `deploy` lifecycle step does with Teams Toolkit. +# Runs with the Deploy menu item or CLI using `teamsfx deploy --env {environment name}`. +deploy: + + # Install any dependencies and build the web app using .NET CLI. + - uses: cli/runDotnetCommand + with: + args: publish --configuration Release --runtime win-x86 --self-contained + + # Deploy to an Azure App Service using the artifact created in the above step. + - uses: azureAppService/zipDeploy + with: + artifactFolder: bin/Release/net6.0/win-x86/publish + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/wwwroot/auth-end.html b/dotnet/samples/06.auth.teamsSSO.messageExtension/wwwroot/auth-end.html new file mode 100644 index 0000000000..ce35ef8c6b --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/wwwroot/auth-end.html @@ -0,0 +1,63 @@ + + + Login End Page + + + + + +
+ + + \ No newline at end of file diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/wwwroot/auth-start.html b/dotnet/samples/06.auth.teamsSSO.messageExtension/wwwroot/auth-start.html new file mode 100644 index 0000000000..64c58bb1cf --- /dev/null +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/wwwroot/auth-start.html @@ -0,0 +1,177 @@ + + + + + Login Start Page + + + + + + + \ No newline at end of file