1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-06 21:48:12 -05:00

handle user delete scenarios when part of org

This commit is contained in:
Kyle Spearrin 2017-04-27 17:28:39 -04:00
parent da03c276aa
commit 628a72b13f
6 changed files with 66 additions and 4 deletions

View File

@ -11,6 +11,7 @@ namespace Bit.Core.Repositories
{ {
Task<int> GetCountByOrganizationIdAsync(Guid organizationId); Task<int> GetCountByOrganizationIdAsync(Guid organizationId);
Task<int> GetCountByFreeOrganizationAdminUserAsync(Guid userId); Task<int> GetCountByFreeOrganizationAdminUserAsync(Guid userId);
Task<int> GetCountByOrganizationOwnerUserAsync(Guid userId);
Task<ICollection<OrganizationUser>> GetManyByUserAsync(Guid userId); Task<ICollection<OrganizationUser>> GetManyByUserAsync(Guid userId);
Task<ICollection<OrganizationUser>> GetManyByOrganizationAsync(Guid organizationId, OrganizationUserType? type); Task<ICollection<OrganizationUser>> GetManyByOrganizationAsync(Guid organizationId, OrganizationUserType? type);
Task<OrganizationUser> GetByOrganizationAsync(Guid organizationId, string email); Task<OrganizationUser> GetByOrganizationAsync(Guid organizationId, string email);

View File

@ -47,6 +47,19 @@ namespace Bit.Core.Repositories.SqlServer
} }
} }
public async Task<int> GetCountByOrganizationOwnerUserAsync(Guid userId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteScalarAsync<int>(
"[dbo].[OrganizationUser_ReadCountByOrganizationOwnerUser]",
new { UserId = userId },
commandType: CommandType.StoredProcedure);
return results;
}
}
public async Task<OrganizationUser> GetByOrganizationAsync(Guid organizationId, string email) public async Task<OrganizationUser> GetByOrganizationAsync(Guid organizationId, string email)
{ {
using(var connection = new SqlConnection(ConnectionString)) using(var connection = new SqlConnection(ConnectionString))

View File

@ -18,6 +18,7 @@ namespace Bit.Core.Services
{ {
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly ICipherRepository _cipherRepository; private readonly ICipherRepository _cipherRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IMailService _mailService; private readonly IMailService _mailService;
private readonly IPushService _pushService; private readonly IPushService _pushService;
private readonly IdentityErrorDescriber _identityErrorDescriber; private readonly IdentityErrorDescriber _identityErrorDescriber;
@ -29,6 +30,7 @@ namespace Bit.Core.Services
public UserService( public UserService(
IUserRepository userRepository, IUserRepository userRepository,
ICipherRepository cipherRepository, ICipherRepository cipherRepository,
IOrganizationUserRepository organizationUserRepository,
IMailService mailService, IMailService mailService,
IPushService pushService, IPushService pushService,
IUserStore<User> store, IUserStore<User> store,
@ -54,6 +56,7 @@ namespace Bit.Core.Services
{ {
_userRepository = userRepository; _userRepository = userRepository;
_cipherRepository = cipherRepository; _cipherRepository = cipherRepository;
_organizationUserRepository = organizationUserRepository;
_mailService = mailService; _mailService = mailService;
_pushService = pushService; _pushService = pushService;
_identityOptions = optionsAccessor?.Value ?? new IdentityOptions(); _identityOptions = optionsAccessor?.Value ?? new IdentityOptions();
@ -133,6 +136,22 @@ namespace Bit.Core.Services
await _pushService.PushSyncSettingsAsync(user.Id); await _pushService.PushSyncSettingsAsync(user.Id);
} }
public override async Task<IdentityResult> DeleteAsync(User user)
{
// Check if user is the owner of any organizations.
var organizationOwnerCount = await _organizationUserRepository.GetCountByOrganizationOwnerUserAsync(user.Id);
if(organizationOwnerCount > 0)
{
return IdentityResult.Failed(new IdentityError
{
Description = "You must leave or delete any organizations that you are the owner of first."
});
}
await _userRepository.DeleteAsync(user);
return IdentityResult.Success;
}
public async Task<IdentityResult> RegisterUserAsync(User user, string masterPassword) public async Task<IdentityResult> RegisterUserAsync(User user, string masterPassword)
{ {
var result = await base.CreateAsync(user, masterPassword); var result = await base.CreateAsync(user, masterPassword);
@ -184,7 +203,7 @@ namespace Bit.Core.Services
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
} }
if(!await base.VerifyUserTokenAsync(user, _identityOptions.Tokens.ChangeEmailTokenProvider, if(!await base.VerifyUserTokenAsync(user, _identityOptions.Tokens.ChangeEmailTokenProvider,
GetChangeEmailTokenPurpose(newEmail), token)) GetChangeEmailTokenPurpose(newEmail), token))
{ {
return IdentityResult.Failed(_identityErrorDescriber.InvalidToken()); return IdentityResult.Failed(_identityErrorDescriber.InvalidToken());
@ -224,7 +243,7 @@ namespace Bit.Core.Services
throw new NotImplementedException(); throw new NotImplementedException();
} }
public async Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, public async Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword,
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders, string privateKey) IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders, string privateKey)
{ {
if(user == null) if(user == null)
@ -373,7 +392,7 @@ namespace Bit.Core.Services
if(errors.Count > 0) if(errors.Count > 0)
{ {
Logger.LogWarning("User {userId} password validation failed: {errors}.", await GetUserIdAsync(user), Logger.LogWarning("User {userId} password validation failed: {errors}.", await GetUserIdAsync(user),
string.Join(";", errors.Select(e => e.Code))); string.Join(";", errors.Select(e => e.Code)));
return IdentityResult.Failed(errors.ToArray()); return IdentityResult.Failed(errors.ToArray());
} }

View File

@ -179,5 +179,6 @@
<Build Include="dbo\Stored Procedures\OrganizationUserOrganizationDetails_ReadByUserIdStatus.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUserOrganizationDetails_ReadByUserIdStatus.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadById.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUserUserDetails_ReadById.sql" />
<Build Include="dbo\User Defined Types\GuidIdArray.sql" /> <Build Include="dbo\User Defined Types\GuidIdArray.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_ReadCountByOrganizationOwnerUser.sql" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,14 @@
CREATE PROCEDURE [dbo].[OrganizationUser_ReadCountByOrganizationOwnerUser]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
COUNT(1)
FROM
[dbo].[OrganizationUser] OU
WHERE
OU.[UserId] = @UserId
AND OU.[Type] = 0
END

View File

@ -6,6 +6,7 @@ BEGIN
SET NOCOUNT ON SET NOCOUNT ON
DECLARE @BatchSize INT = 100 DECLARE @BatchSize INT = 100
-- Delete ciphers
WHILE @BatchSize > 0 WHILE @BatchSize > 0
BEGIN BEGIN
BEGIN TRANSACTION User_DeleteById_Ciphers BEGIN TRANSACTION User_DeleteById_Ciphers
@ -23,12 +24,25 @@ BEGIN
BEGIN TRANSACTION User_DeleteById BEGIN TRANSACTION User_DeleteById
-- Delete collection users
DELETE
CU
FROM
[dbo].[CollectionUser] CU
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[Id] = CU.[OrganizationUserId]
WHERE
OU.[UserId] = @Id
-- Delete organization users
DELETE DELETE
FROM FROM
[dbo].[Device] [dbo].[OrganizationUser]
WHERE WHERE
[UserId] = @Id [UserId] = @Id
AND [Type] != 0 -- 0 = owner
-- Finally, delete the user
DELETE DELETE
FROM FROM
[dbo].[User] [dbo].[User]