mirror of
https://github.com/bitwarden/server.git
synced 2025-04-06 21:48:12 -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 MySql { get; set; } = new SqlSettings();
|
||||
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 MailSettings Mail { get; set; } = new MailSettings();
|
||||
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 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 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 WebhookQueueName { get; set; } = "events-webhook-queue";
|
||||
public virtual string SlackQueueName { get; set; } = "events-slack-queue";
|
||||
|
||||
public string HostName
|
||||
{
|
||||
|
@ -117,6 +117,21 @@ public class Startup
|
||||
globalSettings,
|
||||
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))
|
||||
{
|
||||
services.AddSingleton<WebhookEventHandler>();
|
||||
|
Loading…
x
Reference in New Issue
Block a user