From ed8d5d69a4923e6f856b7a73990f2790b2aaab62 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 21 Mar 2017 00:04:39 -0400 Subject: [PATCH] Move into and read ciphers from org subvaults --- src/Api/Controllers/CiphersController.cs | 15 +++++++ src/Core/Core.csproj | 1 + .../Models/Api/Request/CipherRequestModel.cs | 28 +++++++++--- .../Api/Response/CipherResponseModel.cs | 16 +++++++ .../Models/Api/Response/LoginResponseModel.cs | 2 + .../Models/Data/SubvaultUserPermissions.cs | 11 +++++ src/Core/Models/Table/Cipher.cs | 2 +- src/Core/Models/Table/SubvaultCipher.cs | 10 +++++ src/Core/Repositories/ICipherRepository.cs | 1 + .../Repositories/ISubvaultUserRepository.cs | 4 ++ .../SqlServer/CipherRepository.cs | 21 +++++++++ .../SqlServer/SubvaultUserRepository.cs | 29 +++++++++++++ src/Core/Services/ICipherService.cs | 2 + .../Services/Implementations/CipherService.cs | 39 +++++++++++++++++ .../Implementations/PushSharpPushService.cs | 4 +- src/Core/Utilities/CoreHelpers.cs | 27 +++++++++++- src/Sql/Sql.sqlproj | 4 ++ .../CipherDetails_ReadByUserId.sql | 15 +++++-- .../Cipher_UpdateWithSubvaults.sql | 43 +++++++++++++++++++ ...ltUser_ReadPermissionsBySubvaultUserId.sql | 22 ++++++++++ src/Sql/dbo/Tables/History.sql | 2 +- src/Sql/dbo/UserDefinedTypes/GuidIdArray.sql | 1 + 22 files changed, 283 insertions(+), 16 deletions(-) create mode 100644 src/Core/Models/Data/SubvaultUserPermissions.cs create mode 100644 src/Core/Models/Table/SubvaultCipher.cs create mode 100644 src/Sql/dbo/Stored Procedures/Cipher_UpdateWithSubvaults.sql create mode 100644 src/Sql/dbo/Stored Procedures/SubvaultUser_ReadPermissionsBySubvaultUserId.sql create mode 100644 src/Sql/dbo/UserDefinedTypes/GuidIdArray.sql diff --git a/src/Api/Controllers/CiphersController.cs b/src/Api/Controllers/CiphersController.cs index ce517bac7f..faa753abd2 100644 --- a/src/Api/Controllers/CiphersController.cs +++ b/src/Api/Controllers/CiphersController.cs @@ -88,6 +88,21 @@ namespace Bit.Api.Controllers // await _cipherService.SaveAsync(cipher); //} + [HttpPut("{id}/move")] + [HttpPost("{id}/move")] + public async Task PostMoveSubvault(string id, [FromBody]CipherMoveRequestModel model) + { + var userId = _userService.GetProperUserId(User).Value; + var cipher = await _cipherRepository.GetByIdAsync(new Guid(id)); + if(cipher == null) + { + throw new NotFoundException(); + } + + await _cipherService.MoveSubvaultAsync(model.Cipher.ToCipher(cipher), + model.SubvaultIds.Select(s => new Guid(s)), userId); + } + [HttpDelete("{id}")] [HttpPost("{id}/delete")] public async Task Delete(string id) diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 6e71211605..cddb6183a6 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Core/Models/Api/Request/CipherRequestModel.cs b/src/Core/Models/Api/Request/CipherRequestModel.cs index 17c492c799..04c77e0b14 100644 --- a/src/Core/Models/Api/Request/CipherRequestModel.cs +++ b/src/Core/Models/Api/Request/CipherRequestModel.cs @@ -4,6 +4,7 @@ using Bit.Core.Utilities; using Bit.Core.Models.Table; using Bit.Core.Enums; using Newtonsoft.Json; +using System.Collections.Generic; namespace Bit.Core.Models.Api { @@ -15,6 +16,8 @@ namespace Bit.Core.Models.Api [StringLength(36)] public string Id { get; set; } [StringLength(36)] + public string OrganizationId { get; set; } + [StringLength(36)] public string FolderId { get; set; } [Required] [EncryptedString] @@ -35,24 +38,35 @@ namespace Bit.Core.Models.Api public virtual Cipher ToCipher(Guid userId) { - var cipher = new Cipher + return ToCipher(new Cipher { Id = new Guid(Id), - UserId = userId, - //FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : (Guid?)new Guid(FolderId), + UserId = string.IsNullOrWhiteSpace(OrganizationId) ? (Guid?)userId : null, Type = Type - }; + }); + } - switch(Type) + public Cipher ToCipher(Cipher existingCipher) + { + existingCipher.OrganizationId = string.IsNullOrWhiteSpace(OrganizationId) ? null : (Guid?)new Guid(OrganizationId); + + switch(existingCipher.Type) { case CipherType.Login: - cipher.Data = JsonConvert.SerializeObject(new LoginDataModel(this), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingCipher.Data = JsonConvert.SerializeObject(new LoginDataModel(this), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); break; default: throw new ArgumentException("Unsupported " + nameof(Type) + "."); } - return cipher; + return existingCipher; } } + + public class CipherMoveRequestModel + { + public IEnumerable SubvaultIds { get; set; } + [Required] + public CipherRequestModel Cipher { get; set; } + } } diff --git a/src/Core/Models/Api/Response/CipherResponseModel.cs b/src/Core/Models/Api/Response/CipherResponseModel.cs index 2bc0a626c5..6486892b86 100644 --- a/src/Core/Models/Api/Response/CipherResponseModel.cs +++ b/src/Core/Models/Api/Response/CipherResponseModel.cs @@ -1,5 +1,8 @@ using System; using Core.Models.Data; +using System.Collections.Generic; +using Bit.Core.Models.Table; +using System.Linq; namespace Bit.Core.Models.Api { @@ -16,6 +19,7 @@ namespace Bit.Core.Models.Api Id = cipher.Id.ToString(); Type = cipher.Type; RevisionDate = cipher.RevisionDate; + OrganizationId = cipher.OrganizationId?.ToString(); FolderId = cipher.FolderId?.ToString(); Favorite = cipher.Favorite; @@ -30,10 +34,22 @@ namespace Bit.Core.Models.Api } public string Id { get; set; } + public string OrganizationId { get; set; } public string FolderId { get; set; } public Enums.CipherType Type { get; set; } public bool Favorite { get; set; } public dynamic Data { get; set; } public DateTime RevisionDate { get; set; } } + + public class CipherDetailsResponseModel : CipherResponseModel + { + public CipherDetailsResponseModel(CipherDetails cipher, IEnumerable subvaultCipher) + : base(cipher, "cipherDetails") + { + SubvaultIds = subvaultCipher.Select(s => s.SubvaultId); + } + + public IEnumerable SubvaultIds { get; set; } + } } diff --git a/src/Core/Models/Api/Response/LoginResponseModel.cs b/src/Core/Models/Api/Response/LoginResponseModel.cs index d547b9b00c..cc2f2282f2 100644 --- a/src/Core/Models/Api/Response/LoginResponseModel.cs +++ b/src/Core/Models/Api/Response/LoginResponseModel.cs @@ -21,6 +21,7 @@ namespace Bit.Core.Models.Api var data = new LoginDataModel(cipher); Id = cipher.Id.ToString(); + OrganizationId = cipher.OrganizationId?.ToString(); FolderId = cipher.FolderId?.ToString(); Favorite = cipher.Favorite; Name = data.Name; @@ -32,6 +33,7 @@ namespace Bit.Core.Models.Api } public string Id { get; set; } + public string OrganizationId { get; set; } public string FolderId { get; set; } public bool Favorite { get; set; } public string Name { get; set; } diff --git a/src/Core/Models/Data/SubvaultUserPermissions.cs b/src/Core/Models/Data/SubvaultUserPermissions.cs new file mode 100644 index 0000000000..0f121359df --- /dev/null +++ b/src/Core/Models/Data/SubvaultUserPermissions.cs @@ -0,0 +1,11 @@ +using System; + +namespace Bit.Core.Models.Data +{ + public class SubvaultUserPermissions + { + public Guid SubvaultId { get; set; } + public bool ReadOnly { get; set; } + public bool Admin { get; set; } + } +} diff --git a/src/Core/Models/Table/Cipher.cs b/src/Core/Models/Table/Cipher.cs index 0d7cc3d04b..94d386afeb 100644 --- a/src/Core/Models/Table/Cipher.cs +++ b/src/Core/Models/Table/Cipher.cs @@ -6,7 +6,7 @@ namespace Bit.Core.Models.Table public class Cipher : IDataObject { public Guid Id { get; set; } - public Guid UserId { get; set; } + public Guid? UserId { get; set; } public Guid? OrganizationId { get; set; } public Enums.CipherType Type { get; set; } public string Data { get; set; } diff --git a/src/Core/Models/Table/SubvaultCipher.cs b/src/Core/Models/Table/SubvaultCipher.cs new file mode 100644 index 0000000000..ae87ab41fc --- /dev/null +++ b/src/Core/Models/Table/SubvaultCipher.cs @@ -0,0 +1,10 @@ +using System; + +namespace Bit.Core.Models.Table +{ + public class SubvaultCipher + { + public Guid SubvaultId { get; set; } + public Guid CipherId { get; set; } + } +} diff --git a/src/Core/Repositories/ICipherRepository.cs b/src/Core/Repositories/ICipherRepository.cs index 892e5f94cd..5f7e9b776c 100644 --- a/src/Core/Repositories/ICipherRepository.cs +++ b/src/Core/Repositories/ICipherRepository.cs @@ -16,6 +16,7 @@ namespace Bit.Core.Repositories Task CreateAsync(CipherDetails cipher); Task ReplaceAsync(CipherDetails cipher); Task UpsertAsync(CipherDetails cipher); + Task ReplaceAsync(Cipher obj, IEnumerable subvaultIds); Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers); Task CreateAsync(IEnumerable ciphers); } diff --git a/src/Core/Repositories/ISubvaultUserRepository.cs b/src/Core/Repositories/ISubvaultUserRepository.cs index ce682ff7e1..883a118f3d 100644 --- a/src/Core/Repositories/ISubvaultUserRepository.cs +++ b/src/Core/Repositories/ISubvaultUserRepository.cs @@ -2,11 +2,15 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; using System.Collections.Generic; +using Bit.Core.Models.Data; namespace Bit.Core.Repositories { public interface ISubvaultUserRepository : IRepository { Task> GetManyByOrganizationUserIdAsync(Guid orgUserId); + Task> GetManyDetailsByUserIdAsync(Guid userId); + Task> GetPermissionsByUserIdAsync(Guid userId, IEnumerable subvaultIds, + Guid organizationId); } } diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index eb9abde8f3..d1e77f808f 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -8,6 +8,8 @@ using Bit.Core.Models.Table; using System.Data; using Dapper; using Core.Models.Data; +using Bit.Core.Utilities; +using Newtonsoft.Json; namespace Bit.Core.Repositories.SqlServer { @@ -120,6 +122,20 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task ReplaceAsync(Cipher obj, IEnumerable subvaultIds) + { + var objWithSubvaults = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + objWithSubvaults.SubvaultIds = subvaultIds.ToGuidIdArrayTVP(); + + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.ExecuteAsync( + $"[{Schema}].[Cipher_UpdateWithSubvaults]", + objWithSubvaults, + commandType: CommandType.StoredProcedure); + } + } + public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers) { if(ciphers.Count() == 0) @@ -252,5 +268,10 @@ namespace Bit.Core.Repositories.SqlServer return Task.FromResult(0); } + + public class CipherWithSubvaults : Cipher + { + public DataTable SubvaultIds { get; set; } + } } } diff --git a/src/Core/Repositories/SqlServer/SubvaultUserRepository.cs b/src/Core/Repositories/SqlServer/SubvaultUserRepository.cs index 565d16f82a..fecb589239 100644 --- a/src/Core/Repositories/SqlServer/SubvaultUserRepository.cs +++ b/src/Core/Repositories/SqlServer/SubvaultUserRepository.cs @@ -6,6 +6,8 @@ using System.Data; using System.Data.SqlClient; using Dapper; using System.Linq; +using Bit.Core.Models.Data; +using Bit.Core.Utilities; namespace Bit.Core.Repositories.SqlServer { @@ -31,5 +33,32 @@ namespace Bit.Core.Repositories.SqlServer return results.ToList(); } } + + public async Task> GetManyDetailsByUserIdAsync(Guid userId) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[SubvaultUserDetails_ReadByUserId]", + new { UserId = userId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task> GetPermissionsByUserIdAsync(Guid userId, + IEnumerable subvaultIds, Guid organizationId) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[SubvaultUser_ReadPermissionsBySubvaultUserId]", + new { UserId = userId, SubvaultIds = subvaultIds.ToGuidIdArrayTVP(), OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } } } diff --git a/src/Core/Services/ICipherService.cs b/src/Core/Services/ICipherService.cs index 0afe7e35ed..1bfdbf6d7f 100644 --- a/src/Core/Services/ICipherService.cs +++ b/src/Core/Services/ICipherService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; using Core.Models.Data; +using System; namespace Bit.Core.Services { @@ -11,6 +12,7 @@ namespace Bit.Core.Services Task DeleteAsync(Cipher cipher); Task SaveFolderAsync(Folder folder); Task DeleteFolderAsync(Folder folder); + Task MoveSubvaultAsync(Cipher cipher, IEnumerable subvaultIds, Guid userId); Task ImportCiphersAsync(List folders, List ciphers, IEnumerable> folderRelationships); } diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index c910f00932..fe64902bfb 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Core.Models.Data; +using Bit.Core.Exceptions; namespace Bit.Core.Services { @@ -13,17 +14,26 @@ namespace Bit.Core.Services private readonly ICipherRepository _cipherRepository; private readonly IFolderRepository _folderRepository; private readonly IUserRepository _userRepository; + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly ISubvaultUserRepository _subvaultUserRepository; private readonly IPushService _pushService; public CipherService( ICipherRepository cipherRepository, IFolderRepository folderRepository, IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ISubvaultUserRepository subvaultUserRepository, IPushService pushService) { _cipherRepository = cipherRepository; _folderRepository = folderRepository; _userRepository = userRepository; + _organizationRepository = organizationRepository; + _organizationUserRepository = organizationUserRepository; + _subvaultUserRepository = subvaultUserRepository; _pushService = pushService; } @@ -81,6 +91,35 @@ namespace Bit.Core.Services //await _pushService.PushSyncCipherDeleteAsync(cipher); } + public async Task MoveSubvaultAsync(Cipher cipher, IEnumerable subvaultIds, Guid userId) + { + if(cipher.Id == default(Guid)) + { + throw new BadRequestException(nameof(cipher.Id)); + } + + if(!cipher.OrganizationId.HasValue) + { + throw new BadRequestException(nameof(cipher.OrganizationId)); + } + + var existingCipher = await _cipherRepository.GetByIdAsync(cipher.Id); + if(existingCipher == null || (existingCipher.UserId.HasValue && existingCipher.UserId != userId)) + { + throw new NotFoundException(); + } + + var subvaultUserDetails = await _subvaultUserRepository.GetPermissionsByUserIdAsync(userId, subvaultIds, + cipher.OrganizationId.Value); + + cipher.UserId = null; + cipher.RevisionDate = DateTime.UtcNow; + await _cipherRepository.ReplaceAsync(cipher, subvaultUserDetails.Where(s => s.Admin).Select(s => s.SubvaultId)); + + // push + await _pushService.PushSyncCipherUpdateAsync(cipher); + } + public async Task ImportCiphersAsync( List folders, List ciphers, diff --git a/src/Core/Services/Implementations/PushSharpPushService.cs b/src/Core/Services/Implementations/PushSharpPushService.cs index de39fbf8ca..b3815e66f3 100644 --- a/src/Core/Services/Implementations/PushSharpPushService.cs +++ b/src/Core/Services/Implementations/PushSharpPushService.cs @@ -71,7 +71,7 @@ namespace Bit.Core.Services { Type = type, Id = cipher.Id, - UserId = cipher.UserId, + UserId = cipher.UserId.Value, RevisionDate = cipher.RevisionDate, Aps = new PushNotification.AppleData { ContentAvailable = 1 } }; @@ -82,7 +82,7 @@ namespace Bit.Core.Services excludedTokens.Add(_currentContext.DeviceIdentifier); } - await PushToAllUserDevicesAsync(cipher.UserId, JObject.FromObject(message), excludedTokens); + await PushToAllUserDevicesAsync(cipher.UserId.Value, JObject.FromObject(message), excludedTokens); } public async Task PushSyncCiphersAsync(Guid userId) diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 56bed884f0..e411041403 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -1,4 +1,7 @@ -using System; +using Dapper; +using System; +using System.Collections.Generic; +using System.Data; using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; @@ -40,6 +43,28 @@ namespace Bit.Core.Utilities return new Guid(guidArray); } + public static DataTable ToGuidIdArrayTVP(this IEnumerable ids) + { + return ids.ToArrayTVP("GuidId"); + } + + public static DataTable ToArrayTVP(this IEnumerable values, string columnName) + { + var table = new DataTable(); + table.Columns.Add(columnName, typeof(T)); + table.SetTypeName($"[dbo].[{columnName}Array]"); + + if(values != null) + { + foreach(var value in values) + { + table.Rows.Add(value); + } + } + + return table; + } + public static X509Certificate2 GetCertificate(string thumbprint) { // Clean possible garbage characters from thumbprint copy/paste diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 44f6e567ca..adf7ad4678 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -63,6 +63,7 @@ + @@ -168,5 +169,8 @@ + + + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/CipherDetails_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/CipherDetails_ReadByUserId.sql index 5b6a1e1e89..74c2a44cbb 100644 --- a/src/Sql/dbo/Stored Procedures/CipherDetails_ReadByUserId.sql +++ b/src/Sql/dbo/Stored Procedures/CipherDetails_ReadByUserId.sql @@ -4,10 +4,17 @@ AS BEGIN SET NOCOUNT ON - SELECT - * + SELECT DISTINCT + C.* FROM - [dbo].[CipherDetailsView] + [dbo].[CipherDetailsView] C + LEFT JOIN + [dbo].[SubvaultCipher] SC ON SC.[CipherId] = C.[Id] + LEFT JOIN + [dbo].[SubvaultUser] SU ON SU.[SubvaultId] = SC.[SubvaultId] + LEFT JOIN + [dbo].[OrganizationUser] OU ON OU.[Id] = SU.[OrganizationUserId] WHERE - [UserId] = @UserId + (C.[UserId] IS NOT NULL AND C.[UserId] = @UserId) + OR OU.[UserId] = @UserId END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithSubvaults.sql b/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithSubvaults.sql new file mode 100644 index 0000000000..811c9239ac --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Cipher_UpdateWithSubvaults.sql @@ -0,0 +1,43 @@ +CREATE PROCEDURE [dbo].[Cipher_UpdateWithSubvaults] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @SubvaultIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Cipher] + SET + [UserId] = @UserId, + [OrganizationId] = @OrganizationId, + [Type] = @Type, + [Data] = @Data, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE + [Id] = @Id + + MERGE + [dbo].[SubvaultCipher] AS [Target] + USING + @SubvaultIds AS [Source] + ON + [Target].[SubvaultId] = [Source].[Id] + AND [Target].[CipherId] = @Id + WHEN NOT MATCHED BY TARGET THEN + INSERT VALUES + ( + [Source].[Id], + @Id + ) + WHEN NOT MATCHED BY SOURCE + AND [Target].[CipherId] = @Id THEN + DELETE + ; +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/SubvaultUser_ReadPermissionsBySubvaultUserId.sql b/src/Sql/dbo/Stored Procedures/SubvaultUser_ReadPermissionsBySubvaultUserId.sql new file mode 100644 index 0000000000..3acb89b26a --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/SubvaultUser_ReadPermissionsBySubvaultUserId.sql @@ -0,0 +1,22 @@ +CREATE PROCEDURE [dbo].[SubvaultUser_ReadPermissionsBySubvaultUserId] + @UserId UNIQUEIDENTIFIER, + @SubvaultIds AS [dbo].[GuidIdArray] READONLY, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + SU.[SubvaultId], + CASE WHEN OU.[Type] = 2 THEN SU.[Admin] ELSE 1 END AS [Admin], -- 2 = Regular User + CASE WHEN OU.[Type] = 2 THEN SU.[ReadOnly] ELSE 0 END AS [ReadOnly] -- 2 = Regular User + FROM + [dbo].[SubvaultUser] SU + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.Id = SU.OrganizationUserId + WHERE + OU.[UserId] = @UserId + AND OU.[OrganizationId] = @OrganizationId + AND OU.[Status] = 2 -- 2 = Confirmed + AND SU.[SubvaultId] IN (SELECT [Id] FROM @SubvaultIds) +END \ No newline at end of file diff --git a/src/Sql/dbo/Tables/History.sql b/src/Sql/dbo/Tables/History.sql index e86b6d30d2..ec68d90ca0 100644 --- a/src/Sql/dbo/Tables/History.sql +++ b/src/Sql/dbo/Tables/History.sql @@ -1,6 +1,6 @@ CREATE TABLE [dbo].[History] ( [Id] BIGINT IDENTITY (1, 1) NOT NULL, - [UserId] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NULL, [CipherId] UNIQUEIDENTIFIER NOT NULL, [Event] TINYINT NOT NULL, [Date] DATETIME2 (7) NOT NULL, diff --git a/src/Sql/dbo/UserDefinedTypes/GuidIdArray.sql b/src/Sql/dbo/UserDefinedTypes/GuidIdArray.sql new file mode 100644 index 0000000000..4d3f49e4e4 --- /dev/null +++ b/src/Sql/dbo/UserDefinedTypes/GuidIdArray.sql @@ -0,0 +1 @@ +CREATE TYPE [dbo].[GuidIdArray] AS TABLE ([Id] UNIQUEIDENTIFIER NOT NULL);