1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 20:50:21 -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.Exceptions;
using Bit.Core.Models.Data.Integrations;
@ -13,14 +15,13 @@ namespace Bit.Api.AdminConsole.Controllers;
[Authorize("Application")]
public class SlackOAuthController(
ICurrentContext currentContext,
IOrganizationIntegrationConfigurationRepository integrationConfigurationRepository,
IOrganizationIntegrationRepository integrationRepository,
ISlackService slackService) : Controller
{
[HttpGet("redirect/{id}")]
public async Task<IActionResult> RedirectToSlack(string id)
[HttpGet("redirect/{id:guid}")]
public async Task<IActionResult> RedirectToSlack(Guid id)
{
var orgIdGuid = new Guid(id);
if (!await currentContext.OrganizationOwner(orgIdGuid))
if (!await currentContext.OrganizationOwner(id))
{
throw new NotFoundException();
}
@ -35,11 +36,10 @@ public class SlackOAuthController(
return Redirect(redirectUrl);
}
[HttpGet("callback/{id}", Name = nameof(OAuthCallback))]
public async Task<IActionResult> OAuthCallback(string id, [FromQuery] string code)
[HttpGet("callback/{id:guid}", Name = nameof(OAuthCallback))]
public async Task<IActionResult> OAuthCallback(Guid id, [FromQuery] string code)
{
var orgIdGuid = new Guid(id);
if (!await currentContext.OrganizationOwner(orgIdGuid))
if (!await currentContext.OrganizationOwner(id))
{
throw new NotFoundException();
}
@ -49,7 +49,7 @@ public class SlackOAuthController(
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);
if (string.IsNullOrEmpty(token))
@ -57,10 +57,12 @@ public class SlackOAuthController(
throw new BadRequestException("Invalid response from Slack.");
}
await integrationConfigurationRepository.CreateOrganizationIntegrationAsync(
orgIdGuid,
IntegrationType.Slack,
new SlackIntegration(token));
return Ok("Slack OAuth successful. Your bot is now installed.");
var integration = await integrationRepository.CreateAsync(new OrganizationIntegration
{
OrganizationId = id,
Type = IntegrationType.Slack,
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.Auth.Models.Data;
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tools.ImportFeatures;
using Bit.Core.Tools.ReportFeatures;
@ -223,14 +222,10 @@ public class Startup
{
services.AddHttpClient(SlackService.HttpClientName);
services.AddSingleton<ISlackService, SlackService>();
services.AddSingleton<IOrganizationIntegrationConfigurationRepository,
LocalOrganizationIntegrationConfigurationRepository>();
}
else
{
services.AddSingleton<ISlackService, NoopSlackService>();
services.AddSingleton<IOrganizationIntegrationConfigurationRepository,
LocalOrganizationIntegrationConfigurationRepository>();
}
}

View File

@ -1,17 +1,3 @@
namespace Bit.Core.Models.Data.Integrations;
public class SlackConfiguration
{
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;
}
public record SlackConfiguration(string channelId, string token);

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ using Bit.Core.AdminConsole.Services.Implementations;
using Bit.Core.AdminConsole.Services.NoopImplementations;
using Bit.Core.Context;
using Bit.Core.IdentityServer;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
@ -119,8 +118,6 @@ public class Startup
globalSettings,
globalSettings.EventLogging.RabbitMq.EventRepositoryQueueName));
services.AddSingleton<IOrganizationIntegrationConfigurationRepository, LocalOrganizationIntegrationConfigurationRepository>();
if (CoreHelpers.SettingHasValue(globalSettings.Slack.ClientId) &&
CoreHelpers.SettingHasValue(globalSettings.Slack.ClientSecret) &&
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<IOrganizationApiKeyRepository, OrganizationApiKeyRepository>();
services.AddSingleton<IOrganizationConnectionRepository, OrganizationConnectionRepository>();
services.AddSingleton<IOrganizationIntegrationConfigurationRepository, OrganizationIntegrationConfigurationRepository>();
services.AddSingleton<IOrganizationIntegrationRepository, OrganizationIntegrationRepository>();
services.AddSingleton<IOrganizationRepository, OrganizationRepository>();
services.AddSingleton<IOrganizationSponsorshipRepository, OrganizationSponsorshipRepository>();
services.AddSingleton<IOrganizationUserRepository, OrganizationUserRepository>();