mirror of
https://github.com/bitwarden/server.git
synced 2025-06-27 14:16:19 -05:00
[PM-17562] Add support for Auth on Webhook integration requests (#5970)
* [PM-17562] Update documentation for event integrations * Fix SonarQube suggestion, bring ASB event listener in line with integration listener * Apply suggestions from code review Co-authored-by: Matt Bishop <mbishop@bitwarden.com> * Updates to README - PR fixes, additional context, tense alignment * Add links to different sections; remove inline code formatting in favor of single bacticks for JSON * [PM-17562] Add aupport for Auth on Webhook integration requests * Repsond to PR feedback - move optional params to end, add tests for optional cases --------- Co-authored-by: Matt Bishop <mbishop@bitwarden.com>
This commit is contained in:
parent
7fd1ccb7a2
commit
b418b07f26
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
|
|
||||||
public record SlackIntegration(string token);
|
public record SlackIntegration(string Token);
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
|
|
||||||
public record SlackIntegrationConfiguration(string channelId);
|
public record SlackIntegrationConfiguration(string ChannelId);
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
|
|
||||||
public record SlackIntegrationConfigurationDetails(string channelId, string token);
|
public record SlackIntegrationConfigurationDetails(string ChannelId, string Token);
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
|
|
||||||
public record WebhookIntegrationConfiguration(string url);
|
public record WebhookIntegrationConfiguration(string Url, string? Scheme = null, string? Token = null);
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
|
|
||||||
public record WebhookIntegrationConfigurationDetails(string url);
|
public record WebhookIntegrationConfigurationDetails(string Url, string? Scheme = null, string? Token = null);
|
||||||
|
@ -11,9 +11,9 @@ public class SlackIntegrationHandler(
|
|||||||
public override async Task<IntegrationHandlerResult> HandleAsync(IntegrationMessage<SlackIntegrationConfigurationDetails> message)
|
public override async Task<IntegrationHandlerResult> HandleAsync(IntegrationMessage<SlackIntegrationConfigurationDetails> message)
|
||||||
{
|
{
|
||||||
await slackService.SendSlackMessageByChannelIdAsync(
|
await slackService.SendSlackMessageByChannelIdAsync(
|
||||||
message.Configuration.token,
|
message.Configuration.Token,
|
||||||
message.RenderedTemplate,
|
message.RenderedTemplate,
|
||||||
message.Configuration.channelId
|
message.Configuration.ChannelId
|
||||||
);
|
);
|
||||||
|
|
||||||
return new IntegrationHandlerResult(success: true, message: message);
|
return new IntegrationHandlerResult(success: true, message: message);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
|
|
||||||
@ -20,8 +21,16 @@ public class WebhookIntegrationHandler(
|
|||||||
|
|
||||||
public override async Task<IntegrationHandlerResult> HandleAsync(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
public override async Task<IntegrationHandlerResult> HandleAsync(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||||
{
|
{
|
||||||
var content = new StringContent(message.RenderedTemplate, Encoding.UTF8, "application/json");
|
var request = new HttpRequestMessage(HttpMethod.Post, message.Configuration.Url);
|
||||||
var response = await _httpClient.PostAsync(message.Configuration.url, content);
|
request.Content = new StringContent(message.RenderedTemplate, Encoding.UTF8, "application/json");
|
||||||
|
if (!string.IsNullOrEmpty(message.Configuration.Scheme))
|
||||||
|
{
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue(
|
||||||
|
scheme: message.Configuration.Scheme,
|
||||||
|
parameter: message.Configuration.Token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
var result = new IntegrationHandlerResult(success: response.IsSuccessStatusCode, message);
|
var result = new IntegrationHandlerResult(success: response.IsSuccessStatusCode, message);
|
||||||
|
|
||||||
switch (response.StatusCode)
|
switch (response.StatusCode)
|
||||||
|
@ -151,7 +151,7 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
{
|
{
|
||||||
organizationIntegration.OrganizationId = organizationId;
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
organizationIntegration.Type = IntegrationType.Slack;
|
organizationIntegration.Type = IntegrationType.Slack;
|
||||||
var slackConfig = new SlackIntegrationConfiguration(channelId: "C123456");
|
var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456");
|
||||||
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
||||||
model.Template = "Template String";
|
model.Template = "Template String";
|
||||||
|
|
||||||
@ -188,7 +188,44 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
{
|
{
|
||||||
organizationIntegration.OrganizationId = organizationId;
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
organizationIntegration.Type = IntegrationType.Webhook;
|
organizationIntegration.Type = IntegrationType.Webhook;
|
||||||
var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost");
|
var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||||
|
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||||
|
model.Template = "Template String";
|
||||||
|
|
||||||
|
var expected = new OrganizationIntegrationConfigurationResponseModel(organizationIntegrationConfiguration);
|
||||||
|
|
||||||
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationOwner(organizationId)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||||
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
|
.Returns(organizationIntegration);
|
||||||
|
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||||
|
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||||
|
.Returns(organizationIntegrationConfiguration);
|
||||||
|
var requestAction = await sutProvider.Sut.CreateAsync(organizationId, organizationIntegration.Id, model);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||||
|
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||||
|
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(requestAction);
|
||||||
|
Assert.Equal(expected.Id, requestAction.Id);
|
||||||
|
Assert.Equal(expected.Configuration, requestAction.Configuration);
|
||||||
|
Assert.Equal(expected.EventType, requestAction.EventType);
|
||||||
|
Assert.Equal(expected.Template, requestAction.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task PostAsync_OnlyUrlProvided_Webhook_Succeeds(
|
||||||
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
|
Guid organizationId,
|
||||||
|
OrganizationIntegration organizationIntegration,
|
||||||
|
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||||
|
OrganizationIntegrationConfigurationRequestModel model)
|
||||||
|
{
|
||||||
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
|
organizationIntegration.Type = IntegrationType.Webhook;
|
||||||
|
var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost");
|
||||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||||
model.Template = "Template String";
|
model.Template = "Template String";
|
||||||
|
|
||||||
@ -350,7 +387,7 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
{
|
{
|
||||||
organizationIntegration.OrganizationId = organizationId;
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
organizationIntegration.Type = IntegrationType.Webhook;
|
organizationIntegration.Type = IntegrationType.Webhook;
|
||||||
var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost");
|
var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||||
model.Template = null;
|
model.Template = null;
|
||||||
|
|
||||||
@ -393,7 +430,7 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
organizationIntegration.OrganizationId = organizationId;
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||||
organizationIntegration.Type = IntegrationType.Slack;
|
organizationIntegration.Type = IntegrationType.Slack;
|
||||||
var slackConfig = new SlackIntegrationConfiguration(channelId: "C123456");
|
var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456");
|
||||||
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
||||||
model.Template = "Template String";
|
model.Template = "Template String";
|
||||||
|
|
||||||
@ -436,7 +473,49 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
organizationIntegration.OrganizationId = organizationId;
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||||
organizationIntegration.Type = IntegrationType.Webhook;
|
organizationIntegration.Type = IntegrationType.Webhook;
|
||||||
var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost");
|
var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||||
|
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||||
|
model.Template = "Template String";
|
||||||
|
|
||||||
|
var expected = new OrganizationIntegrationConfigurationResponseModel(model.ToOrganizationIntegrationConfiguration(organizationIntegrationConfiguration));
|
||||||
|
|
||||||
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationOwner(organizationId)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||||
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
|
.Returns(organizationIntegration);
|
||||||
|
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||||
|
.GetByIdAsync(Arg.Any<Guid>())
|
||||||
|
.Returns(organizationIntegrationConfiguration);
|
||||||
|
var requestAction = await sutProvider.Sut.UpdateAsync(
|
||||||
|
organizationId,
|
||||||
|
organizationIntegration.Id,
|
||||||
|
organizationIntegrationConfiguration.Id,
|
||||||
|
model);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||||
|
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||||
|
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(requestAction);
|
||||||
|
Assert.Equal(expected.Id, requestAction.Id);
|
||||||
|
Assert.Equal(expected.Configuration, requestAction.Configuration);
|
||||||
|
Assert.Equal(expected.EventType, requestAction.EventType);
|
||||||
|
Assert.Equal(expected.Template, requestAction.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task UpdateAsync_OnlyUrlProvided_Webhook_Succeeds(
|
||||||
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
|
Guid organizationId,
|
||||||
|
OrganizationIntegration organizationIntegration,
|
||||||
|
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||||
|
OrganizationIntegrationConfigurationRequestModel model)
|
||||||
|
{
|
||||||
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
|
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||||
|
organizationIntegration.Type = IntegrationType.Webhook;
|
||||||
|
var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost");
|
||||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||||
model.Template = "Template String";
|
model.Template = "Template String";
|
||||||
|
|
||||||
@ -476,7 +555,7 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
{
|
{
|
||||||
organizationIntegration.OrganizationId = organizationId;
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
organizationIntegration.Type = IntegrationType.Webhook;
|
organizationIntegration.Type = IntegrationType.Webhook;
|
||||||
var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost");
|
var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||||
model.Template = "Template String";
|
model.Template = "Template String";
|
||||||
|
|
||||||
@ -582,7 +661,7 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
organizationIntegration.OrganizationId = organizationId;
|
organizationIntegration.OrganizationId = organizationId;
|
||||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||||
organizationIntegration.Type = IntegrationType.Slack;
|
organizationIntegration.Type = IntegrationType.Slack;
|
||||||
var slackConfig = new SlackIntegrationConfiguration(channelId: "C123456");
|
var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456");
|
||||||
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
||||||
model.Template = null;
|
model.Template = null;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class OrganizationIntegrationConfigurationRequestModelTests
|
|||||||
[InlineData(" ")]
|
[InlineData(" ")]
|
||||||
public void IsValidForType_EmptyTemplate_ReturnsFalse(string? template)
|
public void IsValidForType_EmptyTemplate_ReturnsFalse(string? template)
|
||||||
{
|
{
|
||||||
var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com"));
|
var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com", "Bearer", "AUTH-TOKEN"));
|
||||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||||
{
|
{
|
||||||
Configuration = config,
|
Configuration = config,
|
||||||
@ -92,7 +92,7 @@ public class OrganizationIntegrationConfigurationRequestModelTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsValidForType_ValidWebhookConfiguration_ReturnsTrue()
|
public void IsValidForType_ValidNoAuthWebhookConfiguration_ReturnsTrue()
|
||||||
{
|
{
|
||||||
var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com"));
|
var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com"));
|
||||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||||
@ -104,6 +104,19 @@ public class OrganizationIntegrationConfigurationRequestModelTests
|
|||||||
Assert.True(model.IsValidForType(IntegrationType.Webhook));
|
Assert.True(model.IsValidForType(IntegrationType.Webhook));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValidForType_ValidWebhookConfiguration_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com", "Bearer", "AUTH-TOKEN"));
|
||||||
|
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||||
|
{
|
||||||
|
Configuration = config,
|
||||||
|
Template = "template"
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.True(model.IsValidForType(IntegrationType.Webhook));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsValidForType_UnknownIntegrationType_ReturnsFalse()
|
public void IsValidForType_UnknownIntegrationType_ReturnsFalse()
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@ public class IntegrationMessageTests
|
|||||||
{
|
{
|
||||||
var message = new IntegrationMessage<WebhookIntegrationConfigurationDetails>
|
var message = new IntegrationMessage<WebhookIntegrationConfigurationDetails>
|
||||||
{
|
{
|
||||||
Configuration = new WebhookIntegrationConfigurationDetails("https://localhost"),
|
Configuration = new WebhookIntegrationConfigurationDetails("https://localhost", "Bearer", "AUTH-TOKEN"),
|
||||||
MessageId = _messageId,
|
MessageId = _messageId,
|
||||||
RetryCount = 2,
|
RetryCount = 2,
|
||||||
RenderedTemplate = string.Empty,
|
RenderedTemplate = string.Empty,
|
||||||
@ -34,7 +34,7 @@ public class IntegrationMessageTests
|
|||||||
{
|
{
|
||||||
var message = new IntegrationMessage<WebhookIntegrationConfigurationDetails>
|
var message = new IntegrationMessage<WebhookIntegrationConfigurationDetails>
|
||||||
{
|
{
|
||||||
Configuration = new WebhookIntegrationConfigurationDetails("https://localhost"),
|
Configuration = new WebhookIntegrationConfigurationDetails("https://localhost", "Bearer", "AUTH-TOKEN"),
|
||||||
MessageId = _messageId,
|
MessageId = _messageId,
|
||||||
RenderedTemplate = "This is the message",
|
RenderedTemplate = "This is the message",
|
||||||
IntegrationType = IntegrationType.Webhook,
|
IntegrationType = IntegrationType.Webhook,
|
||||||
|
@ -62,7 +62,7 @@ public class EventIntegrationHandlerTests
|
|||||||
{
|
{
|
||||||
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||||
config.Configuration = null;
|
config.Configuration = null;
|
||||||
config.IntegrationConfiguration = JsonSerializer.Serialize(new { url = _url });
|
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url });
|
||||||
config.Template = template;
|
config.Template = template;
|
||||||
|
|
||||||
return [config];
|
return [config];
|
||||||
@ -72,11 +72,11 @@ public class EventIntegrationHandlerTests
|
|||||||
{
|
{
|
||||||
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
var config = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||||
config.Configuration = null;
|
config.Configuration = null;
|
||||||
config.IntegrationConfiguration = JsonSerializer.Serialize(new { url = _url });
|
config.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url });
|
||||||
config.Template = template;
|
config.Template = template;
|
||||||
var config2 = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
var config2 = Substitute.For<OrganizationIntegrationConfigurationDetails>();
|
||||||
config2.Configuration = null;
|
config2.Configuration = null;
|
||||||
config2.IntegrationConfiguration = JsonSerializer.Serialize(new { url = _url2 });
|
config2.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url2 });
|
||||||
config2.Template = template;
|
config2.Template = template;
|
||||||
|
|
||||||
return [config, config2];
|
return [config, config2];
|
||||||
|
@ -14,7 +14,7 @@ public class IntegrationHandlerTests
|
|||||||
var sut = new TestIntegrationHandler();
|
var sut = new TestIntegrationHandler();
|
||||||
var expected = new IntegrationMessage<WebhookIntegrationConfigurationDetails>()
|
var expected = new IntegrationMessage<WebhookIntegrationConfigurationDetails>()
|
||||||
{
|
{
|
||||||
Configuration = new WebhookIntegrationConfigurationDetails("https://localhost"),
|
Configuration = new WebhookIntegrationConfigurationDetails("https://localhost", "Bearer", "AUTH-TOKEN"),
|
||||||
MessageId = "TestMessageId",
|
MessageId = "TestMessageId",
|
||||||
IntegrationType = IntegrationType.Webhook,
|
IntegrationType = IntegrationType.Webhook,
|
||||||
RenderedTemplate = "Template",
|
RenderedTemplate = "Template",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
@ -16,6 +17,8 @@ public class WebhookIntegrationHandlerTests
|
|||||||
{
|
{
|
||||||
private readonly MockedHttpMessageHandler _handler;
|
private readonly MockedHttpMessageHandler _handler;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private const string _scheme = "Bearer";
|
||||||
|
private const string _token = "AUTH_TOKEN";
|
||||||
private const string _webhookUrl = "http://localhost/test/event";
|
private const string _webhookUrl = "http://localhost/test/event";
|
||||||
|
|
||||||
public WebhookIntegrationHandlerTests()
|
public WebhookIntegrationHandlerTests()
|
||||||
@ -39,7 +42,7 @@ public class WebhookIntegrationHandlerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task HandleAsync_SuccessfulRequest_ReturnsSuccess(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
public async Task HandleAsync_SuccessfulRequestWithoutAuth_ReturnsSuccess(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||||
{
|
{
|
||||||
var sutProvider = GetSutProvider();
|
var sutProvider = GetSutProvider();
|
||||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
||||||
@ -59,6 +62,33 @@ public class WebhookIntegrationHandlerTests
|
|||||||
var returned = await request.Content.ReadAsStringAsync();
|
var returned = await request.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
Assert.Equal(HttpMethod.Post, request.Method);
|
Assert.Equal(HttpMethod.Post, request.Method);
|
||||||
|
Assert.Null(request.Headers.Authorization);
|
||||||
|
Assert.Equal(_webhookUrl, request.RequestUri.ToString());
|
||||||
|
AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleAsync_SuccessfulRequestWithAuthorizationHeader_ReturnsSuccess(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token);
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.HandleAsync(message);
|
||||||
|
|
||||||
|
Assert.True(result.Success);
|
||||||
|
Assert.Equal(result.Message, message);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookIntegrationHandler.HttpClientName))
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Single(_handler.CapturedRequests);
|
||||||
|
var request = _handler.CapturedRequests[0];
|
||||||
|
Assert.NotNull(request);
|
||||||
|
var returned = await request.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
Assert.Equal(HttpMethod.Post, request.Method);
|
||||||
|
Assert.Equal(new AuthenticationHeaderValue(_scheme, _token), request.Headers.Authorization);
|
||||||
Assert.Equal(_webhookUrl, request.RequestUri.ToString());
|
Assert.Equal(_webhookUrl, request.RequestUri.ToString());
|
||||||
AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned);
|
AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned);
|
||||||
}
|
}
|
||||||
@ -71,7 +101,7 @@ public class WebhookIntegrationHandlerTests
|
|||||||
var retryAfter = now.AddSeconds(60);
|
var retryAfter = now.AddSeconds(60);
|
||||||
|
|
||||||
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
|
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
|
||||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token);
|
||||||
|
|
||||||
_handler.Fallback
|
_handler.Fallback
|
||||||
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
||||||
@ -94,7 +124,7 @@ public class WebhookIntegrationHandlerTests
|
|||||||
var sutProvider = GetSutProvider();
|
var sutProvider = GetSutProvider();
|
||||||
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||||
var retryAfter = now.AddSeconds(60);
|
var retryAfter = now.AddSeconds(60);
|
||||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token);
|
||||||
|
|
||||||
_handler.Fallback
|
_handler.Fallback
|
||||||
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
||||||
@ -115,7 +145,7 @@ public class WebhookIntegrationHandlerTests
|
|||||||
public async Task HandleAsync_InternalServerError_ReturnsFailureSetsRetryable(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
public async Task HandleAsync_InternalServerError_ReturnsFailureSetsRetryable(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||||
{
|
{
|
||||||
var sutProvider = GetSutProvider();
|
var sutProvider = GetSutProvider();
|
||||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token);
|
||||||
|
|
||||||
_handler.Fallback
|
_handler.Fallback
|
||||||
.WithStatusCode(HttpStatusCode.InternalServerError)
|
.WithStatusCode(HttpStatusCode.InternalServerError)
|
||||||
@ -134,7 +164,7 @@ public class WebhookIntegrationHandlerTests
|
|||||||
public async Task HandleAsync_UnexpectedRedirect_ReturnsFailureNotRetryable(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
public async Task HandleAsync_UnexpectedRedirect_ReturnsFailureNotRetryable(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||||
{
|
{
|
||||||
var sutProvider = GetSutProvider();
|
var sutProvider = GetSutProvider();
|
||||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token);
|
||||||
|
|
||||||
_handler.Fallback
|
_handler.Fallback
|
||||||
.WithStatusCode(HttpStatusCode.TemporaryRedirect)
|
.WithStatusCode(HttpStatusCode.TemporaryRedirect)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user