mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01:00
[EC-449] Event log user for SCIM events (#2306)
* [EC-449] Added new Enum EventSystemUser * [EC-449] Added SystemUser property to Event model * [EC-449] Added SQL migration to add new column 'SystemUserType' to Event * [EC-449] EF migrations * [EC-449] Added EventSystemUser to EventResponseModel * [EC-449] Saving EventSystemUser.SCIM on SCIM controller actions * [EC-449] Updated Event_Create stored procedure on Sql project * [EC-449] Fixed SystemUser column name on Event table * [EC-507] SCIM CQRS Refactor - Groups/Put (#2269) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-531] Implemented CQRS for Groups Put and added unit tests * [EC-507] Created ScimServiceCollectionExtensions * [EC-507] Renamed AddScimCommands to AddScimGroupCommands * [EC-507] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-507] Removed unneeded dependencies from GroupsController * [EC-507] Update PutGroupCommand to return Group PutGroupCommand returns Group and GroupsController creates ScimGroupResponseModel response * [EC-507] Remove Queries/Commands folders from Scim and Scim.Tests * [EC-507] Remove unneeded check on empty provided memberIds * [EC-507] SCIM CQRS Refactor - Groups/GetList (#2272) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-508] Implemented CQRS for Groups GetList and added unit tests * [EC-507] Created ScimServiceCollectionExtensions and renamed GetGroupsListCommand to GetGroupsListQuery * [EC-507] Renamed AddScimCommands to AddScimGroupQueries * [EC-507] Removed unneeded dependencies from GroupsController * [EC-507] Remove 'Queries' folder from Scim and Scim.Test * [EC-507] Move ScimListResponseModel from GetGroupsListQuery to Scim.GroupsController * [EC-507] Remove asserts on IGroupRepository.GetManyByOrganizationIdAsync from unit tests * [EC-507] SCIM CQRS Refactor - Groups/Get (#2271) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-507] Implemented CQRS for Groups Get and added unit tests * [EC-507] Created ScimServiceCollectionExtensions and renamed GetGroupCommand to GetGroupQuery * [EC-507] Renamed AddScimCommands to AddScimGroupQueries * [EC-507] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-507] Removed unneeded dependencies from GroupsController * [EC-507] Sorted order of methods * [EC-507] Removed GetGroupQuery and moved logic to controller * [EC-507] Remove 'Queries' folder from Scim and Scim.Test * [EC-507] SCIM CQRS Refactor - Groups/Patch (#2268) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-532] Implemented CQRS for Groups Patch and added unit tests * [EC-507] Created ScimServiceCollectionExtensions * [EC-507] Renamed AddScimCommands to AddScimGroupCommands * [EC-507] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-507] Removed unneeded dependencies from GroupsController * [EC-507] Remove Queries/Commands folders from Scim and Scim.Tests * [EC-507] Assert group.Name after saving. Assert userIds saved. * [EC-508] SCIM CQRS Refactor - Users/Delete (#2261) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-539] Implemented CQRS for Users Delete and added unit tests * [EC-508] Created ScimServiceCollectionExtensions * [EC-508] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-508] Removed unneeded model from DeleteUserCommand. Removed unneeded dependencies from UsersController * [EC-508] Removed Bit.Scim.Models dependency from DeleteUserCommandTests * [EC-508] Deleted 'DeleteUserCommand' from SCIM; Created commands on Core 'DeleteOrganizationUserCommand', 'PushDeleteUserRegistrationOrganizationCommand' and 'OrganizationHasConfirmedOwnersExceptQuery' * [EC-508] Changed DeleteOrganizationUserCommand back to using IOrganizationService * [EC-508] Fixed DeleteOrganizationUserCommand unit tests * [EC-508] Remove unneeded obsolete comments. Update DeleteUserAsync Obsolete comment with ticket reference * [EC-508] Move DeleteOrganizationUserCommand to OrganizationFeatures folder * [EC-508] SCIM CQRS Refactor - Users/Post (#2264) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-536] Implemented CQRS for Users Post and added unit tests * [EC-508] Created ScimServiceCollectionExtensions * [EC-508] Renamed AddScimCommands to AddScimUserCommands * [EC-508] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-508] Catching NotFoundException on ExceptionHandlerFilter * [EC-508] Remove Queries/Commands folders from Scim and Scim.Tests * [EC-508] SCIM CQRS Refactor - Users/Patch (#2262) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-538] Implemented CQRS for Users Patch and added unit tests * [EC-508] Added ScimServiceCollectionExtensions * [EC-508] Removed HandleActiveOperationAsync method from UsersController * [EC-508] Renamed AddScimCommands to AddScimUserCommands * [EC-508] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-508] Removed unneeded dependencies from UsersController * [EC-508] Remove 'Query' folder from Scim and Scim.Test * [EC-507] SCIM CQRS Refactor - Groups/Post (#2270) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-530] Implemented CQRS for Groups Post and added unit tests * [EC-507] Created ScimServiceCollectionExtensions * [EC-507] Renamed AddScimCommands to AddScimGroupCommands * [EC-507] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-507] Removed unneeded dependencies from GroupsController * [EC-507] Remove Queries/Commands folders from Scim and Scim.Test * [EC-507] Remove unneeded skipIfEmpty argument. Updated unit test to check provided userIds * [EC-507] Remove UpdateGroupMembersAsync from GroupsController * [EC-508] SCIM CQRS Refactor - Users/GetList (#2265) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-535] Implemented CQRS for Users GetList and added unit tests * [EC-508] Created ScimServiceCollectionExtensions and renamed GetUsersListCommand to GetUsersListQuery * [EC-508] Renamed AddScimCommands to AddScimUserQueries * [EC-508] Removed unneeded IUserRepository and IOptions<ScimSettings> from UsersController * [EC-508] Sorted UsersController properties and dependencies * [EC-508] Remove 'Queries' folder from Scim and Scim.Test * [EC-508] Move ScimListResponseModel creation to Scim.UsersController * [EC-508] Move ScimUserResponseModel creation to Scim.UsersController Co-authored-by: Thomas Rittson <trittson@bitwarden.com> * [EC-507] SCIM CQRS Refactor - Groups/Delete (#2267) * [EC-390] Added Scim.Test unit tests project * [EC-390] Added ConflictException type. Updated BadRequestException to have parameterless constructor. Updated NotFoundException to have constructor with a message parameter * [EC-533] Implemented CQRS for Groups Delete and added unit tests * [EC-507] Created ScimServiceCollectionExtensions * [EC-507] Renamed AddScimCommands to AddScimGroupCommands * [EC-507] Created ExceptionHandlerFilterAttribute on SCIM project * [EC-507] Removed unneeded dependencies from GroupsController * [EC-507] Move DeleteGroupCommand to OrganizationFeatures/OrganizationUsers * [EC-507] Remove IGetUserQuery and move logic to UsersController. Remove unused references. * [EC-449] Add overloads for EventService and GroupService methods that accept EventSystemUser as an argument * [EC-507] Move IDeleteGroupCommand to Groups folder * [EC-449] Add method overloads in IOrganizationService without EventSystemUser * [EC-449] Add RevokeUserAsync overload without EventSystemUser * [EC-449] Reverted OrganizationUsersController to not pass EventSystemUser argument * [EC-449] Uncomment assertion in GroupServiceTests * [EC-449] Update method overloads to not have nullable EventSystemUser * [EC-449] Add unit tests around events that can store EventSystemUser * [EC-449] Deleted private method GroupService.GroupRepositoryDeleteAsync * [EC-449] Move Event log call to public DeleteUserAsync methods * [EC-449] Move call to EventService log to public OrganizationService.InviteUsersAsync methods * [EC-449] Move EventService call to public OrganizationService.DeleteUserAsync methods * [EC-449] Move EventService call to OrganizationService.RevokeUserAsync methods * [EC-449] Move EventService call to OrganizationService.RestoreUserAsync methods * [EC-449] Add missing comma in SQL script for new SystemUser column on the Event table * [EC-449] Remove Autofixture hack from OrganizationServiceTests * [EC-449] Remove invitingUser param when methods expect an EventSystemUser param * [EC-449] Move DeleteUserAsync validation to private method * [EC-449] Move revokingUserId from RevokeUserAsync private method * [EC-449] Move restoringUserId to RestoreUserAsync public method * [EC-449] Set up OrganizationServiceTest Restore and Revoke tests on a single method * [EC-449] SaveUsersSendInvitesAsync to return both OrganizationUsers and Events list * [EC-449] Undo unintended change on CipherRepository * [EC-449] Add SystemUser value to EventTableEntity Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
parent
2d5235b43d
commit
37ed4f43b2
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Scim.Groups.Interfaces;
|
using Bit.Scim.Groups.Interfaces;
|
||||||
@ -96,7 +97,7 @@ public class GroupsController : Controller
|
|||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public async Task<IActionResult> Delete(Guid organizationId, Guid id)
|
public async Task<IActionResult> Delete(Guid organizationId, Guid id)
|
||||||
{
|
{
|
||||||
await _deleteGroupCommand.DeleteGroupAsync(organizationId, id);
|
await _deleteGroupCommand.DeleteGroupAsync(organizationId, id, EventSystemUser.SCIM);
|
||||||
return new NoContentResult();
|
return new NoContentResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ public class UsersController : Controller
|
|||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public async Task<IActionResult> Delete(Guid organizationId, Guid id)
|
public async Task<IActionResult> Delete(Guid organizationId, Guid id)
|
||||||
{
|
{
|
||||||
await _deleteOrganizationUserCommand.DeleteUserAsync(organizationId, id, null);
|
await _deleteOrganizationUserCommand.DeleteUserAsync(organizationId, id, EventSystemUser.SCIM);
|
||||||
return new NoContentResult();
|
return new NoContentResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -48,7 +49,7 @@ public class PatchGroupCommand : IPatchGroupCommand
|
|||||||
else if (operation.Path?.ToLowerInvariant() == "displayname")
|
else if (operation.Path?.ToLowerInvariant() == "displayname")
|
||||||
{
|
{
|
||||||
group.Name = operation.Value.GetString();
|
group.Name = operation.Value.GetString();
|
||||||
await _groupService.SaveAsync(group);
|
await _groupService.SaveAsync(group, EventSystemUser.SCIM);
|
||||||
operationHandled = true;
|
operationHandled = true;
|
||||||
}
|
}
|
||||||
// Replace group name from value object
|
// Replace group name from value object
|
||||||
@ -56,7 +57,7 @@ public class PatchGroupCommand : IPatchGroupCommand
|
|||||||
operation.Value.TryGetProperty("displayName", out var displayNameProperty))
|
operation.Value.TryGetProperty("displayName", out var displayNameProperty))
|
||||||
{
|
{
|
||||||
group.Name = displayNameProperty.GetString();
|
group.Name = displayNameProperty.GetString();
|
||||||
await _groupService.SaveAsync(group);
|
await _groupService.SaveAsync(group, EventSystemUser.SCIM);
|
||||||
operationHandled = true;
|
operationHandled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +95,7 @@ public class PatchGroupCommand : IPatchGroupCommand
|
|||||||
var removeId = GetOperationPathId(operation.Path);
|
var removeId = GetOperationPathId(operation.Path);
|
||||||
if (removeId.HasValue)
|
if (removeId.HasValue)
|
||||||
{
|
{
|
||||||
await _groupService.DeleteUserAsync(group, removeId.Value);
|
await _groupService.DeleteUserAsync(group, removeId.Value, EventSystemUser.SCIM);
|
||||||
operationHandled = true;
|
operationHandled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -38,7 +39,7 @@ public class PostGroupCommand : IPostGroupCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
var group = model.ToGroup(organizationId);
|
var group = model.ToGroup(organizationId);
|
||||||
await _groupService.SaveAsync(group, null);
|
await _groupService.SaveAsync(group, EventSystemUser.SCIM, null);
|
||||||
await UpdateGroupMembersAsync(group, model);
|
await UpdateGroupMembersAsync(group, model);
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -33,7 +34,7 @@ public class PutGroupCommand : IPutGroupCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
group.Name = model.DisplayName;
|
group.Name = model.DisplayName;
|
||||||
await _groupService.SaveAsync(group);
|
await _groupService.SaveAsync(group, EventSystemUser.SCIM);
|
||||||
await UpdateGroupMembersAsync(group, model);
|
await UpdateGroupMembersAsync(group, model);
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
|
@ -74,12 +74,12 @@ public class PatchUserCommand : IPatchUserCommand
|
|||||||
{
|
{
|
||||||
if (active && orgUser.Status == OrganizationUserStatusType.Revoked)
|
if (active && orgUser.Status == OrganizationUserStatusType.Revoked)
|
||||||
{
|
{
|
||||||
await _organizationService.RestoreUserAsync(orgUser, null, _userService);
|
await _organizationService.RestoreUserAsync(orgUser, EventSystemUser.SCIM, _userService);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (!active && orgUser.Status != OrganizationUserStatusType.Revoked)
|
else if (!active && orgUser.Status != OrganizationUserStatusType.Revoked)
|
||||||
{
|
{
|
||||||
await _organizationService.RevokeUserAsync(orgUser, null);
|
await _organizationService.RevokeUserAsync(orgUser, EventSystemUser.SCIM);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -79,7 +79,7 @@ public class PostUserCommand : IPostUserCommand
|
|||||||
throw new ConflictException();
|
throw new ConflictException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var invitedOrgUser = await _organizationService.InviteUserAsync(organizationId, null, email,
|
var invitedOrgUser = await _organizationService.InviteUserAsync(organizationId, EventSystemUser.SCIM, email,
|
||||||
OrganizationUserType.User, false, externalId, new List<SelectionReadOnly>());
|
OrganizationUserType.User, false, externalId, new List<SelectionReadOnly>());
|
||||||
var orgUser = await _organizationUserRepository.GetDetailsByIdAsync(invitedOrgUser.Id);
|
var orgUser = await _organizationUserRepository.GetDetailsByIdAsync(invitedOrgUser.Id);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -67,7 +68,7 @@ public class PatchGroupCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel);
|
await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group);
|
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, EventSystemUser.SCIM);
|
||||||
Assert.Equal(displayName, group.Name);
|
Assert.Equal(displayName, group.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ public class PatchGroupCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel);
|
await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group);
|
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, EventSystemUser.SCIM);
|
||||||
Assert.Equal(displayName, group.Name);
|
Assert.Equal(displayName, group.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +183,7 @@ public class PatchGroupCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel);
|
await sutProvider.Sut.PatchGroupAsync(group.OrganizationId, group.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupService>().Received(1).DeleteUserAsync(group, userId);
|
await sutProvider.GetDependency<IGroupService>().Received(1).DeleteUserAsync(group, userId, EventSystemUser.SCIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -42,7 +43,7 @@ public class PostGroupCommandTests
|
|||||||
|
|
||||||
var group = await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel);
|
var group = await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, null);
|
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, EventSystemUser.SCIM, null);
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received(0).UpdateUsersAsync(Arg.Any<Guid>(), Arg.Any<IEnumerable<Guid>>());
|
await sutProvider.GetDependency<IGroupRepository>().Received(0).UpdateUsersAsync(Arg.Any<Guid>(), Arg.Any<IEnumerable<Guid>>());
|
||||||
|
|
||||||
AssertHelper.AssertPropertyEqual(expectedResult, group, "Id", "CreationDate", "RevisionDate");
|
AssertHelper.AssertPropertyEqual(expectedResult, group, "Id", "CreationDate", "RevisionDate");
|
||||||
@ -77,7 +78,7 @@ public class PostGroupCommandTests
|
|||||||
|
|
||||||
var group = await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel);
|
var group = await sutProvider.Sut.PostGroupAsync(organizationId, scimGroupRequestModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, null);
|
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, EventSystemUser.SCIM, null);
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received(1).UpdateUsersAsync(Arg.Any<Guid>(), Arg.Is<IEnumerable<Guid>>(arg => arg.All(id => membersUserIds.Contains(id))));
|
await sutProvider.GetDependency<IGroupRepository>().Received(1).UpdateUsersAsync(Arg.Any<Guid>(), Arg.Is<IEnumerable<Guid>>(arg => arg.All(id => membersUserIds.Contains(id))));
|
||||||
|
|
||||||
AssertHelper.AssertPropertyEqual(expectedResult, group, "Id", "CreationDate", "RevisionDate");
|
AssertHelper.AssertPropertyEqual(expectedResult, group, "Id", "CreationDate", "RevisionDate");
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -45,7 +46,7 @@ public class PutGroupCommandTests
|
|||||||
AssertHelper.AssertPropertyEqual(expectedResult, result, "CreationDate", "RevisionDate");
|
AssertHelper.AssertPropertyEqual(expectedResult, result, "CreationDate", "RevisionDate");
|
||||||
Assert.Equal(displayName, group.Name);
|
Assert.Equal(displayName, group.Name);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group);
|
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, EventSystemUser.SCIM);
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received(0).UpdateUsersAsync(group.Id, Arg.Any<IEnumerable<Guid>>());
|
await sutProvider.GetDependency<IGroupRepository>().Received(0).UpdateUsersAsync(group.Id, Arg.Any<IEnumerable<Guid>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ public class PutGroupCommandTests
|
|||||||
AssertHelper.AssertPropertyEqual(expectedResult, result, "CreationDate", "RevisionDate");
|
AssertHelper.AssertPropertyEqual(expectedResult, result, "CreationDate", "RevisionDate");
|
||||||
Assert.Equal(displayName, group.Name);
|
Assert.Equal(displayName, group.Name);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group);
|
await sutProvider.GetDependency<IGroupService>().Received(1).SaveAsync(group, EventSystemUser.SCIM);
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received(1).UpdateUsersAsync(group.Id, Arg.Is<IEnumerable<Guid>>(arg => arg.All(id => membersUserIds.Contains(id))));
|
await sutProvider.GetDependency<IGroupRepository>().Received(1).UpdateUsersAsync(group.Id, Arg.Is<IEnumerable<Guid>>(arg => arg.All(id => membersUserIds.Contains(id))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -42,7 +43,7 @@ public class PatchUserCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).RestoreUserAsync(organizationUser, null, Arg.Any<IUserService>());
|
await sutProvider.GetDependency<IOrganizationService>().Received(1).RestoreUserAsync(organizationUser, EventSystemUser.SCIM, Arg.Any<IUserService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -70,7 +71,7 @@ public class PatchUserCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).RestoreUserAsync(organizationUser, null, Arg.Any<IUserService>());
|
await sutProvider.GetDependency<IOrganizationService>().Received(1).RestoreUserAsync(organizationUser, EventSystemUser.SCIM, Arg.Any<IUserService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -99,7 +100,7 @@ public class PatchUserCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).RevokeUserAsync(organizationUser, null);
|
await sutProvider.GetDependency<IOrganizationService>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -127,7 +128,7 @@ public class PatchUserCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).RevokeUserAsync(organizationUser, null);
|
await sutProvider.GetDependency<IOrganizationService>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -146,8 +147,8 @@ public class PatchUserCommandTests
|
|||||||
|
|
||||||
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationService>().Received(0).RestoreUserAsync(organizationUser, null, Arg.Any<IUserService>());
|
await sutProvider.GetDependency<IOrganizationService>().Received(0).RestoreUserAsync(organizationUser, EventSystemUser.SCIM, Arg.Any<IUserService>());
|
||||||
await sutProvider.GetDependency<IOrganizationService>().Received(0).RevokeUserAsync(organizationUser, null);
|
await sutProvider.GetDependency<IOrganizationService>().Received(0).RevokeUserAsync(organizationUser, EventSystemUser.SCIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -34,12 +34,12 @@ public class PostUserCommandTests
|
|||||||
.Returns(organizationUsers);
|
.Returns(organizationUsers);
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationService>()
|
sutProvider.GetDependency<IOrganizationService>()
|
||||||
.InviteUserAsync(organizationId, null, scimUserRequestModel.PrimaryEmail.ToLowerInvariant(), OrganizationUserType.User, false, externalId, Arg.Any<List<SelectionReadOnly>>())
|
.InviteUserAsync(organizationId, EventSystemUser.SCIM, scimUserRequestModel.PrimaryEmail.ToLowerInvariant(), OrganizationUserType.User, false, externalId, Arg.Any<List<SelectionReadOnly>>())
|
||||||
.Returns(newUser);
|
.Returns(newUser);
|
||||||
|
|
||||||
var user = await sutProvider.Sut.PostUserAsync(organizationId, scimUserRequestModel);
|
var user = await sutProvider.Sut.PostUserAsync(organizationId, scimUserRequestModel);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).InviteUserAsync(organizationId, null, scimUserRequestModel.PrimaryEmail.ToLowerInvariant(),
|
await sutProvider.GetDependency<IOrganizationService>().Received(1).InviteUserAsync(organizationId, EventSystemUser.SCIM, scimUserRequestModel.PrimaryEmail.ToLowerInvariant(),
|
||||||
OrganizationUserType.User, false, scimUserRequestModel.ExternalId, Arg.Any<List<SelectionReadOnly>>());
|
OrganizationUserType.User, false, scimUserRequestModel.ExternalId, Arg.Any<List<SelectionReadOnly>>());
|
||||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetDetailsByIdAsync(newUser.Id);
|
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetDetailsByIdAsync(newUser.Id);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ public class EventResponseModel : ResponseModel
|
|||||||
DeviceType = ev.DeviceType;
|
DeviceType = ev.DeviceType;
|
||||||
IpAddress = ev.IpAddress;
|
IpAddress = ev.IpAddress;
|
||||||
InstallationId = ev.InstallationId;
|
InstallationId = ev.InstallationId;
|
||||||
|
SystemUser = ev.SystemUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventType Type { get; set; }
|
public EventType Type { get; set; }
|
||||||
@ -48,4 +49,5 @@ public class EventResponseModel : ResponseModel
|
|||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public DeviceType? DeviceType { get; set; }
|
public DeviceType? DeviceType { get; set; }
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
|
public EventSystemUser? SystemUser { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ public class Event : ITableObject<Guid>, IEvent
|
|||||||
DeviceType = e.DeviceType;
|
DeviceType = e.DeviceType;
|
||||||
IpAddress = e.IpAddress;
|
IpAddress = e.IpAddress;
|
||||||
ActingUserId = e.ActingUserId;
|
ActingUserId = e.ActingUserId;
|
||||||
|
SystemUser = e.SystemUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
@ -47,6 +48,7 @@ public class Event : ITableObject<Guid>, IEvent
|
|||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
public Guid? ActingUserId { get; set; }
|
public Guid? ActingUserId { get; set; }
|
||||||
|
public EventSystemUser? SystemUser { get; set; }
|
||||||
|
|
||||||
public void SetNewId()
|
public void SetNewId()
|
||||||
{
|
{
|
||||||
|
6
src/Core/Enums/EventSystemUser.cs
Normal file
6
src/Core/Enums/EventSystemUser.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.Core.Enums;
|
||||||
|
|
||||||
|
public enum EventSystemUser : byte
|
||||||
|
{
|
||||||
|
SCIM = 1
|
||||||
|
}
|
@ -31,4 +31,5 @@ public class EventMessage : IEvent
|
|||||||
public DeviceType? DeviceType { get; set; }
|
public DeviceType? DeviceType { get; set; }
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
public Guid? IdempotencyId { get; private set; } = Guid.NewGuid();
|
public Guid? IdempotencyId { get; private set; } = Guid.NewGuid();
|
||||||
|
public EventSystemUser? SystemUser { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ public class EventTableEntity : TableEntity, IEvent
|
|||||||
DeviceType = e.DeviceType;
|
DeviceType = e.DeviceType;
|
||||||
IpAddress = e.IpAddress;
|
IpAddress = e.IpAddress;
|
||||||
ActingUserId = e.ActingUserId;
|
ActingUserId = e.ActingUserId;
|
||||||
|
SystemUser = e.SystemUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
@ -44,6 +45,7 @@ public class EventTableEntity : TableEntity, IEvent
|
|||||||
public DeviceType? DeviceType { get; set; }
|
public DeviceType? DeviceType { get; set; }
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
public Guid? ActingUserId { get; set; }
|
public Guid? ActingUserId { get; set; }
|
||||||
|
public EventSystemUser? SystemUser { get; set; }
|
||||||
|
|
||||||
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
|
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
|
||||||
{
|
{
|
||||||
@ -69,6 +71,16 @@ public class EventTableEntity : TableEntity, IEvent
|
|||||||
result.Add(deviceTypeName, new EntityProperty((int?)DeviceType));
|
result.Add(deviceTypeName, new EntityProperty((int?)DeviceType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var systemUserTypeName = nameof(SystemUser);
|
||||||
|
if (result.ContainsKey(systemUserTypeName))
|
||||||
|
{
|
||||||
|
result[systemUserTypeName] = new EntityProperty((int?)SystemUser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Add(systemUserTypeName, new EntityProperty((int?)SystemUser));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +100,12 @@ public class EventTableEntity : TableEntity, IEvent
|
|||||||
{
|
{
|
||||||
DeviceType = (DeviceType)properties[deviceTypeName].Int32Value.Value;
|
DeviceType = (DeviceType)properties[deviceTypeName].Int32Value.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var systemUserTypeName = nameof(SystemUser);
|
||||||
|
if (properties.ContainsKey(systemUserTypeName) && properties[systemUserTypeName].Int32Value.HasValue)
|
||||||
|
{
|
||||||
|
SystemUser = (EventSystemUser)properties[systemUserTypeName].Int32Value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<EventTableEntity> IndexEvent(EventMessage e)
|
public static List<EventTableEntity> IndexEvent(EventMessage e)
|
||||||
|
@ -20,4 +20,5 @@ public interface IEvent
|
|||||||
DeviceType? DeviceType { get; set; }
|
DeviceType? DeviceType { get; set; }
|
||||||
string IpAddress { get; set; }
|
string IpAddress { get; set; }
|
||||||
DateTime Date { get; set; }
|
DateTime Date { get; set; }
|
||||||
|
EventSystemUser? SystemUser { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -17,6 +19,18 @@ public class DeleteGroupCommand : IDeleteGroupCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteGroupAsync(Guid organizationId, Guid id)
|
public async Task DeleteGroupAsync(Guid organizationId, Guid id)
|
||||||
|
{
|
||||||
|
var group = await GroupRepositoryDeleteGroupAsync(organizationId, id);
|
||||||
|
await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteGroupAsync(Guid organizationId, Guid id, EventSystemUser eventSystemUser)
|
||||||
|
{
|
||||||
|
var group = await GroupRepositoryDeleteGroupAsync(organizationId, id);
|
||||||
|
await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Deleted, eventSystemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Group> GroupRepositoryDeleteGroupAsync(Guid organizationId, Guid id)
|
||||||
{
|
{
|
||||||
var group = await _groupRepository.GetByIdAsync(id);
|
var group = await _groupRepository.GetByIdAsync(id);
|
||||||
if (group == null || group.OrganizationId != organizationId)
|
if (group == null || group.OrganizationId != organizationId)
|
||||||
@ -25,6 +39,7 @@ public class DeleteGroupCommand : IDeleteGroupCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _groupRepository.DeleteAsync(group);
|
await _groupRepository.DeleteAsync(group);
|
||||||
await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Deleted);
|
|
||||||
|
return group;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
namespace Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.OrganizationFeatures.Groups.Interfaces;
|
||||||
|
|
||||||
public interface IDeleteGroupCommand
|
public interface IDeleteGroupCommand
|
||||||
{
|
{
|
||||||
Task DeleteGroupAsync(Guid organizationId, Guid id);
|
Task DeleteGroupAsync(Guid organizationId, Guid id);
|
||||||
|
Task DeleteGroupAsync(Guid organizationId, Guid id, EventSystemUser eventSystemUser);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -20,13 +21,25 @@ public class DeleteOrganizationUserCommand : IDeleteOrganizationUserCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
||||||
|
{
|
||||||
|
await ValidateDeleteUserAsync(organizationId, organizationUserId);
|
||||||
|
|
||||||
|
await _organizationService.DeleteUserAsync(organizationId, organizationUserId, deletingUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
|
||||||
|
{
|
||||||
|
await ValidateDeleteUserAsync(organizationId, organizationUserId);
|
||||||
|
|
||||||
|
await _organizationService.DeleteUserAsync(organizationId, organizationUserId, eventSystemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateDeleteUserAsync(Guid organizationId, Guid organizationUserId)
|
||||||
{
|
{
|
||||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||||
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
||||||
{
|
{
|
||||||
throw new NotFoundException("User not found.");
|
throw new NotFoundException("User not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await _organizationService.DeleteUserAsync(organizationId, organizationUserId, deletingUserId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
|
|
||||||
public interface IDeleteOrganizationUserCommand
|
public interface IDeleteOrganizationUserCommand
|
||||||
{
|
{
|
||||||
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
||||||
|
|
||||||
|
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser);
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,12 @@ public interface IEventService
|
|||||||
Task LogCipherEventsAsync(IEnumerable<Tuple<Cipher, EventType, DateTime?>> events);
|
Task LogCipherEventsAsync(IEnumerable<Tuple<Cipher, EventType, DateTime?>> events);
|
||||||
Task LogCollectionEventAsync(Collection collection, EventType type, DateTime? date = null);
|
Task LogCollectionEventAsync(Collection collection, EventType type, DateTime? date = null);
|
||||||
Task LogGroupEventAsync(Group group, EventType type, DateTime? date = null);
|
Task LogGroupEventAsync(Group group, EventType type, DateTime? date = null);
|
||||||
|
Task LogGroupEventAsync(Group group, EventType type, EventSystemUser systemUser, DateTime? date = null);
|
||||||
Task LogPolicyEventAsync(Policy policy, EventType type, DateTime? date = null);
|
Task LogPolicyEventAsync(Policy policy, EventType type, DateTime? date = null);
|
||||||
Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, DateTime? date = null);
|
Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, DateTime? date = null);
|
||||||
|
Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, EventSystemUser systemUser, DateTime? date = null);
|
||||||
Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, DateTime?)> events);
|
Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, DateTime?)> events);
|
||||||
|
Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)> events);
|
||||||
Task LogOrganizationEventAsync(Organization organization, EventType type, DateTime? date = null);
|
Task LogOrganizationEventAsync(Organization organization, EventType type, DateTime? date = null);
|
||||||
Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null);
|
Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null);
|
||||||
Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events);
|
Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
@ -6,7 +7,11 @@ namespace Bit.Core.Services;
|
|||||||
public interface IGroupService
|
public interface IGroupService
|
||||||
{
|
{
|
||||||
Task SaveAsync(Group group, IEnumerable<SelectionReadOnly> collections = null);
|
Task SaveAsync(Group group, IEnumerable<SelectionReadOnly> collections = null);
|
||||||
|
Task SaveAsync(Group group, EventSystemUser systemUser, IEnumerable<SelectionReadOnly> collections = null);
|
||||||
[Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")]
|
[Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")]
|
||||||
Task DeleteAsync(Group group);
|
Task DeleteAsync(Group group);
|
||||||
|
[Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")]
|
||||||
|
Task DeleteAsync(Group group, EventSystemUser systemUser);
|
||||||
Task DeleteUserAsync(Group group, Guid organizationUserId);
|
Task DeleteUserAsync(Group group, Guid organizationUserId);
|
||||||
|
Task DeleteUserAsync(Group group, Guid organizationUserId, EventSystemUser systemUser);
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,12 @@ public interface IOrganizationService
|
|||||||
Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
||||||
Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId,
|
Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId,
|
||||||
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites);
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites);
|
||||||
|
Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, EventSystemUser systemUser,
|
||||||
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites);
|
||||||
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
||||||
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
|
||||||
|
Task<OrganizationUser> InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email,
|
||||||
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
|
||||||
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
|
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
|
||||||
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId);
|
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId);
|
||||||
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
||||||
@ -45,6 +49,8 @@ public interface IOrganizationService
|
|||||||
Task SaveUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable<SelectionReadOnly> collections);
|
Task SaveUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable<SelectionReadOnly> collections);
|
||||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||||
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
||||||
|
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||||
|
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser systemUser);
|
||||||
Task DeleteUserAsync(Guid organizationId, Guid userId);
|
Task DeleteUserAsync(Guid organizationId, Guid userId);
|
||||||
Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId,
|
Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId,
|
||||||
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
|
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
|
||||||
@ -60,9 +66,11 @@ public interface IOrganizationService
|
|||||||
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
|
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
|
||||||
Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId, bool includeProvider = true);
|
Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId, bool includeProvider = true);
|
||||||
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId);
|
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId);
|
||||||
|
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);
|
||||||
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
||||||
IEnumerable<Guid> organizationUserIds, Guid? revokingUserId);
|
IEnumerable<Guid> organizationUserIds, Guid? revokingUserId);
|
||||||
Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId, IUserService userService);
|
Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId, IUserService userService);
|
||||||
|
Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser, IUserService userService);
|
||||||
Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
|
Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
|
||||||
IEnumerable<Guid> organizationUserIds, Guid? restoringUserId, IUserService userService);
|
IEnumerable<Guid> organizationUserIds, Guid? restoringUserId, IUserService userService);
|
||||||
Task<int> GetOccupiedSeatCount(Organization organization);
|
Task<int> GetOccupiedSeatCount(Organization organization);
|
||||||
|
@ -155,7 +155,19 @@ public class EventService : IEventService
|
|||||||
await _eventWriteService.CreateAsync(e);
|
await _eventWriteService.CreateAsync(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogGroupEventAsync(Group group, EventType type, DateTime? date = null)
|
public async Task LogGroupEventAsync(Group group, EventType type,
|
||||||
|
DateTime? date = null)
|
||||||
|
{
|
||||||
|
await CreateLogGroupEventAsync(group, type, systemUser: null, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogGroupEventAsync(Group group, EventType type, EventSystemUser systemUser,
|
||||||
|
DateTime? date = null)
|
||||||
|
{
|
||||||
|
await CreateLogGroupEventAsync(group, type, systemUser, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateLogGroupEventAsync(Group group, EventType type, EventSystemUser? systemUser, DateTime? date = null)
|
||||||
{
|
{
|
||||||
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
|
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
|
||||||
if (!CanUseEvents(orgAbilities, group.OrganizationId))
|
if (!CanUseEvents(orgAbilities, group.OrganizationId))
|
||||||
@ -170,7 +182,8 @@ public class EventService : IEventService
|
|||||||
Type = type,
|
Type = type,
|
||||||
ActingUserId = _currentContext?.UserId,
|
ActingUserId = _currentContext?.UserId,
|
||||||
ProviderId = await GetProviderIdAsync(@group.OrganizationId),
|
ProviderId = await GetProviderIdAsync(@group.OrganizationId),
|
||||||
Date = date.GetValueOrDefault(DateTime.UtcNow)
|
Date = date.GetValueOrDefault(DateTime.UtcNow),
|
||||||
|
SystemUser = systemUser
|
||||||
};
|
};
|
||||||
await _eventWriteService.CreateAsync(e);
|
await _eventWriteService.CreateAsync(e);
|
||||||
}
|
}
|
||||||
@ -197,13 +210,29 @@ public class EventService : IEventService
|
|||||||
|
|
||||||
public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type,
|
public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type,
|
||||||
DateTime? date = null) =>
|
DateTime? date = null) =>
|
||||||
await LogOrganizationUserEventsAsync(new[] { (organizationUser, type, date) });
|
await CreateLogOrganizationUserEventsAsync(new (OrganizationUser, EventType, EventSystemUser?, DateTime?)[] { (organizationUser, type, null, date) });
|
||||||
|
|
||||||
public async Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, DateTime?)> events)
|
public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type,
|
||||||
|
EventSystemUser systemUser, DateTime? date = null) =>
|
||||||
|
await CreateLogOrganizationUserEventsAsync(new (OrganizationUser, EventType, EventSystemUser?, DateTime?)[] { (organizationUser, type, systemUser, date) });
|
||||||
|
|
||||||
|
public async Task LogOrganizationUserEventsAsync(
|
||||||
|
IEnumerable<(OrganizationUser, EventType, DateTime?)> events)
|
||||||
|
{
|
||||||
|
await CreateLogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, (EventSystemUser?)null, e.Item3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogOrganizationUserEventsAsync(
|
||||||
|
IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)> events)
|
||||||
|
{
|
||||||
|
await CreateLogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, (EventSystemUser?)e.Item3, e.Item4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateLogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, EventSystemUser?, DateTime?)> events)
|
||||||
{
|
{
|
||||||
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
|
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
|
||||||
var eventMessages = new List<IEvent>();
|
var eventMessages = new List<IEvent>();
|
||||||
foreach (var (organizationUser, type, date) in events)
|
foreach (var (organizationUser, type, systemUser, date) in events)
|
||||||
{
|
{
|
||||||
if (!CanUseEvents(orgAbilities, organizationUser.OrganizationId))
|
if (!CanUseEvents(orgAbilities, organizationUser.OrganizationId))
|
||||||
{
|
{
|
||||||
@ -218,7 +247,8 @@ public class EventService : IEventService
|
|||||||
ProviderId = await GetProviderIdAsync(organizationUser.OrganizationId),
|
ProviderId = await GetProviderIdAsync(organizationUser.OrganizationId),
|
||||||
Type = type,
|
Type = type,
|
||||||
ActingUserId = _currentContext?.UserId,
|
ActingUserId = _currentContext?.UserId,
|
||||||
Date = date.GetValueOrDefault(DateTime.UtcNow)
|
Date = date.GetValueOrDefault(DateTime.UtcNow),
|
||||||
|
SystemUser = systemUser
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,19 @@ public class GroupService : IGroupService
|
|||||||
_referenceEventService = referenceEventService;
|
_referenceEventService = referenceEventService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAsync(Group group, IEnumerable<SelectionReadOnly> collections = null)
|
public async Task SaveAsync(Group group,
|
||||||
|
IEnumerable<SelectionReadOnly> collections = null)
|
||||||
|
{
|
||||||
|
await GroupRepositorySaveAsync(group, systemUser: null, collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveAsync(Group group, EventSystemUser systemUser,
|
||||||
|
IEnumerable<SelectionReadOnly> collections = null)
|
||||||
|
{
|
||||||
|
await GroupRepositorySaveAsync(group, systemUser, collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GroupRepositorySaveAsync(Group group, EventSystemUser? systemUser, IEnumerable<SelectionReadOnly> collections = null)
|
||||||
{
|
{
|
||||||
var org = await _organizationRepository.GetByIdAsync(group.OrganizationId);
|
var org = await _organizationRepository.GetByIdAsync(group.OrganizationId);
|
||||||
if (org == null)
|
if (org == null)
|
||||||
@ -55,7 +67,15 @@ public class GroupService : IGroupService
|
|||||||
await _groupRepository.CreateAsync(group, collections);
|
await _groupRepository.CreateAsync(group, collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (systemUser.HasValue)
|
||||||
|
{
|
||||||
|
await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created, systemUser.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created);
|
await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created);
|
||||||
|
}
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.GroupCreated, org));
|
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.GroupCreated, org));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -71,25 +91,53 @@ public class GroupService : IGroupService
|
|||||||
await _groupRepository.ReplaceAsync(group, collections);
|
await _groupRepository.ReplaceAsync(group, collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (systemUser.HasValue)
|
||||||
|
{
|
||||||
|
await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated, systemUser.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated);
|
await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")]
|
[Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")]
|
||||||
public async Task DeleteAsync(Group group)
|
public async Task DeleteAsync(Group group)
|
||||||
{
|
{
|
||||||
await _groupRepository.DeleteAsync(group);
|
await _groupRepository.DeleteAsync(group);
|
||||||
await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Deleted);
|
await _eventService.LogGroupEventAsync(group, EventType.Group_Deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("IDeleteGroupCommand should be used instead. To be removed by EC-608.")]
|
||||||
|
public async Task DeleteAsync(Group group, EventSystemUser systemUser)
|
||||||
|
{
|
||||||
|
await _groupRepository.DeleteAsync(group);
|
||||||
|
await _eventService.LogGroupEventAsync(group, EventType.Group_Deleted, systemUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteUserAsync(Group group, Guid organizationUserId)
|
public async Task DeleteUserAsync(Group group, Guid organizationUserId)
|
||||||
|
{
|
||||||
|
var orgUser = await GroupRepositoryDeleteUserAsync(group, organizationUserId, systemUser: null);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_UpdatedGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteUserAsync(Group group, Guid organizationUserId, EventSystemUser systemUser)
|
||||||
|
{
|
||||||
|
var orgUser = await GroupRepositoryDeleteUserAsync(group, organizationUserId, systemUser);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_UpdatedGroups, systemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OrganizationUser> GroupRepositoryDeleteUserAsync(Group group, Guid organizationUserId, EventSystemUser? systemUser)
|
||||||
{
|
{
|
||||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||||
if (orgUser == null || orgUser.OrganizationId != group.OrganizationId)
|
if (orgUser == null || orgUser.OrganizationId != group.OrganizationId)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _groupRepository.DeleteUserAsync(group.Id, organizationUserId);
|
await _groupRepository.DeleteUserAsync(group.Id, organizationUserId);
|
||||||
await _eventService.LogOrganizationUserEventAsync(orgUser, Enums.EventType.OrganizationUser_UpdatedGroups);
|
|
||||||
|
return orgUser;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1115,13 +1115,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId,
|
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId,
|
||||||
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
||||||
{
|
{
|
||||||
var organization = await GetOrgById(organizationId);
|
|
||||||
var initialSeatCount = organization.Seats;
|
|
||||||
if (organization == null || invites.Any(i => i.invite.Emails == null))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var inviteTypes = new HashSet<OrganizationUserType>(invites.Where(i => i.invite.Type.HasValue)
|
var inviteTypes = new HashSet<OrganizationUserType>(invites.Where(i => i.invite.Type.HasValue)
|
||||||
.Select(i => i.invite.Type.Value));
|
.Select(i => i.invite.Type.Value));
|
||||||
if (invitingUserId.HasValue && inviteTypes.Count > 0)
|
if (invitingUserId.HasValue && inviteTypes.Count > 0)
|
||||||
@ -1132,6 +1125,33 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites);
|
||||||
|
|
||||||
|
await _eventService.LogOrganizationUserEventsAsync(events);
|
||||||
|
|
||||||
|
return organizationUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, EventSystemUser systemUser,
|
||||||
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
||||||
|
{
|
||||||
|
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites);
|
||||||
|
|
||||||
|
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, systemUser, e.Item3)));
|
||||||
|
|
||||||
|
return organizationUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(List<OrganizationUser> organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId,
|
||||||
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
||||||
|
{
|
||||||
|
var organization = await GetOrgById(organizationId);
|
||||||
|
var initialSeatCount = organization.Seats;
|
||||||
|
if (organization == null || invites.Any(i => i.invite.Emails == null))
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
var newSeatsRequired = 0;
|
var newSeatsRequired = 0;
|
||||||
var existingEmails = new HashSet<string>(await _organizationUserRepository.SelectKnownEmailsAsync(
|
var existingEmails = new HashSet<string>(await _organizationUserRepository.SelectKnownEmailsAsync(
|
||||||
organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase);
|
organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase);
|
||||||
@ -1157,7 +1177,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var orgUsers = new List<OrganizationUser>();
|
var orgUsers = new List<OrganizationUser>();
|
||||||
var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<SelectionReadOnly>)>();
|
var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<SelectionReadOnly>)>();
|
||||||
var orgUserInvitedCount = 0;
|
var orgUserInvitedCount = 0;
|
||||||
@ -1235,7 +1254,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
await AutoAddSeatsAsync(organization, newSeatsRequired, prorationDate);
|
await AutoAddSeatsAsync(organization, newSeatsRequired, prorationDate);
|
||||||
await SendInvitesAsync(orgUsers.Concat(limitedCollectionOrgUsers.Select(u => u.Item1)), organization);
|
await SendInvitesAsync(orgUsers.Concat(limitedCollectionOrgUsers.Select(u => u.Item1)), organization);
|
||||||
await _eventService.LogOrganizationUserEventsAsync(events);
|
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
await _referenceEventService.RaiseEventAsync(
|
||||||
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization)
|
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization)
|
||||||
@ -1263,7 +1281,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
|
throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return orgUsers;
|
return (orgUsers, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId,
|
public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId,
|
||||||
@ -1647,6 +1665,20 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||||
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
||||||
|
{
|
||||||
|
var orgUser = await RepositoryDeleteUserAsync(organizationId, organizationUserId, deletingUserId);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||||
|
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId,
|
||||||
|
EventSystemUser systemUser)
|
||||||
|
{
|
||||||
|
var orgUser = await RepositoryDeleteUserAsync(organizationId, organizationUserId, null);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed, systemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OrganizationUser> RepositoryDeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
||||||
{
|
{
|
||||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||||
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
||||||
@ -1671,12 +1703,13 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _organizationUserRepository.DeleteAsync(orgUser);
|
await _organizationUserRepository.DeleteAsync(orgUser);
|
||||||
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
|
|
||||||
|
|
||||||
if (orgUser.UserId.HasValue)
|
if (orgUser.UserId.HasValue)
|
||||||
{
|
{
|
||||||
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return orgUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteUserAsync(Guid organizationId, Guid userId)
|
public async Task DeleteUserAsync(Guid organizationId, Guid userId)
|
||||||
@ -1852,6 +1885,18 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
||||||
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
|
||||||
|
{
|
||||||
|
return await SaveUserSendInviteAsync(organizationId, invitingUserId, systemUser: null, email, type, accessAll, externalId, collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email,
|
||||||
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
|
||||||
|
{
|
||||||
|
return await SaveUserSendInviteAsync(organizationId, invitingUserId: null, systemUser, email, type, accessAll, externalId, collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OrganizationUser> SaveUserSendInviteAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, string email,
|
||||||
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
|
||||||
{
|
{
|
||||||
var invite = new OrganizationUserInvite()
|
var invite = new OrganizationUserInvite()
|
||||||
{
|
{
|
||||||
@ -1860,7 +1905,8 @@ public class OrganizationService : IOrganizationService
|
|||||||
AccessAll = accessAll,
|
AccessAll = accessAll,
|
||||||
Collections = collections,
|
Collections = collections,
|
||||||
};
|
};
|
||||||
var results = await InviteUsersAsync(organizationId, invitingUserId,
|
var results = systemUser.HasValue ? await InviteUsersAsync(organizationId, systemUser.Value,
|
||||||
|
new (OrganizationUserInvite, string)[] { (invite, externalId) }) : await InviteUsersAsync(organizationId, invitingUserId,
|
||||||
new (OrganizationUserInvite, string)[] { (invite, externalId) });
|
new (OrganizationUserInvite, string)[] { (invite, externalId) });
|
||||||
var result = results.FirstOrDefault();
|
var result = results.FirstOrDefault();
|
||||||
if (result == null)
|
if (result == null)
|
||||||
@ -2221,11 +2267,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId)
|
public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId)
|
||||||
{
|
{
|
||||||
if (organizationUser.Status == OrganizationUserStatusType.Revoked)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Already revoked.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId.Value)
|
if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId.Value)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You cannot revoke yourself.");
|
throw new BadRequestException("You cannot revoke yourself.");
|
||||||
@ -2237,6 +2278,24 @@ public class OrganizationService : IOrganizationService
|
|||||||
throw new BadRequestException("Only owners can revoke other owners.");
|
throw new BadRequestException("Only owners can revoke other owners.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await RepositoryRevokeUserAsync(organizationUser);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RevokeUserAsync(OrganizationUser organizationUser,
|
||||||
|
EventSystemUser systemUser)
|
||||||
|
{
|
||||||
|
await RepositoryRevokeUserAsync(organizationUser);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, systemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RepositoryRevokeUserAsync(OrganizationUser organizationUser)
|
||||||
|
{
|
||||||
|
if (organizationUser.Status == OrganizationUserStatusType.Revoked)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Already revoked.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!await HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id }))
|
if (!await HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id }))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||||
@ -2244,7 +2303,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
await _organizationUserRepository.RevokeAsync(organizationUser.Id);
|
await _organizationUserRepository.RevokeAsync(organizationUser.Id);
|
||||||
organizationUser.Status = OrganizationUserStatusType.Revoked;
|
organizationUser.Status = OrganizationUserStatusType.Revoked;
|
||||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
public async Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
||||||
@ -2306,13 +2364,9 @@ public class OrganizationService : IOrganizationService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId, IUserService userService)
|
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId,
|
||||||
|
IUserService userService)
|
||||||
{
|
{
|
||||||
if (organizationUser.Status != OrganizationUserStatusType.Revoked)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Already active.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restoringUserId.HasValue && organizationUser.UserId == restoringUserId.Value)
|
if (restoringUserId.HasValue && organizationUser.UserId == restoringUserId.Value)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You cannot restore yourself.");
|
throw new BadRequestException("You cannot restore yourself.");
|
||||||
@ -2324,6 +2378,24 @@ public class OrganizationService : IOrganizationService
|
|||||||
throw new BadRequestException("Only owners can restore other owners.");
|
throw new BadRequestException("Only owners can restore other owners.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await RepositoryRestoreUserAsync(organizationUser, userService);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser,
|
||||||
|
IUserService userService)
|
||||||
|
{
|
||||||
|
await RepositoryRestoreUserAsync(organizationUser, userService);
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, systemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RepositoryRestoreUserAsync(OrganizationUser organizationUser, IUserService userService)
|
||||||
|
{
|
||||||
|
if (organizationUser.Status != OrganizationUserStatusType.Revoked)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Already active.");
|
||||||
|
}
|
||||||
|
|
||||||
var organization = await _organizationRepository.GetByIdAsync(organizationUser.OrganizationId);
|
var organization = await _organizationRepository.GetByIdAsync(organizationUser.OrganizationId);
|
||||||
var occupiedSeats = await GetOccupiedSeatCount(organization);
|
var occupiedSeats = await GetOccupiedSeatCount(organization);
|
||||||
var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats;
|
var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats;
|
||||||
@ -2338,7 +2410,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
await _organizationUserRepository.RestoreAsync(organizationUser.Id, status);
|
await _organizationUserRepository.RestoreAsync(organizationUser.Id, status);
|
||||||
organizationUser.Status = status;
|
organizationUser.Status = status;
|
||||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
|
public async Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
|
||||||
|
@ -31,6 +31,11 @@ public class NoopEventService : IEventService
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task LogGroupEventAsync(Group group, EventType type, EventSystemUser systemUser, DateTime? date = null)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
public Task LogOrganizationEventAsync(Organization organization, EventType type, DateTime? date = null)
|
public Task LogOrganizationEventAsync(Organization organization, EventType type, DateTime? date = null)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
@ -52,8 +57,13 @@ public class NoopEventService : IEventService
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, DateTime? date = null)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type,
|
public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type,
|
||||||
DateTime? date = null)
|
EventSystemUser systemUser, DateTime? date = null)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
@ -63,6 +73,11 @@ public class NoopEventService : IEventService
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)> events)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
public Task LogUserEventAsync(Guid userId, EventType type, DateTime? date = null)
|
public Task LogUserEventAsync(Guid userId, EventType type, DateTime? date = null)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
|
@ -562,8 +562,7 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
|
|||||||
var attachments = string.IsNullOrWhiteSpace(cipher.Attachments) ?
|
var attachments = string.IsNullOrWhiteSpace(cipher.Attachments) ?
|
||||||
new Dictionary<string, CipherAttachment.MetaData>() :
|
new Dictionary<string, CipherAttachment.MetaData>() :
|
||||||
JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(cipher.Attachments);
|
JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(cipher.Attachments);
|
||||||
var metaData = JsonConvert.DeserializeObject<CipherAttachment.MetaData>(attachment.AttachmentData);
|
attachments.Add(attachment.AttachmentId, JsonConvert.DeserializeObject<CipherAttachment.MetaData>(attachment.AttachmentData));
|
||||||
attachments[attachment.AttachmentId] = metaData;
|
|
||||||
cipher.Attachments = JsonConvert.SerializeObject(attachments);
|
cipher.Attachments = JsonConvert.SerializeObject(attachments);
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
@ActingUserId UNIQUEIDENTIFIER,
|
@ActingUserId UNIQUEIDENTIFIER,
|
||||||
@DeviceType SMALLINT,
|
@DeviceType SMALLINT,
|
||||||
@IpAddress VARCHAR(50),
|
@IpAddress VARCHAR(50),
|
||||||
@Date DATETIME2(7)
|
@Date DATETIME2(7),
|
||||||
|
@SystemUser TINYINT = null
|
||||||
AS
|
AS
|
||||||
BEGIN
|
BEGIN
|
||||||
SET NOCOUNT ON
|
SET NOCOUNT ON
|
||||||
@ -38,7 +39,8 @@ BEGIN
|
|||||||
[ActingUserId],
|
[ActingUserId],
|
||||||
[DeviceType],
|
[DeviceType],
|
||||||
[IpAddress],
|
[IpAddress],
|
||||||
[Date]
|
[Date],
|
||||||
|
[SystemUser]
|
||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
@ -58,6 +60,7 @@ BEGIN
|
|||||||
@ActingUserId,
|
@ActingUserId,
|
||||||
@DeviceType,
|
@DeviceType,
|
||||||
@IpAddress,
|
@IpAddress,
|
||||||
@Date
|
@Date,
|
||||||
|
@SystemUser
|
||||||
)
|
)
|
||||||
END
|
END
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
[ProviderId] UNIQUEIDENTIFIER NULL,
|
[ProviderId] UNIQUEIDENTIFIER NULL,
|
||||||
[ProviderUserId] UNIQUEIDENTIFIER NULL,
|
[ProviderUserId] UNIQUEIDENTIFIER NULL,
|
||||||
[ProviderOrganizationId] UNIQUEIDENTIFIER NULL,
|
[ProviderOrganizationId] UNIQUEIDENTIFIER NULL,
|
||||||
|
[SystemUser] TINYINT NULL,
|
||||||
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
|
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.OrganizationFeatures.Groups;
|
using Bit.Core.OrganizationFeatures.Groups;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
@ -48,4 +49,18 @@ public class DeleteGroupCommandTests
|
|||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteGroupAsync(organizationId, groupId));
|
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteGroupAsync(organizationId, groupId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task DeleteGroup_WithEventSystemUser_Success(SutProvider<DeleteGroupCommand> sutProvider, Group group, EventSystemUser eventSystemUser)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IGroupRepository>()
|
||||||
|
.GetByIdAsync(group.Id)
|
||||||
|
.Returns(group);
|
||||||
|
|
||||||
|
await sutProvider.Sut.DeleteGroupAsync(group.OrganizationId, group.Id, eventSystemUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IGroupRepository>().Received(1).DeleteAsync(group);
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogGroupEventAsync(group, Core.Enums.EventType.Group_Deleted, eventSystemUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationUsers;
|
using Bit.Core.OrganizationFeatures.OrganizationUsers;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
@ -51,4 +52,21 @@ public class DeleteOrganizationUserCommandTests
|
|||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteUserAsync(organizationId, organizationUserId, null));
|
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteUserAsync(organizationId, organizationUserId, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task DeleteUser_WithEventSystemUser_Success(SutProvider<DeleteOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUserId)
|
||||||
|
.Returns(new OrganizationUser
|
||||||
|
{
|
||||||
|
Id = organizationUserId,
|
||||||
|
OrganizationId = organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
await sutProvider.Sut.DeleteUserAsync(organizationId, organizationUserId, eventSystemUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationService>().Received(1).DeleteUserAsync(organizationId, organizationUserId, eventSystemUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,56 @@ public class EventServiceTests
|
|||||||
Enum.GetValues<EventType>().Select(e => (object)e)
|
Enum.GetValues<EventType>().Select(e => (object)e)
|
||||||
).Select(p => p.ToArray());
|
).Select(p => p.ToArray());
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task LogGroupEvent_LogsRequiredInfo(Group group, EventType eventType, DateTime date,
|
||||||
|
Guid actingUserId, Guid providerId, DeviceType deviceType, SutProvider<EventService> sutProvider)
|
||||||
|
{
|
||||||
|
var orgAbilities = new Dictionary<Guid, OrganizationAbility>()
|
||||||
|
{
|
||||||
|
{ group.OrganizationId, new OrganizationAbility() { UseEvents = true, Enabled = true } }
|
||||||
|
};
|
||||||
|
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilitiesAsync().Returns(orgAbilities);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ProviderIdForOrg(Arg.Any<Guid>()).Returns(providerId);
|
||||||
|
|
||||||
|
await sutProvider.Sut.LogGroupEventAsync(group, eventType, date);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateAsync(Arg.Is<IEvent>(e =>
|
||||||
|
e.OrganizationId == group.OrganizationId &&
|
||||||
|
e.GroupId == group.Id &&
|
||||||
|
e.Type == eventType &&
|
||||||
|
e.ActingUserId == actingUserId &&
|
||||||
|
e.ProviderId == providerId &&
|
||||||
|
e.Date == date &&
|
||||||
|
e.SystemUser == null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task LogGroupEvent_WithEventSystemUser_LogsRequiredInfo(Group group, EventType eventType, EventSystemUser eventSystemUser, DateTime date,
|
||||||
|
Guid actingUserId, Guid providerId, DeviceType deviceType, SutProvider<EventService> sutProvider)
|
||||||
|
{
|
||||||
|
var orgAbilities = new Dictionary<Guid, OrganizationAbility>()
|
||||||
|
{
|
||||||
|
{ group.OrganizationId, new OrganizationAbility() { UseEvents = true, Enabled = true } }
|
||||||
|
};
|
||||||
|
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilitiesAsync().Returns(orgAbilities);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ProviderIdForOrg(Arg.Any<Guid>()).Returns(providerId);
|
||||||
|
|
||||||
|
await sutProvider.Sut.LogGroupEventAsync(group, eventType, eventSystemUser, date);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateAsync(Arg.Is<IEvent>(e =>
|
||||||
|
e.OrganizationId == group.OrganizationId &&
|
||||||
|
e.GroupId == group.Id &&
|
||||||
|
e.Type == eventType &&
|
||||||
|
e.ActingUserId == actingUserId &&
|
||||||
|
e.ProviderId == providerId &&
|
||||||
|
e.Date == date &&
|
||||||
|
e.SystemUser == eventSystemUser));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitMemberAutoData(nameof(InstallationIdTestCases))]
|
[BitMemberAutoData(nameof(InstallationIdTestCases))]
|
||||||
public async Task LogOrganizationEvent_ProvidesInstallationId(Guid? installationId, EventType eventType,
|
public async Task LogOrganizationEvent_ProvidesInstallationId(Guid? installationId, EventType eventType,
|
||||||
@ -73,6 +123,41 @@ public class EventServiceTests
|
|||||||
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
|
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task LogOrganizationUserEvent_WithEventSystemUser_LogsRequiredInfo(OrganizationUser orgUser, EventType eventType, EventSystemUser eventSystemUser, DateTime date,
|
||||||
|
Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider<EventService> sutProvider)
|
||||||
|
{
|
||||||
|
var orgAbilities = new Dictionary<Guid, OrganizationAbility>()
|
||||||
|
{
|
||||||
|
{orgUser.OrganizationId, new OrganizationAbility() { UseEvents = true, Enabled = true } }
|
||||||
|
};
|
||||||
|
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilitiesAsync().Returns(orgAbilities);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ProviderIdForOrg(Arg.Any<Guid>()).Returns(providerId);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
|
||||||
|
|
||||||
|
await sutProvider.Sut.LogOrganizationUserEventAsync(orgUser, eventType, eventSystemUser, date);
|
||||||
|
|
||||||
|
var expected = new List<IEvent>() {
|
||||||
|
new EventMessage()
|
||||||
|
{
|
||||||
|
IpAddress = ipAddress,
|
||||||
|
DeviceType = deviceType,
|
||||||
|
OrganizationId = orgUser.OrganizationId,
|
||||||
|
UserId = orgUser.UserId,
|
||||||
|
OrganizationUserId = orgUser.Id,
|
||||||
|
ProviderId = providerId,
|
||||||
|
Type = eventType,
|
||||||
|
ActingUserId = actingUserId,
|
||||||
|
Date = date,
|
||||||
|
SystemUser = eventSystemUser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task LogProviderUserEvent_LogsRequiredInfo(ProviderUser providerUser, EventType eventType, DateTime date,
|
public async Task LogProviderUserEvent_LogsRequiredInfo(ProviderUser providerUser, EventType eventType, DateTime date,
|
||||||
Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider<EventService> sutProvider)
|
Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider<EventService> sutProvider)
|
||||||
|
@ -27,8 +27,23 @@ public class GroupServiceTests
|
|||||||
await sutProvider.Sut.SaveAsync(group);
|
await sutProvider.Sut.SaveAsync(group);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group);
|
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group);
|
||||||
await sutProvider.GetDependency<IEventService>().Received()
|
await sutProvider.GetDependency<IEventService>().Received().LogGroupEventAsync(group, EventType.Group_Created);
|
||||||
.LogGroupEventAsync(group, EventType.Group_Created);
|
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||||
|
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveAsync_DefaultGroupId_WithEventSystemUser_CreatesGroupInRepository(Group group, Organization organization, EventSystemUser eventSystemUser, SutProvider<GroupService> sutProvider)
|
||||||
|
{
|
||||||
|
group.Id = default(Guid);
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||||
|
organization.UseGroups = true;
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
await sutProvider.Sut.SaveAsync(group, eventSystemUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group);
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received().LogGroupEventAsync(group, EventType.Group_Created, eventSystemUser);
|
||||||
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||||
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||||
}
|
}
|
||||||
@ -44,8 +59,7 @@ public class GroupServiceTests
|
|||||||
await sutProvider.Sut.SaveAsync(group, collections);
|
await sutProvider.Sut.SaveAsync(group, collections);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group, collections);
|
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group, collections);
|
||||||
await sutProvider.GetDependency<IEventService>().Received()
|
await sutProvider.GetDependency<IEventService>().Received().LogGroupEventAsync(group, EventType.Group_Created);
|
||||||
.LogGroupEventAsync(group, EventType.Group_Created);
|
|
||||||
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||||
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||||
}
|
}
|
||||||
@ -59,8 +73,7 @@ public class GroupServiceTests
|
|||||||
await sutProvider.Sut.SaveAsync(group, collections);
|
await sutProvider.Sut.SaveAsync(group, collections);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received().ReplaceAsync(group, collections);
|
await sutProvider.GetDependency<IGroupRepository>().Received().ReplaceAsync(group, collections);
|
||||||
await sutProvider.GetDependency<IEventService>().Received()
|
await sutProvider.GetDependency<IEventService>().Received().LogGroupEventAsync(group, EventType.Group_Updated);
|
||||||
.LogGroupEventAsync(group, EventType.Group_Updated);
|
|
||||||
Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
|
Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,8 +86,7 @@ public class GroupServiceTests
|
|||||||
await sutProvider.Sut.SaveAsync(group, null);
|
await sutProvider.Sut.SaveAsync(group, null);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received().ReplaceAsync(group);
|
await sutProvider.GetDependency<IGroupRepository>().Received().ReplaceAsync(group);
|
||||||
await sutProvider.GetDependency<IEventService>().Received()
|
await sutProvider.GetDependency<IEventService>().Received().LogGroupEventAsync(group, EventType.Group_Updated);
|
||||||
.LogGroupEventAsync(group, EventType.Group_Updated);
|
|
||||||
Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
|
Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,8 +121,16 @@ public class GroupServiceTests
|
|||||||
await sutProvider.Sut.DeleteAsync(group);
|
await sutProvider.Sut.DeleteAsync(group);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteAsync(group);
|
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteAsync(group);
|
||||||
await sutProvider.GetDependency<IEventService>().Received()
|
await sutProvider.GetDependency<IEventService>().Received().LogGroupEventAsync(group, EventType.Group_Deleted);
|
||||||
.LogGroupEventAsync(group, EventType.Group_Deleted);
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task DeleteAsync_ValidData_WithEventSystemUser_DeletesGroup(Group group, EventSystemUser eventSystemUser, SutProvider<GroupService> sutProvider)
|
||||||
|
{
|
||||||
|
await sutProvider.Sut.DeleteAsync(group, eventSystemUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteAsync(group);
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received().LogGroupEventAsync(group, EventType.Group_Deleted, eventSystemUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@ -130,6 +150,23 @@ public class GroupServiceTests
|
|||||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task DeleteUserAsync_ValidData_WithEventSystemUser_DeletesUserInGroupRepository(Group group, Organization organization, OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<GroupService> sutProvider)
|
||||||
|
{
|
||||||
|
group.OrganizationId = organization.Id;
|
||||||
|
organization.UseGroups = true;
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||||
|
organizationUser.OrganizationId = organization.Id;
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
|
||||||
|
await sutProvider.Sut.DeleteUserAsync(group, organizationUser.Id, eventSystemUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteUserAsync(group.Id, organizationUser.Id);
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received()
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups, eventSystemUser);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task DeleteUserAsync_InvalidUser_ThrowsNotFound(Group group, Organization organization, OrganizationUser organizationUser, SutProvider<GroupService> sutProvider)
|
public async Task DeleteUserAsync_InvalidUser_ThrowsNotFound(Group group, Organization organization, OrganizationUser organizationUser, SutProvider<GroupService> sutProvider)
|
||||||
{
|
{
|
||||||
|
@ -186,11 +186,13 @@ public class OrganizationServiceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[OrganizationInviteCustomize, BitAutoData]
|
[OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User,
|
||||||
|
InvitorUserType = OrganizationUserType.Owner), BitAutoData]
|
||||||
public async Task InviteUser_NoEmails_Throws(Organization organization, OrganizationUser invitor,
|
public async Task InviteUser_NoEmails_Throws(Organization organization, OrganizationUser invitor,
|
||||||
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||||
{
|
{
|
||||||
invite.Emails = null;
|
invite.Emails = null;
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
||||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||||
await Assert.ThrowsAsync<NotFoundException>(
|
await Assert.ThrowsAsync<NotFoundException>(
|
||||||
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
||||||
@ -357,11 +359,6 @@ public class OrganizationServiceTests
|
|||||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
SutProvider<OrganizationService> sutProvider)
|
SutProvider<OrganizationService> sutProvider)
|
||||||
{
|
{
|
||||||
// Autofixture will add collections for all of the invites, remove the first and for all the rest set all access false
|
|
||||||
invites.First().invite.AccessAll = true;
|
|
||||||
invites.First().invite.Collections = null;
|
|
||||||
invites.Skip(1).ToList().ForEach(i => i.invite.AccessAll = false);
|
|
||||||
|
|
||||||
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
|
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
|
||||||
new JsonSerializerOptions
|
new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
@ -382,6 +379,42 @@ public class OrganizationServiceTests
|
|||||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||||
.BulkSendOrganizationInviteEmailAsync(organization.Name,
|
.BulkSendOrganizationInviteEmailAsync(organization.Name,
|
||||||
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(v => v.Count() == invites.SelectMany(i => i.invite.Emails).Count()));
|
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(v => v.Count() == invites.SelectMany(i => i.invite.Emails).Count()));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[OrganizationInviteCustomize(
|
||||||
|
InviteeUserType = OrganizationUserType.User,
|
||||||
|
InvitorUserType = OrganizationUserType.Custom
|
||||||
|
), BitAutoData]
|
||||||
|
public async Task InviteUser_WithEventSystemUser_Passes(Organization organization, EventSystemUser eventSystemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
|
||||||
|
OrganizationUser invitor,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
|
||||||
|
new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
});
|
||||||
|
|
||||||
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||||
|
|
||||||
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||||
|
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
|
||||||
|
.Returns(new[] { owner });
|
||||||
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
||||||
|
|
||||||
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, eventSystemUser, invites);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||||
|
.BulkSendOrganizationInviteEmailAsync(organization.Name,
|
||||||
|
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(v => v.Count() == invites.SelectMany(i => i.invite.Emails).Count()));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@ -505,6 +538,29 @@ public class OrganizationServiceTests
|
|||||||
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
|
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
|
||||||
|
|
||||||
await sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
|
await sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task DeleteUser_WithEventSystemUser_Success(
|
||||||
|
OrganizationUser organizationUser,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser, EventSystemUser eventSystemUser,
|
||||||
|
SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||||
|
|
||||||
|
organizationUser.OrganizationId = deletingUser.OrganizationId;
|
||||||
|
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
|
||||||
|
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
|
||||||
|
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
|
||||||
|
.Returns(new[] { deletingUser, organizationUser });
|
||||||
|
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
|
||||||
|
|
||||||
|
await sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, eventSystemUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@ -962,4 +1018,77 @@ public class OrganizationServiceTests
|
|||||||
await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default);
|
await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default);
|
||||||
await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default);
|
await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RestoreRevokeUser_Setup(Organization organization, OrganizationUser owner, OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationUser.OrganizationId).Returns(organization);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
organizationUserRepository.GetManyByOrganizationAsync(organizationUser.OrganizationId, OrganizationUserType.Owner)
|
||||||
|
.Returns(new[] { owner });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RevokeUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
[OrganizationUser] OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var eventService = sutProvider.GetDependency<IEventService>();
|
||||||
|
|
||||||
|
await sutProvider.Sut.RevokeUserAsync(organizationUser, owner.Id);
|
||||||
|
|
||||||
|
await organizationUserRepository.Received().RevokeAsync(organizationUser.Id);
|
||||||
|
await eventService.Received()
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RevokeUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
[OrganizationUser] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var eventService = sutProvider.GetDependency<IEventService>();
|
||||||
|
|
||||||
|
await sutProvider.Sut.RevokeUserAsync(organizationUser, eventSystemUser);
|
||||||
|
|
||||||
|
await organizationUserRepository.Received().RevokeAsync(organizationUser.Id);
|
||||||
|
await eventService.Received()
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, eventSystemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RestoreUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||||
|
var userService = Substitute.For<IUserService>();
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var eventService = sutProvider.GetDependency<IEventService>();
|
||||||
|
|
||||||
|
await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService);
|
||||||
|
|
||||||
|
await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited);
|
||||||
|
await eventService.Received()
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RestoreUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||||
|
var userService = Substitute.For<IUserService>();
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var eventService = sutProvider.GetDependency<IEventService>();
|
||||||
|
|
||||||
|
await sutProvider.Sut.RestoreUserAsync(organizationUser, eventSystemUser, userService);
|
||||||
|
|
||||||
|
await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited);
|
||||||
|
await eventService.Received()
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, eventSystemUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
92
util/Migrator/DbScripts/2022-09-26_00_EventsSystemUser.sql
Normal file
92
util/Migrator/DbScripts/2022-09-26_00_EventsSystemUser.sql
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
-- Add column SystemUser to Event table
|
||||||
|
IF COL_LENGTH('[dbo].[Event]', 'SystemUser') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE
|
||||||
|
[dbo].[Event]
|
||||||
|
ADD
|
||||||
|
[SystemUser] TINYINT NULL;
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Recreate EventView so that it includes the SystemUser column
|
||||||
|
IF OBJECT_ID('[dbo].[EventView]') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP VIEW [dbo].[EventView]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE VIEW [dbo].[EventView]
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
[dbo].[Event]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE [dbo].[Event_Create]
|
||||||
|
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||||
|
@Type INT,
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@OrganizationId UNIQUEIDENTIFIER,
|
||||||
|
@InstallationId UNIQUEIDENTIFIER,
|
||||||
|
@ProviderId UNIQUEIDENTIFIER,
|
||||||
|
@CipherId UNIQUEIDENTIFIER,
|
||||||
|
@CollectionId UNIQUEIDENTIFIER,
|
||||||
|
@PolicyId UNIQUEIDENTIFIER,
|
||||||
|
@GroupId UNIQUEIDENTIFIER,
|
||||||
|
@OrganizationUserId UNIQUEIDENTIFIER,
|
||||||
|
@ProviderUserId UNIQUEIDENTIFIER,
|
||||||
|
@ProviderOrganizationId UNIQUEIDENTIFIER = null,
|
||||||
|
@ActingUserId UNIQUEIDENTIFIER,
|
||||||
|
@DeviceType SMALLINT,
|
||||||
|
@IpAddress VARCHAR(50),
|
||||||
|
@Date DATETIME2(7),
|
||||||
|
@SystemUser TINYINT = null
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
INSERT INTO [dbo].[Event]
|
||||||
|
(
|
||||||
|
[Id],
|
||||||
|
[Type],
|
||||||
|
[UserId],
|
||||||
|
[OrganizationId],
|
||||||
|
[InstallationId],
|
||||||
|
[ProviderId],
|
||||||
|
[CipherId],
|
||||||
|
[CollectionId],
|
||||||
|
[PolicyId],
|
||||||
|
[GroupId],
|
||||||
|
[OrganizationUserId],
|
||||||
|
[ProviderUserId],
|
||||||
|
[ProviderOrganizationId],
|
||||||
|
[ActingUserId],
|
||||||
|
[DeviceType],
|
||||||
|
[IpAddress],
|
||||||
|
[Date],
|
||||||
|
[SystemUser]
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@Id,
|
||||||
|
@Type,
|
||||||
|
@UserId,
|
||||||
|
@OrganizationId,
|
||||||
|
@InstallationId,
|
||||||
|
@ProviderId,
|
||||||
|
@CipherId,
|
||||||
|
@CollectionId,
|
||||||
|
@PolicyId,
|
||||||
|
@GroupId,
|
||||||
|
@OrganizationUserId,
|
||||||
|
@ProviderUserId,
|
||||||
|
@ProviderOrganizationId,
|
||||||
|
@ActingUserId,
|
||||||
|
@DeviceType,
|
||||||
|
@IpAddress,
|
||||||
|
@Date,
|
||||||
|
@SystemUser
|
||||||
|
)
|
||||||
|
END
|
||||||
|
GO
|
1599
util/MySqlMigrations/Migrations/20220927142038_EventsSystemUser.Designer.cs
generated
Normal file
1599
util/MySqlMigrations/Migrations/20220927142038_EventsSystemUser.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Bit.MySqlMigrations.Migrations;
|
||||||
|
|
||||||
|
public partial class EventsSystemUser : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<byte>(
|
||||||
|
name: "SystemUser",
|
||||||
|
table: "Event",
|
||||||
|
type: "tinyint unsigned",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SystemUser",
|
||||||
|
table: "Event");
|
||||||
|
}
|
||||||
|
}
|
@ -351,6 +351,9 @@ namespace Bit.MySqlMigrations.Migrations
|
|||||||
b.Property<Guid?>("ProviderUserId")
|
b.Property<Guid?>("ProviderUserId")
|
||||||
.HasColumnType("char(36)");
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.Property<byte?>("SystemUser")
|
||||||
|
.HasColumnType("tinyint unsigned");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
1610
util/PostgresMigrations/Migrations/20220927142152_EventsSystemUser.Designer.cs
generated
Normal file
1610
util/PostgresMigrations/Migrations/20220927142152_EventsSystemUser.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Bit.PostgresMigrations.Migrations;
|
||||||
|
|
||||||
|
public partial class EventsSystemUser : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<byte>(
|
||||||
|
name: "SystemUser",
|
||||||
|
table: "Event",
|
||||||
|
type: "smallint",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SystemUser",
|
||||||
|
table: "Event");
|
||||||
|
}
|
||||||
|
}
|
@ -352,6 +352,9 @@ namespace Bit.PostgresMigrations.Migrations
|
|||||||
b.Property<Guid?>("ProviderUserId")
|
b.Property<Guid?>("ProviderUserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte?>("SystemUser")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user