IF COL_LENGTH('[dbo].[User]', 'Kdf') IS NULL BEGIN ALTER TABLE [dbo].[User] ADD [Kdf] TINYINT NULL, [KdfIterations] INT NULL END GO UPDATE [dbo].[User] SET [Kdf] = 0, [KdfIterations] = 5000 WHERE [Kdf] IS NULL OR [KdfIterations] IS NULL GO ALTER TABLE [dbo].[User] ALTER COLUMN [Kdf] TINYINT NOT NULL GO ALTER TABLE [dbo].[User] ALTER COLUMN [KdfIterations] INT NOT NULL GO IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'UserView') BEGIN DROP VIEW [dbo].[UserView] END GO CREATE VIEW [dbo].[UserView] AS SELECT * FROM [dbo].[User] GO IF OBJECT_ID('[dbo].[User_Create]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[User_Create] END GO CREATE PROCEDURE [dbo].[User_Create] @Id UNIQUEIDENTIFIER, @Name NVARCHAR(50), @Email NVARCHAR(50), @EmailVerified BIT, @MasterPassword NVARCHAR(300), @MasterPasswordHint NVARCHAR(50), @Culture NVARCHAR(10), @SecurityStamp NVARCHAR(50), @TwoFactorProviders NVARCHAR(MAX), @TwoFactorRecoveryCode NVARCHAR(32), @EquivalentDomains NVARCHAR(MAX), @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), @AccountRevisionDate DATETIME2(7), @Key NVARCHAR(MAX), @PublicKey NVARCHAR(MAX), @PrivateKey NVARCHAR(MAX), @Premium BIT, @PremiumExpirationDate DATETIME2(7), @RenewalReminderDate DATETIME2(7), @Storage BIGINT, @MaxStorageGb SMALLINT, @Gateway TINYINT, @GatewayCustomerId VARCHAR(50), @GatewaySubscriptionId VARCHAR(50), @LicenseKey VARCHAR(100), @Kdf TINYINT, @KdfIterations INT, @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) AS BEGIN SET NOCOUNT ON INSERT INTO [dbo].[User] ( [Id], [Name], [Email], [EmailVerified], [MasterPassword], [MasterPasswordHint], [Culture], [SecurityStamp], [TwoFactorProviders], [TwoFactorRecoveryCode], [EquivalentDomains], [ExcludedGlobalEquivalentDomains], [AccountRevisionDate], [Key], [PublicKey], [PrivateKey], [Premium], [PremiumExpirationDate], [RenewalReminderDate], [Storage], [MaxStorageGb], [Gateway], [GatewayCustomerId], [GatewaySubscriptionId], [LicenseKey], [Kdf], [KdfIterations], [CreationDate], [RevisionDate] ) VALUES ( @Id, @Name, @Email, @EmailVerified, @MasterPassword, @MasterPasswordHint, @Culture, @SecurityStamp, @TwoFactorProviders, @TwoFactorRecoveryCode, @EquivalentDomains, @ExcludedGlobalEquivalentDomains, @AccountRevisionDate, @Key, @PublicKey, @PrivateKey, @Premium, @PremiumExpirationDate, @RenewalReminderDate, @Storage, @MaxStorageGb, @Gateway, @GatewayCustomerId, @GatewaySubscriptionId, @LicenseKey, @Kdf, @KdfIterations, @CreationDate, @RevisionDate ) END GO IF OBJECT_ID('[dbo].[User_Update]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[User_Update] END GO CREATE PROCEDURE [dbo].[User_Update] @Id UNIQUEIDENTIFIER, @Name NVARCHAR(50), @Email NVARCHAR(50), @EmailVerified BIT, @MasterPassword NVARCHAR(300), @MasterPasswordHint NVARCHAR(50), @Culture NVARCHAR(10), @SecurityStamp NVARCHAR(50), @TwoFactorProviders NVARCHAR(MAX), @TwoFactorRecoveryCode NVARCHAR(32), @EquivalentDomains NVARCHAR(MAX), @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), @AccountRevisionDate DATETIME2(7), @Key NVARCHAR(MAX), @PublicKey NVARCHAR(MAX), @PrivateKey NVARCHAR(MAX), @Premium BIT, @PremiumExpirationDate DATETIME2(7), @RenewalReminderDate DATETIME2(7), @Storage BIGINT, @MaxStorageGb SMALLINT, @Gateway TINYINT, @GatewayCustomerId VARCHAR(50), @GatewaySubscriptionId VARCHAR(50), @LicenseKey VARCHAR(100), @Kdf TINYINT, @KdfIterations INT, @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) AS BEGIN SET NOCOUNT ON UPDATE [dbo].[User] SET [Name] = @Name, [Email] = @Email, [EmailVerified] = @EmailVerified, [MasterPassword] = @MasterPassword, [MasterPasswordHint] = @MasterPasswordHint, [Culture] = @Culture, [SecurityStamp] = @SecurityStamp, [TwoFactorProviders] = @TwoFactorProviders, [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, [EquivalentDomains] = @EquivalentDomains, [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, [AccountRevisionDate] = @AccountRevisionDate, [Key] = @Key, [PublicKey] = @PublicKey, [PrivateKey] = @PrivateKey, [Premium] = @Premium, [PremiumExpirationDate] = @PremiumExpirationDate, [RenewalReminderDate] = @RenewalReminderDate, [Storage] = @Storage, [MaxStorageGb] = @MaxStorageGb, [Gateway] = @Gateway, [GatewayCustomerId] = @GatewayCustomerId, [GatewaySubscriptionId] = @GatewaySubscriptionId, [LicenseKey] = @LicenseKey, [Kdf] = @Kdf, [KdfIterations] = @KdfIterations, [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate WHERE [Id] = @Id END GO IF OBJECT_ID('[dbo].[User_ReadKdfByEmail]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[User_ReadKdfByEmail] END GO CREATE PROCEDURE [dbo].[User_ReadKdfByEmail] @Email NVARCHAR(50) AS BEGIN SET NOCOUNT ON SELECT [Kdf], [KdfIterations] FROM [dbo].[User] WHERE [Email] = @Email 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 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. 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_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId SELECT 0 -- 0 = Success END GO IF OBJECT_ID('[dbo].[CollectionCipher_UpdateCollections]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[CollectionCipher_UpdateCollections] 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.[CollectionId] = C.[Id] 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 IF OBJECT_ID('[dbo].[CollectionCipher_UpdateCollectionsForCiphers]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsForCiphers] END GO CREATE PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsForCiphers] @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 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 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