mirror of
https://github.com/bitwarden/server.git
synced 2025-04-07 05:58:13 -05:00
[PM-17562] Slack Event Investigation
This commit is contained in:
parent
b66f255c5c
commit
17c0d66e37
67
src/Api/AdminConsole/Controllers/SlackOAuthController.cs
Normal file
67
src/Api/AdminConsole/Controllers/SlackOAuthController.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Bit.Api.AdminConsole.Controllers;
|
||||||
|
|
||||||
|
[Route("slack/oauth")]
|
||||||
|
public class SlackOAuthController(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
GlobalSettings globalSettings)
|
||||||
|
: Controller
|
||||||
|
{
|
||||||
|
private readonly string _clientId = globalSettings.Slack.ClientId;
|
||||||
|
private readonly string _clientSecret = globalSettings.Slack.ClientSecret;
|
||||||
|
private readonly string _scopes = globalSettings.Slack.Scopes;
|
||||||
|
private readonly string _redirectUrl = globalSettings.Slack.RedirectUrl;
|
||||||
|
private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
|
||||||
|
|
||||||
|
public const string HttpClientName = "SlackOAuthContollerHttpClient";
|
||||||
|
|
||||||
|
[HttpGet("redirect")]
|
||||||
|
public IActionResult RedirectToSlack()
|
||||||
|
{
|
||||||
|
string slackOAuthUrl = $"https://slack.com/oauth/v2/authorize?client_id={_clientId}&scope={_scopes}&redirect_uri={_redirectUrl}";
|
||||||
|
|
||||||
|
return Redirect(slackOAuthUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("callback")]
|
||||||
|
public async Task<IActionResult> OAuthCallback([FromQuery] string code)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(code))
|
||||||
|
{
|
||||||
|
return BadRequest("Missing code from Slack.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResponse = await _httpClient.PostAsync("https://slack.com/api/oauth.v2.access",
|
||||||
|
new FormUrlEncodedContent(new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("client_id", _clientId),
|
||||||
|
new KeyValuePair<string, string>("client_secret", _clientSecret),
|
||||||
|
new KeyValuePair<string, string>("code", code),
|
||||||
|
new KeyValuePair<string, string>("redirect_uri", _redirectUrl)
|
||||||
|
}));
|
||||||
|
|
||||||
|
var responseBody = await tokenResponse.Content.ReadAsStringAsync();
|
||||||
|
var jsonDoc = JsonDocument.Parse(responseBody);
|
||||||
|
var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
|
if (!root.GetProperty("ok").GetBoolean())
|
||||||
|
{
|
||||||
|
return BadRequest($"OAuth failed: {root.GetProperty("error").GetString()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
string botToken = root.GetProperty("access_token").GetString();
|
||||||
|
string teamId = root.GetProperty("team").GetProperty("id").GetString();
|
||||||
|
|
||||||
|
SaveTokenToDatabase(teamId, botToken);
|
||||||
|
|
||||||
|
return Ok("Slack OAuth successful. Your bot is now installed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveTokenToDatabase(string teamId, string botToken)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Stored bot token for team {teamId}: {botToken}");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public class SlackEventHandler(
|
||||||
|
GlobalSettings globalSettings,
|
||||||
|
SlackMessageSender slackMessageSender)
|
||||||
|
: IEventMessageHandler
|
||||||
|
{
|
||||||
|
private readonly string _token = globalSettings.EventLogging.SlackToken;
|
||||||
|
private readonly string _email = globalSettings.EventLogging.SlackUserEmail;
|
||||||
|
|
||||||
|
public async Task HandleEventAsync(EventMessage eventMessage)
|
||||||
|
{
|
||||||
|
await slackMessageSender.SendDirectMessageByEmailAsync(
|
||||||
|
_token,
|
||||||
|
JsonSerializer.Serialize(eventMessage),
|
||||||
|
_email
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleManyEventsAsync(IEnumerable<EventMessage> eventMessages)
|
||||||
|
{
|
||||||
|
await slackMessageSender.SendDirectMessageByEmailAsync(
|
||||||
|
_token,
|
||||||
|
JsonSerializer.Serialize(eventMessages),
|
||||||
|
_email
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public class SlackMessageSender(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILogger<SlackMessageSender> logger)
|
||||||
|
{
|
||||||
|
private HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
|
||||||
|
|
||||||
|
public const string HttpClientName = "SlackMessageSenderHttpClient";
|
||||||
|
|
||||||
|
public async Task SendDirectMessageByEmailAsync(string token, string message, string email)
|
||||||
|
{
|
||||||
|
var userId = await UserIdByEmail(token, email);
|
||||||
|
|
||||||
|
if (userId is not null)
|
||||||
|
{
|
||||||
|
await SendSlackDirectMessageByUserId(token, message, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> UserIdByEmail(string token, string email)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, $"https://slack.com/api/users.lookupByEmail?email={email}");
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
var content = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var root = content.RootElement;
|
||||||
|
|
||||||
|
if (root.GetProperty("ok").GetBoolean())
|
||||||
|
{
|
||||||
|
return root.GetProperty("user").GetProperty("id").GetString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogError("Error retrieving slack userId: " + root.GetProperty("error").GetString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendSlackDirectMessageByUserId(string token, string message, string userId)
|
||||||
|
{
|
||||||
|
var channelId = await OpenDmChannel(token, userId);
|
||||||
|
|
||||||
|
var payload = JsonContent.Create(new { channel = channelId, text = message });
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/chat.postMessage");
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
request.Content = payload;
|
||||||
|
|
||||||
|
await _httpClient.SendAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> OpenDmChannel(string token, string userId)
|
||||||
|
{
|
||||||
|
var payload = JsonContent.Create(new { users = userId });
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/conversations.open");
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
request.Content = payload;
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
var content = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var root = content.RootElement;
|
||||||
|
|
||||||
|
if (root.GetProperty("ok").GetBoolean())
|
||||||
|
{
|
||||||
|
return content.RootElement.GetProperty("channel").GetProperty("id").GetString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogError("Error opening DM channel: " + root.GetProperty("error").GetString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,7 @@ public class GlobalSettings : IGlobalSettings
|
|||||||
public virtual SqlSettings PostgreSql { get; set; } = new SqlSettings();
|
public virtual SqlSettings PostgreSql { get; set; } = new SqlSettings();
|
||||||
public virtual SqlSettings MySql { get; set; } = new SqlSettings();
|
public virtual SqlSettings MySql { get; set; } = new SqlSettings();
|
||||||
public virtual SqlSettings Sqlite { get; set; } = new SqlSettings() { ConnectionString = "Data Source=:memory:" };
|
public virtual SqlSettings Sqlite { get; set; } = new SqlSettings() { ConnectionString = "Data Source=:memory:" };
|
||||||
|
public virtual SlackSettings Slack { get; set; } = new SlackSettings();
|
||||||
public virtual EventLoggingSettings EventLogging { get; set; } = new EventLoggingSettings();
|
public virtual EventLoggingSettings EventLogging { get; set; } = new EventLoggingSettings();
|
||||||
public virtual MailSettings Mail { get; set; } = new MailSettings();
|
public virtual MailSettings Mail { get; set; } = new MailSettings();
|
||||||
public virtual IConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings();
|
public virtual IConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings();
|
||||||
@ -269,9 +270,19 @@ public class GlobalSettings : IGlobalSettings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SlackSettings
|
||||||
|
{
|
||||||
|
public virtual string ClientId { get; set; }
|
||||||
|
public virtual string ClientSecret { get; set; }
|
||||||
|
public virtual string RedirectUrl { get; set; }
|
||||||
|
public virtual string Scopes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class EventLoggingSettings
|
public class EventLoggingSettings
|
||||||
{
|
{
|
||||||
public AzureServiceBusSettings AzureServiceBus { get; set; } = new AzureServiceBusSettings();
|
public AzureServiceBusSettings AzureServiceBus { get; set; } = new AzureServiceBusSettings();
|
||||||
|
public virtual string SlackToken { get; set; }
|
||||||
|
public virtual string SlackUserEmail { get; set; }
|
||||||
public virtual string WebhookUrl { get; set; }
|
public virtual string WebhookUrl { get; set; }
|
||||||
public RabbitMqSettings RabbitMq { get; set; } = new RabbitMqSettings();
|
public RabbitMqSettings RabbitMq { get; set; } = new RabbitMqSettings();
|
||||||
|
|
||||||
@ -305,6 +316,7 @@ public class GlobalSettings : IGlobalSettings
|
|||||||
|
|
||||||
public virtual string EventRepositoryQueueName { get; set; } = "events-write-queue";
|
public virtual string EventRepositoryQueueName { get; set; } = "events-write-queue";
|
||||||
public virtual string WebhookQueueName { get; set; } = "events-webhook-queue";
|
public virtual string WebhookQueueName { get; set; } = "events-webhook-queue";
|
||||||
|
public virtual string SlackQueueName { get; set; } = "events-slack-queue";
|
||||||
|
|
||||||
public string HostName
|
public string HostName
|
||||||
{
|
{
|
||||||
|
@ -117,6 +117,21 @@ public class Startup
|
|||||||
globalSettings,
|
globalSettings,
|
||||||
globalSettings.EventLogging.RabbitMq.EventRepositoryQueueName));
|
globalSettings.EventLogging.RabbitMq.EventRepositoryQueueName));
|
||||||
|
|
||||||
|
if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.SlackToken) &&
|
||||||
|
CoreHelpers.SettingHasValue(globalSettings.EventLogging.SlackUserEmail))
|
||||||
|
{
|
||||||
|
services.AddHttpClient(SlackMessageSender.HttpClientName);
|
||||||
|
services.AddSingleton<SlackMessageSender>();
|
||||||
|
services.AddSingleton<SlackEventHandler>();
|
||||||
|
|
||||||
|
services.AddSingleton<IHostedService>(provider =>
|
||||||
|
new RabbitMqEventListenerService(
|
||||||
|
provider.GetRequiredService<SlackEventHandler>(),
|
||||||
|
provider.GetRequiredService<ILogger<RabbitMqEventListenerService>>(),
|
||||||
|
globalSettings,
|
||||||
|
globalSettings.EventLogging.RabbitMq.SlackQueueName));
|
||||||
|
}
|
||||||
|
|
||||||
if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.WebhookUrl))
|
if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.WebhookUrl))
|
||||||
{
|
{
|
||||||
services.AddSingleton<WebhookEventHandler>();
|
services.AddSingleton<WebhookEventHandler>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user