1
0
mirror of https://github.com/bitwarden/server synced 2025-12-29 06:33:43 +00:00

Add Microsoft Teams integration (#6410)

* Add Microsoft Teams integration

* Fix method naming error

* Expand and clean up unit test coverage

* Update with PR feedback

* Add documentation, add In Progress logic/tests for Teams

* Fixed lowercase Slack

* Added docs; Updated PR suggestions;

* Fix broken tests
This commit is contained in:
Brant DeBow
2025-10-10 10:39:31 -04:00
committed by GitHub
parent 3272586e31
commit a565fd9ee4
41 changed files with 1839 additions and 99 deletions

View File

@@ -0,0 +1,12 @@
using Bit.Core.Models.Teams;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record TeamsIntegration(
string TenantId,
IReadOnlyList<TeamInfo> Teams,
string? ChannelId = null,
Uri? ServiceUrl = null)
{
public bool IsCompleted => !string.IsNullOrEmpty(ChannelId) && ServiceUrl is not null;
}

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record TeamsIntegrationConfigurationDetails(string ChannelId, Uri ServiceUrl);

View File

@@ -0,0 +1,38 @@
using Bit.Core.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public class TeamsListenerConfiguration(GlobalSettings globalSettings) :
ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration
{
public IntegrationType IntegrationType
{
get => IntegrationType.Teams;
}
public string EventQueueName
{
get => _globalSettings.EventLogging.RabbitMq.TeamsEventsQueueName;
}
public string IntegrationQueueName
{
get => _globalSettings.EventLogging.RabbitMq.TeamsIntegrationQueueName;
}
public string IntegrationRetryQueueName
{
get => _globalSettings.EventLogging.RabbitMq.TeamsIntegrationRetryQueueName;
}
public string EventSubscriptionName
{
get => _globalSettings.EventLogging.AzureServiceBus.TeamsEventSubscriptionName;
}
public string IntegrationSubscriptionName
{
get => _globalSettings.EventLogging.AzureServiceBus.TeamsIntegrationSubscriptionName;
}
}

View File

@@ -1,6 +1,4 @@
#nullable enable
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace Bit.Core.Models.Slack;

View File

@@ -0,0 +1,41 @@
using System.Text.Json.Serialization;
namespace Bit.Core.Models.Teams;
/// <summary>Represents the response returned by the Microsoft OAuth 2.0 token endpoint.
/// See <see href="https://learn.microsoft.com/graph/auth-v2-user">Microsoft identity platform and OAuth 2.0
/// authorization code flow</see>.</summary>
public class TeamsOAuthResponse
{
/// <summary>The access token issued by Microsoft, used to call the Microsoft Graph API.</summary>
[JsonPropertyName("access_token")]
public string AccessToken { get; set; } = string.Empty;
}
/// <summary>Represents the response from the <c>/me/joinedTeams</c> Microsoft Graph API call.
/// See <see href="https://learn.microsoft.com/graph/api/user-list-joinedteams">List joined teams -
/// Microsoft Graph v1.0</see>.</summary>
public class JoinedTeamsResponse
{
/// <summary>The collection of teams that the user has joined.</summary>
[JsonPropertyName("value")]
public List<TeamInfo> Value { get; set; } = [];
}
/// <summary>Represents a Microsoft Teams team returned by the Graph API.
/// See <see href="https://learn.microsoft.com/graph/api/resources/team">Team resource type -
/// Microsoft Graph v1.0</see>.</summary>
public class TeamInfo
{
/// <summary>The unique identifier of the team.</summary>
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
/// <summary>The name of the team.</summary>
[JsonPropertyName("displayName")]
public string DisplayName { get; set; } = string.Empty;
/// <summary>The ID of the Microsoft Entra tenant for this team.</summary>
[JsonPropertyName("tenantId")]
public string TenantId { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,28 @@
using Microsoft.Bot.Connector.Authentication;
namespace Bit.Core.AdminConsole.Models.Teams;
public class TeamsBotCredentialProvider(string clientId, string clientSecret) : ICredentialProvider
{
private const string _microsoftBotFrameworkIssuer = AuthenticationConstants.ToBotFromChannelTokenIssuer;
public Task<bool> IsValidAppIdAsync(string appId)
{
return Task.FromResult(appId == clientId);
}
public Task<string?> GetAppPasswordAsync(string appId)
{
return Task.FromResult(appId == clientId ? clientSecret : null);
}
public Task<bool> IsAuthenticationDisabledAsync()
{
return Task.FromResult(false);
}
public Task<bool> ValidateIssuerAsync(string issuer)
{
return Task.FromResult(issuer == _microsoftBotFrameworkIssuer);
}
}