From a6a1e9265b2963e08bf7034feb2d59f1942e8d2f Mon Sep 17 00:00:00 2001 From: Gary Pretty Date: Wed, 1 Jul 2020 15:48:45 +0100 Subject: [PATCH] Update Facebook, Slack, Webex adapters (#4214) * Update Facebook, Slack, Webex adapters to follow pattern from Twilio adapter * Tweak exception messages --- .../FacebookAdapter.cs | 15 +- .../FacebookAdapterOptions.cs | 61 -- .../FacebookClientWrapper.cs | 6 +- .../FacebookClientWrapperOptions.cs | 62 ++ .../SlackAdapter.cs | 26 +- .../SlackAdapterOptions.cs | 85 +-- .../SlackClientWrapper.cs | 47 +- .../SlackClientWrapperOptions.cs | 72 ++ .../SlackHelper.cs | 28 +- .../WebexAdapter.cs | 17 +- .../WebexAdapterOptions.cs | 43 +- .../WebexClientWrapper.cs | 626 +----------------- .../WebexClientWrapperOptions.cs | 52 ++ .../FacebookAdapterTests.cs | 59 +- .../FacebookWrapperTests.cs | 2 +- .../SlackAdapterTests.cs | 74 ++- .../SlackClientWrapperTests.cs | 2 +- .../SlackHelperTests.cs | 26 +- .../WebexAdapterTests.cs | 46 +- .../WebexHelperTests.cs | 2 +- 20 files changed, 382 insertions(+), 969 deletions(-) create mode 100644 libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapperOptions.cs create mode 100644 libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapperOptions.cs create mode 100644 libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapperOptions.cs diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapter.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapter.cs index 607e2fe4d7..e4c91923f8 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapter.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapter.cs @@ -48,23 +48,24 @@ public class FacebookAdapter : BotAdapter, IBotFrameworkHttpAdapter /// - `AppSecret`, the secret used to validate incoming webhooks. /// - `AccessToken`, an access token for the bot. /// + /// An instance of . /// The logger this adapter should use. - public FacebookAdapter(IConfiguration configuration, ILogger logger = null) - : this(new FacebookAdapterOptions(configuration["FacebookVerifyToken"], configuration["FacebookAppSecret"], configuration["FacebookAccessToken"]), logger) + public FacebookAdapter(IConfiguration configuration, FacebookAdapterOptions options = null, ILogger logger = null) + : this(new FacebookClientWrapper(new FacebookClientWrapperOptions(configuration["FacebookVerifyToken"], configuration["FacebookAppSecret"], configuration["FacebookAccessToken"])), options, logger) { } /// /// Initializes a new instance of the class using an existing Facebook client. /// + /// /// Client used to interact with the Facebook API. /// Options for the Facebook Adapter. /// The logger this adapter should use. - /// Client used to interact with the Facebook API. /// is null. - public FacebookAdapter(FacebookAdapterOptions options, ILogger logger = null, FacebookClientWrapper facebookClient = null) + public FacebookAdapter(FacebookClientWrapper facebookClient, FacebookAdapterOptions options, ILogger logger = null) { - _options = options ?? throw new ArgumentNullException(nameof(options)); - _facebookClient = facebookClient ?? new FacebookClientWrapper(options); + _options = options ?? new FacebookAdapterOptions(); + _facebookClient = facebookClient ?? throw new ArgumentNullException(nameof(facebookClient)); _logger = logger ?? NullLogger.Instance; } @@ -241,7 +242,7 @@ public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpRespons if (!_facebookClient.VerifySignature(httpRequest, stringifiedBody) && _options.VerifyIncomingRequests) { await FacebookHelper.WriteAsync(httpResponse, HttpStatusCode.Unauthorized, string.Empty, Encoding.UTF8, cancellationToken).ConfigureAwait(false); - throw new AuthenticationException("WARNING: Webhook received message with invalid signature. Potential malicious behavior!"); + throw new AuthenticationException("Webhook received message with invalid signature. Potential malicious behavior!"); } FacebookResponseEvent facebookResponseEvent = null; diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapterOptions.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapterOptions.cs index 7cb601d803..5409cd49cf 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapterOptions.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookAdapterOptions.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; -using System.Threading.Tasks; - namespace Microsoft.Bot.Builder.Adapters.Facebook { /// @@ -11,69 +8,11 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook /// public class FacebookAdapterOptions { - /// - /// Initializes a new instance of the class. - /// - /// The token used to validate that incoming webhooks are originated from Facebook. - /// The app secret. - /// The Facebook access token. - /// A token for validating the origin of incoming webhooks. - /// A token for a bot to work on a single workspace. - public FacebookAdapterOptions(string verifyToken, string appSecret, string accessToken, string apiHost = "graph.facebook.com", string apiVersion = "v3.2") - { - FacebookVerifyToken = verifyToken; - FacebookAppSecret = appSecret; - FacebookAccessToken = accessToken; - FacebookApiHost = apiHost; - FacebookApiVersion = apiVersion; - } - - /// - /// Gets or sets the alternate root URL used to construct calls to Facebook's API. Defaults to "graph.facebook.com" but can be changed (for mocking, proxy, etc). - /// - /// The API host. - public string FacebookApiHost { get; set; } - - /// - /// Gets or sets the alternate API version used to construct calls to Facebook's API. Defaults to "v3.2". - /// - /// The API version. - public string FacebookApiVersion { get; set; } - - /// - /// Gets or sets the verify token used to initially create and verify the webhooks subscription settings on Facebook's developer portal. - /// - /// The verify token. - public string FacebookVerifyToken { get; set; } - - /// - /// Gets or sets the app secret from the **Basic Settings** page from your app's configuration in the Facebook developer portal. - /// - /// The app secret. - public string FacebookAppSecret { get; set; } - - /// - /// Gets or sets the access token. - /// When bound to a single page, use `access_token` to specify the "page access token" provided in the Facebook developer portal's "Access Tokens" widget of the "Messenger Settings" page. - /// - /// The access token. - public string FacebookAccessToken { get; set; } - /// /// Gets or sets a value indicating whether incoming requests should be verified. /// Should be set to true in Production but can be set to false for testing or development purposes. /// /// The flag to indicate if incoming requests should be verified. public bool VerifyIncomingRequests { get; set; } = true; - - /// - /// Throws a exception in all cases. - /// - /// The page ID. - /// The access token for the page. - public virtual Task GetAccessTokenForPageAsync(string pageId) - { - throw new NotImplementedException(); - } } } diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapper.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapper.cs index 9fdd73c6c8..4f9ff16bb0 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapper.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapper.cs @@ -21,15 +21,15 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook public class FacebookClientWrapper { /// - /// An instance of the FacebookAdapterOptions class. + /// An instance of the FacebookClientWrapperOptions class. /// - private readonly FacebookAdapterOptions _options; + private readonly FacebookClientWrapperOptions _options; /// /// Initializes a new instance of the class. /// /// An object containing API credentials, a webhook verification token, and other options. - public FacebookClientWrapper(FacebookAdapterOptions options) + public FacebookClientWrapper(FacebookClientWrapperOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapperOptions.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapperOptions.cs new file mode 100644 index 0000000000..bbfdb3d5c5 --- /dev/null +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Facebook/FacebookClientWrapperOptions.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.Bot.Builder.Adapters.Facebook +{ + /// + /// Options class for Facebook Adapter. + /// + public class FacebookClientWrapperOptions + { + /// + /// Initializes a new instance of the class. + /// + /// The token used to validate that incoming webhooks are originated from Facebook. + /// The app secret. + /// The Facebook access token. + /// A token for validating the origin of incoming webhooks. + /// A token for a bot to work on a single workspace. + public FacebookClientWrapperOptions(string verifyToken, string appSecret, string accessToken, string apiHost = "graph.facebook.com", string apiVersion = "v3.2") + { + FacebookVerifyToken = verifyToken; + FacebookAppSecret = appSecret; + FacebookAccessToken = accessToken; + FacebookApiHost = apiHost; + FacebookApiVersion = apiVersion; + } + + /// + /// Gets or sets the alternate root URL used to construct calls to Facebook's API. Defaults to "graph.facebook.com" but can be changed (for mocking, proxy, etc). + /// + /// The API host. + public string FacebookApiHost { get; set; } + + /// + /// Gets or sets the alternate API version used to construct calls to Facebook's API. Defaults to "v3.2". + /// + /// The API version. + public string FacebookApiVersion { get; set; } + + /// + /// Gets or sets the verify token used to initially create and verify the webhooks subscription settings on Facebook's developer portal. + /// + /// The verify token. + public string FacebookVerifyToken { get; set; } + + /// + /// Gets or sets the app secret from the **Basic Settings** page from your app's configuration in the Facebook developer portal. + /// + /// The app secret. + public string FacebookAppSecret { get; set; } + + /// + /// Gets or sets the access token. + /// When bound to a single page, use `access_token` to specify the "page access token" provided in the Facebook developer portal's "Access Tokens" widget of the "Messenger Settings" page. + /// + /// The access token. + public string FacebookAccessToken { get; set; } + } +} diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapter.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapter.cs index 506345f86f..a84deca26f 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapter.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapter.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Security.Authentication; using System.Security.Claims; using System.Security.Principal; using System.Text; @@ -27,6 +28,7 @@ public class SlackAdapter : BotAdapter, IBotFrameworkHttpAdapter { private readonly SlackClientWrapper _slackClient; private readonly ILogger _logger; + private readonly SlackAdapterOptions _options; /// /// Initializes a new instance of the class using configuration settings. @@ -38,9 +40,10 @@ public class SlackAdapter : BotAdapter, IBotFrameworkHttpAdapter /// SlackBotToken: A token for a bot to work on a single workspace. /// SlackClientSigningSecret: The token used to validate that incoming webhooks are originated from Slack. /// + /// An instance of . /// The ILogger implementation this adapter should use. - public SlackAdapter(IConfiguration configuration, ILogger logger = null) - : this(new SlackAdapterOptions(configuration["SlackVerificationToken"], configuration["SlackBotToken"], configuration["SlackClientSigningSecret"]), logger) + public SlackAdapter(IConfiguration configuration, SlackAdapterOptions options = null, ILogger logger = null) + : this(new SlackClientWrapper(new SlackClientWrapperOptions(configuration["SlackVerificationToken"], configuration["SlackBotToken"], configuration["SlackClientSigningSecret"])), options, logger) { } @@ -51,10 +54,11 @@ public SlackAdapter(IConfiguration configuration, ILogger logger = null) /// The adapter options to be used when connecting to the Slack API. /// The ILogger implementation this adapter should use. /// The SlackClientWrapper used to connect to the Slack API. - public SlackAdapter(SlackAdapterOptions adapterOptions, ILogger logger = null, SlackClientWrapper slackClient = null) + public SlackAdapter(SlackClientWrapper slackClient, SlackAdapterOptions adapterOptions, ILogger logger = null) { - _slackClient = slackClient ?? new SlackClientWrapper(adapterOptions) ?? throw new ArgumentNullException(nameof(adapterOptions)); + _slackClient = slackClient ?? throw new ArgumentNullException(nameof(adapterOptions)); _logger = logger ?? NullLogger.Instance; + _options = adapterOptions ?? new SlackAdapterOptions(); } /// @@ -140,9 +144,9 @@ public override async Task UpdateActivityAsync(ITurnContext tu var results = await _slackClient.UpdateAsync(message.Ts, message.Channel, message.Text, cancellationToken: cancellationToken).ConfigureAwait(false); - if (!results.ok) + if (!results.Ok) { - throw new Exception($"Error updating activity on Slack:{results}"); + throw new InvalidOperationException($"Error updating activity on Slack:{results}"); } return new ResourceResponse() @@ -271,11 +275,11 @@ public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot body = await sr.ReadToEndAsync().ConfigureAwait(false); } - if (!_slackClient.VerifySignature(request, body)) + if (_options.VerifyIncomingRequests && !_slackClient.VerifySignature(request, body)) { const string text = "Rejected due to mismatched header signature"; await SlackHelper.WriteAsync(response, HttpStatusCode.Unauthorized, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); - throw new Exception(text); + throw new AuthenticationException(text); } var requestContentType = request.Headers["Content-Type"].ToString(); @@ -293,7 +297,7 @@ public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot { var serializedPayload = JsonConvert.SerializeObject(postValues); var payload = JsonConvert.DeserializeObject(serializedPayload); - activity = await SlackHelper.CommandToActivityAsync(payload, _slackClient, cancellationToken).ConfigureAwait(false); + activity = SlackHelper.CommandToActivity(payload, _slackClient); } } else if (requestContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) @@ -311,14 +315,14 @@ public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot { var text = $"Rejected due to mismatched verificationToken:{bodyObject["token"]}"; await SlackHelper.WriteAsync(response, HttpStatusCode.Forbidden, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); - throw new Exception(text); + throw new AuthenticationException(text); } if (bodyObject["type"]?.ToString() == "event_callback") { // this is an event api post var eventRequest = bodyObject.ToObject(); - activity = await SlackHelper.EventToActivityAsync(eventRequest, _slackClient, cancellationToken).ConfigureAwait(false); + activity = SlackHelper.EventToActivity(eventRequest, _slackClient); } } diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapterOptions.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapterOptions.cs index 9188dcccd0..65a29e9a53 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapterOptions.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackAdapterOptions.cs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.Bot.Builder.Adapters.Slack { /// @@ -14,81 +9,11 @@ namespace Microsoft.Bot.Builder.Adapters.Slack public class SlackAdapterOptions { /// - /// Initializes a new instance of the class. - /// - /// A token for validating the origin of incoming webhooks. - /// A token for a bot to work on a single workspace. - /// The token used to validate that incoming webhooks are originated from Slack. - public SlackAdapterOptions(string slackVerificationToken, string slackBotToken, string slackClientSigningSecret) - { - SlackVerificationToken = slackVerificationToken; - SlackBotToken = slackBotToken; - SlackClientSigningSecret = slackClientSigningSecret; - } - - /// - /// Gets or Sets the token for validating the origin of incoming webhooks. - /// - /// The verification token. - public string SlackVerificationToken { get; set; } - - /// - /// Gets or Sets a token used to validate that incoming webhooks are originated from Slack. - /// - /// The Client Signing Secret. - public string SlackClientSigningSecret { get; set; } - - /// - /// Gets or Sets a token (provided by Slack) for a bot to work on a single workspace. - /// - /// The Bot token. - public string SlackBotToken { get; set; } - - /// - /// Gets or Sets the oauth client id provided by Slack for multi-team apps. - /// - /// The Client Id. - public string SlackClientId { get; set; } - - /// - /// Gets or Sets the oauth client secret provided by Slack for multi-team apps. - /// - /// The Client Secret. - public string SlackClientSecret { get; set; } - - /// - /// Gets or Sets the URI users will be redirected to after an oauth flow. In most cases, should be `https://mydomain.com/install/auth`. - /// - /// The Redirect URI. - public Uri SlackRedirectUri { get; set; } - - /// - /// Gets an array of scope names that are being requested during the oauth process. Must match the scopes defined at api.slack.com. - /// - /// The scopes array. - /// An array of scope names that are being requested during the oauth process. - public List SlackScopes { get; } = new List(); - - /// - /// A method that receives a Slack team id and returns the bot token associated with that team. Required for multi-team apps. - /// - /// Team ID. - /// A for the task. - /// The bot token associated with the team. - public Task GetTokenForTeamAsync(string teamId, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - /// A method that receives a Slack team id and returns the bot user id associated with that team. Required for multi-team apps. + /// Gets or sets a value indicating whether the signatures of incoming requests should be verified. /// - /// Team ID. - /// A for the task. - /// The bot user id associated with that team. - public virtual Task GetBotUserByTeamAsync(string teamId, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + /// + /// A value indicating whether the signatures of incoming requests should be verified. + /// + public bool VerifyIncomingRequests { get; set; } = true; } } diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapper.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapper.cs index 616dfa69ac..baf7bd3c90 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapper.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapper.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Bot.Builder.Adapters.Slack.Model; +using Microsoft.Bot.Builder.Adapters.Slack.Model.Events; using Microsoft.Bot.Schema; using Newtonsoft.Json; using SlackAPI; @@ -29,13 +30,13 @@ public class SlackClientWrapper /// Creates a Slack client by supplying the access token. /// /// An object containing API credentials, a webhook verification token and other options. - public SlackClientWrapper(SlackAdapterOptions options) + public SlackClientWrapper(SlackClientWrapperOptions options) { Options = options ?? throw new ArgumentNullException(nameof(options)); if (string.IsNullOrWhiteSpace(options.SlackVerificationToken) && string.IsNullOrWhiteSpace(options.SlackClientSigningSecret)) { - const string warning = "****************************************************************************************" + + const string message = "****************************************************************************************" + "* WARNING: Your bot is operating without recommended security mechanisms in place. *" + "* Initialize your adapter with a clientSigningSecret parameter to enable *" + "* verification that all incoming webhooks originate with Slack: *" + @@ -45,7 +46,7 @@ public SlackClientWrapper(SlackAdapterOptions options) "****************************************************************************************" + ">> Slack docs: https://api.slack.com/docs/verifying-requests-from-slack"; - throw new Exception(warning + Environment.NewLine + "Required: include a verificationToken or clientSigningSecret to verify incoming Events API webhooks"); + throw new InvalidOperationException(message + Environment.NewLine + "Required: include a verificationToken or clientSigningSecret to verify incoming Events API webhooks"); } _api = new SlackTaskClient(options.SlackBotToken); @@ -53,12 +54,12 @@ public SlackClientWrapper(SlackAdapterOptions options) } /// - /// Gets the SlackAdapterOptions. + /// Gets the . /// /// /// An object containing API credentials, a webhook verification token and other options. /// - public SlackAdapterOptions Options { get; } + public SlackClientWrapperOptions Options { get; } /// /// Gets the user identity. @@ -104,9 +105,22 @@ public virtual async Task TestAuthAsync(CancellationToken cancellationTo /// If the message is being sent as user instead of as a bot. /// A cancellation token for the task. /// A representing the response to the operation. - public virtual async Task UpdateAsync(string ts, string channelId, string text, string botName = null, string parse = null, bool linkNames = false, Attachment[] attachments = null, bool asUser = false, CancellationToken cancellationToken = default) + public virtual async Task UpdateAsync(string ts, string channelId, string text, string botName = null, string parse = null, bool linkNames = false, Attachment[] attachments = null, bool asUser = false, CancellationToken cancellationToken = default) { - return await _api.UpdateAsync(ts, channelId, text, botName, parse, linkNames, attachments, asUser).ConfigureAwait(false); + var updateResponse = await _api.UpdateAsync(ts, channelId, text, botName, parse, linkNames, attachments, asUser).ConfigureAwait(false); + + return new SlackResponse() + { + Ok = updateResponse.ok, + Message = new MessageEvent() + { + User = updateResponse.message.user, + Type = updateResponse.message.type, + Text = updateResponse.message.text + }, + Channel = updateResponse.channel, + Ts = updateResponse.ts + }; } /// @@ -188,23 +202,10 @@ public virtual async Task PostMessageAsync(NewSlackMessage messag /// In multi-team mode, this will use the `getBotUserByTeam` method passed to the constructor to pull the information from a developer-defined source. /// /// An Activity. - /// A cancellation token for the task. /// The identity of the bot's user. - public async Task GetBotUserByTeamAsync(Activity activity, CancellationToken cancellationToken) + public virtual string GetBotUserIdentity(Activity activity) { - if (!string.IsNullOrWhiteSpace(Identity)) - { - return Identity; - } - - if (activity.Conversation.Properties["team"] == null) - { - return null; - } - - // multi-team mode - var userId = await Options.GetBotUserByTeamAsync(activity.Conversation.Properties["team"].ToString(), cancellationToken).ConfigureAwait(false); - return !string.IsNullOrWhiteSpace(userId) ? userId : throw new Exception("Missing credentials for team."); + return Identity; } /// @@ -223,7 +224,7 @@ public async Task LoginWithSlackAsync(CancellationToken cancellationToken) Options.SlackRedirectUri == null || Options.SlackScopes.Count == 0) { - throw new Exception("Missing Slack API credentials! Provide SlackClientId, SlackClientSecret, scopes and SlackRedirectUri as part of the SlackAdapter options."); + throw new InvalidOperationException("Missing Slack API credentials! Provide SlackClientId, SlackClientSecret, scopes and SlackRedirectUri as part of the SlackAdapter options."); } } } diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapperOptions.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapperOptions.cs new file mode 100644 index 0000000000..05dd2eda8c --- /dev/null +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackClientWrapperOptions.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Bot.Builder.Adapters.Slack +{ + /// + /// Class for defining implementation of the SlackClientWrapperOptions Options. + /// + public class SlackClientWrapperOptions + { + /// + /// Initializes a new instance of the class. + /// + /// A token for validating the origin of incoming webhooks. + /// A token for a bot to work on a single workspace. + /// The token used to validate that incoming webhooks are originated from Slack. + public SlackClientWrapperOptions(string slackVerificationToken, string slackBotToken, string slackClientSigningSecret) + { + SlackVerificationToken = slackVerificationToken; + SlackBotToken = slackBotToken; + SlackClientSigningSecret = slackClientSigningSecret; + } + + /// + /// Gets or Sets the token for validating the origin of incoming webhooks. + /// + /// The verification token. + public string SlackVerificationToken { get; set; } + + /// + /// Gets or Sets a token used to validate that incoming webhooks are originated from Slack. + /// + /// The Client Signing Secret. + public string SlackClientSigningSecret { get; set; } + + /// + /// Gets or Sets a token (provided by Slack) for a bot to work on a single workspace. + /// + /// The Bot token. + public string SlackBotToken { get; set; } + + /// + /// Gets or Sets the oauth client id provided by Slack for multi-team apps. + /// + /// The Client Id. + public string SlackClientId { get; set; } + + /// + /// Gets or Sets the oauth client secret provided by Slack for multi-team apps. + /// + /// The Client Secret. + public string SlackClientSecret { get; set; } + + /// + /// Gets or Sets the URI users will be redirected to after an oauth flow. In most cases, should be `https://mydomain.com/install/auth`. + /// + /// The Redirect URI. + public Uri SlackRedirectUri { get; set; } + + /// + /// Gets an array of scope names that are being requested during the oauth process. Must match the scopes defined at api.slack.com. + /// + /// The scopes array. + /// An array of scope names that are being requested during the oauth process. + public List SlackScopes { get; } = new List(); + } +} diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackHelper.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackHelper.cs index 8da5dde32f..60c6220694 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackHelper.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Slack/SlackHelper.cs @@ -202,9 +202,8 @@ public static Activity PayloadToActivity(InteractionPayload slackPayload) /// /// The data of the slack event. /// The Slack client. - /// A cancellation token for the task. /// An activity containing the event data. - public static async Task EventToActivityAsync(EventRequest eventRequest, SlackClientWrapper client, CancellationToken cancellationToken) + public static Activity EventToActivity(EventRequest eventRequest, SlackClientWrapper client) { if (eventRequest == null) { @@ -213,15 +212,16 @@ public static async Task EventToActivityAsync(EventRequest eventReques var innerEvent = eventRequest.Event; - var activity = new Activity() + var activity = new Activity { Id = innerEvent.EventTs, Timestamp = default, ChannelId = "slack", - Conversation = new ConversationAccount() - { - Id = innerEvent.Channel ?? innerEvent.ChannelId ?? eventRequest.TeamId - }, + Conversation = + new ConversationAccount() + { + Id = innerEvent.Channel ?? innerEvent.ChannelId ?? eventRequest.TeamId + }, From = new ChannelAccount() { Id = innerEvent.User ?? innerEvent.BotId ?? eventRequest.TeamId @@ -232,7 +232,7 @@ public static async Task EventToActivityAsync(EventRequest eventReques activity.Recipient = new ChannelAccount() { - Id = await client.GetBotUserByTeamAsync(activity, cancellationToken).ConfigureAwait(false) + Id = client.GetBotUserIdentity(activity) }; if (!string.IsNullOrEmpty(innerEvent.ThreadTs)) @@ -267,9 +267,8 @@ public static async Task EventToActivityAsync(EventRequest eventReques /// /// The data of the slack command request. /// The Slack client. - /// A cancellation token for the task. /// An activity containing the event data. - public static async Task CommandToActivityAsync(CommandPayload commandRequest, SlackClientWrapper client, CancellationToken cancellationToken) + public static Activity CommandToActivity(CommandPayload commandRequest, SlackClientWrapper client) { if (commandRequest == null) { @@ -289,17 +288,16 @@ public static async Task CommandToActivityAsync(CommandPayload command { Id = commandRequest.UserId, }, - Recipient = new ChannelAccount() - { - Id = null, - }, ChannelData = commandRequest, Type = ActivityTypes.Event, Name = "Command", Value = commandRequest.Command }; - activity.Recipient.Id = await client.GetBotUserByTeamAsync(activity, cancellationToken).ConfigureAwait(false); + activity.Recipient = new ChannelAccount() + { + Id = client.GetBotUserIdentity(activity) + }; activity.Conversation.Properties["team"] = commandRequest.TeamId; diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapter.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapter.cs index 6315ca86f0..d2584f773b 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapter.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Security.Authentication; using System.Security.Claims; using System.Security.Principal; using System.Threading; @@ -26,6 +27,7 @@ public class WebexAdapter : BotAdapter, IBotFrameworkHttpAdapter { private readonly WebexClientWrapper _webexClient; private readonly ILogger _logger; + private readonly WebexAdapterOptions _options; /// /// Initializes a new instance of the class using configuration settings. @@ -38,9 +40,10 @@ public class WebexAdapter : BotAdapter, IBotFrameworkHttpAdapter /// WebexSecret: The secret used to validate incoming webhooks. /// WebexWebhookName: A name for the webhook subscription. /// + /// An instance of . /// The ILogger implementation this adapter should use. - public WebexAdapter(IConfiguration configuration, ILogger logger = null) - : this(new WebexClientWrapper(new WebexAdapterOptions(configuration["WebexAccessToken"], new Uri(configuration["WebexPublicAddress"]), configuration["WebexSecret"], configuration["WebexWebhookName"])), logger) + public WebexAdapter(IConfiguration configuration, WebexAdapterOptions options = null, ILogger logger = null) + : this(new WebexClientWrapper(new WebexClientWrapperOptions(configuration["WebexAccessToken"], new Uri(configuration["WebexPublicAddress"]), configuration["WebexSecret"], configuration["WebexWebhookName"])), options, logger) { } @@ -49,10 +52,12 @@ public WebexAdapter(IConfiguration configuration, ILogger logger = null) /// Creates a Webex adapter. /// /// A Webex API interface. + /// An instance of . /// The ILogger implementation this adapter should use. - public WebexAdapter(WebexClientWrapper webexClient, ILogger logger = null) + public WebexAdapter(WebexClientWrapper webexClient, WebexAdapterOptions options, ILogger logger = null) { _webexClient = webexClient ?? throw new ArgumentNullException(nameof(webexClient)); + _options = options ?? new WebexAdapterOptions(); _logger = logger ?? NullLogger.Instance; } @@ -93,7 +98,7 @@ public override async Task SendActivitiesAsync(ITurnContext } else { - throw new Exception("No Person, Email or Room to send the message"); + throw new InvalidOperationException("No Person, Email or Room to send the message"); } string responseId; @@ -247,9 +252,9 @@ public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot payload = JsonConvert.DeserializeObject(json); } - if (!_webexClient.ValidateSignature(request, json)) + if (_options.ValidateIncomingRequests && !_webexClient.ValidateSignature(request, json)) { - throw new Exception("WARNING: Webhook received message with invalid signature. Potential malicious behavior!"); + throw new AuthenticationException("Webhook received message with invalid signature. Potential malicious behavior!"); } Activity activity; diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapterOptions.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapterOptions.cs index 97d8b50c5d..86cb775fea 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapterOptions.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexAdapterOptions.cs @@ -6,47 +6,16 @@ namespace Microsoft.Bot.Builder.Adapters.Webex { /// - /// Defines implementation of the WebexAdapter Options. + /// Options class for the . /// public class WebexAdapterOptions { /// - /// Initializes a new instance of the class. + /// Gets or sets a value indicating whether the signature on incoming requests should be validated as originating from Webex. /// - /// An access token for the bot. - /// The root URL of the bot application. - /// The secret used to validate incoming webhooks. - /// A name for the webhook subscription. - public WebexAdapterOptions(string webexAccessToken, Uri webexPublicAddress, string webexSecret, string webexWebhookName = null) - { - WebexAccessToken = webexAccessToken; - WebexPublicAddress = webexPublicAddress; - WebexSecret = webexSecret; - WebexWebhookName = webexWebhookName; - } - - /// - /// Gets or sets an access token for the bot. - /// - /// An access token for the bot. Get one from 'https://developer.webex.com/'. - public string WebexAccessToken { get; set; } - - /// - /// Gets or sets the secret used to validate incoming webhooks. - /// - /// The secret used to validate incoming webhooks. You can define this yourself. - public string WebexSecret { get; set; } - - /// - /// Gets or sets the root URI of your bot application. Something like 'https://mybot.com/'. - /// - /// the root URI of your bot application. - public Uri WebexPublicAddress { get; set; } - - /// - /// Gets or sets a name for the webhook subscription that will be created to tell Webex to send your bot webhooks. - /// - /// A name for the webhook subscription. - public string WebexWebhookName { get; set; } + /// + /// A value indicating if the signature on incoming requests should be validated as originating from Webex. + /// + public bool ValidateIncomingRequests { get; set; } = true; } } diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapper.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapper.cs index bfcd0ab705..232724e9c0 100644 --- a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapper.cs +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapper.cs @@ -23,7 +23,6 @@ namespace Microsoft.Bot.Builder.Adapters.Webex /// public class WebexClientWrapper { - private const string WebhookUrl = "https://api.ciscospark.com/v1/webhooks"; private const string MessageUrl = "https://api.ciscospark.com/v1/messages"; private const string ActionsUrl = "https://api.ciscospark.com/v1/attachment/actions"; private const string SparkSignature = "x-spark-signature"; @@ -32,10 +31,10 @@ public class WebexClientWrapper /// /// Initializes a new instance of the class. - /// Creates a Webex Client Wrapper. See for a full definition of the allowed parameters. + /// Creates a Webex Client Wrapper. See for a full definition of the allowed parameters. /// /// An object containing API credentials, a webhook verification token and other options. - public WebexClientWrapper(WebexAdapterOptions options) + public WebexClientWrapper(WebexClientWrapperOptions options) { Options = options ?? throw new ArgumentNullException(nameof(options)); @@ -55,117 +54,8 @@ public WebexClientWrapper(WebexAdapterOptions options) /// /// Gets the options collection for the adapter. /// - /// A WebexAdapterOptions class exposing properties for each of the available options. - public WebexAdapterOptions Options { get; } - - /// - /// Lists all webhook subscriptions currently associated with this application. - /// - /// A cancellation token for the task. - /// A list of webhook subscriptions. - public async Task ListWebhookSubscriptionsAsync(CancellationToken cancellationToken) - { - return await ListWebhooksAsync(cancellationToken).ConfigureAwait(false); - } - - /// - /// Clears out and resets the list of webhook subscriptions. - /// - /// A cancellation token for the task. - /// A representing the asynchronous operation. - public async Task ResetWebhookSubscriptionsAsync(CancellationToken cancellationToken) - { - var webhookList = await ListWebhooksAsync(cancellationToken).ConfigureAwait(false); - - for (var i = 0; i < webhookList.ItemCount; i++) - { - await DeleteWebhookAsync(webhookList.Items[i], cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Register webhook subscriptions to start receiving message events and adaptive cards events. - /// - /// The path of the webhook endpoint like '/api/messages'. - /// A cancellation token for the task. - /// An array of registered . - public async Task RegisterWebhookSubscriptionsAsync(string webhookPath = "api/messages", CancellationToken cancellationToken = default) - { - var webHookName = string.IsNullOrWhiteSpace(Options.WebexWebhookName) ? "Webex Firehose" : Options.WebexWebhookName; - var webHookCardsName = string.IsNullOrWhiteSpace(Options.WebexWebhookName) ? "Webex AttachmentActions" : $"{Options.WebexWebhookName}_AttachmentActions)"; - - var webhookList = await ListWebhooksAsync(cancellationToken).ConfigureAwait(false); - - string webhookId = null; - string webhookCardsId = null; - - for (var i = 0; i < webhookList.ItemCount; i++) - { - if (webhookList.Items[i].Name == webHookName) - { - webhookId = webhookList.Items[i].Id; - } - else if (webhookList.Items[i].Name == webHookCardsName) - { - webhookCardsId = webhookList.Items[i].Id; - } - } - - var webhookUrl = new Uri(Options.WebexPublicAddress + webhookPath); - - var webhook = await RegisterWebhookSubscriptionAsync(webhookId, webHookName, webhookUrl, cancellationToken).ConfigureAwait(false); - var cardsWebhook = await RegisterAdaptiveCardsWebhookSubscriptionAsync(webhookCardsId, webHookCardsName, webhookUrl, cancellationToken).ConfigureAwait(false); - - return new[] { webhook, cardsWebhook }; - } - - /// - /// Register a webhook subscription with Webex Teams to start receiving message events. - /// - /// The id of the webhook to be registered. - /// The name of the webhook to be registered. - /// The Uri of the webhook. - /// A cancellation token for the task. - /// The registered . - public async Task RegisterWebhookSubscriptionAsync(string hookId, string webHookName, Uri webhookUrl, CancellationToken cancellationToken) - { - Webhook webhook; - - if (hookId != null) - { - webhook = await UpdateWebhookAsync(hookId, webHookName, webhookUrl, Options.WebexSecret, cancellationToken).ConfigureAwait(false); - } - else - { - webhook = await CreateWebhookAsync(webHookName, webhookUrl, EventResource.All, EventType.All, null, Options.WebexSecret, cancellationToken).ConfigureAwait(false); - } - - return webhook; - } - - /// - /// Register a webhook subscription with Webex Teams to start receiving events related to adaptive cards. - /// - /// The id of the webhook to be registered. - /// The name of the webhook to be registered. - /// The Uri of the webhook. - /// A cancellation token for the task. - /// A representing the asynchronous operation. - public async Task RegisterAdaptiveCardsWebhookSubscriptionAsync(string hookId, string webHookName, Uri webhookUrl, CancellationToken cancellationToken) - { - Webhook webhook; - - if (hookId != null) - { - webhook = await UpdateAdaptiveCardsWebhookAsync(hookId, webHookName, webhookUrl, Options.WebexSecret, Options.WebexAccessToken, cancellationToken).ConfigureAwait(false); - } - else - { - webhook = await CreateAdaptiveCardsWebhookAsync(webHookName, webhookUrl, EventType.All, Options.WebexSecret, Options.WebexAccessToken, cancellationToken).ConfigureAwait(false); - } - - return webhook; - } + /// A WebexClientWrapperOptions class exposing properties for each of the available options. + public WebexClientWrapperOptions Options { get; } /// /// Validates the local secret against the one obtained from the request header. @@ -177,7 +67,7 @@ public virtual bool ValidateSignature(HttpRequest request, string jsonPayload) { var signature = request.Headers.ContainsKey(SparkSignature) ? request.Headers[SparkSignature].ToString().ToUpperInvariant() - : throw new Exception($"HttpRequest is missing \"{SparkSignature}\""); + : throw new InvalidOperationException($"HttpRequest is missing \"{SparkSignature}\""); #pragma warning disable CA5350 // Webex API uses SHA1 as cryptographic algorithm. using (var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(Options.WebexSecret))) @@ -190,21 +80,6 @@ public virtual bool ValidateSignature(HttpRequest request, string jsonPayload) #pragma warning restore CA5350 // Webex API uses SHA1 as cryptographic algorithm. } - /// - /// Wraps Webex API's CreateDirectMessageAsync method. - /// - /// Id or email of message recipient. - /// Text of the message. - /// List of files attached to the message. - /// A cancellation token for the task. - /// The created message id. - public virtual async Task CreateDirectMessageAsync(string toPersonOrEmail, string text, IList files = null, CancellationToken cancellationToken = default) - { - var webexResponse = await _api.CreateDirectMessageAsync(toPersonOrEmail, text, files, cancellationToken: cancellationToken).ConfigureAwait(false); - - return webexResponse.Data.Id; - } - /// /// Wraps Webex API's CreateMessageAsync method. /// @@ -331,18 +206,6 @@ public virtual async Task GetMeAsync(CancellationToken cancellationToken return resultPerson.GetData(false); } - /// - /// Wraps Webex API's GetMeFromCacheAsync method. - /// - /// A cancellation token for the task. - /// The object associated with the bot, from cache. - public virtual async Task GetMeFromCacheAsync(CancellationToken cancellationToken) - { - var resultPerson = await _api.GetMeFromCacheAsync(cancellationToken).ConfigureAwait(false); - - return resultPerson.GetData(false); - } - /// /// Wraps Webex API's GetMessageAsync method. /// @@ -355,484 +218,5 @@ public virtual async Task GetMessageAsync(string messageId, Cancellatio return message.GetData(false); } - - /// - /// Wraps Webex API's ActivateWebhookAsync method. - /// - /// to be activated. - /// A cancellation token for the task. - /// The Activated . - public virtual async Task ActivateWebhookAsync(Webhook webhook, CancellationToken cancellationToken) - { - var resultWebhook = await _api.ActivateWebhookAsync(webhook, cancellationToken).ConfigureAwait(false); - - return resultWebhook.GetData(false); - } - - /// - /// Wraps Webex API's ListWebhooksAsync method. - /// - /// A cancellation token for the task. - /// A list of Webhooks associated with the application. - public virtual async Task ListWebhooksAsync(CancellationToken cancellationToken) - { - var webhookList = await _api.ListWebhooksAsync(cancellationToken: cancellationToken).ConfigureAwait(false); - - return webhookList.GetData(false); - } - - /// - /// Wraps Webex API's CreateWebhookAsync method. - /// - /// Name for the webhook. - /// Uri of the webhook. - /// Event resource associated with the webhook. - /// Event type associated with the webhook. - /// Filters for the webhook. - /// Secret used to validate the webhook. - /// A cancellation token for the task. - /// The created . - public virtual async Task CreateWebhookAsync(string name, Uri targetUri, EventResource resource, EventType type, IEnumerable filters, string secret, CancellationToken cancellationToken) - { - var resultWebhook = await _api.CreateWebhookAsync(name, targetUri, resource, type, null, secret, cancellationToken: cancellationToken).ConfigureAwait(false); - - return resultWebhook.GetData(false); - } - - /// - /// Creates a Webhook subscription to handle Adaptive cards messages. - /// - /// Name for the webhook. - /// Uri of the webhook. - /// Event type associated with the webhook. - /// Secret used to validate the webhook. - /// Access Token for authorization. - /// A cancellation token for the task. - /// The created . - public virtual async Task CreateAdaptiveCardsWebhookAsync(string name, Uri targetUri, EventType type, string secret, string token, CancellationToken cancellationToken) - { - var data = new NameValueCollection - { - ["name"] = name, - ["targetUrl"] = targetUri.AbsoluteUri, - ["resource"] = "attachmentActions", - ["event"] = "all", - ["secret"] = secret, - }; - - using (var client = new WebClient()) - { - client.Headers[HttpRequestHeader.Authorization] = "Bearer " + token; - - var response = await client.UploadValuesTaskAsync(new Uri(WebhookUrl), "POST", data).ConfigureAwait(false); - - var result = JsonConvert.DeserializeObject(Encoding.ASCII.GetString(response)); - - return result; - } - } - - /// - /// Updates a Webhook subscription to handle Adaptive cards messages. - /// - /// Id of the webhook to be updated. - /// Name for the webhook. - /// Uri of the webhook. - /// Secret used to validate the webhook. - /// Access Token for authorization. - /// A cancellation token for the task. - /// The created . - public virtual async Task UpdateAdaptiveCardsWebhookAsync(string webhookId, string name, Uri targetUri, string secret, string token, CancellationToken cancellationToken) - { - var url = $"{WebhookUrl}/{webhookId}"; - - var data = new NameValueCollection - { - ["name"] = name, - ["targetUrl"] = targetUri.AbsoluteUri, - ["resource"] = "attachmentActions", - ["event"] = "all", - ["secret"] = secret, - }; - - using (var client = new WebClient()) - { - client.Headers[HttpRequestHeader.Authorization] = "Bearer " + token; - - var response = await client.UploadValuesTaskAsync(new Uri(url), "PUT", data).ConfigureAwait(false); - - var result = JsonConvert.DeserializeObject(Encoding.ASCII.GetString(response)); - - return result; - } - } - - /// - /// Wraps Webex API's GetWebhookAsync method. - /// - /// The id of the Webhook to get. - /// A cancellation token for the task. - /// The requested Webhook. - public virtual async Task GetWebhookAsync(string webhookId, CancellationToken cancellationToken) - { - var resultWebhook = await _api.GetWebhookAsync(webhookId, cancellationToken).ConfigureAwait(false); - - return resultWebhook.GetData(false); - } - - /// - /// Wraps Webex API's DeleteWebhookAsync method. - /// - /// Id of the webhook to be deleted. - /// A cancellation token for the task. - /// A representing the asynchronous operation. - public virtual async Task DeleteWebhookAsync(Webhook id, CancellationToken cancellationToken) - { - await _api.DeleteWebhookAsync(id, cancellationToken).ConfigureAwait(false); - } - - /// - /// Wraps Webex API's UpdateWebhookAsync method. - /// - /// Id of the webhook to be updated. - /// Name for the webhook. - /// Uri of the webhook. - /// Secret used to validate the webhook. - /// A cancellation token for the task. - /// The updated . - public virtual async Task UpdateWebhookAsync(string webhookId, string name, Uri targetUri, string secret, CancellationToken cancellationToken) - { - var resultWebhook = await _api.UpdateWebhookAsync(webhookId, name, targetUri, secret, cancellationToken: cancellationToken).ConfigureAwait(false); - - return resultWebhook.GetData(false); - } - - /// - /// Wraps Webex API's CreateSpaceAsync method. - /// - /// Space title. - /// The ID for the team with which this room is associated. - /// A cancellation token for the task. - /// The space created. - public virtual async Task CreateSpaceAsync(string title, string teamId = null, CancellationToken cancellationToken = default) - { - var resultSpace = await _api.CreateSpaceAsync(title, teamId, cancellationToken).ConfigureAwait(false); - - return resultSpace.GetData(false); - } - - /// - /// Wraps Webex API's CreateSpaceMembershipAsync method. - /// - /// The space ID. - /// The person ID or Email. - /// True for moderator persons. - /// for personIdOrEmail parameter. - /// A cancellation token for the task. - /// The resulting space membership. - public virtual async Task CreateSpaceMembershipAsync(string spaceId, string personIdOrEmail, bool? isModerator = null, PersonIdType personIdType = PersonIdType.Detect, CancellationToken cancellationToken = default) - { - var resultSpaceMembership = await _api.CreateSpaceMembershipAsync(spaceId, personIdOrEmail, isModerator, personIdType, cancellationToken).ConfigureAwait(false); - - return resultSpaceMembership.GetData(false); - } - - /// - /// Wraps Webex API's DeleteSpaceAsync method. - /// - /// The id of the space to be deleted. - /// A cancellation token for the task. - /// A representing the asynchronous operation. - public virtual async Task DeleteSpaceAsync(string spaceId, CancellationToken cancellationToken) - { - await _api.DeleteSpaceAsync(spaceId, cancellationToken).ConfigureAwait(false); - } - - /// - /// Wraps Webex API's DeleteSpaceMembershipAsync method. - /// - /// The id of the membership to be deleted. - /// A cancellation token for the task. - /// A representing the asynchronous operation. - public virtual async Task DeleteSpaceMembershipAsync(string membershipId, CancellationToken cancellationToken) - { - await _api.DeleteSpaceMembershipAsync(membershipId, cancellationToken).ConfigureAwait(false); - } - - /// - /// Wraps Webex API's GetSpaceAsync method. - /// - /// The id of the space to be gotten. - /// A cancellation token for the task. - /// The space requested. - public virtual async Task GetSpaceAsync(string spaceId, CancellationToken cancellationToken) - { - var resultSpace = await _api.GetSpaceAsync(spaceId, cancellationToken).ConfigureAwait(false); - return resultSpace.GetData(false); - } - - /// - /// Wraps Webex API's GetSpaceMembershipAsync method. - /// - /// The id of the membership to get. - /// A cancellation token for the task. - /// The requested space membership. - public virtual async Task GetSpaceMembershipAsync(string membershipId, CancellationToken cancellationToken) - { - var resultSpaceMembership = await _api.GetSpaceMembershipAsync(membershipId, cancellationToken).ConfigureAwait(false); - - return resultSpaceMembership.GetData(false); - } - - /// - /// Wraps Webex API's ListSpacesAsync method. - /// - /// Limit the rooms to those associated with a team, by ID. - /// returns all 1-to-1 rooms. returns all group rooms.If not specified or values are not matched, will return all room types. - /// Sort results by space ID(), most recent activity(), or most recently created(). - /// Limit the maximum number of messages in the response. - /// A cancellation token for the task. - /// A list of the spaces. - public virtual async Task ListSpacesAsync(string teamId = null, SpaceType type = null, SpaceSortBy sortBy = null, int? max = null, CancellationToken cancellationToken = default) - { - var resultSpaceList = await _api.ListSpacesAsync(teamId, type, sortBy, max, cancellationToken).ConfigureAwait(false); - - return resultSpaceList.GetData(false); - } - - /// - /// Wraps Webex API's ListSpaceMembershipsAsync method. - /// - /// Limit results to a specific space, by ID. - /// Limit results to a specific person, by ID or Email. - /// Limit the maximum number of items in the response. - /// for personIdOrEmail parameter. - /// A cancellation token for the task. - /// The space memberships list. - public virtual async Task ListSpaceMembershipsAsync(string spaceId = null, string personIdOrEmail = null, int? max = null, PersonIdType personIdType = PersonIdType.Detect, CancellationToken cancellationToken = default) - { - var resultSpaceMembershipsList = await _api.ListSpaceMembershipsAsync(spaceId, personIdOrEmail, max, personIdType, cancellationToken).ConfigureAwait(false); - - return resultSpaceMembershipsList.GetData(false); - } - - /// - /// Wraps Webex API's UpdateSpaceAsync method. - /// - /// Space id to be updated. - /// A user-friendly name for the space. - /// A cancellation token for the task. - /// The updated space. - public virtual async Task UpdateSpaceAsync(string spaceId, string title, CancellationToken cancellationToken) - { - var resultSpace = await _api.UpdateSpaceAsync(spaceId, title, cancellationToken).ConfigureAwait(false); - - return resultSpace.GetData(false); - } - - /// - /// Wraps Webex API's UpdateSpaceMembershipAsync method. - /// - /// Membership id to be updated. - /// Set to true to make the person a space moderator. - /// A cancellation token for the task. - /// The updated space membership. - public virtual async Task UpdateSpaceMembershipAsync(string membershipId, bool isModerator, CancellationToken cancellationToken) - { - var resultSpaceMembership = await _api.UpdateSpaceMembershipAsync(membershipId, isModerator, cancellationToken).ConfigureAwait(false); - - return resultSpaceMembership.GetData(false); - } - - /// - /// Wraps Webex API's CreateTeamAsync method. - /// - /// A user-friendly name for the team. - /// A cancellation token for the task. - /// The created team. - public virtual async Task CreateTeamAsync(string name, CancellationToken cancellationToken) - { - var resultTeam = await _api.CreateTeamAsync(name, cancellationToken).ConfigureAwait(false); - - return resultTeam.GetData(false); - } - - /// - /// Wraps Webex API's CreateTeamMembershipAsync method. - /// - /// The team ID. - /// The person ID or Email. - /// Set to true to make the person a room moderator. - /// for personIdOrEmail parameter. - /// A cancellation token for the task. - /// The team membership created. - public virtual async Task CreateTeamMembershipAsync(string teamId, string personIdOrEmail, bool? isModerator = null, PersonIdType personIdType = PersonIdType.Detect, CancellationToken cancellationToken = default) - { - var resultTeamMembership = await _api.CreateTeamMembershipAsync(teamId, personIdOrEmail, isModerator, personIdType, cancellationToken).ConfigureAwait(false); - - return resultTeamMembership.GetData(false); - } - - /// - /// Wraps Webex API's DeleteTeamAsync method. - /// - /// Team id to be deleted. - /// A cancellation token for the task. - /// A representing the asynchronous operation. - public virtual async Task DeleteTeamAsync(string teamId, CancellationToken cancellationToken) - { - await _api.DeleteTeamAsync(teamId, cancellationToken).ConfigureAwait(false); - } - - /// - /// Wraps Webex API's DeleteTeamMembershipAsync method. - /// - /// Team Membership id to be deleted. - /// A cancellation token for the task. - /// A representing the asynchronous operation. - public virtual async Task DeleteTeamMembershipAsync(string membershipId, CancellationToken cancellationToken) - { - await _api.DeleteTeamMembershipAsync(membershipId, cancellationToken).ConfigureAwait(false); - } - - /// - /// Wraps Webex API's GetTeamAsync method. - /// - /// Team id that the detail info is gotten. - /// A cancellation token for the task. - /// The requested team. - public virtual async Task GetTeamAsync(string teamId, CancellationToken cancellationToken) - { - var resultTeam = await _api.GetTeamAsync(teamId, cancellationToken).ConfigureAwait(false); - - return resultTeam.GetData(false); - } - - /// - /// Wraps Webex API's GetTeamMembershipAsync method. - /// - /// Team Membership id that the detail info is gotten. - /// A cancellation token for the task. - /// The requested team membership. - public virtual async Task GetTeamMembershipAsync(string membershipId, CancellationToken cancellationToken) - { - var resultTeamMembership = await _api.GetTeamMembershipAsync(membershipId, cancellationToken).ConfigureAwait(false); - - return resultTeamMembership.GetData(false); - } - - /// - /// Wraps Webex API's ListTeamMembershipsAsync method. - /// - /// List team memberships for a team, by ID. - /// Limit the maximum number of items in the response. - /// A cancellation token for the task. - /// A list of team memberships. - public virtual async Task ListTeamMembershipsAsync(string teamId, int? max = null, CancellationToken cancellationToken = default) - { - var resultTeamMembershipList = await _api.ListTeamMembershipsAsync(teamId, max, cancellationToken).ConfigureAwait(false); - - return resultTeamMembershipList.GetData(false); - } - - /// - /// Wraps Webex API's ListTeamsAsync method. - /// - /// Limit the maximum number of teams in the response. - /// A cancellation token for the task. - /// A list of the teams. - public virtual async Task ListTeamsAsync(int? max = null, CancellationToken cancellationToken = default) - { - var resultTeamsList = await _api.ListTeamsAsync(max, cancellationToken).ConfigureAwait(false); - - return resultTeamsList.GetData(false); - } - - /// - /// Wraps Webex API's UpdateTeamAsync method. - /// - /// Team id to be updated. - /// A user-friendly name for the team. - /// A cancellation token for the task. - /// The updated team. - public virtual async Task UpdateTeamAsync(string teamId, string name, CancellationToken cancellationToken) - { - var resultTeam = await _api.UpdateTeamAsync(teamId, name, cancellationToken).ConfigureAwait(false); - - return resultTeam.GetData(false); - } - - /// - /// Wraps Webex API's UpdateTeamMembershipAsync method. - /// - /// Membership id to be updated. - /// Set to true to make the person a team moderator. - /// A cancellation token for the task. - /// The updated team membership. - public virtual async Task UpdateTeamMembershipAsync(string membershipId, bool isModerator, CancellationToken cancellationToken) - { - var resultTeamMembership = await _api.UpdateTeamMembershipAsync(membershipId, isModerator, cancellationToken).ConfigureAwait(false); - - return resultTeamMembership.GetData(false); - } - - /// - /// Wraps Webex API's GetFileDataAsync method. - /// - /// Uri of the file. - /// A cancellation token for the task. - /// The teams file data. - public virtual async Task GetFileDataAsync(Uri fileUri, CancellationToken cancellationToken) - { - var resultTeamsFileData = await _api.GetFileDataAsync(fileUri, cancellationToken).ConfigureAwait(false); - - return resultTeamsFileData.GetData(false); - } - - /// - /// Wraps Webex API's GetFileInfoAsync method. - /// - /// Uri of the file. - /// A cancellation token for the task. - /// The teams file info. - public virtual async Task GetFileInfoAsync(Uri fileUri, CancellationToken cancellationToken) - { - var resultTeamsFileInfo = await _api.GetFileInfoAsync(fileUri, cancellationToken).ConfigureAwait(false); - - return resultTeamsFileInfo.GetData(false); - } - - /// - /// Wraps Webex API's ListMessagesAsync method. - /// - /// List messages for a space, by ID. - /// List messages where the caller is mentioned by specifying "me" or the caller personId. - /// List messages sent before a date and time. - /// List messages sent before a message, by ID. - /// Limit the maximum number of messages in the response. - /// A cancellation token for the task. - /// A list of the messages. - public virtual async Task ListMessagesAsync(string spaceId, string mentionedPeople = null, DateTime? before = null, string beforeMessage = null, int? max = null, CancellationToken cancellationToken = default) - { - var resultMessageList = await _api.ListMessagesAsync(spaceId, mentionedPeople, before, beforeMessage, max, cancellationToken).ConfigureAwait(false); - - return resultMessageList.GetData(false); - } - - /// - /// Wraps Webex API's ListPeopleAsync method. - /// - /// List people with this email address. For non-admin requests, either this or displayName are required. - /// List people whose name starts with this string. For non-admin requests, either this or email are required. - /// List people by ID. Accepts up to 85 person IDs. - /// Limit the maximum number of people in the response. - /// A cancellation token for the task. - /// A list with the requested people. - public virtual async Task ListPeopleAsync(string email = null, string displayName = null, IEnumerable ids = null, int? max = null, CancellationToken cancellationToken = default) - { - var resultPersonList = await _api.ListPeopleAsync(email, displayName, ids, max, cancellationToken).ConfigureAwait(false); - - return resultPersonList.GetData(false); - } } } diff --git a/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapperOptions.cs b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapperOptions.cs new file mode 100644 index 0000000000..bba48ab90f --- /dev/null +++ b/libraries/Adapters/Microsoft.Bot.Builder.Adapters.Webex/WebexClientWrapperOptions.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Bot.Builder.Adapters.Webex +{ + /// + /// Defines implementation of the WebexAdapter Options. + /// + public class WebexClientWrapperOptions + { + /// + /// Initializes a new instance of the class. + /// + /// An access token for the bot. + /// The root URL of the bot application. + /// The secret used to validate incoming webhooks. + /// A name for the webhook subscription. + public WebexClientWrapperOptions(string webexAccessToken, Uri webexPublicAddress, string webexSecret, string webexWebhookName = null) + { + WebexAccessToken = webexAccessToken; + WebexPublicAddress = webexPublicAddress; + WebexSecret = webexSecret; + WebexWebhookName = webexWebhookName; + } + + /// + /// Gets or sets an access token for the bot. + /// + /// An access token for the bot. Get one from 'https://developer.webex.com/'. + public string WebexAccessToken { get; set; } + + /// + /// Gets or sets the secret used to validate incoming webhooks. + /// + /// The secret used to validate incoming webhooks. You can define this yourself. + public string WebexSecret { get; set; } + + /// + /// Gets or sets the root URI of your bot application. Something like 'https://mybot.com/'. + /// + /// the root URI of your bot application. + public Uri WebexPublicAddress { get; set; } + + /// + /// Gets or sets a name for the webhook subscription that will be created to tell Webex to send your bot webhooks. + /// + /// A name for the webhook subscription. + public string WebexWebhookName { get; set; } + } +} diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookAdapterTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookAdapterTests.cs index 82fda085a8..f04682aaa7 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookAdapterTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookAdapterTests.cs @@ -18,30 +18,26 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.Tests { public class FacebookAdapterTests { - private readonly FacebookAdapterOptions _testOptions = new FacebookAdapterOptions("Test", "Test", "Test"); - - public FacebookAdapterTests() - { - _testOptions.VerifyIncomingRequests = false; - } + private readonly FacebookClientWrapperOptions _testOptions = new FacebookClientWrapperOptions("Test", "Test", "Test"); + private readonly FacebookAdapterOptions _adapterOptions = new FacebookAdapterOptions() { VerifyIncomingRequests = false }; [Fact] public void ConstructorWithArgumentsShouldSucceed() { - Assert.NotNull(new FacebookAdapter(_testOptions)); + Assert.NotNull(new FacebookAdapter(new FacebookClientWrapper(_testOptions), _adapterOptions)); } [Fact] - public void ConstructorShouldFailWithNullOptions() + public void ConstructorShouldFailWithNullClient() { - Assert.Throws(() => { new FacebookAdapter((FacebookAdapterOptions)null); }); + Assert.Throws(() => { new FacebookAdapter((FacebookClientWrapper)null, _adapterOptions); }); } [Fact] public async void ContinueConversationAsyncShouldSucceed() { var callbackInvoked = false; - var facebookAdapter = new FacebookAdapter(_testOptions); + var facebookAdapter = new FacebookAdapter(new FacebookClientWrapper(_testOptions), _adapterOptions); var conversationReference = new ConversationReference(); Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) { @@ -56,7 +52,7 @@ Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) [Fact] public async void ContinueConversationAsyncShouldFailWithNullConversationReference() { - var facebookAdapter = new FacebookAdapter(_testOptions); + var facebookAdapter = new FacebookAdapter(new FacebookClientWrapper(_testOptions), _adapterOptions); Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) { return Task.CompletedTask; @@ -68,7 +64,7 @@ Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) [Fact] public async void ContinueConversationAsyncShouldFailWithNullLogic() { - var facebookAdapter = new FacebookAdapter(_testOptions); + var facebookAdapter = new FacebookAdapter(new FacebookClientWrapper(_testOptions), _adapterOptions); var conversationReference = new ConversationReference(); await Assert.ThrowsAsync(async () => { await facebookAdapter.ContinueConversationAsync(conversationReference, null, default); }); @@ -77,7 +73,7 @@ public async void ContinueConversationAsyncShouldFailWithNullLogic() [Fact] public async Task DeleteActivityAsyncShouldThrowNotImplementedException() { - var facebookAdapter = new FacebookAdapter(_testOptions); + var facebookAdapter = new FacebookAdapter(new FacebookClientWrapper(_testOptions), _adapterOptions); var activity = new Activity(); var conversationReference = new ConversationReference(); using (var turnContext = new TurnContext(facebookAdapter, activity)) @@ -91,7 +87,7 @@ public async void ProcessAsyncShouldSucceedWithCorrectData() { var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/Payload.json"); var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)); var httpRequest = new Mock(); var httpResponse = new Mock(); @@ -110,7 +106,7 @@ public async void ProcessAsyncShouldSucceedWithStandbyMessages() { var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/PayloadWithStandby.json"); var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)); var httpRequest = new Mock(); var httpResponse = new Mock(); @@ -126,13 +122,13 @@ public async void ProcessAsyncShouldSucceedWithStandbyMessages() [Fact] public async void ProcessAsyncShouldVerifyWebhookOnHubModeSubscribe() { - var testOptionsVerifyEnabled = new FacebookAdapterOptions("Test", "Test", "Test") + var testOptionsVerifyEnabled = new FacebookAdapterOptions() { VerifyIncomingRequests = true }; - var facebookClientWrapper = new Mock(testOptionsVerifyEnabled); - var facebookAdapter = new FacebookAdapter(testOptionsVerifyEnabled, null, facebookClientWrapper.Object); + var facebookClientWrapper = new Mock(_testOptions); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, testOptionsVerifyEnabled); var httpRequest = new Mock(); var httpResponse = new Mock(); var bot = new Mock(); @@ -147,13 +143,13 @@ public async void ProcessAsyncShouldVerifyWebhookOnHubModeSubscribe() [Fact] public async Task ProcessAsyncShouldThrowExceptionWithUnverifiedSignature() { - var testOptionsVerifyEnabled = new FacebookAdapterOptions("Test", "Test", "Test") - { - VerifyIncomingRequests = true - }; + var testOptionsVerifyEnabled = new FacebookAdapterOptions() + { + VerifyIncomingRequests = true + }; - var facebookClientWrapper = new Mock(testOptionsVerifyEnabled); - var facebookAdapter = new FacebookAdapter(testOptionsVerifyEnabled, null, facebookClientWrapper.Object); + var facebookClientWrapper = new Mock(_testOptions); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, testOptionsVerifyEnabled); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/Payload.json"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)); var httpRequest = new Mock(); @@ -180,7 +176,7 @@ public async void SendActivitiesAsyncShouldSucceedWithActivityTypeMessage() { const string testResponse = "Test Response"; var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var activity = new Activity { Type = ActivityTypes.Message, @@ -209,7 +205,7 @@ public async void SendActivitiesAsyncShouldSucceedWithActivityTypeMessageAndAtta { const string testResponse = "Test Response"; var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var attachments = new List(); var activity = new Activity { @@ -241,7 +237,7 @@ public async void SendActivitiesAsyncShouldSucceedAndNoActivityReturnedWithActiv { const string testResponse = "Test Response"; var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var attachments = new List(); var activity = new Activity { @@ -273,7 +269,7 @@ public async void SendActivitiesAsyncShouldPostToFacebookOnPassThreadControl() { const string testResponse = "Test Response"; var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var activity = new Activity { Type = ActivityTypes.Event, @@ -305,7 +301,7 @@ public async void SendActivitiesAsyncShouldPostToFacebookOnTakeThreadControl() { const string testResponse = "Test Response"; var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var activity = new Activity { Type = ActivityTypes.Event, @@ -337,7 +333,7 @@ public async void SendActivitiesAsyncShouldPostToFacebookOnRequestThreadControl( { const string testResponse = "Test Response"; var facebookClientWrapper = new Mock(_testOptions); - var facebookAdapter = new FacebookAdapter(_testOptions, null, facebookClientWrapper.Object); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var activity = new Activity { Type = ActivityTypes.Event, @@ -367,7 +363,8 @@ public async void SendActivitiesAsyncShouldPostToFacebookOnRequestThreadControl( [Fact] public async Task UpdateActivityAsyncShouldThrowNotImplementedException() { - var facebookAdapter = new FacebookAdapter(_testOptions); + var facebookClientWrapper = new Mock(_testOptions); + var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object, _adapterOptions); var activity = new Activity(); using (var turnContext = new TurnContext(facebookAdapter, activity)) diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookWrapperTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookWrapperTests.cs index 911a5a2c17..c020f3660b 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookWrapperTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.Tests/FacebookWrapperTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.Tests { public class FacebookWrapperTests { - private readonly FacebookAdapterOptions _testOptions = new FacebookAdapterOptions("TestVerifyToken", "TestAppSecret", "TestAccessToken"); + private readonly FacebookClientWrapperOptions _testOptions = new FacebookClientWrapperOptions("TestVerifyToken", "TestAppSecret", "TestAccessToken"); [Fact] public void GetAppSecretProofShouldAlwaysReturnAStringWith64Characters() diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackAdapterTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackAdapterTests.cs index 6493974a1d..ad4ab0be58 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackAdapterTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackAdapterTests.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Net; +using System.Security.Authentication; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -21,12 +22,13 @@ namespace Microsoft.Bot.Builder.Adapters.Slack.Tests { public class SlackAdapterTests { - private readonly SlackAdapterOptions _testOptions = new SlackAdapterOptions("VerificationToken", "ClientSigningSecret", "BotToken"); + private readonly SlackClientWrapperOptions _testOptions = new SlackClientWrapperOptions("VerificationToken", "ClientSigningSecret", "BotToken"); + private readonly SlackAdapterOptions _adapterOptions = new SlackAdapterOptions(); [Fact] - public void ConstructorShouldFailWithNullOptions() + public void ConstructorShouldFailWithNullClient() { - Assert.Throws(() => new SlackAdapter((SlackAdapterOptions)null)); + Assert.Throws(() => new SlackAdapter((SlackClientWrapper)null, _adapterOptions)); } [Fact] @@ -35,7 +37,7 @@ public void ConstructorSucceeds() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - Assert.NotNull(new SlackAdapter(_testOptions, slackClient: slackApi.Object)); + Assert.NotNull(new SlackAdapter(slackApi.Object, _adapterOptions)); } [Fact] @@ -44,7 +46,7 @@ public async Task UpdateActivityAsyncShouldFailWithNullActivityTimestamp() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Activity { @@ -65,7 +67,7 @@ public async Task UpdateActivityAsyncShouldFailWithNullContext() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Activity(); @@ -81,7 +83,7 @@ public async Task UpdateActivityAsyncShouldFailWithNullActivity() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); using (var turnContext = new TurnContext(slackAdapter, new Activity())) { @@ -98,7 +100,7 @@ public async Task UpdateActivityAsyncShouldFailWithNullActivityConversation() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Activity { @@ -120,9 +122,9 @@ public async Task UpdateActivityAsyncShouldSucceed() { var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - slackApi.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny(), It.IsAny())).Returns(Task.FromResult(new UpdateResponse { ok = true })); + slackApi.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny(), It.IsAny())).Returns(Task.FromResult(new SlackResponse { Ok = true })); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Mock().SetupAllProperties(); activity.Object.Id = "MockActivityId"; @@ -147,9 +149,9 @@ public async Task UpdateActivityAsyncShouldFailWithResponseError() { var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - slackApi.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny(), It.IsAny())).Returns(Task.FromResult(new UpdateResponse { ok = false })); + slackApi.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), null, It.IsAny(), It.IsAny())).Returns(Task.FromResult(new SlackResponse { Ok = false })); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Mock().SetupAllProperties(); activity.Object.Id = "MockActivityId"; @@ -161,7 +163,7 @@ public async Task UpdateActivityAsyncShouldFailWithResponseError() using (var turnContext = new TurnContext(slackAdapter, activity.Object)) { - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => { await slackAdapter.UpdateActivityAsync(turnContext, activity.Object, default); }); @@ -174,7 +176,7 @@ public async Task DeleteActivityAsyncShouldFailWithNullReference() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); using (var context = new TurnContext(slackAdapter, new Activity())) { @@ -191,7 +193,7 @@ public async Task DeleteActivityAsyncShouldFailWithNullTurnContext() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var reference = new ConversationReference(); @@ -207,7 +209,7 @@ public async Task DeleteActivityAsyncShouldFailWithNullChannelId() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); using (var context = new TurnContext(slackAdapter, new Activity())) { @@ -229,7 +231,7 @@ public async Task DeleteActivityAsyncShouldFailWithNullTimestamp() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); using (var context = new TurnContext(slackAdapter, new Activity())) { @@ -254,7 +256,7 @@ public async Task DeleteActivityAsyncShouldSucceed() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.DeleteMessageAsync(It.IsAny(), It.IsAny(), It.IsAny())).Callback(() => { deletedMessages++; }); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Mock(); activity.Object.Timestamp = new DateTimeOffset(); @@ -278,7 +280,7 @@ public async Task SendActivitiesAsyncShouldThrowExceptionWithNullContext() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Activity { @@ -302,7 +304,7 @@ public async Task SendActivitiesAsyncShouldThrowExceptionWithNullActivity() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Activity { @@ -334,7 +336,7 @@ public async Task SendActivitiesAsyncShouldSucceed() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.PostMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(slackResponse)); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var activity = new Activity { @@ -361,7 +363,7 @@ public async Task ContinueConversationAsyncShouldFailWithNullReference() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) { @@ -377,7 +379,7 @@ public async Task ContinueConversationAsyncShouldFailWithNullBot() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); await Assert.ThrowsAsync(async () => { await slackAdapter.ContinueConversationAsync(new ConversationReference(), null, default); }); } @@ -390,7 +392,7 @@ public async Task ContinueConversationAsyncShouldSucceed() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) { @@ -409,7 +411,7 @@ public async void ProcessAsyncShouldFailWithNullHttpRequest() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var httpResponse = new Mock(); @@ -425,7 +427,7 @@ public async void ProcessAsyncShouldFailWithNullHttpResponse() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var httpRequest = new Mock(); @@ -441,7 +443,7 @@ public async void ProcessAsyncShouldFailWithNullBot() var slackApi = new Mock(_testOptions); slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var httpRequest = new Mock(); var httpResponse = new Mock(); @@ -461,7 +463,7 @@ public async Task ProcessAsyncShouldSucceedOnUrlVerification() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.VerifySignature(It.IsAny(), It.IsAny())).Returns(true); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/URLVerificationBody.json"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -500,7 +502,7 @@ public async Task ProcessAsyncShouldFailWithSignatureMismatch() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.VerifySignature(It.IsAny(), It.IsAny())).Returns(false); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/MessageBody.json"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -523,7 +525,7 @@ public async Task ProcessAsyncShouldFailWithSignatureMismatch() } }); - await Assert.ThrowsAsync(async () => { await slackAdapter.ProcessAsync(httpRequest.Object, httpResponse.Object, new Mock().Object, default); }); + await Assert.ThrowsAsync(async () => { await slackAdapter.ProcessAsync(httpRequest.Object, httpResponse.Object, new Mock().Object, default); }); } [Fact] @@ -535,7 +537,7 @@ public async Task ProcessAsyncShouldFailOnVerificationTokenMismatch() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.VerifySignature(It.IsAny(), It.IsAny())).Returns(true); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/MessageBody.json"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -553,7 +555,7 @@ public async Task ProcessAsyncShouldFailOnVerificationTokenMismatch() var mockStream = new Mock(); httpResponse.SetupGet(req => req.Body).Returns(mockStream.Object); - await Assert.ThrowsAsync(async () => { await slackAdapter.ProcessAsync(httpRequest.Object, httpResponse.Object, new Mock().Object, default); }); + await Assert.ThrowsAsync(async () => { await slackAdapter.ProcessAsync(httpRequest.Object, httpResponse.Object, new Mock().Object, default); }); } [Fact] @@ -563,7 +565,7 @@ public async Task ProcessAsyncShouldSucceedWithUnknownEventType() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.VerifySignature(It.IsAny(), It.IsAny())).Returns(true); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/UnknownEvent.json"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -594,7 +596,7 @@ public async Task ProcessAsyncShouldSucceedOnEventCallback() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.VerifySignature(It.IsAny(), It.IsAny())).Returns(true); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/MessageBody.json"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -641,7 +643,7 @@ public async Task ProcessAsyncShouldSucceedOnSlashCommand() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.VerifySignature(It.IsAny(), It.IsAny())).Returns(true); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/SlashCommandBody.txt"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -688,7 +690,7 @@ public async Task ProcessAsyncShouldSucceedOnInteractiveMessage() slackApi.Setup(x => x.TestAuthAsync(It.IsAny())).Returns(Task.FromResult("mockedUserId")); slackApi.Setup(x => x.VerifySignature(It.IsAny(), It.IsAny())).Returns(true); - var slackAdapter = new SlackAdapter(_testOptions, slackClient: slackApi.Object); + var slackAdapter = new SlackAdapter(slackApi.Object, _adapterOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/InteractiveMessageBody.txt"); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackClientWrapperTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackClientWrapperTests.cs index 78bd165f3f..7f8dc7620a 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackClientWrapperTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackClientWrapperTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Bot.Builder.Adapters.Slack.Tests { public class SlackClientWrapperTests { - private readonly SlackAdapterOptions _testOptions = new SlackAdapterOptions("VerificationToken", "ClientSigningSecret", "BotToken"); + private readonly SlackClientWrapperOptions _testOptions = new SlackClientWrapperOptions("VerificationToken", "ClientSigningSecret", "BotToken"); [Fact] public void VerifySignatureShouldReturnFalseWithNullParameters() diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackHelperTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackHelperTests.cs index 0dabaaaf91..f527459dd8 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackHelperTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Slack.Tests/SlackHelperTests.cs @@ -21,7 +21,7 @@ public class SlackHelperTests { public const string ImageUrl = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU"; - private readonly SlackAdapterOptions _testOptions = new SlackAdapterOptions("VerificationToken", "ClientSigningSecret", "BotToken"); + private readonly SlackClientWrapperOptions _testOptions = new SlackClientWrapperOptions("VerificationToken", "ClientSigningSecret", "BotToken"); [Fact] public void ActivityToSlackShouldThrowArgumentNullExceptionWithNullActivity() @@ -122,31 +122,31 @@ await Assert.ThrowsAsync(async () => } [Fact] - public async Task EventToActivityAsyncShouldThrowArgumentNullException() + public void EventToActivityAsyncShouldThrowArgumentNullException() { var slackApi = new Mock(_testOptions); - await Assert.ThrowsAsync(async () => + Assert.Throws(() => { - await SlackHelper.EventToActivityAsync(null, slackApi.Object, new CancellationToken()).ConfigureAwait(false); + SlackHelper.EventToActivity(null, slackApi.Object); }); } [Fact] - public async Task EventToActivityAsyncShouldReturnActivity() + public void EventToActivityAsyncShouldReturnActivity() { var slackApi = new Mock(_testOptions); var payload = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/MessageBody.json"); var slackBody = JsonConvert.DeserializeObject(payload); - var activity = await SlackHelper.EventToActivityAsync(slackBody, slackApi.Object, new CancellationToken()).ConfigureAwait(false); + var activity = SlackHelper.EventToActivity(slackBody, slackApi.Object); Assert.Equal(slackBody.Event.AdditionalProperties["text"].ToString(), activity.Text); } [Fact] - public async Task EventToActivityAsyncShouldReturnActivityWithTeamId() + public void EventToActivityAsyncShouldReturnActivityWithTeamId() { var slackApi = new Mock(_testOptions); @@ -154,24 +154,24 @@ public async Task EventToActivityAsyncShouldReturnActivityWithTeamId() var slackBody = JsonConvert.DeserializeObject(payload); slackBody.Event.Channel = null; - var activity = await SlackHelper.EventToActivityAsync(slackBody, slackApi.Object, new CancellationToken()).ConfigureAwait(false); + var activity = SlackHelper.EventToActivity(slackBody, slackApi.Object); Assert.Equal(slackBody.Event.AdditionalProperties["team"].ToString(), activity.Conversation.Id); } [Fact] - public async Task CommandToActivityAsyncShouldThrowArgumentNullException() + public void CommandToActivityAsyncShouldThrowArgumentNullException() { var slackApi = new Mock(_testOptions); - await Assert.ThrowsAsync(async () => + Assert.Throws(() => { - await SlackHelper.CommandToActivityAsync(null, slackApi.Object, new CancellationToken()).ConfigureAwait(false); + SlackHelper.CommandToActivity(null, slackApi.Object); }); } [Fact] - public async Task CommandToActivityAsyncShouldReturnActivity() + public void CommandToActivityAsyncShouldReturnActivity() { var slackApi = new Mock(_testOptions); @@ -179,7 +179,7 @@ public async Task CommandToActivityAsyncShouldReturnActivity() var commandBody = SlackHelper.QueryStringToDictionary(payload); var slackBody = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(commandBody)); - var activity = await SlackHelper.CommandToActivityAsync(slackBody, slackApi.Object, new CancellationToken()).ConfigureAwait(false); + var activity = SlackHelper.CommandToActivity(slackBody, slackApi.Object); Assert.Equal(slackBody.TriggerId, activity.Id); } diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexAdapterTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexAdapterTests.cs index 0be06cd840..88754ef811 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexAdapterTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexAdapterTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Security.Authentication; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,18 +21,19 @@ public class WebexAdapterTests { private static readonly Uri _testPublicAddress = new Uri("http://contoso.com"); private readonly Person _identity = JsonConvert.DeserializeObject(File.ReadAllText(PathUtils.NormalizePath(Directory.GetCurrentDirectory() + @"/Files/Person.json"))); - private readonly WebexAdapterOptions _testOptions = new WebexAdapterOptions("Test", _testPublicAddress, "Test"); + private readonly WebexClientWrapperOptions _testOptions = new WebexClientWrapperOptions("Test", _testPublicAddress, "Test"); + private readonly WebexAdapterOptions _adapterOptions = new WebexAdapterOptions(); [Fact] public void ConstructorWithArgumentsShouldSucceed() { - Assert.NotNull(new WebexAdapter(new Mock(_testOptions).Object)); + Assert.NotNull(new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions)); } [Fact] public void ConstructorShouldFailWithNullClient() { - Assert.Throws(() => { new WebexAdapter((WebexClientWrapper)null); }); + Assert.Throws(() => { new WebexAdapter((WebexClientWrapper)null, _adapterOptions); }); } [Fact] @@ -39,7 +41,7 @@ public async void ContinueConversationAsyncShouldSucceed() { var callbackInvoked = false; - var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object); + var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions); var conversationReference = new ConversationReference(); Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) { @@ -54,7 +56,7 @@ Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) [Fact] public async void ContinueConversationAsyncShouldFailWithNullConversationReference() { - var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object); + var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions); Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) { @@ -67,7 +69,7 @@ Task BotsLogic(ITurnContext turnContext, CancellationToken cancellationToken) [Fact] public async void ContinueConversationAsyncShouldFailWithNullLogic() { - var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object); + var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions); var conversationReference = new ConversationReference(); await Assert.ThrowsAsync(async () => { await webexAdapter.ContinueConversationAsync(conversationReference, null, default); }); @@ -87,7 +89,7 @@ public async void ProcessAsyncWithEvenTypeCreatedShouldSucceed() webexApi.Setup(x => x.ValidateSignature(It.IsAny(), It.IsAny())).Returns(true); webexApi.Setup(x => x.GetMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(message)); - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var httpRequest = new Mock(); httpRequest.SetupGet(req => req.Body).Returns(stream); @@ -112,7 +114,7 @@ public async void ProcessAsyncWithEvenTypeUpdatedShouldSucceed() webexApi.Setup(x => x.GetMeAsync(It.IsAny())).Returns(Task.FromResult(_identity)); webexApi.Setup(x => x.ValidateSignature(It.IsAny(), It.IsAny())).Returns(true); - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var payload = File.ReadAllText(PathUtils.NormalizePath(Directory.GetCurrentDirectory() + @"/Files/Payload2.json")); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -147,7 +149,7 @@ public async void ProcessAsyncWithAttachmentActionsShouldSucceed() webexApi.Setup(x => x.ValidateSignature(It.IsAny(), It.IsAny())).Returns(true); webexApi.Setup(x => x.GetAttachmentActionAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(message)); - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var httpRequest = new Mock(); httpRequest.SetupGet(req => req.Body).Returns(stream); @@ -167,7 +169,7 @@ public async void ProcessAsyncWithAttachmentActionsShouldSucceed() [Fact] public async void ProcessAsyncShouldFailWithNullHttpRequest() { - var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object); + var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions); var httpResponse = new Mock(); var bot = new Mock(); @@ -180,7 +182,7 @@ await Assert.ThrowsAsync(async () => [Fact] public async void ProcessAsyncShouldFailWithNullHttpResponse() { - var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object); + var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions); var httpRequest = new Mock(); var bot = new Mock(); @@ -193,7 +195,7 @@ await Assert.ThrowsAsync(async () => [Fact] public async void ProcessAsyncShouldFailWithNullBot() { - var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object); + var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions); var httpRequest = new Mock(); var httpResponse = new Mock(); @@ -210,7 +212,7 @@ public async void ProcessAsyncShouldFailWithNonMatchingSignature() webexApi.SetupAllProperties(); webexApi.Setup(x => x.GetMeAsync(It.IsAny())).Returns(Task.FromResult(_identity)); - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var payload = File.ReadAllText(PathUtils.NormalizePath(Directory.GetCurrentDirectory() + @"/Files/Payload2.json")); var stream = new MemoryStream(Encoding.UTF8.GetBytes(payload.ToString())); @@ -223,7 +225,7 @@ public async void ProcessAsyncShouldFailWithNonMatchingSignature() var httpResponse = new Mock(); var bot = new Mock(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => { await webexAdapter.ProcessAsync(httpRequest.Object, httpResponse.Object, bot.Object, default); }); @@ -232,7 +234,7 @@ await Assert.ThrowsAsync(async () => [Fact] public async void UpdateActivityAsyncShouldThrowNotSupportedException() { - var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object); + var webexAdapter = new WebexAdapter(new Mock(_testOptions).Object, _adapterOptions); var activity = new Activity(); @@ -253,7 +255,7 @@ public async void SendActivitiesAsyncNotNullToPersonEmailShouldSucceed() webexApi.Setup(x => x.CreateMessageAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedResponseId)); // Create a new Webex Adapter with the mocked classes and get the responses - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var activity = new Mock().SetupAllProperties(); activity.Object.Type = "message"; @@ -277,7 +279,7 @@ public async void SendActivitiesAsyncWithAttachmentShouldSucceed() webexApi.Setup(x => x.CreateMessageAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedResponseId)); // Create a new Webex Adapter with the mocked classes and get the responses - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var activity = new Mock().SetupAllProperties(); activity.Object.Type = "message"; @@ -303,7 +305,7 @@ public async void SendActivitiesAsyncWithAttachmentActionsShouldSucceed() var webexApi = new Mock(_testOptions); webexApi.Setup(x => x.CreateMessageWithAttachmentsAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedResponseId)); - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var activity = new Mock().SetupAllProperties(); activity.Object.Type = "message"; @@ -328,7 +330,7 @@ public async void SendActivitiesAsyncShouldSucceedAndNoActivityReturnedWithActiv var webexApi = new Mock(_testOptions); webexApi.Setup(x => x.CreateMessageWithAttachmentsAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedResponseId)); - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var activity = new Mock().SetupAllProperties(); activity.Object.Type = ActivityTypes.Trace; @@ -351,14 +353,14 @@ public async void SendActivitiesAsyncShouldFailWithNullToPersonEmail() webexApi.Setup(x => x.CreateMessageAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(expectedResponseId)); // Create a new Webex Adapter with the mocked classes and get the responses - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var activity = new Mock().SetupAllProperties(); activity.Object.Type = "message"; activity.Object.Text = "Hello, Bot!"; var turnContext = new TurnContext(webexAdapter, activity.Object); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => { await webexAdapter.SendActivitiesAsync(turnContext, new Activity[] { activity.Object }, default); }); @@ -372,7 +374,7 @@ public async void DeleteActivityAsyncWithActivityIdShouldSucceed() var webexApi = new Mock(_testOptions); webexApi.Setup(x => x.DeleteMessageAsync(It.IsAny(), It.IsAny())).Callback(() => { deletedMessages++; }); - var webexAdapter = new WebexAdapter(webexApi.Object); + var webexAdapter = new WebexAdapter(webexApi.Object, _adapterOptions); var activity = new Activity(); diff --git a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexHelperTests.cs b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexHelperTests.cs index 57580c31c8..7bae4cdbae 100644 --- a/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexHelperTests.cs +++ b/tests/Adapters/Microsoft.Bot.Builder.Adapters.Webex.Tests/WebexHelperTests.cs @@ -44,7 +44,7 @@ public async void GetDecryptedMessageAsyncShouldReturnNullWithNullPayload() [Fact] public async void GetDecryptedMessageAsyncShouldSucceed() { - var testOptions = new WebexAdapterOptions("Test", new Uri("http://contoso.com"), "Test"); + var testOptions = new WebexClientWrapperOptions("Test", new Uri("http://contoso.com"), "Test"); var payload = JsonConvert.DeserializeObject(File.ReadAllText(PathUtils.NormalizePath(Directory.GetCurrentDirectory() + @"/Files/Payload.json"))); var message = JsonConvert.DeserializeObject(File.ReadAllText(PathUtils.NormalizePath(Directory.GetCurrentDirectory() + @"/Files/Message.json")));