mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01:00
[AC-1344] Provider users unable to bulk restore vault items for client organizations (#2871)
* [AC-1344] Added method PutRestoreManyAdmin to CiphersController and refactored PutRestoreMany * [AC-1344] Fixed unit test * [AC-1344] Removed comment * [AC-1344] Fixed sql.csproj * [AC-1344] Added check for empty or null array; added more unit tests
This commit is contained in:
parent
4a110ad135
commit
d94a54516e
@ -451,7 +451,7 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("restore")]
|
[HttpPut("restore")]
|
||||||
public async Task<ListResponseModel<CipherResponseModel>> PutRestoreMany([FromBody] CipherBulkRestoreRequestModel model)
|
public async Task<ListResponseModel<CipherMiniResponseModel>> PutRestoreMany([FromBody] CipherBulkRestoreRequestModel model)
|
||||||
{
|
{
|
||||||
if (!_globalSettings.SelfHosted && model.Ids.Count() > 500)
|
if (!_globalSettings.SelfHosted && model.Ids.Count() > 500)
|
||||||
{
|
{
|
||||||
@ -461,12 +461,30 @@ public class CiphersController : Controller
|
|||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipherIdsToRestore = new HashSet<Guid>(model.Ids.Select(i => new Guid(i)));
|
var cipherIdsToRestore = new HashSet<Guid>(model.Ids.Select(i => new Guid(i)));
|
||||||
|
|
||||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId);
|
var restoredCiphers = await _cipherService.RestoreManyAsync(cipherIdsToRestore, userId);
|
||||||
var restoringCiphers = ciphers.Where(c => cipherIdsToRestore.Contains(c.Id) && c.Edit);
|
var responses = restoredCiphers.Select(c => new CipherMiniResponseModel(c, _globalSettings, c.OrganizationUseTotp));
|
||||||
|
return new ListResponseModel<CipherMiniResponseModel>(responses);
|
||||||
|
}
|
||||||
|
|
||||||
await _cipherService.RestoreManyAsync(restoringCiphers, userId);
|
[HttpPut("restore-admin")]
|
||||||
var responses = restoringCiphers.Select(c => new CipherResponseModel(c, _globalSettings));
|
public async Task<ListResponseModel<CipherMiniResponseModel>> PutRestoreManyAdmin([FromBody] CipherBulkRestoreRequestModel model)
|
||||||
return new ListResponseModel<CipherResponseModel>(responses);
|
{
|
||||||
|
if (!_globalSettings.SelfHosted && model.Ids.Count() > 500)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You can only restore up to 500 items at a time.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model == null || model.OrganizationId == default || !await _currentContext.EditAnyCollection(model.OrganizationId))
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
var cipherIdsToRestore = new HashSet<Guid>(model.Ids.Select(i => new Guid(i)));
|
||||||
|
|
||||||
|
var restoredCiphers = await _cipherService.RestoreManyAsync(cipherIdsToRestore, userId, model.OrganizationId, true);
|
||||||
|
var responses = restoredCiphers.Select(c => new CipherMiniResponseModel(c, _globalSettings, c.OrganizationUseTotp));
|
||||||
|
return new ListResponseModel<CipherMiniResponseModel>(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("move")]
|
[HttpPut("move")]
|
||||||
|
@ -291,6 +291,7 @@ public class CipherBulkRestoreRequestModel
|
|||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public IEnumerable<string> Ids { get; set; }
|
public IEnumerable<string> Ids { get; set; }
|
||||||
|
public Guid OrganizationId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CipherBulkMoveRequestModel
|
public class CipherBulkMoveRequestModel
|
||||||
|
@ -36,5 +36,6 @@ public interface ICipherRepository : IRepository<Cipher, Guid>
|
|||||||
Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||||
Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||||
Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId);
|
Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId);
|
||||||
|
Task<DateTime> RestoreByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||||
Task DeleteDeletedAsync(DateTime deletedDateBefore);
|
Task DeleteDeletedAsync(DateTime deletedDateBefore);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ public interface ICipherService
|
|||||||
Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
||||||
Task SoftDeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId = null, 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 RestoreAsync(Cipher cipher, Guid restoringUserId, bool orgAdmin = false);
|
||||||
Task RestoreManyAsync(IEnumerable<CipherDetails> ciphers, Guid restoringUserId);
|
Task<ICollection<CipherOrganizationDetails>> RestoreManyAsync(IEnumerable<Guid> cipherIds, Guid restoringUserId, Guid? organizationId = null, bool orgAdmin = false);
|
||||||
Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachmentId);
|
Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachmentId);
|
||||||
Task<AttachmentResponseData> GetAttachmentDownloadDataAsync(Cipher cipher, string attachmentId);
|
Task<AttachmentResponseData> GetAttachmentDownloadDataAsync(Cipher cipher, string attachmentId);
|
||||||
Task<bool> ValidateCipherAttachmentFile(Cipher cipher, CipherAttachment.MetaData attachmentData);
|
Task<bool> ValidateCipherAttachmentFile(Cipher cipher, CipherAttachment.MetaData attachmentData);
|
||||||
|
@ -898,13 +898,33 @@ public class CipherService : ICipherService
|
|||||||
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
|
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RestoreManyAsync(IEnumerable<CipherDetails> ciphers, Guid restoringUserId)
|
public async Task<ICollection<CipherOrganizationDetails>> RestoreManyAsync(IEnumerable<Guid> cipherIds, Guid restoringUserId, Guid? organizationId = null, bool orgAdmin = false)
|
||||||
{
|
{
|
||||||
var revisionDate = await _cipherRepository.RestoreAsync(ciphers.Select(c => c.Id), restoringUserId);
|
if (cipherIds == null || !cipherIds.Any())
|
||||||
|
|
||||||
var events = ciphers.Select(c =>
|
|
||||||
{
|
{
|
||||||
c.RevisionDate = revisionDate;
|
return new List<CipherOrganizationDetails>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var cipherIdsSet = new HashSet<Guid>(cipherIds);
|
||||||
|
var restoringCiphers = new List<CipherOrganizationDetails>();
|
||||||
|
DateTime? revisionDate;
|
||||||
|
|
||||||
|
if (orgAdmin && organizationId.HasValue)
|
||||||
|
{
|
||||||
|
var ciphers = await _cipherRepository.GetManyOrganizationDetailsByOrganizationIdAsync(organizationId.Value);
|
||||||
|
restoringCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id)).ToList();
|
||||||
|
revisionDate = await _cipherRepository.RestoreByIdsOrganizationIdAsync(restoringCiphers.Select(c => c.Id), organizationId.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var ciphers = await _cipherRepository.GetManyByUserIdAsync(restoringUserId);
|
||||||
|
restoringCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(c => (CipherOrganizationDetails)c).ToList();
|
||||||
|
revisionDate = await _cipherRepository.RestoreAsync(restoringCiphers.Select(c => c.Id), restoringUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var events = restoringCiphers.Select(c =>
|
||||||
|
{
|
||||||
|
c.RevisionDate = revisionDate.Value;
|
||||||
c.DeletedDate = null;
|
c.DeletedDate = null;
|
||||||
return new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_Restored, null);
|
return new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_Restored, null);
|
||||||
});
|
});
|
||||||
@ -915,6 +935,8 @@ public class CipherService : ICipherService
|
|||||||
|
|
||||||
// push
|
// push
|
||||||
await _pushService.PushSyncCiphersAsync(restoringUserId);
|
await _pushService.PushSyncCiphersAsync(restoringUserId);
|
||||||
|
|
||||||
|
return restoringCiphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(IEnumerable<CipherOrganizationDetails>, Dictionary<Guid, IGrouping<Guid, CollectionCipher>>)> GetOrganizationCiphers(Guid userId, Guid organizationId)
|
public async Task<(IEnumerable<CipherOrganizationDetails>, Dictionary<Guid, IGrouping<Guid, CollectionCipher>>)> GetOrganizationCiphers(Guid userId, Guid organizationId)
|
||||||
|
@ -669,6 +669,19 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<DateTime> RestoreByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId)
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.ExecuteScalarAsync<DateTime>(
|
||||||
|
$"[{Schema}].[Cipher_RestoreByIdsOrganizationId]",
|
||||||
|
new { Ids = ids.ToGuidIdArrayTVP(), OrganizationId = organizationId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteDeletedAsync(DateTime deletedDateBefore)
|
public async Task DeleteDeletedAsync(DateTime deletedDateBefore)
|
||||||
{
|
{
|
||||||
using (var connection = new SqlConnection(ConnectionString))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
@ -625,6 +625,31 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
|||||||
return await ToggleCipherStates(ids, userId, CipherStateAction.Restore);
|
return await ToggleCipherStates(ids, userId, CipherStateAction.Restore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<DateTime> RestoreByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId)
|
||||||
|
{
|
||||||
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
var ciphers = from c in dbContext.Ciphers
|
||||||
|
where c.OrganizationId == organizationId &&
|
||||||
|
ids.Contains(c.Id)
|
||||||
|
select c;
|
||||||
|
|
||||||
|
await ciphers.ForEachAsync(cipher =>
|
||||||
|
{
|
||||||
|
dbContext.Attach(cipher);
|
||||||
|
cipher.DeletedDate = null;
|
||||||
|
cipher.RevisionDate = utcNow;
|
||||||
|
});
|
||||||
|
|
||||||
|
await OrganizationUpdateStorage(organizationId);
|
||||||
|
await dbContext.UserBumpAccountRevisionDateByOrganizationIdAsync(organizationId);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
return utcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
||||||
{
|
{
|
||||||
await ToggleCipherStates(ids, userId, CipherStateAction.SoftDelete);
|
await ToggleCipherStates(ids, userId, CipherStateAction.SoftDelete);
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[Cipher_RestoreByIdsOrganizationId]
|
||||||
|
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||||
|
@OrganizationId AS UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
IF (SELECT COUNT(1) FROM @Ids) < 1
|
||||||
|
BEGIN
|
||||||
|
RETURN(-1)
|
||||||
|
END
|
||||||
|
|
||||||
|
-- Delete ciphers
|
||||||
|
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||||
|
UPDATE
|
||||||
|
[dbo].[Cipher]
|
||||||
|
SET
|
||||||
|
[DeletedDate] = NULL,
|
||||||
|
[RevisionDate] = @UtcNow
|
||||||
|
WHERE
|
||||||
|
[Id] IN (SELECT * FROM @Ids)
|
||||||
|
AND OrganizationId = @OrganizationId
|
||||||
|
|
||||||
|
-- Cleanup organization
|
||||||
|
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||||
|
|
||||||
|
SELECT @UtcNow
|
||||||
|
END
|
@ -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;
|
||||||
@ -601,21 +602,23 @@ public class CipherServiceTests
|
|||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task RestoreManyAsync_UpdatesCiphers(IEnumerable<CipherDetails> ciphers,
|
public async Task RestoreManyAsync_UpdatesCiphers(ICollection<CipherDetails> ciphers,
|
||||||
SutProvider<CipherService> sutProvider)
|
SutProvider<CipherService> sutProvider)
|
||||||
{
|
{
|
||||||
|
var cipherIds = ciphers.Select(c => c.Id).ToArray();
|
||||||
var restoringUserId = ciphers.First().UserId.Value;
|
var restoringUserId = ciphers.First().UserId.Value;
|
||||||
var previousRevisionDate = DateTime.UtcNow;
|
var previousRevisionDate = DateTime.UtcNow;
|
||||||
foreach (var cipher in ciphers)
|
foreach (var cipher in ciphers)
|
||||||
{
|
{
|
||||||
|
cipher.Edit = true;
|
||||||
cipher.RevisionDate = previousRevisionDate;
|
cipher.RevisionDate = previousRevisionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICipherRepository>().GetManyByUserIdAsync(restoringUserId).Returns(ciphers);
|
||||||
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
|
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
|
||||||
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId)
|
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId).Returns(revisionDate);
|
||||||
.Returns(revisionDate);
|
|
||||||
|
|
||||||
await sutProvider.Sut.RestoreManyAsync(ciphers, restoringUserId);
|
await sutProvider.Sut.RestoreManyAsync(cipherIds, restoringUserId);
|
||||||
|
|
||||||
foreach (var cipher in ciphers)
|
foreach (var cipher in ciphers)
|
||||||
{
|
{
|
||||||
@ -624,6 +627,58 @@ public class CipherServiceTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RestoreManyAsync_WithOrgAdmin_UpdatesCiphers(Guid organizationId, ICollection<CipherOrganizationDetails> ciphers,
|
||||||
|
SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var cipherIds = ciphers.Select(c => c.Id).ToArray();
|
||||||
|
var restoringUserId = ciphers.First().UserId.Value;
|
||||||
|
var previousRevisionDate = DateTime.UtcNow;
|
||||||
|
foreach (var cipher in ciphers)
|
||||||
|
{
|
||||||
|
cipher.RevisionDate = previousRevisionDate;
|
||||||
|
cipher.OrganizationId = organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICipherRepository>().GetManyOrganizationDetailsByOrganizationIdAsync(organizationId).Returns(ciphers);
|
||||||
|
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
|
||||||
|
sutProvider.GetDependency<ICipherRepository>().RestoreByIdsOrganizationIdAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.All(i => cipherIds.Contains(i))), organizationId).Returns(revisionDate);
|
||||||
|
|
||||||
|
await sutProvider.Sut.RestoreManyAsync(cipherIds, restoringUserId, organizationId, true);
|
||||||
|
|
||||||
|
foreach (var cipher in ciphers)
|
||||||
|
{
|
||||||
|
Assert.Null(cipher.DeletedDate);
|
||||||
|
Assert.Equal(revisionDate, cipher.RevisionDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogCipherEventsAsync(Arg.Is<IEnumerable<Tuple<Cipher, EventType, DateTime?>>>(events => events.All(e => cipherIds.Contains(e.Item1.Id))));
|
||||||
|
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncCiphersAsync(restoringUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RestoreManyAsync_WithEmptyCipherIdsArray_DoesNothing(Guid restoringUserId,
|
||||||
|
SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var cipherIds = Array.Empty<Guid>();
|
||||||
|
|
||||||
|
await sutProvider.Sut.RestoreManyAsync(cipherIds, restoringUserId);
|
||||||
|
|
||||||
|
await AssertNoActionsAsync(sutProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RestoreManyAsync_WithNullCipherIdsArray_DoesNothing(Guid restoringUserId,
|
||||||
|
SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
await sutProvider.Sut.RestoreManyAsync(null, restoringUserId);
|
||||||
|
|
||||||
|
await AssertNoActionsAsync(sutProvider);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ShareManyAsync_FreeOrgWithAttachment_Throws(SutProvider<CipherService> sutProvider,
|
public async Task ShareManyAsync_FreeOrgWithAttachment_Throws(SutProvider<CipherService> sutProvider,
|
||||||
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
|
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
|
||||||
@ -667,4 +722,15 @@ public class CipherServiceTests
|
|||||||
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync(sharingUserId,
|
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync(sharingUserId,
|
||||||
Arg.Is<IEnumerable<Cipher>>(arg => arg.Except(ciphers).IsNullOrEmpty()));
|
Arg.Is<IEnumerable<Cipher>>(arg => arg.Except(ciphers).IsNullOrEmpty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task AssertNoActionsAsync(SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().GetManyOrganizationDetailsByOrganizationIdAsync(default);
|
||||||
|
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default);
|
||||||
|
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default);
|
||||||
|
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushSyncCiphersAsync(default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
CREATE OR ALTER PROCEDURE [dbo].[Cipher_RestoreByIdsOrganizationId]
|
||||||
|
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||||
|
@OrganizationId AS UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
IF (SELECT COUNT(1) FROM @Ids) < 1
|
||||||
|
BEGIN
|
||||||
|
RETURN(-1)
|
||||||
|
END
|
||||||
|
|
||||||
|
-- Delete ciphers
|
||||||
|
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||||
|
UPDATE
|
||||||
|
[dbo].[Cipher]
|
||||||
|
SET
|
||||||
|
[DeletedDate] = NULL,
|
||||||
|
[RevisionDate] = @UtcNow
|
||||||
|
WHERE
|
||||||
|
[Id] IN (SELECT * FROM @Ids)
|
||||||
|
AND OrganizationId = @OrganizationId
|
||||||
|
|
||||||
|
-- Cleanup organization
|
||||||
|
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
|
||||||
|
|
||||||
|
SELECT @UtcNow
|
||||||
|
END
|
||||||
|
GO
|
Loading…
Reference in New Issue
Block a user