From 770fa10f3e1c5f98f1bfd575e7a76f605eeaa215 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 30 Aug 2017 18:18:39 -0400 Subject: [PATCH] fix bugs around collection association --- src/Core/Repositories/ICipherRepository.cs | 2 +- .../SqlServer/CipherRepository.cs | 5 +- .../Services/Implementations/CipherService.cs | 6 +- .../Cipher_UpdateWithCollections.sql | 71 ++++--- .../CollectionCipher_UpdateCollections.sql | 6 +- .../2017-08-30_00_CollectionWriteOnly.sql | 176 ++++++++++++++++++ 6 files changed, 230 insertions(+), 36 deletions(-) diff --git a/src/Core/Repositories/ICipherRepository.cs b/src/Core/Repositories/ICipherRepository.cs index 9d7a3cb18b..4e6f96251b 100644 --- a/src/Core/Repositories/ICipherRepository.cs +++ b/src/Core/Repositories/ICipherRepository.cs @@ -18,7 +18,7 @@ namespace Bit.Core.Repositories Task CreateAsync(CipherDetails cipher); Task ReplaceAsync(CipherDetails cipher); Task UpsertAsync(CipherDetails cipher); - Task ReplaceAsync(Cipher obj, IEnumerable collectionIds); + Task ReplaceAsync(Cipher obj, IEnumerable collectionIds); Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite); Task UpdateAttachmentAsync(CipherAttachment attachment); Task DeleteAttachmentAsync(Guid cipherId, string attachmentId); diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index 68341b2191..e4e7e8d573 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -152,17 +152,18 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task ReplaceAsync(Cipher obj, IEnumerable collectionIds) + public async Task ReplaceAsync(Cipher obj, IEnumerable collectionIds) { var objWithCollections = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); using(var connection = new SqlConnection(ConnectionString)) { - var results = await connection.ExecuteAsync( + var result = await connection.ExecuteScalarAsync( $"[{Schema}].[Cipher_UpdateWithCollections]", objWithCollections, commandType: CommandType.StoredProcedure); + return result >= 0; } } diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index b7d617b3de..c987e6d86b 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -338,7 +338,11 @@ namespace Bit.Core.Services cipher.UserId = sharingUserId; cipher.OrganizationId = organizationId; cipher.RevisionDate = DateTime.UtcNow; - await _cipherRepository.ReplaceAsync(cipher, collectionIds); + if(!await _cipherRepository.ReplaceAsync(cipher, collectionIds)) + { + throw new BadRequestException("Unable to save."); + } + updatedCipher = true; if(hasAttachments) diff --git a/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql index 24d89e18e6..bca9d6f328 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql @@ -14,6 +14,45 @@ 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 OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] + WHERE + O.[Id] = @OrganizationId + AND O.[Enabled] = 1 + AND OU.[Status] = 2 -- Confirmed + AND ( + OU.[AccessAll] = 1 + OR CU.[ReadOnly] = 0 + OR G.[AccessAll] = 1 + OR CG.[ReadOnly] = 0 + ) + + IF (SELECT COUNT(1) FROM #AvailableCollections) < 1 + BEGIN + -- No writable collections available to share with in this organization. + SELECT -1 -- -1 = Failure + RETURN + END + UPDATE [dbo].[Cipher] SET @@ -26,34 +65,6 @@ BEGIN WHERE [Id] = @Id - ;WITH [AvailableCollectionsCTE] AS( - SELECT - S.[Id] - FROM - [dbo].[Collection] S - INNER JOIN - [Organization] O ON O.[Id] = S.[OrganizationId] - INNER JOIN - [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId - LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] - LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] - WHERE - O.[Id] = @OrganizationId - AND O.[Enabled] = 1 - AND OU.[Status] = 2 -- Confirmed - AND ( - OU.[AccessAll] = 1 - OR CU.[ReadOnly] = 0 - OR G.[AccessAll] = 1 - OR CG.[ReadOnly] = 0 - ) - ) INSERT INTO [dbo].[CollectionCipher] ( [CollectionId], @@ -65,7 +76,7 @@ BEGIN FROM @CollectionIds WHERE - [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) + [Id] IN (SELECT [Id] FROM #AvailableCollections) IF @Attachments IS NOT NULL BEGIN @@ -74,4 +85,6 @@ BEGIN END EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId + + SELECT 0 -- 0 = Success END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql index 53dbfaa769..8951c08d06 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql @@ -31,16 +31,16 @@ BEGIN LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] WHERE O.[Id] = @OrgId AND O.[Enabled] = 1 AND OU.[Status] = 2 -- Confirmed AND ( OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL + OR CU.[ReadOnly] = 0 OR G.[AccessAll] = 1 - OR CG.[CollectionId] IS NOT NULL + OR CG.[ReadOnly] = 0 ) ) MERGE diff --git a/util/Setup/DbScripts/2017-08-30_00_CollectionWriteOnly.sql b/util/Setup/DbScripts/2017-08-30_00_CollectionWriteOnly.sql index d4fa34329b..9f9ee3a557 100644 --- a/util/Setup/DbScripts/2017-08-30_00_CollectionWriteOnly.sql +++ b/util/Setup/DbScripts/2017-08-30_00_CollectionWriteOnly.sql @@ -4,6 +4,18 @@ BEGIN END GO +IF OBJECT_ID('[dbo].[Cipher_UpdateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_UpdateWithCollections] +END +GO + +IF OBJECT_ID('[dbo].[CollectionCipher_UpdateCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CollectionCipher_UpdateCollections] +END +GO + CREATE PROCEDURE [dbo].[Collection_ReadByUserId] @UserId UNIQUEIDENTIFIER, @WriteOnly BIT @@ -44,3 +56,167 @@ BEGIN ) END GO + +CREATE PROCEDURE [dbo].[Cipher_UpdateWithCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), + @Folders NVARCHAR(MAX), + @Attachments NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @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 OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] + WHERE + O.[Id] = @OrganizationId + AND O.[Enabled] = 1 + AND OU.[Status] = 2 -- Confirmed + AND ( + OU.[AccessAll] = 1 + OR CU.[ReadOnly] = 0 + OR G.[AccessAll] = 1 + OR CG.[ReadOnly] = 0 + ) + + IF (SELECT COUNT(1) FROM #AvailableCollections) < 1 + BEGIN + -- No writable collections available to share with in this organization. + SELECT -1 -- -1 = Failure + RETURN + END + + UPDATE + [dbo].[Cipher] + SET + [UserId] = NULL, + [OrganizationId] = @OrganizationId, + [Data] = @Data, + [Attachments] = @Attachments, + [RevisionDate] = @RevisionDate + -- No need to update CreationDate, Favorites, Folders, or Type since that data will not change + WHERE + [Id] = @Id + + INSERT INTO [dbo].[CollectionCipher] + ( + [CollectionId], + [CipherId] + ) + SELECT + [Id], + @Id + FROM + @CollectionIds + WHERE + [Id] IN (SELECT [Id] FROM #AvailableCollections) + + IF @Attachments IS NOT NULL + BEGIN + EXEC [dbo].[Organization_UpdateStorage] @OrganizationId + EXEC [dbo].[User_UpdateStorage] @UserId + END + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId + + SELECT 0 -- 0 = Success +END +GO + +CREATE PROCEDURE [dbo].[CollectionCipher_UpdateCollections] + @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 OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] + WHERE + O.[Id] = @OrgId + AND O.[Enabled] = 1 + AND OU.[Status] = 2 -- Confirmed + AND ( + OU.[AccessAll] = 1 + OR CU.[ReadOnly] = 0 + OR G.[AccessAll] = 1 + OR CG.[ReadOnly] = 0 + ) + ) + MERGE + [dbo].[CollectionCipher] 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