1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[AC-1330] [AC-1816] Deprecate AccessAll in CollectionCipher sprocs (#3480)

This commit is contained in:
Thomas Rittson 2023-12-06 11:10:39 +10:00 committed by GitHub
parent 59879f913b
commit dbf8907bfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 582 additions and 42 deletions

View File

@ -106,7 +106,7 @@ public class CiphersController : Controller
throw new NotFoundException();
}
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id);
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id, UseFlexibleCollections);
return new CipherDetailsResponseModel(cipher, _globalSettings, collectionCiphers);
}
@ -120,7 +120,7 @@ public class CiphersController : Controller
Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict = null;
if (hasOrgs)
{
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(userId);
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(userId, UseFlexibleCollections);
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
}
@ -189,7 +189,7 @@ public class CiphersController : Controller
ValidateClientVersionForItemLevelEncryptionSupport(cipher);
ValidateClientVersionForFido2CredentialSupport(cipher);
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList();
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id, UseFlexibleCollections)).Select(c => c.CollectionId).ToList();
var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ?
(Guid?)null : new Guid(model.OrganizationId);
if (cipher.OrganizationId != modelOrgId)
@ -220,7 +220,7 @@ public class CiphersController : Controller
throw new NotFoundException();
}
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList();
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id, UseFlexibleCollections)).Select(c => c.CollectionId).ToList();
// object cannot be a descendant of CipherDetails, so let's clone it.
var cipherClone = model.ToCipher(cipher).Clone();
await _cipherService.SaveAsync(cipherClone, userId, model.LastKnownRevisionDate, collectionIds, true, false);

View File

@ -95,7 +95,7 @@ public class SyncController : Controller
if (hasEnabledOrgs)
{
collections = await _collectionRepository.GetManyByUserIdAsync(user.Id);
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(user.Id);
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(user.Id, UseFlexibleCollections);
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
}

View File

@ -4,11 +4,11 @@ namespace Bit.Core.Repositories;
public interface ICollectionCipherRepository
{
Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId);
Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections);
Task<ICollection<CollectionCipher>> GetManyByOrganizationIdAsync(Guid organizationId);
Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId);
Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds);
Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId, bool useFlexibleCollections);
Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds, bool useFlexibleCollections);
Task UpdateCollectionsForAdminAsync(Guid cipherId, Guid organizationId, IEnumerable<Guid> collectionIds);
Task UpdateCollectionsForCiphersAsync(IEnumerable<Guid> cipherIds, Guid userId, Guid organizationId,
IEnumerable<Guid> collectionIds);
IEnumerable<Guid> collectionIds, bool useFlexibleCollections);
}

View File

@ -585,11 +585,11 @@ public class CipherService : ICipherService
originalCipher.SetAttachments(originalAttachments);
}
var currentCollectionsForCipher = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(sharingUserId, originalCipher.Id);
var currentCollectionsForCipher = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(sharingUserId, originalCipher.Id, UseFlexibleCollections);
var currentCollectionIdsForCipher = currentCollectionsForCipher.Select(c => c.CollectionId).ToList();
currentCollectionIdsForCipher.RemoveAll(id => collectionIds.Contains(id));
await _collectionCipherRepository.UpdateCollectionsAsync(originalCipher.Id, sharingUserId, currentCollectionIdsForCipher);
await _collectionCipherRepository.UpdateCollectionsAsync(originalCipher.Id, sharingUserId, currentCollectionIdsForCipher, UseFlexibleCollections);
await _cipherRepository.ReplaceAsync(originalCipher);
}
@ -634,7 +634,7 @@ public class CipherService : ICipherService
await _cipherRepository.UpdateCiphersAsync(sharingUserId, cipherInfos.Select(c => c.cipher));
await _collectionCipherRepository.UpdateCollectionsForCiphersAsync(cipherIds, sharingUserId,
organizationId, collectionIds);
organizationId, collectionIds, UseFlexibleCollections);
var events = cipherInfos.Select(c =>
new Tuple<Cipher, EventType, DateTime?>(c.cipher, EventType.Cipher_Shared, null));
@ -675,7 +675,7 @@ public class CipherService : ICipherService
{
throw new BadRequestException("You do not have permissions to edit this.");
}
await _collectionCipherRepository.UpdateCollectionsAsync(cipher.Id, savingUserId, collectionIds);
await _collectionCipherRepository.UpdateCollectionsAsync(cipher.Id, savingUserId, collectionIds, UseFlexibleCollections);
}
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_UpdatedCollections);

View File

@ -17,12 +17,16 @@ public class CollectionCipherRepository : BaseRepository, ICollectionCipherRepos
: base(connectionString, readOnlyConnectionString)
{ }
public async Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId)
public async Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections)
{
var sprocName = useFlexibleCollections
? "[dbo].[CollectionCipher_ReadByUserId_V2]"
: "[dbo].[CollectionCipher_ReadByUserId]";
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<CollectionCipher>(
"[dbo].[CollectionCipher_ReadByUserId]",
sprocName,
new { UserId = userId },
commandType: CommandType.StoredProcedure);
@ -43,12 +47,16 @@ public class CollectionCipherRepository : BaseRepository, ICollectionCipherRepos
}
}
public async Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId)
public async Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId, bool useFlexibleCollections)
{
var sprocName = useFlexibleCollections
? "[dbo].[CollectionCipher_ReadByUserIdCipherId_V2]"
: "[dbo].[CollectionCipher_ReadByUserIdCipherId]";
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<CollectionCipher>(
"[dbo].[CollectionCipher_ReadByUserIdCipherId]",
sprocName,
new { UserId = userId, CipherId = cipherId },
commandType: CommandType.StoredProcedure);
@ -56,12 +64,16 @@ public class CollectionCipherRepository : BaseRepository, ICollectionCipherRepos
}
}
public async Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds)
public async Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds, bool useFlexibleCollections)
{
var sprocName = useFlexibleCollections
? "[dbo].[CollectionCipher_UpdateCollections_V2]"
: "[dbo].[CollectionCipher_UpdateCollections]";
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
"[dbo].[CollectionCipher_UpdateCollections]",
sprocName,
new { CipherId = cipherId, UserId = userId, CollectionIds = collectionIds.ToGuidIdArrayTVP() },
commandType: CommandType.StoredProcedure);
}
@ -79,12 +91,16 @@ public class CollectionCipherRepository : BaseRepository, ICollectionCipherRepos
}
public async Task UpdateCollectionsForCiphersAsync(IEnumerable<Guid> cipherIds, Guid userId,
Guid organizationId, IEnumerable<Guid> collectionIds)
Guid organizationId, IEnumerable<Guid> collectionIds, bool useFlexibleCollections)
{
var sprocName = useFlexibleCollections
? "[dbo].[CollectionCipher_UpdateCollectionsForCiphers_V2]"
: "[dbo].[CollectionCipher_UpdateCollectionsForCiphers]";
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
"[dbo].[CollectionCipher_UpdateCollectionsForCiphers]",
sprocName,
new
{
CipherIds = cipherIds.ToGuidIdArrayTVP(),

View File

@ -46,31 +46,31 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
}
}
public async Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId)
public async Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var data = await new CollectionCipherReadByUserIdQuery(userId)
var data = await new CollectionCipherReadByUserIdQuery(userId, useFlexibleCollections)
.Run(dbContext)
.ToArrayAsync();
return data;
}
}
public async Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId)
public async Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId, bool useFlexibleCollections)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var data = await new CollectionCipherReadByUserIdCipherIdQuery(userId, cipherId)
var data = await new CollectionCipherReadByUserIdCipherIdQuery(userId, cipherId, useFlexibleCollections)
.Run(dbContext)
.ToArrayAsync();
return data;
}
}
public async Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds)
public async Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds, bool useFlexibleCollections)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
@ -81,7 +81,17 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
.Select(c => c.OrganizationId)
.FirstAsync();
var availableCollections = await (from c in dbContext.Collections
List<Guid> availableCollections;
if (useFlexibleCollections)
{
var availableCollectionsQuery = new CollectionsReadByOrganizationIdUserIdQuery(organizationId, userId);
availableCollections = await availableCollectionsQuery
.Run(dbContext)
.Select(c => c.Id).ToListAsync();
}
else
{
availableCollections = await (from c in dbContext.Collections
join o in dbContext.Organizations on c.OrganizationId equals o.Id
join ou in dbContext.OrganizationUsers
on new { OrganizationId = o.Id, UserId = (Guid?)userId } equals
@ -104,6 +114,8 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
&& (ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly)
select c.Id).ToListAsync();
}
var collectionCiphers = await (from cc in dbContext.CollectionCiphers
where cc.CipherId == cipherId
select cc).ToListAsync();
@ -176,12 +188,22 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
}
}
public async Task UpdateCollectionsForCiphersAsync(IEnumerable<Guid> cipherIds, Guid userId, Guid organizationId, IEnumerable<Guid> collectionIds)
public async Task UpdateCollectionsForCiphersAsync(IEnumerable<Guid> cipherIds, Guid userId, Guid organizationId, IEnumerable<Guid> collectionIds, bool useFlexibleCollections)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var availableCollections = from c in dbContext.Collections
IQueryable<Models.Collection> availableCollections;
if (useFlexibleCollections)
{
var availableCollectionsQuery = new CollectionsReadByOrganizationIdUserIdQuery(organizationId, userId);
availableCollections = availableCollectionsQuery
.Run(dbContext);
}
else
{
availableCollections = from c in dbContext.Collections
join o in dbContext.Organizations
on c.OrganizationId equals o.Id
join ou in dbContext.OrganizationUsers
@ -204,8 +226,10 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
where !g.AccessAll && cg.CollectionId == c.Id &&
(o.Id == organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed &&
(ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly))
select new { c, o, ou, cu, gu, g, cg };
var count = await availableCollections.CountAsync();
select c;
}
if (await availableCollections.CountAsync() < 1)
{
return;
@ -213,7 +237,7 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
var insertData = from collectionId in collectionIds
from cipherId in cipherIds
where availableCollections.Select(x => x.c.Id).Contains(collectionId)
where availableCollections.Select(c => c.Id).Contains(collectionId)
select new Models.CollectionCipher
{
CollectionId = collectionId,

View File

@ -6,7 +6,7 @@ public class CollectionCipherReadByUserIdCipherIdQuery : CollectionCipherReadByU
{
private readonly Guid _cipherId;
public CollectionCipherReadByUserIdCipherIdQuery(Guid userId, Guid cipherId) : base(userId)
public CollectionCipherReadByUserIdCipherIdQuery(Guid userId, Guid cipherId, bool useFlexibleCollections) : base(userId, useFlexibleCollections)
{
_cipherId = cipherId;
}

View File

@ -6,13 +6,58 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
public class CollectionCipherReadByUserIdQuery : IQuery<CollectionCipher>
{
private readonly Guid _userId;
private readonly bool _useFlexibleCollections;
public CollectionCipherReadByUserIdQuery(Guid userId)
public CollectionCipherReadByUserIdQuery(Guid userId, bool useFlexibleCollections)
{
_userId = userId;
_useFlexibleCollections = useFlexibleCollections;
}
public virtual IQueryable<CollectionCipher> Run(DatabaseContext dbContext)
{
return _useFlexibleCollections
? Run_VNext(dbContext)
: Run_VCurrent(dbContext);
}
private IQueryable<CollectionCipher> Run_VNext(DatabaseContext dbContext)
{
var query = from cc in dbContext.CollectionCiphers
join c in dbContext.Collections
on cc.CollectionId equals c.Id
join ou in dbContext.OrganizationUsers
on new { c.OrganizationId, UserId = (Guid?)_userId } equals
new { ou.OrganizationId, ou.UserId }
join cu in dbContext.CollectionUsers
on new { CollectionId = c.Id, OrganizationUserId = ou.Id } equals
new { cu.CollectionId, cu.OrganizationUserId } into cu_g
from cu in cu_g.DefaultIfEmpty()
join gu in dbContext.GroupUsers
on new { CollectionId = (Guid?)cu.CollectionId, OrganizationUserId = ou.Id } equals
new { CollectionId = (Guid?)null, gu.OrganizationUserId } into gu_g
from gu in gu_g.DefaultIfEmpty()
join g in dbContext.Groups
on gu.GroupId equals g.Id into g_g
from g in g_g.DefaultIfEmpty()
join cg in dbContext.CollectionGroups
on new { CollectionId = c.Id, gu.GroupId } equals
new { cg.CollectionId, cg.GroupId } into cg_g
from cg in cg_g.DefaultIfEmpty()
where ou.Status == OrganizationUserStatusType.Confirmed &&
(cu.CollectionId != null || cg.CollectionId != null)
select cc;
return query;
}
private IQueryable<CollectionCipher> Run_VCurrent(DatabaseContext dbContext)
{
var query = from cc in dbContext.CollectionCiphers

View File

@ -0,0 +1,44 @@
using Bit.Core.Enums;
using Bit.Infrastructure.EntityFramework.Models;
namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
public class CollectionsReadByOrganizationIdUserIdQuery : IQuery<Collection>
{
private readonly Guid? _organizationId;
private readonly Guid _userId;
public CollectionsReadByOrganizationIdUserIdQuery(Guid? organizationId, Guid userId)
{
_organizationId = organizationId;
_userId = userId;
}
public virtual IQueryable<Collection> Run(DatabaseContext dbContext)
{
var query = from c in dbContext.Collections
join o in dbContext.Organizations on c.OrganizationId equals o.Id
join ou in dbContext.OrganizationUsers
on new { OrganizationId = o.Id, UserId = (Guid?)_userId } equals
new { ou.OrganizationId, ou.UserId }
join cu in dbContext.CollectionUsers
on new { CollectionId = c.Id, OrganizationUserId = ou.Id } equals
new { cu.CollectionId, cu.OrganizationUserId } into cu_g
from cu in cu_g.DefaultIfEmpty()
join gu in dbContext.GroupUsers
on new { CollectionId = (Guid?)cu.CollectionId, OrganizationUserId = ou.Id } equals
new { CollectionId = (Guid?)null, gu.OrganizationUserId } into gu_g
from gu in gu_g.DefaultIfEmpty()
join g in dbContext.Groups on gu.GroupId equals g.Id into g_g
from g in g_g.DefaultIfEmpty()
join cg in dbContext.CollectionGroups
on new { CollectionId = c.Id, gu.GroupId } equals
new { cg.CollectionId, cg.GroupId } into cg_g
from cg in cg_g.DefaultIfEmpty()
where o.Id == _organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed
&& (!cu.ReadOnly || !cg.ReadOnly)
select c;
return query;
}
}

View File

@ -0,0 +1,31 @@
CREATE PROCEDURE [dbo].[CollectionCipher_ReadByUserIdCipherId_V2]
@UserId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
CC.*
FROM
[dbo].[CollectionCipher] CC
INNER JOIN
[dbo].[Collection] S ON S.[Id] = CC.[CollectionId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
WHERE
CC.[CipherId] = @CipherId
AND OU.[Status] = 2 -- Confirmed
AND (
CU.[CollectionId] IS NOT NULL
OR CG.[CollectionId] IS NOT NULL
)
END

View File

@ -0,0 +1,29 @@
CREATE PROCEDURE [dbo].[CollectionCipher_ReadByUserId_V2]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
CC.*
FROM
[dbo].[CollectionCipher] CC
INNER JOIN
[dbo].[Collection] S ON S.[Id] = CC.[CollectionId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
WHERE
OU.[Status] = 2 -- Confirmed
AND (
CU.[CollectionId] IS NOT NULL
OR CG.[CollectionId] IS NOT NULL
)
END

View File

@ -0,0 +1,62 @@
CREATE PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsForCiphers_V2]
@CipherIds AS [dbo].[GuidIdArray] READONLY,
@OrganizationId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #AvailableCollections (
[Id] UNIQUEIDENTIFIER
)
INSERT INTO #AvailableCollections
SELECT
C.[Id]
FROM
[dbo].[Collection] C
INNER JOIN
[Organization] O ON O.[Id] = C.[OrganizationId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
O.[Id] = @OrganizationId
AND O.[Enabled] = 1
AND OU.[Status] = 2 -- Confirmed
AND (
CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
)
IF (SELECT COUNT(1) FROM #AvailableCollections) < 1
BEGIN
-- No writable collections available to share with in this organization.
RETURN
END
INSERT INTO [dbo].[CollectionCipher]
(
[CollectionId],
[CipherId]
)
SELECT
[Collection].[Id],
[Cipher].[Id]
FROM
@CollectionIds [Collection]
INNER JOIN
@CipherIds [Cipher] ON 1 = 1
WHERE
[Collection].[Id] IN (SELECT [Id] FROM #AvailableCollections)
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
END

View File

@ -0,0 +1,77 @@
CREATE PROCEDURE [dbo].[CollectionCipher_UpdateCollections_V2]
@CipherId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE @OrgId UNIQUEIDENTIFIER = (
SELECT TOP 1
[OrganizationId]
FROM
[dbo].[Cipher]
WHERE
[Id] = @CipherId
)
;WITH [AvailableCollectionsCTE] AS(
SELECT
C.[Id]
FROM
[dbo].[Collection] C
INNER JOIN
[Organization] O ON O.[Id] = C.[OrganizationId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
O.[Id] = @OrgId
AND O.[Enabled] = 1
AND OU.[Status] = 2 -- Confirmed
AND (
CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
)
),
[CollectionCiphersCTE] AS(
SELECT
[CollectionId],
[CipherId]
FROM
[dbo].[CollectionCipher]
WHERE
[CipherId] = @CipherId
)
MERGE
[CollectionCiphersCTE] AS [Target]
USING
@CollectionIds AS [Source]
ON
[Target].[CollectionId] = [Source].[Id]
AND [Target].[CipherId] = @CipherId
WHEN NOT MATCHED BY TARGET
AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN
INSERT VALUES
(
[Source].[Id],
@CipherId
)
WHEN NOT MATCHED BY SOURCE
AND [Target].[CipherId] = @CipherId
AND [Target].[CollectionId] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN
DELETE
;
IF @OrgId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
END
END

View File

@ -116,7 +116,7 @@ public class SyncControllerTests
// Returns for methods only called if we have enabled orgs
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
collectionCipherRepository.GetManyByUserIdAsync(user.Id, Arg.Any<bool>()).Returns(new List<CollectionCipher>());
// Back to standard test setup
userService.TwoFactorIsEnabledAsync(user).Returns(false);
@ -281,7 +281,7 @@ public class SyncControllerTests
// Returns for methods only called if we have enabled orgs
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
collectionCipherRepository.GetManyByUserIdAsync(user.Id, Arg.Any<bool>()).Returns(new List<CollectionCipher>());
// Back to standard test setup
userService.TwoFactorIsEnabledAsync(user).Returns(false);
@ -346,7 +346,7 @@ public class SyncControllerTests
await collectionRepository.ReceivedWithAnyArgs(1)
.GetManyByUserIdAsync(default);
await collectionCipherRepository.ReceivedWithAnyArgs(1)
.GetManyByUserIdAsync(default);
.GetManyByUserIdAsync(default, default);
}
else
{
@ -354,7 +354,7 @@ public class SyncControllerTests
await collectionRepository.ReceivedWithAnyArgs(0)
.GetManyByUserIdAsync(default);
await collectionCipherRepository.ReceivedWithAnyArgs(0)
.GetManyByUserIdAsync(default);
.GetManyByUserIdAsync(default, default);
}
await userService.ReceivedWithAnyArgs(1)

View File

@ -432,7 +432,7 @@ public class CipherServiceTests
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var attachmentStorageService = sutProvider.GetDependency<IAttachmentStorageService>();
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
collectionCipherRepository.GetManyByUserIdCipherIdAsync(cipher.UserId.Value, cipher.Id).Returns(
collectionCipherRepository.GetManyByUserIdCipherIdAsync(cipher.UserId.Value, cipher.Id, Arg.Any<bool>()).Returns(
Task.FromResult((ICollection<CollectionCipher>)new List<CollectionCipher>
{
new CollectionCipher
@ -512,7 +512,7 @@ public class CipherServiceTests
Assert.Contains("ex from StartShareAttachmentAsync", exception.Message);
await collectionCipherRepository.Received().UpdateCollectionsAsync(cipher.Id, cipher.UserId.Value,
Arg.Is<List<Guid>>(ids => ids.Count == 1 && ids[0] != collectionIds[0]));
Arg.Is<List<Guid>>(ids => ids.Count == 1 && ids[0] != collectionIds[0]), Arg.Any<bool>());
await cipherRepository.Received().ReplaceAsync(Arg.Is<Cipher>(c =>
c.GetAttachments()[v0AttachmentId].Key == null
@ -537,7 +537,7 @@ public class CipherServiceTests
var attachmentStorageService = sutProvider.GetDependency<IAttachmentStorageService>();
var userRepository = sutProvider.GetDependency<IUserRepository>();
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
collectionCipherRepository.GetManyByUserIdCipherIdAsync(cipher.UserId.Value, cipher.Id).Returns(
collectionCipherRepository.GetManyByUserIdCipherIdAsync(cipher.UserId.Value, cipher.Id, Arg.Any<bool>()).Returns(
Task.FromResult((ICollection<CollectionCipher>)new List<CollectionCipher>
{
new CollectionCipher
@ -644,7 +644,7 @@ public class CipherServiceTests
Assert.Contains("ex from StartShareAttachmentAsync", exception.Message);
await collectionCipherRepository.Received().UpdateCollectionsAsync(cipher.Id, cipher.UserId.Value,
Arg.Is<List<Guid>>(ids => ids.Count == 1 && ids[0] != collectionIds[0]));
Arg.Is<List<Guid>>(ids => ids.Count == 1 && ids[0] != collectionIds[0]), Arg.Any<bool>());
await cipherRepository.Received().ReplaceAsync(Arg.Is<Cipher>(c =>
c.GetAttachments()[v0AttachmentId1].Key == null

View File

@ -0,0 +1,212 @@
-- Flexible Collections: create new CollectionCipher sprocs that don't use AccessAll logic
-- CollectionCipher_ReadByUserId_V2
CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_ReadByUserId_V2]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
CC.*
FROM
[dbo].[CollectionCipher] CC
INNER JOIN
[dbo].[Collection] S ON S.[Id] = CC.[CollectionId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
WHERE
OU.[Status] = 2 -- Confirmed
AND (
CU.[CollectionId] IS NOT NULL
OR CG.[CollectionId] IS NOT NULL
)
END
GO
-- CollectionCipher_ReadByUserIdCipherId_V2
CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_ReadByUserIdCipherId_V2]
@UserId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
CC.*
FROM
[dbo].[CollectionCipher] CC
INNER JOIN
[dbo].[Collection] S ON S.[Id] = CC.[CollectionId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
WHERE
CC.[CipherId] = @CipherId
AND OU.[Status] = 2 -- Confirmed
AND (
CU.[CollectionId] IS NOT NULL
OR CG.[CollectionId] IS NOT NULL
)
END
GO
-- CollectionCipher_UpdateCollections_V2
CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_UpdateCollections_V2]
@CipherId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE @OrgId UNIQUEIDENTIFIER = (
SELECT TOP 1
[OrganizationId]
FROM
[dbo].[Cipher]
WHERE
[Id] = @CipherId
)
;WITH [AvailableCollectionsCTE] AS(
SELECT
C.[Id]
FROM
[dbo].[Collection] C
INNER JOIN
[Organization] O ON O.[Id] = C.[OrganizationId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
O.[Id] = @OrgId
AND O.[Enabled] = 1
AND OU.[Status] = 2 -- Confirmed
AND (
CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
)
),
[CollectionCiphersCTE] AS(
SELECT
[CollectionId],
[CipherId]
FROM
[dbo].[CollectionCipher]
WHERE
[CipherId] = @CipherId
)
MERGE
[CollectionCiphersCTE] AS [Target]
USING
@CollectionIds AS [Source]
ON
[Target].[CollectionId] = [Source].[Id]
AND [Target].[CipherId] = @CipherId
WHEN NOT MATCHED BY TARGET
AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN
INSERT VALUES
(
[Source].[Id],
@CipherId
)
WHEN NOT MATCHED BY SOURCE
AND [Target].[CipherId] = @CipherId
AND [Target].[CollectionId] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN
DELETE
;
IF @OrgId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
END
END
GO
-- CollectionCipher_UpdateCollectionsForCiphers_V2
CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsForCiphers_V2]
@CipherIds AS [dbo].[GuidIdArray] READONLY,
@OrganizationId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #AvailableCollections (
[Id] UNIQUEIDENTIFIER
)
INSERT INTO #AvailableCollections
SELECT
C.[Id]
FROM
[dbo].[Collection] C
INNER JOIN
[Organization] O ON O.[Id] = C.[OrganizationId]
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
O.[Id] = @OrganizationId
AND O.[Enabled] = 1
AND OU.[Status] = 2 -- Confirmed
AND (
CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
)
IF (SELECT COUNT(1) FROM #AvailableCollections) < 1
BEGIN
-- No writable collections available to share with in this organization.
RETURN
END
INSERT INTO [dbo].[CollectionCipher]
(
[CollectionId],
[CipherId]
)
SELECT
[Collection].[Id],
[Cipher].[Id]
FROM
@CollectionIds [Collection]
INNER JOIN
@CipherIds [Cipher] ON 1 = 1
WHERE
[Collection].[Id] IN (SELECT [Id] FROM #AvailableCollections)
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId
END
GO