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:
parent
efe91fd0d8
commit
1652669667
@ -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 &&
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user