mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
Add cipher response to restore (#1072)
* Return revised ciphers on restore api call * Return restored date from restore sproc * Test Restore updates passed in ciphers This is necessary for CipherController to appropriately return the up-to-date ciphers without an extra db call to read them. * Add missing SELECT
This commit is contained in:
parent
6143ad2b95
commit
5aba9f7549
@ -437,7 +437,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpPut("{id}/restore")]
|
||||
public async Task PutRestore(string id)
|
||||
public async Task<CipherResponseModel> PutRestore(string id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
@ -447,13 +447,14 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
await _cipherService.RestoreAsync(cipher, userId);
|
||||
return new CipherResponseModel(cipher, _globalSettings);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/restore-admin")]
|
||||
public async Task PutRestoreAdmin(string id)
|
||||
public async Task<CipherMiniResponseModel> PutRestoreAdmin(string id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
|
||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
{
|
||||
@ -461,10 +462,11 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
await _cipherService.RestoreAsync(cipher, userId, true);
|
||||
return new CipherMiniResponseModel(cipher, _globalSettings, cipher.OrganizationUseTotp);
|
||||
}
|
||||
|
||||
[HttpPut("restore")]
|
||||
public async Task PutRestoreMany([FromBody]CipherBulkRestoreRequestModel model)
|
||||
public async Task<ListResponseModel<CipherResponseModel>> PutRestoreMany([FromBody] CipherBulkRestoreRequestModel model)
|
||||
{
|
||||
if (!_globalSettings.SelfHosted && model.Ids.Count() > 500)
|
||||
{
|
||||
@ -472,7 +474,14 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
await _cipherService.RestoreManyAsync(model.Ids.Select(i => new Guid(i)), userId);
|
||||
var cipherIdsToRestore = new HashSet<Guid>(model.Ids.Select(i => new Guid(i)));
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId);
|
||||
var restoringCiphers = ciphers.Where(c => cipherIdsToRestore.Contains(c.Id) && c.Edit);
|
||||
|
||||
await _cipherService.RestoreManyAsync(restoringCiphers, userId);
|
||||
var responses = restoringCiphers.Select(c => new CipherResponseModel(c, _globalSettings));
|
||||
return new ListResponseModel<CipherResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPut("move")]
|
||||
|
3
src/Core/AssemblyInfo.cs
Normal file
3
src/Core/AssemblyInfo.cs
Normal file
@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Core.Test")]
|
@ -35,6 +35,6 @@ namespace Bit.Core.Repositories
|
||||
IEnumerable<CollectionCipher> collectionCiphers);
|
||||
Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||
Task RestoreAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
}
|
||||
}
|
||||
|
@ -610,14 +610,16 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RestoreAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
public async Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
var results = await connection.ExecuteScalarAsync<DateTime>(
|
||||
$"[{Schema}].[Cipher_Restore]",
|
||||
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,6 @@ namespace Bit.Core.Services
|
||||
Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
||||
Task SoftDeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false);
|
||||
Task RestoreAsync(Cipher cipher, Guid restoringUserId, bool orgAdmin = false);
|
||||
Task RestoreManyAsync(IEnumerable<Guid> cipherIds, Guid restoringUserId);
|
||||
Task RestoreManyAsync(IEnumerable<CipherDetails> ciphers, Guid restoringUserId);
|
||||
}
|
||||
}
|
||||
|
@ -786,16 +786,16 @@ namespace Bit.Core.Services
|
||||
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
|
||||
}
|
||||
|
||||
public async Task RestoreManyAsync(IEnumerable<Guid> cipherIds, Guid restoringUserId)
|
||||
public async Task RestoreManyAsync(IEnumerable<CipherDetails> ciphers, Guid restoringUserId)
|
||||
{
|
||||
var cipherIdsSet = new HashSet<Guid>(cipherIds);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(restoringUserId);
|
||||
var restoringCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit);
|
||||
var revisionDate = await _cipherRepository.RestoreAsync(ciphers.Select(c => c.Id), restoringUserId);
|
||||
|
||||
await _cipherRepository.RestoreAsync(cipherIds, restoringUserId);
|
||||
|
||||
var events = restoringCiphers.Select(c =>
|
||||
new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_Restored, null));
|
||||
var events = ciphers.Select(c =>
|
||||
{
|
||||
c.RevisionDate = revisionDate;
|
||||
c.DeletedDate = null;
|
||||
return new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_Restored, null);
|
||||
});
|
||||
foreach (var eventsBatch in events.Batch(100))
|
||||
{
|
||||
await _eventService.LogCipherEventsAsync(eventsBatch);
|
||||
|
@ -57,4 +57,6 @@ BEGIN
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
END
|
||||
|
||||
SELECT @UtcNow
|
||||
END
|
||||
|
@ -116,5 +116,46 @@ namespace Bit.Core.Test.Services
|
||||
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync(sharingUserId,
|
||||
Arg.Is<IEnumerable<Cipher>>(arg => arg.Except(ciphers).IsNullOrEmpty()));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineKnownUserCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e", "c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
|
||||
[InlineOrganizationCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
|
||||
public async Task RestoreAsync_UpdatesCipher(Guid restoringUserId, Cipher cipher, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICipherRepository>().GetCanEditByIdAsync(restoringUserId, cipher.Id).Returns(true);
|
||||
|
||||
var initialRevisionDate = new DateTime(1970, 1, 1, 0, 0, 0);
|
||||
cipher.DeletedDate = initialRevisionDate;
|
||||
cipher.RevisionDate = initialRevisionDate;
|
||||
|
||||
await sutProvider.Sut.RestoreAsync(cipher, restoringUserId, cipher.OrganizationId.HasValue);
|
||||
|
||||
Assert.Null(cipher.DeletedDate);
|
||||
Assert.NotEqual(initialRevisionDate, cipher.RevisionDate);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineKnownUserCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e", "c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
|
||||
public async Task RestoreManyAsync_UpdatesCiphers(Guid restoringUserId, IEnumerable<CipherDetails> ciphers,
|
||||
SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var previousRevisionDate = DateTime.UtcNow;
|
||||
foreach (var cipher in ciphers)
|
||||
{
|
||||
cipher.RevisionDate = previousRevisionDate;
|
||||
}
|
||||
|
||||
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
|
||||
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId)
|
||||
.Returns(revisionDate);
|
||||
|
||||
await sutProvider.Sut.RestoreManyAsync(ciphers, restoringUserId);
|
||||
|
||||
foreach (var cipher in ciphers)
|
||||
{
|
||||
Assert.Null(cipher.DeletedDate);
|
||||
Assert.Equal(revisionDate, cipher.RevisionDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,71 @@
|
||||
IF OBJECT_ID('[dbo].[Cipher_Restore]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[Cipher_Restore]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[Cipher_Restore]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId],
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [DeletedDate] IS NOT NULL
|
||||
AND [Id] IN (SELECT *
|
||||
FROM @Ids)
|
||||
|
||||
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[DeletedDate] = NULL,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id]
|
||||
FROM #Temp)
|
||||
|
||||
-- Bump orgs
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER
|
||||
DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR
|
||||
SELECT
|
||||
[OrganizationId]
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[OrganizationId] IS NOT NULL
|
||||
GROUP BY
|
||||
[OrganizationId]
|
||||
OPEN [OrgCursor]
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
WHILE @@FETCH_STATUS = 0 BEGIN
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
END
|
||||
CLOSE [OrgCursor]
|
||||
DEALLOCATE [OrgCursor]
|
||||
|
||||
-- Bump user
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
|
||||
SELECT @UtcNow
|
||||
END
|
||||
GO
|
Loading…
Reference in New Issue
Block a user