1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

Progress on integrating new Repositories to existing code

This commit is contained in:
Brant DeBow 2025-03-28 10:04:35 -04:00
commit 6d9c2956ce
No known key found for this signature in database
GPG Key ID: 94411BB25947C72B
13 changed files with 113 additions and 114 deletions

View File

@ -1,4 +1,6 @@
using Bit.Core.Context; using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Integrations; using Bit.Core.Models.Data.Integrations;
@ -13,14 +15,13 @@ namespace Bit.Api.AdminConsole.Controllers;
[Authorize("Application")] [Authorize("Application")]
public class SlackOAuthController( public class SlackOAuthController(
ICurrentContext currentContext, ICurrentContext currentContext,
IOrganizationIntegrationConfigurationRepository integrationConfigurationRepository, IOrganizationIntegrationRepository integrationRepository,
ISlackService slackService) : Controller ISlackService slackService) : Controller
{ {
[HttpGet("redirect/{id}")] [HttpGet("redirect/{id:guid}")]
public async Task<IActionResult> RedirectToSlack(string id) public async Task<IActionResult> RedirectToSlack(Guid id)
{ {
var orgIdGuid = new Guid(id); if (!await currentContext.OrganizationOwner(id))
if (!await currentContext.OrganizationOwner(orgIdGuid))
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
@ -35,11 +36,10 @@ public class SlackOAuthController(
return Redirect(redirectUrl); return Redirect(redirectUrl);
} }
[HttpGet("callback/{id}", Name = nameof(OAuthCallback))] [HttpGet("callback/{id:guid}", Name = nameof(OAuthCallback))]
public async Task<IActionResult> OAuthCallback(string id, [FromQuery] string code) public async Task<IActionResult> OAuthCallback(Guid id, [FromQuery] string code)
{ {
var orgIdGuid = new Guid(id); if (!await currentContext.OrganizationOwner(id))
if (!await currentContext.OrganizationOwner(orgIdGuid))
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
@ -49,7 +49,7 @@ public class SlackOAuthController(
throw new BadRequestException("Missing code from Slack."); throw new BadRequestException("Missing code from Slack.");
} }
string callbackUrl = Url.RouteUrl(nameof(OAuthCallback)); string callbackUrl = Url.RouteUrl(nameof(OAuthCallback), new { id = id }, currentContext.HttpContext.Request.Scheme);
var token = await slackService.ObtainTokenViaOAuth(code, callbackUrl); var token = await slackService.ObtainTokenViaOAuth(code, callbackUrl);
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
@ -57,10 +57,12 @@ public class SlackOAuthController(
throw new BadRequestException("Invalid response from Slack."); throw new BadRequestException("Invalid response from Slack.");
} }
await integrationConfigurationRepository.CreateOrganizationIntegrationAsync( var integration = await integrationRepository.CreateAsync(new OrganizationIntegration
orgIdGuid, {
IntegrationType.Slack, OrganizationId = id,
new SlackIntegration(token)); Type = IntegrationType.Slack,
return Ok("Slack OAuth successful. Your bot is now installed."); Configuration = JsonSerializer.Serialize(new SlackIntegration(token)),
});
return Ok("Your bot is now installed.");
} }
} }

View File

@ -30,7 +30,6 @@ using Bit.Api.Auth.Models.Request.WebAuthn;
using Bit.Core.AdminConsole.Services.NoopImplementations; using Bit.Core.AdminConsole.Services.NoopImplementations;
using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Identity.TokenProviders; using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Tools.ImportFeatures; using Bit.Core.Tools.ImportFeatures;
using Bit.Core.Tools.ReportFeatures; using Bit.Core.Tools.ReportFeatures;
@ -223,14 +222,10 @@ public class Startup
{ {
services.AddHttpClient(SlackService.HttpClientName); services.AddHttpClient(SlackService.HttpClientName);
services.AddSingleton<ISlackService, SlackService>(); services.AddSingleton<ISlackService, SlackService>();
services.AddSingleton<IOrganizationIntegrationConfigurationRepository,
LocalOrganizationIntegrationConfigurationRepository>();
} }
else else
{ {
services.AddSingleton<ISlackService, NoopSlackService>(); services.AddSingleton<ISlackService, NoopSlackService>();
services.AddSingleton<IOrganizationIntegrationConfigurationRepository,
LocalOrganizationIntegrationConfigurationRepository>();
} }
} }

View File

@ -1,17 +1,3 @@
namespace Bit.Core.Models.Data.Integrations; namespace Bit.Core.Models.Data.Integrations;
public class SlackConfiguration public record SlackConfiguration(string channelId, string token);
{
public SlackConfiguration()
{
}
public SlackConfiguration(string channelId, string token)
{
ChannelId = channelId;
Token = token;
}
public string Token { get; set; } = string.Empty;
public string ChannelId { get; set; } = string.Empty;
}

View File

@ -1,7 +1,3 @@
namespace Bit.Core.Models.Data.Integrations; namespace Bit.Core.Models.Data.Integrations;
public class WebhookConfiguration public record WebhookConfiguration(string url);
{
public string Url { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
}

View File

@ -1,17 +1,12 @@
using Bit.Core.Enums; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Models.Data.Integrations; using Bit.Core.Enums;
namespace Bit.Core.Repositories; namespace Bit.Core.Repositories;
public interface IOrganizationIntegrationConfigurationRepository public interface IOrganizationIntegrationConfigurationRepository : IRepository<OrganizationIntegrationConfiguration, Guid>
{ {
Task<List<IntegrationConfiguration<T>>> GetConfigurationsAsync<T>( Task<List<OrganizationIntegrationConfiguration>> GetConfigurationsAsync(
Guid organizationId, Guid organizationId,
IntegrationType integrationType, IntegrationType integrationType,
EventType eventType); EventType eventType);
Task CreateOrganizationIntegrationAsync<T>(
Guid organizationId,
IntegrationType integrationType,
T configuration);
} }

View File

@ -0,0 +1,7 @@
using Bit.Core.AdminConsole.Entities;
namespace Bit.Core.Repositories;
public interface IOrganizationIntegrationRepository : IRepository<OrganizationIntegration, Guid>
{
}

View File

@ -1,52 +0,0 @@
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Integrations;
using Bit.Core.Settings;
namespace Bit.Core.Repositories;
public class LocalOrganizationIntegrationConfigurationRepository(GlobalSettings globalSettings)
: IOrganizationIntegrationConfigurationRepository
{
public async Task<List<IntegrationConfiguration<T>>> GetConfigurationsAsync<T>(Guid organizationId,
IntegrationType integrationType,
EventType eventType)
{
var configurations = new List<IntegrationConfiguration<T>>();
switch (integrationType)
{
case IntegrationType.Slack:
foreach (var configuration in globalSettings.EventLogging.SlackConfigurations)
{
configurations.Add(new IntegrationConfiguration<SlackConfiguration>
{
Configuration = configuration,
Template = "This is a test of the new Slack integration, #UserId#, #Type#, #Date#"
} as IntegrationConfiguration<T>);
}
break;
case IntegrationType.Webhook:
foreach (var configuration in globalSettings.EventLogging.WebhookConfigurations)
{
configurations.Add(new IntegrationConfiguration<WebhookConfiguration>
{
Configuration = configuration,
Template = "{ \"Date\": \"#Date#\", \"Type\": \"#Type#\", \"UserId\": \"#UserId#\" }"
} as IntegrationConfiguration<T>);
}
break;
}
return configurations;
}
public async Task CreateOrganizationIntegrationAsync<T>(
Guid organizationId,
IntegrationType integrationType,
T configuration)
{
var json = JsonSerializer.Serialize(configuration);
Console.WriteLine($"Organization: {organizationId}, IntegrationType: {integrationType}, Configuration: {json}");
}
}

View File

@ -1,4 +1,5 @@
using Bit.Core.Enums; using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Integrations; using Bit.Core.Models.Data.Integrations;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -12,15 +13,21 @@ public class SlackEventHandler(
{ {
public async Task HandleEventAsync(EventMessage eventMessage) public async Task HandleEventAsync(EventMessage eventMessage)
{ {
var organizationId = eventMessage.OrganizationId ?? Guid.NewGuid(); var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
var configurations = await configurationRepository.GetConfigurationsAsync<SlackConfiguration>(organizationId, IntegrationType.Slack, eventMessage.Type); var configurations = await configurationRepository.GetConfigurationsAsync(organizationId, IntegrationType.Slack, eventMessage.Type);
foreach (var configuration in configurations) foreach (var configuration in configurations)
{ {
var config = JsonSerializer.Deserialize<SlackConfiguration>(configuration.Configuration ?? string.Empty);
if (config is null)
{
continue;
}
await slackService.SendSlackMessageByChannelIdAsync( await slackService.SendSlackMessageByChannelIdAsync(
configuration.Configuration.Token, config.token,
TemplateProcessor.ReplaceTokens(configuration.Template, eventMessage), TemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
configuration.Configuration.ChannelId config.channelId
); );
} }
} }

View File

@ -1,4 +1,5 @@
using System.Text; using System.Text;
using System.Text.Json;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Integrations; using Bit.Core.Models.Data.Integrations;
@ -19,20 +20,25 @@ public class WebhookEventHandler(
public async Task HandleEventAsync(EventMessage eventMessage) public async Task HandleEventAsync(EventMessage eventMessage)
{ {
Guid organizationId = eventMessage.OrganizationId ?? Guid.NewGuid(); var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
var configurations = await configurationRepository.GetConfigurationsAsync(organizationId,
var configurations = await configurationRepository.GetConfigurationsAsync<WebhookConfiguration>(organizationId,
IntegrationType.Webhook, eventMessage.Type); IntegrationType.Webhook, eventMessage.Type);
foreach (var configuration in configurations) foreach (var configuration in configurations)
{ {
var config = JsonSerializer.Deserialize<WebhookConfiguration>(configuration.Configuration ?? string.Empty);
if (config is null)
{
continue;
}
var content = new StringContent( var content = new StringContent(
TemplateProcessor.ReplaceTokens(configuration.Template, eventMessage), TemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
Encoding.UTF8, Encoding.UTF8,
"application/json" "application/json"
); );
var response = await _httpClient.PostAsync( var response = await _httpClient.PostAsync(
configuration.Configuration.Url, config.url,
content); content);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }

View File

@ -3,7 +3,6 @@ using Bit.Core.AdminConsole.Services.Implementations;
using Bit.Core.AdminConsole.Services.NoopImplementations; using Bit.Core.AdminConsole.Services.NoopImplementations;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.IdentityServer; using Bit.Core.IdentityServer;
using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -119,8 +118,6 @@ public class Startup
globalSettings, globalSettings,
globalSettings.EventLogging.RabbitMq.EventRepositoryQueueName)); globalSettings.EventLogging.RabbitMq.EventRepositoryQueueName));
services.AddSingleton<IOrganizationIntegrationConfigurationRepository, LocalOrganizationIntegrationConfigurationRepository>();
if (CoreHelpers.SettingHasValue(globalSettings.Slack.ClientId) && if (CoreHelpers.SettingHasValue(globalSettings.Slack.ClientId) &&
CoreHelpers.SettingHasValue(globalSettings.Slack.ClientSecret) && CoreHelpers.SettingHasValue(globalSettings.Slack.ClientSecret) &&
CoreHelpers.SettingHasValue(globalSettings.Slack.Scopes)) CoreHelpers.SettingHasValue(globalSettings.Slack.Scopes))

View File

@ -0,0 +1,42 @@
using System.Data;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Infrastructure.Dapper.Repositories;
using Dapper;
using Microsoft.Data.SqlClient;
namespace Bit.Infrastructure.Dapper.AdminConsole.Repositories;
public class OrganizationIntegrationConfigurationRepository : Repository<OrganizationIntegrationConfiguration, Guid>, IOrganizationIntegrationConfigurationRepository
{
public OrganizationIntegrationConfigurationRepository(GlobalSettings globalSettings)
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
{ }
public OrganizationIntegrationConfigurationRepository(string connectionString, string readOnlyConnectionString)
: base(connectionString, readOnlyConnectionString)
{ }
public async Task<List<OrganizationIntegrationConfiguration>> GetConfigurationsAsync(
Guid organizationId,
IntegrationType integrationType,
EventType eventType)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<OrganizationIntegrationConfiguration>(
"[dbo].[OrganizationIntegrationConfiguration_ReadManyByEventTypeOrganizationIdIntegrationType]",
new
{
EventType = eventType,
OrganizationId = organizationId,
IntegrationType = integrationType
},
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
}

View File

@ -0,0 +1,16 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Repositories;
using Bit.Core.Settings;
namespace Bit.Infrastructure.Dapper.Repositories;
public class OrganizationIntegrationRepository : Repository<OrganizationIntegration, Guid>, IOrganizationIntegrationRepository
{
public OrganizationIntegrationRepository(GlobalSettings globalSettings)
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
{ }
public OrganizationIntegrationRepository(string connectionString, string readOnlyConnectionString)
: base(connectionString, readOnlyConnectionString)
{ }
}

View File

@ -41,6 +41,8 @@ public static class DapperServiceCollectionExtensions
services.AddSingleton<IMaintenanceRepository, MaintenanceRepository>(); services.AddSingleton<IMaintenanceRepository, MaintenanceRepository>();
services.AddSingleton<IOrganizationApiKeyRepository, OrganizationApiKeyRepository>(); services.AddSingleton<IOrganizationApiKeyRepository, OrganizationApiKeyRepository>();
services.AddSingleton<IOrganizationConnectionRepository, OrganizationConnectionRepository>(); services.AddSingleton<IOrganizationConnectionRepository, OrganizationConnectionRepository>();
services.AddSingleton<IOrganizationIntegrationConfigurationRepository, OrganizationIntegrationConfigurationRepository>();
services.AddSingleton<IOrganizationIntegrationRepository, OrganizationIntegrationRepository>();
services.AddSingleton<IOrganizationRepository, OrganizationRepository>(); services.AddSingleton<IOrganizationRepository, OrganizationRepository>();
services.AddSingleton<IOrganizationSponsorshipRepository, OrganizationSponsorshipRepository>(); services.AddSingleton<IOrganizationSponsorshipRepository, OrganizationSponsorshipRepository>();
services.AddSingleton<IOrganizationUserRepository, OrganizationUserRepository>(); services.AddSingleton<IOrganizationUserRepository, OrganizationUserRepository>();