diff --git a/src/Api/Controllers/CiphersController.cs b/src/Api/Controllers/CiphersController.cs index 244d325a55..86334eb404 100644 --- a/src/Api/Controllers/CiphersController.cs +++ b/src/Api/Controllers/CiphersController.cs @@ -108,23 +108,42 @@ namespace Bit.Api.Controllers { var userId = _userService.GetProperUserId(User).Value; var cipher = model.ToCipherDetails(userId); - await _cipherService.SaveDetailsAsync(cipher, userId); + if(cipher.OrganizationId.HasValue && !_currentContext.OrganizationUser(cipher.OrganizationId.Value)) + { + throw new NotFoundException(); + } + await _cipherService.SaveDetailsAsync(cipher, userId, null, cipher.OrganizationId.HasValue); + var response = new CipherResponseModel(cipher, _globalSettings); + return response; + } + + [HttpPost("create")] + public async Task PostCreate([FromBody]CipherCreateRequestModel model) + { + var userId = _userService.GetProperUserId(User).Value; + var cipher = model.Cipher.ToCipherDetails(userId); + if(cipher.OrganizationId.HasValue && !_currentContext.OrganizationUser(cipher.OrganizationId.Value)) + { + throw new NotFoundException(); + } + + await _cipherService.SaveDetailsAsync(cipher, userId, model.CollectionIds, cipher.OrganizationId.HasValue); var response = new CipherResponseModel(cipher, _globalSettings); return response; } [HttpPost("admin")] - public async Task PostAdmin([FromBody]CipherRequestModel model) + public async Task PostAdmin([FromBody]CipherCreateRequestModel model) { - var cipher = model.ToOrganizationCipher(); + var cipher = model.Cipher.ToOrganizationCipher(); if(!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value)) { throw new NotFoundException(); } var userId = _userService.GetProperUserId(User).Value; - await _cipherService.SaveAsync(cipher, userId, true); + await _cipherService.SaveAsync(cipher, userId, model.CollectionIds, true); var response = new CipherMiniResponseModel(cipher, _globalSettings, false); return response; @@ -168,7 +187,7 @@ namespace Bit.Api.Controllers // object cannot be a descendant of CipherDetails, so let's clone it. var cipherClone = CoreHelpers.CloneObject(model.ToCipher(cipher)); - await _cipherService.SaveAsync(cipherClone, userId, true); + await _cipherService.SaveAsync(cipherClone, userId, null, true); var response = new CipherMiniResponseModel(cipherClone, _globalSettings, cipher.OrganizationUseTotp); return response; @@ -206,7 +225,7 @@ namespace Bit.Api.Controllers var userId = _userService.GetProperUserId(User).Value; var folders = model.Folders.Select(f => f.ToFolder(userId)).ToList(); - var ciphers = model.Ciphers.Select(c => c.ToCipherDetails(userId)).ToList(); + var ciphers = model.Ciphers.Select(c => c.ToCipherDetails(userId, false)).ToList(); await _cipherService.ImportCiphersAsync(folders, ciphers, model.FolderRelationships); } diff --git a/src/Core/Models/Api/Request/CipherRequestModel.cs b/src/Core/Models/Api/Request/CipherRequestModel.cs index b0fa74df7f..24abbeced5 100644 --- a/src/Core/Models/Api/Request/CipherRequestModel.cs +++ b/src/Core/Models/Api/Request/CipherRequestModel.cs @@ -36,13 +36,14 @@ namespace Bit.Core.Models.Api public CipherIdentityModel Identity { get; set; } public CipherSecureNoteModel SecureNote { get; set; } - public CipherDetails ToCipherDetails(Guid userId) + public CipherDetails ToCipherDetails(Guid userId, bool allowOrgIdSet = true) { + var hasOrgId = !string.IsNullOrWhiteSpace(OrganizationId); var cipher = new CipherDetails { Type = Type, - UserId = string.IsNullOrWhiteSpace(OrganizationId) ? (Guid?)userId : null, - OrganizationId = null, + UserId = !hasOrgId ? (Guid?)userId : null, + OrganizationId = allowOrgIdSet && hasOrgId ? new Guid(OrganizationId) : (Guid?)null, Edit = true }; ToCipherDetails(cipher); @@ -142,6 +143,22 @@ namespace Bit.Core.Models.Api } } + public class CipherCreateRequestModel : IValidatableObject + { + public IEnumerable CollectionIds { get; set; } + [Required] + public CipherRequestModel Cipher { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if(!string.IsNullOrWhiteSpace(Cipher.OrganizationId) && (!CollectionIds?.Any() ?? true)) + { + yield return new ValidationResult("You must select at least one collection.", + new string[] { nameof(CollectionIds) }); + } + } + } + public class CipherShareRequestModel : IValidatableObject { [Required] diff --git a/src/Core/Repositories/ICipherRepository.cs b/src/Core/Repositories/ICipherRepository.cs index d10ebede4a..3e529f32cf 100644 --- a/src/Core/Repositories/ICipherRepository.cs +++ b/src/Core/Repositories/ICipherRepository.cs @@ -14,7 +14,9 @@ namespace Bit.Core.Repositories Task GetCanEditByIdAsync(Guid userId, Guid cipherId); Task> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true); Task> GetManyByOrganizationIdAsync(Guid organizationId); + Task CreateAsync(Cipher cipher, IEnumerable collectionIds); Task CreateAsync(CipherDetails cipher); + Task CreateAsync(CipherDetails cipher, IEnumerable collectionIds); Task ReplaceAsync(CipherDetails cipher); Task UpsertAsync(CipherDetails cipher); Task ReplaceAsync(Cipher obj, IEnumerable collectionIds); diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index d0320a15c9..ec76c697e3 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -101,6 +101,21 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task CreateAsync(Cipher cipher, IEnumerable collectionIds) + { + cipher.SetNewId(); + var objWithCollections = JsonConvert.DeserializeObject( + JsonConvert.SerializeObject(cipher)); + objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.ExecuteAsync( + $"[{Schema}].[Cipher_CreateWithCollections]", + objWithCollections, + commandType: CommandType.StoredProcedure); + } + } + public async Task CreateAsync(CipherDetails cipher) { cipher.SetNewId(); @@ -113,6 +128,21 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task CreateAsync(CipherDetails cipher, IEnumerable collectionIds) + { + cipher.SetNewId(); + var objWithCollections = JsonConvert.DeserializeObject( + JsonConvert.SerializeObject(cipher)); + objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.ExecuteAsync( + $"[{Schema}].[CipherDetails_CreateWithCollections]", + objWithCollections, + commandType: CommandType.StoredProcedure); + } + } + public async Task ReplaceAsync(CipherDetails obj) { using(var connection = new SqlConnection(ConnectionString)) @@ -715,6 +745,11 @@ namespace Bit.Core.Repositories.SqlServer return collectionCiphersTable; } + public class CipherDetailsWithCollections : CipherDetails + { + public DataTable CollectionIds { get; set; } + } + public class CipherWithCollections : Cipher { public DataTable CollectionIds { get; set; } diff --git a/src/Core/Services/ICipherService.cs b/src/Core/Services/ICipherService.cs index 9d2f99ed9a..f8b92d61ce 100644 --- a/src/Core/Services/ICipherService.cs +++ b/src/Core/Services/ICipherService.cs @@ -9,8 +9,8 @@ namespace Bit.Core.Services { public interface ICipherService { - Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false); - Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId); + Task SaveAsync(Cipher cipher, Guid savingUserId, IEnumerable collectionIds = null, bool skipPermissionCheck = false); + Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId, IEnumerable collectionIds = null, bool skipPermissionCheck = false); Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, long requestLength, Guid savingUserId, bool orgAdmin = false); Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, long requestLength, string attachmentId, diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index 3c4a5a0aa1..2e700a2ae6 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -55,16 +55,24 @@ namespace Bit.Core.Services _globalSettings = globalSettings; } - public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false) + public async Task SaveAsync(Cipher cipher, Guid savingUserId, IEnumerable collectionIds = null, + bool skipPermissionCheck = false) { - if(!orgAdmin && !(await UserCanEditAsync(cipher, savingUserId))) + if(!skipPermissionCheck && !(await UserCanEditAsync(cipher, savingUserId))) { throw new BadRequestException("You do not have permissions to edit this."); } if(cipher.Id == default(Guid)) { - await _cipherRepository.CreateAsync(cipher); + if(cipher.OrganizationId.HasValue && collectionIds != null) + { + await _cipherRepository.CreateAsync(cipher, collectionIds); + } + else + { + await _cipherRepository.CreateAsync(cipher); + } await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created); // push @@ -72,6 +80,10 @@ namespace Bit.Core.Services } else { + if(collectionIds != null) + { + throw new ArgumentException("Cannot create cipher with collection ids at the same time."); + } cipher.RevisionDate = DateTime.UtcNow; await _cipherRepository.ReplaceAsync(cipher); await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Updated); @@ -81,9 +93,10 @@ namespace Bit.Core.Services } } - public async Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId) + public async Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId, + IEnumerable collectionIds = null, bool skipPermissionCheck = false) { - if(!(await UserCanEditAsync(cipher, savingUserId))) + if(!skipPermissionCheck && !(await UserCanEditAsync(cipher, savingUserId))) { throw new BadRequestException("You do not have permissions to edit this."); } @@ -91,7 +104,14 @@ namespace Bit.Core.Services cipher.UserId = savingUserId; if(cipher.Id == default(Guid)) { - await _cipherRepository.CreateAsync(cipher); + if(cipher.OrganizationId.HasValue && collectionIds != null) + { + await _cipherRepository.CreateAsync(cipher, collectionIds); + } + else + { + await _cipherRepository.CreateAsync(cipher); + } await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created); if(cipher.OrganizationId.HasValue) @@ -105,6 +125,10 @@ namespace Bit.Core.Services } else { + if(collectionIds != null) + { + throw new ArgumentException("Cannot create cipher with collection ids at the same time."); + } cipher.RevisionDate = DateTime.UtcNow; await _cipherRepository.ReplaceAsync(cipher); await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Updated); diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index fe9d6dedf1..95bfe03d42 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -237,5 +237,8 @@ + + + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/CipherDetails_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/CipherDetails_CreateWithCollections.sql new file mode 100644 index 0000000000..3c391bbb02 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/CipherDetails_CreateWithCollections.sql @@ -0,0 +1,26 @@ +CREATE PROCEDURE [dbo].[CipherDetails_CreateWithCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), -- not used + @Folders NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), -- not used + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @FolderId UNIQUEIDENTIFIER, + @Favorite BIT, + @Edit BIT, -- not used + @OrganizationUseTotp BIT, -- not used + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[CipherDetails_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, + @Attachments, @CreationDate, @RevisionDate, @FolderId, @Favorite, @Edit, @OrganizationUseTotp + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Cipher_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Cipher_CreateWithCollections.sql new file mode 100644 index 0000000000..260bbeb25d --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Cipher_CreateWithCollections.sql @@ -0,0 +1,22 @@ +CREATE PROCEDURE [dbo].[Cipher_CreateWithCollections] + @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 + + EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, + @Attachments, @CreationDate, @RevisionDate + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Cipher_UpdateCollections.sql b/src/Sql/dbo/Stored Procedures/Cipher_UpdateCollections.sql new file mode 100644 index 0000000000..7e99e05dc5 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Cipher_UpdateCollections.sql @@ -0,0 +1,67 @@ +CREATE PROCEDURE [dbo].[Cipher_UpdateCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + IF @OrganizationId IS NULL OR (SELECT COUNT(1) FROM @CollectionIds) < 1 + BEGIN + RETURN(-1) + END + + 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.[CollectionId] = C.[Id] 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. + RETURN(-1) + END + + INSERT INTO [dbo].[CollectionCipher] + ( + [CollectionId], + [CipherId] + ) + SELECT + [Id], + @Id + FROM + @CollectionIds + WHERE + [Id] IN (SELECT [Id] FROM #AvailableCollections) + + RETURN(0) +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql index 15e7cc388b..5c576d6338 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithCollections.sql @@ -14,41 +14,14 @@ AS BEGIN SET NOCOUNT ON - CREATE TABLE #AvailableCollections ( - [Id] UNIQUEIDENTIFIER - ) + BEGIN TRANSACTION Cipher_UpdateWithCollections - 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.[CollectionId] = C.[Id] 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 - ) + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds - IF (SELECT COUNT(1) FROM #AvailableCollections) < 1 + IF @UpdateCollectionsSuccess < 0 BEGIN - -- No writable collections available to share with in this organization. + COMMIT TRANSACTION Cipher_UpdateWithCollections SELECT -1 -- -1 = Failure RETURN END @@ -65,18 +38,7 @@ BEGIN WHERE [Id] = @Id - INSERT INTO [dbo].[CollectionCipher] - ( - [CollectionId], - [CipherId] - ) - SELECT - [Id], - @Id - FROM - @CollectionIds - WHERE - [Id] IN (SELECT [Id] FROM #AvailableCollections) + COMMIT TRANSACTION Cipher_UpdateWithCollections IF @Attachments IS NOT NULL BEGIN diff --git a/util/Setup/DbScripts/2018-10-17_00_ManagerRole.sql b/util/Setup/DbScripts/2018-10-17_00_ManagerRole.sql index 875ba72b02..a079f431c8 100644 --- a/util/Setup/DbScripts/2018-10-17_00_ManagerRole.sql +++ b/util/Setup/DbScripts/2018-10-17_00_ManagerRole.sql @@ -227,3 +227,202 @@ BEGIN [GroupId] = @GroupId END GO + +IF OBJECT_ID('[dbo].[Cipher_UpdateCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_UpdateCollections] +END +GO + +CREATE PROCEDURE [dbo].[Cipher_UpdateCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + IF @OrganizationId IS NULL OR (SELECT COUNT(1) FROM @CollectionIds) < 1 + BEGIN + RETURN(-1) + END + + 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.[CollectionId] = C.[Id] 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. + RETURN(-1) + END + + INSERT INTO [dbo].[CollectionCipher] + ( + [CollectionId], + [CipherId] + ) + SELECT + [Id], + @Id + FROM + @CollectionIds + WHERE + [Id] IN (SELECT [Id] FROM #AvailableCollections) + + RETURN(0) +END +GO + +IF OBJECT_ID('[dbo].[CipherDetails_CreateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CipherDetails_CreateWithCollections] +END +GO + +CREATE PROCEDURE [dbo].[CipherDetails_CreateWithCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), -- not used + @Folders NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), -- not used + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @FolderId UNIQUEIDENTIFIER, + @Favorite BIT, + @Edit BIT, -- not used + @OrganizationUseTotp BIT, -- not used + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[CipherDetails_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, + @Attachments, @CreationDate, @RevisionDate, @FolderId, @Favorite, @Edit, @OrganizationUseTotp + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds +END +GO + +IF OBJECT_ID('[dbo].[Cipher_CreateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_CreateWithCollections] +END +GO + +CREATE PROCEDURE [dbo].[Cipher_CreateWithCollections] + @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 + + EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, + @Attachments, @CreationDate, @RevisionDate + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds +END +GO + +IF OBJECT_ID('[dbo].[Cipher_UpdateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_UpdateWithCollections] +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 + + BEGIN TRANSACTION Cipher_UpdateWithCollections + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds + + IF @UpdateCollectionsSuccess < 0 + BEGIN + COMMIT TRANSACTION Cipher_UpdateWithCollections + 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 + + COMMIT TRANSACTION Cipher_UpdateWithCollections + + IF @Attachments IS NOT NULL + BEGIN + EXEC [dbo].[Organization_UpdateStorage] @OrganizationId + EXEC [dbo].[User_UpdateStorage] @UserId + END + + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + + SELECT 0 -- 0 = Success +END +GO