mirror of
https://github.com/bitwarden/server
synced 2025-12-15 15:53:59 +00:00
update to cipher archive
This commit is contained in:
@@ -18,6 +18,7 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
||||
|
||||
public virtual IQueryable<CipherDetails> Run(DatabaseContext dbContext)
|
||||
{
|
||||
var userId = _userId;
|
||||
var query = from c in dbContext.Ciphers
|
||||
|
||||
join ou in dbContext.OrganizationUsers
|
||||
@@ -49,7 +50,15 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
||||
join cg in dbContext.CollectionGroups
|
||||
on new { cc.CollectionId, gu.GroupId } equals
|
||||
new { cg.CollectionId, cg.GroupId } into cg_g
|
||||
|
||||
join ca in dbContext.CipherArchives
|
||||
on c.Id equals ca.CipherId
|
||||
into caGroup
|
||||
|
||||
from cg in cg_g.DefaultIfEmpty()
|
||||
from ca in caGroup
|
||||
.Where(a => userId.HasValue && a.UserId == userId.Value)
|
||||
.DefaultIfEmpty()
|
||||
|
||||
where (cu == null ? (Guid?)null : cu.CollectionId) != null || (cg == null ? (Guid?)null : cg.CollectionId) != null
|
||||
|
||||
@@ -72,11 +81,19 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
||||
OrganizationUseTotp = o.UseTotp,
|
||||
c.Reprompt,
|
||||
c.Key,
|
||||
c.ArchivedDate
|
||||
ArchivedDate = (DateTime?)ca.ArchivedDate
|
||||
};
|
||||
|
||||
var query2 = from c in dbContext.Ciphers
|
||||
where c.UserId == _userId
|
||||
|
||||
join ca in dbContext.CipherArchives
|
||||
on c.Id equals ca.CipherId
|
||||
into caGroup
|
||||
from ca in caGroup
|
||||
.Where(a => userId.HasValue && a.UserId == userId.Value)
|
||||
.DefaultIfEmpty()
|
||||
|
||||
select new
|
||||
{
|
||||
c.Id,
|
||||
@@ -96,7 +113,7 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
||||
OrganizationUseTotp = false,
|
||||
c.Reprompt,
|
||||
c.Key,
|
||||
c.ArchivedDate
|
||||
ArchivedDate = (DateTime?)ca.ArchivedDate
|
||||
};
|
||||
|
||||
var union = query.Union(query2).Select(c => new CipherDetails
|
||||
|
||||
@@ -786,7 +786,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
{
|
||||
CipherStateAction.Unarchive => ucd.ArchivedDate != null,
|
||||
CipherStateAction.Archive => ucd.ArchivedDate == null,
|
||||
_ => true
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -794,21 +794,65 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var userCipherDetailsQuery = new UserCipherDetailsQuery(userId);
|
||||
var cipherEntitiesToCheck = await dbContext.Ciphers.Where(c => ids.Contains(c.Id)).ToListAsync();
|
||||
var query = from ucd in await userCipherDetailsQuery.Run(dbContext).ToListAsync()
|
||||
join c in cipherEntitiesToCheck
|
||||
on ucd.Id equals c.Id
|
||||
where ucd.Edit && FilterArchivedDate(action, ucd)
|
||||
select c;
|
||||
|
||||
var userCipherDetails = await userCipherDetailsQuery
|
||||
.Run(dbContext)
|
||||
.Where(ucd => ids.Contains(ucd.Id) && ucd.Edit)
|
||||
.ToListAsync();
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
var cipherIdsToModify = query.Select(c => c.Id);
|
||||
var cipherEntitiesToModify = dbContext.Ciphers.Where(x => cipherIdsToModify.Contains(x.Id));
|
||||
|
||||
var cipherIdsToModify = userCipherDetails
|
||||
.Where(ucd => FilterArchivedDate(action, ucd))
|
||||
.Select(ucd => ucd.Id)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (!cipherIdsToModify.Any())
|
||||
{
|
||||
return utcNow;
|
||||
}
|
||||
|
||||
if (action == CipherStateAction.Archive)
|
||||
{
|
||||
var existingArchiveCipherIds = await dbContext.CipherArchives
|
||||
.Where(ca => ca.UserId == userId && cipherIdsToModify.Contains(ca.CipherId))
|
||||
.Select(ca => ca.CipherId)
|
||||
.ToListAsync();
|
||||
|
||||
var cipherIdsToArchive = cipherIdsToModify
|
||||
.Except(existingArchiveCipherIds)
|
||||
.ToList();
|
||||
|
||||
if (cipherIdsToArchive.Any())
|
||||
{
|
||||
var archives = cipherIdsToArchive.Select(id => new CipherArchive
|
||||
{
|
||||
CipherId = id,
|
||||
UserId = userId,
|
||||
ArchivedDate = utcNow,
|
||||
});
|
||||
|
||||
await dbContext.CipherArchives.AddRangeAsync(archives);
|
||||
}
|
||||
}
|
||||
else if (action == CipherStateAction.Unarchive)
|
||||
{
|
||||
var archivesToRemove = await dbContext.CipherArchives
|
||||
.Where(ca => ca.UserId == userId && cipherIdsToModify.Contains(ca.CipherId))
|
||||
.ToListAsync();
|
||||
|
||||
if (archivesToRemove.Count > 0)
|
||||
{
|
||||
dbContext.CipherArchives.RemoveRange(archivesToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the behavior that archive/unarchive "touches" the cipher row for sync.
|
||||
var cipherEntitiesToModify = dbContext.Ciphers.Where(c => cipherIdsToModify.Contains(c.Id));
|
||||
await cipherEntitiesToModify.ForEachAsync(cipher =>
|
||||
{
|
||||
dbContext.Attach(cipher);
|
||||
cipher.ArchivedDate = action == CipherStateAction.Unarchive ? null : utcNow;
|
||||
cipher.RevisionDate = utcNow;
|
||||
});
|
||||
|
||||
@@ -819,6 +863,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<DateTime> ToggleDeleteCipherStatesAsync(IEnumerable<Guid> ids, Guid userId, CipherStateAction action)
|
||||
{
|
||||
static bool FilterDeletedDate(CipherStateAction action, CipherDetails ucd)
|
||||
|
||||
@@ -9,33 +9,72 @@ public class CipherDetailsQuery : IQuery<CipherDetails>
|
||||
{
|
||||
private readonly Guid? _userId;
|
||||
private readonly bool _ignoreFolders;
|
||||
|
||||
public CipherDetailsQuery(Guid? userId, bool ignoreFolders = false)
|
||||
{
|
||||
_userId = userId;
|
||||
_ignoreFolders = ignoreFolders;
|
||||
}
|
||||
|
||||
public virtual IQueryable<CipherDetails> Run(DatabaseContext dbContext)
|
||||
{
|
||||
var query = from c in dbContext.Ciphers
|
||||
select new CipherDetails
|
||||
{
|
||||
Id = c.Id,
|
||||
UserId = c.UserId,
|
||||
OrganizationId = c.OrganizationId,
|
||||
Type = c.Type,
|
||||
Data = c.Data,
|
||||
Attachments = c.Attachments,
|
||||
CreationDate = c.CreationDate,
|
||||
RevisionDate = c.RevisionDate,
|
||||
DeletedDate = c.DeletedDate,
|
||||
Reprompt = c.Reprompt,
|
||||
Key = c.Key,
|
||||
Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.ToLowerInvariant().Contains($"\"{_userId}\":true"),
|
||||
FolderId = (_ignoreFolders || !_userId.HasValue || c.Folders == null || !c.Folders.ToLowerInvariant().Contains(_userId.Value.ToString())) ?
|
||||
null :
|
||||
CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(c.Folders)[_userId.Value],
|
||||
ArchivedDate = c.ArchivedDate,
|
||||
};
|
||||
return query;
|
||||
// No user context: we can’t resolve per-user favorites/folders/archive.
|
||||
if (!_userId.HasValue)
|
||||
{
|
||||
var query = from c in dbContext.Ciphers
|
||||
select new CipherDetails
|
||||
{
|
||||
Id = c.Id,
|
||||
UserId = c.UserId,
|
||||
OrganizationId = c.OrganizationId,
|
||||
Type = c.Type,
|
||||
Data = c.Data,
|
||||
Attachments = c.Attachments,
|
||||
CreationDate = c.CreationDate,
|
||||
RevisionDate = c.RevisionDate,
|
||||
DeletedDate = c.DeletedDate,
|
||||
Reprompt = c.Reprompt,
|
||||
Key = c.Key,
|
||||
Favorite = false,
|
||||
FolderId = null,
|
||||
ArchivedDate = null,
|
||||
};
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
var userId = _userId.Value;
|
||||
|
||||
var queryWithArchive =
|
||||
from c in dbContext.Ciphers
|
||||
join ca in dbContext.CipherArchives
|
||||
on new { CipherId = c.Id, UserId = userId }
|
||||
equals new { CipherId = ca.CipherId, ca.UserId }
|
||||
into caGroup
|
||||
from ca in caGroup.DefaultIfEmpty()
|
||||
select new CipherDetails
|
||||
{
|
||||
Id = c.Id,
|
||||
UserId = c.UserId,
|
||||
OrganizationId = c.OrganizationId,
|
||||
Type = c.Type,
|
||||
Data = c.Data,
|
||||
Attachments = c.Attachments,
|
||||
CreationDate = c.CreationDate,
|
||||
RevisionDate = c.RevisionDate,
|
||||
DeletedDate = c.DeletedDate,
|
||||
Reprompt = c.Reprompt,
|
||||
Key = c.Key,
|
||||
Favorite = c.Favorites != null &&
|
||||
c.Favorites.ToLowerInvariant().Contains($"\"{userId}\":true"),
|
||||
FolderId = (_ignoreFolders ||
|
||||
c.Folders == null ||
|
||||
!c.Folders.ToLowerInvariant().Contains(userId.ToString()))
|
||||
? null
|
||||
: CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(c.Folders)[userId],
|
||||
ArchivedDate = ca.ArchivedDate,
|
||||
};
|
||||
|
||||
return queryWithArchive;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,8 @@ BEGIN
|
||||
*
|
||||
FROM
|
||||
[dbo].[UserCipherDetails](@UserId)
|
||||
LEFT JOIN [dbo].[CipherArchive] ca
|
||||
ON ca.CipherId = c.Id
|
||||
AND ca.UserId = @UserId
|
||||
WHERE
|
||||
END
|
||||
@@ -12,6 +12,9 @@ BEGIN
|
||||
0 [OrganizationUseTotp]
|
||||
FROM
|
||||
[dbo].[CipherDetails](@UserId)
|
||||
LEFT JOIN [dbo].[CipherArchive] ca
|
||||
ON ca.CipherId = c.Id
|
||||
AND ca.UserId = @UserId
|
||||
WHERE
|
||||
[UserId] = @UserId
|
||||
END
|
||||
@@ -1,39 +1,31 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_Archive]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
@Ids dbo.GuidIdArray READONLY,
|
||||
@UserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [ArchivedDate] IS NULL
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[ArchivedDate] = @UtcNow,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
WITH CipherIdsToArchive AS
|
||||
(
|
||||
SELECT DISTINCT C.Id
|
||||
FROM [dbo].[Cipher] C
|
||||
INNER JOIN @Ids I ON C.Id = I.[Id]
|
||||
WHERE (C.[UserId] = @UserId)
|
||||
)
|
||||
INSERT INTO [dbo].[CipherArchive] (CipherId, UserId, ArchivedDate)
|
||||
SELECT Cta.Id, @UserId, @UtcNow
|
||||
FROM CipherIdsToArchive Cta
|
||||
WHERE NOT EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM [dbo].[CipherArchive] Ca
|
||||
WHERE Ca.CipherId = Cta.Id
|
||||
AND Ca.UserId = @UserId
|
||||
);
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
|
||||
SELECT @UtcNow
|
||||
END
|
||||
|
||||
@@ -1,39 +1,18 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_Unarchive]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
@Ids dbo.GuidIdArray READONLY,
|
||||
@UserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [ArchivedDate] IS NOT NULL
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[ArchivedDate] = NULL,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
DELETE Ca
|
||||
FROM [dbo].[CipherArchive] Ca
|
||||
INNER JOIN @Ids I ON Ca.CipherId = I.[Id]
|
||||
WHERE Ca.UserId = @UserId
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
|
||||
SELECT @UtcNow
|
||||
END
|
||||
SELECT @UtcNow;
|
||||
END
|
||||
@@ -11,8 +11,7 @@
|
||||
@RevisionDate DATETIME2(7),
|
||||
@DeletedDate DATETIME2(7),
|
||||
@Reprompt TINYINT,
|
||||
@Key VARCHAR(MAX) = NULL,
|
||||
@ArchivedDate DATETIME2(7) = NULL
|
||||
@Key VARCHAR(MAX) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@@ -31,8 +30,7 @@ BEGIN
|
||||
[RevisionDate] = @RevisionDate,
|
||||
[DeletedDate] = @DeletedDate,
|
||||
[Reprompt] = @Reprompt,
|
||||
[Key] = @Key,
|
||||
[ArchivedDate] = @ArchivedDate
|
||||
[Key] = @Key
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ ADD CONSTRAINT [FK_CipherArchive_User]
|
||||
ON DELETE CASCADE;
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[CipherArchive]
|
||||
ADD CONSTRAINT [UX_CipherArchive_CipherId_UserId]
|
||||
UNIQUE (CipherId, UserId);
|
||||
GO
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_CipherArchive_UserId]
|
||||
ON [dbo].[CipherArchive]([UserId]);
|
||||
GO
|
||||
|
||||
Reference in New Issue
Block a user