1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-08 14:38:15 -05:00

[PS-1928] Cipher Collections Fix (#2462)

* Simplify UpdateCollectionsAsync

* Make final JOIN a LEFT JOIN
This commit is contained in:
Justin Baur 2022-12-02 17:04:01 -05:00 committed by GitHub
parent efe91fd0d8
commit 1652669667
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 59 deletions

View File

@ -484,7 +484,7 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
join o in context.Organizations join o in context.Organizations
on c.OrganizationId equals o.Id on c.OrganizationId equals o.Id
join ou in context.OrganizationUsers join ou in context.OrganizationUsers
on new { OrganizationId = o.Id, UserId = (Guid?)userId.Value } equals on new { OrganizationId = o.Id, UserId = userId } equals
new { ou.OrganizationId, ou.UserId } new { ou.OrganizationId, ou.UserId }
join cu in context.CollectionUsers join cu in context.CollectionUsers
on new { ou.AccessAll, CollectionId = c.Id, OrganizationUserId = ou.Id } equals on new { ou.AccessAll, CollectionId = c.Id, OrganizationUserId = ou.Id } equals
@ -499,7 +499,8 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
from g in g_g.DefaultIfEmpty() from g in g_g.DefaultIfEmpty()
join cg in context.CollectionGroups join cg in context.CollectionGroups
on new { g.AccessAll, CollectionId = c.Id, gu.GroupId } equals on new { g.AccessAll, CollectionId = c.Id, gu.GroupId } equals
new { AccessAll = false, cg.CollectionId, cg.GroupId } new { AccessAll = false, cg.CollectionId, cg.GroupId } into cg_g
from cg in cg_g.DefaultIfEmpty()
where o.Id == organizationId && where o.Id == organizationId &&
o.Enabled && o.Enabled &&
ou.Status == OrganizationUserStatusType.Confirmed && ou.Status == OrganizationUserStatusType.Confirmed &&

View File

@ -75,64 +75,64 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
var dbContext = GetDatabaseContext(scope); var dbContext = GetDatabaseContext(scope);
var organizationId = (await dbContext.Ciphers.FindAsync(cipherId)).OrganizationId;
var availableCollectionsCte = from c in dbContext.Collections var organizationId = await dbContext.Ciphers
join o in dbContext.Organizations .Where(c => c.Id == cipherId)
on c.OrganizationId equals o.Id .Select(c => c.OrganizationId)
join ou in dbContext.OrganizationUsers .FirstAsync();
on o.Id equals ou.OrganizationId
where ou.UserId == userId var availableCollections = await (from c in dbContext.Collections
join cu in dbContext.CollectionUsers join o in dbContext.Organizations on c.OrganizationId equals o.Id
on ou.Id equals cu.OrganizationUserId into cu_g join ou in dbContext.OrganizationUsers
from cu in cu_g.DefaultIfEmpty() on new { OrganizationId = o.Id, UserId = (Guid?)userId } equals
where !ou.AccessAll && cu.CollectionId == c.Id new { ou.OrganizationId, ou.UserId }
join gu in dbContext.GroupUsers join cu in dbContext.CollectionUsers
on ou.Id equals gu.OrganizationUserId into gu_g on new { ou.AccessAll, CollectionId = c.Id, OrganizationUserId = ou.Id } equals
from gu in gu_g.DefaultIfEmpty() new { AccessAll = false, cu.CollectionId, cu.OrganizationUserId } into cu_g
where cu.CollectionId == null && !ou.AccessAll from cu in cu_g.DefaultIfEmpty()
join g in dbContext.Groups join gu in dbContext.GroupUsers
on gu.GroupId equals g.Id into g_g on new { CollectionId = (Guid?)cu.CollectionId, ou.AccessAll, OrganizationUserId = ou.Id } equals
from g in g_g.DefaultIfEmpty() new { CollectionId = (Guid?)null, AccessAll = false, gu.OrganizationUserId } into gu_g
join cg in dbContext.CollectionGroups from gu in gu_g.DefaultIfEmpty()
on gu.GroupId equals cg.GroupId into cg_g join g in dbContext.Groups on gu.GroupId equals g.Id into g_g
from cg in cg_g.DefaultIfEmpty() from g in g_g.DefaultIfEmpty()
where !g.AccessAll && cg.CollectionId == c.Id && join cg in dbContext.CollectionGroups
(o.Id == organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed && ( on new { g.AccessAll, CollectionId = c.Id, gu.GroupId } equals
ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly)) new { AccessAll = false, cg.CollectionId, cg.GroupId } into cg_g
select new { c, o, cu, gu, g, cg }; from cg in cg_g.DefaultIfEmpty()
var target = from cc in dbContext.CollectionCiphers where o.Id == organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed
where cc.CipherId == cipherId && (ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly)
select new { cc.CollectionId, cc.CipherId }; select c.Id).ToListAsync();
var source = collectionIds.Select(x => new { CollectionId = x, CipherId = cipherId });
var merge1 = from t in target var collectionCiphers = await (from cc in dbContext.CollectionCiphers
join s in source where cc.CipherId == cipherId
on t.CollectionId equals s.CollectionId into s_g select cc).ToListAsync();
from s in s_g.DefaultIfEmpty()
where t.CipherId == s.CipherId foreach (var requestedCollectionId in collectionIds)
select new { t, s }; {
var merge2 = from s in source // I don't totally agree with t.CipherId = cipherId here because that should have been guarenteed by
join t in target // the WHERE above but the SQL Server CTE has it
on s.CollectionId equals t.CollectionId into t_g var existingCollectionCipher = collectionCiphers
from t in t_g.DefaultIfEmpty() .FirstOrDefault(t => t.CollectionId == requestedCollectionId && t.CipherId == cipherId);
where t.CipherId == s.CipherId // requestedCollectionId = SOURCE
select new { t, s }; // existingCollectionCipher = TARGET
var union = merge1.Union(merge2).Distinct();
var insert = union // They have to want it selected and it has to exist
.Where(x => x.t == null && collectionIds.Contains(x.s.CollectionId)) if (existingCollectionCipher == null && availableCollections.Contains(requestedCollectionId))
.Select(x => new Models.CollectionCipher
{ {
CollectionId = x.s.CollectionId, // WHEN NOT MATCHED BY TARGET AND ...
CipherId = x.s.CipherId, dbContext.CollectionCiphers.Add(new Models.CollectionCipher
}); {
var delete = union CollectionId = requestedCollectionId,
.Where(x => x.s == null && x.t.CipherId == cipherId && collectionIds.Contains(x.t.CollectionId)) CipherId = cipherId,
.Select(x => new Models.CollectionCipher });
{ }
CollectionId = x.t.CollectionId,
CipherId = x.t.CipherId, // If it has fallen to here it's requested but not actually available to don't add anything
}); }
await dbContext.AddRangeAsync(insert);
dbContext.RemoveRange(delete); // Now we need to remove collection ciphers that are no longer requested
dbContext.CollectionCiphers.RemoveRange(collectionCiphers.Where(cc => !collectionIds.Contains(cc.CollectionId) && cc.CipherId == cipherId));
if (organizationId.HasValue) if (organizationId.HasValue)
{ {