1
0
mirror of https://github.com/bitwarden/server synced 2025-12-24 20:23:21 +00:00

[PM-19151] [PM-19161] Innovation/archive/server (#5672)

* Added the ArchivedDate to cipher entity and response model
* Created migration scripts for sqlserver and ef core migration to add the ArchivedDate column

---------

Co-authored-by: gbubemismith <gsmithwalter@gmail.com>
Co-authored-by: SmithThe4th <gsmith@bitwarden.com>
Co-authored-by: Shane <smelton@bitwarden.com>
Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com>
Co-authored-by: jng <jng@bitwarden.com>
This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-09-12 13:24:30 -04:00
committed by GitHub
parent 18aed0bd79
commit 4e64d35f89
43 changed files with 10342 additions and 42 deletions

View File

@@ -71,7 +71,8 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
Manage = cu == null ? (cg != null && cg.Manage == true) : cu.Manage == true,
OrganizationUseTotp = o.UseTotp,
c.Reprompt,
c.Key
c.Key,
c.ArchivedDate
};
var query2 = from c in dbContext.Ciphers
@@ -94,7 +95,8 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
Manage = true,
OrganizationUseTotp = false,
c.Reprompt,
c.Key
c.Key,
c.ArchivedDate
};
var union = query.Union(query2).Select(c => new CipherDetails
@@ -115,7 +117,8 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
ViewPassword = c.ViewPassword,
Manage = c.Manage,
OrganizationUseTotp = c.OrganizationUseTotp,
Key = c.Key
Key = c.Key,
ArchivedDate = c.ArchivedDate
});
return union;
}

View File

@@ -230,7 +230,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId)
{
await ToggleCipherStates(ids, userId, CipherStateAction.HardDelete);
await ToggleDeleteCipherStatesAsync(ids, userId, CipherStateAction.HardDelete);
}
public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId)
@@ -508,7 +508,8 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
ViewPassword = true,
Manage = true,
OrganizationUseTotp = false,
Key = c.Key
Key = c.Key,
ArchivedDate = c.ArchivedDate,
};
}
@@ -751,9 +752,14 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
}
}
public async Task<DateTime> UnarchiveAsync(IEnumerable<Guid> ids, Guid userId)
{
return await ToggleArchiveCipherStatesAsync(ids, userId, CipherStateAction.Unarchive);
}
public async Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId)
{
return await ToggleCipherStates(ids, userId, CipherStateAction.Restore);
return await ToggleDeleteCipherStatesAsync(ids, userId, CipherStateAction.Restore);
}
public async Task<DateTime> RestoreByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId)
@@ -781,20 +787,25 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
}
}
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId)
public async Task<DateTime> ArchiveAsync(IEnumerable<Guid> ids, Guid userId)
{
await ToggleCipherStates(ids, userId, CipherStateAction.SoftDelete);
return await ToggleArchiveCipherStatesAsync(ids, userId, CipherStateAction.Archive);
}
private async Task<DateTime> ToggleCipherStates(IEnumerable<Guid> ids, Guid userId, CipherStateAction action)
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId)
{
static bool FilterDeletedDate(CipherStateAction action, CipherDetails ucd)
await ToggleDeleteCipherStatesAsync(ids, userId, CipherStateAction.SoftDelete);
}
private async Task<DateTime> ToggleArchiveCipherStatesAsync(IEnumerable<Guid> ids, Guid userId, CipherStateAction action)
{
static bool FilterArchivedDate(CipherStateAction action, CipherDetails ucd)
{
return action switch
{
CipherStateAction.Restore => ucd.DeletedDate != null,
CipherStateAction.SoftDelete => ucd.DeletedDate == null,
_ => true,
CipherStateAction.Unarchive => ucd.ArchivedDate != null,
CipherStateAction.Archive => ucd.ArchivedDate == null,
_ => true
};
}
@@ -802,8 +813,49 @@ 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()
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 utcNow = DateTime.UtcNow;
var cipherIdsToModify = query.Select(c => c.Id);
var cipherEntitiesToModify = dbContext.Ciphers.Where(x => cipherIdsToModify.Contains(x.Id));
await cipherEntitiesToModify.ForEachAsync(cipher =>
{
dbContext.Attach(cipher);
cipher.ArchivedDate = action == CipherStateAction.Unarchive ? null : utcNow;
cipher.RevisionDate = utcNow;
});
await dbContext.UserBumpAccountRevisionDateAsync(userId);
await dbContext.SaveChangesAsync();
return utcNow;
}
}
private async Task<DateTime> ToggleDeleteCipherStatesAsync(IEnumerable<Guid> ids, Guid userId, CipherStateAction action)
{
static bool FilterDeletedDate(CipherStateAction action, CipherDetails ucd)
{
return action switch
{
CipherStateAction.Restore => ucd.DeletedDate != null,
CipherStateAction.SoftDelete => ucd.DeletedDate == null,
_ => true
};
}
using (var scope = ServiceScopeFactory.CreateScope())
{
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 && FilterDeletedDate(action, ucd)
@@ -841,6 +893,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
}
await dbContext.UserBumpAccountRevisionDateAsync(userId);
await dbContext.SaveChangesAsync();
return utcNow;
}
}

View File

@@ -34,6 +34,7 @@ public class CipherDetailsQuery : IQuery<CipherDetails>
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;
}