1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-03 10:42:21 -05:00

[PM-20550] Add JSON validation to Cipher Delete/Update attachment sprocs (#5656)

* Add JSON validation to Cipher Delete/Update attachment sprocs

* Remove [Attachment] assignment from cipher create/update sprocs

* Add additional validation and use JSON_PATH_EXISTS for delete sproc check

* Update migration script date
This commit is contained in:
Shane Melton 2025-04-30 08:43:39 -07:00 committed by GitHub
parent cf7a59c077
commit 92701d8cd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 468 additions and 29 deletions

View File

@ -6,7 +6,7 @@
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used
@Attachments NVARCHAR(MAX),
@Attachments NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER,
@ -50,7 +50,6 @@ BEGIN
ELSE
JSON_MODIFY([Favorites], @UserIdPath, NULL)
END,
[Attachments] = @Attachments,
[Reprompt] = @Reprompt,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,

View File

@ -6,7 +6,7 @@
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX),
@Attachments NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@ -25,7 +25,6 @@ BEGIN
[Data],
[Favorites],
[Folders],
[Attachments],
[CreationDate],
[RevisionDate],
[DeletedDate],
@ -41,7 +40,6 @@ BEGIN
@Data,
@Favorites,
@Folders,
@Attachments,
@CreationDate,
@RevisionDate,
@DeletedDate,

View File

@ -10,20 +10,59 @@ BEGIN
DECLARE @UserId UNIQUEIDENTIFIER
DECLARE @OrganizationId UNIQUEIDENTIFIER
DECLARE @CurrentAttachments NVARCHAR(MAX)
DECLARE @NewAttachments NVARCHAR(MAX)
-- Get current cipher data
SELECT
@UserId = [UserId],
@OrganizationId = [OrganizationId]
FROM
@OrganizationId = [OrganizationId],
@CurrentAttachments = [Attachments]
FROM
[dbo].[Cipher]
WHERE [Id] = @Id
UPDATE
[dbo].[Cipher]
SET
[Attachments] = JSON_MODIFY([Attachments], @AttachmentIdPath, NULL)
WHERE
[Id] = @Id
-- If there are no attachments, nothing to do
IF @CurrentAttachments IS NULL
BEGIN
RETURN;
END
-- Validate the initial JSON
IF ISJSON(@CurrentAttachments) = 0
BEGIN
THROW 50000, 'Current initial attachments data is not valid JSON', 1;
RETURN;
END
-- Check if the attachment exists before trying to remove it
IF JSON_PATH_EXISTS(@CurrentAttachments, @AttachmentIdPath) = 0
BEGIN
-- Attachment doesn't exist, nothing to do
RETURN;
END
-- Create the new attachments JSON with the specified attachment removed
SET @NewAttachments = JSON_MODIFY(@CurrentAttachments, @AttachmentIdPath, NULL)
-- Validate the resulting JSON
IF ISJSON(@NewAttachments) = 0
BEGIN
THROW 50000, 'Failed to create valid JSON when removing attachment', 1;
RETURN;
END
-- Check if we've removed all attachments and have an empty object
IF @NewAttachments = '{}'
BEGIN
-- If we have an empty JSON object, set to NULL instead
SET @NewAttachments = NULL;
END
-- Update with validated JSON
UPDATE [dbo].[Cipher]
SET [Attachments] = @NewAttachments
WHERE [Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN

View File

@ -6,7 +6,7 @@
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX),
@Attachments NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@ -25,7 +25,6 @@ BEGIN
[Data] = @Data,
[Favorites] = @Favorites,
[Folders] = @Folders,
[Attachments] = @Attachments,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate,
@ -42,4 +41,4 @@ BEGIN
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
END

View File

@ -8,21 +8,75 @@ AS
BEGIN
SET NOCOUNT ON
-- Validate that AttachmentData is valid JSON
IF ISJSON(@AttachmentData) = 0
BEGIN
THROW 50000, 'Invalid JSON format in AttachmentData parameter', 1;
RETURN;
END
-- Validate that AttachmentData has the expected structure
-- Check for required fields
IF JSON_VALUE(@AttachmentData, '$.FileName') IS NULL OR
JSON_VALUE(@AttachmentData, '$.Size') IS NULL
BEGIN
THROW 50000, 'AttachmentData is missing required fields (FileName, Size)', 1;
RETURN;
END
-- Validate data types for critical fields
DECLARE @Size BIGINT = TRY_CAST(JSON_VALUE(@AttachmentData, '$.Size') AS BIGINT)
IF @Size IS NULL OR @Size <= 0
BEGIN
THROW 50000, 'AttachmentData has invalid Size value', 1;
RETURN;
END
DECLARE @AttachmentIdKey VARCHAR(50) = CONCAT('"', @AttachmentId, '"')
DECLARE @AttachmentIdPath VARCHAR(50) = CONCAT('$.', @AttachmentIdKey)
DECLARE @NewAttachments NVARCHAR(MAX)
UPDATE
[dbo].[Cipher]
SET
[Attachments] =
CASE
WHEN [Attachments] IS NULL THEN
CONCAT('{', @AttachmentIdKey, ':', @AttachmentData, '}')
ELSE
JSON_MODIFY([Attachments], @AttachmentIdPath, JSON_QUERY(@AttachmentData, '$'))
END
WHERE
[Id] = @Id
-- Get current attachments
DECLARE @CurrentAttachments NVARCHAR(MAX)
SELECT @CurrentAttachments = [Attachments] FROM [dbo].[Cipher] WHERE [Id] = @Id
-- Prepare the new attachments value based on current state
IF @CurrentAttachments IS NULL
BEGIN
-- Create new JSON object with the attachment
SET @NewAttachments = CONCAT('{', @AttachmentIdKey, ':', @AttachmentData, '}')
-- Validate the constructed JSON
IF ISJSON(@NewAttachments) = 0
BEGIN
THROW 50000, 'Failed to create valid JSON when adding new attachment', 1;
RETURN;
END
END
ELSE
BEGIN
-- Validate existing attachments
IF ISJSON(@CurrentAttachments) = 0
BEGIN
THROW 50000, 'Current attachments data is not valid JSON', 1;
RETURN;
END
-- Modify existing JSON
SET @NewAttachments = JSON_MODIFY(@CurrentAttachments, @AttachmentIdPath, JSON_QUERY(@AttachmentData, '$'))
-- Validate the modified JSON
IF ISJSON(@NewAttachments) = 0
BEGIN
THROW 50000, 'Failed to create valid JSON when updating existing attachments', 1;
RETURN;
END
END
-- Update with validated JSON
UPDATE [dbo].[Cipher]
SET [Attachments] = @NewAttachments
WHERE [Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN
@ -34,4 +88,4 @@ BEGIN
EXEC [dbo].[User_UpdateStorage] @UserId
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
END

View File

@ -0,0 +1,350 @@
CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateAttachment]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@AttachmentId VARCHAR(50),
@AttachmentData NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON
-- Validate that AttachmentData is valid JSON
IF ISJSON(@AttachmentData) = 0
BEGIN
THROW 50000, 'Invalid JSON format in AttachmentData parameter', 1;
RETURN;
END
-- Validate that AttachmentData has the expected structure
-- Check for required fields
IF JSON_VALUE(@AttachmentData, '$.FileName') IS NULL OR
JSON_VALUE(@AttachmentData, '$.Size') IS NULL
BEGIN
THROW 50000, 'AttachmentData is missing required fields (FileName, Size)', 1;
RETURN;
END
-- Validate data types for critical fields
DECLARE @Size BIGINT = TRY_CAST(JSON_VALUE(@AttachmentData, '$.Size') AS BIGINT)
IF @Size IS NULL OR @Size <= 0
BEGIN
THROW 50000, 'AttachmentData has invalid Size value', 1;
RETURN;
END
DECLARE @AttachmentIdKey VARCHAR(50) = CONCAT('"', @AttachmentId, '"')
DECLARE @AttachmentIdPath VARCHAR(50) = CONCAT('$.', @AttachmentIdKey)
DECLARE @NewAttachments NVARCHAR(MAX)
-- Get current attachments
DECLARE @CurrentAttachments NVARCHAR(MAX)
SELECT @CurrentAttachments = [Attachments] FROM [dbo].[Cipher] WHERE [Id] = @Id
-- Prepare the new attachments value based on current state
IF @CurrentAttachments IS NULL
BEGIN
-- Create new JSON object with the attachment
SET @NewAttachments = CONCAT('{', @AttachmentIdKey, ':', @AttachmentData, '}')
-- Validate the constructed JSON
IF ISJSON(@NewAttachments) = 0
BEGIN
THROW 50000, 'Failed to create valid JSON when adding new attachment', 1;
RETURN;
END
END
ELSE
BEGIN
-- Validate existing attachments
IF ISJSON(@CurrentAttachments) = 0
BEGIN
THROW 50000, 'Current attachments data is not valid JSON', 1;
RETURN;
END
-- Modify existing JSON
SET @NewAttachments = JSON_MODIFY(@CurrentAttachments, @AttachmentIdPath, JSON_QUERY(@AttachmentData, '$'))
-- Validate the modified JSON
IF ISJSON(@NewAttachments) = 0
BEGIN
THROW 50000, 'Failed to create valid JSON when updating existing attachments', 1;
RETURN;
END
END
-- Update with validated JSON
UPDATE [dbo].[Cipher]
SET [Attachments] = @NewAttachments
WHERE [Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_UpdateStorage] @UserId
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Cipher_DeleteAttachment]
@Id UNIQUEIDENTIFIER,
@AttachmentId VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON
DECLARE @AttachmentIdKey VARCHAR(50) = CONCAT('"', @AttachmentId, '"')
DECLARE @AttachmentIdPath VARCHAR(50) = CONCAT('$.', @AttachmentIdKey)
DECLARE @UserId UNIQUEIDENTIFIER
DECLARE @OrganizationId UNIQUEIDENTIFIER
DECLARE @CurrentAttachments NVARCHAR(MAX)
DECLARE @NewAttachments NVARCHAR(MAX)
-- Get current cipher data
SELECT
@UserId = [UserId],
@OrganizationId = [OrganizationId],
@CurrentAttachments = [Attachments]
FROM
[dbo].[Cipher]
WHERE [Id] = @Id
-- If there are no attachments, nothing to do
IF @CurrentAttachments IS NULL
BEGIN
RETURN;
END
-- Validate the initial JSON
IF ISJSON(@CurrentAttachments) = 0
BEGIN
THROW 50000, 'Current initial attachments data is not valid JSON', 1;
RETURN;
END
-- Check if the attachment exists before trying to remove it
IF JSON_PATH_EXISTS(@CurrentAttachments, @AttachmentIdPath) = 0
BEGIN
-- Attachment doesn't exist, nothing to do
RETURN;
END
-- Create the new attachments JSON with the specified attachment removed
SET @NewAttachments = JSON_MODIFY(@CurrentAttachments, @AttachmentIdPath, NULL)
-- Validate the resulting JSON
IF ISJSON(@NewAttachments) = 0
BEGIN
THROW 50000, 'Failed to create valid JSON when removing attachment', 1;
RETURN;
END
-- Check if we've removed all attachments and have an empty object
IF @NewAttachments = '{}'
BEGIN
-- If we have an empty JSON object, set to NULL instead
SET @NewAttachments = NULL;
END
-- Update with validated JSON
UPDATE [dbo].[Cipher]
SET [Attachments] = @NewAttachments
WHERE [Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_UpdateStorage] @UserId
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
-- Remove [Attachments] assignment from Cipher_Create, Cipher_Update, and CipherDetails_Update procedures
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Cipher]
(
[Id],
[UserId],
[OrganizationId],
[Type],
[Data],
[Favorites],
[Folders],
[CreationDate],
[RevisionDate],
[DeletedDate],
[Reprompt],
[Key]
)
VALUES
(
@Id,
CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
@OrganizationId,
@Type,
@Data,
@Favorites,
@Folders,
@CreationDate,
@RevisionDate,
@DeletedDate,
@Reprompt,
@Key
)
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update]
@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
@ViewPassword BIT, -- not used
@Manage BIT, -- not used
@OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(2),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"')
DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey)
UPDATE
[dbo].[Cipher]
SET
[UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
[OrganizationId] = @OrganizationId,
[Type] = @Type,
[Data] = @Data,
[Folders] =
CASE
WHEN @FolderId IS NOT NULL AND [Folders] IS NULL THEN
CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}')
WHEN @FolderId IS NOT NULL THEN
JSON_MODIFY([Folders], @UserIdPath, CAST(@FolderId AS VARCHAR(50)))
ELSE
JSON_MODIFY([Folders], @UserIdPath, NULL)
END,
[Favorites] =
CASE
WHEN @Favorite = 1 AND [Favorites] IS NULL THEN
CONCAT('{', @UserIdKey, ':true}')
WHEN @Favorite = 1 THEN
JSON_MODIFY([Favorites], @UserIdPath, CAST(1 AS BIT))
ELSE
JSON_MODIFY([Favorites], @UserIdPath, NULL)
END,
[Reprompt] = @Reprompt,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate,
[Key] = @Key
WHERE
[Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Update]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[Cipher]
SET
[UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
[OrganizationId] = @OrganizationId,
[Type] = @Type,
[Data] = @Data,
[Favorites] = @Favorites,
[Folders] = @Folders,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate,
[Reprompt] = @Reprompt,
[Key] = @Key
WHERE
[Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO