mirror of
https://github.com/bitwarden/server.git
synced 2026-04-21 01:06:34 -05:00
Merge branch 'renovate/linq2db-6.x' of https://github.com/bitwarden/server into renovate/linq2db-6.x
This commit is contained in:
@@ -31,18 +31,39 @@ public class OrganizationIntegrationController(
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new organization integration.
|
||||
/// Validates that only one integration of each type can exist per organization.
|
||||
/// </summary>
|
||||
/// <param name="organizationId"></param>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotFoundException">Not enough permissions to access the organization.</exception>
|
||||
/// <exception cref="ConflictResult">When an integration of the same type already exists for the organization.</exception>
|
||||
[HttpPost("")]
|
||||
public async Task<OrganizationIntegrationResponseModel> CreateAsync(Guid organizationId, [FromBody] OrganizationIntegrationRequestModel model)
|
||||
public async Task<ActionResult<OrganizationIntegrationResponseModel>> CreateAsync(Guid organizationId, [FromBody] OrganizationIntegrationRequestModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (!await HasPermission(organizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var integration = model.ToOrganizationIntegration(organizationId);
|
||||
|
||||
var canCreate = await createCommand.CanCreateAsync(integration);
|
||||
if (!canCreate)
|
||||
{
|
||||
return Conflict();
|
||||
}
|
||||
|
||||
var created = await createCommand.CreateAsync(integration);
|
||||
|
||||
return new OrganizationIntegrationResponseModel(created);
|
||||
return Ok(new OrganizationIntegrationResponseModel(created));
|
||||
}
|
||||
|
||||
[HttpPut("{integrationId:guid}")]
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<PackageReference Include="Microsoft.Bot.Builder" Version="4.23.0" />
|
||||
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.23.0" />
|
||||
<PackageReference Include="Microsoft.Bot.Connector" Version="4.23.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Cosmos" Version="1.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||
|
||||
@@ -17,11 +17,21 @@ public class CreateOrganizationIntegrationCommand(
|
||||
IFusionCache cache)
|
||||
: ICreateOrganizationIntegrationCommand
|
||||
{
|
||||
public async Task<OrganizationIntegration> CreateAsync(OrganizationIntegration integration)
|
||||
public async Task<bool> CanCreateAsync(OrganizationIntegration integration)
|
||||
{
|
||||
var existingIntegrations = await integrationRepository
|
||||
.GetManyByOrganizationAsync(integration.OrganizationId);
|
||||
if (existingIntegrations.Any(i => i.Type == integration.Type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<OrganizationIntegration> CreateAsync(OrganizationIntegration integration)
|
||||
{
|
||||
if (await CanCreateAsync(integration) == false)
|
||||
{
|
||||
throw new BadRequestException("An integration of this type already exists for this organization.");
|
||||
}
|
||||
|
||||
@@ -15,4 +15,12 @@ public interface ICreateOrganizationIntegrationCommand
|
||||
/// <exception cref="Exceptions.BadRequestException">Thrown when an integration
|
||||
/// of the same type already exists for the organization.</exception>
|
||||
Task<OrganizationIntegration> CreateAsync(OrganizationIntegration integration);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a new organization integration can be created based on existing integrations.
|
||||
/// Enforces a validation to ensure that only one integration of each type can exist per organization.
|
||||
/// </summary>
|
||||
/// <param name="integration"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CanCreateAsync(OrganizationIntegration integration);
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ public class EventService : IEventService
|
||||
});
|
||||
}
|
||||
|
||||
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync();
|
||||
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, userId);
|
||||
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync(providers.Select(provider => provider.Id));
|
||||
var providerEvents = providers.Where(o => CanUseProviderEvents(providerAbilities, o.Id))
|
||||
.Select(p => new EventMessage(_currentContext)
|
||||
{
|
||||
@@ -382,9 +382,11 @@ public class EventService : IEventService
|
||||
|
||||
public async Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events)
|
||||
{
|
||||
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync();
|
||||
var materializedEvents = events.ToList();
|
||||
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync(
|
||||
materializedEvents.Select(materializedEvent => materializedEvent.Item1.ProviderId));
|
||||
var eventMessages = new List<IEvent>();
|
||||
foreach (var (providerUser, type, date) in events)
|
||||
foreach (var (providerUser, type, date) in materializedEvents)
|
||||
{
|
||||
if (!CanUseProviderEvents(providerAbilities, providerUser.ProviderId))
|
||||
{
|
||||
@@ -412,9 +414,11 @@ public class EventService : IEventService
|
||||
|
||||
public async Task LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events)
|
||||
{
|
||||
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync();
|
||||
var materializedEvents = events.ToList();
|
||||
var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync(
|
||||
materializedEvents.Select(materializedEvent => materializedEvent.Item1.ProviderId));
|
||||
var eventMessages = new List<IEvent>();
|
||||
foreach (var (providerOrganization, type, date) in events)
|
||||
foreach (var (providerOrganization, type, date) in materializedEvents)
|
||||
{
|
||||
if (!CanUseProviderEvents(providerAbilities, providerOrganization.ProviderId))
|
||||
{
|
||||
|
||||
@@ -88,6 +88,9 @@ public class OrganizationIntegrationControllerTests
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<ICreateOrganizationIntegrationCommand>()
|
||||
.CanCreateAsync(Arg.Any<OrganizationIntegration>())
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<ICreateOrganizationIntegrationCommand>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegration>())
|
||||
.Returns(integration);
|
||||
@@ -98,7 +101,31 @@ public class OrganizationIntegrationControllerTests
|
||||
.CreateAsync(Arg.Is<OrganizationIntegration>(i =>
|
||||
i.OrganizationId == organizationId &&
|
||||
i.Type == IntegrationType.Webhook));
|
||||
Assert.IsType<OrganizationIntegrationResponseModel>(response);
|
||||
Assert.IsType<ActionResult<OrganizationIntegrationResponseModel>>(response);
|
||||
Assert.IsType<OkObjectResult>(response.Result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_TheTypeAlreadyExists_ThrowsConflict(
|
||||
SutProvider<OrganizationIntegrationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<ICreateOrganizationIntegrationCommand>()
|
||||
.CanCreateAsync(Arg.Any<OrganizationIntegration>())
|
||||
.Returns(false);
|
||||
sutProvider.GetDependency<ICreateOrganizationIntegrationCommand>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegration>())
|
||||
.Returns(integration);
|
||||
|
||||
var response = await sutProvider.Sut.CreateAsync(organizationId, _webhookRequestModel);
|
||||
|
||||
Assert.IsType<ActionResult<OrganizationIntegrationResponseModel>>(response);
|
||||
Assert.IsType<ConflictResult>(response.Result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
||||
@@ -214,7 +214,7 @@ public class EventServiceTests
|
||||
{
|
||||
{providerUser.ProviderId, new ProviderAbility() { UseEvents = true, Enabled = true } }
|
||||
};
|
||||
sutProvider.GetDependency<IApplicationCacheService>().GetProviderAbilitiesAsync().Returns(providerAbilities);
|
||||
sutProvider.GetDependency<IApplicationCacheService>().GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>()).Returns(providerAbilities);
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
|
||||
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
|
||||
@@ -347,7 +347,7 @@ public class EventServiceTests
|
||||
{
|
||||
{ provider.Id, new ProviderAbility() { UseEvents = true, Enabled = true } }
|
||||
};
|
||||
sutProvider.GetDependency<IApplicationCacheService>().GetProviderAbilitiesAsync().Returns(providerAbilities);
|
||||
sutProvider.GetDependency<IApplicationCacheService>().GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>()).Returns(providerAbilities);
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
|
||||
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
|
||||
@@ -386,7 +386,7 @@ public class EventServiceTests
|
||||
.GetOrganizationAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(orgAbilities);
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, ProviderAbility>());
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
@@ -421,7 +421,7 @@ public class EventServiceTests
|
||||
.GetOrganizationAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(orgAbilities);
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, ProviderAbility>());
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByUserAsync(userId)
|
||||
@@ -441,4 +441,199 @@ public class EventServiceTests
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.CreateManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task LogProviderUsersEventAsync_LogsRequiredInfo(Provider provider, ICollection<ProviderUser> providerUsers,
|
||||
EventType eventType, DateTime date, Guid actingUserId, string ipAddress, DeviceType deviceType,
|
||||
SutProvider<EventService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
foreach (var providerUser in providerUsers)
|
||||
{
|
||||
providerUser.ProviderId = provider.Id;
|
||||
}
|
||||
|
||||
var providerAbilities = new Dictionary<Guid, ProviderAbility>()
|
||||
{
|
||||
{ provider.Id, new ProviderAbility() { UseEvents = true, Enabled = true } }
|
||||
};
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(providerAbilities);
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
|
||||
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.LogProviderUsersEventAsync(providerUsers.Select(providerUser => (providerUser, eventType, (DateTime?)date)));
|
||||
|
||||
// Assert
|
||||
var expected = providerUsers.Select(pu => new EventMessage()
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
DeviceType = deviceType,
|
||||
ProviderId = provider.Id,
|
||||
UserId = pu.UserId,
|
||||
ProviderUserId = pu.Id,
|
||||
Type = eventType,
|
||||
ActingUserId = actingUserId,
|
||||
Date = date
|
||||
}).ToList();
|
||||
|
||||
await sutProvider.GetDependency<IEventWriteService>().Received(1)
|
||||
.CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task LogProviderUsersEventAsync_WhenEventsDisabled_DoesNotLog(Provider provider,
|
||||
ICollection<ProviderUser> providerUsers, EventType eventType, DateTime date,
|
||||
SutProvider<EventService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
foreach (var providerUser in providerUsers)
|
||||
{
|
||||
providerUser.ProviderId = provider.Id;
|
||||
}
|
||||
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, ProviderAbility>
|
||||
{
|
||||
{ provider.Id, new ProviderAbility() { UseEvents = false, Enabled = true } }
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.LogProviderUsersEventAsync(providerUsers.Select(providerUser => (providerUser, eventType, (DateTime?)date)));
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IEventWriteService>().Received(1)
|
||||
.CreateManyAsync(Arg.Is<IEnumerable<IEvent>>(events => !events.Any()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task LogProviderUsersEventAsync_QueriesOnlyRelevantProviderIds(
|
||||
ICollection<ProviderUser> providerUsers, EventType eventType, DateTime date,
|
||||
SutProvider<EventService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, ProviderAbility>());
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.LogProviderUsersEventAsync(providerUsers.Select(pu => (pu, eventType, (DateTime?)date)));
|
||||
|
||||
// Assert
|
||||
var expectedIds = providerUsers.Select(pu => pu.ProviderId).Distinct();
|
||||
await sutProvider.GetDependency<IApplicationCacheService>().Received(1)
|
||||
.GetProviderAbilitiesAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.OrderBy(x => x).SequenceEqual(expectedIds.OrderBy(x => x))));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task LogProviderOrganizationEventsAsync_WhenEventsDisabled_DoesNotLog(Provider provider,
|
||||
ICollection<ProviderOrganization> providerOrganizations, EventType eventType, DateTime date,
|
||||
SutProvider<EventService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
foreach (var providerOrganization in providerOrganizations)
|
||||
{
|
||||
providerOrganization.ProviderId = provider.Id;
|
||||
}
|
||||
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, ProviderAbility>
|
||||
{
|
||||
{ provider.Id, new ProviderAbility() { UseEvents = false, Enabled = true } }
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.LogProviderOrganizationEventsAsync(
|
||||
providerOrganizations.Select(po => (po, eventType, (DateTime?)date)));
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IEventWriteService>().Received(1)
|
||||
.CreateManyAsync(Arg.Is<IEnumerable<IEvent>>(events => !events.Any()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task LogProviderOrganizationEventsAsync_QueriesOnlyRelevantProviderIds(
|
||||
ICollection<ProviderOrganization> providerOrganizations, EventType eventType, DateTime date,
|
||||
SutProvider<EventService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, ProviderAbility>());
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.LogProviderOrganizationEventsAsync(
|
||||
providerOrganizations.Select(providerOrganization => (providerOrganization, eventType, (DateTime?)date)));
|
||||
|
||||
// Assert
|
||||
var expectedIds = providerOrganizations.Select(po => po.ProviderId).Distinct();
|
||||
await sutProvider.GetDependency<IApplicationCacheService>().Received(1)
|
||||
.GetProviderAbilitiesAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.OrderBy(x => x).SequenceEqual(expectedIds.OrderBy(x => x))));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task LogUserEventAsync_WithProviderMembership_LogsProviderEvent(
|
||||
Guid userId, EventType eventType, CurrentContextProvider provider,
|
||||
SutProvider<EventService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var providerAbilities = new Dictionary<Guid, ProviderAbility>
|
||||
{
|
||||
{ provider.Id, new ProviderAbility() { UseEvents = true, Enabled = true } }
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetOrganizationAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, OrganizationAbility>());
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(providerAbilities);
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationMembershipAsync(Arg.Any<IOrganizationUserRepository>(), userId)
|
||||
.Returns(new List<CurrentContextOrganization>());
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ProviderMembershipAsync(Arg.Any<IProviderUserRepository>(), userId)
|
||||
.Returns(new List<CurrentContextProvider> { provider });
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.LogUserEventAsync(userId, eventType);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IEventWriteService>().Received(1)
|
||||
.CreateManyAsync(Arg.Is<IEnumerable<IEvent>>(events =>
|
||||
events.Any(e => e.ProviderId == provider.Id && e.UserId == userId && e.Type == eventType)));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task LogUserEventAsync_QueriesOnlyMemberProviderIds(
|
||||
Guid userId, EventType eventType, ICollection<CurrentContextProvider> providers,
|
||||
SutProvider<EventService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetOrganizationAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, OrganizationAbility>());
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync(Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(new Dictionary<Guid, ProviderAbility>());
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationMembershipAsync(Arg.Any<IOrganizationUserRepository>(), userId)
|
||||
.Returns(new List<CurrentContextOrganization>());
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ProviderMembershipAsync(Arg.Any<IProviderUserRepository>(), userId)
|
||||
.Returns(providers.ToList());
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.LogUserEventAsync(userId, eventType);
|
||||
|
||||
// Assert
|
||||
var expectedIds = providers.Select(provider => provider.Id);
|
||||
await sutProvider.GetDependency<IApplicationCacheService>().Received(1)
|
||||
.GetProviderAbilitiesAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.OrderBy(x => x).SequenceEqual(expectedIds.OrderBy(x => x))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dbup-sqlserver" Version="6.0.0" />
|
||||
<PackageReference Include="dbup-sqlserver" Version="7.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user