1
0
mirror of https://github.com/bitwarden/server synced 2025-12-15 15:53:59 +00:00
Files
server/util/Migrator/DbScripts/2025-12-02_AddCipherArchives.sql
2025-12-03 09:49:35 -08:00

378 lines
8.5 KiB
Transact-SQL

-- Add new JSON column for Archives (similar to Favorites/Folders pattern)
IF NOT EXISTS (
SELECT 1
FROM sys.columns
WHERE object_id = OBJECT_ID(N'[dbo].[Cipher]')
AND name = 'Archives'
)
BEGIN
ALTER TABLE [dbo].[Cipher]
ADD [Archives] NVARCHAR(MAX) NULL;
END;
GO
-- Update CipherDetails function to use JSON column approach
IF OBJECT_ID('[dbo].[CipherDetails]') IS NOT NULL
BEGIN
DROP FUNCTION [dbo].[CipherDetails];
END
GO
CREATE FUNCTION [dbo].[CipherDetails](@UserId UNIQUEIDENTIFIER)
RETURNS TABLE
AS RETURN
SELECT
C.[Id],
C.[UserId],
C.[OrganizationId],
C.[Type],
C.[Data],
C.[Attachments],
C.[CreationDate],
C.[RevisionDate],
CASE
WHEN
@UserId IS NULL
OR C.[Favorites] IS NULL
OR JSON_VALUE(C.[Favorites], CONCAT('$."', @UserId, '"')) IS NULL
THEN 0
ELSE 1
END [Favorite],
CASE
WHEN
@UserId IS NULL
OR C.[Folders] IS NULL
THEN NULL
ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
END [FolderId],
C.[DeletedDate],
C.[Reprompt],
C.[Key],
CASE
WHEN
@UserId IS NULL
OR C.[Archives] IS NULL
THEN NULL
ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[Archives], CONCAT('$."', @UserId, '"')))
END [ArchivedDate]
FROM
[dbo].[Cipher] C;
GO
-- Update UserCipherDetails function
IF OBJECT_ID('[dbo].[UserCipherDetails]') IS NOT NULL
BEGIN
DROP FUNCTION [dbo].[UserCipherDetails];
END
GO
CREATE FUNCTION [dbo].[UserCipherDetails](@UserId UNIQUEIDENTIFIER)
RETURNS TABLE
AS RETURN
WITH [CTE] AS (
SELECT
[Id],
[OrganizationId]
FROM
[OrganizationUser]
WHERE
[UserId] = @UserId
AND [Status] = 2 -- Confirmed
)
SELECT
C.Id,
C.UserId,
C.OrganizationId,
C.Type,
C.Data,
C.Attachments,
C.CreationDate,
C.RevisionDate,
C.Favorite,
C.FolderId,
C.DeletedDate,
C.ArchivedDate,
C.Reprompt,
C.[Key],
CASE
WHEN COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0
THEN 1
ELSE 0
END [Edit],
CASE
WHEN COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0
THEN 1
ELSE 0
END [ViewPassword],
CASE
WHEN COALESCE(CU.[Manage], CG.[Manage], 0) = 1
THEN 1
ELSE 0
END [Manage],
CASE
WHEN O.[UseTotp] = 1
THEN 1
ELSE 0
END [OrganizationUseTotp]
FROM
[dbo].[CipherDetails](@UserId) C
INNER JOIN
[CTE] OU ON C.[UserId] IS NULL AND C.[OrganizationId] IN (SELECT [OrganizationId] FROM [CTE])
INNER JOIN
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1
LEFT JOIN
[dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id]
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] 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
CU.[CollectionId] IS NOT NULL
OR CG.[CollectionId] IS NOT NULL
UNION ALL
SELECT
C.Id,
C.UserId,
C.OrganizationId,
C.Type,
C.Data,
C.Attachments,
C.CreationDate,
C.RevisionDate,
C.Favorite,
C.FolderId,
C.DeletedDate,
C.ArchivedDate,
C.Reprompt,
C.[Key],
1 [Edit],
1 [ViewPassword],
1 [Manage],
0 [OrganizationUseTotp]
FROM
[dbo].[CipherDetails](@UserId) AS C
WHERE
C.[UserId] = @UserId;
GO
IF OBJECT_ID('[dbo].[Cipher_Archive]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Cipher_Archive];
END
GO
CREATE PROCEDURE [dbo].[Cipher_Archive]
@Ids AS [dbo].[GuidIdArray] READONLY,
@UserId AS UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #Temp
(
[Id] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL
)
INSERT INTO #Temp
SELECT
[Id],
[UserId]
FROM
[dbo].[UserCipherDetails](@UserId)
WHERE
[Edit] = 1
AND [ArchivedDate] IS NULL
AND [Id] IN (SELECT * FROM @Ids)
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
UPDATE
[dbo].[Cipher]
SET
[ArchivedDate] = JSON_MODIFY(
COALESCE([Archives], N'{}'),
'$."' + CONVERT(NVARCHAR(36), @UserId) + '"',
@UtcNow
),
[RevisionDate] = @UtcNow
WHERE
[Id] IN (SELECT [Id] FROM #Temp)
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
DROP TABLE #Temp
SELECT @UtcNow
END
GO
IF OBJECT_ID('[dbo].[Cipher_Unarchive]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Cipher_Unarchive];
END
GO
CREATE PROCEDURE [dbo].[Cipher_Unarchive]
@Ids AS [dbo].[GuidIdArray] READONLY,
@UserId AS UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #Temp
(
[Id] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL
)
INSERT INTO #Temp
SELECT
[Id],
[UserId]
FROM
[dbo].[UserCipherDetails](@UserId)
WHERE
[Edit] = 1
AND [ArchivedDate] IS NOT NULL
AND [Id] IN (SELECT * FROM @Ids)
DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME();
UPDATE
[dbo].[Cipher]
SET
[Archives] = JSON_MODIFY(
COALESCE([Archives], N'{}'),
'$."' + CONVERT(NVARCHAR(36), @UserId) + '"',
NULL
),
[RevisionDate] = @UtcNow
WHERE
[Id] IN (SELECT [Id] FROM #Temp)
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
DROP TABLE #Temp
SELECT @UtcNow
END
GO
IF OBJECT_ID('[dbo].[Cipher_Create]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Cipher_Create];
END
GO
CREATE 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,
@ArchivedDate DATETIME2(7) = NULL
@Archives NVARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Cipher]
(
[Id],
[UserId],
[OrganizationId],
[Type],
[Data],
[Favorites],
[Folders],
[CreationDate],
[RevisionDate],
[DeletedDate],
[Reprompt],
[Key],
[ArchivedDate],
[Archives]
)
VALUES
(
@Id,
CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
@OrganizationId,
@Type,
@Data,
@Favorites,
@Folders,
@CreationDate,
@RevisionDate,
@DeletedDate,
@Reprompt,
@Key,
@ArchivedDate,
@Archives
)
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
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),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY,
@ArchivedDate DATETIME2(7) = NULL,
@Archives NVARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders,
@Attachments, @CreationDate, @RevisionDate, @DeletedDate, @Reprompt, @Key, @ArchivedDate
DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds
-- Bump the account revision date AFTER collections are assigned.
IF @UpdateCollectionsSuccess = 0
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
END
GO