From e3188c3baaab4c5ef2fee459d548ef3d8f613b5d Mon Sep 17 00:00:00 2001 From: Edwin Bwambale Date: Fri, 3 Dec 2021 15:54:25 -0800 Subject: [PATCH] Fetch and Use AAD Token for authentication --- libraries/Client/Conversations.cs | 8 +- libraries/Client/ConversationsExtensions.cs | 7 +- libraries/Client/IConversations.cs | 2 +- libraries/Client/ITokens.cs | 2 +- libraries/Client/Tokens.cs | 24 ++- libraries/Client/TokensExtensions.cs | 72 ++++++--- libraries/DirectLineClientCredentials.cs | 18 ++- .../Microsoft.Bot.Connector.DirectLine.csproj | 1 + samples/core-DirectLine/DirectLineBot.sln | 21 ++- .../DirectLineClientAcquireToken/App.config | 22 +++ .../DirectLineSampleAcuireTokenClient.csproj | 125 +++++++++++++++ .../Models/DirectLineCardContent.cs | 9 ++ .../Models/Token.cs | 14 ++ .../Models/TokenResponse.cs | 13 ++ .../DirectLineClientAcquireToken/Program.cs | 151 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 +++++ .../TokenHelper.cs | 31 ++++ .../packages.config | 16 ++ 18 files changed, 537 insertions(+), 35 deletions(-) create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/App.config create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/DirectLineSampleAcuireTokenClient.csproj create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/Models/DirectLineCardContent.cs create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/Models/Token.cs create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/Models/TokenResponse.cs create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/Program.cs create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/Properties/AssemblyInfo.cs create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/TokenHelper.cs create mode 100644 samples/core-DirectLine/DirectLineClientAcquireToken/packages.config diff --git a/libraries/Client/Conversations.cs b/libraries/Client/Conversations.cs index 9ff3962..3f02a35 100644 --- a/libraries/Client/Conversations.cs +++ b/libraries/Client/Conversations.cs @@ -20,7 +20,8 @@ namespace Microsoft.Bot.Connector.DirectLine using Microsoft.Rest; using Microsoft.Rest.Serialization; using Newtonsoft.Json; - + using System.Net.Http.Json; + /// /// Conversations operations. @@ -59,7 +60,7 @@ public Conversations(DirectLineClient client) /// /// A response object containing the response body and response headers. /// - public async Task> StartConversationWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> StartConversationWithHttpMessagesAsync(string siteId = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) { // Tracing bool _shouldTrace = ServiceClientTracing.IsEnabled; @@ -92,6 +93,9 @@ public Conversations(DirectLineClient client) } } + // set request param + _httpRequest.Content = JsonContent.Create(new { SiteId = siteId }); + // Serialize Request string _requestContent = null; // Set Credentials diff --git a/libraries/Client/ConversationsExtensions.cs b/libraries/Client/ConversationsExtensions.cs index df3c2c6..62bcdee 100644 --- a/libraries/Client/ConversationsExtensions.cs +++ b/libraries/Client/ConversationsExtensions.cs @@ -1,4 +1,5 @@ -// Copyright (c) Microsoft. All rights reserved. + +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. // Code generated by Microsoft (R) AutoRest Code Generator 0.16.0.0 @@ -40,9 +41,9 @@ public static Conversation StartConversation(this IConversations operations) /// /// The cancellation token. /// - public static async Task StartConversationAsync(this IConversations operations, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task StartConversationAsync(this IConversations operations, string siteId = null, CancellationToken cancellationToken = default(CancellationToken)) { - using (var _result = await operations.StartConversationWithHttpMessagesAsync(null, cancellationToken).ConfigureAwait(false)) + using (var _result = await operations.StartConversationWithHttpMessagesAsync(siteId, null, cancellationToken).ConfigureAwait(false)) { return _result.Body; } diff --git a/libraries/Client/IConversations.cs b/libraries/Client/IConversations.cs index f196973..69f0371 100644 --- a/libraries/Client/IConversations.cs +++ b/libraries/Client/IConversations.cs @@ -29,7 +29,7 @@ public partial interface IConversations /// /// The cancellation token. /// - Task> StartConversationWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + Task> StartConversationWithHttpMessagesAsync(string siteId = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); /// /// Get information about an existing conversation /// diff --git a/libraries/Client/ITokens.cs b/libraries/Client/ITokens.cs index 8e11718..846b94c 100644 --- a/libraries/Client/ITokens.cs +++ b/libraries/Client/ITokens.cs @@ -29,7 +29,7 @@ public partial interface ITokens /// /// The cancellation token. /// - Task> RefreshTokenWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + Task> RefreshTokenWithHttpMessagesAsync(Dictionary> customHeaders = null, string conversationId = null, Func> tokenRefreshCallback = null, CancellationToken cancellationToken = default(CancellationToken)); /// /// Generate a token for a new conversation /// diff --git a/libraries/Client/Tokens.cs b/libraries/Client/Tokens.cs index afed274..faeb003 100644 --- a/libraries/Client/Tokens.cs +++ b/libraries/Client/Tokens.cs @@ -59,11 +59,14 @@ public Tokens(DirectLineClient client) /// /// A response object containing the response body and response headers. /// - public async Task> RefreshTokenWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> RefreshTokenWithHttpMessagesAsync(Dictionary> customHeaders = null, string conversationId = null, Func> tokenRefreshCallback = null, CancellationToken cancellationToken = default(CancellationToken)) { + var newToken = await tokenRefreshCallback(); + // Tracing bool _shouldTrace = ServiceClientTracing.IsEnabled; string _invocationId = null; + if (_shouldTrace) { _invocationId = ServiceClientTracing.NextInvocationId.ToString(); @@ -73,7 +76,8 @@ public Tokens(DirectLineClient client) } // Construct URL var _baseUrl = this.Client.BaseUri.AbsoluteUri; - var _url = new Uri(new Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "v3/directline/tokens/refresh").ToString(); + var path = string .IsNullOrEmpty(newToken) || string.IsNullOrEmpty(conversationId) ? "v3/directline/tokens/refresh" : $"v3/directline/tokens/refresh/{conversationId}"; + var _url = new Uri(new Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), path).ToString(); // Create HTTP transport objects HttpRequestMessage _httpRequest = new HttpRequestMessage(); HttpResponseMessage _httpResponse = null; @@ -99,6 +103,7 @@ public Tokens(DirectLineClient client) { cancellationToken.ThrowIfCancellationRequested(); await this.Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } // Send Request if (_shouldTrace) @@ -106,6 +111,12 @@ public Tokens(DirectLineClient client) ServiceClientTracing.SendRequest(_invocationId, _httpRequest); } cancellationToken.ThrowIfCancellationRequested(); + + if (!(string.IsNullOrEmpty(newToken) || string.IsNullOrEmpty(conversationId))) + { + _httpRequest.Headers.TryAddWithoutValidation(TokensExtensions.Refresh_Token, newToken); + } + _httpResponse = await this.Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); if (_shouldTrace) { @@ -142,6 +153,15 @@ public Tokens(DirectLineClient client) try { _result.Body = SafeJsonConvert.DeserializeObject(_responseContent, this.Client.DeserializationSettings); + + var cc = this.Client.Credentials as DirectLineClientCredentials; + + if(string.Equals(newToken, _result.Body.Token)) + { + // Make the new token the new auth token from here on until next token refresh is called + cc.Authorization = _result.Body.Token; + } + } catch (JsonException ex) { diff --git a/libraries/Client/TokensExtensions.cs b/libraries/Client/TokensExtensions.cs index 59f8441..0e8455f 100644 --- a/libraries/Client/TokensExtensions.cs +++ b/libraries/Client/TokensExtensions.cs @@ -20,33 +20,61 @@ namespace Microsoft.Bot.Connector.DirectLine /// public static partial class TokensExtensions { - /// - /// Refresh a token - /// - /// - /// The operations group for this extension method. - /// - public static Conversation RefreshToken(this ITokens operations) + public static readonly string Refresh_Token = "Refresh-Token"; + + /// + /// Refresh a token + /// + /// + /// The operations group for this extension method. + /// + public static Conversation RefreshToken(this ITokens operations) + { + return Task.Factory.StartNew(s => ((ITokens)s).RefreshTokenAsync(), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Refresh a token + /// + /// + /// The operations group for this extension method. + /// + /// + /// The cancellation token. + /// + public static async Task RefreshTokenAsync(this ITokens operations, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.RefreshTokenWithHttpMessagesAsync(null, null, null, cancellationToken).ConfigureAwait(false)) { - return Task.Factory.StartNew(s => ((ITokens)s).RefreshTokenAsync(), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + return _result.Body; } + } - /// - /// Refresh a token - /// - /// - /// The operations group for this extension method. - /// - /// - /// The cancellation token. - /// - public static async Task RefreshTokenAsync(this ITokens operations, CancellationToken cancellationToken = default(CancellationToken)) + /// + /// Refresh a token + /// + /// + /// The operations group for this extension method. + /// + /// + /// The new AAD token used to continue a conversation. + /// + /// + /// The conversationId of the conversation to be continued. + /// + /// + /// A callback function to fetch an AAD token. Must return a token string. + /// + /// + /// The cancellation token. + /// + public static async Task RefreshTokenAsync(this ITokens operations, string conversationId, Func> tokenRefreshCallback = null, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.RefreshTokenWithHttpMessagesAsync(null, conversationId, tokenRefreshCallback, cancellationToken).ConfigureAwait(false)) { - using (var _result = await operations.RefreshTokenWithHttpMessagesAsync(null, cancellationToken).ConfigureAwait(false)) - { - return _result.Body; - } + return _result.Body; } + } /// /// Generate a token for a new conversation diff --git a/libraries/DirectLineClientCredentials.cs b/libraries/DirectLineClientCredentials.cs index eea7ce2..db1ae50 100644 --- a/libraries/DirectLineClientCredentials.cs +++ b/libraries/DirectLineClientCredentials.cs @@ -24,12 +24,15 @@ public class DirectLineClientCredentials : ServiceClientCredentials #else // .NET Core does not support System.Configuration API's. private static Lazy _secret = new Lazy(() => null); + private static Lazy _token = new Lazy(() => null); private static Lazy _endpoint = new Lazy(() => null); #endif public string Secret { get; private set; } - public string Authorization { get; private set; } + public string Token { get; private set; } + + public string Authorization { get; internal set; } public string Endpoint { get; protected set; } @@ -44,6 +47,19 @@ public DirectLineClientCredentials(string secret = null, string endpoint = null) this.Endpoint = endpoint ?? _endpoint.Value ?? "https://directline.botframework.com/"; } + /// + /// Create a new instance of the DirectLineClientCredentials class + /// + /// default will come from Settings["DirectLineSecret"] + /// default will come from Settings["AadToken"] + public DirectLineClientCredentials(string secret, string token, string endpoint = null) + { + this.Secret = secret ?? _secret.Value; + this.Token = token ?? _token.Value; + this.Authorization = this.Secret ?? this.Token; + this.Endpoint = endpoint ?? _endpoint.Value ?? "https://directline.botframework.com/"; + } + /// /// Apply the credentials to the HTTP request. /// diff --git a/libraries/Microsoft.Bot.Connector.DirectLine.csproj b/libraries/Microsoft.Bot.Connector.DirectLine.csproj index c214402..48db56d 100644 --- a/libraries/Microsoft.Bot.Connector.DirectLine.csproj +++ b/libraries/Microsoft.Bot.Connector.DirectLine.csproj @@ -27,5 +27,6 @@ + \ No newline at end of file diff --git a/samples/core-DirectLine/DirectLineBot.sln b/samples/core-DirectLine/DirectLineBot.sln index 98de69d..37b74d6 100644 --- a/samples/core-DirectLine/DirectLineBot.sln +++ b/samples/core-DirectLine/DirectLineBot.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31910.167 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DirectLineBot", "DirectLineBot\DirectLineBot.csproj", "{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectLineBot", "DirectLineBot\DirectLineBot.csproj", "{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DirectLineSampleClient", "DirectLineClient\DirectLineSampleClient.csproj", "{10935995-5C58-438B-B5F0-FA94BEA2667F}" EndProject @@ -12,6 +12,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "README", "README", "{24EE8F README.md = README.md EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Connector.DirectLine", "..\..\libraries\Microsoft.Bot.Connector.DirectLine.csproj", "{222A7014-9EA6-4CCB-8F1E-8DBAF58668CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DirectLineSampleAcuireTokenClient", "DirectLineClientAcquireToken\DirectLineSampleAcuireTokenClient.csproj", "{305A8431-1C17-4BF2-A0B6-8AC984D8F8AD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,8 +30,19 @@ Global {10935995-5C58-438B-B5F0-FA94BEA2667F}.Debug|Any CPU.Build.0 = Debug|Any CPU {10935995-5C58-438B-B5F0-FA94BEA2667F}.Release|Any CPU.ActiveCfg = Release|Any CPU {10935995-5C58-438B-B5F0-FA94BEA2667F}.Release|Any CPU.Build.0 = Release|Any CPU + {222A7014-9EA6-4CCB-8F1E-8DBAF58668CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {222A7014-9EA6-4CCB-8F1E-8DBAF58668CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {222A7014-9EA6-4CCB-8F1E-8DBAF58668CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {222A7014-9EA6-4CCB-8F1E-8DBAF58668CA}.Release|Any CPU.Build.0 = Release|Any CPU + {305A8431-1C17-4BF2-A0B6-8AC984D8F8AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {305A8431-1C17-4BF2-A0B6-8AC984D8F8AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {305A8431-1C17-4BF2-A0B6-8AC984D8F8AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {305A8431-1C17-4BF2-A0B6-8AC984D8F8AD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {53F48480-F4AA-49AE-AE20-BB0564D0C452} + EndGlobalSection EndGlobal diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/App.config b/samples/core-DirectLine/DirectLineClientAcquireToken/App.config new file mode 100644 index 0000000..7b97251 --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/App.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/DirectLineSampleAcuireTokenClient.csproj b/samples/core-DirectLine/DirectLineClientAcquireToken/DirectLineSampleAcuireTokenClient.csproj new file mode 100644 index 0000000..22e46c1 --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/DirectLineSampleAcuireTokenClient.csproj @@ -0,0 +1,125 @@ + + + + + Debug + AnyCPU + {305A8431-1C17-4BF2-A0B6-8AC984D8F8AD} + Exe + Properties + DirectLineSampleClient + DirectLineSampleClient + v4.6.1 + 512 + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.Rest.ClientRuntime.2.3.4\lib\net45\Microsoft.Rest.ClientRuntime.dll + True + + + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\System.Net.Http.Json.6.0.0\lib\net461\System.Net.Http.Json.dll + + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + + ..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.6.0.0\lib\net461\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + + + + + + + + + + + + + + + + + + + + + + + {222a7014-9ea6-4ccb-8f1e-8dbaf58668ca} + Microsoft.Bot.Connector.DirectLine + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/Models/DirectLineCardContent.cs b/samples/core-DirectLine/DirectLineClientAcquireToken/Models/DirectLineCardContent.cs new file mode 100644 index 0000000..e37e0d4 --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/Models/DirectLineCardContent.cs @@ -0,0 +1,9 @@ +namespace DirectLineSampleClient.Models +{ + public class DirectLineCardContent + { + public string Text { get; set; } + + public string Title { get; set; } + } +} \ No newline at end of file diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/Models/Token.cs b/samples/core-DirectLine/DirectLineClientAcquireToken/Models/Token.cs new file mode 100644 index 0000000..6513b3f --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/Models/Token.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DirectLineSampleClient.Models +{ + public class Token + { + // You can add more properties as in the token response object + public string accessToken { get; set; } + } +} diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/Models/TokenResponse.cs b/samples/core-DirectLine/DirectLineClientAcquireToken/Models/TokenResponse.cs new file mode 100644 index 0000000..87c095a --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/Models/TokenResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DirectLineSampleClient.Models +{ + public class TokenResponse + { + public Token Token { get; set; } + } +} diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/Program.cs b/samples/core-DirectLine/DirectLineClientAcquireToken/Program.cs new file mode 100644 index 0000000..fbf3bdd --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/Program.cs @@ -0,0 +1,151 @@ +namespace DirectLineSampleClient +{ + using System; + using System.Configuration; + using System.Diagnostics; + using System.Linq; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading.Tasks; + using Microsoft.Bot.Connector.DirectLine; + using Models; + using Newtonsoft.Json; + + public class Program + { + private static string directLineSecret = null; + private static string token = null; // First initialized when a conversation is started. + private static string siteId = ""; // Add Site ID from Direct Line config channel here. + private static string botId = ""; // Add bot ID here + private static string fromUser = "DirectLineSampleClientUser"; + + public static void Main(string[] args) + { + StartBotConversation().Wait(); + } + + private static async Task StartBotConversation() + { + token = await TokenHelper.GetTokenAsync(); + + // if you are using a region-specific endpoint, change the uri and uncomment the code + var directLineUri = "https://directline.scratch.botframework.com/"; // endpoint in Azure Public Cloud + // If using token then first parameter of DirectLineClientCredentials must always be null + DirectLineClient client = new DirectLineClient(new Uri(directLineUri), new DirectLineClientCredentials(null, token, null)); + + var conversation = await client.Conversations.StartConversationAsync(siteId); + + new System.Threading.Thread(async () => await ReadBotMessagesAsync(client, conversation.ConversationId)).Start(); + + // This is just to help validate that token refresh works by refreshing the conversion with a new AAD token. + // The next activity post will be sent with a new token. + // In an ideal project this should be in a token refresh loop that keeps on updating the AAD token token before it expires + var conv = await client.Tokens.RefreshTokenAsync(conversation.ConversationId, () => TokenHelper.GetTokenAsync()); + + if (!string.Equals(conversation?.ConversationId, conv?.ConversationId)) + { + throw new Exception("Token not successfully refreshed. New conversation created."); + } + + if (string.Equals(conversation?.Token, conv?.Token)) + { + throw new Exception("Token not successfully refreshed. No new refresh token."); + } + + // Update the token in case it is to be used in here and hereafter + if (!(string.IsNullOrEmpty(conv?.Token) && string.IsNullOrEmpty(conv?.ConversationId))) + { + token = conv.Token; + } + + Console.Write("Command> "); + + while (true) + { + string input = Console.ReadLine().Trim(); + + if (input.ToLower() == "exit") + { + break; + } + else + { + if (input.Length > 0) + { + Activity userMessage = new Activity + { + From = new ChannelAccount(fromUser), + Text = input, + Type = ActivityTypes.Message + }; + + await client.Conversations.PostActivityAsync(conversation.ConversationId, userMessage); + } + } + } + } + + private static async Task ReadBotMessagesAsync(DirectLineClient client, string conversationId) + { + string watermark = null; + + while (true) + { + var activitySet = await client.Conversations.GetActivitiesAsync(conversationId, watermark); + watermark = activitySet?.Watermark; + + var activities = from x in activitySet.Activities + where x.From.Id == botId + select x; + + foreach (Activity activity in activities) + { + Console.WriteLine(activity.Text); + + if (activity.Attachments != null) + { + foreach (Attachment attachment in activity.Attachments) + { + switch (attachment.ContentType) + { + case "application/vnd.microsoft.card.hero": + RenderHeroCard(attachment); + break; + + case "image/png": + Console.WriteLine($"Opening the requested image '{attachment.ContentUrl}'"); + + Process.Start(attachment.ContentUrl); + break; + } + } + } else + { + Console.WriteLine(activity?.AsMessageActivity()); + } + + Console.Write("Command> "); + } + + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); + } + } + + private static void RenderHeroCard(Attachment attachment) + { + const int Width = 70; + Func contentLine = (content) => string.Format($"{{0, -{Width}}}", string.Format("{0," + ((Width + content.Length) / 2).ToString() + "}", content)); + + var heroCard = JsonConvert.DeserializeObject(attachment.Content.ToString()); + + if (heroCard != null) + { + Console.WriteLine("/{0}", new string('*', Width + 1)); + Console.WriteLine("*{0}*", contentLine(heroCard.Title)); + Console.WriteLine("*{0}*", new string(' ', Width)); + Console.WriteLine("*{0}*", contentLine(heroCard.Text)); + Console.WriteLine("{0}/", new string('*', Width + 1)); + } + } + } +} diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/Properties/AssemblyInfo.cs b/samples/core-DirectLine/DirectLineClientAcquireToken/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..41b3efe --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DirectLineSampleClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DirectLineSampleClient")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("10935995-5c58-438b-b5f0-fa94bea2667f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/TokenHelper.cs b/samples/core-DirectLine/DirectLineClientAcquireToken/TokenHelper.cs new file mode 100644 index 0000000..781867b --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/TokenHelper.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using DirectLineSampleClient.Models; + +namespace DirectLineSampleClient +{ + public class TokenHelper + { + private static HttpClient client = new HttpClient(); + // Your Token source service endpoint. + private static string tokenRefreshEndpoint = "http://localhost:3000/token/refresh"; + + public static async Task GetTokenAsync() + { + HttpResponseMessage response = await client.GetAsync(tokenRefreshEndpoint); + if (response.IsSuccessStatusCode) + { + var tokenResponse = await response.Content.ReadAsAsync(); + + return tokenResponse.Token.accessToken; + } + + throw new Exception("Request Failed!"); + + } + } +} diff --git a/samples/core-DirectLine/DirectLineClientAcquireToken/packages.config b/samples/core-DirectLine/DirectLineClientAcquireToken/packages.config new file mode 100644 index 0000000..f4dd23b --- /dev/null +++ b/samples/core-DirectLine/DirectLineClientAcquireToken/packages.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file