diff --git a/Directory.Build.props b/Directory.Build.props index a98aa02c2a..bd7e8db982 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,9 @@ <Project> <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> <Version>1.41.5</Version> - <TargetFramework>netcoreapp3.1</TargetFramework> <RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace> </PropertyGroup> -</Project> \ No newline at end of file +</Project> diff --git a/README.md b/README.md index 9083fb1318..ea5cc68c53 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Please read the [Setup guide](https://github.com/bitwarden/server/blob/master/SE ### Requirements -- [.NET Core 3.1 SDK](https://www.microsoft.com/net/download/core) +- [.NET Core 5.0 SDK](https://www.microsoft.com/net/download/core) - [SQL Server 2017](https://docs.microsoft.com/en-us/sql/index) *These dependencies are free to use.* diff --git a/SETUP.md b/SETUP.md index 77c9b43b6e..06c1ef74eb 100644 --- a/SETUP.md +++ b/SETUP.md @@ -91,7 +91,7 @@ User secrets are a method for managing application settings on a per-developer b User secrets override the settings in `appsettings.json` of each project. Your user secrets file should match the structure of the `appsettings.json` file for the settings you intend to override. -For more information, see: [Safe storage of app secrets in development in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.1). +For more information, see: [Safe storage of app secrets in development in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-5.0). Open the server solution file (`bitwarden-server.sln`) in Visual Studio before proceeding. diff --git a/bitwarden-server.sln b/bitwarden-server.sln index 75d09e19d2..39f7dc8211 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -72,6 +72,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommCore.Test", "bitwarden_ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test - Bitwarden License", "test - Bitwarden License", "{287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MySqlMigrations", "util\MySqlMigrations\MySqlMigrations.csproj", "{BDC1D592-5947-47ED-9903-7CDBB12A50C8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostgresMigrations", "util\PostgresMigrations\PostgresMigrations.csproj", "{F72E0229-2EF7-49B3-9004-FF4C0043816E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -152,6 +156,15 @@ Global {C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {C7BA2255-C1B1-4789-8BB9-C27540DA6FB8}.Release|Any CPU.Build.0 = Release|Any CPU + {BDC1D592-5947-47ED-9903-7CDBB12A50C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDC1D592-5947-47ED-9903-7CDBB12A50C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDC1D592-5947-47ED-9903-7CDBB12A50C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDC1D592-5947-47ED-9903-7CDBB12A50C8}.Release|Any CPU.Build.0 = Release|Any CPU + {F72E0229-2EF7-49B3-9004-FF4C0043816E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F72E0229-2EF7-49B3-9004-FF4C0043816E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F72E0229-2EF7-49B3-9004-FF4C0043816E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F72E0229-2EF7-49B3-9004-FF4C0043816E}.Release|Any CPU.Build.0 = Release|Any CPU + {EDC0D688-D58C-4CE1-AA07-3606AC6874B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EDC0D688-D58C-4CE1-AA07-3606AC6874B8}.Debug|Any CPU.Build.0 = Debug|Any CPU {EDC0D688-D58C-4CE1-AA07-3606AC6874B8}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -183,6 +196,8 @@ Global {BA852F18-852F-4154-973B-77D577B8CA04} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A} {4866AF64-6640-4C65-A662-A31E02FF9064} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A} {C7BA2255-C1B1-4789-8BB9-C27540DA6FB8} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} + {BDC1D592-5947-47ED-9903-7CDBB12A50C8} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E} + {F72E0229-2EF7-49B3-9004-FF4C0043816E} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E} {EDC0D688-D58C-4CE1-AA07-3606AC6874B8} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A} {0E99A21B-684B-4C59-9831-90F775CAB6F7} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B} EndGlobalSection diff --git a/bitwarden_license/src/CommCore/CommCore.csproj b/bitwarden_license/src/CommCore/CommCore.csproj index d77902c605..a562ebbe94 100644 --- a/bitwarden_license/src/CommCore/CommCore.csproj +++ b/bitwarden_license/src/CommCore/CommCore.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netcoreapp3.1</TargetFramework> + <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> diff --git a/bitwarden_license/src/Portal/Dockerfile b/bitwarden_license/src/Portal/Dockerfile index e361700faa..8668004ffb 100644 --- a/bitwarden_license/src/Portal/Dockerfile +++ b/bitwarden_license/src/Portal/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/bitwarden_license/src/Sso/Dockerfile b/bitwarden_license/src/Sso/Dockerfile index e361700faa..8668004ffb 100644 --- a/bitwarden_license/src/Sso/Dockerfile +++ b/bitwarden_license/src/Sso/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/bitwarden_license/test/CmmCore.Test/CommCore.Test.csproj b/bitwarden_license/test/CmmCore.Test/CommCore.Test.csproj index 50d12b4f8c..5a319e5e89 100644 --- a/bitwarden_license/test/CmmCore.Test/CommCore.Test.csproj +++ b/bitwarden_license/test/CmmCore.Test/CommCore.Test.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netcoreapp3.1</TargetFramework> + <TargetFramework>net5.0</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> diff --git a/src/Admin/Dockerfile b/src/Admin/Dockerfile index 156091819a..f5a46d67f4 100644 --- a/src/Admin/Dockerfile +++ b/src/Admin/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index 84264f890f..3a5b1847f5 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -28,7 +28,7 @@ </Choose> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.6" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.2" /> <PackageReference Include="NewRelic.Agent" Version="8.30.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" /> <PackageReference Include="Microsoft.Azure.EventGrid" Version="3.2.0" /> diff --git a/src/Api/Dockerfile b/src/Api/Dockerfile index e361700faa..8668004ffb 100644 --- a/src/Api/Dockerfile +++ b/src/Api/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 5db7064b39..5367890044 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -27,18 +27,20 @@ <PackageReference Include="Fido2.AspNet" Version="1.1.0" /> <PackageReference Include="Handlebars.Net" Version="1.10.1" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" /> + <PackageReference Include="linq2db.EntityFrameworkCore" Version="5.2.1" /> <PackageReference Include="MailKit" Version="2.8.0" /> - <PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureStorage" Version="3.1.6" /> - <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.6" /> + <PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureStorage" Version="3.1.16" /> + <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.2" /> <PackageReference Include="Microsoft.Azure.Cosmos.Table" Version="1.0.7" /> <PackageReference Include="Microsoft.Azure.NotificationHubs" Version="3.3.0" /> <PackageReference Include="Microsoft.Azure.ServiceBus" Version="4.1.3" /> <PackageReference Include="Microsoft.Azure.Storage.Blob" Version="11.1.7" /> - <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.6" /> - <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.6" /> - <PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="3.1.6" /> - <PackageReference Include="Npgsql" Version="4.1.4" /> - <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.5" /> + <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="5.0.0" /> + <PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="5.0.2" /> + <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2" /> + <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.0" /> <PackageReference Include="Quartz" Version="3.1.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" /> diff --git a/src/Core/Enums/CipherStateAction.cs b/src/Core/Enums/CipherStateAction.cs new file mode 100644 index 0000000000..1d2c1dcf6d --- /dev/null +++ b/src/Core/Enums/CipherStateAction.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Enums +{ + public enum CipherStateAction + { + Restore, + SoftDelete, + HardDelete, + } +} diff --git a/src/Core/Enums/SupportedDatabaseProviders.cs b/src/Core/Enums/SupportedDatabaseProviders.cs new file mode 100644 index 0000000000..301297d4b8 --- /dev/null +++ b/src/Core/Enums/SupportedDatabaseProviders.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Enums +{ + public enum SupportedDatabaseProviders + { + SqlServer, + MySql, + Postgres, + } +} + diff --git a/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs b/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs index e7b563b046..b3728e812a 100644 --- a/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs +++ b/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs @@ -52,8 +52,8 @@ namespace Bit.Core.Models.Api public bool UseBusinessPortal => UsePolicies || UseSso; // TODO add events if needed public bool UsersGetPremium { get; set; } public bool SelfHost { get; set; } - public int Seats { get; set; } - public int MaxCollections { get; set; } + public int? Seats { get; set; } + public short? MaxCollections { get; set; } public short? MaxStorageGb { get; set; } public string Key { get; set; } public OrganizationUserStatusType Status { get; set; } diff --git a/src/Core/Models/Data/GroupWithCollections.cs b/src/Core/Models/Data/GroupWithCollections.cs new file mode 100644 index 0000000000..a3cbe786fa --- /dev/null +++ b/src/Core/Models/Data/GroupWithCollections.cs @@ -0,0 +1,10 @@ +using System.Data; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Data +{ + public class GroupWithCollections : Group + { + public DataTable Collections { get; set; } + } +} diff --git a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs index 4e6748ce11..dc5c13f2e3 100644 --- a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs +++ b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs @@ -19,8 +19,8 @@ namespace Bit.Core.Models.Data public bool UseBusinessPortal => UsePolicies || UseSso; public bool SelfHost { get; set; } public bool UsersGetPremium { get; set; } - public int Seats { get; set; } - public int MaxCollections { get; set; } + public int? Seats { get; set; } + public short? MaxCollections { get; set; } public short? MaxStorageGb { get; set; } public string Key { get; set; } public Enums.OrganizationUserStatusType Status { get; set; } diff --git a/src/Core/Models/Data/OrganizationUserWithCollections.cs b/src/Core/Models/Data/OrganizationUserWithCollections.cs new file mode 100644 index 0000000000..f1b7d11f64 --- /dev/null +++ b/src/Core/Models/Data/OrganizationUserWithCollections.cs @@ -0,0 +1,10 @@ +using System.Data; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Data +{ + public class OrganizationUserWithCollections : OrganizationUser + { + public DataTable Collections { get; set; } + } +} diff --git a/src/Core/Models/EntityFramework/Cipher.cs b/src/Core/Models/EntityFramework/Cipher.cs index 6edfe8773b..59b21cc14c 100644 --- a/src/Core/Models/EntityFramework/Cipher.cs +++ b/src/Core/Models/EntityFramework/Cipher.cs @@ -1,57 +1,14 @@ -using System.Text.Json; +using System.Collections.Generic; +using System.Text.Json; using AutoMapper; namespace Bit.Core.Models.EntityFramework { public class Cipher : Table.Cipher { - private JsonDocument _dataJson; - private JsonDocument _attachmentsJson; - private JsonDocument _favoritesJson; - private JsonDocument _foldersJson; - - public User User { get; set; } - public Organization Organization { get; set; } - [IgnoreMap] - public JsonDocument DataJson - { - get => _dataJson; - set - { - Data = value?.ToString(); - _dataJson = value; - } - } - [IgnoreMap] - public JsonDocument AttachmentsJson - { - get => _attachmentsJson; - set - { - Attachments = value?.ToString(); - _attachmentsJson = value; - } - } - [IgnoreMap] - public JsonDocument FavoritesJson - { - get => _favoritesJson; - set - { - Favorites = value?.ToString(); - _favoritesJson = value; - } - } - [IgnoreMap] - public JsonDocument FoldersJson - { - get => _foldersJson; - set - { - Folders = value?.ToString(); - _foldersJson = value; - } - } + public virtual User User { get; set; } + public virtual Organization Organization { get; set; } + public virtual ICollection<CollectionCipher> CollectionCiphers { get; set; } } public class CipherMapperProfile : Profile diff --git a/src/Core/Models/EntityFramework/Collection.cs b/src/Core/Models/EntityFramework/Collection.cs new file mode 100644 index 0000000000..697f724d96 --- /dev/null +++ b/src/Core/Models/EntityFramework/Collection.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Collection : Table.Collection + { + public virtual Organization Organization { get; set; } + public virtual ICollection<CollectionUser> CollectionUsers { get; set; } + public virtual ICollection<CollectionCipher> CollectionCiphers { get; set; } + public virtual ICollection<CollectionGroup> CollectionGroups { get; set; } + } + + public class CollectionMapperProfile : Profile + { + public CollectionMapperProfile() + { + CreateMap<Table.Collection, Collection>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/CollectionCipher.cs b/src/Core/Models/EntityFramework/CollectionCipher.cs new file mode 100644 index 0000000000..00650b92d4 --- /dev/null +++ b/src/Core/Models/EntityFramework/CollectionCipher.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class CollectionCipher : Table.CollectionCipher + { + public virtual Cipher Cipher { get; set; } + public virtual Collection Collection { get; set; } + } + + public class CollectionCipherMapperProfile : Profile + { + public CollectionCipherMapperProfile() + { + CreateMap<Table.CollectionCipher, CollectionCipher>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/CollectionGroup.cs b/src/Core/Models/EntityFramework/CollectionGroup.cs new file mode 100644 index 0000000000..afd714fdff --- /dev/null +++ b/src/Core/Models/EntityFramework/CollectionGroup.cs @@ -0,0 +1,18 @@ +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class CollectionGroup : Table.CollectionGroup + { + public virtual Collection Collection { get; set; } + public virtual Group Group { get; set; } + } + + public class CollectionGroupMapperProfile : Profile + { + public CollectionGroupMapperProfile() + { + CreateMap<Table.CollectionGroup, CollectionGroup>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/CollectionUser.cs b/src/Core/Models/EntityFramework/CollectionUser.cs new file mode 100644 index 0000000000..ec5a7e62ec --- /dev/null +++ b/src/Core/Models/EntityFramework/CollectionUser.cs @@ -0,0 +1,18 @@ +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class CollectionUser : Table.CollectionUser + { + public virtual Collection Collection { get; set; } + public virtual OrganizationUser OrganizationUser { get; set; } + } + + public class CollectionUserMapperProfile : Profile + { + public CollectionUserMapperProfile() + { + CreateMap<Table.CollectionUser, CollectionUser>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Device.cs b/src/Core/Models/EntityFramework/Device.cs new file mode 100644 index 0000000000..a70e97cc32 --- /dev/null +++ b/src/Core/Models/EntityFramework/Device.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Device : Table.Device + { + public virtual User User { get; set; } + } + + public class DeviceMapperProfile : Profile + { + public DeviceMapperProfile() + { + CreateMap<Table.Device, Device>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/EmergencyAccess.cs b/src/Core/Models/EntityFramework/EmergencyAccess.cs new file mode 100644 index 0000000000..950e386b75 --- /dev/null +++ b/src/Core/Models/EntityFramework/EmergencyAccess.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class EmergencyAccess : Table.EmergencyAccess + { + public virtual User Grantee { get; set; } + public virtual User Grantor { get; set; } + } + + public class EmergencyAccessMapperProfile : Profile + { + public EmergencyAccessMapperProfile() + { + CreateMap<Table.EmergencyAccess, EmergencyAccess>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Event.cs b/src/Core/Models/EntityFramework/Event.cs new file mode 100644 index 0000000000..3e9faa2bc7 --- /dev/null +++ b/src/Core/Models/EntityFramework/Event.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Event : Table.Event + { + } + + public class EventMapperProfile : Profile + { + public EventMapperProfile() + { + CreateMap<Table.Event, Event>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Folder.cs b/src/Core/Models/EntityFramework/Folder.cs new file mode 100644 index 0000000000..6e5bdaf0f9 --- /dev/null +++ b/src/Core/Models/EntityFramework/Folder.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Folder : Table.Folder + { + public virtual User User { get; set; } + } + + public class FolderMapperProfile : Profile + { + public FolderMapperProfile() + { + CreateMap<Table.Folder, Folder>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Grant.cs b/src/Core/Models/EntityFramework/Grant.cs new file mode 100644 index 0000000000..e110233d9e --- /dev/null +++ b/src/Core/Models/EntityFramework/Grant.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Grant : Table.Grant + { + } + + public class GrantMapperProfile : Profile + { + public GrantMapperProfile() + { + CreateMap<Table.Grant, Grant>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Group.cs b/src/Core/Models/EntityFramework/Group.cs new file mode 100644 index 0000000000..122ded667b --- /dev/null +++ b/src/Core/Models/EntityFramework/Group.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Group : Table.Group + { + public virtual Organization Organization { get; set; } + public virtual ICollection<GroupUser> GroupUsers { get; set; } + } + + public class GroupMapperProfile : Profile + { + public GroupMapperProfile() + { + CreateMap<Table.Group, Group>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/GroupUser.cs b/src/Core/Models/EntityFramework/GroupUser.cs new file mode 100644 index 0000000000..55f18d8ad2 --- /dev/null +++ b/src/Core/Models/EntityFramework/GroupUser.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class GroupUser : Table.GroupUser + { + public virtual Group Group { get; set; } + public virtual OrganizationUser OrganizationUser { get; set; } + } + + public class GroupUserMapperProfile : Profile + { + public GroupUserMapperProfile() + { + CreateMap<Table.GroupUser, GroupUser>().ReverseMap(); + } + } +} + diff --git a/src/Core/Models/EntityFramework/Installation.cs b/src/Core/Models/EntityFramework/Installation.cs new file mode 100644 index 0000000000..6b65fdd291 --- /dev/null +++ b/src/Core/Models/EntityFramework/Installation.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Installation : Table.Installation + { + } + + public class InstallationMapperProfile : Profile + { + public InstallationMapperProfile() + { + CreateMap<Table.Installation, Installation>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Organization.cs b/src/Core/Models/EntityFramework/Organization.cs index 2ee5c4c190..ff3d29b9a4 100644 --- a/src/Core/Models/EntityFramework/Organization.cs +++ b/src/Core/Models/EntityFramework/Organization.cs @@ -1,25 +1,17 @@ using System.Collections.Generic; -using System.Text.Json; using AutoMapper; namespace Bit.Core.Models.EntityFramework { public class Organization : Table.Organization { - private JsonDocument _twoFactorProvidersJson; - - public ICollection<Cipher> Ciphers { get; set; } - - [IgnoreMap] - public JsonDocument TwoFactorProvidersJson - { - get => _twoFactorProvidersJson; - set - { - TwoFactorProviders = value?.ToString(); - _twoFactorProvidersJson = value; - } - } + public virtual ICollection<Cipher> Ciphers { get; set; } + public virtual ICollection<OrganizationUser> OrganizationUsers { get; set; } + public virtual ICollection<Group> Groups { get; set; } + public virtual ICollection<Policy> Policies { get; set; } + public virtual ICollection<SsoConfig> SsoConfigs { get; set; } + public virtual ICollection<SsoUser> SsoUsers { get; set; } + public virtual ICollection<Transaction> Transactions { get; set; } } public class OrganizationMapperProfile : Profile diff --git a/src/Core/Models/EntityFramework/OrganizationUser.cs b/src/Core/Models/EntityFramework/OrganizationUser.cs new file mode 100644 index 0000000000..de75f24d6e --- /dev/null +++ b/src/Core/Models/EntityFramework/OrganizationUser.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class OrganizationUser : Table.OrganizationUser + { + public virtual Organization Organization { get; set; } + public virtual User User { get; set; } + public virtual ICollection<CollectionUser> CollectionUsers { get; set; } + } + + public class OrganizationUserMapperProfile : Profile + { + public OrganizationUserMapperProfile() + { + CreateMap<Table.OrganizationUser, OrganizationUser>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Policy.cs b/src/Core/Models/EntityFramework/Policy.cs new file mode 100644 index 0000000000..3646e58ad9 --- /dev/null +++ b/src/Core/Models/EntityFramework/Policy.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Policy : Table.Policy + { + public virtual Organization Organization { get; set; } + } + + public class PolicyMapperProfile : Profile + { + public PolicyMapperProfile() + { + CreateMap<Table.Policy, Policy>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Provider/Provider.cs b/src/Core/Models/EntityFramework/Provider/Provider.cs new file mode 100644 index 0000000000..fc595b4498 --- /dev/null +++ b/src/Core/Models/EntityFramework/Provider/Provider.cs @@ -0,0 +1,16 @@ +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework.Provider +{ + public class Provider : Table.Provider.Provider + { + } + + public class ProviderMapperProfile : Profile + { + public ProviderMapperProfile() + { + CreateMap<Table.Provider.Provider, Provider>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Provider/ProviderOrganization.cs b/src/Core/Models/EntityFramework/Provider/ProviderOrganization.cs new file mode 100644 index 0000000000..5f1077d81c --- /dev/null +++ b/src/Core/Models/EntityFramework/Provider/ProviderOrganization.cs @@ -0,0 +1,18 @@ +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework.Provider +{ + public class ProviderOrganization : Table.Provider.ProviderOrganization + { + public virtual Provider Provider { get; set; } + public virtual Organization Organization { get; set; } + } + + public class ProviderOrganizationMapperProfile : Profile + { + public ProviderOrganizationMapperProfile() + { + CreateMap<Table.Provider.ProviderOrganization, ProviderOrganization>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Provider/ProviderOrganizationProviderUser.cs b/src/Core/Models/EntityFramework/Provider/ProviderOrganizationProviderUser.cs new file mode 100644 index 0000000000..70db4fb7ed --- /dev/null +++ b/src/Core/Models/EntityFramework/Provider/ProviderOrganizationProviderUser.cs @@ -0,0 +1,18 @@ +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework.Provider +{ + public class ProviderOrganizationProviderUser : Table.Provider.ProviderOrganizationProviderUser + { + public virtual ProviderOrganization ProviderOrganization { get; set; } + public virtual ProviderUser ProviderUser { get; set; } + } + + public class ProviderOrganizationProviderUserMapperProfile : Profile + { + public ProviderOrganizationProviderUserMapperProfile() + { + CreateMap<Table.Provider.ProviderOrganizationProviderUser, ProviderOrganizationProviderUser>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Provider/ProviderUser.cs b/src/Core/Models/EntityFramework/Provider/ProviderUser.cs new file mode 100644 index 0000000000..14d22764f7 --- /dev/null +++ b/src/Core/Models/EntityFramework/Provider/ProviderUser.cs @@ -0,0 +1,18 @@ +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework.Provider +{ + public class ProviderUser : Table.Provider.ProviderUser + { + public virtual User User { get; set; } + public virtual Provider Provider { get; set; } + } + + public class ProviderUserMapperProfile : Profile + { + public ProviderUserMapperProfile() + { + CreateMap<Table.Provider.ProviderUser, ProviderUser>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Role.cs b/src/Core/Models/EntityFramework/Role.cs new file mode 100644 index 0000000000..706e6cbd19 --- /dev/null +++ b/src/Core/Models/EntityFramework/Role.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Role : Table.Role + { + } + + public class RoleMapperProfile : Profile + { + public RoleMapperProfile() + { + CreateMap<Table.Role, Role>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Send.cs b/src/Core/Models/EntityFramework/Send.cs new file mode 100644 index 0000000000..517e077496 --- /dev/null +++ b/src/Core/Models/EntityFramework/Send.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Send : Table.Send + { + public virtual Organization Organization { get; set; } + public virtual User User { get; set; } + } + + public class SendMapperProfile : Profile + { + public SendMapperProfile() + { + CreateMap<Table.Send, Send>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/SsoConfig.cs b/src/Core/Models/EntityFramework/SsoConfig.cs new file mode 100644 index 0000000000..74b83f6618 --- /dev/null +++ b/src/Core/Models/EntityFramework/SsoConfig.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class SsoConfig : Table.SsoConfig + { + public virtual Organization Organization { get; set; } + } + + public class SsoConfigMapperProfile : Profile + { + public SsoConfigMapperProfile() + { + CreateMap<Table.SsoConfig, SsoConfig>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/SsoUser.cs b/src/Core/Models/EntityFramework/SsoUser.cs new file mode 100644 index 0000000000..616e4d8649 --- /dev/null +++ b/src/Core/Models/EntityFramework/SsoUser.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class SsoUser : Table.SsoUser + { + public virtual Organization Organization { get; set; } + public virtual User User { get; set; } + } + + public class SsoUserMapperProfile : Profile + { + public SsoUserMapperProfile() + { + CreateMap<Table.SsoUser, SsoUser>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/TaxRate.cs b/src/Core/Models/EntityFramework/TaxRate.cs new file mode 100644 index 0000000000..1fe2b499e9 --- /dev/null +++ b/src/Core/Models/EntityFramework/TaxRate.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class TaxRate : Table.TaxRate + { + } + + public class TaxRateMapperProfile : Profile + { + public TaxRateMapperProfile() + { + CreateMap<Table.TaxRate, TaxRate>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/Transaction.cs b/src/Core/Models/EntityFramework/Transaction.cs new file mode 100644 index 0000000000..bef6271dff --- /dev/null +++ b/src/Core/Models/EntityFramework/Transaction.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Transaction : Table.Transaction + { + public virtual Organization Organization { get; set; } + public virtual User User { get; set; } + } + + public class TransactionMapperProfile : Profile + { + public TransactionMapperProfile() + { + CreateMap<Table.Transaction, Transaction>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/U2f.cs b/src/Core/Models/EntityFramework/U2f.cs new file mode 100644 index 0000000000..206baff10a --- /dev/null +++ b/src/Core/Models/EntityFramework/U2f.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class U2f : Table.U2f + { + public virtual User User { get; set; } + } + + public class U2fMapperProfile : Profile + { + public U2fMapperProfile() + { + CreateMap<Table.U2f, U2f>().ReverseMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/User.cs b/src/Core/Models/EntityFramework/User.cs index 5f1aff6daa..1836bec44f 100644 --- a/src/Core/Models/EntityFramework/User.cs +++ b/src/Core/Models/EntityFramework/User.cs @@ -6,20 +6,14 @@ namespace Bit.Core.Models.EntityFramework { public class User : Table.User { - private JsonDocument _twoFactorProvidersJson; - - public ICollection<Cipher> Ciphers { get; set; } - - [IgnoreMap] - public JsonDocument TwoFactorProvidersJson - { - get => _twoFactorProvidersJson; - set - { - TwoFactorProviders = value?.ToString(); - _twoFactorProvidersJson = value; - } - } + public virtual ICollection<Cipher> Ciphers { get; set; } + public virtual ICollection<Folder> Folders { get; set; } + public virtual ICollection<CollectionUser> CollectionUsers { get; set; } + public virtual ICollection<GroupUser> GroupUsers { get; set; } + public virtual ICollection<OrganizationUser> OrganizationUsers { get; set; } + public virtual ICollection<SsoUser> SsoUsers { get; set; } + public virtual ICollection<Transaction> Transactions { get; set; } + public virtual ICollection<U2f> U2fs { get; set; } } public class UserMapperProfile : Profile diff --git a/src/Core/Models/Table/Collection.cs b/src/Core/Models/Table/Collection.cs index 369592789e..85cb287386 100644 --- a/src/Core/Models/Table/Collection.cs +++ b/src/Core/Models/Table/Collection.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Utilities; namespace Bit.Core.Models.Table @@ -8,6 +9,7 @@ namespace Bit.Core.Models.Table public Guid Id { get; set; } public Guid OrganizationId { get; set; } public string Name { get; set; } + [MaxLength(300)] public string ExternalId { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Core/Models/Table/CollectionGroup.cs b/src/Core/Models/Table/CollectionGroup.cs new file mode 100644 index 0000000000..b510e05e08 --- /dev/null +++ b/src/Core/Models/Table/CollectionGroup.cs @@ -0,0 +1,13 @@ +using System; +using Bit.Core.Utilities; + +namespace Bit.Core.Models.Table +{ + public class CollectionGroup + { + public Guid CollectionId { get; set; } + public Guid GroupId { get; set; } + public bool ReadOnly { get; set; } + public bool HidePasswords { get; set; } + } +} diff --git a/src/Core/Models/Table/CollectionUser.cs b/src/Core/Models/Table/CollectionUser.cs new file mode 100644 index 0000000000..3bcf15d706 --- /dev/null +++ b/src/Core/Models/Table/CollectionUser.cs @@ -0,0 +1,13 @@ +using System; +using Bit.Core.Utilities; + +namespace Bit.Core.Models.Table +{ + public class CollectionUser + { + public Guid CollectionId { get; set; } + public Guid OrganizationUserId { get; set; } + public bool ReadOnly { get; set; } + public bool HidePasswords { get; set; } + } +} diff --git a/src/Core/Models/Table/Device.cs b/src/Core/Models/Table/Device.cs index 4fb20652df..b4e740e5be 100644 --- a/src/Core/Models/Table/Device.cs +++ b/src/Core/Models/Table/Device.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Utilities; namespace Bit.Core.Models.Table @@ -7,9 +8,12 @@ namespace Bit.Core.Models.Table { public Guid Id { get; set; } public Guid UserId { get; set; } + [MaxLength(50)] public string Name { get; set; } public Enums.DeviceType Type { get; set; } + [MaxLength(50)] public string Identifier { get; set; } + [MaxLength(255)] public string PushToken { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Core/Models/Table/EmergencyAccess.cs b/src/Core/Models/Table/EmergencyAccess.cs index fe118ca76a..0e256cff37 100644 --- a/src/Core/Models/Table/EmergencyAccess.cs +++ b/src/Core/Models/Table/EmergencyAccess.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; using Bit.Core.Utilities; @@ -9,6 +10,7 @@ namespace Bit.Core.Models.Table public Guid Id { get; set; } public Guid GrantorId { get; set; } public Guid? GranteeId { get; set; } + [MaxLength(256)] public string Email { get; set; } public string KeyEncrypted { get; set; } public EmergencyAccessType Type { get; set; } diff --git a/src/Core/Models/Table/Event.cs b/src/Core/Models/Table/Event.cs index 6b00a7b81c..eb68289fb8 100644 --- a/src/Core/Models/Table/Event.cs +++ b/src/Core/Models/Table/Event.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Utilities; @@ -36,6 +37,7 @@ namespace Bit.Core.Models.Table public Guid? GroupId { get; set; } public Guid? OrganizationUserId { get; set; } public DeviceType? DeviceType { get; set; } + [MaxLength(50)] public string IpAddress { get; set; } public Guid? ActingUserId { get; set; } diff --git a/src/Core/Models/Table/Grant.cs b/src/Core/Models/Table/Grant.cs index 785fa5d1a0..e8b052d114 100644 --- a/src/Core/Models/Table/Grant.cs +++ b/src/Core/Models/Table/Grant.cs @@ -1,14 +1,21 @@ using System; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Table { public class Grant { + [MaxLength(200)] public string Key { get; set; } + [MaxLength(50)] public string Type { get; set; } + [MaxLength(200)] public string SubjectId { get; set; } + [MaxLength(100)] public string SessionId { get; set; } + [MaxLength(200)] public string ClientId { get; set; } + [MaxLength(200)] public string Description { get; set; } public DateTime CreationDate { get; set; } public DateTime? ExpirationDate { get; set; } diff --git a/src/Core/Models/Table/Group.cs b/src/Core/Models/Table/Group.cs index bdb72b4d5d..d732746886 100644 --- a/src/Core/Models/Table/Group.cs +++ b/src/Core/Models/Table/Group.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Utilities; namespace Bit.Core.Models.Table @@ -7,8 +8,10 @@ namespace Bit.Core.Models.Table { public Guid Id { get; set; } public Guid OrganizationId { get; set; } + [MaxLength(100)] public string Name { get; set; } public bool AccessAll { get; set; } + [MaxLength(300)] public string ExternalId { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Core/Models/Table/Installation.cs b/src/Core/Models/Table/Installation.cs index e7abc7d0d7..631fa5bf6d 100644 --- a/src/Core/Models/Table/Installation.cs +++ b/src/Core/Models/Table/Installation.cs @@ -1,12 +1,15 @@ using Bit.Core.Utilities; using System; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Table { public class Installation : ITableObject<Guid> { public Guid Id { get; set; } + [MaxLength(256)] public string Email { get; set; } + [MaxLength(150)] public string Key { get; set; } public bool Enabled { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Core/Models/Table/Organization.cs b/src/Core/Models/Table/Organization.cs index 6af3e46656..4a23bcd163 100644 --- a/src/Core/Models/Table/Organization.cs +++ b/src/Core/Models/Table/Organization.cs @@ -4,6 +4,7 @@ using Bit.Core.Enums; using System.Collections.Generic; using Newtonsoft.Json; using System.Linq; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Table { @@ -12,15 +13,25 @@ namespace Bit.Core.Models.Table private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders; public Guid Id { get; set; } + [MaxLength(50)] public string Identifier { get; set; } + [MaxLength(50)] public string Name { get; set; } + [MaxLength(50)] public string BusinessName { get; set; } + [MaxLength(50)] public string BusinessAddress1 { get; set; } + [MaxLength(50)] public string BusinessAddress2 { get; set; } + [MaxLength(50)] public string BusinessAddress3 { get; set; } + [MaxLength(2)] public string BusinessCountry { get; set; } + [MaxLength(30)] public string BusinessTaxNumber { get; set; } + [MaxLength(256)] public string BillingEmail { get; set; } + [MaxLength(50)] public string Plan { get; set; } public PlanType PlanType { get; set; } public int? Seats { get; set; } @@ -39,11 +50,15 @@ namespace Bit.Core.Models.Table public long? Storage { get; set; } public short? MaxStorageGb { get; set; } public GatewayType? Gateway { get; set; } + [MaxLength(50)] public string GatewayCustomerId { get; set; } + [MaxLength(50)] public string GatewaySubscriptionId { get; set; } public string ReferenceData { get; set; } public bool Enabled { get; set; } = true; + [MaxLength(100)] public string LicenseKey { get; set; } + [MaxLength(30)] public string ApiKey { get; set; } public string PublicKey { get; set; } public string PrivateKey { get; set; } diff --git a/src/Core/Models/Table/OrganizationUser.cs b/src/Core/Models/Table/OrganizationUser.cs index 13ab71ab3f..5a275ec753 100644 --- a/src/Core/Models/Table/OrganizationUser.cs +++ b/src/Core/Models/Table/OrganizationUser.cs @@ -1,6 +1,7 @@ using System; using Bit.Core.Utilities; using Bit.Core.Enums; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Table { @@ -9,12 +10,14 @@ namespace Bit.Core.Models.Table public Guid Id { get; set; } public Guid OrganizationId { get; set; } public Guid? UserId { get; set; } + [MaxLength(256)] public string Email { get; set; } public string Key { get; set; } public string ResetPasswordKey { get; set; } public OrganizationUserStatusType Status { get; set; } public OrganizationUserType Type { get; set; } public bool AccessAll { get; set; } + [MaxLength(300)] public string ExternalId { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Core/Models/Table/Send.cs b/src/Core/Models/Table/Send.cs index 2660e9e8a6..36607d965f 100644 --- a/src/Core/Models/Table/Send.cs +++ b/src/Core/Models/Table/Send.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; using Bit.Core.Utilities; @@ -12,6 +13,7 @@ namespace Bit.Core.Models.Table public SendType Type { get; set; } public string Data { get; set; } public string Key { get; set; } + [MaxLength(300)] public string Password { get; set; } public int? MaxAccessCount { get; set; } public int AccessCount { get; set; } diff --git a/src/Core/Models/Table/SsoConfig.cs b/src/Core/Models/Table/SsoConfig.cs index 5e5d47f4a2..6a1b232126 100644 --- a/src/Core/Models/Table/SsoConfig.cs +++ b/src/Core/Models/Table/SsoConfig.cs @@ -13,7 +13,8 @@ namespace Bit.Core.Models.Table public void SetNewId() { - // nothing - int will be auto-populated + // int will be auto-populated + Id = 0; } } } diff --git a/src/Core/Models/Table/SsoUser.cs b/src/Core/Models/Table/SsoUser.cs index da4e38528e..6c7cd6257f 100644 --- a/src/Core/Models/Table/SsoUser.cs +++ b/src/Core/Models/Table/SsoUser.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Table { @@ -7,12 +8,14 @@ namespace Bit.Core.Models.Table public long Id { get; set; } public Guid UserId { get; set; } public Guid? OrganizationId { get; set; } + [MaxLength(50)] public string ExternalId { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public void SetNewId() { - // nothing - int will be auto-populated + // int will be auto-populated + Id = 0; } } } diff --git a/src/Core/Models/Table/TaxRate.cs b/src/Core/Models/Table/TaxRate.cs index 195465efda..a9a7585774 100644 --- a/src/Core/Models/Table/TaxRate.cs +++ b/src/Core/Models/Table/TaxRate.cs @@ -1,10 +1,16 @@ +using System.ComponentModel.DataAnnotations; + namespace Bit.Core.Models.Table { public class TaxRate: ITableObject<string> { + [MaxLength(40)] public string Id { get; set; } + [MaxLength(50)] public string Country { get; set; } + [MaxLength(2)] public string State { get; set; } + [MaxLength(10)] public string PostalCode { get; set; } public decimal Rate { get; set; } public bool Active { get; set; } diff --git a/src/Core/Models/Table/Transaction.cs b/src/Core/Models/Table/Transaction.cs index 637f40f403..0f5e80e457 100644 --- a/src/Core/Models/Table/Transaction.cs +++ b/src/Core/Models/Table/Transaction.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; using Bit.Core.Utilities; @@ -13,9 +14,11 @@ namespace Bit.Core.Models.Table public decimal Amount { get; set; } public bool? Refunded { get; set; } public decimal? RefundedAmount { get; set; } + [MaxLength(100)] public string Details { get; set; } public PaymentMethodType? PaymentMethodType { get; set; } public GatewayType? Gateway { get; set; } + [MaxLength(50)] public string GatewayId { get; set; } public DateTime CreationDate { get; set; } = DateTime.UtcNow; diff --git a/src/Core/Models/Table/U2f.cs b/src/Core/Models/Table/U2f.cs index e8a97c7fe5..5d089dacaa 100644 --- a/src/Core/Models/Table/U2f.cs +++ b/src/Core/Models/Table/U2f.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Table { @@ -6,15 +7,20 @@ namespace Bit.Core.Models.Table { public int Id { get; set; } public Guid UserId { get; set; } + [MaxLength(200)] public string KeyHandle { get; set; } + [MaxLength(200)] public string Challenge { get; set; } + [MaxLength(50)] public string AppId { get; set; } + [MaxLength(20)] public string Version { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public void SetNewId() { - // do nothing since it is an identity + // int will be auto-populated + Id = 0; } } } diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index 20513e0967..12c3f50d28 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -4,6 +4,7 @@ using Bit.Core.Utilities; using System.Collections.Generic; using Newtonsoft.Json; using Microsoft.AspNetCore.Identity; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Table { @@ -12,14 +13,23 @@ namespace Bit.Core.Models.Table private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders; public Guid Id { get; set; } + [MaxLength(50)] public string Name { get; set; } + [Required] + [MaxLength(256)] public string Email { get; set; } public bool EmailVerified { get; set; } + [MaxLength(300)] public string MasterPassword { get; set; } + [MaxLength(50)] public string MasterPasswordHint { get; set; } + [MaxLength(10)] public string Culture { get; set; } = "en-US"; + [Required] + [MaxLength(50)] public string SecurityStamp { get; set; } public string TwoFactorProviders { get; set; } + [MaxLength(32)] public string TwoFactorRecoveryCode { get; set; } public string EquivalentDomains { get; set; } public string ExcludedGlobalEquivalentDomains { get; set; } @@ -33,10 +43,15 @@ namespace Bit.Core.Models.Table public long? Storage { get; set; } public short? MaxStorageGb { get; set; } public GatewayType? Gateway { get; set; } + [MaxLength(50)] public string GatewayCustomerId { get; set; } + [MaxLength(50)] public string GatewaySubscriptionId { get; set; } public string ReferenceData { get; set; } + [MaxLength(100)] public string LicenseKey { get; set; } + [Required] + [MaxLength(30)] public string ApiKey { get; set; } public KdfType Kdf { get; set; } = KdfType.PBKDF2_SHA256; public int KdfIterations { get; set; } = 5000; diff --git a/src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs b/src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs index fd20430111..d1b20764de 100644 --- a/src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs +++ b/src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs @@ -1,10 +1,28 @@ using AutoMapper; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework.Queries; +using EfModel = Bit.Core.Models.EntityFramework; +using LinqToDB.Data; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using System; +using Bit.Core.Enums; +using Bit.Core.Enums.Provider; namespace Bit.Core.Repositories.EntityFramework { public abstract class BaseEntityFrameworkRepository { + protected BulkCopyOptions DefaultBulkCopyOptions { get; set; } = new BulkCopyOptions + { + KeepIdentity = true, + BulkCopyType = BulkCopyType.MultipleRows, + }; + public BaseEntityFrameworkRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) { ServiceScopeFactory = serviceScopeFactory; @@ -18,5 +36,233 @@ namespace Bit.Core.Repositories.EntityFramework { return serviceScope.ServiceProvider.GetRequiredService<DatabaseContext>(); } + + public void ClearChangeTracking() + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + dbContext.ChangeTracker.Clear(); + } + } + + public async Task<int> GetCountFromQuery<T>(IQuery<T> query) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + return await query.Run(GetDatabaseContext(scope)).CountAsync(); + } + } + + protected async Task UserBumpAccountRevisionDateByCipherId(Cipher cipher) + { + var list = new List<Cipher> { cipher }; + await UserBumpAccountRevisionDateByCipherId(list); + } + + protected async Task UserBumpAccountRevisionDateByCipherId(IEnumerable<Cipher> ciphers) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + foreach (var cipher in ciphers) + { + var dbContext = GetDatabaseContext(scope); + var query = new UserBumpAccountRevisionDateByCipherIdQuery(cipher); + var users = query.Run(dbContext); + + await users.ForEachAsync(e => + { + dbContext.Attach(e); + e.RevisionDate = DateTime.UtcNow; + }); + await dbContext.SaveChangesAsync(); + } + } + } + + protected async Task UserBumpAccountRevisionDateByOrganizationId(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new UserBumpAccountRevisionDateByOrganizationIdQuery(organizationId); + var users = query.Run(dbContext); + await users.ForEachAsync(e => + { + dbContext.Attach(e); + e.RevisionDate = DateTime.UtcNow; + }); + await dbContext.SaveChangesAsync(); + } + } + + protected async Task UserBumpAccountRevisionDate(Guid userId) + { + await UserBumpManyAccountRevisionDates(new[] { userId }); + } + + protected async Task UserBumpManyAccountRevisionDates(ICollection<Guid> userIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var users = dbContext.Users.Where(u => userIds.Contains(u.Id)); + await users.ForEachAsync(u => + { + dbContext.Attach(u); + u.RevisionDate = DateTime.UtcNow; + }); + await dbContext.SaveChangesAsync(); + } + } + + protected async Task OrganizationUpdateStorage(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var attachments = await dbContext.Ciphers + .Where(e => e.UserId == null && + e.OrganizationId == organizationId && + !string.IsNullOrWhiteSpace(e.Attachments)) + .Select(e => e.Attachments) + .ToListAsync(); + var storage = attachments.Sum(e => JsonDocument.Parse(e)?.RootElement.EnumerateArray() + .Sum(p => p.GetProperty("Size").GetInt64()) ?? 0); + var organization = new EfModel.Organization + { + Id = organizationId, + RevisionDate = DateTime.UtcNow, + Storage = storage, + }; + dbContext.Organizations.Attach(organization); + var entry = dbContext.Entry(organization); + entry.Property(e => e.RevisionDate).IsModified = true; + entry.Property(e => e.Storage).IsModified = true; + await dbContext.SaveChangesAsync(); + } + } + + protected async Task UserUpdateStorage(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var attachments = await dbContext.Ciphers + .Where(e => e.UserId.HasValue && + e.UserId.Value == userId && + e.OrganizationId == null && + !string.IsNullOrWhiteSpace(e.Attachments)) + .Select(e => e.Attachments) + .ToListAsync(); + var storage = attachments.Sum(e => JsonDocument.Parse(e)?.RootElement.EnumerateArray() + .Sum(p => p.GetProperty("Size").GetInt64()) ?? 0); + var user = new EfModel.User + { + Id = userId, + RevisionDate = DateTime.UtcNow, + Storage = storage, + }; + dbContext.Users.Attach(user); + var entry = dbContext.Entry(user); + entry.Property(e => e.RevisionDate).IsModified = true; + entry.Property(e => e.Storage).IsModified = true; + await dbContext.SaveChangesAsync(); + } + } + + protected async Task UserUpdateKeys(User user) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.Users.FindAsync(user.Id); + if (entity == null) + { + return; + } + entity.SecurityStamp = user.SecurityStamp; + entity.Key = user.Key; + entity.PrivateKey = user.PrivateKey; + entity.RevisionDate = DateTime.UtcNow; + await dbContext.SaveChangesAsync(); + } + } + + protected async Task UserBumpAccountRevisionDateByCollectionId(Guid collectionId, Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from u in dbContext.Users + join ou in dbContext.OrganizationUsers + on u.Id equals ou.UserId + join cu in dbContext.CollectionUsers + on ou.Id equals cu.OrganizationUserId into cu_g + from cu in cu_g.DefaultIfEmpty() + where !ou.AccessAll && cu.CollectionId.Equals(collectionId) + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where cu.CollectionId == default(Guid) && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g.DefaultIfEmpty() + join cg in dbContext.CollectionGroups + on gu.GroupId equals cg.GroupId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && cg.CollectionId == collectionId && + (ou.OrganizationId == organizationId && ou.Status == OrganizationUserStatusType.Confirmed && + (cu.CollectionId != default(Guid) || cg.CollectionId != default(Guid) || ou.AccessAll || g.AccessAll)) + select new { u, ou, cu, gu, g, cg }; + var users = query.Select(x => x.u); + await users.ForEachAsync(u => + { + dbContext.Attach(u); + u.RevisionDate = DateTime.UtcNow; + }); + await dbContext.SaveChangesAsync(); + } + } + + protected async Task UserBumpAccountRevisionDateByOrganizationUserId(Guid organizationUserId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from u in dbContext.Users + join ou in dbContext.OrganizationUsers + on u.Id equals ou.UserId + where ou.Id.Equals(organizationUserId) && ou.Status.Equals(OrganizationUserStatusType.Confirmed) + select new { u, ou }; + var users = query.Select(x => x.u); + await users.ForEachAsync(u => + { + dbContext.Attach(u); + u.AccountRevisionDate = DateTime.UtcNow; + }); + await dbContext.SaveChangesAsync(); + } + } + + protected async Task UserBumpAccountRevisionDateByProviderUserIds(ICollection<Guid> providerUserIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from pu in dbContext.ProviderUsers + join u in dbContext.Users + on pu.UserId equals u.Id + where pu.Status.Equals(ProviderUserStatusType.Confirmed) && + providerUserIds.Contains(pu.Id) + select new { pu, u }; + var users = query.Select(x => x.u); + await users.ForEachAsync(u => { + dbContext.Attach(u); + u.AccountRevisionDate = DateTime.UtcNow; + }); + await dbContext.SaveChangesAsync(); + } + } } } diff --git a/src/Core/Repositories/EntityFramework/CipherRepository.cs b/src/Core/Repositories/EntityFramework/CipherRepository.cs new file mode 100644 index 0000000000..f365d0da02 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/CipherRepository.cs @@ -0,0 +1,642 @@ +using AutoMapper; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework.Queries; +using Bit.Core.Utilities; +using Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using LinqToDB.Data; +using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Enums; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class CipherRepository : Repository<TableModel.Cipher, EfModel.Cipher, Guid>, ICipherRepository + { + public CipherRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Ciphers) + { } + + public override async Task<Cipher> CreateAsync(Cipher cipher) + { + cipher = await base.CreateAsync(cipher); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + if (cipher.OrganizationId.HasValue) + { + await UserBumpAccountRevisionDateByCipherId(cipher); + } + else if (cipher.UserId.HasValue) + { + await UserBumpAccountRevisionDate(cipher.UserId.Value); + } + } + return cipher; + } + + public IQueryable<User> GetBumpedAccountsByCipherId(Cipher cipher) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new UserBumpAccountRevisionDateByCipherIdQuery(cipher); + return query.Run(dbContext); + } + } + + public async Task CreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds) + { + cipher = await base.CreateAsync(cipher); + await UpdateCollections(cipher, collectionIds); + } + + private async Task UpdateCollections(Cipher cipher, IEnumerable<Guid> collectionIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var cipherEntity = await dbContext.Ciphers.FindAsync(cipher.Id); + var query = new CipherUpdateCollectionsQuery(cipherEntity, collectionIds).Run(dbContext); + await dbContext.AddRangeAsync(query); + await dbContext.SaveChangesAsync(); + } + } + + public async Task CreateAsync(CipherDetails cipher) + { + await CreateAsyncReturnCipher(cipher); + } + + private async Task<CipherDetails> CreateAsyncReturnCipher(CipherDetails cipher) + { + cipher.SetNewId(); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var userIdKey = $"\"{cipher.UserId}\""; + cipher.UserId = cipher.OrganizationId.HasValue ? null : cipher.UserId; + cipher.Favorites = cipher.Favorite ? + $"{{{userIdKey}:true}}" : + null; + cipher.Folders = cipher.FolderId.HasValue ? + $"{{{userIdKey}:\"{cipher.FolderId}\"}}" : + null; + var entity = Mapper.Map<EfModel.Cipher>((TableModel.Cipher)cipher); + await dbContext.AddAsync(entity); + await dbContext.SaveChangesAsync(); + } + await UserBumpAccountRevisionDateByCipherId(cipher); + return cipher; + } + + public async Task CreateAsync(CipherDetails cipher, IEnumerable<Guid> collectionIds) + { + cipher = await CreateAsyncReturnCipher(cipher); + await UpdateCollections(cipher, collectionIds); + } + + public async Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders) + { + if (!ciphers.Any()) + { + return; + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var folderEntities = Mapper.Map<List<EfModel.Folder>>(folders); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, folderEntities); + var cipherEntities = Mapper.Map<List<EfModel.Cipher>>(ciphers); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, cipherEntities); + await UserBumpAccountRevisionDateByCipherId(ciphers); + } + } + + public async Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<CollectionCipher> collectionCiphers) + { + if (!ciphers.Any()) + { + return; + } + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var cipherEntities = Mapper.Map<List<EfModel.Cipher>>(ciphers); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, cipherEntities); + if (collections.Any()) + { + var collectionEntities = Mapper.Map<List<EfModel.Collection>>(collections); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, collectionEntities); + + if (collectionCiphers.Any()) + { + var collectionCipherEntities = Mapper.Map<List<EfModel.CollectionCipher>>(collectionCiphers); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, collectionCipherEntities); + } + } + await UserBumpAccountRevisionDateByOrganizationId(ciphers.First().OrganizationId.Value); + } + } + + public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId) + { + await ToggleCipherStates(ids, userId, CipherStateAction.HardDelete); + } + + public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var cipher = await dbContext.Ciphers.FindAsync(cipherId); + var attachmentsJson = JObject.Parse(cipher.Attachments); + attachmentsJson.Remove(attachmentId); + cipher.Attachments = JsonConvert.SerializeObject(attachmentsJson); + await dbContext.SaveChangesAsync(); + + if (cipher.OrganizationId.HasValue) + { + await OrganizationUpdateStorage(cipher.OrganizationId.Value); + await UserBumpAccountRevisionDateByCipherId(cipher); + } + else if (cipher.UserId.HasValue) + { + await UserUpdateStorage(cipher.UserId.Value); + await UserBumpAccountRevisionDate(cipher.UserId.Value); + } + } + } + + public async Task DeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var ciphers = from c in dbContext.Ciphers + where c.OrganizationId == organizationId && + ids.Contains(c.Id) + select c; + dbContext.RemoveRange(ciphers); + await dbContext.SaveChangesAsync(); + } + await OrganizationUpdateStorage(organizationId); + await UserBumpAccountRevisionDateByOrganizationId(organizationId); + } + + public async Task DeleteByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + + var collectionCiphers = from cc in dbContext.CollectionCiphers + join c in dbContext.Collections + on cc.CollectionId equals c.Id + where c.OrganizationId == organizationId + select cc; + dbContext.RemoveRange(collectionCiphers); + + var ciphers = from c in dbContext.Ciphers + where c.OrganizationId == organizationId + select c; + dbContext.RemoveRange(ciphers); + + await dbContext.SaveChangesAsync(); + } + await OrganizationUpdateStorage(organizationId); + await UserBumpAccountRevisionDateByOrganizationId(organizationId); + } + + public async Task DeleteByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var ciphers = from c in dbContext.Ciphers + where c.UserId == userId + select c; + dbContext.RemoveRange(ciphers); + var folders = from f in dbContext.Folders + where f.UserId == userId + select f; + dbContext.RemoveRange(folders); + await dbContext.SaveChangesAsync(); + await UserUpdateStorage(userId); + await UserBumpAccountRevisionDate(userId); + } + + } + + public async Task DeleteDeletedAsync(DateTime deletedDateBefore) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Ciphers.Where(c => c.DeletedDate < deletedDateBefore); + dbContext.RemoveRange(query); + await dbContext.SaveChangesAsync(); + } + } + + public async Task<CipherDetails> GetByIdAsync(Guid id, Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var userCipherDetails = new UserCipherDetailsQuery(userId); + var data = await userCipherDetails.Run(dbContext).FirstOrDefaultAsync(c => c.Id == id); + return data; + } + } + + public async Task<bool> GetCanEditByIdAsync(Guid userId, Guid cipherId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new CipherReadCanEditByIdUserIdQuery(userId, cipherId); + var canEdit = await query.Run(dbContext).AnyAsync(); + return canEdit; + } + } + + public async Task<ICollection<Cipher>> GetManyByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Ciphers.Where(x => !x.UserId.HasValue && x.OrganizationId == organizationId); + var data = await query.ToListAsync(); + return Mapper.Map<List<TableModel.Cipher>>(data); + } + } + + public async Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + IQueryable<CipherDetails> cipherDetailsView = withOrganizations ? + new UserCipherDetailsQuery(userId).Run(dbContext) : + new CipherDetailsQuery(userId).Run(dbContext); + if (!withOrganizations) + { + cipherDetailsView = from c in cipherDetailsView + where c.UserId == userId + select new CipherDetails { + Id = c.Id, + UserId = c.UserId, + OrganizationId = c.OrganizationId, + Type= c.Type, + Data = c.Data, + Attachments = c.Attachments, + CreationDate = c.CreationDate, + RevisionDate = c.RevisionDate, + DeletedDate = c.DeletedDate, + Favorite = c.Favorite, + FolderId = c.FolderId, + Edit = true, + ViewPassword = true, + OrganizationUseTotp = false, + }; + } + var ciphers = await cipherDetailsView.ToListAsync(); + return ciphers; + } + } + + public async Task<CipherOrganizationDetails> GetOrganizationDetailsByIdAsync(Guid id) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new CipherOrganizationDetailsReadByIdQuery(id); + var data = await query.Run(dbContext).FirstOrDefaultAsync(); + return data; + } + } + + public async Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var cipherEntities = dbContext.Ciphers.Where(c => ids.Contains(c.Id)); + var userCipherDetails = new UserCipherDetailsQuery(userId).Run(dbContext); + var idsToMove = from ucd in userCipherDetails + join c in cipherEntities + on ucd.Id equals c.Id + where ucd.Edit + select c; + await idsToMove.ForEachAsync(cipher => + { + var foldersJson = string.IsNullOrWhiteSpace(cipher.Folders) ? + new JObject() : + JObject.Parse(cipher.Folders); + + if (folderId.HasValue) + { + foldersJson.Remove(userId.ToString()); + foldersJson.Add(userId.ToString(), folderId.Value.ToString()); + } + else if (!string.IsNullOrWhiteSpace(cipher.Folders)) + { + foldersJson.Remove(userId.ToString()); + } + dbContext.Attach(cipher); + cipher.Folders = JsonConvert.SerializeObject(foldersJson); + }); + await dbContext.SaveChangesAsync(); + await UserBumpAccountRevisionDate(userId); + } + } + + public async Task ReplaceAsync(CipherDetails cipher) + { + cipher.UserId = cipher.OrganizationId.HasValue ? + null : + cipher.UserId; + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.Ciphers.FindAsync(cipher.Id); + if (entity != null) + { + var userIdKey = $"\"{cipher.UserId}\""; + if (cipher.Favorite) + { + if (cipher.Favorites == null) + { + cipher.Favorites = $"{{{userIdKey}:true}}"; + } + else + { + var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites); + favorites.Add(cipher.UserId.Value, true); + cipher.Favorites = JsonConvert.SerializeObject(favorites); + } + } + else + { + if (cipher.Favorites != null && cipher.Favorites.Contains(cipher.UserId.Value.ToString())) + { + var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites); + favorites.Remove(cipher.UserId.Value); + cipher.Favorites = JsonConvert.SerializeObject(favorites); + } + } + if (cipher.FolderId.HasValue) + { + if (cipher.Folders == null) + { + cipher.Folders = $"{{{userIdKey}:\"{cipher.FolderId}\"}}"; + } + else + { + var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(cipher.Folders); + folders.Add(cipher.UserId.Value, cipher.FolderId.Value); + cipher.Folders = JsonConvert.SerializeObject(folders); + } + } + else + { + if (cipher.Folders != null && cipher.Folders.Contains(cipher.UserId.Value.ToString())) + { + var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites); + folders.Remove(cipher.UserId.Value); + cipher.Favorites = JsonConvert.SerializeObject(folders); + } + } + var mappedEntity = Mapper.Map<EfModel.Cipher>((TableModel.Cipher)cipher); + dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); + await UserBumpAccountRevisionDateByCipherId(cipher); + await dbContext.SaveChangesAsync(); + } + } + } + + public async Task<bool> ReplaceAsync(Cipher obj, IEnumerable<Guid> collectionIds) + { + await UpdateCollections(obj, collectionIds); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var cipher = await dbContext.Ciphers.FindAsync(obj.Id); + cipher.UserId = null; + cipher.OrganizationId = obj.OrganizationId; + cipher.Data = obj.Data; + cipher.Attachments = obj.Attachments; + cipher.RevisionDate = obj.RevisionDate; + cipher.DeletedDate = obj.DeletedDate; + await dbContext.SaveChangesAsync(); + + if (!string.IsNullOrWhiteSpace(cipher.Attachments)) + { + if (cipher.OrganizationId.HasValue) + { + await OrganizationUpdateStorage(cipher.OrganizationId.Value); + } + else if (cipher.UserId.HasValue) + { + await UserUpdateStorage(cipher.UserId.Value); + } + } + + await UserBumpAccountRevisionDateByCipherId(cipher); + return true; + } + } + + public async Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId) + { + return await ToggleCipherStates(ids, userId, CipherStateAction.Restore); + } + + public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId) + { + await ToggleCipherStates(ids, userId, CipherStateAction.SoftDelete); + } + + private async Task<DateTime> ToggleCipherStates(IEnumerable<Guid> ids, Guid userId, CipherStateAction action) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var userCipherDetailsQuery = new UserCipherDetailsQuery(userId); + var cipherEntitiesToCheck = await (dbContext.Ciphers.Where(c => ids.Contains(c.Id))).ToListAsync(); + var query = from ucd in await (userCipherDetailsQuery.Run(dbContext)).ToListAsync() + join c in cipherEntitiesToCheck + on ucd.Id equals c.Id + where ucd.Edit && ucd.DeletedDate == null + select c; + + var utcNow = DateTime.UtcNow; + var cipherIdsToModify = query.Select(c => c.Id); + var cipherEntitiesToModify = dbContext.Ciphers.Where(x => cipherIdsToModify.Contains(x.Id)); + if (action == CipherStateAction.HardDelete) + { + dbContext.RemoveRange(cipherEntitiesToModify); + } + else + { + await cipherEntitiesToModify.ForEachAsync(cipher => + { + dbContext.Attach(cipher); + cipher.DeletedDate = action == CipherStateAction.Restore ? null : utcNow; + cipher.RevisionDate = utcNow; + }); + } + + var orgIds = query + .Where(c => c.OrganizationId.HasValue) + .GroupBy(c => c.OrganizationId).Select(x => x.Key); + + foreach (var orgId in orgIds) + { + await OrganizationUpdateStorage(orgId.Value); + await UserBumpAccountRevisionDateByOrganizationId(orgId.Value); + } + if (query.Any(c => c.UserId.HasValue && !string.IsNullOrWhiteSpace(c.Attachments))) + { + await UserUpdateStorage(userId); + } + await UserBumpAccountRevisionDate(userId); + await dbContext.SaveChangesAsync(); + return utcNow; + } + } + + public async Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var utcNow = DateTime.UtcNow; + var ciphers = dbContext.Ciphers.Where(c => ids.Contains(c.Id) && c.OrganizationId == organizationId); + await ciphers.ForEachAsync(cipher => + { + dbContext.Attach(cipher); + cipher.DeletedDate = utcNow; + cipher.RevisionDate = utcNow; + }); + await dbContext.SaveChangesAsync(); + await OrganizationUpdateStorage(organizationId); + await UserBumpAccountRevisionDateByOrganizationId(organizationId); + } + } + + public async Task UpdateAttachmentAsync(CipherAttachment attachment) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var cipher = await dbContext.Ciphers.FindAsync(attachment.Id); + var attachmentsJson = string.IsNullOrWhiteSpace(cipher.Attachments) ? new JObject() : JObject.Parse(cipher.Attachments); + attachmentsJson.Add(attachment.AttachmentId, attachment.AttachmentData); + cipher.Attachments = JsonConvert.SerializeObject(attachmentsJson); + await dbContext.SaveChangesAsync(); + + if (attachment.OrganizationId.HasValue) + { + await OrganizationUpdateStorage(cipher.OrganizationId.Value); + await UserBumpAccountRevisionDateByCipherId(new List<Cipher> { cipher }); + } + else if (attachment.UserId.HasValue) + { + await UserUpdateStorage(attachment.UserId.Value); + await UserBumpAccountRevisionDate(attachment.UserId.Value); + } + } + } + + public async Task UpdateCiphersAsync(Guid userId, IEnumerable<Cipher> ciphers) + { + if (!ciphers.Any()) + { + return; + } + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entities = Mapper.Map<List<EfModel.Cipher>>(ciphers); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, entities); + await UserBumpAccountRevisionDate(userId); + } + } + + public async Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var cipher = await dbContext.Ciphers.FindAsync(id); + + var foldersJson = JObject.Parse(cipher.Folders); + if (foldersJson == null && folderId.HasValue) + { + foldersJson.Add(userId.ToString(), folderId.Value); + } + else if (foldersJson != null && folderId.HasValue) + { + foldersJson[userId] = folderId.Value; + } + else + { + foldersJson.Remove(userId.ToString()); + } + + var favoritesJson = JObject.Parse(cipher.Favorites); + if (favorite) + { + favoritesJson.Add(userId.ToString(), favorite); + } + else + { + favoritesJson.Remove(userId.ToString()); + } + + await dbContext.SaveChangesAsync(); + await UserBumpAccountRevisionDate(userId); + } + } + + public async Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders, IEnumerable<Send> sends) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + await UserUpdateKeys(user); + var cipherEntities = Mapper.Map<List<EfModel.Cipher>>(ciphers); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, cipherEntities); + var folderEntities = Mapper.Map<List<EfModel.Folder>>(folders); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, folderEntities); + var sendEntities = Mapper.Map<List<EfModel.Send>>(sends); + await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, sendEntities); + await dbContext.SaveChangesAsync(); + } + } + + public async Task UpsertAsync(CipherDetails cipher) + { + if (cipher.Id.Equals(default)) + { + await CreateAsync(cipher); + } + else + { + await ReplaceAsync(cipher); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/CollectionCipherRepository.cs b/src/Core/Repositories/EntityFramework/CollectionCipherRepository.cs new file mode 100644 index 0000000000..e1e788d69f --- /dev/null +++ b/src/Core/Repositories/EntityFramework/CollectionCipherRepository.cs @@ -0,0 +1,242 @@ +using AutoMapper; +using Bit.Core.Enums; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework.Queries; +using EfModel = Bit.Core.Models.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollectionCipherRepository + { + public CollectionCipherRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper) + { } + + public async Task<CollectionCipher> CreateAsync(CollectionCipher obj) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = Mapper.Map<EfModel.CollectionCipher>(obj); + dbContext.Add(entity); + await dbContext.SaveChangesAsync(); + var organizationId = (await dbContext.Ciphers.FirstOrDefaultAsync(c => c.Id.Equals(obj.CipherId))).OrganizationId; + if (organizationId.HasValue) + { + await UserBumpAccountRevisionDateByCollectionId(obj.CollectionId, organizationId.Value); + } + return obj; + } + } + + public async Task<ICollection<CollectionCipher>> GetManyByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var data = await (from cc in dbContext.CollectionCiphers + join c in dbContext.Collections + on cc.CollectionId equals c.Id + where c.OrganizationId == organizationId + select cc).ToArrayAsync(); + return data; + } + } + + public async Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var data = await new CollectionCipherReadByUserIdQuery(userId) + .Run(dbContext) + .ToArrayAsync(); + return data; + } + } + + public async Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var data = await new CollectionCipherReadByUserIdCipherIdQuery(userId, cipherId) + .Run(dbContext) + .ToArrayAsync(); + return data; + } + } + + public async Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var organizationId = (await dbContext.Ciphers.FindAsync(cipherId)).OrganizationId; + var availableCollectionsCte = from c in dbContext.Collections + join o in dbContext.Organizations + on c.OrganizationId equals o.Id + join ou in dbContext.OrganizationUsers + on o.Id equals ou.OrganizationId + where ou.UserId == userId + join cu in dbContext.CollectionUsers + on ou.Id equals cu.OrganizationUserId into cu_g + from cu in cu_g.DefaultIfEmpty() + where !ou.AccessAll && cu.CollectionId == c.Id + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where cu.CollectionId == null && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g.DefaultIfEmpty() + join cg in dbContext.CollectionGroups + on gu.GroupId equals cg.GroupId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && cg.CollectionId == c.Id && + (o.Id == organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed && ( + ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly)) + select new { c, o, cu, gu, g, cg }; + var target = from cc in dbContext.CollectionCiphers + where cc.CipherId == cipherId + select new { cc.CollectionId, cc.CipherId }; + var source = collectionIds.Select(x => new { CollectionId = x, CipherId = cipherId }); + var merge1 = from t in target + join s in source + on t.CollectionId equals s.CollectionId into s_g + from s in s_g.DefaultIfEmpty() + where t.CipherId == s.CipherId + select new { t, s }; + var merge2 = from s in source + join t in target + on s.CollectionId equals t.CollectionId into t_g + from t in t_g.DefaultIfEmpty() + where t.CipherId == s.CipherId + select new { t, s }; + var union = merge1.Union(merge2).Distinct(); + var insert = union + .Where(x => x.t == null && collectionIds.Contains(x.s.CollectionId)) + .Select(x => new EfModel.CollectionCipher + { + CollectionId = x.s.CollectionId, + CipherId = x.s.CipherId, + }); + var delete = union + .Where(x => x.s == null && x.t.CipherId == cipherId && collectionIds.Contains(x.t.CollectionId)) + .Select(x => new EfModel.CollectionCipher + { + CollectionId = x.t.CollectionId, + CipherId = x.t.CipherId, + }); + await dbContext.AddRangeAsync(insert); + dbContext.RemoveRange(delete); + await dbContext.SaveChangesAsync(); + + if (organizationId.HasValue) + { + await UserBumpAccountRevisionDateByOrganizationId(organizationId.Value); + } + } + } + + public async Task UpdateCollectionsForAdminAsync(Guid cipherId, Guid organizationId, IEnumerable<Guid> collectionIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var availableCollectionsCte = from c in dbContext.Collections + where c.OrganizationId == organizationId + select c; + var target = from cc in dbContext.CollectionCiphers + where cc.CipherId == cipherId + select new { cc.CollectionId, cc.CipherId }; + var source = collectionIds.Select(x => new { CollectionId = x, CipherId = cipherId }); + var merge1 = from t in target + join s in source + on t.CollectionId equals s.CollectionId into s_g + from s in s_g.DefaultIfEmpty() + where t.CipherId == s.CipherId + select new { t, s }; + var merge2 = from s in source + join t in target + on s.CollectionId equals t.CollectionId into t_g + from t in t_g.DefaultIfEmpty() + where t.CipherId == s.CipherId + select new { t, s }; + var union = merge1.Union(merge2).Distinct(); + var insert = union + .Where(x => x.t == null && collectionIds.Contains(x.s.CollectionId)) + .Select(x => new EfModel.CollectionCipher + { + CollectionId = x.s.CollectionId, + CipherId = x.s.CipherId, + }); + var delete = union + .Where(x => x.s == null && x.t.CipherId == cipherId) + .Select(x => new EfModel.CollectionCipher + { + CollectionId = x.t.CollectionId, + CipherId = x.t.CipherId, + }); + await dbContext.AddRangeAsync(insert); + dbContext.RemoveRange(delete); + await dbContext.SaveChangesAsync(); + await UserBumpAccountRevisionDateByOrganizationId(organizationId); + } + } + + public async Task UpdateCollectionsForCiphersAsync(IEnumerable<Guid> cipherIds, Guid userId, Guid organizationId, IEnumerable<Guid> collectionIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var availibleCollections = from c in dbContext.Collections + join o in dbContext.Organizations + on c.OrganizationId equals o.Id + join ou in dbContext.OrganizationUsers + on o.Id equals ou.OrganizationId + where ou.UserId == userId + join cu in dbContext.CollectionUsers + on ou.Id equals cu.OrganizationUserId into cu_g + from cu in cu_g.DefaultIfEmpty() + where !ou.AccessAll && cu.CollectionId == c.Id + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where cu.CollectionId == null && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g.DefaultIfEmpty() + join cg in dbContext.CollectionGroups + on gu.GroupId equals cg.GroupId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && cg.CollectionId == c.Id && + (o.Id == organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed && + (ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly)) + select new { c, o, ou, cu, gu, g, cg }; + var count = await availibleCollections.CountAsync(); + if (await availibleCollections.CountAsync() < 1) + { + return; + } + + var insertData = from collectionId in collectionIds + from cipherId in cipherIds + where availibleCollections.Select(x => x.c.Id).Contains(collectionId) + select new EfModel.CollectionCipher + { + CollectionId = collectionId, + CipherId = cipherId, + }; + await dbContext.AddRangeAsync(insertData); + await UserBumpAccountRevisionDateByOrganizationId(organizationId); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/CollectionRepository.cs b/src/Core/Repositories/EntityFramework/CollectionRepository.cs new file mode 100644 index 0000000000..33823bd3e0 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/CollectionRepository.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework.Queries; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class CollectionRepository : Repository<TableModel.Collection, EfModel.Collection, Guid>, ICollectionRepository + { + public CollectionRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Collections) + { } + + public override async Task<TableModel.Collection> CreateAsync(Collection obj) + { + await base.CreateAsync(obj); + await UserBumpAccountRevisionDateByCollectionId(obj.Id, obj.OrganizationId); + return obj; + } + + public async Task CreateAsync(Collection obj, IEnumerable<SelectionReadOnly> groups) + { + await base.CreateAsync(obj); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var availibleGroups = await (from g in dbContext.Groups + where g.OrganizationId == obj.OrganizationId + select g.Id).ToListAsync(); + var collectionGroups = groups + .Where(g => availibleGroups.Contains(g.Id)) + .Select(g => new EfModel.CollectionGroup + { + CollectionId = obj.Id, + GroupId = g.Id, + ReadOnly = g.ReadOnly, + HidePasswords = g.HidePasswords, + }); + await dbContext.AddRangeAsync(collectionGroups); + await dbContext.SaveChangesAsync(); + await UserBumpAccountRevisionDateByOrganizationId(obj.OrganizationId); + } + } + + public async Task DeleteUserAsync(Guid collectionId, Guid organizationUserId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from cu in dbContext.CollectionUsers + where cu.CollectionId == collectionId && + cu.OrganizationUserId == organizationUserId + select cu; + dbContext.RemoveRange(await query.ToListAsync()); + await dbContext.SaveChangesAsync(); + await UserBumpAccountRevisionDateByOrganizationUserId(organizationUserId); + } + } + + public async Task<CollectionDetails> GetByIdAsync(Guid id, Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new UserCollectionDetailsQuery(userId); + var collection = await query.Run(dbContext).FirstOrDefaultAsync(); + return collection; + } + } + + public async Task<Tuple<Collection, ICollection<SelectionReadOnly>>> GetByIdWithGroupsAsync(Guid id) + { + var collection = await base.GetByIdAsync(id); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var collectionGroups = await (from cg in dbContext.CollectionGroups + where cg.CollectionId == id + select cg).ToListAsync(); + var selectionReadOnlys = collectionGroups.Select(cg => new SelectionReadOnly + { + Id = cg.GroupId, + ReadOnly = cg.ReadOnly, + HidePasswords = cg.HidePasswords, + }).ToList(); + return new Tuple<Collection, ICollection<SelectionReadOnly>>(collection, selectionReadOnlys); + } + } + + public async Task<Tuple<CollectionDetails, ICollection<SelectionReadOnly>>> GetByIdWithGroupsAsync(Guid id, Guid userId) + { + var collection = await GetByIdAsync(id, userId); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from cg in dbContext.CollectionGroups + where cg.CollectionId.Equals(id) + select new SelectionReadOnly + { + Id = cg.GroupId, + ReadOnly = cg.ReadOnly, + HidePasswords = cg.HidePasswords, + }; + var configurations = await query.ToArrayAsync(); + return new Tuple<CollectionDetails, ICollection<SelectionReadOnly>>(collection, configurations); + } + } + + public async Task<int> GetCountByOrganizationIdAsync(Guid organizationId) + { + var query = new CollectionReadCountByOrganizationIdQuery(organizationId); + return await GetCountFromQuery(query); + } + + public async Task<ICollection<Collection>> GetManyByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from c in dbContext.Collections + where c.OrganizationId == organizationId + select c; + var collections = await query.ToArrayAsync(); + return collections; + } + } + + public async Task<ICollection<CollectionDetails>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new UserCollectionDetailsQuery(userId).Run(dbContext); + var data = await query.ToListAsync(); + return data.GroupBy(c => c.Id).Select(c => c.First()).ToList(); + } + } + + public async Task<ICollection<SelectionReadOnly>> GetManyUsersByIdAsync(Guid id) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from cu in dbContext.CollectionUsers + where cu.CollectionId == id + select cu; + var collectionUsers = await query.ToListAsync(); + return collectionUsers.Select(cu => new SelectionReadOnly + { + Id = cu.OrganizationUserId, + ReadOnly = cu.ReadOnly, + HidePasswords = cu.HidePasswords, + }).ToArray(); + } + } + + public async Task ReplaceAsync(Collection collection, IEnumerable<SelectionReadOnly> groups) + { + await base.ReplaceAsync(collection); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var groupsInOrg = dbContext.Groups.Where(g => g.OrganizationId == collection.OrganizationId); + var modifiedGroupEntities = dbContext.Groups.Where(x => groups.Select(x => x.Id).Contains(x.Id)); + var target = (from cg in dbContext.CollectionGroups + join g in modifiedGroupEntities + on cg.CollectionId equals collection.Id into s_g + from g in s_g.DefaultIfEmpty() + where g == null || cg.GroupId == g.Id + select new {cg, g}).AsNoTracking(); + var source = (from g in modifiedGroupEntities + from cg in dbContext.CollectionGroups + .Where(cg => cg.CollectionId == collection.Id && cg.GroupId == g.Id).DefaultIfEmpty() + select new {cg, g}).AsNoTracking(); + var union = await target + .Union(source) + .Where(x => + x.cg == null || + ((x.g == null || x.g.Id == x.cg.GroupId) && + (x.cg.CollectionId == collection.Id))) + .AsNoTracking() + .ToListAsync(); + var insert = union.Where(x => x.cg == null && groupsInOrg.Any(c => x.g.Id == c.Id)) + .Select(x => new EfModel.CollectionGroup + { + CollectionId = collection.Id, + GroupId = x.g.Id, + ReadOnly = groups.FirstOrDefault(g => g.Id == x.g.Id).ReadOnly, + HidePasswords = groups.FirstOrDefault(g => g.Id == x.g.Id).HidePasswords, + }).ToList(); + var update = union + .Where( + x => x.g != null && + x.cg != null && + (x.cg.ReadOnly != groups.FirstOrDefault(g => g.Id == x.g.Id).ReadOnly || + x.cg.HidePasswords != groups.FirstOrDefault(g => g.Id == x.g.Id).HidePasswords) + ) + .Select(x => new EfModel.CollectionGroup + { + CollectionId = collection.Id, + GroupId = x.g.Id, + ReadOnly = groups.FirstOrDefault(g => g.Id == x.g.Id).ReadOnly, + HidePasswords = groups.FirstOrDefault(g => g.Id == x.g.Id).HidePasswords, + }); + var delete = union + .Where( + x => x.g == null && + x.cg.CollectionId == collection.Id + ) + .Select(x => new EfModel.CollectionGroup + { + CollectionId = collection.Id, + GroupId = x.cg.GroupId, + }) + .ToList(); + + await dbContext.AddRangeAsync(insert); + dbContext.UpdateRange(update); + dbContext.RemoveRange(delete); + await dbContext.SaveChangesAsync(); + await UserBumpAccountRevisionDateByCollectionId(collection.Id, collection.OrganizationId); + } + } + + public async Task UpdateUsersAsync(Guid id, IEnumerable<SelectionReadOnly> users) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var procedure = new CollectionUserUpdateUsersQuery(id, users); + var updateData = await procedure.Update.BuildInMemory(dbContext); + dbContext.UpdateRange(updateData); + var insertData = await procedure.Insert.BuildInMemory(dbContext); + await dbContext.AddRangeAsync(insertData); + dbContext.RemoveRange(await procedure.Delete.Run(dbContext).ToListAsync()); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/DatabaseContext.cs b/src/Core/Repositories/EntityFramework/DatabaseContext.cs index df418a0ee0..c0940d330b 100644 --- a/src/Core/Repositories/EntityFramework/DatabaseContext.cs +++ b/src/Core/Repositories/EntityFramework/DatabaseContext.cs @@ -1,39 +1,136 @@ -using System; -using Bit.Core.Models.EntityFramework; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models.EntityFramework.Provider; using Microsoft.EntityFrameworkCore; namespace Bit.Core.Repositories.EntityFramework { public class DatabaseContext : DbContext { + public const string postgresIndetermanisticCollation = "postgresIndetermanisticCollation"; + public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } - public DbSet<User> Users { get; set; } public DbSet<Cipher> Ciphers { get; set; } + public DbSet<Collection> Collections { get; set; } + public DbSet<CollectionCipher> CollectionCiphers { get; set; } + public DbSet<CollectionGroup> CollectionGroups { get; set; } + public DbSet<CollectionUser> CollectionUsers { get; set; } + public DbSet<Device> Devices { get; set; } + public DbSet<EmergencyAccess> EmergencyAccesses { get; set; } + public DbSet<Event> Events { get; set; } + public DbSet<Folder> Folders { get; set; } + public DbSet<Grant> Grants { get; set; } + public DbSet<Group> Groups { get; set; } + public DbSet<GroupUser> GroupUsers { get; set; } + public DbSet<Installation> Installations { get; set; } public DbSet<Organization> Organizations { get; set; } - + public DbSet<OrganizationUser> OrganizationUsers { get; set; } + public DbSet<Policy> Policies { get; set; } + public DbSet<Provider> Providers { get; set; } + public DbSet<ProviderUser> ProviderUsers { get; set; } + public DbSet<ProviderOrganization> ProviderOrganizations { get; set; } + public DbSet<ProviderOrganizationProviderUser> ProviderOrganizationProviderUsers { get; set; } + public DbSet<Send> Sends { get; set; } + public DbSet<SsoConfig> SsoConfigs { get; set; } + public DbSet<SsoUser> SsoUsers { get; set; } + public DbSet<TaxRate> TaxRates { get; set; } + public DbSet<Transaction> Transactions { get; set; } + public DbSet<U2f> U2fs { get; set; } + public DbSet<User> Users { get; set; } + protected override void OnModelCreating(ModelBuilder builder) { - builder.Entity<Cipher>().Ignore(e => e.Data); - builder.Entity<Cipher>().Property(e => e.DataJson).HasColumnName("Data"); - builder.Entity<Cipher>().Ignore(e => e.Attachments); - builder.Entity<Cipher>().Property(e => e.AttachmentsJson).HasColumnName("Attachments"); - builder.Entity<Cipher>().Ignore(e => e.Favorites); - builder.Entity<Cipher>().Property(e => e.FavoritesJson).HasColumnName("Favorites"); - builder.Entity<Cipher>().Ignore(e => e.Folders); - builder.Entity<Cipher>().Property(e => e.FoldersJson).HasColumnName("Folders"); + var eCipher = builder.Entity<Cipher>(); + var eCollection = builder.Entity<Collection>(); + var eCollectionCipher = builder.Entity<CollectionCipher>(); + var eCollectionUser = builder.Entity<CollectionUser>(); + var eCollectionGroup = builder.Entity<CollectionGroup>(); + var eDevice = builder.Entity<Device>(); + var eEmergencyAccess = builder.Entity<EmergencyAccess>(); + var eEvent = builder.Entity<Event>(); + var eFolder = builder.Entity<Folder>(); + var eGrant = builder.Entity<Grant>(); + var eGroup = builder.Entity<Group>(); + var eGroupUser = builder.Entity<GroupUser>(); + var eInstallation = builder.Entity<Installation>(); + var eOrganization = builder.Entity<Organization>(); + var eOrganizationUser = builder.Entity<OrganizationUser>(); + var ePolicy = builder.Entity<Policy>(); + var eProvider = builder.Entity<Provider>(); + var eProviderUser = builder.Entity<ProviderUser>(); + var eProviderOrganization = builder.Entity<ProviderOrganization>(); + var eProviderOrganizationProviderUser = builder.Entity<ProviderOrganizationProviderUser>(); + var eSend = builder.Entity<Send>(); + var eSsoConfig = builder.Entity<SsoConfig>(); + var eSsoUser = builder.Entity<SsoUser>(); + var eTaxRate = builder.Entity<TaxRate>(); + var eTransaction = builder.Entity<Transaction>(); + var eU2f = builder.Entity<U2f>(); + var eUser = builder.Entity<User>(); - builder.Entity<User>().Ignore(e => e.TwoFactorProviders); - builder.Entity<User>().Property(e => e.TwoFactorProvidersJson).HasColumnName("TwoFactorProviders"); + eCipher.Property(c => c.Id).ValueGeneratedNever(); + eCollection.Property(c => c.Id).ValueGeneratedNever(); + eEmergencyAccess.Property(c => c.Id).ValueGeneratedNever(); + eEvent.Property(c => c.Id).ValueGeneratedNever(); + eFolder.Property(c => c.Id).ValueGeneratedNever(); + eGroup.Property(c => c.Id).ValueGeneratedNever(); + eInstallation.Property(c => c.Id).ValueGeneratedNever(); + eOrganization.Property(c => c.Id).ValueGeneratedNever(); + eOrganizationUser.Property(c => c.Id).ValueGeneratedNever(); + ePolicy.Property(c => c.Id).ValueGeneratedNever(); + eProvider.Property(c => c.Id).ValueGeneratedNever(); + eProviderUser.Property(c => c.Id).ValueGeneratedNever(); + eProviderOrganization.Property(c => c.Id).ValueGeneratedNever(); + eProviderOrganizationProviderUser.Property(c => c.Id).ValueGeneratedNever(); + eSend.Property(c => c.Id).ValueGeneratedNever(); + eTransaction.Property(c => c.Id).ValueGeneratedNever(); + eUser.Property(c => c.Id).ValueGeneratedNever(); - builder.Entity<Organization>().Ignore(e => e.TwoFactorProviders); - builder.Entity<Organization>().Property(e => e.TwoFactorProvidersJson).HasColumnName("TwoFactorProviders"); + eCollectionCipher.HasKey(cc => new { cc.CollectionId, cc.CipherId }); + eCollectionUser.HasKey(cu => new { cu.CollectionId, cu.OrganizationUserId }); + eCollectionGroup.HasKey(cg => new { cg.CollectionId, cg.GroupId }); + eGrant.HasKey(x => x.Key); + eGroupUser.HasKey(gu => new { gu.GroupId, gu.OrganizationUserId }); - builder.Entity<User>().ToTable(nameof(User)); - builder.Entity<Cipher>().ToTable(nameof(Cipher)); - builder.Entity<Organization>().ToTable(nameof(Organization)); + + if (Database.IsNpgsql()) + { + // the postgres provider doesn't currently support database level non-deterministic collations. + // see https://www.npgsql.org/efcore/misc/collations-and-case-sensitivity.html#database-collation + builder.HasCollation(postgresIndetermanisticCollation, locale: "en-u-ks-primary", provider: "icu", deterministic: false); + eUser.Property(e => e.Email).UseCollation(postgresIndetermanisticCollation); + eSsoUser.Property(e => e.ExternalId).UseCollation(postgresIndetermanisticCollation); + eOrganization.Property(e => e.Identifier).UseCollation(postgresIndetermanisticCollation); + // + } + + eCipher.ToTable(nameof(Cipher)); + eCollection.ToTable(nameof(Collection)); + eCollectionCipher.ToTable(nameof(CollectionCipher)); + eDevice.ToTable(nameof(Device)); + eEmergencyAccess.ToTable(nameof(EmergencyAccess)); + eEvent.ToTable(nameof(Event)); + eFolder.ToTable(nameof(Folder)); + eGrant.ToTable(nameof(Grant)); + eGroup.ToTable(nameof(Group)); + eGroupUser.ToTable(nameof(GroupUser)); + eInstallation.ToTable(nameof(Installation)); + eOrganization.ToTable(nameof(Organization)); + eOrganizationUser.ToTable(nameof(OrganizationUser)); + ePolicy.ToTable(nameof(Policy)); + eProvider.ToTable(nameof(Provider)); + eProviderUser.ToTable(nameof(ProviderUser)); + eProviderOrganization.ToTable(nameof(ProviderOrganization)); + eProviderOrganizationProviderUser.ToTable(nameof(ProviderOrganizationProviderUser)); + eSend.ToTable(nameof(Send)); + eSsoConfig.ToTable(nameof(SsoConfig)); + eSsoUser.ToTable(nameof(SsoUser)); + eTaxRate.ToTable(nameof(TaxRate)); + eTransaction.ToTable(nameof(Transaction)); + eU2f.ToTable(nameof(U2f)); + eUser.ToTable(nameof(User)); } } } diff --git a/src/Core/Repositories/EntityFramework/DeviceRepository.cs b/src/Core/Repositories/EntityFramework/DeviceRepository.cs new file mode 100644 index 0000000000..79f6e8c93f --- /dev/null +++ b/src/Core/Repositories/EntityFramework/DeviceRepository.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class DeviceRepository : Repository<TableModel.Device, EfModel.Device, Guid>, IDeviceRepository + { + public DeviceRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Devices) + { } + + public async Task ClearPushTokenAsync(Guid id) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Devices.Where(d => d.Id == id); + dbContext.AttachRange(query); + await query.ForEachAsync(x => x.PushToken = null); + await dbContext.SaveChangesAsync(); + } + } + + public async Task<Device> GetByIdAsync(Guid id, Guid userId) + { + var device = await base.GetByIdAsync(id); + if (device == null || device.UserId != userId) + { + return null; + } + + return Mapper.Map<TableModel.Device>(device); + } + + public async Task<Device> GetByIdentifierAsync(string identifier) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Devices.Where(d => d.Identifier == identifier); + var device = await query.FirstOrDefaultAsync(); + return Mapper.Map<TableModel.Device>(device); + } + } + + public async Task<Device> GetByIdentifierAsync(string identifier, Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Devices.Where(d => d.Identifier == identifier && d.UserId == userId); + var device = await query.FirstOrDefaultAsync(); + return Mapper.Map<TableModel.Device>(device); + } + } + + public async Task<ICollection<Device>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Devices.Where(d => d.UserId == userId); + var devices = await query.ToListAsync(); + return Mapper.Map<List<TableModel.Device>>(devices); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/EmergencyAccessRepository.cs b/src/Core/Repositories/EntityFramework/EmergencyAccessRepository.cs new file mode 100644 index 0000000000..c8c4ecfaa4 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/EmergencyAccessRepository.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Repositories.EntityFramework.Queries; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class EmergencyAccessRepository : Repository<TableModel.EmergencyAccess, EfModel.EmergencyAccess, Guid>, IEmergencyAccessRepository + { + public EmergencyAccessRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.EmergencyAccesses) + { } + + public async Task<int> GetCountByGrantorIdEmailAsync(Guid grantorId, string email, bool onlyRegisteredUsers) + { + var query = new EmergencyAccessReadCountByGrantorIdEmailQuery(grantorId, email, onlyRegisteredUsers); + return await GetCountFromQuery(query); + } + + public async Task<EmergencyAccessDetails> GetDetailsByIdGrantorIdAsync(Guid id, Guid grantorId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new EmergencyAccessDetailsViewQuery(); + var query = view.Run(dbContext).Where(ea => + ea.Id == id && + ea.GrantorId == grantorId + ); + return await query.FirstOrDefaultAsync(); + } + } + + public async Task<ICollection<EmergencyAccessDetails>> GetExpiredRecoveriesAsync() + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new EmergencyAccessDetailsViewQuery(); + var query = view.Run(dbContext).Where(ea => + ea.Status == EmergencyAccessStatusType.RecoveryInitiated + ); + return await query.ToListAsync(); + } + } + + public async Task<ICollection<EmergencyAccessDetails>> GetManyDetailsByGranteeIdAsync(Guid granteeId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new EmergencyAccessDetailsViewQuery(); + var query = view.Run(dbContext).Where(ea => + ea.GranteeId == granteeId + ); + return await query.ToListAsync(); + } + } + + public async Task<ICollection<EmergencyAccessDetails>> GetManyDetailsByGrantorIdAsync(Guid grantorId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new EmergencyAccessDetailsViewQuery(); + var query = view.Run(dbContext).Where(ea => + ea.GrantorId == grantorId + ); + return await query.ToListAsync(); + } + } + + public async Task<ICollection<EmergencyAccessNotify>> GetManyToNotifyAsync() + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new EmergencyAccessDetailsViewQuery(); + var query = view.Run(dbContext).Where(ea => + ea.Status == EmergencyAccessStatusType.RecoveryInitiated + ); + var notifies = await query.Select(ea => new EmergencyAccessNotify + { + Id = ea.Id, + GrantorId = ea.GrantorId, + GranteeId = ea.GranteeId, + Email = ea.Email, + KeyEncrypted = ea.KeyEncrypted, + Type = ea.Type, + Status = ea.Status, + WaitTimeDays = ea.WaitTimeDays, + RecoveryInitiatedDate = ea.RecoveryInitiatedDate, + LastNotificationDate = ea.LastNotificationDate, + CreationDate = ea.CreationDate, + RevisionDate = ea.RevisionDate, + GranteeName = ea.GranteeName, + GranteeEmail = ea.GranteeEmail, + GrantorEmail = ea.GrantorEmail, + }).ToListAsync(); + return notifies; + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/EventRepository.cs b/src/Core/Repositories/EntityFramework/EventRepository.cs new file mode 100644 index 0000000000..bff7843b87 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/EventRepository.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework.Queries; +using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class EventRepository : Repository<TableModel.Event, EfModel.Event, Guid>, IEventRepository + { + public EventRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Events) + { } + + public async Task CreateAsync(IEvent e) + { + if (e is not Event ev) + { + ev = new Event(e); + } + + await base.CreateAsync(ev); + } + + public async Task CreateManyAsync(IEnumerable<IEvent> entities) + { + if (!entities?.Any() ?? true) + { + return; + } + + if (!entities.Skip(1).Any()) + { + await CreateAsync(entities.First()); + return; + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var tableEvents = entities.Select(e => e as Event ?? new Event(e)); + var entityEvents = Mapper.Map<List<EfModel.Event>>(tableEvents); + await dbContext.BulkCopyAsync(entityEvents); + } + } + + public async Task<PagedResult<IEvent>> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate, PageOptions pageOptions) + { + DateTime? beforeDate = null; + if (!string.IsNullOrWhiteSpace(pageOptions.ContinuationToken) && + long.TryParse(pageOptions.ContinuationToken, out var binaryDate)) + { + beforeDate = DateTime.SpecifyKind(DateTime.FromBinary(binaryDate), DateTimeKind.Utc); + } + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new EventReadPageByCipherIdQuery(cipher, startDate, endDate, beforeDate, pageOptions); + var events = await query.Run(dbContext).ToListAsync(); + + var result = new PagedResult<IEvent>(); + if (events.Any() && events.Count >= pageOptions.PageSize) + { + result.ContinuationToken = events.Last().Date.ToBinary().ToString(); + } + result.Data.AddRange(events); + return result; + } + } + + + public async Task<PagedResult<IEvent>> GetManyByOrganizationActingUserAsync(Guid organizationId, Guid actingUserId, DateTime startDate, DateTime endDate, PageOptions pageOptions) + { + DateTime? beforeDate = null; + if (!string.IsNullOrWhiteSpace(pageOptions.ContinuationToken) && + long.TryParse(pageOptions.ContinuationToken, out var binaryDate)) + { + beforeDate = DateTime.SpecifyKind(DateTime.FromBinary(binaryDate), DateTimeKind.Utc); + } + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new EventReadPageByOrganizationIdActingUserIdQuery(organizationId, actingUserId, + startDate, endDate, beforeDate, pageOptions); + var events = await query.Run(dbContext).ToListAsync(); + + var result = new PagedResult<IEvent>(); + if (events.Any() && events.Count >= pageOptions.PageSize) + { + result.ContinuationToken = events.Last().Date.ToBinary().ToString(); + } + result.Data.AddRange(events); + return result; + } + } + + public async Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate, PageOptions pageOptions) + { + DateTime? beforeDate = null; + if (!string.IsNullOrWhiteSpace(pageOptions.ContinuationToken) && + long.TryParse(pageOptions.ContinuationToken, out var binaryDate)) + { + beforeDate = DateTime.SpecifyKind(DateTime.FromBinary(binaryDate), DateTimeKind.Utc); + } + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new EventReadPageByOrganizationIdQuery(organizationId, startDate, + endDate, beforeDate, pageOptions); + var events = await query.Run(dbContext).ToListAsync(); + + var result = new PagedResult<IEvent>(); + if (events.Any() && events.Count >= pageOptions.PageSize) + { + result.ContinuationToken = events.Last().Date.ToBinary().ToString(); + } + result.Data.AddRange(events); + return result; + } + } + + public async Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate, PageOptions pageOptions) + { + DateTime? beforeDate = null; + if (!string.IsNullOrWhiteSpace(pageOptions.ContinuationToken) && + long.TryParse(pageOptions.ContinuationToken, out var binaryDate)) + { + beforeDate = DateTime.SpecifyKind(DateTime.FromBinary(binaryDate), DateTimeKind.Utc); + } + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new EventReadPageByUserIdQuery(userId, startDate, + endDate, beforeDate, pageOptions); + var events = await query.Run(dbContext).ToListAsync(); + + var result = new PagedResult<IEvent>(); + if (events.Any() && events.Count >= pageOptions.PageSize) + { + result.ContinuationToken = events.Last().Date.ToBinary().ToString(); + } + result.Data.AddRange(events); + return result; + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/FolderRepository.cs b/src/Core/Repositories/EntityFramework/FolderRepository.cs new file mode 100644 index 0000000000..823491f351 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/FolderRepository.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class FolderRepository : Repository<TableModel.Folder, EfModel.Folder, Guid>, IFolderRepository + { + public FolderRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Folders) + { } + + public async Task<Folder> GetByIdAsync(Guid id, Guid userId) + { + var folder = await base.GetByIdAsync(id); + if (folder == null || folder.UserId != userId) + { + return null; + } + + return folder; + } + + public async Task<ICollection<Folder>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from f in dbContext.Folders + where f.UserId == userId + select f; + var folders = await query.ToListAsync(); + return Mapper.Map<List<TableModel.Folder>>(folders); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/GrantRepository.cs b/src/Core/Repositories/EntityFramework/GrantRepository.cs new file mode 100644 index 0000000000..a71aff2272 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/GrantRepository.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class GrantRepository : BaseEntityFrameworkRepository, IGrantRepository + { + public GrantRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper) + { } + + public async Task DeleteByKeyAsync(string key) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from g in dbContext.Grants + where g.Key == key + select g; + dbContext.Remove(query); + await dbContext.SaveChangesAsync(); + } + } + + public async Task DeleteManyAsync(string subjectId, string sessionId, string clientId, string type) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from g in dbContext.Grants + where g.SubjectId == subjectId && + g.ClientId == clientId && + g.SessionId == sessionId && + g.Type == type + select g; + dbContext.Remove(query); + await dbContext.SaveChangesAsync(); + } + } + + public async Task<Grant> GetByKeyAsync(string key) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from g in dbContext.Grants + where g.Key == key + select g; + var grant = await query.FirstOrDefaultAsync(); + return grant; + } + } + + public async Task<ICollection<Grant>> GetManyAsync(string subjectId, string sessionId, string clientId, string type) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from g in dbContext.Grants + where g.SubjectId == subjectId && + g.ClientId == clientId && + g.SessionId == sessionId && + g.Type == type + select g; + var grants = await query.ToListAsync(); + return (ICollection<Grant>)grants; + } + } + + public async Task SaveAsync(Grant obj) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var existingGrant = await (from g in dbContext.Grants + where g.Key == obj.Key + select g).FirstOrDefaultAsync(); + if (existingGrant != null) + { + dbContext.Entry(existingGrant).CurrentValues.SetValues(obj); + } + else + { + var entity = Mapper.Map<EfModel.Grant>(obj); + await dbContext.AddAsync(entity); + await dbContext.SaveChangesAsync(); + } + } + } + } +} + diff --git a/src/Core/Repositories/EntityFramework/GroupRepository.cs b/src/Core/Repositories/EntityFramework/GroupRepository.cs new file mode 100644 index 0000000000..863e3ae6e8 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/GroupRepository.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class GroupRepository : Repository<TableModel.Group, EfModel.Group, Guid>, IGroupRepository + { + public GroupRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Groups) + { } + + public async Task CreateAsync(Group obj, IEnumerable<SelectionReadOnly> collections) + { + var grp = await base.CreateAsync(obj); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var availibleCollections = await ( + from c in dbContext.Collections + where c.OrganizationId == grp.OrganizationId + select c).ToListAsync(); + var filteredCollections = collections.Where(c => availibleCollections.Any(a => c.Id == a.Id)); + var collectionGroups = filteredCollections.Select(y => new EfModel.CollectionGroup + { + CollectionId = y.Id, + GroupId = grp.Id, + ReadOnly = y.ReadOnly, + HidePasswords = y.HidePasswords, + }); + await dbContext.CollectionGroups.AddRangeAsync(collectionGroups); + await dbContext.SaveChangesAsync(); + } + } + + public async Task DeleteUserAsync(Guid groupId, Guid organizationUserId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from gu in dbContext.GroupUsers + where gu.GroupId == groupId && + gu.OrganizationUserId == organizationUserId + select gu; + dbContext.RemoveRange(await query.ToListAsync()); + await dbContext.SaveChangesAsync(); + } + } + + public async Task<Tuple<Group, ICollection<SelectionReadOnly>>> GetByIdWithCollectionsAsync(Guid id) + { + var grp = await base.GetByIdAsync(id); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = await ( + from cg in dbContext.CollectionGroups + where cg.GroupId == id + select cg).ToListAsync(); + var collections = query.Select(c => new SelectionReadOnly + { + Id = c.CollectionId, + ReadOnly = c.ReadOnly, + HidePasswords = c.HidePasswords, + }).ToList(); + return new Tuple<Group, ICollection<SelectionReadOnly>>( + grp, collections); + } + } + + public async Task<ICollection<Group>> GetManyByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var data = await ( + from g in dbContext.Groups + where g.OrganizationId == organizationId + select g).ToListAsync(); + return Mapper.Map<List<TableModel.Group>>(data); + } + } + + public async Task<ICollection<GroupUser>> GetManyGroupUsersByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = + from gu in dbContext.GroupUsers + join g in dbContext.Groups + on gu.GroupId equals g.Id + where g.OrganizationId == organizationId + select gu; + var groupUsers = await query.ToListAsync(); + return Mapper.Map<List<TableModel.GroupUser>>(groupUsers); + } + } + + public async Task<ICollection<Guid>> GetManyIdsByUserIdAsync(Guid organizationUserId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = + from gu in dbContext.GroupUsers + where gu.OrganizationUserId == organizationUserId + select gu; + var groupIds = await query.Select(x => x.GroupId).ToListAsync(); + return groupIds; + } + } + + public async Task<ICollection<Guid>> GetManyUserIdsByIdAsync(Guid id) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = + from gu in dbContext.GroupUsers + where gu.GroupId == id + select gu; + var groupIds = await query.Select(x => x.OrganizationUserId).ToListAsync(); + return groupIds; + } + } + + public async Task ReplaceAsync(Group obj, IEnumerable<SelectionReadOnly> collections) + { + await base.ReplaceAsync(obj); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + await UserBumpAccountRevisionDateByOrganizationId(obj.OrganizationId); + } + } + + public async Task UpdateUsersAsync(Guid groupId, IEnumerable<Guid> organizationUserIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var orgId = (await dbContext.Groups.FindAsync(groupId)).OrganizationId; + var insert = from ou in dbContext.OrganizationUsers + where organizationUserIds.Contains(ou.Id) && + ou.OrganizationId == orgId && + !dbContext.GroupUsers.Any(gu => gu.GroupId == groupId && ou.Id == gu.OrganizationUserId) + select new EfModel.GroupUser + { + GroupId = groupId, + OrganizationUserId = ou.Id, + }; + await dbContext.AddRangeAsync(insert); + + var delete = from gu in dbContext.GroupUsers + where gu.GroupId == groupId && + !organizationUserIds.Contains(gu.OrganizationUserId) + select gu; + dbContext.RemoveRange(delete); + await dbContext.SaveChangesAsync(); + await UserBumpAccountRevisionDateByOrganizationId(orgId); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/InstallationRepository.cs b/src/Core/Repositories/EntityFramework/InstallationRepository.cs new file mode 100644 index 0000000000..f28e7cd2be --- /dev/null +++ b/src/Core/Repositories/EntityFramework/InstallationRepository.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class InstallationRepository : Repository<TableModel.Installation, EfModel.Installation, Guid>, IInstallationRepository + { + public InstallationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Installations) + { } + } +} diff --git a/src/Core/Repositories/EntityFramework/MaintenanceRepository.cs b/src/Core/Repositories/EntityFramework/MaintenanceRepository.cs new file mode 100644 index 0000000000..9ad8e85144 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/MaintenanceRepository.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class MaintenanceRepository : BaseEntityFrameworkRepository, IMaintenanceRepository + { + public MaintenanceRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper) + { } + + public async Task DeleteExpiredGrantsAsync() + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from g in dbContext.Grants + where g.ExpirationDate < DateTime.UtcNow + select g; + dbContext.RemoveRange(query); + await dbContext.SaveChangesAsync(); + } + } + + public Task DisableCipherAutoStatsAsync() + { + return Task.CompletedTask; + } + + public Task RebuildIndexesAsync() + { + return Task.CompletedTask; + } + + public Task UpdateStatisticsAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/OrganizationRepository.cs b/src/Core/Repositories/EntityFramework/OrganizationRepository.cs index 47bae92fef..2f7c5781d5 100644 --- a/src/Core/Repositories/EntityFramework/OrganizationRepository.cs +++ b/src/Core/Repositories/EntityFramework/OrganizationRepository.cs @@ -1,14 +1,14 @@ -using System; -using System.Threading.Tasks; -using TableModel = Bit.Core.Models.Table; +using AutoMapper; +using Bit.Core.Models.Table; using DataModel = Bit.Core.Models.Data; using EFModel = Bit.Core.Models.EntityFramework; -using System.Linq; -using System.Collections.Generic; -using AutoMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Bit.Core.Models.Table; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; +using TableModel = Bit.Core.Models.Table; namespace Bit.Core.Repositories.EntityFramework { @@ -40,48 +40,35 @@ namespace Bit.Core.Repositories.EntityFramework } public async Task<ICollection<TableModel.Organization>> GetManyByUserIdAsync(Guid userId) - { - // TODO - return await Task.FromResult(null as ICollection<TableModel.Organization>); - } - - public async Task<ICollection<TableModel.Organization>> SearchAsync(string name, string userEmail, bool? paid, - int skip, int take) { using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - // TODO: more filters var organizations = await GetDbSet(dbContext) - .Where(e => name == null || e.Name.StartsWith(name)) - .OrderBy(e => e.Name) - .Skip(skip).Take(take) - .ToListAsync(); + .Select(e => e.OrganizationUsers + .Where(ou => ou.UserId == userId) + .Select(ou => ou.Organization)) + .ToListAsync(); return Mapper.Map<List<TableModel.Organization>>(organizations); } } - public async Task UpdateStorageAsync(Guid id) + public async Task<ICollection<TableModel.Organization>> SearchAsync(string name, string userEmail, + bool? paid, int skip, int take) { using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - var ciphers = await dbContext.Ciphers - .Where(e => e.UserId == null && e.OrganizationId == id).ToListAsync(); - var storage = ciphers.Sum(e => e.AttachmentsJson?.RootElement.EnumerateArray() - .Sum(p => p.GetProperty("Size").GetInt64()) ?? 0); - var organization = new EFModel.Organization - { - Id = id, - RevisionDate = DateTime.UtcNow, - Storage = storage, - }; - var set = GetDbSet(dbContext); - set.Attach(organization); - var entry = dbContext.Entry(organization); - entry.Property(e => e.RevisionDate).IsModified = true; - entry.Property(e => e.Storage).IsModified = true; - await dbContext.SaveChangesAsync(); + var organizations = await GetDbSet(dbContext) + .Where(e => name == null || e.Name.Contains(name)) + .Where(e => userEmail == null || e.OrganizationUsers.Any(u => u.Email == userEmail)) + .Where(e => paid == null || + (paid == true && !string.IsNullOrWhiteSpace(e.GatewaySubscriptionId)) || + (paid == false && e.GatewaySubscriptionId == null)) + .OrderBy(e => e.CreationDate) + .Skip(skip).Take(take) + .ToListAsync(); + return Mapper.Map<List<TableModel.Organization>>(organizations); } } @@ -103,5 +90,10 @@ namespace Bit.Core.Repositories.EntityFramework }).ToListAsync(); } } + + public async Task UpdateStorageAsync(Guid id) + { + await OrganizationUpdateStorage(id); + } } } diff --git a/src/Core/Repositories/EntityFramework/OrganizationUserRepository.cs b/src/Core/Repositories/EntityFramework/OrganizationUserRepository.cs new file mode 100644 index 0000000000..5175cb27eb --- /dev/null +++ b/src/Core/Repositories/EntityFramework/OrganizationUserRepository.cs @@ -0,0 +1,393 @@ +using AutoMapper; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework.Queries; +using EfModel = Bit.Core.Models.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class OrganizationUserRepository : Repository<TableModel.OrganizationUser, EfModel.OrganizationUser, Guid>, IOrganizationUserRepository + { + public OrganizationUserRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationUsers) + { } + + public async Task CreateAsync(OrganizationUser obj, IEnumerable<SelectionReadOnly> collections) + { + var organizationUser = await base.CreateAsync(obj); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var availibleCollections = await ( + from c in dbContext.Collections + where c.OrganizationId == organizationUser.OrganizationId + select c).ToListAsync(); + var filteredCollections = collections.Where(c => availibleCollections.Any(a => c.Id == a.Id)); + var collectionUsers = filteredCollections.Select(y => new EfModel.CollectionUser + { + CollectionId = y.Id, + OrganizationUserId = organizationUser.Id, + ReadOnly = y.ReadOnly, + HidePasswords = y.HidePasswords, + }); + await dbContext.CollectionUsers.AddRangeAsync(collectionUsers); + await dbContext.SaveChangesAsync(); + } + } + + public async Task CreateManyAsync(IEnumerable<OrganizationUser> organizationUsers) + { + if (!organizationUsers.Any()) + { + return; + } + + foreach (var organizationUser in organizationUsers) + { + organizationUser.SetNewId(); + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entities = Mapper.Map<List<EfModel.OrganizationUser>>(organizationUsers); + await dbContext.AddRangeAsync(entities); + } + } + + public async Task DeleteManyAsync(IEnumerable<Guid> organizationUserIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entities = dbContext.FindAsync<EfModel.OrganizationUser>(organizationUserIds); + dbContext.RemoveRange(entities); + await dbContext.SaveChangesAsync(); + } + } + + public async Task<Tuple<OrganizationUser, ICollection<SelectionReadOnly>>> GetByIdWithCollectionsAsync(Guid id) + { + var organizationUser = await base.GetByIdAsync(id); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = await ( + from ou in dbContext.OrganizationUsers + join cu in dbContext.CollectionUsers + on ou.Id equals cu.OrganizationUserId + where !ou.AccessAll && + ou.Id == id + select cu).ToListAsync(); + var collections = query.Select(cu => new SelectionReadOnly + { + Id = cu.CollectionId, + ReadOnly = cu.ReadOnly, + HidePasswords = cu.HidePasswords, + }); + return new Tuple<OrganizationUser, ICollection<SelectionReadOnly>>( + organizationUser, collections.ToList()); + } + } + + public async Task<OrganizationUser> GetByOrganizationAsync(Guid organizationId, Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = await GetDbSet(dbContext) + .FirstOrDefaultAsync(e => e.OrganizationId == organizationId && e.UserId == userId); + return entity; + } + } + + public async Task<OrganizationUser> GetByOrganizationEmailAsync(Guid organizationId, string email) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = await GetDbSet(dbContext) + .FirstOrDefaultAsync(ou => ou.OrganizationId == organizationId && + !string.IsNullOrWhiteSpace(ou.Email) && + ou.Email == email); + return entity; + } + } + + public async Task<int> GetCountByFreeOrganizationAdminUserAsync(Guid userId) + { + var query = new OrganizationUserReadCountByFreeOrganizationAdminUserQuery(userId); + return await GetCountFromQuery(query); + } + + public async Task<int> GetCountByOnlyOwnerAsync(Guid userId) + { + var query = new OrganizationUserReadCountByOnlyOwnerQuery(userId); + return await GetCountFromQuery(query); + } + + public async Task<int> GetCountByOrganizationAsync(Guid organizationId, string email, bool onlyRegisteredUsers) + { + var query = new OrganizationUserReadCountByOrganizationIdEmailQuery(organizationId, email, onlyRegisteredUsers); + return await GetCountFromQuery(query); + } + + public async Task<int> GetCountByOrganizationIdAsync(Guid organizationId) + { + var query = new OrganizationUserReadCountByOrganizationIdQuery(organizationId); + return await GetCountFromQuery(query); + } + + public async Task<OrganizationUserUserDetails> GetDetailsByIdAsync(Guid id) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new OrganizationUserUserDetailsViewQuery(); + var entity = await view.Run(dbContext).FirstOrDefaultAsync(ou => ou.Id == id); + return entity; + } + } + + public async Task<Tuple<OrganizationUserUserDetails, ICollection<SelectionReadOnly>>> GetDetailsByIdWithCollectionsAsync(Guid id) + { + var organizationUserUserDetails = await GetDetailsByIdAsync(id); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from ou in dbContext.OrganizationUsers + join cu in dbContext.CollectionUsers on ou.Id equals cu.OrganizationUserId + where !ou.AccessAll && ou.Id == id + select cu; + var collections = await query.Select(cu => new SelectionReadOnly + { + Id = cu.CollectionId, + ReadOnly = cu.ReadOnly, + HidePasswords = cu.HidePasswords, + }).ToListAsync(); + return new Tuple<OrganizationUserUserDetails, ICollection<SelectionReadOnly>>(organizationUserUserDetails, collections); + } + } + + public async Task<OrganizationUserOrganizationDetails> GetDetailsByUserAsync(Guid userId, Guid organizationId, OrganizationUserStatusType? status = null) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new OrganizationUserOrganizationDetailsViewQuery(); + var t = await (view.Run(dbContext)).ToArrayAsync(); + var entity = await view.Run(dbContext) + .FirstOrDefaultAsync(o => o.UserId == userId && + o.OrganizationId == organizationId && + (status == null || o.Status == status)); + return entity; + } + } + + public async Task<ICollection<OrganizationUser>> GetManyAsync(IEnumerable<Guid> Ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from ou in dbContext.OrganizationUsers + where Ids.Contains(ou.Id) + select ou; + var data = await query.ToArrayAsync(); + return data; + } + } + + public async Task<ICollection<OrganizationUser>> GetManyByManyUsersAsync(IEnumerable<Guid> userIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from ou in dbContext.OrganizationUsers + where userIds.Contains(ou.Id) + select ou; + return Mapper.Map<List<TableModel.OrganizationUser>>(await query.ToListAsync()); + } + } + + public async Task<ICollection<OrganizationUser>> GetManyByOrganizationAsync(Guid organizationId, OrganizationUserType? type) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from ou in dbContext.OrganizationUsers + where ou.OrganizationId == organizationId && + (type == null || ou.Type == type) + select ou; + return Mapper.Map<List<TableModel.OrganizationUser>>(await query.ToListAsync()); + } + } + + public async Task<ICollection<OrganizationUser>> GetManyByUserAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from ou in dbContext.OrganizationUsers + where ou.UserId == userId + select ou; + return Mapper.Map<List<TableModel.OrganizationUser>>(await query.ToListAsync()); + } + } + + public async Task<ICollection<OrganizationUserUserDetails>> GetManyDetailsByOrganizationAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new OrganizationUserUserDetailsViewQuery(); + var query = from ou in view.Run(dbContext) + where ou.OrganizationId == organizationId + select ou; + return await query.ToListAsync(); + } + } + + public async Task<ICollection<OrganizationUserOrganizationDetails>> GetManyDetailsByUserAsync(Guid userId, + OrganizationUserStatusType? status = null) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = new OrganizationUserOrganizationDetailsViewQuery(); + var query = from ou in view.Run(dbContext) + where ou.UserId == userId && + (status == null || ou.Status == status) + select ou; + var organizationUsers = await query.ToListAsync(); + return organizationUsers; + } + } + + public async Task<IEnumerable<OrganizationUserPublicKey>> GetManyPublicKeysByOrganizationUserAsync(Guid organizationId, IEnumerable<Guid> Ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from ou in dbContext.OrganizationUsers + where Ids.Contains(ou.Id) && ou.Status == OrganizationUserStatusType.Accepted + join u in dbContext.Users + on ou.UserId equals u.Id + where ou.OrganizationId == organizationId + select new { ou, u }; + var data = await query + .Select(x => new OrganizationUserPublicKey() + { + Id = x.ou.Id, + PublicKey = x.u.PublicKey, + }).ToListAsync(); + return data; + } + } + + public async Task ReplaceAsync(OrganizationUser obj, IEnumerable<SelectionReadOnly> collections) + { + await base.ReplaceAsync(obj); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + + var procedure = new OrganizationUserUpdateWithCollectionsQuery(obj, collections); + + var update = procedure.Update.Run(dbContext); + dbContext.UpdateRange(await update.ToListAsync()); + + var insert = procedure.Insert.Run(dbContext); + await dbContext.AddRangeAsync(await insert.ToListAsync()); + + dbContext.RemoveRange(await procedure.Delete.Run(dbContext).ToListAsync()); + await dbContext.SaveChangesAsync(); + } + } + + public async Task ReplaceManyAsync(IEnumerable<OrganizationUser> organizationUsers) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + dbContext.UpdateRange(organizationUsers); + await dbContext.SaveChangesAsync(); + await UserBumpManyAccountRevisionDates(organizationUsers + .Where(ou => ou.UserId.HasValue) + .Select(ou => ou.UserId.Value).ToArray()); + } + } + + public async Task<IEnumerable<string>> SelectKnownEmailsAsync(Guid organizationId, IEnumerable<string> emails, bool onlyRegisteredUsers) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var usersQuery = from ou in dbContext.OrganizationUsers + join u in dbContext.Users + on ou.UserId equals u.Id into u_g + from u in u_g + where ou.OrganizationId == organizationId + select new { ou, u }; + var ouu = await usersQuery.ToListAsync(); + var ouEmails = ouu.Select(x => x.ou.Email); + var uEmails = ouu.Select(x => x.u.Email); + var knownEmails = from e in emails + where (ouEmails.Contains(e) || uEmails.Contains(e)) && + (!onlyRegisteredUsers && (uEmails.Contains(e) || ouEmails.Contains(e))) || + (onlyRegisteredUsers && uEmails.Contains(e)) + select e; + return knownEmails; + } + } + + public async Task UpdateGroupsAsync(Guid orgUserId, IEnumerable<Guid> groupIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + + var procedure = new GroupUserUpdateGroupsQuery(orgUserId, groupIds); + + var insert = procedure.Insert.Run(dbContext); + var data = await insert.ToListAsync(); + await dbContext.AddRangeAsync(data); + + var delete = procedure.Delete.Run(dbContext); + var deleteData = await delete.ToListAsync(); + dbContext.RemoveRange(deleteData); + await UserBumpAccountRevisionDateByOrganizationUserId(orgUserId); + await dbContext.SaveChangesAsync(); + } + } + + public async Task UpsertManyAsync(IEnumerable<OrganizationUser> organizationUsers) + { + var createUsers = new List<OrganizationUser>(); + var replaceUsers = new List<OrganizationUser>(); + foreach (var organizationUser in organizationUsers) + { + if (organizationUser.Id.Equals(default)) + { + createUsers.Add(organizationUser); + } + else + { + replaceUsers.Add(organizationUser); + } + } + + await CreateManyAsync(createUsers); + await ReplaceManyAsync(replaceUsers); + } + + Task<ICollection<string>> IOrganizationUserRepository.SelectKnownEmailsAsync(Guid organizationId, IEnumerable<string> emails, bool onlyRegisteredUsers) => throw new NotImplementedException(); + } +} diff --git a/src/Core/Repositories/EntityFramework/PolicyRepository.cs b/src/Core/Repositories/EntityFramework/PolicyRepository.cs new file mode 100644 index 0000000000..8ad345000f --- /dev/null +++ b/src/Core/Repositories/EntityFramework/PolicyRepository.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Enums; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework.Queries; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class PolicyRepository : Repository<TableModel.Policy, EfModel.Policy, Guid>, IPolicyRepository + { + public PolicyRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Policies) + { } + + public async Task<Policy> GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Policies + .FirstOrDefaultAsync(p => p.OrganizationId == organizationId && p.Type == type); + return Mapper.Map<TableModel.Policy>(results); + } + } + + public async Task<ICollection<Policy>> GetManyByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Policies + .Where(p => p.OrganizationId == organizationId) + .ToListAsync(); + return Mapper.Map<List<TableModel.Policy>>(results); + } + } + + public async Task<ICollection<Policy>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + + var query = new PolicyReadByUserIdQuery(userId); + var results = await query.Run(dbContext).ToListAsync(); + return Mapper.Map<List<TableModel.Policy>>(results); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/ProviderOrganizationProviderUserRepository.cs b/src/Core/Repositories/EntityFramework/ProviderOrganizationProviderUserRepository.cs new file mode 100644 index 0000000000..a468c4e31c --- /dev/null +++ b/src/Core/Repositories/EntityFramework/ProviderOrganizationProviderUserRepository.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Models.Table.Provider; +using Bit.Core.Repositories.EntityFramework; +using TableModel = Bit.Core.Models.Table; +using EfModel = Bit.Core.Models.EntityFramework; +using Microsoft.Extensions.DependencyInjection; +using AutoMapper; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class ProviderOrganizationProviderUserRepository : + Repository<TableModel.Provider.ProviderOrganizationProviderUser, EfModel.Provider.ProviderOrganizationProviderUser, Guid>, IProviderOrganizationProviderUserRepository + { + public ProviderOrganizationProviderUserRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.ProviderOrganizationProviderUsers) + { } + } +} diff --git a/src/Core/Repositories/EntityFramework/ProviderOrganizationRepository.cs b/src/Core/Repositories/EntityFramework/ProviderOrganizationRepository.cs new file mode 100644 index 0000000000..6bce3cc190 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/ProviderOrganizationRepository.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Models.Table.Provider; +using Bit.Core.Repositories.EntityFramework; +using TableModel = Bit.Core.Models.Table; +using EfModel = Bit.Core.Models.EntityFramework; +using Microsoft.Extensions.DependencyInjection; +using AutoMapper; +using Bit.Core.Models.Data; +using Bit.Core.Repositories.EntityFramework.Queries; +using Microsoft.EntityFrameworkCore; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class ProviderOrganizationRepository : + Repository<TableModel.Provider.ProviderOrganization, EfModel.Provider.ProviderOrganization, Guid>, IProviderOrganizationRepository + { + public ProviderOrganizationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.ProviderOrganizations) + { } + + public async Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new ProviderOrganizationOrganizationDetailsReadByProviderIdQuery(providerId); + var data = await query.Run(dbContext).ToListAsync(); + return data; + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/ProviderRepository.cs b/src/Core/Repositories/EntityFramework/ProviderRepository.cs new file mode 100644 index 0000000000..1cb261ad4c --- /dev/null +++ b/src/Core/Repositories/EntityFramework/ProviderRepository.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.Core.Models.Table.Provider; +using Bit.Core.Repositories.EntityFramework; +using TableModel = Bit.Core.Models.Table; +using EfModel = Bit.Core.Models.EntityFramework; +using Microsoft.Extensions.DependencyInjection; +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class ProviderRepository : Repository<TableModel.Provider.Provider, EfModel.Provider.Provider, Guid>, IProviderRepository + { + + public ProviderRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Providers) + { } + + public Task<ICollection<ProviderAbility>> GetManyAbilitiesAsync() => throw new NotImplementedException(); + + public async Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = !string.IsNullOrWhiteSpace(userEmail) ? + (from p in dbContext.Providers + join pu in dbContext.ProviderUsers + on p.Id equals pu.ProviderId + join u in dbContext.Users + on pu.UserId equals u.Id + where (string.IsNullOrWhiteSpace(name) || p.Name.Contains(name)) && + u.Email == userEmail + orderby p.CreationDate descending + select new { p, pu, u }).Skip(skip).Take(take).Select(x => x.p) : + (from p in dbContext.Providers + where string.IsNullOrWhiteSpace(name) || p.Name.Contains(name) + orderby p.CreationDate descending + select new { p }).Skip(skip).Take(take).Select(x => x.p); + return await query.ToArrayAsync(); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/ProviderUserRepository.cs b/src/Core/Repositories/EntityFramework/ProviderUserRepository.cs new file mode 100644 index 0000000000..67c642b3c0 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/ProviderUserRepository.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.Core.Models.Table.Provider; +using TableModel = Bit.Core.Models.Table; +using EfModel = Bit.Core.Models.EntityFramework; +using Microsoft.Extensions.DependencyInjection; +using AutoMapper; +using Bit.Core.Enums.Provider; +using Microsoft.EntityFrameworkCore; +using Bit.Core.Models.Data; +using Bit.Core.Repositories.EntityFramework.Queries; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class ProviderUserRepository : + Repository<TableModel.Provider.ProviderUser, EfModel.Provider.ProviderUser, Guid>, IProviderUserRepository + { + public ProviderUserRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.ProviderUsers) + { } + + public async Task<int> GetCountByProviderAsync(Guid providerId, string email, bool onlyRegisteredUsers) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from pu in dbContext.ProviderUsers + join u in dbContext.Users + on pu.UserId equals u.Id into u_g + from u in u_g.DefaultIfEmpty() + where pu.ProviderId == providerId && + ((!onlyRegisteredUsers && (pu.Email == email || u.Email == email)) || + (onlyRegisteredUsers && u.Email == email)) + select new { pu, u }; + return await query.CountAsync(); + } + } + + public async Task<ICollection<ProviderUser>> GetManyAsync(IEnumerable<Guid> ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.ProviderUsers.Where(item => ids.Contains(item.Id)); + return await query.ToArrayAsync(); + } + } + + public async Task<ICollection<ProviderUser>> GetManyByProviderAsync(Guid providerId, ProviderUserType? type = null) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = dbContext.ProviderUsers.Where(pu => pu.ProviderId.Equals(providerId) && + (type != null && pu.Type.Equals(type))); + return await query.ToArrayAsync(); + } + } + + public async Task DeleteManyAsync(IEnumerable<Guid> providerUserIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + await UserBumpAccountRevisionDateByProviderUserIds(providerUserIds.ToArray()); + var entities = dbContext.ProviderUsers.Where(pu => providerUserIds.Contains(pu.Id)); + dbContext.ProviderUsers.RemoveRange(entities); + await dbContext.SaveChangesAsync(); + } + } + + public async Task<ICollection<ProviderUser>> GetManyByUserAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from pu in dbContext.ProviderUsers + where pu.UserId == userId + select pu; + return await query.ToArrayAsync(); + } + } + public async Task<ProviderUser> GetByProviderUserAsync(Guid providerId, Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from pu in dbContext.ProviderUsers + where pu.UserId == userId && + pu.ProviderId == providerId + select pu; + return await query.FirstOrDefaultAsync(); + } + } + public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var view = from pu in dbContext.ProviderUsers + join u in dbContext.Users + on pu.UserId equals u.Id into u_g + from u in u_g.DefaultIfEmpty() + select new {pu, u}; + var data = await view.Where(e => e.pu.ProviderId == providerId).Select(e => new ProviderUserUserDetails + { + Id = e.pu.Id, + UserId = e.pu.UserId, + ProviderId = e.pu.ProviderId, + Name = e.u.Name, + Email = e.u.Email ?? e.pu.Email, + Status = e.pu.Status, + Type = e.pu.Type, + Permissions = e.pu.Permissions, + }).ToArrayAsync(); + return data; + } + } + + public async Task<ICollection<ProviderUserProviderDetails>> GetManyDetailsByUserAsync(Guid userId, ProviderUserStatusType? status = null) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new ProviderUserProviderDetailsReadByUserIdStatusQuery(userId, status); + var data = await query.Run(dbContext).ToArrayAsync(); + return data; + } + } + + public async Task<IEnumerable<ProviderUserPublicKey>> GetManyPublicKeysByProviderUserAsync(Guid providerId, IEnumerable<Guid> Ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new UserReadPublicKeysByProviderUserIdsQuery(providerId, Ids); + var data = await query.Run(dbContext).ToListAsync(); + return data; + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CipherDetailsQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CipherDetailsQuery.cs new file mode 100644 index 0000000000..761dc7471a --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CipherDetailsQuery.cs @@ -0,0 +1,40 @@ +using System.Linq; +using System; +using System.Collections.Generic; +using Core.Models.Data; +using Bit.Core.Utilities; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CipherDetailsQuery : IQuery<CipherDetails> + { + private readonly Guid? _userId; + private readonly bool _ignoreFolders; + public CipherDetailsQuery(Guid? userId, bool ignoreFolders = false) + { + _userId = userId; + _ignoreFolders = ignoreFolders; + } + public virtual IQueryable<CipherDetails> Run(DatabaseContext dbContext) + { + var query = from c in dbContext.Ciphers + select new CipherDetails + { + Id = c.Id, + UserId = c.UserId, + OrganizationId = c.OrganizationId, + Type= c.Type, + Data = c.Data, + Attachments = c.Attachments, + CreationDate = c.CreationDate, + RevisionDate = c.RevisionDate, + DeletedDate = c.DeletedDate, + Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.Contains($"\"{_userId}\":true"), + FolderId = (_ignoreFolders || !_userId.HasValue || c.Folders == null || !c.Folders.Contains(_userId.Value.ToString())) ? + null : + CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(c.Folders)[_userId.Value], + }; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CipherOrganizationDetailsReadByIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CipherOrganizationDetailsReadByIdQuery.cs new file mode 100644 index 0000000000..52566c9e2a --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CipherOrganizationDetailsReadByIdQuery.cs @@ -0,0 +1,41 @@ +using System.Linq; +using System; +using Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CipherOrganizationDetailsReadByIdQuery : IQuery<CipherOrganizationDetails> + { + private readonly Guid _cipherId; + + public CipherOrganizationDetailsReadByIdQuery(Guid cipherId) + { + _cipherId = cipherId; + } + + public virtual IQueryable<CipherOrganizationDetails> Run(DatabaseContext dbContext) + { + var query = from c in dbContext.Ciphers + join o in dbContext.Organizations + on c.OrganizationId equals o.Id into o_g + from o in o_g.DefaultIfEmpty() + where c.Id == _cipherId + select new CipherOrganizationDetails + { + Id = c.Id, + UserId = c.UserId, + OrganizationId = c.OrganizationId, + Type = c.Type, + Data = c.Data, + Favorites = c.Favorites, + Folders = c.Folders, + Attachments = c.Attachments, + CreationDate = c.CreationDate, + RevisionDate = c.RevisionDate, + DeletedDate = c.DeletedDate, + OrganizationUseTotp = o.UseTotp, + }; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CipherReadCanEditByIdUserIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CipherReadCanEditByIdUserIdQuery.cs new file mode 100644 index 0000000000..5187726b21 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CipherReadCanEditByIdUserIdQuery.cs @@ -0,0 +1,58 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Bit.Core.Enums; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CipherReadCanEditByIdUserIdQuery : IQuery<Cipher> + { + private readonly Guid _userId; + private readonly Guid _cipherId; + + public CipherReadCanEditByIdUserIdQuery(Guid userId, Guid cipherId) + { + _userId = userId; + _cipherId = cipherId; + } + + public virtual IQueryable<Cipher> Run(DatabaseContext dbContext) + { + var query = from c in dbContext.Ciphers + join o in dbContext.Organizations + on c.OrganizationId equals o.Id into o_g + from o in o_g.DefaultIfEmpty() + where !c.UserId.HasValue + join ou in dbContext.OrganizationUsers + on o.Id equals ou.OrganizationId into ou_g + from ou in ou_g.DefaultIfEmpty() + where ou.UserId == _userId + join cc in dbContext.CollectionCiphers + on c.Id equals cc.CipherId into cc_g + from cc in cc_g.DefaultIfEmpty() + where !c.UserId.HasValue && !ou.AccessAll + join cu in dbContext.CollectionUsers + on cc.CollectionId equals cu.CollectionId into cu_g + from cu in cu_g.DefaultIfEmpty() + where ou.Id == cu.OrganizationUserId + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where !c.UserId.HasValue && cu.CollectionId == null && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g.DefaultIfEmpty() + join cg in dbContext.CollectionGroups + on gu.GroupId equals cg.GroupId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && cg.CollectionId == cc.CollectionId && + (c.Id == _cipherId && + (c.UserId == _userId || + (!c.UserId.HasValue && ou.Status == OrganizationUserStatusType.Confirmed && o.Enabled && + (ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null)))) && + (c.UserId.HasValue || ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly) + select c; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CipherUpdateCollectionsQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CipherUpdateCollectionsQuery.cs new file mode 100644 index 0000000000..8c3fbd5078 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CipherUpdateCollectionsQuery.cs @@ -0,0 +1,69 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Bit.Core.Enums; +using System.Collections.Generic; +using Table = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CipherUpdateCollectionsQuery : IQuery<CollectionCipher> + { + private readonly Table.Cipher _cipher; + private readonly IEnumerable<Guid> _collectionIds; + + public CipherUpdateCollectionsQuery(Table.Cipher cipher, IEnumerable<Guid> collectionIds) + { + _cipher = cipher; + _collectionIds = collectionIds; + } + + public virtual IQueryable<CollectionCipher> Run(DatabaseContext dbContext) + { + if (!_cipher.OrganizationId.HasValue || !_collectionIds.Any()) + { + return null; + } + + var availibleCollections = !_cipher.UserId.HasValue ? + from c in dbContext.Collections + where c.OrganizationId == _cipher.OrganizationId + select c.Id : + from c in dbContext.Collections + join o in dbContext.Organizations + on c.OrganizationId equals o.Id + join ou in dbContext.OrganizationUsers + on o.Id equals ou.OrganizationId + where ou.UserId == _cipher.UserId + join cu in dbContext.CollectionUsers + on c.Id equals cu.CollectionId into cu_g + from cu in cu_g.DefaultIfEmpty() + where !ou.AccessAll && cu.OrganizationUserId == ou.Id + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where cu.CollectionId == null && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g.DefaultIfEmpty() + join cg in dbContext.CollectionGroups + on c.Id equals cg.CollectionId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && gu.GroupId == cg.GroupId && + o.Id == _cipher.OrganizationId && + o.Enabled && + ou.Status == OrganizationUserStatusType.Confirmed && + (ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly) + select c.Id; + + if (!availibleCollections.Any()) + { + return null; + } + + var query = from c in availibleCollections + select new CollectionCipher { CollectionId = c, CipherId = _cipher.Id }; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CollectionCipherReadByUserIdCipherIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CollectionCipherReadByUserIdCipherIdQuery.cs new file mode 100644 index 0000000000..00608b3fe4 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CollectionCipherReadByUserIdCipherIdQuery.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CollectionCipherReadByUserIdCipherIdQuery : CollectionCipherReadByUserIdQuery + { + private readonly Guid _cipherId; + + public CollectionCipherReadByUserIdCipherIdQuery(Guid userId, Guid cipherId) : base(userId) + { + _cipherId = cipherId; + } + + public override IQueryable<CollectionCipher> Run(DatabaseContext dbContext) + { + var query = base.Run(dbContext); + return query.Where(x => x.CipherId == _cipherId); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CollectionCipherReadByUserIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CollectionCipherReadByUserIdQuery.cs new file mode 100644 index 0000000000..2b1d0d0db7 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CollectionCipherReadByUserIdQuery.cs @@ -0,0 +1,46 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Bit.Core.Enums; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CollectionCipherReadByUserIdQuery : IQuery<CollectionCipher> + { + private readonly Guid _userId; + + public CollectionCipherReadByUserIdQuery(Guid userId) + { + _userId = userId; + } + + public virtual IQueryable<CollectionCipher> Run(DatabaseContext dbContext) + { + var query = from cc in dbContext.CollectionCiphers + join c in dbContext.Collections + on cc.CollectionId equals c.Id + join ou in dbContext.OrganizationUsers + on c.OrganizationId equals ou.OrganizationId + where ou.UserId == _userId + join cu in dbContext.CollectionUsers + on c.Id equals cu.CollectionId into cu_g + from cu in cu_g + where ou.AccessAll && cu.OrganizationUserId == ou.Id + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g + where cu.CollectionId == null && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g + join cg in dbContext.CollectionGroups + on cc.CollectionId equals cg.CollectionId into cg_g + from cg in cg_g + where g.AccessAll && cg.GroupId == gu.GroupId && + ou.Status == OrganizationUserStatusType.Confirmed && + (ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null) + select cc; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CollectionReadCountByOrganizationIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CollectionReadCountByOrganizationIdQuery.cs new file mode 100644 index 0000000000..64f62682f1 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CollectionReadCountByOrganizationIdQuery.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CollectionReadCountByOrganizationIdQuery : IQuery<Collection> + { + private readonly Guid _organizationId; + + public CollectionReadCountByOrganizationIdQuery(Guid organizationId) + { + _organizationId = organizationId; + } + + public IQueryable<Collection> Run(DatabaseContext dbContext) + { + var query = from c in dbContext.Collections + where c.OrganizationId == _organizationId + select c; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/CollectionUserUpdateUsersQuery.cs b/src/Core/Repositories/EntityFramework/Queries/CollectionUserUpdateUsersQuery.cs new file mode 100644 index 0000000000..24b4ca9d17 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/CollectionUserUpdateUsersQuery.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.Linq; +using EfModel = Bit.Core.Models.EntityFramework; +using Bit.Core.Models.Data; +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class CollectionUserUpdateUsersQuery + { + public readonly CollectionUserUpdateUsersInsertQuery Insert; + public readonly CollectionUserUpdateUsersUpdateQuery Update; + public readonly CollectionUserUpdateUsersDeleteQuery Delete; + + public CollectionUserUpdateUsersQuery(Guid collectionId, IEnumerable<SelectionReadOnly> users) + { + Insert = new CollectionUserUpdateUsersInsertQuery(collectionId, users); + Update = new CollectionUserUpdateUsersUpdateQuery(collectionId, users); + Delete = new CollectionUserUpdateUsersDeleteQuery(collectionId, users); + } + } + + public class CollectionUserUpdateUsersInsertQuery : IQuery<EfModel.OrganizationUser> + { + private readonly Guid _collectionId; + private readonly IEnumerable<SelectionReadOnly> _users; + + public CollectionUserUpdateUsersInsertQuery(Guid collectionId, IEnumerable<SelectionReadOnly> users) + { + _collectionId = collectionId; + _users = users; + } + + public IQueryable<EfModel.OrganizationUser> Run(DatabaseContext dbContext) + { + var orgId = dbContext.Collections.FirstOrDefault(c => c.Id == _collectionId)?.OrganizationId; + var organizationUserIds = _users.Select(u => u.Id); + var insertQuery = from ou in dbContext.OrganizationUsers + where + organizationUserIds.Contains(ou.Id) && + ou.OrganizationId == orgId && + !dbContext.CollectionUsers.Any( + x => x.CollectionId != _collectionId && x.OrganizationUserId == ou.Id) + select ou; + return insertQuery; + } + + public async Task<IEnumerable<EfModel.CollectionUser>> BuildInMemory(DatabaseContext dbContext) + { + var data = await Run(dbContext).ToListAsync(); + var collectionUsers = data.Select(x => new EfModel.CollectionUser() + { + CollectionId = _collectionId, + OrganizationUserId = x.Id, + ReadOnly = _users.FirstOrDefault(u => u.Id.Equals(x.Id)).ReadOnly, + HidePasswords = _users.FirstOrDefault(u => u.Id.Equals(x.Id)).HidePasswords, + }); + return collectionUsers; + } + } + + public class CollectionUserUpdateUsersUpdateQuery: IQuery<EfModel.CollectionUser> + { + private readonly Guid _collectionId; + private readonly IEnumerable<SelectionReadOnly> _users; + + public CollectionUserUpdateUsersUpdateQuery(Guid collectionId, IEnumerable<SelectionReadOnly> users) + { + _collectionId = collectionId; + _users = users; + } + + public IQueryable<EfModel.CollectionUser> Run(DatabaseContext dbContext) + { + var orgId = dbContext.Collections.FirstOrDefault(c => c.Id == _collectionId)?.OrganizationId; + var ids = _users.Select(x => x.Id); + var updateQuery = from target in dbContext.CollectionUsers + where target.CollectionId == _collectionId && + ids.Contains(target.OrganizationUserId) + select target; + return updateQuery; + } + + public async Task<IEnumerable<EfModel.CollectionUser>> BuildInMemory(DatabaseContext dbContext) + { + var data = await Run(dbContext).ToListAsync(); + var collectionUsers = data.Select(x => new EfModel.CollectionUser + { + CollectionId = _collectionId, + OrganizationUserId = x.OrganizationUserId, + ReadOnly = _users.FirstOrDefault(u => u.Id.Equals(x.OrganizationUserId)).ReadOnly, + HidePasswords = _users.FirstOrDefault(u => u.Id.Equals(x.OrganizationUserId)).HidePasswords, + }); + return collectionUsers; + } + } + + public class CollectionUserUpdateUsersDeleteQuery: IQuery<EfModel.CollectionUser> + { + private readonly Guid _collectionId; + private readonly IEnumerable<SelectionReadOnly> _users; + + public CollectionUserUpdateUsersDeleteQuery(Guid collectionId, IEnumerable<SelectionReadOnly> users) + { + _collectionId = collectionId; + _users = users; + } + + public IQueryable<EfModel.CollectionUser> Run(DatabaseContext dbContext) + { + var orgId = dbContext.Collections.FirstOrDefault(c => c.Id == _collectionId)?.OrganizationId; + var deleteQuery = from cu in dbContext.CollectionUsers + where !dbContext.Users.Any( + u => u.Id == cu.OrganizationUserId) + select cu; + return deleteQuery; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/EmergencyAccessDetailsViewQuery.cs b/src/Core/Repositories/EntityFramework/Queries/EmergencyAccessDetailsViewQuery.cs new file mode 100644 index 0000000000..f2818b504a --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/EmergencyAccessDetailsViewQuery.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class EmergencyAccessDetailsViewQuery : IQuery<EmergencyAccessDetails> + { + public IQueryable<EmergencyAccessDetails> Run(DatabaseContext dbContext) + { + var query = from ea in dbContext.EmergencyAccesses + join grantee in dbContext.Users + on ea.GranteeId equals grantee.Id into grantee_g + from grantee in grantee_g.DefaultIfEmpty() + join grantor in dbContext.Users + on ea.GrantorId equals grantor.Id into grantor_g + from grantor in grantor_g.DefaultIfEmpty() + select new {ea, grantee, grantor}; + return query.Select(x => new EmergencyAccessDetails + { + Id = x.ea.Id, + GrantorId = x.ea.GrantorId, + GranteeId = x.ea.GranteeId, + Email = x.ea.Email, + KeyEncrypted = x.ea.KeyEncrypted, + Type = x.ea.Type, + Status = x.ea.Status, + WaitTimeDays = x.ea.WaitTimeDays, + RecoveryInitiatedDate = x.ea.RecoveryInitiatedDate, + LastNotificationDate = x.ea.LastNotificationDate, + CreationDate = x.ea.CreationDate, + RevisionDate = x.ea.RevisionDate, + GranteeName = x.grantee.Name, + GranteeEmail = x.grantee.Email, + GrantorName = x.grantor.Name, + GrantorEmail = x.grantor.Email, + }); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/EmergencyAccessReadCountByGrantorIdEmailQuery.cs b/src/Core/Repositories/EntityFramework/Queries/EmergencyAccessReadCountByGrantorIdEmailQuery.cs new file mode 100644 index 0000000000..b1007d56fc --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/EmergencyAccessReadCountByGrantorIdEmailQuery.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class EmergencyAccessReadCountByGrantorIdEmailQuery : IQuery<EmergencyAccess> + { + private readonly Guid _grantorId; + private readonly string _email; + private readonly bool _onlyRegisteredUsers; + + public EmergencyAccessReadCountByGrantorIdEmailQuery(Guid grantorId, string email, bool onlyRegisteredUsers) + { + _grantorId = grantorId; + _email = email; + _onlyRegisteredUsers = onlyRegisteredUsers; + } + + public IQueryable<EmergencyAccess> Run(DatabaseContext dbContext) + { + var query = from ea in dbContext.EmergencyAccesses + join u in dbContext.Users + on ea.GranteeId equals u.Id into u_g + from u in u_g.DefaultIfEmpty() + where ea.GrantorId == _grantorId && + ((!_onlyRegisteredUsers && (ea.Email == _email || u.Email == _email)) + || (_onlyRegisteredUsers && u.Email == _email)) + select ea; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/EventReadPageByCipherIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByCipherIdQuery.cs new file mode 100644 index 0000000000..47d5a02533 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByCipherIdQuery.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Bit.Core.Models.Data; +using Table = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class EventReadPageByCipherIdQuery : IQuery<Event> + { + private readonly Table.Cipher _cipher; + private readonly DateTime _startDate; + private readonly DateTime _endDate; + private readonly DateTime? _beforeDate; + private readonly PageOptions _pageOptions; + + public EventReadPageByCipherIdQuery(Table.Cipher cipher, DateTime startDate, DateTime endDate, PageOptions pageOptions) + { + _cipher = cipher; + _startDate = startDate; + _endDate = endDate; + _beforeDate = null; + _pageOptions = pageOptions; + } + + public EventReadPageByCipherIdQuery(Table.Cipher cipher, DateTime startDate, DateTime endDate, DateTime? beforeDate, PageOptions pageOptions) + { + _cipher = cipher; + _startDate = startDate; + _endDate = endDate; + _beforeDate = beforeDate; + _pageOptions = pageOptions; + } + + public IQueryable<Event> Run(DatabaseContext dbContext) + { + var q = from e in dbContext.Events + where e.Date >= _startDate && + (_beforeDate == null || e.Date < _beforeDate.Value) && + ((!_cipher.OrganizationId.HasValue && !e.OrganizationId.HasValue) || + (_cipher.OrganizationId.HasValue && _cipher.OrganizationId == e.OrganizationId)) && + ((!_cipher.UserId.HasValue && !e.UserId.HasValue) || + (_cipher.UserId.HasValue && _cipher.UserId == e.UserId)) && + _cipher.Id == e.CipherId + orderby e.Date descending + select e; + return q.Skip(0).Take(_pageOptions.PageSize); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/EventReadPageByOrganizationIdActingUserIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByOrganizationIdActingUserIdQuery.cs new file mode 100644 index 0000000000..207b8435b3 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByOrganizationIdActingUserIdQuery.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class EventReadPageByOrganizationIdActingUserIdQuery : IQuery<Event> + { + private readonly Guid _organizationId; + private readonly Guid _actingUserId; + private readonly DateTime _startDate; + private readonly DateTime _endDate; + private readonly DateTime? _beforeDate; + private readonly PageOptions _pageOptions; + + public EventReadPageByOrganizationIdActingUserIdQuery(Guid organizationId, Guid actingUserId, + DateTime startDate, DateTime endDate, DateTime? beforeDate, PageOptions pageOptions) + { + _organizationId = organizationId; + _actingUserId = actingUserId; + _startDate = startDate; + _endDate = endDate; + _beforeDate = beforeDate; + _pageOptions = pageOptions; + } + + public IQueryable<Event> Run(DatabaseContext dbContext) + { + var q = from e in dbContext.Events + where e.Date >= _startDate && + (_beforeDate != null || e.Date <= _endDate) && + (_beforeDate == null || e.Date < _beforeDate.Value) && + e.OrganizationId == _organizationId && + e.ActingUserId == _actingUserId + orderby e.Date descending + select e; + return q.Skip(0).Take(_pageOptions.PageSize); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/EventReadPageByOrganizationIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByOrganizationIdQuery.cs new file mode 100644 index 0000000000..95ac7d59e0 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByOrganizationIdQuery.cs @@ -0,0 +1,38 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class EventReadPageByOrganizationIdQuery: IQuery<Event> + { + private readonly Guid _organizationId; + private readonly DateTime _startDate; + private readonly DateTime _endDate; + private readonly DateTime? _beforeDate; + private readonly PageOptions _pageOptions; + + public EventReadPageByOrganizationIdQuery(Guid organizationId, DateTime startDate, + DateTime endDate, DateTime? beforeDate, PageOptions pageOptions) + { + _organizationId = organizationId; + _startDate = startDate; + _endDate = endDate; + _beforeDate = beforeDate; + _pageOptions = pageOptions; + } + + public IQueryable<Event> Run(DatabaseContext dbContext) + { + var q = from e in dbContext.Events + where e.Date >= _startDate && + (_beforeDate != null || e.Date <= _endDate) && + (_beforeDate == null || e.Date < _beforeDate.Value) && + e.OrganizationId == _organizationId + orderby e.Date descending + select e; + return q.Skip(0).Take(_pageOptions.PageSize); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/EventReadPageByUserIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByUserIdQuery.cs new file mode 100644 index 0000000000..d434e10f65 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/EventReadPageByUserIdQuery.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class EventReadPageByUserIdQuery: IQuery<Event> + { + private readonly Guid _userId; + private readonly DateTime _startDate; + private readonly DateTime _endDate; + private readonly DateTime? _beforeDate; + private readonly PageOptions _pageOptions; + + public EventReadPageByUserIdQuery(Guid userId, DateTime startDate, + DateTime endDate, DateTime? beforeDate, PageOptions pageOptions) + { + _userId = userId; + _startDate = startDate; + _endDate = endDate; + _beforeDate = beforeDate; + _pageOptions = pageOptions; + } + + public IQueryable<Event> Run(DatabaseContext dbContext) + { + var q = from e in dbContext.Events + where e.Date >= _startDate && + (_beforeDate != null || e.Date <= _endDate) && + (_beforeDate == null || e.Date < _beforeDate.Value) && + !e.OrganizationId.HasValue && + e.ActingUserId == _userId + orderby e.Date descending + select e; + return q.Skip(0).Take(_pageOptions.PageSize); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/GroupUserUpdateGroupsQuery.cs b/src/Core/Repositories/EntityFramework/Queries/GroupUserUpdateGroupsQuery.cs new file mode 100644 index 0000000000..6b61533a73 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/GroupUserUpdateGroupsQuery.cs @@ -0,0 +1,73 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class GroupUserUpdateGroupsQuery + { + public readonly GroupUserUpdateGroupsInsertQuery Insert; + public readonly GroupUserUpdateGroupsDeleteQuery Delete; + + public GroupUserUpdateGroupsQuery(Guid organizationUserId, IEnumerable<Guid> groupIds) + { + Insert = new GroupUserUpdateGroupsInsertQuery(organizationUserId, groupIds); + Delete = new GroupUserUpdateGroupsDeleteQuery(organizationUserId, groupIds); + } + } + + public class GroupUserUpdateGroupsInsertQuery : IQuery<GroupUser> + { + private readonly Guid _organizationUserId; + private readonly IEnumerable<Guid> _groupIds; + + public GroupUserUpdateGroupsInsertQuery(Guid organizationUserId, IEnumerable<Guid> collections) + { + _organizationUserId = organizationUserId; + _groupIds = collections; + } + + public IQueryable<GroupUser> Run(DatabaseContext dbContext) + { + var orgUser = from ou in dbContext.OrganizationUsers + where ou.Id == _organizationUserId + select ou; + var groupIdEntities = dbContext.Groups.Where(x => _groupIds.Contains(x.Id)); + var query = from g in dbContext.Groups + join ou in orgUser + on g.OrganizationId equals ou.OrganizationId + join gie in groupIdEntities + on g.Id equals gie.Id + where !dbContext.GroupUsers.Any(gu => _groupIds.Contains(gu.GroupId) && gu.OrganizationUserId == _organizationUserId) + select g; + return query.Select(x => new GroupUser + { + GroupId = x.Id, + OrganizationUserId = _organizationUserId, + }); + } + } + + public class GroupUserUpdateGroupsDeleteQuery : IQuery<GroupUser> + { + private readonly Guid _organizationUserId; + private readonly IEnumerable<Guid> _groupIds; + + public GroupUserUpdateGroupsDeleteQuery(Guid organizationUserId, IEnumerable<Guid> groupIds) + { + _organizationUserId = organizationUserId; + _groupIds = groupIds; + } + + public IQueryable<GroupUser> Run(DatabaseContext dbContext) + { + var deleteQuery = from gu in dbContext.GroupUsers + where gu.OrganizationUserId == _organizationUserId && + !_groupIds.Any(x => gu.GroupId == x) + select gu; + return deleteQuery; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/IQuery.cs b/src/Core/Repositories/EntityFramework/Queries/IQuery.cs new file mode 100644 index 0000000000..de6c129030 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/IQuery.cs @@ -0,0 +1,9 @@ +using System.Linq; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public interface IQuery<TOut> + { + IQueryable<TOut> Run(DatabaseContext dbContext); + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs new file mode 100644 index 0000000000..dd3e53d01c --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationUserOrganizationDetails> + { + public IQueryable<OrganizationUserOrganizationDetails> Run(DatabaseContext dbContext) + { + var query = from ou in dbContext.OrganizationUsers + join o in dbContext.Organizations on ou.OrganizationId equals o.Id + join su in dbContext.SsoUsers on ou.UserId equals su.UserId into su_g + from su in su_g.DefaultIfEmpty() + where ((su == null || !su.OrganizationId.HasValue) || su.OrganizationId == ou.OrganizationId) + select new { ou, o, su }; + return query.Select(x => new OrganizationUserOrganizationDetails + { + OrganizationId = x.ou.OrganizationId, + UserId = x.ou.UserId, + Name = x.o.Name, + Enabled = x.o.Enabled, + UsePolicies = x.o.UsePolicies, + UseSso = x.o.UseSso, + UseGroups = x.o.UseGroups, + UseDirectory = x.o.UseDirectory, + UseEvents = x.o.UseEvents, + UseTotp = x.o.UseTotp, + Use2fa = x.o.Use2fa, + UseApi = x.o.UseApi, + SelfHost = x.o.SelfHost, + UsersGetPremium = x.o.UsersGetPremium, + Seats = x.o.Seats, + MaxCollections = x.o.MaxCollections, + MaxStorageGb = x.o.MaxStorageGb, + Identifier = x.o.Identifier, + Key = x.ou.Key, + ResetPasswordKey = x.ou.ResetPasswordKey, + Status = x.ou.Status, + Type = x.ou.Type, + SsoExternalId = x.su.ExternalId, + Permissions = x.ou.Permissions, + PublicKey = x.o.PublicKey, + PrivateKey = x.o.PrivateKey, + }); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByFreeOrganizationAdminUserQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByFreeOrganizationAdminUserQuery.cs new file mode 100644 index 0000000000..c2f0c32e1f --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByFreeOrganizationAdminUserQuery.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Enums; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class OrganizationUserReadCountByFreeOrganizationAdminUserQuery : IQuery<OrganizationUser> + { + private readonly Guid _userId; + + public OrganizationUserReadCountByFreeOrganizationAdminUserQuery(Guid userId) + { + _userId = userId; + } + + public IQueryable<OrganizationUser> Run(DatabaseContext dbContext) + { + var query = from ou in dbContext.OrganizationUsers + join o in dbContext.Organizations + on ou.OrganizationId equals o.Id + where ou.UserId == _userId && + (ou.Type == OrganizationUserType.Owner || ou.Type == OrganizationUserType.Admin) && + o.PlanType == PlanType.Free && + ou.Status == OrganizationUserStatusType.Confirmed + select ou; + + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOnlyOwnerQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOnlyOwnerQuery.cs new file mode 100644 index 0000000000..ed36db2c79 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOnlyOwnerQuery.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Enums; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class OrganizationUserReadCountByOnlyOwnerQuery : IQuery<OrganizationUser> + { + private readonly Guid _userId; + + public OrganizationUserReadCountByOnlyOwnerQuery(Guid userId) + { + _userId = userId; + } + + public IQueryable<OrganizationUser> Run(DatabaseContext dbContext) + { + var owners = from ou in dbContext.OrganizationUsers + where ou.Type == OrganizationUserType.Owner && + ou.Status == OrganizationUserStatusType.Confirmed + group ou by ou.OrganizationId into g + select new + { + OrgUser = g.Select(x => new {x.UserId, x.Id}).FirstOrDefault(), ConfirmedOwnerCount = g.Count() + }; + + var query = from owner in owners + join ou in dbContext.OrganizationUsers + on owner.OrgUser.Id equals ou.Id + where owner.OrgUser.UserId == _userId && + owner.ConfirmedOwnerCount == 1 + select ou; + + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOrganizationIdEmailQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOrganizationIdEmailQuery.cs new file mode 100644 index 0000000000..17da7814e9 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOrganizationIdEmailQuery.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class OrganizationUserReadCountByOrganizationIdEmailQuery : IQuery<OrganizationUser> + { + private readonly Guid _organizationId; + private readonly string _email; + private readonly bool _onlyUsers; + + public OrganizationUserReadCountByOrganizationIdEmailQuery(Guid organizationId, string email, bool onlyUsers) + { + _organizationId = organizationId; + _email = email; + _onlyUsers = onlyUsers; + } + + public IQueryable<OrganizationUser> Run(DatabaseContext dbContext) + { + var query = from ou in dbContext.OrganizationUsers + join u in dbContext.Users + on ou.UserId equals u.Id into u_g + from u in u_g.DefaultIfEmpty() + where ou.OrganizationId == _organizationId && + ((!_onlyUsers && (ou.Email == _email || u.Email == _email)) + || (_onlyUsers && u.Email == _email)) + select ou; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOrganizationIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOrganizationIdQuery.cs new file mode 100644 index 0000000000..3a47772376 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserReadCountByOrganizationIdQuery.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class OrganizationUserReadCountByOrganizationIdQuery : IQuery<OrganizationUser> + { + private readonly Guid _organizationId; + + public OrganizationUserReadCountByOrganizationIdQuery(Guid organizationId) + { + _organizationId = organizationId; + } + + public IQueryable<OrganizationUser> Run(DatabaseContext dbContext) + { + var query = from ou in dbContext.OrganizationUsers + where ou.OrganizationId == _organizationId + select ou; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUpdateWithCollectionsQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUpdateWithCollectionsQuery.cs new file mode 100644 index 0000000000..de2e519c54 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUpdateWithCollectionsQuery.cs @@ -0,0 +1,110 @@ +using Bit.Core.Models.Data; +using Bit.Core.Models.EntityFramework; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System; +using Table = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class OrganizationUserUpdateWithCollectionsQuery + { + public OrganizationUserUpdateWithCollectionsInsertQuery Insert { get; set; } + public OrganizationUserUpdateWithCollectionsUpdateQuery Update { get; set; } + public OrganizationUserUpdateWithCollectionsDeleteQuery Delete { get; set; } + + public OrganizationUserUpdateWithCollectionsQuery(Table.OrganizationUser organizationUser, + IEnumerable<SelectionReadOnly> collections) + { + Insert = new OrganizationUserUpdateWithCollectionsInsertQuery(organizationUser, collections); + Update = new OrganizationUserUpdateWithCollectionsUpdateQuery(organizationUser, collections); + Delete = new OrganizationUserUpdateWithCollectionsDeleteQuery(organizationUser, collections); + } + } + + public class OrganizationUserUpdateWithCollectionsInsertQuery : IQuery<CollectionUser> + { + private readonly Table.OrganizationUser _organizationUser; + private readonly IEnumerable<SelectionReadOnly> _collections; + + public OrganizationUserUpdateWithCollectionsInsertQuery(Table.OrganizationUser organizationUser, IEnumerable<SelectionReadOnly> collections) + { + _organizationUser = organizationUser; + _collections = collections; + } + + public IQueryable<CollectionUser> Run(DatabaseContext dbContext) + { + var collectionIds = _collections.Select(c => c.Id).ToArray(); + var t = (from cu in dbContext.CollectionUsers + where collectionIds.Contains(cu.CollectionId) && + cu.OrganizationUserId == _organizationUser.Id + select cu).AsEnumerable(); + var insertQuery = (from c in dbContext.Collections + where collectionIds.Contains(c.Id) && + c.OrganizationId == _organizationUser.OrganizationId && + !t.Any() + select c).AsEnumerable(); + return insertQuery.Select(x => new CollectionUser + { + CollectionId = x.Id, + OrganizationUserId = _organizationUser.Id, + ReadOnly = _collections.FirstOrDefault(c => c.Id == x.Id).ReadOnly, + HidePasswords = _collections.FirstOrDefault(c => c.Id == x.Id).HidePasswords, + }).AsQueryable(); + } + } + + public class OrganizationUserUpdateWithCollectionsUpdateQuery: IQuery<CollectionUser> + { + private readonly Table.OrganizationUser _organizationUser; + private readonly IEnumerable<SelectionReadOnly> _collections; + + public OrganizationUserUpdateWithCollectionsUpdateQuery(Table.OrganizationUser organizationUser, IEnumerable<SelectionReadOnly> collections) + { + _organizationUser = organizationUser; + _collections = collections; + } + + public IQueryable<CollectionUser> Run(DatabaseContext dbContext) + { + var collectionIds = _collections.Select(c => c.Id).ToArray(); + var updateQuery = (from target in dbContext.CollectionUsers + where collectionIds.Contains(target.CollectionId) && + target.OrganizationUserId == _organizationUser.Id + select new { target }).AsEnumerable(); + updateQuery = updateQuery.Where(cu => + cu.target.ReadOnly == _collections.FirstOrDefault(u => u.Id == cu.target.CollectionId).ReadOnly && + cu.target.HidePasswords == _collections.FirstOrDefault(u => u.Id == cu.target.CollectionId).HidePasswords); + return updateQuery.Select(x => new CollectionUser + { + CollectionId = x.target.CollectionId, + OrganizationUserId = _organizationUser.Id, + ReadOnly = x.target.ReadOnly, + HidePasswords = x.target.HidePasswords, + }).AsQueryable(); + } + } + + public class OrganizationUserUpdateWithCollectionsDeleteQuery: IQuery<CollectionUser> + { + private readonly Table.OrganizationUser _organizationUser; + private readonly IEnumerable<SelectionReadOnly> _collections; + + public OrganizationUserUpdateWithCollectionsDeleteQuery(Table.OrganizationUser organizationUser, IEnumerable<SelectionReadOnly> collections) + { + _organizationUser = organizationUser; + _collections = collections; + } + + public IQueryable<CollectionUser> Run(DatabaseContext dbContext) + { + var deleteQuery = from cu in dbContext.CollectionUsers + where !_collections.Any( + c => c.Id == cu.CollectionId) + select cu; + return deleteQuery; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUserViewQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUserViewQuery.cs new file mode 100644 index 0000000000..861a334a88 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUserViewQuery.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class OrganizationUserUserDetailsViewQuery : IQuery<OrganizationUserUserDetails> + { + public IQueryable<OrganizationUserUserDetails> Run(DatabaseContext dbContext) + { + var query = from ou in dbContext.OrganizationUsers + join u in dbContext.Users on ou.UserId equals u.Id into u_g + from u in u_g.DefaultIfEmpty() + join su in dbContext.SsoUsers on u.Id equals su.UserId into su_g + from su in su_g.DefaultIfEmpty() + select new { ou, u, su }; + return query.Select(x => new OrganizationUserUserDetails + { + Id = x.ou.Id, + OrganizationId = x.ou.OrganizationId, + UserId = x.ou.UserId, + Name = x.u.Name, + Email = x.u.Email ?? x.ou.Email, + TwoFactorProviders = x.u.TwoFactorProviders, + Premium = x.u.Premium, + Status = x.ou.Status, + Type = x.ou.Type, + AccessAll = x.ou.AccessAll, + ExternalId = x.ou.ExternalId, + SsoExternalId = x.su.ExternalId, + Permissions = x.ou.Permissions, + ResetPasswordKey = x.ou.ResetPasswordKey, + }); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/PolicyReadByUserIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/PolicyReadByUserIdQuery.cs new file mode 100644 index 0000000000..09532e1015 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/PolicyReadByUserIdQuery.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Enums; +using Bit.Core.Models.EntityFramework; +using System; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class PolicyReadByUserIdQuery : IQuery<Policy> + { + private readonly Guid _userId; + + public PolicyReadByUserIdQuery(Guid userId) + { + _userId = userId; + } + + public IQueryable<Policy> Run(DatabaseContext dbContext) + { + var query = from p in dbContext.Policies + join ou in dbContext.OrganizationUsers + on p.OrganizationId equals ou.OrganizationId + join o in dbContext.Organizations + on ou.OrganizationId equals o.Id + where ou.UserId == _userId && + ou.Status == OrganizationUserStatusType.Confirmed && + o.Enabled == true + select p; + + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/ProviderOrganizationOrganizationDetailsReadByProviderIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/ProviderOrganizationOrganizationDetailsReadByProviderIdQuery.cs new file mode 100644 index 0000000000..3919fae591 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/ProviderOrganizationOrganizationDetailsReadByProviderIdQuery.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class ProviderOrganizationOrganizationDetailsReadByProviderIdQuery : IQuery<ProviderOrganizationOrganizationDetails> + { + private readonly Guid _providerId; + public ProviderOrganizationOrganizationDetailsReadByProviderIdQuery(Guid providerId) + { + _providerId = providerId; + } + + public IQueryable<ProviderOrganizationOrganizationDetails> Run(DatabaseContext dbContext) + { + var query = from po in dbContext.ProviderOrganizations + join o in dbContext.Organizations + on po.OrganizationId equals o.Id + where po.ProviderId == _providerId + select new { po, o }; + return query.Select(x => new ProviderOrganizationOrganizationDetails() + { + Id = x.po.Id, + ProviderId = x.po.ProviderId, + OrganizationId = x.po.OrganizationId, + OrganizationName = x.o.Name, + Key = x.po.Key, + Settings = x.po.Settings, + CreationDate = x.po.CreationDate, + RevisionDate = x.po.RevisionDate, + }); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/ProviderUserProviderDetailsReadByUserIdStatusQuery.cs b/src/Core/Repositories/EntityFramework/Queries/ProviderUserProviderDetailsReadByUserIdStatusQuery.cs new file mode 100644 index 0000000000..5188033191 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/ProviderUserProviderDetailsReadByUserIdStatusQuery.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Bit.Core.Enums.Provider; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class ProviderUserProviderDetailsReadByUserIdStatusQuery : IQuery<ProviderUserProviderDetails> + { + private readonly Guid _userId; + private readonly ProviderUserStatusType? _status; + public ProviderUserProviderDetailsReadByUserIdStatusQuery(Guid userId, ProviderUserStatusType? status) + { + _userId = userId; + _status = status; + } + + public IQueryable<ProviderUserProviderDetails> Run(DatabaseContext dbContext) + { + var query = from pu in dbContext.ProviderUsers + join p in dbContext.Providers + on pu.ProviderId equals p.Id into p_g + from p in p_g.DefaultIfEmpty() + where pu.UserId == _userId && (_status == null || pu.Status == _status) + select new { pu, p }; + return query.Select(x => new ProviderUserProviderDetails() + { + UserId = x.pu.UserId, + ProviderId = x.pu.ProviderId, + Name = x.p.Name, + Key = x.pu.Key, + Status = x.pu.Status, + Type = x.pu.Type, + Enabled = x.p.Enabled, + Permissions = x.pu.Permissions, + }); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/UserBumpAccountRevisionDateByCipherIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/UserBumpAccountRevisionDateByCipherIdQuery.cs new file mode 100644 index 0000000000..38bc0cf507 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/UserBumpAccountRevisionDateByCipherIdQuery.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Enums; +using Bit.Core.Models.EntityFramework; +using Table = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class UserBumpAccountRevisionDateByCipherIdQuery : IQuery<User> + { + private readonly Table.Cipher _cipher; + + public UserBumpAccountRevisionDateByCipherIdQuery(Table.Cipher cipher) + { + _cipher = cipher; + } + + public IQueryable<User> Run(DatabaseContext dbContext) + { + var query = from u in dbContext.Users + join ou in dbContext.OrganizationUsers + on u.Id equals ou.UserId + join collectionCipher in dbContext.CollectionCiphers + on _cipher.Id equals collectionCipher.CipherId into cc_g + from cc in cc_g.DefaultIfEmpty() + join collectionUser in dbContext.CollectionUsers + on cc.CollectionId equals collectionUser.CollectionId into cu_g + from cu in cu_g.DefaultIfEmpty() + where ou.AccessAll && + cu.OrganizationUserId == ou.Id + join groupUser in dbContext.GroupUsers + on ou.Id equals groupUser.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where cu.CollectionId == null && + !ou.AccessAll + join grp in dbContext.Groups + on gu.GroupId equals grp.Id into g_g + from g in g_g.DefaultIfEmpty() + join collectionGroup in dbContext.CollectionGroups + on cc.CollectionId equals collectionGroup.CollectionId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && + cg.GroupId == gu.GroupId + where ou.OrganizationId == _cipher.OrganizationId && + ou.Status == OrganizationUserStatusType.Confirmed && + (cu.CollectionId != null || + cg.CollectionId != null || + ou.AccessAll || + g.AccessAll) + select u; + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/UserBumpAccountRevisionDateByOrganizationIdQuery.cs b/src/Core/Repositories/EntityFramework/Queries/UserBumpAccountRevisionDateByOrganizationIdQuery.cs new file mode 100644 index 0000000000..4145d27195 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/UserBumpAccountRevisionDateByOrganizationIdQuery.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Enums; +using System; +using Bit.Core.Models.EntityFramework; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class UserBumpAccountRevisionDateByOrganizationIdQuery : IQuery<User> + { + private readonly Guid _organizationId; + + public UserBumpAccountRevisionDateByOrganizationIdQuery(Guid organizationId) + { + _organizationId = organizationId; + } + + public IQueryable<User> Run(DatabaseContext dbContext) + { + var query = from u in dbContext.Users + join ou in dbContext.OrganizationUsers + on u.Id equals ou.UserId + where ou.OrganizationId == _organizationId && + ou.Status == OrganizationUserStatusType.Confirmed + select u; + + return query; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/UserCipherDetailsQuery.cs b/src/Core/Repositories/EntityFramework/Queries/UserCipherDetailsQuery.cs new file mode 100644 index 0000000000..8254b091ce --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/UserCipherDetailsQuery.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System; +using Bit.Core.Enums; +using System.Collections.Generic; +using Core.Models.Data; +using Bit.Core.Utilities; +using Newtonsoft.Json.Linq; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class UserCipherDetailsQuery : IQuery<CipherDetails> + { + private readonly Guid? _userId; + public UserCipherDetailsQuery(Guid? userId) + { + _userId = userId; + } + public virtual IQueryable<CipherDetails> Run(DatabaseContext dbContext) + { + var query = from c in dbContext.Ciphers + join ou in dbContext.OrganizationUsers + on c.OrganizationId equals ou.OrganizationId + where ou.UserId == _userId && + ou.Status == OrganizationUserStatusType.Confirmed + join o in dbContext.Organizations + on c.OrganizationId equals o.Id + where o.Id == ou.OrganizationId && o.Enabled + join cc in dbContext.CollectionCiphers + on c.Id equals cc.CipherId into cc_g + from cc in cc_g.DefaultIfEmpty() + where ou.AccessAll + join cu in dbContext.CollectionUsers + on cc.CollectionId equals cu.CollectionId into cu_g + from cu in cu_g.DefaultIfEmpty() + where cu.OrganizationUserId == ou.Id + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where cu.CollectionId == null && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g.DefaultIfEmpty() + join cg in dbContext.CollectionGroups + on cc.CollectionId equals cg.CollectionId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && cg.GroupId == gu.GroupId && + ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null + select new {c, ou, o, cc, cu, gu, g, cg}.c; + + var query2 = from c in dbContext.Ciphers + where c.UserId == _userId + select c; + + var union = query.Union(query2).Select(c => new CipherDetails + { + Id = c.Id, + UserId = c.UserId, + OrganizationId = c.OrganizationId, + Type= c.Type, + Data = c.Data, + Attachments = c.Attachments, + CreationDate = c.CreationDate, + RevisionDate = c.RevisionDate, + DeletedDate = c.DeletedDate, + Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.Contains($"\"{_userId}\":true"), + FolderId = _userId.HasValue && !string.IsNullOrWhiteSpace(c.Folders) ? + Guid.Parse(JObject.Parse(c.Folders)[_userId.Value.ToString()].Value<string>()) : + null, + Edit = true, + ViewPassword = true, + OrganizationUseTotp = false, + }); + return union; + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/UserCollectionDetailsQuery.cs b/src/Core/Repositories/EntityFramework/Queries/UserCollectionDetailsQuery.cs new file mode 100644 index 0000000000..9c56239d22 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/UserCollectionDetailsQuery.cs @@ -0,0 +1,56 @@ +using System.Linq; +using System; +using Bit.Core.Enums; +using System.Collections.Generic; +using Bit.Core.Models.Data; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class UserCollectionDetailsQuery : IQuery<CollectionDetails> + { + private readonly Guid? _userId; + public UserCollectionDetailsQuery(Guid? userId) + { + _userId = userId; + } + public virtual IQueryable<CollectionDetails> Run(DatabaseContext dbContext) + { + var query = from c in dbContext.Collections + join ou in dbContext.OrganizationUsers + on c.OrganizationId equals ou.OrganizationId + join o in dbContext.Organizations + on c.OrganizationId equals o.Id + join cu in dbContext.CollectionUsers + on c.Id equals cu.CollectionId into cu_g + from cu in cu_g.DefaultIfEmpty() + where ou.AccessAll && cu.OrganizationUserId == ou.Id + join gu in dbContext.GroupUsers + on ou.Id equals gu.OrganizationUserId into gu_g + from gu in gu_g.DefaultIfEmpty() + where cu.CollectionId == null && !ou.AccessAll + join g in dbContext.Groups + on gu.GroupId equals g.Id into g_g + from g in g_g.DefaultIfEmpty() + join cg in dbContext.CollectionGroups + on gu.GroupId equals cg.GroupId into cg_g + from cg in cg_g.DefaultIfEmpty() + where !g.AccessAll && cg.CollectionId == c.Id && + ou.UserId == _userId && + ou.Status == OrganizationUserStatusType.Confirmed && + o.Enabled && + (ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null) + select new { c, ou, o, cu, gu, g, cg }; + return query.Select(x => new CollectionDetails + { + Id = x.c.Id, + OrganizationId = x.c.OrganizationId, + Name = x.c.Name, + ExternalId = x.c.ExternalId, + CreationDate = x.c.CreationDate, + RevisionDate = x.c.RevisionDate, + ReadOnly = !x.ou.AccessAll || !x.g.AccessAll || (x.cu.ReadOnly || x.cg.ReadOnly), + HidePasswords = !x.ou.AccessAll || !x.g.AccessAll || (x.cu.HidePasswords || x.cg.HidePasswords), + }); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Queries/UserReadPublicKeysByProviderUserIdsQuery.cs b/src/Core/Repositories/EntityFramework/Queries/UserReadPublicKeysByProviderUserIdsQuery.cs new file mode 100644 index 0000000000..d3b629f6dc --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Queries/UserReadPublicKeysByProviderUserIdsQuery.cs @@ -0,0 +1,38 @@ +using System.Linq; +using System; +using System.Collections.Generic; +using Core.Models.Data; +using Bit.Core.Utilities; +using Bit.Core.Models.Data; +using Bit.Core.Enums.Provider; + +namespace Bit.Core.Repositories.EntityFramework.Queries +{ + public class UserReadPublicKeysByProviderUserIdsQuery : IQuery<ProviderUserPublicKey> + { + private readonly Guid _providerId; + private readonly IEnumerable<Guid> _ids; + + public UserReadPublicKeysByProviderUserIdsQuery(Guid providerId, IEnumerable<Guid> Ids) + { + _providerId = providerId; + _ids = Ids; + } + + public virtual IQueryable<ProviderUserPublicKey> Run(DatabaseContext dbContext) + { + var query = from pu in dbContext.ProviderUsers + join u in dbContext.Users + on pu.UserId equals u.Id + where _ids.Contains(pu.Id) && + pu.Status == ProviderUserStatusType.Accepted && + pu.ProviderId == _providerId + select new { pu, u }; + return query.Select(x => new ProviderUserPublicKey + { + Id = x.pu.Id, + PublicKey = x.u.PublicKey, + }); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Repository.cs b/src/Core/Repositories/EntityFramework/Repository.cs index b8158065b8..4f29ed621d 100644 --- a/src/Core/Repositories/EntityFramework/Repository.cs +++ b/src/Core/Repositories/EntityFramework/Repository.cs @@ -4,6 +4,9 @@ using Bit.Core.Models.Table; using AutoMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Repositories.EntityFramework.Queries; namespace Bit.Core.Repositories.EntityFramework { @@ -26,19 +29,21 @@ namespace Bit.Core.Repositories.EntityFramework { var dbContext = GetDatabaseContext(scope); var entity = await GetDbSet(dbContext).FindAsync(id); - return entity as T; + return Mapper.Map<T>(entity); } } - public virtual async Task CreateAsync(T obj) + public virtual async Task<T> CreateAsync(T obj) { using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); + obj.SetNewId(); var entity = Mapper.Map<TEntity>(obj); - dbContext.Add(entity); + await dbContext.AddAsync(entity); await dbContext.SaveChangesAsync(); obj.Id = entity.Id; + return obj; } } @@ -59,7 +64,7 @@ namespace Bit.Core.Repositories.EntityFramework public virtual async Task UpsertAsync(T obj) { - if (obj.Id.Equals(default(T))) + if (obj.Id.Equals(default(TId))) { await CreateAsync(obj); } @@ -75,9 +80,46 @@ namespace Bit.Core.Repositories.EntityFramework { var dbContext = GetDatabaseContext(scope); var entity = Mapper.Map<TEntity>(obj); - dbContext.Entry(entity).State = EntityState.Deleted; + dbContext.Remove(entity); await dbContext.SaveChangesAsync(); } } + + public virtual async Task RefreshDb() + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var context = GetDatabaseContext(scope); + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + } + } + + public virtual async Task<List<T>> CreateMany(List<T> objs) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var entities = new List<TEntity>(); + foreach (var o in objs) + { + o.SetNewId(); + var entity = Mapper.Map<TEntity>(o); + entities.Add(entity); + } + var dbContext = GetDatabaseContext(scope); + await GetDbSet(dbContext).AddRangeAsync(entities); + await dbContext.SaveChangesAsync(); + return objs; + } + } + + public IQueryable<Tout> Run<Tout>(IQuery<Tout> query) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + return query.Run(dbContext); + } + } } } diff --git a/src/Core/Repositories/EntityFramework/SendRepository.cs b/src/Core/Repositories/EntityFramework/SendRepository.cs new file mode 100644 index 0000000000..151c4e67a1 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/SendRepository.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class SendRepository : Repository<TableModel.Send, EfModel.Send, Guid>, ISendRepository + { + public SendRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Sends) + { } + + public override async Task<Send> CreateAsync(Send send) + { + send = await base.CreateAsync(send); + if (send.UserId.HasValue) + { + await UserUpdateStorage(send.UserId.Value); + await UserBumpAccountRevisionDate(send.UserId.Value); + } + return send; + } + + public async Task<ICollection<Send>> GetManyByDeletionDateAsync(DateTime deletionDateBefore) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Sends.Where(s => s.DeletionDate < deletionDateBefore).ToListAsync(); + return Mapper.Map<List<TableModel.Send>>(results); + } + } + + public async Task<ICollection<Send>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Sends.Where(s => s.UserId == userId).ToListAsync(); + return Mapper.Map<List<TableModel.Send>>(results); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/SsoConfigRepository.cs b/src/Core/Repositories/EntityFramework/SsoConfigRepository.cs new file mode 100644 index 0000000000..e96bb6485f --- /dev/null +++ b/src/Core/Repositories/EntityFramework/SsoConfigRepository.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class SsoConfigRepository : Repository<TableModel.SsoConfig, EfModel.SsoConfig, long>, ISsoConfigRepository + { + public SsoConfigRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.SsoConfigs) + { } + + public async Task<SsoConfig> GetByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var ssoConfig = await GetDbSet(dbContext).SingleOrDefaultAsync(sc => sc.OrganizationId == organizationId); + return Mapper.Map<TableModel.SsoConfig>(ssoConfig); + } + } + + public async Task<SsoConfig> GetByIdentifierAsync(string identifier) + { + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var ssoConfig = await GetDbSet(dbContext).SingleOrDefaultAsync(sc => sc.Organization.Identifier == identifier); + return Mapper.Map<TableModel.SsoConfig>(ssoConfig); + } + } + + public async Task<ICollection<SsoConfig>> GetManyByRevisionNotBeforeDate(DateTime? notBefore) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var ssoConfigs = await GetDbSet(dbContext).Where(sc => sc.Enabled && sc.RevisionDate >= notBefore).ToListAsync(); + return Mapper.Map<List<TableModel.SsoConfig>>(ssoConfigs); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/SsoUserRepository.cs b/src/Core/Repositories/EntityFramework/SsoUserRepository.cs new file mode 100644 index 0000000000..6eacfa0afc --- /dev/null +++ b/src/Core/Repositories/EntityFramework/SsoUserRepository.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class SsoUserRepository : Repository<TableModel.SsoUser, EfModel.SsoUser, long>, ISsoUserRepository + { + public SsoUserRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.SsoUsers) + { } + + public async Task DeleteAsync(Guid userId, Guid? organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = await GetDbSet(dbContext).SingleOrDefaultAsync(su => su.UserId == userId && su.OrganizationId == organizationId); + dbContext.Entry(entity).State = EntityState.Deleted; + await dbContext.SaveChangesAsync(); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/TaxRateRepository.cs b/src/Core/Repositories/EntityFramework/TaxRateRepository.cs new file mode 100644 index 0000000000..f2180002c2 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/TaxRateRepository.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class TaxRateRepository : Repository<TableModel.TaxRate, EfModel.TaxRate, string>, ITaxRateRepository + { + public TaxRateRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.TaxRates) + { } + + public async Task ArchiveAsync(TaxRate model) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.FindAsync<TaxRate>(model); + entity.Active = false; + await dbContext.SaveChangesAsync(); + } + } + + public async Task<ICollection<TaxRate>> GetAllActiveAsync() + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.TaxRates + .Where(t => t.Active) + .ToListAsync(); + return Mapper.Map<List<TableModel.TaxRate>>(results); + } + } + + public async Task<ICollection<TaxRate>> GetByLocationAsync(TaxRate taxRate) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.TaxRates + .Where(t => t.Active && + t.Country == taxRate.Country && + t.PostalCode == taxRate.PostalCode) + .ToListAsync(); + return Mapper.Map<List<TableModel.TaxRate>>(results); + } + } + + public async Task<ICollection<TaxRate>> SearchAsync(int skip, int count) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.TaxRates + .Skip(skip) + .Take(count) + .Where(t => t.Active) + .OrderBy(t => t.Country).ThenByDescending(t => t.PostalCode) + .ToListAsync(); + return Mapper.Map<List<TableModel.TaxRate>>(results); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/TransactionRepository.cs b/src/Core/Repositories/EntityFramework/TransactionRepository.cs new file mode 100644 index 0000000000..07ceebe0f4 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/TransactionRepository.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Enums; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class TransactionRepository : Repository<TableModel.Transaction, EfModel.Transaction, Guid>, ITransactionRepository + { + public TransactionRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Transactions) + { } + + public async Task<Transaction> GetByGatewayIdAsync(GatewayType gatewayType, string gatewayId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Transactions + .FirstOrDefaultAsync(t => (t.GatewayId == gatewayId && t.Gateway == gatewayType)); + return Mapper.Map<TableModel.Transaction>(results); + } + } + + public async Task<ICollection<Transaction>> GetManyByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Transactions + .Where(t => (t.OrganizationId == organizationId && !t.UserId.HasValue)) + .ToListAsync(); + return Mapper.Map<List<TableModel.Transaction>>(results); + } + } + + public async Task<ICollection<Transaction>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Transactions + .Where(t => (t.UserId == userId)) + .ToListAsync(); + return Mapper.Map<List<TableModel.Transaction>>(results); + } + } + } +} diff --git a/src/Core/Repositories/EntityFramework/U2fRepository.cs b/src/Core/Repositories/EntityFramework/U2fRepository.cs new file mode 100644 index 0000000000..1afe000e0a --- /dev/null +++ b/src/Core/Repositories/EntityFramework/U2fRepository.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Models.Table; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using DataModel = Bit.Core.Models.Data; +using EfModel = Bit.Core.Models.EntityFramework; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class U2fRepository : Repository<TableModel.U2f, EfModel.U2f, int>, IU2fRepository + { + public U2fRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.U2fs) + { } + + public async Task<ICollection<U2f>> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.U2fs.Where(u => u.UserId == userId).ToListAsync(); + return (ICollection<U2f>)results; + } + } + + public async Task DeleteManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var u2fs = dbContext.U2fs.Where(u => u.UserId == userId); + dbContext.RemoveRange(u2fs); + await dbContext.SaveChangesAsync(); + } + } + + public override Task ReplaceAsync(U2f obj) + { + throw new NotSupportedException(); + } + + public override Task UpsertAsync(U2f obj) + { + throw new NotSupportedException(); + } + + public override Task DeleteAsync(U2f obj) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/UserRepository.cs b/src/Core/Repositories/EntityFramework/UserRepository.cs index 75e3a22f5b..38c432131d 100644 --- a/src/Core/Repositories/EntityFramework/UserRepository.cs +++ b/src/Core/Repositories/EntityFramework/UserRepository.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Bit.Core.Models.Table; +using System.Text.Json; namespace Bit.Core.Repositories.EntityFramework { @@ -23,7 +24,8 @@ namespace Bit.Core.Repositories.EntityFramework using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - return await GetDbSet(dbContext).FirstOrDefaultAsync(e => e.Email == email); + var entity = await GetDbSet(dbContext).FirstOrDefaultAsync(e => e.Email == email); + return Mapper.Map<TableModel.User>(entity); } } @@ -46,11 +48,23 @@ namespace Bit.Core.Repositories.EntityFramework using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - var users = await GetDbSet(dbContext) - .Where(e => email == null || e.Email.StartsWith(email)) - .OrderBy(e => e.Email) - .Skip(skip).Take(take) - .ToListAsync(); + List<EFModel.User> users; + if (dbContext.Database.IsNpgsql()) + { + users = await GetDbSet(dbContext) + .Where(e => e.Email == null || + EF.Functions.ILike(EF.Functions.Collate(e.Email, "default"), "a%")) + .OrderBy(e => e.Email) + .Skip(skip).Take(take) + .ToListAsync(); + } + else { + users = await GetDbSet(dbContext) + .Where(e => email == null || e.Email.StartsWith(email)) + .OrderBy(e => e.Email) + .Skip(skip).Take(take) + .ToListAsync(); + } return Mapper.Map<List<TableModel.User>>(users); } } @@ -86,25 +100,7 @@ namespace Bit.Core.Repositories.EntityFramework public async Task UpdateStorageAsync(Guid id) { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - var ciphers = await dbContext.Ciphers.Where(e => e.UserId == id).ToListAsync(); - var storage = ciphers.Sum(e => e.AttachmentsJson?.RootElement.EnumerateArray() - .Sum(p => p.GetProperty("Size").GetInt64()) ?? 0); - var user = new EFModel.User - { - Id = id, - RevisionDate = DateTime.UtcNow, - Storage = storage, - }; - var set = GetDbSet(dbContext); - set.Attach(user); - var entry = dbContext.Entry(user); - entry.Property(e => e.RevisionDate).IsModified = true; - entry.Property(e => e.Storage).IsModified = true; - await dbContext.SaveChangesAsync(); - } + await base.UserUpdateStorage(id); } public async Task UpdateRenewalReminderDateAsync(Guid id, DateTime renewalReminderDate) @@ -115,7 +111,7 @@ namespace Bit.Core.Repositories.EntityFramework var user = new EFModel.User { Id = id, - RenewalReminderDate = renewalReminderDate + RenewalReminderDate = renewalReminderDate, }; var set = GetDbSet(dbContext); set.Attach(user); @@ -124,14 +120,32 @@ namespace Bit.Core.Repositories.EntityFramework } } - public Task<User> GetBySsoUserAsync(string externalId, Guid? organizationId) + public async Task<User> GetBySsoUserAsync(string externalId, Guid? organizationId) { - throw new NotImplementedException(); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var ssoUser = await dbContext.SsoUsers.SingleOrDefaultAsync(e => + e.OrganizationId == organizationId && e.ExternalId == externalId); + + if (ssoUser == null) + { + return null; + } + + var entity = await dbContext.Users.SingleOrDefaultAsync(e => e.Id == ssoUser.UserId); + return Mapper.Map<TableModel.User>(entity); + } } - public Task<IEnumerable<User>> GetManyAsync(IEnumerable<Guid> ids) + public async Task<IEnumerable<User>> GetManyAsync(IEnumerable<Guid> ids) { - throw new NotImplementedException(); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var users = dbContext.Users.Where(x => ids.Contains(x.Id)); + return await users.ToListAsync(); + } } } } diff --git a/src/Core/Repositories/IProviderOrganizationProviderUserRepository.cs b/src/Core/Repositories/IProviderOrganizationProviderUserRepository.cs index 64d54d11ea..8a4f05245d 100644 --- a/src/Core/Repositories/IProviderOrganizationProviderUserRepository.cs +++ b/src/Core/Repositories/IProviderOrganizationProviderUserRepository.cs @@ -3,7 +3,7 @@ using Bit.Core.Models.Table.Provider; namespace Bit.Core.Repositories { - public interface IProviderOrganizationProviderUserRepository : IRepository<Provider, Guid> + public interface IProviderOrganizationProviderUserRepository : IRepository<ProviderOrganizationProviderUser, Guid> { } } diff --git a/src/Core/Repositories/IRepository.cs b/src/Core/Repositories/IRepository.cs index bbb4e81af1..9402e22e18 100644 --- a/src/Core/Repositories/IRepository.cs +++ b/src/Core/Repositories/IRepository.cs @@ -7,7 +7,7 @@ namespace Bit.Core.Repositories public interface IRepository<T, TId> where TId : IEquatable<TId> where T : class, ITableObject<TId> { Task<T> GetByIdAsync(TId id); - Task CreateAsync(T obj); + Task<T> CreateAsync(T obj); Task ReplaceAsync(T obj); Task UpsertAsync(T obj); Task DeleteAsync(T obj); diff --git a/src/Core/Repositories/PostgreSql/BasePostgreSqlRepository.cs b/src/Core/Repositories/PostgreSql/BasePostgreSqlRepository.cs deleted file mode 100644 index 956d6fb977..0000000000 --- a/src/Core/Repositories/PostgreSql/BasePostgreSqlRepository.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Text.RegularExpressions; -using Dapper; - -namespace Bit.Core.Repositories.PostgreSql -{ - public abstract class BasePostgreSqlRepository : BaseRepository - { - static BasePostgreSqlRepository() - { - // Support snake case property names - DefaultTypeMap.MatchNamesWithUnderscores = true; - } - - public BasePostgreSqlRepository(string connectionString, string readOnlyConnectionString) - : base(connectionString, readOnlyConnectionString) - { } - - protected static string SnakeCase(string input) - { - if (string.IsNullOrWhiteSpace(input)) - { - return input; - } - var startUnderscores = Regex.Match(input, @"^_+"); - return startUnderscores + Regex.Replace(input, @"([a-z0-9])([A-Z])", "$1_$2").ToLowerInvariant(); - } - - protected static DynamicParameters ToParam<T>(T obj) - { - var dp = new DynamicParameters(); - var properties = typeof(T).GetProperties(); - foreach (var property in properties) - { - dp.Add($"_{SnakeCase(property.Name)}", property.GetValue(obj)); - } - return dp; - } - } -} diff --git a/src/Core/Repositories/PostgreSql/Repository.cs b/src/Core/Repositories/PostgreSql/Repository.cs deleted file mode 100644 index 1c9053c114..0000000000 --- a/src/Core/Repositories/PostgreSql/Repository.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using Dapper; -using Bit.Core.Models.Table; -using Npgsql; - -namespace Bit.Core.Repositories.PostgreSql -{ - public abstract class Repository<T, TId> : BasePostgreSqlRepository, IRepository<T, TId> - where TId : IEquatable<TId> - where T : class, ITableObject<TId> - { - public Repository(string connectionString, string readOnlyConnectionString, string table = null) - : base(connectionString, readOnlyConnectionString) - { - if (!string.IsNullOrWhiteSpace(table)) - { - Table = table; - } - else - { - Table = SnakeCase(typeof(T).Name).ToLowerInvariant(); - } - } - - protected string Table { get; private set; } - - public virtual async Task<T> GetByIdAsync(TId id) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync<T>( - $"{Table}_read_by_id", - ToParam(new { Id = id }), - commandType: CommandType.StoredProcedure); - - return results.SingleOrDefault(); - } - } - - public virtual async Task CreateAsync(T obj) - { - obj.SetNewId(); - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.ExecuteAsync( - $"{Table}_create", - ToParam(obj), - commandType: CommandType.StoredProcedure); - } - } - - public virtual async Task ReplaceAsync(T obj) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.ExecuteAsync( - $"{Table}_update", - ToParam(obj), - commandType: CommandType.StoredProcedure); - } - } - - public virtual async Task UpsertAsync(T obj) - { - if (obj.Id.Equals(default(TId))) - { - await CreateAsync(obj); - } - else - { - await ReplaceAsync(obj); - } - } - - public virtual async Task DeleteAsync(T obj) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - await connection.ExecuteAsync( - $"{Table}_delete_by_id", - ToParam(new { Id = obj.Id }), - commandType: CommandType.StoredProcedure); - } - } - } -} diff --git a/src/Core/Repositories/PostgreSql/UserRepository.cs b/src/Core/Repositories/PostgreSql/UserRepository.cs deleted file mode 100644 index a5ab31c4f0..0000000000 --- a/src/Core/Repositories/PostgreSql/UserRepository.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using Bit.Core.Models.Data; -using Bit.Core.Models.Table; -using Bit.Core.Settings; -using Dapper; -using Npgsql; - -namespace Bit.Core.Repositories.PostgreSql -{ - public class UserRepository : Repository<User, Guid>, IUserRepository - { - public UserRepository(GlobalSettings globalSettings) - : this(globalSettings.PostgreSql.ConnectionString, globalSettings.PostgreSql.ReadOnlyConnectionString) - { } - - public UserRepository(string connectionString, string readOnlyConnectionString) - : base(connectionString, readOnlyConnectionString) - { } - - public override async Task<User> GetByIdAsync(Guid id) - { - return await base.GetByIdAsync(id); - } - - public async Task<User> GetByEmailAsync(string email) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync<User>( - "user_read_by_email", - ToParam(new { Email = email }), - commandType: CommandType.StoredProcedure); - - return results.SingleOrDefault(); - } - } - - public async Task<UserKdfInformation> GetKdfInformationByEmailAsync(string email) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync<UserKdfInformation>( - "user_read_kdf_by_email", - ToParam(new { Email = email }), - commandType: CommandType.StoredProcedure); - - return results.SingleOrDefault(); - } - } - - public async Task<ICollection<User>> SearchAsync(string email, int skip, int take) - { - using (var connection = new NpgsqlConnection(ReadOnlyConnectionString)) - { - var results = await connection.QueryAsync<User>( - "user_search", - ToParam(new { Email = email, Skip = skip, Take = take }), - commandType: CommandType.StoredProcedure, - commandTimeout: 120); - - return results.ToList(); - } - } - - public async Task<ICollection<User>> GetManyByPremiumAsync(bool premium) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync<User>( - "user_read_by_premium", - ToParam(new { Premium = premium }), - commandType: CommandType.StoredProcedure); - - return results.ToList(); - } - } - - public async Task<ICollection<User>> GetManyByPremiumRenewalAsync() - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync<User>( - "user_read_by_premium_renewal", - commandType: CommandType.StoredProcedure); - - return results.ToList(); - } - } - - public async Task<string> GetPublicKeyAsync(Guid id) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync<string>( - "user_read_public_key_by_id", - ToParam(new { Id = id }), - commandType: CommandType.StoredProcedure); - - return results.SingleOrDefault(); - } - } - - public async Task<DateTime> GetAccountRevisionDateAsync(Guid id) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync<DateTime>( - "user_read_account_revision_date_by_id", - ToParam(new { Id = id }), - commandType: CommandType.StoredProcedure); - - return results.SingleOrDefault(); - } - } - - public override async Task ReplaceAsync(User user) - { - await base.ReplaceAsync(user); - } - - public override async Task DeleteAsync(User user) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - await connection.ExecuteAsync( - $"user_delete_by_id", - ToParam(new { Id = user.Id }), - commandType: CommandType.StoredProcedure, - commandTimeout: 180); - } - } - - public async Task UpdateStorageAsync(Guid id) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - await connection.ExecuteAsync( - "user_update_storage", - ToParam(new { Id = id }), - commandType: CommandType.StoredProcedure, - commandTimeout: 180); - } - } - - public async Task UpdateRenewalReminderDateAsync(Guid id, DateTime renewalReminderDate) - { - using (var connection = new NpgsqlConnection(ConnectionString)) - { - await connection.ExecuteAsync( - "user_update_renewal_reminder_date", - ToParam(new { Id = id, RenewalReminderDate = renewalReminderDate }), - commandType: CommandType.StoredProcedure); - } - } - - public Task<User> GetBySsoUserAsync(string externalId, Guid? organizationId) - { - throw new NotImplementedException(); - } - - public Task<IEnumerable<User>> GetManyAsync(IEnumerable<Guid> ids) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Core/Repositories/SqlServer/GroupRepository.cs b/src/Core/Repositories/SqlServer/GroupRepository.cs index 7d128c88a3..a41777057e 100644 --- a/src/Core/Repositories/SqlServer/GroupRepository.cs +++ b/src/Core/Repositories/SqlServer/GroupRepository.cs @@ -141,10 +141,5 @@ namespace Bit.Core.Repositories.SqlServer commandType: CommandType.StoredProcedure); } } - - public class GroupWithCollections : Group - { - public DataTable Collections { get; set; } - } } } diff --git a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs index c204a5c5f1..3fb672708f 100644 --- a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs +++ b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs @@ -271,11 +271,6 @@ namespace Bit.Core.Repositories.SqlServer } } - public class OrganizationUserWithCollections : OrganizationUser - { - public DataTable Collections { get; set; } - } - public async Task<ICollection<OrganizationUser>> GetManyByManyUsersAsync(IEnumerable<Guid> userIds) { using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Core/Repositories/SqlServer/ProviderOrganizationProviderUserRepository.cs b/src/Core/Repositories/SqlServer/ProviderOrganizationProviderUserRepository.cs index 8a71203b93..8cac8b3658 100644 --- a/src/Core/Repositories/SqlServer/ProviderOrganizationProviderUserRepository.cs +++ b/src/Core/Repositories/SqlServer/ProviderOrganizationProviderUserRepository.cs @@ -4,7 +4,7 @@ using Bit.Core.Settings; namespace Bit.Core.Repositories.SqlServer { - public class ProviderOrganizationProviderUserRepository : Repository<Provider, Guid>, IProviderOrganizationProviderUserRepository + public class ProviderOrganizationProviderUserRepository : Repository<ProviderOrganizationProviderUser, Guid>, IProviderOrganizationProviderUserRepository { public ProviderOrganizationProviderUserRepository(GlobalSettings globalSettings) : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) diff --git a/src/Core/Repositories/SqlServer/Repository.cs b/src/Core/Repositories/SqlServer/Repository.cs index 838fd70cb9..2d6714651a 100644 --- a/src/Core/Repositories/SqlServer/Repository.cs +++ b/src/Core/Repositories/SqlServer/Repository.cs @@ -43,16 +43,21 @@ namespace Bit.Core.Repositories.SqlServer } } - public virtual async Task CreateAsync(T obj) + public virtual async Task<T> CreateAsync(T obj) { obj.SetNewId(); using (var connection = new SqlConnection(ConnectionString)) { + var parameters = new DynamicParameters(); + parameters.AddDynamicParams(obj); + parameters.Add("Id", obj.Id, direction: ParameterDirection.InputOutput); var results = await connection.ExecuteAsync( $"[{Schema}].[{Table}_Create]", - obj, + parameters, commandType: CommandType.StoredProcedure); + obj.Id = parameters.Get<TId>(nameof(obj.Id)); } + return obj; } public virtual async Task ReplaceAsync(T obj) diff --git a/src/Core/Repositories/SqlServer/U2fRepository.cs b/src/Core/Repositories/SqlServer/U2fRepository.cs index 591a299137..55335bcc84 100644 --- a/src/Core/Repositories/SqlServer/U2fRepository.cs +++ b/src/Core/Repositories/SqlServer/U2fRepository.cs @@ -44,11 +44,6 @@ namespace Bit.Core.Repositories.SqlServer } } - public override Task<U2f> GetByIdAsync(int id) - { - throw new NotSupportedException(); - } - public override Task ReplaceAsync(U2f obj) { throw new NotSupportedException(); diff --git a/src/Core/Services/Implementations/I18nViewLocalizer.cs b/src/Core/Services/Implementations/I18nViewLocalizer.cs index b094351144..7164275016 100644 --- a/src/Core/Services/Implementations/I18nViewLocalizer.cs +++ b/src/Core/Services/Implementations/I18nViewLocalizer.cs @@ -30,8 +30,5 @@ namespace Bit.Core.Services public LocalizedString GetString(string name) => _stringLocalizer[name]; public LocalizedString GetString(string name, params object[] arguments) => _stringLocalizer[name, arguments]; - - [Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] - public IHtmlLocalizer WithCulture(CultureInfo culture) => _htmlLocalizer.WithCulture(culture); } } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 717cc1fdb3..95ca661ebf 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -43,8 +43,11 @@ namespace Bit.Core.Settings public virtual CaptchaSettings Captcha { get; set; } = new CaptchaSettings(); public virtual InstallationSettings Installation { get; set; } = new InstallationSettings(); public virtual BaseServiceUriSettings BaseServiceUri { get; set; } + public virtual string DatabaseProvider { get; set; } public virtual SqlSettings SqlServer { get; set; } = new SqlSettings(); public virtual SqlSettings PostgreSql { get; set; } = new SqlSettings(); + public virtual SqlSettings MySql { get; set; } = new SqlSettings(); + public virtual SqlSettings Sqlite { get; set; } = new SqlSettings(); public virtual MailSettings Mail { get; set; } = new MailSettings(); public virtual ConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings(); public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings(); diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index c5b9bd717d..1590be802f 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ using Bit.Core.Utilities; using IdentityModel; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Configuration; +using LinqToDB.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -46,56 +47,108 @@ namespace Bit.Core.Utilities { public static void AddSqlServerRepositories(this IServiceCollection services, GlobalSettings globalSettings) { - var usePostgreSql = CoreHelpers.SettingHasValue(globalSettings.PostgreSql?.ConnectionString); - var useEf = usePostgreSql; + var selectedDatabaseProvider = globalSettings.DatabaseProvider; + var provider = SupportedDatabaseProviders.SqlServer; + var connectionString = string.Empty; + if (!string.IsNullOrWhiteSpace(selectedDatabaseProvider)) + { + switch (selectedDatabaseProvider.ToLowerInvariant()) + { + case "postgres": + case "postgresql": + provider = SupportedDatabaseProviders.Postgres; + connectionString = globalSettings.PostgreSql.ConnectionString; + break; + case "mysql": + case "mariadb": + provider = SupportedDatabaseProviders.MySql; + connectionString = globalSettings.MySql.ConnectionString; + break; + default: + break; + } + } + + var useEf = (provider != SupportedDatabaseProviders.SqlServer); if (useEf) { + if (string.IsNullOrWhiteSpace(connectionString)) + { + throw new Exception($"Database provider type {provider} was selected but no connection string was found."); + } + LinqToDBForEFTools.Initialize(); services.AddAutoMapper(typeof(EntityFrameworkRepos.UserRepository)); services.AddDbContext<EntityFrameworkRepos.DatabaseContext>(options => { - if (usePostgreSql) + if (provider == SupportedDatabaseProviders.Postgres) { - options.UseNpgsql(globalSettings.PostgreSql.ConnectionString); + options.UseNpgsql(connectionString); + } + else if (provider == SupportedDatabaseProviders.MySql) + { + options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); } }); + services.AddSingleton<ICipherRepository, EntityFrameworkRepos.CipherRepository>(); + services.AddSingleton<ICollectionCipherRepository, EntityFrameworkRepos.CollectionCipherRepository>(); + services.AddSingleton<ICollectionRepository, EntityFrameworkRepos.CollectionRepository>(); + services.AddSingleton<IDeviceRepository, EntityFrameworkRepos.DeviceRepository>(); + services.AddSingleton<IEmergencyAccessRepository, EntityFrameworkRepos.EmergencyAccessRepository>(); + services.AddSingleton<IFolderRepository, EntityFrameworkRepos.FolderRepository>(); + services.AddSingleton<IGrantRepository, EntityFrameworkRepos.GrantRepository>(); + services.AddSingleton<IGroupRepository, EntityFrameworkRepos.GroupRepository>(); + services.AddSingleton<IInstallationRepository, EntityFrameworkRepos.InstallationRepository>(); + services.AddSingleton<IMaintenanceRepository, EntityFrameworkRepos.MaintenanceRepository>(); + services.AddSingleton<IOrganizationRepository, EntityFrameworkRepos.OrganizationRepository>(); + services.AddSingleton<IOrganizationUserRepository, EntityFrameworkRepos.OrganizationUserRepository>(); + services.AddSingleton<IPolicyRepository, EntityFrameworkRepos.PolicyRepository>(); + services.AddSingleton<ISendRepository, EntityFrameworkRepos.SendRepository>(); + services.AddSingleton<ISsoConfigRepository, EntityFrameworkRepos.SsoConfigRepository>(); + services.AddSingleton<ISsoUserRepository, EntityFrameworkRepos.SsoUserRepository>(); + services.AddSingleton<ITaxRateRepository, EntityFrameworkRepos.TaxRateRepository>(); + services.AddSingleton<ITransactionRepository, EntityFrameworkRepos.TransactionRepository>(); + services.AddSingleton<IU2fRepository, EntityFrameworkRepos.U2fRepository>(); services.AddSingleton<IUserRepository, EntityFrameworkRepos.UserRepository>(); - //services.AddSingleton<ICipherRepository, EntityFrameworkRepos.CipherRepository>(); - //services.AddSingleton<IOrganizationRepository, EntityFrameworkRepos.OrganizationRepository>(); + services.AddSingleton<IProviderRepository, EntityFrameworkRepos.ProviderRepository>(); + services.AddSingleton<IProviderUserRepository, EntityFrameworkRepos.ProviderUserRepository>(); + services.AddSingleton<IProviderOrganizationRepository, EntityFrameworkRepos.ProviderOrganizationRepository>(); + services.AddSingleton<IProviderOrganizationProviderUserRepository, EntityFrameworkRepos.ProviderOrganizationProviderUserRepository>(); } else { - services.AddSingleton<IUserRepository, SqlServerRepos.UserRepository>(); services.AddSingleton<ICipherRepository, SqlServerRepos.CipherRepository>(); - services.AddSingleton<IDeviceRepository, SqlServerRepos.DeviceRepository>(); - services.AddSingleton<IGrantRepository, SqlServerRepos.GrantRepository>(); - services.AddSingleton<IOrganizationRepository, SqlServerRepos.OrganizationRepository>(); - services.AddSingleton<IOrganizationUserRepository, SqlServerRepos.OrganizationUserRepository>(); - services.AddSingleton<ICollectionRepository, SqlServerRepos.CollectionRepository>(); - services.AddSingleton<IFolderRepository, SqlServerRepos.FolderRepository>(); services.AddSingleton<ICollectionCipherRepository, SqlServerRepos.CollectionCipherRepository>(); + services.AddSingleton<ICollectionRepository, SqlServerRepos.CollectionRepository>(); + services.AddSingleton<IDeviceRepository, SqlServerRepos.DeviceRepository>(); + services.AddSingleton<IEmergencyAccessRepository, SqlServerRepos.EmergencyAccessRepository>(); + services.AddSingleton<IFolderRepository, SqlServerRepos.FolderRepository>(); + services.AddSingleton<IGrantRepository, SqlServerRepos.GrantRepository>(); services.AddSingleton<IGroupRepository, SqlServerRepos.GroupRepository>(); - services.AddSingleton<IU2fRepository, SqlServerRepos.U2fRepository>(); services.AddSingleton<IInstallationRepository, SqlServerRepos.InstallationRepository>(); services.AddSingleton<IMaintenanceRepository, SqlServerRepos.MaintenanceRepository>(); - services.AddSingleton<ITransactionRepository, SqlServerRepos.TransactionRepository>(); + services.AddSingleton<IOrganizationRepository, SqlServerRepos.OrganizationRepository>(); + services.AddSingleton<IOrganizationUserRepository, SqlServerRepos.OrganizationUserRepository>(); services.AddSingleton<IPolicyRepository, SqlServerRepos.PolicyRepository>(); + services.AddSingleton<ISendRepository, SqlServerRepos.SendRepository>(); services.AddSingleton<ISsoConfigRepository, SqlServerRepos.SsoConfigRepository>(); services.AddSingleton<ISsoUserRepository, SqlServerRepos.SsoUserRepository>(); - services.AddSingleton<ISendRepository, SqlServerRepos.SendRepository>(); services.AddSingleton<ITaxRateRepository, SqlServerRepos.TaxRateRepository>(); services.AddSingleton<IEmergencyAccessRepository, SqlServerRepos.EmergencyAccessRepository>(); services.AddSingleton<IProviderRepository, SqlServerRepos.ProviderRepository>(); services.AddSingleton<IProviderUserRepository, SqlServerRepos.ProviderUserRepository>(); services.AddSingleton<IProviderOrganizationRepository, SqlServerRepos.ProviderOrganizationRepository>(); services.AddSingleton<IProviderOrganizationProviderUserRepository, SqlServerRepos.ProviderOrganizationProviderUserRepository>(); + services.AddSingleton<ITransactionRepository, SqlServerRepos.TransactionRepository>(); + services.AddSingleton<IU2fRepository, SqlServerRepos.U2fRepository>(); + services.AddSingleton<IUserRepository, SqlServerRepos.UserRepository>(); } if (globalSettings.SelfHosted) { if (useEf) { - // TODO + services.AddSingleton<IEventRepository, EntityFrameworkRepos.EventRepository>(); } else { diff --git a/src/Events/Dockerfile b/src/Events/Dockerfile index e361700faa..8668004ffb 100644 --- a/src/Events/Dockerfile +++ b/src/Events/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/src/Icons/Dockerfile b/src/Icons/Dockerfile index 97d1eb764d..104b9e9880 100644 --- a/src/Icons/Dockerfile +++ b/src/Icons/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/src/Identity/Dockerfile b/src/Identity/Dockerfile index 9ef1c0cc5d..2985d60f4d 100644 --- a/src/Identity/Dockerfile +++ b/src/Identity/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/src/Notifications/Dockerfile b/src/Notifications/Dockerfile index e361700faa..8668004ffb 100644 --- a/src/Notifications/Dockerfile +++ b/src/Notifications/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/src/Notifications/Notifications.csproj b/src/Notifications/Notifications.csproj index 831f03e899..959401ea5f 100644 --- a/src/Notifications/Notifications.csproj +++ b/src/Notifications/Notifications.csproj @@ -5,8 +5,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="3.1.6" /> - <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="3.1.6" /> + <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.2" /> + <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="5.0.2" /> </ItemGroup> <ItemGroup> diff --git a/src/Notifications/Startup.cs b/src/Notifications/Startup.cs index 4ce00909de..e6d82617ea 100644 --- a/src/Notifications/Startup.cs +++ b/src/Notifications/Startup.cs @@ -52,10 +52,8 @@ namespace Bit.Notifications // SignalR var signalRServerBuilder = services.AddSignalR().AddMessagePackProtocol(options => { - options.FormatterResolvers = new List<MessagePack.IFormatterResolver>() - { - MessagePack.Resolvers.ContractlessStandardResolver.Instance - }; + options.SerializerOptions = MessagePack.MessagePackSerializerOptions.Standard + .WithResolver(MessagePack.Resolvers.ContractlessStandardResolver.Instance); }); if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.RedisConnectionString)) { diff --git a/src/Sql/dbo/Stored Procedures/Cipher_Create.sql b/src/Sql/dbo/Stored Procedures/Cipher_Create.sql index ae8910842c..8bf7ce3a77 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Cipher_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @UserId UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Type TINYINT, @@ -54,4 +54,4 @@ BEGIN BEGIN EXEC [dbo].[User_BumpAccountRevisionDate] @UserId END -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Cipher_DeleteAttachment.sql b/src/Sql/dbo/Stored Procedures/Cipher_DeleteAttachment.sql index 380045e87a..5983f557c2 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_DeleteAttachment.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_DeleteAttachment.sql @@ -35,4 +35,4 @@ BEGIN EXEC [dbo].[User_UpdateStorage] @UserId EXEC [dbo].[User_BumpAccountRevisionDate] @UserId END -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_Create.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_Create.sql index 52ae9b0f2a..54b7695266 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_Create.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_Create.sql @@ -21,4 +21,4 @@ BEGIN BEGIN EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @CollectionId, @OrganizationId END -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Collection_Create.sql b/src/Sql/dbo/Stored Procedures/Collection_Create.sql index c9e2f11853..2e442c6a28 100644 --- a/src/Sql/dbo/Stored Procedures/Collection_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Collection_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Collection_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @OrganizationId UNIQUEIDENTIFIER, @Name VARCHAR(MAX), @ExternalId NVARCHAR(300), @@ -29,4 +29,4 @@ BEGIN ) EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @Id, @OrganizationId -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Device_Create.sql b/src/Sql/dbo/Stored Procedures/Device_Create.sql index cad719d7af..e40460ad7d 100644 --- a/src/Sql/dbo/Stored Procedures/Device_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Device_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Device_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @UserId UNIQUEIDENTIFIER, @Name NVARCHAR(50), @Type TINYINT, @@ -33,4 +33,4 @@ BEGIN @CreationDate, @RevisionDate ) -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/EmergencyAccess_Create.sql b/src/Sql/dbo/Stored Procedures/EmergencyAccess_Create.sql index b949b7bf7f..211fb7ca1a 100644 --- a/src/Sql/dbo/Stored Procedures/EmergencyAccess_Create.sql +++ b/src/Sql/dbo/Stored Procedures/EmergencyAccess_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[EmergencyAccess_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @GrantorId UNIQUEIDENTIFIER, @GranteeId UNIQUEIDENTIFIER, @Email NVARCHAR(256), @@ -45,4 +45,4 @@ BEGIN @CreationDate, @RevisionDate ) -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/EmergencyAccess_ReadToNotify.sql b/src/Sql/dbo/Stored Procedures/EmergencyAccess_ReadToNotify.sql index 320773c95e..e81b0ee5f7 100644 --- a/src/Sql/dbo/Stored Procedures/EmergencyAccess_ReadToNotify.sql +++ b/src/Sql/dbo/Stored Procedures/EmergencyAccess_ReadToNotify.sql @@ -20,4 +20,4 @@ BEGIN DATEADD(DAY, EA.[WaitTimeDays] - 1, EA.[RecoveryInitiatedDate]) <= GETUTCDATE() AND DATEADD(DAY, 1, EA.[LastNotificationDate]) <= GETUTCDATE() -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Event_ReadyById.sql b/src/Sql/dbo/Stored Procedures/Event_ReadyById.sql new file mode 100644 index 0000000000..71e3b28660 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Event_ReadyById.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[Event_ReadById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[Event] + WHERE + [Id] = @Id +END diff --git a/src/Sql/dbo/Stored Procedures/Folder_Create.sql b/src/Sql/dbo/Stored Procedures/Folder_Create.sql index 7b38c7283c..41bd2a4621 100644 --- a/src/Sql/dbo/Stored Procedures/Folder_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Folder_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Folder_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @UserId UNIQUEIDENTIFIER, @Name VARCHAR(MAX), @CreationDate DATETIME2(7), @@ -26,4 +26,4 @@ BEGIN ) EXEC [dbo].[User_BumpAccountRevisionDate] @UserId -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Group_Create.sql b/src/Sql/dbo/Stored Procedures/Group_Create.sql index f47df20897..893cd57e38 100644 --- a/src/Sql/dbo/Stored Procedures/Group_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Group_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Group_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @OrganizationId UNIQUEIDENTIFIER, @Name NVARCHAR(100), @AccessAll BIT, @@ -30,4 +30,4 @@ BEGIN @CreationDate, @RevisionDate ) -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Installation_Create.sql b/src/Sql/dbo/Stored Procedures/Installation_Create.sql index 121ed30b2c..8c91a5b81a 100644 --- a/src/Sql/dbo/Stored Procedures/Installation_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Installation_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Installation_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @Email NVARCHAR(256), @Key VARCHAR(150), @Enabled BIT, @@ -24,4 +24,4 @@ BEGIN @Enabled, @CreationDate ) -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql index dd7c738e12..9dacb45f91 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[OrganizationUser_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @OrganizationId UNIQUEIDENTIFIER, @UserId UNIQUEIDENTIFIER, @Email NVARCHAR(256), diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index cea32a9cef..7082edd4ed 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Organization_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @Identifier NVARCHAR(50), @Name NVARCHAR(50), @BusinessName NVARCHAR(50), diff --git a/src/Sql/dbo/Stored Procedures/Policy_Create.sql b/src/Sql/dbo/Stored Procedures/Policy_Create.sql index c2aea915f4..44934b0500 100644 --- a/src/Sql/dbo/Stored Procedures/Policy_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Policy_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Policy_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @OrganizationId UNIQUEIDENTIFIER, @Type TINYINT, @Data NVARCHAR(MAX), @@ -32,4 +32,4 @@ BEGIN ) EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Send_Create.sql b/src/Sql/dbo/Stored Procedures/Send_Create.sql index 037b069f17..0cfe60b44f 100644 --- a/src/Sql/dbo/Stored Procedures/Send_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Send_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Send_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @UserId UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Type TINYINT, diff --git a/src/Sql/dbo/Stored Procedures/SsoConfig_DeleteById.sql b/src/Sql/dbo/Stored Procedures/SsoConfig_DeleteById.sql new file mode 100644 index 0000000000..e6343dfe08 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/SsoConfig_DeleteById.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[SsoConfig_DeleteById] + @Id BIGINT +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[SsoConfig] + WHERE + [Id] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/SsoConfig_ReadById.sql b/src/Sql/dbo/Stored Procedures/SsoConfig_ReadById.sql new file mode 100644 index 0000000000..fbca19bf15 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/SsoConfig_ReadById.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[SsoUser_ReadById] + @Id BIGINT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[SsoUserView] + WHERE + [Id] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/SsoUser_DeleteById.sql b/src/Sql/dbo/Stored Procedures/SsoUser_DeleteById.sql new file mode 100644 index 0000000000..046f717e12 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/SsoUser_DeleteById.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[SsoUser_DeleteById] + @Id BIGINT +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[SsoUser] + WHERE + [Id] = @Id +END diff --git a/src/Sql/dbo/Stored Procedures/TaxRate_Create.sql b/src/Sql/dbo/Stored Procedures/TaxRate_Create.sql index 5dc6a186b0..be88fe60b3 100644 --- a/src/Sql/dbo/Stored Procedures/TaxRate_Create.sql +++ b/src/Sql/dbo/Stored Procedures/TaxRate_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[TaxRate_Create] - @Id VARCHAR(40), + @Id VARCHAR(40) OUTPUT, @Country VARCHAR(50), @State VARCHAR(2), @PostalCode VARCHAR(10), diff --git a/src/Sql/dbo/Stored Procedures/Transaction_Create.sql b/src/Sql/dbo/Stored Procedures/Transaction_Create.sql index 0f9efba271..a46d9195da 100644 --- a/src/Sql/dbo/Stored Procedures/Transaction_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Transaction_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Transaction_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @UserId UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Type TINYINT, @@ -45,4 +45,4 @@ BEGIN @GatewayId, @CreationDate ) -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/U2f_Create.sql b/src/Sql/dbo/Stored Procedures/U2f_Create.sql index c7b85b3569..b495f2652e 100644 --- a/src/Sql/dbo/Stored Procedures/U2f_Create.sql +++ b/src/Sql/dbo/Stored Procedures/U2f_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[U2f_Create] - @Id INT, + @Id INT OUTPUT, @UserId UNIQUEIDENTIFIER, @KeyHandle VARCHAR(200), @Challenge VARCHAR(200), @@ -28,4 +28,6 @@ BEGIN @Version, @CreationDate ) -END \ No newline at end of file + + SET @Id = (SELECT SCOPE_IDENTITY()) +END diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index cdb8bda180..a28e146170 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[User_Create] - @Id UNIQUEIDENTIFIER, + @Id UNIQUEIDENTIFIER OUTPUT, @Name NVARCHAR(50), @Email NVARCHAR(256), @EmailVerified BIT, diff --git a/src/Sql/dbo/Views/SsoUserView.sql b/src/Sql/dbo/Views/SsoUserView.sql new file mode 100644 index 0000000000..bd29b1b55c --- /dev/null +++ b/src/Sql/dbo/Views/SsoUserView.sql @@ -0,0 +1,6 @@ +CREATE VIEW [dbo].[SsoUserView] +AS +SELECT + * +FROM + [dbo].[SsoUser] diff --git a/test/Core.Test/AutoFixture/Attributes/CiSkippedTheory.cs b/test/Core.Test/AutoFixture/Attributes/CiSkippedTheory.cs new file mode 100644 index 0000000000..446ee3197b --- /dev/null +++ b/test/Core.Test/AutoFixture/Attributes/CiSkippedTheory.cs @@ -0,0 +1,16 @@ +using System; + +namespace Bit.Core.Test.AutoFixture.Attributes +{ + public sealed class CiSkippedTheory : Xunit.TheoryAttribute + { + private static bool IsGithubActions() => Environment.GetEnvironmentVariable("CI") != null; + public CiSkippedTheory() + { + if(IsGithubActions()) + { + Skip = "Ignore during CI builds"; + } + } + } +} diff --git a/test/Core.Test/AutoFixture/CipherFixtures.cs b/test/Core.Test/AutoFixture/CipherFixtures.cs index 410f6e11fb..30d21f132a 100644 --- a/test/Core.Test/AutoFixture/CipherFixtures.cs +++ b/test/Core.Test/AutoFixture/CipherFixtures.cs @@ -1,7 +1,20 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using AutoFixture; +using AutoFixture.Kernel; +using Bit.Core.Models.Data; using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework; using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Test.AutoFixture.OrganizationUserFixtures; +using Bit.Core.Test.AutoFixture.Relays; +using Bit.Core.Test.AutoFixture.TransactionFixtures; +using Bit.Core.Test.AutoFixture.UserFixtures; using Core.Models.Data; namespace Bit.Core.Test.AutoFixture.CipherFixtures @@ -34,6 +47,90 @@ namespace Bit.Core.Test.AutoFixture.CipherFixtures } } + internal class CipherBuilder: ISpecimenBuilder + { + public bool OrganizationOwned { get; set; } + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || (type != typeof(Cipher) && type != typeof(List<Cipher>))) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + + if (!OrganizationOwned) + { + fixture.Customize<Cipher>(composer => composer + .Without(c => c.OrganizationId)); + } + + // Can't test valid Favorites and Folders without creating those values inide each test, + // since we won't have any UserIds until the test is running & creating data + fixture.Customize<Cipher>(c => c + .Without(e => e.Favorites) + .Without(e => e.Folders)); + // + var serializerOptions = new JsonSerializerOptions(){ + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + if(type == typeof(Cipher)) + { + var obj = fixture.WithAutoNSubstitutions().Create<Cipher>(); + var cipherData = fixture.WithAutoNSubstitutions().Create<CipherLoginData>(); + var cipherAttachements = fixture.WithAutoNSubstitutions().Create<List<CipherAttachment>>(); + obj.Data = JsonSerializer.Serialize(cipherData, serializerOptions); + obj.Attachments = JsonSerializer.Serialize(cipherAttachements, serializerOptions); + + return obj; + } + if (type == typeof(List<Cipher>)) + { + var ciphers = fixture.WithAutoNSubstitutions().CreateMany<Cipher>().ToArray(); + for (var i = 0; i < ciphers.Count(); i++ ) + { + var cipherData = fixture.WithAutoNSubstitutions().Create<CipherLoginData>(); + var cipherAttachements = fixture.WithAutoNSubstitutions().Create<List<CipherAttachment>>(); + ciphers[i].Data = JsonSerializer.Serialize(cipherData, serializerOptions); + ciphers[i].Attachments = JsonSerializer.Serialize(cipherAttachements, serializerOptions); + } + + return ciphers; + } + + return new NoSpecimen(); + } + } + + internal class EfCipher: ICustomization + { + public bool OrganizationOwned { get; set; } + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new CipherBuilder(){ + OrganizationOwned = OrganizationOwned + }); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new OrganizationUserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<CipherRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationUserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<CollectionRepository>()); + } + } + internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute { public UserCipherAutoDataAttribute(string userId = null) : base(new SutProviderCustomization(), @@ -67,4 +164,25 @@ namespace Bit.Core.Test.AutoFixture.CipherFixtures typeof(OrganizationCipher) }, values) { } } + + internal class EfUserCipherAutoDataAttribute : CustomAutoDataAttribute + { + public EfUserCipherAutoDataAttribute() : base(new SutProviderCustomization(), new EfCipher()) + { } + } + + internal class EfOrganizationCipherAutoDataAttribute : CustomAutoDataAttribute + { + public EfOrganizationCipherAutoDataAttribute() : base(new SutProviderCustomization(), new EfCipher(){ + OrganizationOwned = true, + }) + { } + } + + internal class InlineEfCipherAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfCipher) }, values) + { } + } } diff --git a/test/Core.Test/AutoFixture/CollectionCipherFixtures.cs b/test/Core.Test/AutoFixture/CollectionCipherFixtures.cs new file mode 100644 index 0000000000..54bc7524cf --- /dev/null +++ b/test/Core.Test/AutoFixture/CollectionCipherFixtures.cs @@ -0,0 +1,74 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.TransactionFixtures; +using Bit.Core.Test.AutoFixture.Relays; +using Bit.Core.Test.AutoFixture.CollectionFixtures; +using Bit.Core.Test.AutoFixture.CipherFixtures; +using Bit.Core.Test.AutoFixture.UserFixtures; + +namespace Bit.Core.Test.AutoFixture.CollectionCipherFixtures +{ + internal class CollectionCipherBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.CollectionCipher)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.CollectionCipher>(); + return obj; + } + } + + internal class EfCollectionCipher: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new CollectionCipherBuilder()); + fixture.Customizations.Add(new CollectionBuilder()); + fixture.Customizations.Add(new CipherBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<CollectionCipherRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<CollectionRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<CipherRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + } + } + + internal class EfCollectionCipherAutoDataAttribute : CustomAutoDataAttribute + { + public EfCollectionCipherAutoDataAttribute() : base(new SutProviderCustomization(), new EfCollectionCipher()) + { } + } + + internal class InlineEfCollectionCipherAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfCollectionCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfCollectionCipher) }, values) + { } + } +} diff --git a/test/Core.Test/AutoFixture/CollectionFixtures.cs b/test/Core.Test/AutoFixture/CollectionFixtures.cs index 22acefef4d..adad029d86 100644 --- a/test/Core.Test/AutoFixture/CollectionFixtures.cs +++ b/test/Core.Test/AutoFixture/CollectionFixtures.cs @@ -1,12 +1,73 @@ -using Bit.Core.Test.AutoFixture.Attributes; -using Bit.Core.Test.AutoFixture.OrganizationFixtures; - -namespace Bit.Core.Test.AutoFixture -{ - - internal class CollectionAutoDataAttribute : CustomAutoDataAttribute - { - public CollectionAutoDataAttribute() : base(new SutProviderCustomization(), new Organization()) - { } - } -} +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.TransactionFixtures; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.AutoFixture.CollectionFixtures +{ + internal class CollectionBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Collection)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Collection>(); + return obj; + } + } + + internal class EfCollection: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new CollectionBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<CollectionRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfCollectionAutoDataAttribute : CustomAutoDataAttribute + { + public EfCollectionAutoDataAttribute() : base(new SutProviderCustomization(), new EfCollection()) + { } + } + + internal class InlineEfCollectionAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfCollectionAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfCollection) }, values) + { } + } + + internal class CollectionAutoDataAttribute : CustomAutoDataAttribute + { + public CollectionAutoDataAttribute() : base(new SutProviderCustomization(), new Bit.Core.Test.AutoFixture.OrganizationFixtures.Organization()) + { } + } +} diff --git a/test/Core.Test/AutoFixture/DeviceFixtures.cs b/test/Core.Test/AutoFixture/DeviceFixtures.cs new file mode 100644 index 0000000000..17472c5fb7 --- /dev/null +++ b/test/Core.Test/AutoFixture/DeviceFixtures.cs @@ -0,0 +1,69 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.UserFixtures; +using Bit.Core.Test.AutoFixture.TransactionFixtures; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.AutoFixture.DeviceFixtures +{ + internal class DeviceBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Device)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Device>(); + return obj; + } + } + + internal class EfDevice: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new DeviceBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<DeviceRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + } + } + + internal class EfDeviceAutoDataAttribute : CustomAutoDataAttribute + { + public EfDeviceAutoDataAttribute() : base(new SutProviderCustomization(), new EfDevice()) + { } + } + + internal class InlineEfDeviceAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfDeviceAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfDevice) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/EmergencyAccessFixtures.cs b/test/Core.Test/AutoFixture/EmergencyAccessFixtures.cs new file mode 100644 index 0000000000..cdf240803c --- /dev/null +++ b/test/Core.Test/AutoFixture/EmergencyAccessFixtures.cs @@ -0,0 +1,71 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.UserFixtures; +using Bit.Core.Test.AutoFixture.TransactionFixtures; +using AutoFixture.DataAnnotations; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.AutoFixture.EmergencyAccessFixtures +{ + internal class EmergencyAccessBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.EmergencyAccess)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.Create<TableModel.EmergencyAccess>(); + return obj; + } + } + + internal class EfEmergencyAccess: ICustomization + { + public void Customize(IFixture fixture) + { + // TODO: Make a base EF Customization with IgnoreVirtualMembers/GlobalSettings/All repos and inherit + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new EmergencyAccessBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<EmergencyAccessRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + } + } + + internal class EfEmergencyAccessAutoDataAttribute : CustomAutoDataAttribute + { + public EfEmergencyAccessAutoDataAttribute() : base(new SutProviderCustomization(), new EfEmergencyAccess()) + { } + } + + internal class InlineEfEmergencyAccessAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfEmergencyAccessAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfEmergencyAccess) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs b/test/Core.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs new file mode 100644 index 0000000000..fdc5e79fe5 --- /dev/null +++ b/test/Core.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs @@ -0,0 +1,119 @@ +using AutoFixture; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using System.Collections.Generic; +using AutoFixture.Kernel; +using System; +using Moq; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.Helpers.Factories; +using Microsoft.EntityFrameworkCore; +using Bit.Core.Settings; + +namespace Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures +{ + internal class ServiceScopeFactoryBuilder: ISpecimenBuilder + { + private DbContextOptions<DatabaseContext> _options { get; set; } + public ServiceScopeFactoryBuilder(DbContextOptions<DatabaseContext> options) { + _options = options; + } + + public object Create(object request, ISpecimenContext context) + { + var fixture = new Fixture(); + var serviceProvider = new Mock<IServiceProvider>(); + var dbContext = new DatabaseContext(_options); + serviceProvider + .Setup(x => x.GetService(typeof(DatabaseContext))) + .Returns(dbContext); + + var serviceScope = new Mock<IServiceScope>(); + serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object); + + var serviceScopeFactory = new Mock<IServiceScopeFactory>(); + serviceScopeFactory + .Setup(x => x.CreateScope()) + .Returns(serviceScope.Object); + return serviceScopeFactory.Object; + } + } + + public class EfRepositoryListBuilder<T>: ISpecimenBuilder where T: BaseEntityFrameworkRepository + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var t = request as ParameterInfo; + if (t == null || t.ParameterType != typeof(List<T>)) + { + return new NoSpecimen(); + } + + var list = new List<T>(); + foreach (var option in DatabaseOptionsFactory.Options) + { + var fixture = new Fixture(); + fixture.Customize<IServiceScopeFactory>(x => x.FromFactory(new ServiceScopeFactoryBuilder(option))); + fixture.Customize<IMapper>(x => x.FromFactory(() => + new MapperConfiguration(cfg => { + cfg.AddProfile<CipherMapperProfile>(); + cfg.AddProfile<CollectionCipherMapperProfile>(); + cfg.AddProfile<CollectionMapperProfile>(); + cfg.AddProfile<DeviceMapperProfile>(); + cfg.AddProfile<EmergencyAccessMapperProfile>(); + cfg.AddProfile<EventMapperProfile>(); + cfg.AddProfile<FolderMapperProfile>(); + cfg.AddProfile<GrantMapperProfile>(); + cfg.AddProfile<GroupMapperProfile>(); + cfg.AddProfile<GroupUserMapperProfile>(); + cfg.AddProfile<InstallationMapperProfile>(); + cfg.AddProfile<OrganizationMapperProfile>(); + cfg.AddProfile<OrganizationUserMapperProfile>(); + cfg.AddProfile<PolicyMapperProfile>(); + cfg.AddProfile<SendMapperProfile>(); + cfg.AddProfile<SsoConfigMapperProfile>(); + cfg.AddProfile<SsoUserMapperProfile>(); + cfg.AddProfile<TaxRateMapperProfile>(); + cfg.AddProfile<TransactionMapperProfile>(); + cfg.AddProfile<U2fMapperProfile>(); + cfg.AddProfile<UserMapperProfile>(); + }) + .CreateMapper())); + + var repo = fixture.Create<T>(); + list.Add(repo); + } + return list; + } + } + + public class IgnoreVirtualMembersCustomization : ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + var pi = request as PropertyInfo; + if (pi == null) + { + return new NoSpecimen(); + } + + if (pi.GetGetMethod().IsVirtual && pi.DeclaringType != typeof(GlobalSettings)) + { + return null; + } + return new NoSpecimen(); + } + } +} diff --git a/test/Core.Test/AutoFixture/EventFixtures.cs b/test/Core.Test/AutoFixture/EventFixtures.cs new file mode 100644 index 0000000000..b903b2639f --- /dev/null +++ b/test/Core.Test/AutoFixture/EventFixtures.cs @@ -0,0 +1,65 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.AutoFixture.EventFixtures +{ + internal class EventBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Event)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Event>(); + return obj; + } + } + + internal class EfEvent: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new EventBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<EventRepository>()); + } + } + + internal class EfEventAutoDataAttribute : CustomAutoDataAttribute + { + public EfEventAutoDataAttribute() : base(new SutProviderCustomization(), new EfEvent()) + { } + } + + internal class InlineEfEventAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfEventAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfEvent) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/FolderFixtures.cs b/test/Core.Test/AutoFixture/FolderFixtures.cs new file mode 100644 index 0000000000..9449ae71c3 --- /dev/null +++ b/test/Core.Test/AutoFixture/FolderFixtures.cs @@ -0,0 +1,68 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.Relays; +using Bit.Core.Test.AutoFixture.UserFixtures; + +namespace Bit.Core.Test.AutoFixture.FolderFixtures +{ + internal class FolderBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Folder)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Folder>(); + return obj; + } + } + + internal class EfFolder: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new FolderBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<FolderRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + } + } + + internal class EfFolderAutoDataAttribute : CustomAutoDataAttribute + { + public EfFolderAutoDataAttribute() : base(new SutProviderCustomization(), new EfFolder()) + { } + } + + internal class InlineEfFolderAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfFolderAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfFolder) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/GlobalSettingsFixtures.cs b/test/Core.Test/AutoFixture/GlobalSettingsFixtures.cs index 55f482126a..9eea0b063c 100644 --- a/test/Core.Test/AutoFixture/GlobalSettingsFixtures.cs +++ b/test/Core.Test/AutoFixture/GlobalSettingsFixtures.cs @@ -1,7 +1,37 @@ -using AutoFixture; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using AutoFixture; +using AutoFixture.Kernel; +using AutoMapper; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.Models.Table; +using Bit.Core.Settings; +using Bit.Core.Test.Helpers.Factories; -namespace Bit.Core.Test.AutoFixture +namespace Bit.Core.Test.AutoFixture.GlobalSettingsFixtures { + internal class GlobalSettingsBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var pi = request as ParameterInfo; + var fixture = new Fixture(); + + if (pi == null || pi.ParameterType != typeof(Settings.GlobalSettings)) + return new NoSpecimen(); + + return GlobalSettingsFactory.GlobalSettings; + } + } + internal class GlobalSettings : ICustomization { public void Customize(IFixture fixture) diff --git a/test/Core.Test/AutoFixture/GrantFixtures.cs b/test/Core.Test/AutoFixture/GrantFixtures.cs new file mode 100644 index 0000000000..a0142a29ad --- /dev/null +++ b/test/Core.Test/AutoFixture/GrantFixtures.cs @@ -0,0 +1,64 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.AutoFixture.GrantFixtures +{ + internal class GrantBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Grant)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Grant>(); + return obj; + } + } + + internal class EfGrant: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new GrantBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<GrantRepository>()); + } + } + + internal class EfGrantAutoDataAttribute : CustomAutoDataAttribute + { + public EfGrantAutoDataAttribute() : base(new SutProviderCustomization(), new EfGrant()) + { } + } + + internal class InlineEfGrantAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfGrantAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfGrant) }, values) + { } + } +} diff --git a/test/Core.Test/AutoFixture/GroupFixtures.cs b/test/Core.Test/AutoFixture/GroupFixtures.cs index cb780761f0..77190ce7b4 100644 --- a/test/Core.Test/AutoFixture/GroupFixtures.cs +++ b/test/Core.Test/AutoFixture/GroupFixtures.cs @@ -1,19 +1,76 @@ -using Bit.Core.Test.AutoFixture.Attributes; +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoFixture.Kernel; +using System; using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.Relays; +using Fixtures = Bit.Core.Test.AutoFixture.OrganizationFixtures; -namespace Bit.Core.Test.AutoFixture +namespace Bit.Core.Test.AutoFixture.GroupFixtures { internal class GroupOrganizationAutoDataAttribute : CustomAutoDataAttribute { public GroupOrganizationAutoDataAttribute() : base( - new SutProviderCustomization(), new Organization { UseGroups = true }) + new SutProviderCustomization(), new Fixtures.Organization { UseGroups = true }) { } } internal class GroupOrganizationNotUseGroupsAutoDataAttribute : CustomAutoDataAttribute { public GroupOrganizationNotUseGroupsAutoDataAttribute() : base( - new SutProviderCustomization(), new Organization { UseGroups = false }) + new SutProviderCustomization(), new Bit.Core.Test.AutoFixture.OrganizationFixtures.Organization { UseGroups = false }) + { } + } + + internal class GroupBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Group)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Group>(); + return obj; + } + } + + internal class EfGroup: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new GroupBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<GroupRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfGroupAutoDataAttribute : CustomAutoDataAttribute + { + public EfGroupAutoDataAttribute() : base(new SutProviderCustomization(), new EfGroup()) + { } + } + + internal class InlineEfGroupAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfGroupAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfGroup) }, values) { } } } diff --git a/test/Core.Test/AutoFixture/GroupUserFixtures.cs b/test/Core.Test/AutoFixture/GroupUserFixtures.cs new file mode 100644 index 0000000000..1ee09445e5 --- /dev/null +++ b/test/Core.Test/AutoFixture/GroupUserFixtures.cs @@ -0,0 +1,63 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; + +namespace Bit.Core.Test.AutoFixture.GroupUserFixtures +{ + internal class GroupUserBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.GroupUser)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.GroupUser>(); + return obj; + } + } + + internal class EfGroupUser: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new GroupUserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<GroupRepository>()); + } + } + + internal class EfGroupUserAutoDataAttribute : CustomAutoDataAttribute + { + public EfGroupUserAutoDataAttribute() : base(new SutProviderCustomization(), new EfGroupUser()) + { } + } + + internal class InlineEfGroupUserAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfGroupUserAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfGroupUser) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/InstallationFixtures.cs b/test/Core.Test/AutoFixture/InstallationFixtures.cs new file mode 100644 index 0000000000..8e1aa74eee --- /dev/null +++ b/test/Core.Test/AutoFixture/InstallationFixtures.cs @@ -0,0 +1,63 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; + +namespace Bit.Core.Test.AutoFixture.InstallationFixtures +{ + internal class InstallationBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Installation)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Installation>(); + return obj; + } + } + + internal class EfInstallation: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new InstallationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<InstallationRepository>()); + } + } + + internal class EfInstallationAutoDataAttribute : CustomAutoDataAttribute + { + public EfInstallationAutoDataAttribute() : base(new SutProviderCustomization(), new EfInstallation()) + { } + } + + internal class InlineEfInstallationAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfInstallationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfInstallation) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/OrganizationFixtures.cs b/test/Core.Test/AutoFixture/OrganizationFixtures.cs index 5fd953270a..36f61e4d1c 100644 --- a/test/Core.Test/AutoFixture/OrganizationFixtures.cs +++ b/test/Core.Test/AutoFixture/OrganizationFixtures.cs @@ -6,9 +6,14 @@ using AutoFixture; using Bit.Core.Enums; using Bit.Core.Models.Business; using Bit.Core.Models.Data; -using Bit.Core.Models.Table; +using TableModel = Bit.Core.Models.Table; using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; using Bit.Core.Utilities; +using AutoFixture.Kernel; +using Bit.Core.Models; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Repositories.EntityFramework; namespace Bit.Core.Test.AutoFixture.OrganizationFixtures { @@ -30,7 +35,30 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures composer .With(c => c.OrganizationId, organizationId)); - fixture.Customize<Group>(composer => composer.With(g => g.OrganizationId, organizationId)); + fixture.Customize<TableModel.Group>(composer => composer.With(g => g.OrganizationId, organizationId)); + } + } + + internal class OrganizationBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Organization)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(); + var organization = new Fixture().WithAutoNSubstitutions().Create<TableModel.Organization>(); + organization.SetTwoFactorProviders(providers); + return organization; } } @@ -46,7 +74,7 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures fixture.Customize<Core.Models.Table.Organization>(composer => composer .With(o => o.PlanType, CheckedPlanType)); fixture.Customize<OrganizationUpgrade>(composer => composer - .With(ou => ou.Plan, validUpgradePlans.First())); + .With(ou => ou.Plan, validUpgradePlans.First())) ; } } @@ -67,6 +95,7 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures .Without(o => o.GatewaySubscriptionId)); } } + internal class OrganizationInvite : ICustomization { public OrganizationUserType InviteeUserType { get; set; } @@ -82,7 +111,7 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures fixture.Customize<Core.Models.Table.Organization>(composer => composer .With(o => o.Id, organizationId) .With(o => o.Seats, (short)100)); - fixture.Customize<OrganizationUser>(composer => composer + fixture.Customize<TableModel.OrganizationUser>(composer => composer .With(ou => ou.OrganizationId, organizationId) .With(ou => ou.Type, InvitorUserType) .With(ou => ou.Permissions, PermissionsBlob)); @@ -91,6 +120,17 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures } } + internal class EfOrganization: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + internal class PaidOrganizationAutoDataAttribute : CustomAutoDataAttribute { public PaidOrganizationAutoDataAttribute(int planType = 0) : base(new SutProviderCustomization(), @@ -136,4 +176,17 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures typeof(OrganizationInvite) }, values) { } } + + internal class EfOrganizationAutoDataAttribute : CustomAutoDataAttribute + { + public EfOrganizationAutoDataAttribute() : base(new SutProviderCustomization(), new EfOrganization()) + { } + } + + internal class InlineEfOrganizationAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfOrganizationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfOrganization) }, values) + { } + } } diff --git a/test/Core.Test/AutoFixture/OrganizationUserFixtures.cs b/test/Core.Test/AutoFixture/OrganizationUserFixtures.cs index 96c4f95afa..f5e5256dd0 100644 --- a/test/Core.Test/AutoFixture/OrganizationUserFixtures.cs +++ b/test/Core.Test/AutoFixture/OrganizationUserFixtures.cs @@ -1,10 +1,63 @@ -using System.Reflection; -using AutoFixture; -using AutoFixture.Xunit2; +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Models.Data; +using System.Text.Json; +using Bit.Core.Test.AutoFixture.UserFixtures; +using AutoFixture.Xunit2; +using System.Reflection; namespace Bit.Core.Test.AutoFixture.OrganizationUserFixtures { + internal class OrganizationUserBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == typeof(OrganizationUser)) + { + var fixture = new Fixture(); + var orgUser = fixture.WithAutoNSubstitutions().Create<TableModel.OrganizationUser>(); + var orgUserPermissions = fixture.WithAutoNSubstitutions().Create<Permissions>(); + orgUser.Permissions = JsonSerializer.Serialize(orgUserPermissions, new JsonSerializerOptions() { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + return orgUser; + } + else if (type == typeof(List<OrganizationUser>)) + { + var fixture = new Fixture(); + var orgUsers = fixture.WithAutoNSubstitutions().CreateMany<TableModel.OrganizationUser>(2); + foreach (var orgUser in orgUsers) + { + var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(); + var orgUserPermissions = fixture.WithAutoNSubstitutions().Create<Permissions>(); + orgUser.Permissions = JsonSerializer.Serialize(orgUserPermissions, new JsonSerializerOptions() { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + } + return orgUsers; + } + return new NoSpecimen(); + } + } + internal class OrganizationUser : ICustomization { public OrganizationUserStatusType Status { get; set; } @@ -42,4 +95,32 @@ namespace Bit.Core.Test.AutoFixture.OrganizationUserFixtures return new OrganizationUser(_status, _type); } } + + internal class EfOrganizationUser: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new OrganizationUserBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationUserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfOrganizationUserAutoDataAttribute : CustomAutoDataAttribute + { + public EfOrganizationUserAutoDataAttribute() : base(new SutProviderCustomization(), new EfOrganizationUser()) + { } + } + + internal class InlineEfOrganizationUserAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfOrganizationUserAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfOrganizationUser) }, values) + { } + } } diff --git a/test/Core.Test/AutoFixture/PolicyFixtures.cs b/test/Core.Test/AutoFixture/PolicyFixtures.cs index ad79ff1cfe..f53cd271cb 100644 --- a/test/Core.Test/AutoFixture/PolicyFixtures.cs +++ b/test/Core.Test/AutoFixture/PolicyFixtures.cs @@ -1,10 +1,23 @@ using System; using System.Reflection; using AutoFixture; -using AutoFixture.Xunit2; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using System.Reflection; +using AutoFixture.Xunit2; -namespace Bit.Core.Test.AutoFixture.OrganizationUserFixtures +namespace Bit.Core.Test.AutoFixture.PolicyFixtures { internal class Policy : ICustomization { @@ -38,4 +51,51 @@ namespace Bit.Core.Test.AutoFixture.OrganizationUserFixtures return new Policy(_type); } } + + internal class PolicyBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Policy)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Policy>(); + return obj; + } + } + + internal class EfPolicy: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new PolicyBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<PolicyRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfPolicyAutoDataAttribute : CustomAutoDataAttribute + { + public EfPolicyAutoDataAttribute() : base(new SutProviderCustomization(), new EfPolicy()) + { } + } + + internal class InlineEfPolicyAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfPolicyAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfPolicy) }, values) + { } + } } diff --git a/test/Core.Test/AutoFixture/Relays/MaxLengthStringRelay.cs b/test/Core.Test/AutoFixture/Relays/MaxLengthStringRelay.cs new file mode 100644 index 0000000000..38e3a1a9c5 --- /dev/null +++ b/test/Core.Test/AutoFixture/Relays/MaxLengthStringRelay.cs @@ -0,0 +1,43 @@ +using AutoFixture.Kernel; +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; + +namespace Bit.Core.Test.AutoFixture.Relays +{ + // Creates a string the same length as any availible MaxLength data annotation + // Modified version of the StringLenfthRelay provided by AutoFixture + // https://github.com/AutoFixture/AutoFixture/blob/master/Src/AutoFixture/DataAnnotations/StringLengthAttributeRelay.cs + internal class MaxLengthStringRelay: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (request == null) + { + return new NoSpecimen(); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var p = request as PropertyInfo; + if (p == null) + { + return new NoSpecimen(); + } + + var a = (MaxLengthAttribute)p.GetCustomAttributes(typeof(MaxLengthAttribute), false).SingleOrDefault(); + + if (a == null) + { + return new NoSpecimen(); + } + + return context.Resolve(new ConstrainedStringRequest(a.Length, a.Length)); + } + } +} + diff --git a/test/Core.Test/AutoFixture/SendFixtures.cs b/test/Core.Test/AutoFixture/SendFixtures.cs index 5847684013..1588837c6e 100644 --- a/test/Core.Test/AutoFixture/SendFixtures.cs +++ b/test/Core.Test/AutoFixture/SendFixtures.cs @@ -1,7 +1,14 @@ using System; using AutoFixture; +using AutoFixture.Kernel; using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework; using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Test.AutoFixture.Relays; +using Bit.Core.Test.AutoFixture.UserFixtures; namespace Bit.Core.Test.AutoFixture.SendFixtures { @@ -62,4 +69,62 @@ namespace Bit.Core.Test.AutoFixture.SendFixtures typeof(SutProviderCustomization), typeof(OrganizationSend) }, values) { } } + + internal class SendBuilder: ISpecimenBuilder + { + public bool OrganizationOwned { get; set; } + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(Send)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + if (!OrganizationOwned) + { + fixture.Customize<Send>(composer => composer + .Without(c => c.OrganizationId)); + } + var obj = fixture.WithAutoNSubstitutions().Create<Send>(); + return obj; + } + } + + internal class EfSend: ICustomization + { + public bool OrganizationOwned { get; set; } + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new SendBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<SendRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfUserSendAutoDataAttribute : CustomAutoDataAttribute + { + public EfUserSendAutoDataAttribute() : base(new SutProviderCustomization(), new EfSend()) + { } + } + + internal class EfOrganizationSendAutoDataAttribute : CustomAutoDataAttribute + { + public EfOrganizationSendAutoDataAttribute() : base(new SutProviderCustomization(), new EfSend(){ + OrganizationOwned = true, + }) + { } + } } diff --git a/test/Core.Test/AutoFixture/SsoConfigFixtures.cs b/test/Core.Test/AutoFixture/SsoConfigFixtures.cs new file mode 100644 index 0000000000..c41f31be40 --- /dev/null +++ b/test/Core.Test/AutoFixture/SsoConfigFixtures.cs @@ -0,0 +1,67 @@ +using System; +using AutoFixture; +using AutoFixture.Kernel; +using AutoMapper; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Models.Data; +using System.Text.Json; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Repositories.EntityFramework; + +namespace Bit.Core.Test.AutoFixture.SsoConfigFixtures +{ + internal class SsoConfigBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.SsoConfig)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + var ssoConfig = fixture.WithAutoNSubstitutions().Create<TableModel.SsoConfig>(); + var ssoConfigData = fixture.WithAutoNSubstitutions().Create<SsoConfigurationData>(); + ssoConfig.Data = JsonSerializer.Serialize(ssoConfigData, new JsonSerializerOptions(){ + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + return ssoConfig; + } + } + + internal class EfSsoConfig: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new SsoConfigBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<SsoConfigRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfSsoConfigAutoDataAttribute : CustomAutoDataAttribute + { + public EfSsoConfigAutoDataAttribute() : base(new SutProviderCustomization(), new EfSsoConfig()) + { } + } + + internal class InlineEfSsoConfigAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfSsoConfigAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfSsoConfig) }, values) + { } + } +} diff --git a/test/Core.Test/AutoFixture/SsoUserFixtures.cs b/test/Core.Test/AutoFixture/SsoUserFixtures.cs new file mode 100644 index 0000000000..bb756a73bc --- /dev/null +++ b/test/Core.Test/AutoFixture/SsoUserFixtures.cs @@ -0,0 +1,41 @@ +using AutoFixture; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Test.AutoFixture.UserFixtures; +using TableModel = Bit.Core.Models.Table; + +namespace Bit.Core.Test.AutoFixture.SsoUserFixtures +{ + internal class EfSsoUser: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customize<TableModel.SsoUser>(composer => composer.Without(ou => ou.Id)); + fixture.Customizations.Add(new EfRepositoryListBuilder<SsoUserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfSsoUserAutoDataAttribute : CustomAutoDataAttribute + { + public EfSsoUserAutoDataAttribute() : base(new SutProviderCustomization(), new EfSsoUser()) + { } + } + + internal class InlineEfSsoUserAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfSsoUserAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfSsoUser) }, values) + { } + } +} diff --git a/test/Core.Test/AutoFixture/SutProvider.cs b/test/Core.Test/AutoFixture/SutProvider.cs index a3cfc210d7..e8b475d248 100644 --- a/test/Core.Test/AutoFixture/SutProvider.cs +++ b/test/Core.Test/AutoFixture/SutProvider.cs @@ -4,6 +4,7 @@ using AutoFixture; using AutoFixture.Kernel; using System.Reflection; using System.Linq; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; namespace Bit.Core.Test.AutoFixture { diff --git a/test/Core.Test/AutoFixture/TaxRateFixtures.cs b/test/Core.Test/AutoFixture/TaxRateFixtures.cs new file mode 100644 index 0000000000..b0a94eacce --- /dev/null +++ b/test/Core.Test/AutoFixture/TaxRateFixtures.cs @@ -0,0 +1,65 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.AutoFixture.TaxRateFixtures +{ + internal class TaxRateBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.TaxRate)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.TaxRate>(); + return obj; + } + } + + internal class EfTaxRate: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new TaxRateBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<TaxRateRepository>()); + } + } + + internal class EfTaxRateAutoDataAttribute : CustomAutoDataAttribute + { + public EfTaxRateAutoDataAttribute() : base(new SutProviderCustomization(), new EfTaxRate()) + { } + } + + internal class InlineEfTaxRateAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfTaxRateAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfTaxRate) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/TransactionFixutres.cs b/test/Core.Test/AutoFixture/TransactionFixutres.cs new file mode 100644 index 0000000000..e2660c8c7a --- /dev/null +++ b/test/Core.Test/AutoFixture/TransactionFixutres.cs @@ -0,0 +1,78 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.UserFixtures; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.AutoFixture.TransactionFixtures +{ + internal class TransactionBuilder: ISpecimenBuilder + { + public bool OrganizationOwned { get; set; } + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.Transaction)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + if (!OrganizationOwned) + { + fixture.Customize<Transaction>(composer => composer + .Without(c => c.OrganizationId)); + } + fixture.Customizations.Add(new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.Transaction>(); + return obj; + } + } + + internal class EfTransaction: ICustomization + { + public bool OrganizationOwned { get; set; } + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new TransactionBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<TransactionRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfUserTransactionAutoDataAttribute : CustomAutoDataAttribute + { + public EfUserTransactionAutoDataAttribute() : base(new SutProviderCustomization(), new EfTransaction()) + { } + } + + internal class EfOrganizationTransactionAutoDataAttribute : CustomAutoDataAttribute + { + public EfOrganizationTransactionAutoDataAttribute() : base(new SutProviderCustomization(), new EfTransaction(){ + OrganizationOwned = true, + }) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/U2fFixtures.cs b/test/Core.Test/AutoFixture/U2fFixtures.cs new file mode 100644 index 0000000000..d813fd20ea --- /dev/null +++ b/test/Core.Test/AutoFixture/U2fFixtures.cs @@ -0,0 +1,68 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; +using Bit.Core.Test.AutoFixture.Relays; +using Bit.Core.Test.AutoFixture.UserFixtures; + +namespace Bit.Core.Test.AutoFixture.U2fFixtures +{ + internal class U2fBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(TableModel.U2f)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Add(new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create<TableModel.U2f>(); + return obj; + } + } + + internal class EfU2f: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new U2fBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<U2fRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + } + } + + internal class EfU2fAutoDataAttribute : CustomAutoDataAttribute + { + public EfU2fAutoDataAttribute() : base(new SutProviderCustomization(), new EfU2f()) + { } + } + + internal class InlineEfU2fAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfU2fAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfU2f) }, values) + { } + } +} + diff --git a/test/Core.Test/AutoFixture/UserFixtures.cs b/test/Core.Test/AutoFixture/UserFixtures.cs new file mode 100644 index 0000000000..1ef978fb07 --- /dev/null +++ b/test/Core.Test/AutoFixture/UserFixtures.cs @@ -0,0 +1,78 @@ +using AutoFixture; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures; +using AutoMapper; +using Bit.Core.Models.EntityFramework; +using Bit.Core.Models; +using System.Collections.Generic; +using Bit.Core.Enums; +using AutoFixture.Kernel; +using System; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures; + +namespace Bit.Core.Test.AutoFixture.UserFixtures +{ + internal class UserBuilder: ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == typeof(TableModel.User)) + { + var fixture = new Fixture(); + var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(); + var user = fixture.WithAutoNSubstitutions().Create<TableModel.User>(); + user.SetTwoFactorProviders(providers); + return user; + } + else if (type == typeof(List<TableModel.User>)) + { + var fixture = new Fixture(); + var users = fixture.WithAutoNSubstitutions().CreateMany<TableModel.User>(2); + foreach (var user in users) + { + var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(); + user.SetTwoFactorProviders(providers); + } + return users; + } + + return new NoSpecimen(); + } + } + + internal class EfUser: ICustomization + { + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<SsoUserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>()); + } + } + + internal class EfUserAutoDataAttribute : CustomAutoDataAttribute + { + public EfUserAutoDataAttribute() : base(new SutProviderCustomization(), new EfUser()) + { } + } + + internal class InlineEfUserAutoDataAttribute : InlineCustomAutoDataAttribute + { + public InlineEfUserAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfUser) }, values) + { } + } +} diff --git a/test/Core.Test/Core.Test.csproj b/test/Core.Test/Core.Test.csproj index 9ed29918d5..fe0812b797 100644 --- a/test/Core.Test/Core.Test.csproj +++ b/test/Core.Test/Core.Test.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <IsPackable>false</IsPackable> @@ -11,6 +11,7 @@ <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" /> + <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="NSubstitute" Version="4.2.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.2"> @@ -23,6 +24,6 @@ <ItemGroup> <ProjectReference Include="..\..\src\Core\Core.csproj" /> + <ProjectReference Include="..\..\src\Api\Api.csproj" /> </ItemGroup> - </Project> diff --git a/test/Core.Test/Helpers/Factories.cs b/test/Core.Test/Helpers/Factories.cs new file mode 100644 index 0000000000..61469a543a --- /dev/null +++ b/test/Core.Test/Helpers/Factories.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Settings; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +namespace Bit.Core.Test.Helpers.Factories +{ + public static class GlobalSettingsFactory + { + public static GlobalSettings GlobalSettings { get; } = new GlobalSettings(); + static GlobalSettingsFactory() + { + var configBuilder = new ConfigurationBuilder().AddUserSecrets<Bit.Api.Startup>(); + var Configuration = configBuilder.Build(); + ConfigurationBinder.Bind(Configuration.GetSection("GlobalSettings"), GlobalSettings); + } + } + + public static class DatabaseOptionsFactory + { + public static List<DbContextOptions<DatabaseContext>> Options { get; } = new List<DbContextOptions<DatabaseContext>>(); + + static DatabaseOptionsFactory() + { + var globalSettings = GlobalSettingsFactory.GlobalSettings; + if (!string.IsNullOrWhiteSpace(GlobalSettingsFactory.GlobalSettings.PostgreSql?.ConnectionString)) + { + Options.Add(new DbContextOptionsBuilder<DatabaseContext>().UseNpgsql(globalSettings.PostgreSql.ConnectionString).Options); + } + if (!string.IsNullOrWhiteSpace(GlobalSettingsFactory.GlobalSettings.MySql?.ConnectionString)) + { + var mySqlConnectionString = globalSettings.MySql.ConnectionString; + Options.Add(new DbContextOptionsBuilder<DatabaseContext>().UseMySql(mySqlConnectionString, ServerVersion.AutoDetect(mySqlConnectionString)).Options); + } + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/CipherRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/CipherRepositoryTests.cs new file mode 100644 index 0000000000..8613949953 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/CipherRepositoryTests.cs @@ -0,0 +1,200 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.CipherFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Microsoft.EntityFrameworkCore; +using Xunit; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using Bit.Core.Test.AutoFixture.CipherFixtures; +using Bit.Core.Repositories.EntityFramework.Queries; +using Bit.Core.Models.Data; +using System; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class CipherRepositoryTests + { + [Theory (Skip = "Run ad-hoc"), EfUserCipherAutoData] + public async void RefreshDb(List<EfRepo.CipherRepository> suts) + { + foreach (var sut in suts) + { + await sut.RefreshDb(); + } + } + + [CiSkippedTheory, EfUserCipherAutoData, EfOrganizationCipherAutoData] + public async void CreateAsync_Works_DataMatches(Cipher cipher, User user, Organization org, + CipherCompare equalityComparer, List<EfRepo.CipherRepository> suts, List<EfRepo.UserRepository> efUserRepos, + List<EfRepo.OrganizationRepository> efOrgRepos, SqlRepo.CipherRepository sqlCipherRepo, + SqlRepo.UserRepository sqlUserRepo, SqlRepo.OrganizationRepository sqlOrgRepo) + { + var savedCiphers = new List<Cipher>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + sut.ClearChangeTracking(); + cipher.UserId = efUser.Id; + + if (cipher.OrganizationId.HasValue) + { + var efOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + cipher.OrganizationId = efOrg.Id; + } + + var postEfCipher = await sut.CreateAsync(cipher); + sut.ClearChangeTracking(); + + var savedCipher = await sut.GetByIdAsync(postEfCipher.Id); + savedCiphers.Add(savedCipher); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + cipher.UserId = sqlUser.Id; + + if (cipher.OrganizationId.HasValue) + { + var sqlOrg = await sqlOrgRepo.CreateAsync(org); + cipher.OrganizationId = sqlOrg.Id; + } + + var sqlCipher = await sqlCipherRepo.CreateAsync(cipher); + var savedSqlCipher = await sqlCipherRepo.GetByIdAsync(sqlCipher.Id); + savedCiphers.Add(savedSqlCipher); + + var distinctItems = savedCiphers.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfUserCipherAutoData] + public async void CreateAsync_BumpsUserAccountRevisionDate(Cipher cipher, User user, + CipherCompare equalityComparer, List<EfRepo.CipherRepository> suts, List<EfRepo.UserRepository> efUserRepos, + SqlRepo.CipherRepository sqlCipherRepo, SqlRepo.UserRepository sqlUserRepo) + { + var bumpedUsers = new List<User>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + efUserRepos[i].ClearChangeTracking(); + cipher.UserId = efUser.Id; + cipher.OrganizationId = null; + + var postEfCipher = await sut.CreateAsync(cipher); + sut.ClearChangeTracking(); + + var bumpedUser = await efUserRepos[i].GetByIdAsync(efUser.Id); + bumpedUsers.Add(bumpedUser); + } + + Assert.True(bumpedUsers.All(u => u.AccountRevisionDate.ToShortDateString() == DateTime.UtcNow.ToShortDateString() )); + } + + [CiSkippedTheory, EfOrganizationCipherAutoData] + public async void CreateAsync_BumpsOrgUserAccountRevisionDates(Cipher cipher, List<User> users, + List<OrganizationUser> orgUsers, Collection collection, Organization org, CipherCompare equalityComparer, + List<EfRepo.CipherRepository> suts, List<EfRepo.UserRepository> efUserRepos, List<EfRepo.OrganizationRepository> efOrgRepos, + List<EfRepo.OrganizationUserRepository> efOrgUserRepos, List<EfRepo.CollectionRepository> efCollectionRepos) + { + var savedCiphers = new List<Cipher>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUsers = await efUserRepos[i].CreateMany(users); + efUserRepos[i].ClearChangeTracking(); + var efOrg = await efOrgRepos[i].CreateAsync(org); + efOrgRepos[i].ClearChangeTracking(); + + cipher.OrganizationId = efOrg.Id; + + collection.OrganizationId = efOrg.Id; + var efCollection = await efCollectionRepos[i].CreateAsync(collection); + efCollectionRepos[i].ClearChangeTracking(); + + IEnumerable<object>[] lists = {efUsers, orgUsers}; + var maxOrgUsers = lists.Min(l => l.Count()); + + orgUsers = orgUsers.Take(maxOrgUsers).ToList(); + efUsers = efUsers.Take(maxOrgUsers).ToList(); + + for (var j = 0; j < maxOrgUsers; j++) + { + orgUsers[j].OrganizationId = efOrg.Id; + orgUsers[j].UserId = efUsers[j].Id; + } + + orgUsers = await efOrgUserRepos[i].CreateMany(orgUsers); + + var selectionReadOnlyList = new List<SelectionReadOnly>(); + orgUsers.ForEach(ou => selectionReadOnlyList.Add(new SelectionReadOnly() { Id = ou.Id })); + + await efCollectionRepos[i].UpdateUsersAsync(efCollection.Id, selectionReadOnlyList); + efCollectionRepos[i].ClearChangeTracking(); + + foreach (var ou in orgUsers) + { + var collectionUser = new CollectionUser() { + CollectionId = efCollection.Id, + OrganizationUserId = ou.Id + }; + } + + cipher.UserId = null; + var postEfCipher = await sut.CreateAsync(cipher); + sut.ClearChangeTracking(); + + var query = new UserBumpAccountRevisionDateByCipherIdQuery(cipher); + var modifiedUsers = await sut.Run(query).ToListAsync(); + Assert.True(modifiedUsers + .All(u => u.AccountRevisionDate.ToShortDateString() == + DateTime.UtcNow.ToShortDateString())); + } + } + + [CiSkippedTheory, EfUserCipherAutoData, EfOrganizationCipherAutoData] + public async void DeleteAsync_CipherIsDeleted( + Cipher cipher, + User user, + Organization org, + List<EfRepo.CipherRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + List<EfRepo.OrganizationRepository> efOrgRepos + ) + { + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var postEfOrg = await efOrgRepos[i].CreateAsync(org); + efOrgRepos[i].ClearChangeTracking(); + var postEfUser = await efUserRepos[i].CreateAsync(user); + efUserRepos[i].ClearChangeTracking(); + + if (cipher.OrganizationId.HasValue) + { + cipher.OrganizationId = postEfOrg.Id; + } + cipher.UserId = postEfUser.Id; + + await sut.CreateAsync(cipher); + sut.ClearChangeTracking(); + + await sut.DeleteAsync(cipher); + sut.ClearChangeTracking(); + + var savedCipher = await sut.GetByIdAsync(cipher.Id); + Assert.True(savedCipher == null); + } + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/CollectionRepository.cs b/test/Core.Test/Repositories/EntityFramework/CollectionRepository.cs new file mode 100644 index 0000000000..b29d265af7 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/CollectionRepository.cs @@ -0,0 +1,55 @@ +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.AutoFixture.Attributes; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Bit.Core.Models.Table; +using System.Collections.Generic; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Bit.Core.Test.AutoFixture.CollectionFixtures; +using System.Linq; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class CollectionRepositoryTests + { + [CiSkippedTheory, EfCollectionAutoData] + public async void CreateAsync_Works_DataMatches( + Collection collection, + Organization organization, + CollectionCompare equalityComparer, + List<EfRepo.CollectionRepository> suts, + List<EfRepo.OrganizationRepository> efOrganizationRepos, + SqlRepo.CollectionRepository sqlCollectionRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo + ) + { + var savedCollections = new List<Collection>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + var efOrganization = await efOrganizationRepos[i].CreateAsync(organization); + sut.ClearChangeTracking(); + + collection.OrganizationId = efOrganization.Id; + var postEfCollection = await sut.CreateAsync(collection); + sut.ClearChangeTracking(); + + var savedCollection = await sut.GetByIdAsync(postEfCollection.Id); + savedCollections.Add(savedCollection); + } + + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organization); + collection.OrganizationId = sqlOrganization.Id; + + var sqlCollection = await sqlCollectionRepo.CreateAsync(collection); + var savedSqlCollection = await sqlCollectionRepo.GetByIdAsync(sqlCollection.Id); + savedCollections.Add(savedSqlCollection); + + var distinctItems = savedCollections.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/DeviceRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/DeviceRepositoryTests.cs new file mode 100644 index 0000000000..33d3bf66a4 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/DeviceRepositoryTests.cs @@ -0,0 +1,49 @@ +using Bit.Core.Test.AutoFixture.Attributes; +using Xunit; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using System.Collections.Generic; +using Bit.Core.Models.Table; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using System.Linq; +using Bit.Core.Test.AutoFixture.DeviceFixtures; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class DeviceRepositoryTests + { + [CiSkippedTheory, EfDeviceAutoData] + public async void CreateAsync_Works_DataMatches(Device device, User user, + DeviceCompare equalityComparer, List<EfRepo.DeviceRepository> suts, + List<EfRepo.UserRepository> efUserRepos, SqlRepo.DeviceRepository sqlDeviceRepo, + SqlRepo.UserRepository sqlUserRepo) + { + var savedDevices = new List<Device>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + device.UserId = efUser.Id; + sut.ClearChangeTracking(); + + var postEfDevice = await sut.CreateAsync(device); + sut.ClearChangeTracking(); + + var savedDevice = await sut.GetByIdAsync(postEfDevice.Id); + savedDevices.Add(savedDevice); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + device.UserId = sqlUser.Id; + + var sqlDevice = await sqlDeviceRepo.CreateAsync(device); + var savedSqlDevice = await sqlDeviceRepo.GetByIdAsync(sqlDevice.Id); + savedDevices.Add(savedSqlDevice); + + var distinctItems = savedDevices.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EmergencyAccessRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/EmergencyAccessRepositoryTests.cs new file mode 100644 index 0000000000..aaf00bbfdd --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EmergencyAccessRepositoryTests.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.EmergencyAccessFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Xunit; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using System.Linq; +using AutoFixture; +using Bit.Core.Test.AutoFixture.TransactionFixtures; +using AutoFixture.DataAnnotations; +using AutoFixture.Kernel; +using Bit.Core.Test.AutoFixture.Relays; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class EmergencyAccessRepositoryTests + { + [CiSkippedTheory, EfEmergencyAccessAutoData] + public async void CreateAsync_Works_DataMatches( + EmergencyAccess emergencyAccess, + List<User> users, + EmergencyAccessCompare equalityComparer, + List<EfRepo.EmergencyAccessRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + SqlRepo.EmergencyAccessRepository sqlEmergencyAccessRepo, + SqlRepo.UserRepository sqlUserRepo + ) + { + var savedEmergencyAccesss = new List<EmergencyAccess>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + for (int j = 0; j < users.Count; j++) + { + users[j] = await efUserRepos[i].CreateAsync(users[j]); + } + sut.ClearChangeTracking(); + + emergencyAccess.GrantorId = users[0].Id; + emergencyAccess.GranteeId = users[0].Id; + var postEfEmergencyAccess = await sut.CreateAsync(emergencyAccess); + sut.ClearChangeTracking(); + + var savedEmergencyAccess = await sut.GetByIdAsync(postEfEmergencyAccess.Id); + savedEmergencyAccesss.Add(savedEmergencyAccess); + } + + for (int j = 0; j < users.Count; j++) + { + users[j] = await sqlUserRepo.CreateAsync(users[j]); + } + + emergencyAccess.GrantorId = users[0].Id; + emergencyAccess.GranteeId = users[0].Id; + var sqlEmergencyAccess = await sqlEmergencyAccessRepo.CreateAsync(emergencyAccess); + var savedSqlEmergencyAccess = await sqlEmergencyAccessRepo.GetByIdAsync(sqlEmergencyAccess.Id); + savedEmergencyAccesss.Add(savedSqlEmergencyAccess); + + var distinctItems = savedEmergencyAccesss.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/CipherCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/CipherCompare.cs new file mode 100644 index 0000000000..979ea7e7b5 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/CipherCompare.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class CipherCompare: IEqualityComparer<Cipher> + { + public bool Equals(Cipher x, Cipher y) + { + return x.Type == y.Type && + x.Data == y.Data && + x.Favorites == y.Favorites && + x.Attachments == y.Attachments; + } + + public int GetHashCode([DisallowNull] Cipher obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/CollectionCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/CollectionCompare.cs new file mode 100644 index 0000000000..99aaa4e6b1 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/CollectionCompare.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class CollectionCompare: IEqualityComparer<Collection> + { + public bool Equals(Collection x, Collection y) + { + return x.Name == y.Name && + x.ExternalId == y.ExternalId; + } + + public int GetHashCode([DisallowNull] Collection obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/DeviceCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/DeviceCompare.cs new file mode 100644 index 0000000000..ae4defa0ff --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/DeviceCompare.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class DeviceCompare: IEqualityComparer<Device> + { + public bool Equals(Device x, Device y) + { + return x.Name == y.Name && + x.Type == y.Type && + x.Identifier == y.Identifier && + x.PushToken == y.PushToken; + } + + public int GetHashCode([DisallowNull] Device obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/EmergencyAccessCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/EmergencyAccessCompare.cs new file mode 100644 index 0000000000..2564f418ca --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/EmergencyAccessCompare.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class EmergencyAccessCompare: IEqualityComparer<EmergencyAccess> + { + public bool Equals(EmergencyAccess x, EmergencyAccess y) + { + return x.Email == y.Email && + x.KeyEncrypted == y.KeyEncrypted && + x.Type == y.Type && + x.Status == y.Status && + x.WaitTimeDays == y.WaitTimeDays && + x.RecoveryInitiatedDate == y.RecoveryInitiatedDate && + x.LastNotificationDate == y.LastNotificationDate; + } + + public int GetHashCode([DisallowNull] EmergencyAccess obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/EventCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/EventCompare.cs new file mode 100644 index 0000000000..ea278388c4 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/EventCompare.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class EventCompare: IEqualityComparer<Event> + { + public bool Equals(Event x, Event y) + { + return x.Date.ToShortDateString() == y.Date.ToShortDateString() && + x.Type == y.Type && + x.IpAddress == y.IpAddress; + } + + public int GetHashCode([DisallowNull] Event obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/FolderCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/FolderCompare.cs new file mode 100644 index 0000000000..1f0a832e60 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/FolderCompare.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class FolderCompare: IEqualityComparer<Folder> + { + public bool Equals(Folder x, Folder y) + { + return x.Name == y.Name; + } + + public int GetHashCode([DisallowNull] Folder obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/GrantCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/GrantCompare.cs new file mode 100644 index 0000000000..09d8f01817 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/GrantCompare.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class GrantCompare: IEqualityComparer<Grant> + { + public bool Equals(Grant x, Grant y) + { + return x.Key == y.Key && + x.Type == y.Type && + x.SubjectId == y.SubjectId && + x.ClientId == y.ClientId && + x.Description == y.Description && + x.ExpirationDate == y.ExpirationDate && + x.ConsumedDate == y.ConsumedDate && + x.Data == y.Data; + } + + public int GetHashCode([DisallowNull] Grant obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/GroupCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/GroupCompare.cs new file mode 100644 index 0000000000..1b06b3fbfb --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/GroupCompare.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class GroupCompare: IEqualityComparer<Group> + { + public bool Equals(Group x, Group y) + { + return x.Name == y.Name && + x.AccessAll == y.AccessAll && + x.ExternalId == y.ExternalId; + } + + public int GetHashCode([DisallowNull] Group obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/InstallationCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/InstallationCompare.cs new file mode 100644 index 0000000000..380a348e7e --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/InstallationCompare.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class InstallationCompare: IEqualityComparer<Installation> + { + public bool Equals(Installation x, Installation y) + { + return x.Email == y.Email && + x.Key == y.Key && + x.Enabled == y.Enabled; + } + + public int GetHashCode([DisallowNull] Installation obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationCompare.cs new file mode 100644 index 0000000000..d8f51febf1 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationCompare.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class OrganizationCompare: IEqualityComparer<Organization> + { + public bool Equals(Organization x, Organization y) + { + var a = x.ExpirationDate.ToString(); + var b = y.ExpirationDate.ToString(); + return x.Identifier.Equals(y.Identifier) && + x.Name.Equals(y.Name) && + x.BusinessName.Equals(y.BusinessName) && + x.BusinessAddress1.Equals(y.BusinessAddress1) && + x.BusinessAddress2.Equals(y.BusinessAddress2) && + x.BusinessAddress3.Equals(y.BusinessAddress3) && + x.BusinessCountry.Equals(y.BusinessCountry) && + x.BusinessTaxNumber.Equals(y.BusinessTaxNumber) && + x.BillingEmail.Equals(y.BillingEmail) && + x.Plan.Equals(y.Plan) && + x.PlanType.Equals(y.PlanType) && + x.Seats.Equals(y.Seats) && + x.MaxCollections.Equals(y.MaxCollections) && + x.UsePolicies.Equals(y.UsePolicies) && + x.UseSso.Equals(y.UseSso) && + x.UseGroups.Equals(y.UseGroups) && + x.UseDirectory.Equals(y.UseDirectory) && + x.UseEvents.Equals(y.UseEvents) && + x.UseTotp.Equals(y.UseTotp) && + x.Use2fa.Equals(y.Use2fa) && + x.UseApi.Equals(y.UseApi) && + x.SelfHost.Equals(y.SelfHost) && + x.UsersGetPremium.Equals(y.UsersGetPremium) && + x.Storage.Equals(y.Storage) && + x.MaxStorageGb.Equals(y.MaxStorageGb) && + x.Gateway.Equals(y.Gateway) && + x.GatewayCustomerId.Equals(y.GatewayCustomerId) && + x.GatewaySubscriptionId.Equals(y.GatewaySubscriptionId) && + x.ReferenceData.Equals(y.ReferenceData) && + x.Enabled.Equals(y.Enabled) && + x.LicenseKey.Equals(y.LicenseKey) && + x.ApiKey.Equals(y.ApiKey) && + x.TwoFactorProviders.Equals(y.TwoFactorProviders) && + x.ExpirationDate.ToString().Equals(y.ExpirationDate.ToString()); + } + + public int GetHashCode([DisallowNull] Organization obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationUserCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationUserCompare.cs new file mode 100644 index 0000000000..7bad311228 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationUserCompare.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class OrganizationUserCompare: IEqualityComparer<OrganizationUser> + { + public bool Equals(OrganizationUser x, OrganizationUser y) + { + return x.Email == y.Email && + x.Status == y.Status && + x.Type == y.Type && + x.AccessAll == y.AccessAll && + x.ExternalId == y.ExternalId && + x.Permissions == y.Permissions; + } + + public int GetHashCode([DisallowNull] OrganizationUser obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/PolicyCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/PolicyCompare.cs new file mode 100644 index 0000000000..f17d63b8b5 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/PolicyCompare.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class PolicyCompare: IEqualityComparer<Policy> + { + public bool Equals(Policy x, Policy y) + { + return x.Type == y.Type && + x.Data == y.Data && + x.Enabled == y.Enabled; + } + + public int GetHashCode([DisallowNull] Policy obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SendCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SendCompare.cs new file mode 100644 index 0000000000..9a590c5483 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SendCompare.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class SendCompare: IEqualityComparer<Send> + { + public bool Equals(Send x, Send y) + { + return x.Type == y.Type && + x.Data == y.Data && + x.Key == y.Key && + x.Password == y.Password && + x.MaxAccessCount == y.MaxAccessCount && + x.AccessCount == y.AccessCount && + x.ExpirationDate?.ToShortDateString() == y.ExpirationDate?.ToShortDateString() && + x.DeletionDate.ToShortDateString() == y.DeletionDate.ToShortDateString() && + x.Disabled == y.Disabled && + x.HideEmail == y.HideEmail; + } + + public int GetHashCode([DisallowNull] Send obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SsoConfigCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SsoConfigCompare.cs new file mode 100644 index 0000000000..fbffd9cb0a --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SsoConfigCompare.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class SsoConfigCompare: IEqualityComparer<SsoConfig> + { + public bool Equals(SsoConfig x, SsoConfig y) + { + return x.Enabled == y.Enabled && + x.OrganizationId == y.OrganizationId && + x.Data == y.Data; + } + + public int GetHashCode([DisallowNull] SsoConfig obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SsoUserCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SsoUserCompare.cs new file mode 100644 index 0000000000..7b6dabd8b4 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/SsoUserCompare.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class SsoUserCompare: IEqualityComparer<SsoUser> + { + public bool Equals(SsoUser x, SsoUser y) + { + return x.ExternalId == y.ExternalId; + } + + public int GetHashCode([DisallowNull] SsoUser obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/TaxRateCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/TaxRateCompare.cs new file mode 100644 index 0000000000..88127de4dd --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/TaxRateCompare.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class TaxRateCompare: IEqualityComparer<TaxRate> + { + public bool Equals(TaxRate x, TaxRate y) + { + return x.Country == y.Country && + x.State == y.State && + x.PostalCode == y.PostalCode && + x.Rate == y.Rate && + x.Active == y.Active; + } + + public int GetHashCode([DisallowNull] TaxRate obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/TransactionCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/TransactionCompare.cs new file mode 100644 index 0000000000..fbf0d981bc --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/TransactionCompare.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class TransactionCompare: IEqualityComparer<Transaction> + { + public bool Equals(Transaction x, Transaction y) + { + return x.Type == y.Type && + x.Amount == y.Amount && + x.Refunded == y.Refunded && + x.Details == y.Details && + x.PaymentMethodType == y.PaymentMethodType && + x.Gateway == y.Gateway && + x.GatewayId == y.GatewayId; + } + + public int GetHashCode([DisallowNull] Transaction obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/U2fCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/U2fCompare.cs new file mode 100644 index 0000000000..3cf4d154b0 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/U2fCompare.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class U2fCompare: IEqualityComparer<U2f> + { + public bool Equals(U2f x, U2f y) + { + return x.KeyHandle == y.KeyHandle && + x.Challenge == y.Challenge && + x.AppId == y.AppId && + x.Version == y.Version; + } + + public int GetHashCode([DisallowNull] U2f obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/UserCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/UserCompare.cs new file mode 100644 index 0000000000..552dfc7d8f --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/UserCompare.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Table; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class UserCompare: IEqualityComparer<User> + { + public bool Equals(User x, User y) + { + return x.Name == y.Name && + x.Email == y.Email && + x.EmailVerified == y.EmailVerified && + x.MasterPassword == y.MasterPassword && + x.MasterPasswordHint == y.MasterPasswordHint && + x.Culture == y.Culture && + x.SecurityStamp == y.SecurityStamp && + x.TwoFactorProviders == y.TwoFactorProviders && + x.TwoFactorRecoveryCode == y.TwoFactorRecoveryCode && + x.EquivalentDomains == y.EquivalentDomains && + x.Key == y.Key && + x.PublicKey == y.PublicKey && + x.PrivateKey == y.PrivateKey && + x.Premium == y.Premium && + x.Storage == y.Storage && + x.MaxStorageGb == y.MaxStorageGb && + x.Gateway == y.Gateway && + x.GatewayCustomerId == y.GatewayCustomerId && + x.ReferenceData == y.ReferenceData && + x.LicenseKey == y.LicenseKey && + x.ApiKey == y.ApiKey && + x.Kdf == y.Kdf && + x.KdfIterations == y.KdfIterations; + } + + public int GetHashCode([DisallowNull] User obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/UserKdfInformation.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/UserKdfInformation.cs new file mode 100644 index 0000000000..d69c767137 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/UserKdfInformation.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Models.Data; + +namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers +{ + public class UserKdfInformationCompare: IEqualityComparer<UserKdfInformation> + { + public bool Equals(UserKdfInformation x, UserKdfInformation y) + { + return x.Kdf == y.Kdf && + x.KdfIterations == y.KdfIterations; + } + + public int GetHashCode([DisallowNull] UserKdfInformation obj) + { + return base.GetHashCode(); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/FolderRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/FolderRepositoryTests.cs new file mode 100644 index 0000000000..717b422863 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/FolderRepositoryTests.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.FolderFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Microsoft.EntityFrameworkCore; +using Xunit; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using System.Linq; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class FolderRepositoryTests + { + [CiSkippedTheory, EfFolderAutoData] + public async void CreateAsync_Works_DataMatches( + Folder folder, + User user, + FolderCompare equalityComparer, + List<EfRepo.FolderRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + SqlRepo.FolderRepository sqlFolderRepo, + SqlRepo.UserRepository sqlUserRepo) + { + var savedFolders = new List<Folder>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + sut.ClearChangeTracking(); + + folder.UserId = efUser.Id; + var postEfFolder = await sut.CreateAsync(folder); + sut.ClearChangeTracking(); + + var savedFolder = await sut.GetByIdAsync(folder.Id); + savedFolders.Add(savedFolder); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + + folder.UserId = sqlUser.Id; + var sqlFolder = await sqlFolderRepo.CreateAsync(folder); + savedFolders.Add(await sqlFolderRepo.GetByIdAsync(sqlFolder.Id)); + + var distinctItems = savedFolders.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/InstallationRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/InstallationRepositoryTests.cs new file mode 100644 index 0000000000..70f70f3254 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/InstallationRepositoryTests.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.InstallationFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Xunit; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using System.Linq; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class InstallationRepositoryTests + { + [CiSkippedTheory, EfInstallationAutoData] + public async void CreateAsync_Works_DataMatches( + Installation installation, + InstallationCompare equalityComparer, + List<EfRepo.InstallationRepository> suts, + SqlRepo.InstallationRepository sqlInstallationRepo + ) + { + var savedInstallations = new List<Installation>(); + foreach (var sut in suts) + { + var postEfInstallation = await sut.CreateAsync(installation); + sut.ClearChangeTracking(); + + var savedInstallation = await sut.GetByIdAsync(postEfInstallation.Id); + savedInstallations.Add(savedInstallation); + } + + var sqlInstallation = await sqlInstallationRepo.CreateAsync(installation); + var savedSqlInstallation = await sqlInstallationRepo.GetByIdAsync(sqlInstallation.Id); + savedInstallations.Add(savedSqlInstallation); + + var distinctItems = savedInstallations.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/OrganizationRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/OrganizationRepositoryTests.cs new file mode 100644 index 0000000000..7f6a2a19f2 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/OrganizationRepositoryTests.cs @@ -0,0 +1,158 @@ +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.Helpers.Factories; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using System.Collections.Generic; +using System.Linq; +using TableModel = Bit.Core.Models.Table; +using Bit.Core.Models.Data; +using Xunit; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Test.AutoFixture.Attributes; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class OrganizationRepositoryTests + { + [CiSkippedTheory, EfOrganizationAutoData] + public async void CreateAsync_Works_DataMatches( + TableModel.Organization organization, + SqlRepo.OrganizationRepository sqlOrganizationRepo, OrganizationCompare equalityComparer, + List<EfRepo.OrganizationRepository> suts) + { + var savedOrganizations = new List<TableModel.Organization>(); + foreach (var sut in suts) + { + var postEfOrganization = await sut.CreateAsync(organization); + sut.ClearChangeTracking(); + + var savedOrganization = await sut.GetByIdAsync(organization.Id); + savedOrganizations.Add(savedOrganization); + } + + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organization); + savedOrganizations.Add(await sqlOrganizationRepo.GetByIdAsync(sqlOrganization.Id)); + + var distinctItems = savedOrganizations.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfOrganizationAutoData] + public async void ReplaceAsync_Works_DataMatches(TableModel.Organization postOrganization, + TableModel.Organization replaceOrganization, SqlRepo.OrganizationRepository sqlOrganizationRepo, + OrganizationCompare equalityComparer, List<EfRepo.OrganizationRepository> suts) + { + var savedOrganizations = new List<TableModel.Organization>(); + foreach (var sut in suts) + { + var postEfOrganization = await sut.CreateAsync(postOrganization); + sut.ClearChangeTracking(); + + replaceOrganization.Id = postEfOrganization.Id; + await sut.ReplaceAsync(replaceOrganization); + sut.ClearChangeTracking(); + + var replacedOrganization = await sut.GetByIdAsync(replaceOrganization.Id); + savedOrganizations.Add(replacedOrganization); + } + + var postSqlOrganization = await sqlOrganizationRepo.CreateAsync(postOrganization); + replaceOrganization.Id = postSqlOrganization.Id; + await sqlOrganizationRepo.ReplaceAsync(replaceOrganization); + savedOrganizations.Add(await sqlOrganizationRepo.GetByIdAsync(replaceOrganization.Id)); + + var distinctItems = savedOrganizations.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfOrganizationAutoData] + public async void DeleteAsync_Works_DataMatches(TableModel.Organization organization, + SqlRepo.OrganizationRepository sqlOrganizationRepo, OrganizationCompare equalityComparer, + List<EfRepo.OrganizationRepository> suts) + { + foreach (var sut in suts) + { + var postEfOrganization = await sut.CreateAsync(organization); + sut.ClearChangeTracking(); + + var savedEfOrganization = await sut.GetByIdAsync(postEfOrganization.Id); + sut.ClearChangeTracking(); + Assert.True(savedEfOrganization != null); + + await sut.DeleteAsync(savedEfOrganization); + sut.ClearChangeTracking(); + + savedEfOrganization = await sut.GetByIdAsync(savedEfOrganization.Id); + Assert.True(savedEfOrganization == null); + } + + var postSqlOrganization = await sqlOrganizationRepo.CreateAsync(organization); + var savedSqlOrganization = await sqlOrganizationRepo.GetByIdAsync(postSqlOrganization.Id); + Assert.True(savedSqlOrganization != null); + + await sqlOrganizationRepo.DeleteAsync(postSqlOrganization); + savedSqlOrganization = await sqlOrganizationRepo.GetByIdAsync(postSqlOrganization.Id); + Assert.True(savedSqlOrganization == null); + } + + [CiSkippedTheory, EfOrganizationAutoData] + public async void GetByIdentifierAsync_Works_DataMatches(TableModel.Organization organization, + SqlRepo.OrganizationRepository sqlOrganizationRepo, OrganizationCompare equalityComparer, + List<EfRepo.OrganizationRepository> suts) + { + var returnedOrgs = new List<TableModel.Organization>(); + foreach (var sut in suts) + { + var postEfOrg = await sut.CreateAsync(organization); + sut.ClearChangeTracking(); + + var returnedOrg = await sut.GetByIdentifierAsync(postEfOrg.Identifier.ToUpperInvariant()); + returnedOrgs.Add(returnedOrg); + } + + var postSqlOrg = await sqlOrganizationRepo.CreateAsync(organization); + returnedOrgs.Add(await sqlOrganizationRepo.GetByIdentifierAsync(postSqlOrg.Identifier.ToUpperInvariant())); + + var distinctItems = returnedOrgs.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfOrganizationAutoData] + public async void GetManyByEnabledAsync_Works_DataMatches(TableModel.Organization organization, + SqlRepo.OrganizationRepository sqlOrganizationRepo, OrganizationCompare equalityCompare, + List<EfRepo.OrganizationRepository> suts) + { + var returnedOrgs = new List<TableModel.Organization>(); + foreach (var sut in suts) + { + var postEfOrg = await sut.CreateAsync(organization); + sut.ClearChangeTracking(); + + var efReturnedOrgs = await sut.GetManyByEnabledAsync(); + returnedOrgs.Concat(efReturnedOrgs); + } + + var postSqlOrg = await sqlOrganizationRepo.CreateAsync(organization); + returnedOrgs.Concat(await sqlOrganizationRepo.GetManyByEnabledAsync()); + + Assert.True(returnedOrgs.All(o => o.Enabled)); + } + + // testing data matches here would require manipulating all organization abilities in the db + [CiSkippedTheory, EfOrganizationAutoData] + public async void GetManyAbilitiesAsync_Works(TableModel.Organization organization, + SqlRepo.OrganizationRepository sqlOrganizationRepo, OrganizationCompare equalityComparer, + List<EfRepo.OrganizationRepository> suts) + { + var list = new List<OrganizationAbility>(); + foreach (var sut in suts) + { + list.Concat(await sut.GetManyAbilitiesAsync()); + } + + list.Concat(await sqlOrganizationRepo.GetManyAbilitiesAsync()); + Assert.True(list.All(x => x.GetType() == typeof(OrganizationAbility))); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/OrganizationUserRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/OrganizationUserRepositoryTests.cs new file mode 100644 index 0000000000..0d6b44bb2e --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/OrganizationUserRepositoryTests.cs @@ -0,0 +1,152 @@ +using Bit.Core.Test.AutoFixture.OrganizationUserFixtures; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using System.Collections.Generic; +using System.Linq; +using TableModel = Bit.Core.Models.Table; +using Xunit; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Bit.Core.Models.Data; +using System; +using Bit.Core.Test.AutoFixture.Attributes; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class OrganizationUserRepositoryTests + { + [CiSkippedTheory, EfOrganizationUserAutoData] + public async void CreateAsync_Works_DataMatches(TableModel.OrganizationUser orgUser, TableModel.User user, TableModel.Organization org, + OrganizationUserCompare equalityComparer, List<EfRepo.OrganizationUserRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos, List<EfRepo.UserRepository> efUserRepos, + SqlRepo.OrganizationUserRepository sqlOrgUserRepo, SqlRepo.UserRepository sqlUserRepo, + SqlRepo.OrganizationRepository sqlOrgRepo) + { + var savedOrgUsers = new List<TableModel.OrganizationUser>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + var postEfUser = await efUserRepos[i].CreateAsync(user); + var postEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + orgUser.UserId = postEfUser.Id; + orgUser.OrganizationId = postEfOrg.Id; + var postEfOrgUser = await sut.CreateAsync(orgUser); + sut.ClearChangeTracking(); + + var savedOrgUser = await sut.GetByIdAsync(postEfOrgUser.Id); + savedOrgUsers.Add(savedOrgUser); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var postSqlOrg = await sqlOrgRepo.CreateAsync(org); + + orgUser.UserId = postSqlUser.Id; + orgUser.OrganizationId = postSqlOrg.Id; + var sqlOrgUser = await sqlOrgUserRepo.CreateAsync(orgUser); + + var savedSqlOrgUser = await sqlOrgUserRepo.GetByIdAsync(sqlOrgUser.Id); + savedOrgUsers.Add(savedSqlOrgUser); + + var distinctItems = savedOrgUsers.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfOrganizationUserAutoData] + public async void ReplaceAsync_Works_DataMatches( + TableModel.OrganizationUser postOrgUser, + TableModel.OrganizationUser replaceOrgUser, + TableModel.User user, + TableModel.Organization org, + OrganizationUserCompare equalityComparer, + List<EfRepo.OrganizationUserRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + List<EfRepo.OrganizationRepository> efOrgRepos, + SqlRepo.OrganizationUserRepository sqlOrgUserRepo, + SqlRepo.UserRepository sqlUserRepo, + SqlRepo.OrganizationRepository sqlOrgRepo + ) + { + var savedOrgUsers = new List<TableModel.OrganizationUser>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + var postEfUser = await efUserRepos[i].CreateAsync(user); + var postEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + postOrgUser.UserId = replaceOrgUser.UserId = postEfUser.Id; + postOrgUser.OrganizationId = replaceOrgUser.OrganizationId = postEfOrg.Id; + var postEfOrgUser = await sut.CreateAsync(postOrgUser); + sut.ClearChangeTracking(); + + replaceOrgUser.Id = postOrgUser.Id; + await sut.ReplaceAsync(replaceOrgUser); + sut.ClearChangeTracking(); + + var replacedOrganizationUser = await sut.GetByIdAsync(replaceOrgUser.Id); + savedOrgUsers.Add(replacedOrganizationUser); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var postSqlOrg = await sqlOrgRepo.CreateAsync(org); + + postOrgUser.UserId = replaceOrgUser.UserId = postSqlUser.Id; + postOrgUser.OrganizationId = replaceOrgUser.OrganizationId = postSqlOrg.Id; + var postSqlOrgUser = await sqlOrgUserRepo.CreateAsync(postOrgUser); + + replaceOrgUser.Id = postSqlOrgUser.Id; + await sqlOrgUserRepo.ReplaceAsync(replaceOrgUser); + + var replacedSqlUser = await sqlOrgUserRepo.GetByIdAsync(replaceOrgUser.Id); + + var distinctItems = savedOrgUsers.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfOrganizationUserAutoData] + public async void DeleteAsync_Works_DataMatches(TableModel.OrganizationUser orgUser, TableModel.User user, TableModel.Organization org, + OrganizationUserCompare equalityComparer, List<EfRepo.OrganizationUserRepository> suts, + List<EfRepo.UserRepository> efUserRepos, List<EfRepo.OrganizationRepository> efOrgRepos, + SqlRepo.OrganizationUserRepository sqlOrgUserRepo, SqlRepo.UserRepository sqlUserRepo, + SqlRepo.OrganizationRepository sqlOrgRepo) + { + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + var postEfUser = await efUserRepos[i].CreateAsync(user); + var postEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + orgUser.UserId = postEfUser.Id; + orgUser.OrganizationId = postEfOrg.Id; + var postEfOrgUser = await sut.CreateAsync(orgUser); + sut.ClearChangeTracking(); + + var savedEfOrgUser = await sut.GetByIdAsync(postEfOrgUser.Id); + Assert.True(savedEfOrgUser != null); + sut.ClearChangeTracking(); + + await sut.DeleteAsync(savedEfOrgUser); + sut.ClearChangeTracking(); + + savedEfOrgUser = await sut.GetByIdAsync(savedEfOrgUser.Id); + Assert.True(savedEfOrgUser == null); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var postSqlOrg = await sqlOrgRepo.CreateAsync(org); + + orgUser.UserId = postSqlUser.Id; + orgUser.OrganizationId = postSqlOrg.Id; + var postSqlOrgUser = await sqlOrgUserRepo.CreateAsync(orgUser); + + var savedSqlOrgUser = await sqlOrgUserRepo.GetByIdAsync(postSqlOrgUser.Id); + Assert.True(savedSqlOrgUser != null); + + await sqlOrgUserRepo.DeleteAsync(postSqlOrgUser); + savedSqlOrgUser = await sqlOrgUserRepo.GetByIdAsync(postSqlOrgUser.Id); + Assert.True(savedSqlOrgUser == null); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/PolicyRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/PolicyRepositoryTests.cs new file mode 100644 index 0000000000..b6fe5b83b5 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/PolicyRepositoryTests.cs @@ -0,0 +1,56 @@ +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.PolicyFixtures; +using Microsoft.EntityFrameworkCore; +using Xunit; +using TableModel = Bit.Core.Models.Table; +using System.Linq; +using System.Collections.Generic; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class PolicyRepositoryTests + { + [CiSkippedTheory, EfPolicyAutoData] + public async void CreateAsync_Works_DataMatches( + TableModel.Policy policy, + TableModel.Organization organization, + PolicyCompare equalityComparer, + List<EfRepo.PolicyRepository> suts, + List<EfRepo.OrganizationRepository> efOrganizationRepos, + SqlRepo.PolicyRepository sqlPolicyRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo + ) + { + var savedPolicys = new List<TableModel.Policy>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efOrganization = await efOrganizationRepos[i].CreateAsync(organization); + sut.ClearChangeTracking(); + + policy.OrganizationId = efOrganization.Id; + var postEfPolicy = await sut.CreateAsync(policy); + sut.ClearChangeTracking(); + + var savedPolicy = await sut.GetByIdAsync(postEfPolicy.Id); + savedPolicys.Add(savedPolicy); + } + + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organization); + + policy.OrganizationId = sqlOrganization.Id; + var sqlPolicy = await sqlPolicyRepo.CreateAsync(policy); + var savedSqlPolicy = await sqlPolicyRepo.GetByIdAsync(sqlPolicy.Id); + savedPolicys.Add(savedSqlPolicy); + + var distinctItems = savedPolicys.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/SendRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/SendRepositoryTests.cs new file mode 100644 index 0000000000..5f3e9fec61 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/SendRepositoryTests.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using Bit.Core.Models.Table; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.SendFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Xunit; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using System.Linq; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class SendRepositoryTests + { + [CiSkippedTheory, EfUserSendAutoData, EfOrganizationSendAutoData] + public async void CreateAsync_Works_DataMatches( + Send send, + User user, + Organization org, + SendCompare equalityComparer, + List<EfRepo.SendRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + List<EfRepo.OrganizationRepository> efOrgRepos, + SqlRepo.SendRepository sqlSendRepo, + SqlRepo.UserRepository sqlUserRepo, + SqlRepo.OrganizationRepository sqlOrgRepo + ) + { + var savedSends = new List<Send>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + if (send.OrganizationId.HasValue) + { + var efOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + send.OrganizationId = efOrg.Id; + } + var efUser = await efUserRepos[i].CreateAsync(user); + sut.ClearChangeTracking(); + + send.UserId = efUser.Id; + var postEfSend = await sut.CreateAsync(send); + sut.ClearChangeTracking(); + + var savedSend = await sut.GetByIdAsync(postEfSend.Id); + savedSends.Add(savedSend); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + if (send.OrganizationId.HasValue) + { + var sqlOrg = await sqlOrgRepo.CreateAsync(org); + send.OrganizationId = sqlOrg.Id; + } + + send.UserId = sqlUser.Id; + var sqlSend = await sqlSendRepo.CreateAsync(send); + var savedSqlSend = await sqlSendRepo.GetByIdAsync(sqlSend.Id); + savedSends.Add(savedSqlSend); + + var distinctItems = savedSends.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/SsoConfigRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/SsoConfigRepositoryTests.cs new file mode 100644 index 0000000000..584ac56b67 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/SsoConfigRepositoryTests.cs @@ -0,0 +1,233 @@ +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.Helpers.Factories; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Models.Table; +using Xunit; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Bit.Core.Test.AutoFixture.SsoConfigFixtures; +using System; +using Bit.Core.Test.AutoFixture.Attributes; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class SsoConfigRepositoryTests + { + [CiSkippedTheory, EfSsoConfigAutoData] + public async void CreateAsync_Works_DataMatches(SsoConfig ssoConfig, Organization org, + SsoConfigCompare equalityComparer, List<EfRepo.SsoConfigRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos, SqlRepo.SsoConfigRepository sqlSsoConfigRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo) + { + var savedSsoConfigs = new List<SsoConfig>(); + + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoConfig.OrganizationId = savedEfOrg.Id; + var postEfSsoConfig = await sut.CreateAsync(ssoConfig); + sut.ClearChangeTracking(); + + var savedEfSsoConfig = await sut.GetByIdAsync(ssoConfig.Id); + Assert.True(savedEfSsoConfig != null); + savedSsoConfigs.Add(savedEfSsoConfig); + } + + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(org); + ssoConfig.OrganizationId = sqlOrganization.Id; + + var sqlSsoConfig = await sqlSsoConfigRepo.CreateAsync(ssoConfig); + var savedSqlSsoConfig = await sqlSsoConfigRepo.GetByIdAsync(sqlSsoConfig.Id); + Assert.True(savedSqlSsoConfig != null); + savedSsoConfigs.Add(savedSqlSsoConfig); + + var distinctItems = savedSsoConfigs.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfSsoConfigAutoData] + public async void ReplaceAsync_Works_DataMatches(SsoConfig postSsoConfig, SsoConfig replaceSsoConfig, + Organization org, SsoConfigCompare equalityComparer, List<EfRepo.SsoConfigRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos, SqlRepo.SsoConfigRepository sqlSsoConfigRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo) + { + var savedSsoConfigs = new List<SsoConfig>(); + + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + postSsoConfig.OrganizationId = replaceSsoConfig.OrganizationId = savedEfOrg.Id; + var postEfSsoConfig = await sut.CreateAsync(postSsoConfig); + sut.ClearChangeTracking(); + + replaceSsoConfig.Id = postEfSsoConfig.Id; + savedSsoConfigs.Add(postEfSsoConfig); + await sut.ReplaceAsync(replaceSsoConfig); + sut.ClearChangeTracking(); + + var replacedSsoConfig = await sut.GetByIdAsync(replaceSsoConfig.Id); + Assert.True(replacedSsoConfig != null); + savedSsoConfigs.Add(replacedSsoConfig); + } + + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(org); + postSsoConfig.OrganizationId = sqlOrganization.Id; + + var postSqlSsoConfig = await sqlSsoConfigRepo.CreateAsync(postSsoConfig); + replaceSsoConfig.Id = postSqlSsoConfig.Id; + savedSsoConfigs.Add(postSqlSsoConfig); + + await sqlSsoConfigRepo.ReplaceAsync(replaceSsoConfig); + var replacedSqlSsoConfig = await sqlSsoConfigRepo.GetByIdAsync(replaceSsoConfig.Id); + Assert.True(replacedSqlSsoConfig != null); + savedSsoConfigs.Add(replacedSqlSsoConfig); + + var distinctItems = savedSsoConfigs.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(2).Any()); + } + + [CiSkippedTheory, EfSsoConfigAutoData] + public async void DeleteAsync_Works_DataMatches(SsoConfig ssoConfig, Organization org, + SsoConfigCompare equalityComparer, List<EfRepo.SsoConfigRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos, SqlRepo.SsoConfigRepository sqlSsoConfigRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo) + { + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoConfig.OrganizationId = savedEfOrg.Id; + var postEfSsoConfig = await sut.CreateAsync(ssoConfig); + sut.ClearChangeTracking(); + + var savedEfSsoConfig = await sut.GetByIdAsync(postEfSsoConfig.Id); + Assert.True(savedEfSsoConfig != null); + sut.ClearChangeTracking(); + + await sut.DeleteAsync(savedEfSsoConfig); + var deletedEfSsoConfig= await sut.GetByIdAsync(savedEfSsoConfig.Id); + Assert.True(deletedEfSsoConfig == null); + } + + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(org); + ssoConfig.OrganizationId = sqlOrganization.Id; + + var postSqlSsoConfig = await sqlSsoConfigRepo.CreateAsync(ssoConfig); + var savedSqlSsoConfig = await sqlSsoConfigRepo.GetByIdAsync(postSqlSsoConfig.Id); + Assert.True(savedSqlSsoConfig != null); + + await sqlSsoConfigRepo.DeleteAsync(savedSqlSsoConfig); + savedSqlSsoConfig = await sqlSsoConfigRepo.GetByIdAsync(postSqlSsoConfig.Id); + Assert.True(savedSqlSsoConfig == null); + } + + [CiSkippedTheory, EfSsoConfigAutoData] + public async void GetByOrganizationIdAsync_Works_DataMatches(SsoConfig ssoConfig, Organization org, + SsoConfigCompare equalityComparer, List<EfRepo.SsoConfigRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos, SqlRepo.SsoConfigRepository sqlSsoConfigRepo, + SqlRepo.OrganizationRepository sqlOrgRepo) + { + var returnedList = new List<SsoConfig>(); + + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoConfig.OrganizationId = savedEfOrg.Id; + await sut.CreateAsync(ssoConfig); + sut.ClearChangeTracking(); + + var savedEfUser = await sut.GetByOrganizationIdAsync(savedEfOrg.Id); + Assert.True(savedEfUser != null); + returnedList.Add(savedEfUser); + } + + var savedSqlOrg = await sqlOrgRepo.CreateAsync(org); + ssoConfig.OrganizationId = savedSqlOrg.Id; + + var postSqlSsoConfig = await sqlSsoConfigRepo.CreateAsync(ssoConfig); + + var savedSqlSsoConfig = await sqlSsoConfigRepo.GetByOrganizationIdAsync(ssoConfig.OrganizationId); + Assert.True(savedSqlSsoConfig != null); + returnedList.Add(savedSqlSsoConfig); + + var distinctItems = returnedList.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfSsoConfigAutoData] + public async void GetByIdentifierAsync_Works_DataMatches(SsoConfig ssoConfig, Organization org, + SsoConfigCompare equalityComparer, List<EfRepo.SsoConfigRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos, SqlRepo.SsoConfigRepository sqlSsoConfigRepo, + SqlRepo.OrganizationRepository sqlOrgRepo) + { + var returnedList = new List<SsoConfig>(); + + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoConfig.OrganizationId = savedEfOrg.Id; + await sut.CreateAsync(ssoConfig); + sut.ClearChangeTracking(); + + var savedEfSsoConfig = await sut.GetByIdentifierAsync(org.Identifier); + Assert.True(savedEfSsoConfig != null); + returnedList.Add(savedEfSsoConfig); + } + + var savedSqlOrg = await sqlOrgRepo.CreateAsync(org); + ssoConfig.OrganizationId = savedSqlOrg.Id; + + var postSqlSsoConfig = await sqlSsoConfigRepo.CreateAsync(ssoConfig); + + var savedSqlSsoConfig = await sqlSsoConfigRepo.GetByIdentifierAsync(org.Identifier); + Assert.True(savedSqlSsoConfig != null); + returnedList.Add(savedSqlSsoConfig); + + var distinctItems = returnedList.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + // Testing that data matches here would involve manipulating all SsoConfig records in the db + [CiSkippedTheory, EfSsoConfigAutoData] + public async void GetManyByRevisionNotBeforeDate_Works(SsoConfig ssoConfig, DateTime notBeforeDate, + Organization org, SsoConfigCompare equalityComparer, List<EfRepo.SsoConfigRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos) + { + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoConfig.OrganizationId = savedEfOrg.Id; + await sut.CreateAsync(ssoConfig); + sut.ClearChangeTracking(); + + var returnedEfSsoConfigs = await sut.GetManyByRevisionNotBeforeDate(notBeforeDate); + Assert.True(returnedEfSsoConfigs.All(sc => sc.RevisionDate >= notBeforeDate)); + } + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/SsoUserRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/SsoUserRepositoryTests.cs new file mode 100644 index 0000000000..3e772ab5e9 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/SsoUserRepositoryTests.cs @@ -0,0 +1,192 @@ +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.Helpers.Factories; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Models.Table; +using Xunit; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Bit.Core.Test.AutoFixture.SsoUserFixtures; +using Bit.Core.Test.AutoFixture.Attributes; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class SsoUserRepositoryTests + { + [CiSkippedTheory, EfSsoUserAutoData] + public async void CreateAsync_Works_DataMatches(SsoUser ssoUser, User user, Organization org, + SsoUserCompare equalityComparer, List<EfRepo.SsoUserRepository> suts, + List<EfRepo.OrganizationRepository> efOrgRepos, List<EfRepo.UserRepository> efUserRepos, + SqlRepo.SsoUserRepository sqlSsoUserRepo, SqlRepo.OrganizationRepository sqlOrgRepo, + SqlRepo.UserRepository sqlUserRepo) + { + var createdSsoUsers = new List<SsoUser>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + var efOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoUser.UserId = efUser.Id; + ssoUser.OrganizationId = efOrg.Id; + var postEfSsoUser = await sut.CreateAsync(ssoUser); + sut.ClearChangeTracking(); + + var savedSsoUser = await sut.GetByIdAsync(ssoUser.Id); + createdSsoUsers.Add(savedSsoUser); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + var sqlOrganization = await sqlOrgRepo.CreateAsync(org); + + ssoUser.UserId = sqlUser.Id; + ssoUser.OrganizationId = sqlOrganization.Id; + var sqlSsoUser = await sqlSsoUserRepo.CreateAsync(ssoUser); + + createdSsoUsers.Add(await sqlSsoUserRepo.GetByIdAsync(sqlSsoUser.Id)); + + var distinctSsoUsers = createdSsoUsers.Distinct(equalityComparer); + Assert.True(!distinctSsoUsers.Skip(1).Any()); + } + + [CiSkippedTheory, EfSsoUserAutoData] + public async void ReplaceAsync_Works_DataMatches(SsoUser postSsoUser, SsoUser replaceSsoUser, + Organization org, User user, SsoUserCompare equalityComparer, + List<EfRepo.SsoUserRepository> suts, List<EfRepo.UserRepository> efUserRepos, + List<EfRepo.OrganizationRepository> efOrgRepos, SqlRepo.SsoUserRepository sqlSsoUserRepo, + SqlRepo.OrganizationRepository sqlOrgRepo, SqlRepo.UserRepository sqlUserRepo) + { + var savedSsoUsers = new List<SsoUser>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + var efOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + postSsoUser.UserId = efUser.Id; + postSsoUser.OrganizationId = efOrg.Id; + var postEfSsoUser = await sut.CreateAsync(postSsoUser); + sut.ClearChangeTracking(); + + replaceSsoUser.Id = postEfSsoUser.Id; + replaceSsoUser.UserId = postEfSsoUser.UserId; + replaceSsoUser.OrganizationId = postEfSsoUser.OrganizationId; + await sut.ReplaceAsync(replaceSsoUser); + sut.ClearChangeTracking(); + + var replacedSsoUser = await sut.GetByIdAsync(replaceSsoUser.Id); + savedSsoUsers.Add(replacedSsoUser); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + var sqlOrganization = await sqlOrgRepo.CreateAsync(org); + + postSsoUser.UserId = sqlUser.Id; + postSsoUser.OrganizationId = sqlOrganization.Id; + var postSqlSsoUser = await sqlSsoUserRepo.CreateAsync(postSsoUser); + + replaceSsoUser.Id = postSqlSsoUser.Id; + replaceSsoUser.UserId = postSqlSsoUser.UserId; + replaceSsoUser.OrganizationId = postSqlSsoUser.OrganizationId; + await sqlSsoUserRepo.ReplaceAsync(replaceSsoUser); + + savedSsoUsers.Add(await sqlSsoUserRepo.GetByIdAsync(replaceSsoUser.Id)); + + var distinctItems = savedSsoUsers.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfSsoUserAutoData] + public async void DeleteAsync_Works_DataMatches(SsoUser ssoUser, Organization org, User user, + SsoUserCompare equalityComparer, List<EfRepo.SsoUserRepository> suts, + List<EfRepo.UserRepository> efUserRepos, List<EfRepo.OrganizationRepository> efOrgRepos, + SqlRepo.SsoUserRepository sqlSsoUserRepo, SqlRepo.UserRepository sqlUserRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo) + { + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfUser = await efUserRepos[i].CreateAsync(user); + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoUser.UserId = savedEfUser.Id; + ssoUser.OrganizationId = savedEfOrg.Id; + var postEfSsoUser = await sut.CreateAsync(ssoUser); + sut.ClearChangeTracking(); + + var savedEfSsoUser = await sut.GetByIdAsync(postEfSsoUser.Id); + Assert.True(savedEfSsoUser != null); + sut.ClearChangeTracking(); + + await sut.DeleteAsync(savedEfSsoUser); + savedEfSsoUser = await sut.GetByIdAsync(savedEfSsoUser.Id); + Assert.True(savedEfSsoUser == null); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(org); + ssoUser.UserId = sqlUser.Id; + ssoUser.OrganizationId = sqlOrganization.Id; + + var postSqlSsoUser = await sqlSsoUserRepo.CreateAsync(ssoUser); + var savedSqlSsoUser = await sqlSsoUserRepo.GetByIdAsync(postSqlSsoUser.Id); + Assert.True(savedSqlSsoUser != null); + + await sqlSsoUserRepo.DeleteAsync(savedSqlSsoUser); + savedSqlSsoUser = await sqlSsoUserRepo.GetByIdAsync(postSqlSsoUser.Id); + Assert.True(savedSqlSsoUser == null); + } + + [CiSkippedTheory, EfSsoUserAutoData] + public async void DeleteAsync_UserIdOrganizationId_Works_DataMatches(SsoUser ssoUser, + User user, Organization org, SsoUserCompare equalityComparer, List<EfRepo.SsoUserRepository> suts, + List<EfRepo.UserRepository> efUserRepos, List<EfRepo.OrganizationRepository> efOrgRepos, + SqlRepo.SsoUserRepository sqlSsoUserRepo, SqlRepo.UserRepository sqlUserRepo, SqlRepo.OrganizationRepository sqlOrgRepo + ) + { + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var savedEfUser = await efUserRepos[i].CreateAsync(user); + var savedEfOrg = await efOrgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoUser.UserId = savedEfUser.Id; + ssoUser.OrganizationId = savedEfOrg.Id; + var postEfSsoUser = await sut.CreateAsync(ssoUser); + sut.ClearChangeTracking(); + + var savedEfSsoUser = await sut.GetByIdAsync(postEfSsoUser.Id); + Assert.True(savedEfSsoUser != null); + sut.ClearChangeTracking(); + + await sut.DeleteAsync(savedEfSsoUser.UserId, savedEfSsoUser.OrganizationId); + sut.ClearChangeTracking(); + + savedEfSsoUser = await sut.GetByIdAsync(savedEfSsoUser.Id); + Assert.True(savedEfSsoUser == null); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + var sqlOrganization = await sqlOrgRepo.CreateAsync(org); + ssoUser.UserId = sqlUser.Id; + ssoUser.OrganizationId = sqlOrganization.Id; + + var postSqlSsoUser = await sqlSsoUserRepo.CreateAsync(ssoUser); + var savedSqlSsoUser = await sqlSsoUserRepo.GetByIdAsync(postSqlSsoUser.Id); + Assert.True(savedSqlSsoUser != null); + + await sqlSsoUserRepo.DeleteAsync(savedSqlSsoUser.UserId, savedSqlSsoUser.OrganizationId); + savedSqlSsoUser = await sqlSsoUserRepo.GetByIdAsync(postSqlSsoUser.Id); + Assert.True(savedSqlSsoUser == null); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/TaxRateRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/TaxRateRepositoryTests.cs new file mode 100644 index 0000000000..594c6e2dcb --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/TaxRateRepositoryTests.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Models.Table; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.TaxRateFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Xunit; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class TaxRateRepositoryTests + { + [CiSkippedTheory, EfTaxRateAutoData] + public async void CreateAsync_Works_DataMatches( + TaxRate taxRate, + TaxRateCompare equalityComparer, + List<EfRepo.TaxRateRepository> suts, + SqlRepo.TaxRateRepository sqlTaxRateRepo + ) + { + var savedTaxRates = new List<TaxRate>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + var postEfTaxRate = await sut.CreateAsync(taxRate); + sut.ClearChangeTracking(); + + var savedTaxRate = await sut.GetByIdAsync(postEfTaxRate.Id); + savedTaxRates.Add(savedTaxRate); + } + + var sqlTaxRate = await sqlTaxRateRepo.CreateAsync(taxRate); + var savedSqlTaxRate = await sqlTaxRateRepo.GetByIdAsync(sqlTaxRate.Id); + savedTaxRates.Add(savedSqlTaxRate); + + var distinctItems = savedTaxRates.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/TransactionRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/TransactionRepositoryTests.cs new file mode 100644 index 0000000000..43458eac59 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/TransactionRepositoryTests.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.TransactionFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Microsoft.EntityFrameworkCore; +using Xunit; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using System.Linq; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class TransactionRepositoryTests + { + + [CiSkippedTheory, EfUserTransactionAutoData, EfOrganizationTransactionAutoData] + public async void CreateAsync_Works_DataMatches( + Transaction transaction, + User user, + Organization org, + TransactionCompare equalityComparer, + List<EfRepo.TransactionRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + List<EfRepo.OrganizationRepository> efOrgRepos, + SqlRepo.TransactionRepository sqlTransactionRepo, + SqlRepo.UserRepository sqlUserRepo, + SqlRepo.OrganizationRepository sqlOrgRepo + ) + { + var savedTransactions = new List<Transaction>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + var efUser = await efUserRepos[i].CreateAsync(user); + if (transaction.OrganizationId.HasValue) + { + var efOrg = await efOrgRepos[i].CreateAsync(org); + transaction.OrganizationId = efOrg.Id; + } + sut.ClearChangeTracking(); + + transaction.UserId = efUser.Id; + var postEfTransaction = await sut.CreateAsync(transaction); + sut.ClearChangeTracking(); + + var savedTransaction = await sut.GetByIdAsync(postEfTransaction.Id); + savedTransactions.Add(savedTransaction); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + if (transaction.OrganizationId.HasValue) + { + var sqlOrg = await sqlOrgRepo.CreateAsync(org); + transaction.OrganizationId = sqlOrg.Id; + } + + transaction.UserId = sqlUser.Id; + var sqlTransaction = await sqlTransactionRepo.CreateAsync(transaction); + var savedSqlTransaction = await sqlTransactionRepo.GetByIdAsync(sqlTransaction.Id); + savedTransactions.Add(savedSqlTransaction); + + var distinctItems = savedTransactions.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/U2fRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/U2fRepositoryTests.cs new file mode 100644 index 0000000000..906438827d --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/U2fRepositoryTests.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Models.Table; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.U2fFixtures; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Microsoft.EntityFrameworkCore; +using Xunit; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class U2fRepositoryTests + { + + [CiSkippedTheory, EfU2fAutoData] + public async void CreateAsync_Works_DataMatches( + U2f u2f, + User user, + U2fCompare equalityComparer, + List<EfRepo.U2fRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + SqlRepo.U2fRepository sqlU2fRepo, + SqlRepo.UserRepository sqlUserRepo + ) + { + var savedU2fs = new List<U2f>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + sut.ClearChangeTracking(); + + u2f.UserId = efUser.Id; + var postEfU2f = await sut.CreateAsync(u2f); + sut.ClearChangeTracking(); + + var savedU2f = await sut.GetByIdAsync(postEfU2f.Id); + savedU2fs.Add(savedU2f); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + + u2f.UserId = sqlUser.Id; + var sqlU2f = await sqlU2fRepo.CreateAsync(u2f); + var savedSqlU2f = await sqlU2fRepo.GetByIdAsync(sqlU2f.Id); + savedU2fs.Add(savedSqlU2f); + + var distinctItems = savedU2fs.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Repositories/EntityFramework/UserRepositoryTests.cs b/test/Core.Test/Repositories/EntityFramework/UserRepositoryTests.cs new file mode 100644 index 0000000000..8b4623f527 --- /dev/null +++ b/test/Core.Test/Repositories/EntityFramework/UserRepositoryTests.cs @@ -0,0 +1,294 @@ +using Bit.Core.Test.AutoFixture.UserFixtures; +using EfRepo = Bit.Core.Repositories.EntityFramework; +using SqlRepo = Bit.Core.Repositories.SqlServer; +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Models.Table; +using Xunit; +using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers; +using Bit.Core.Models.Data; +using System; +using Bit.Core.Test.AutoFixture.Attributes; + +namespace Bit.Core.Test.Repositories.EntityFramework +{ + public class UserRepositoryTests + { + [CiSkippedTheory, EfUserAutoData] + public async void CreateAsync_Works_DataMatches( + User user, UserCompare equalityComparer, + List<EfRepo.UserRepository> suts, + SqlRepo.UserRepository sqlUserRepo + ) + { + var savedUsers = new List<User>(); + + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + + sut.ClearChangeTracking(); + + var savedUser = await sut.GetByIdAsync(postEfUser.Id); + savedUsers.Add(savedUser); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + savedUsers.Add(await sqlUserRepo.GetByIdAsync(sqlUser.Id)); + + var distinctItems = savedUsers.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfUserAutoData] + public async void ReplaceAsync_Works_DataMatches(User postUser, User replaceUser, + UserCompare equalityComparer, List<EfRepo.UserRepository> suts, + SqlRepo.UserRepository sqlUserRepo) + { + var savedUsers = new List<User>(); + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(postUser); + replaceUser.Id = postEfUser.Id; + await sut.ReplaceAsync(replaceUser); + var replacedUser = await sut.GetByIdAsync(replaceUser.Id); + savedUsers.Add(replacedUser); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(postUser); + replaceUser.Id = postSqlUser.Id; + await sqlUserRepo.ReplaceAsync(replaceUser); + savedUsers.Add(await sqlUserRepo.GetByIdAsync(replaceUser.Id)); + + var distinctItems = savedUsers.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfUserAutoData] + public async void DeleteAsync_Works_DataMatches(User user, UserCompare equalityComparer, + List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo) + { + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + + var savedEfUser = await sut.GetByIdAsync(postEfUser.Id); + Assert.True(savedEfUser != null); + sut.ClearChangeTracking(); + + await sut.DeleteAsync(savedEfUser); + sut.ClearChangeTracking(); + + savedEfUser = await sut.GetByIdAsync(savedEfUser.Id); + Assert.True(savedEfUser == null); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var savedSqlUser = await sqlUserRepo.GetByIdAsync(postSqlUser.Id); + Assert.True(savedSqlUser != null); + + await sqlUserRepo.DeleteAsync(postSqlUser); + savedSqlUser = await sqlUserRepo.GetByIdAsync(postSqlUser.Id); + Assert.True(savedSqlUser == null); + } + + [CiSkippedTheory, EfUserAutoData] + public async void GetByEmailAsync_Works_DataMatches(User user, UserCompare equalityComparer, + List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo) + { + var savedUsers = new List<User>(); + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + var savedUser = await sut.GetByEmailAsync(postEfUser.Email.ToUpperInvariant()); + savedUsers.Add(savedUser); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + savedUsers.Add(await sqlUserRepo.GetByEmailAsync(postSqlUser.Email.ToUpperInvariant())); + + var distinctItems = savedUsers.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfUserAutoData] + public async void GetKdfInformationByEmailAsync_Works_DataMatches(User user, + UserKdfInformationCompare equalityComparer, List<EfRepo.UserRepository> suts, + SqlRepo.UserRepository sqlUserRepo) + { + var savedKdfInformation = new List<UserKdfInformation>(); + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + var kdfInformation = await sut.GetKdfInformationByEmailAsync(postEfUser.Email.ToUpperInvariant()); + savedKdfInformation.Add(kdfInformation); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var sqlKdfInformation = await sqlUserRepo.GetKdfInformationByEmailAsync(postSqlUser.Email); + savedKdfInformation.Add(sqlKdfInformation); + + var distinctItems = savedKdfInformation.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfUserAutoData] + public async void SearchAsync_Works_DataMatches(User user, int skip, int take, + UserCompare equalityCompare, List<EfRepo.UserRepository> suts, + SqlRepo.UserRepository sqlUserRepo) + { + var searchedEfUsers = new List<User>(); + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + + var searchedEfUsersCollection = await sut.SearchAsync(postEfUser.Email.ToUpperInvariant(), skip, take); + searchedEfUsers.Concat(searchedEfUsersCollection.ToList()); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var searchedSqlUsers = await sqlUserRepo.SearchAsync(postSqlUser.Email.ToUpperInvariant(), skip, take); + + var distinctItems = searchedEfUsers.Concat(searchedSqlUsers).Distinct(equalityCompare); + Assert.True(!distinctItems.Skip(1).Any()); + } + + [CiSkippedTheory, EfUserAutoData] + public async void GetManyByPremiumAsync_Works_DataMatches(User user, + List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo) + { + var returnedUsers = new List<User>(); + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + + var searchedEfUsers = await sut.GetManyByPremiumAsync(user.Premium); + returnedUsers.Concat(searchedEfUsers.ToList()); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var searchedSqlUsers = await sqlUserRepo.GetManyByPremiumAsync(user.Premium); + returnedUsers.Concat(searchedSqlUsers.ToList()); + + Assert.True(returnedUsers.All(x => x.Premium == user.Premium)); + } + + [CiSkippedTheory, EfUserAutoData] + public async void GetPublicKeyAsync_Works_DataMatches(User user, List<EfRepo.UserRepository> suts, + SqlRepo.UserRepository sqlUserRepo) + { + var returnedKeys = new List<string>(); + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + + var efKey = await sut.GetPublicKeyAsync(postEfUser.Id); + returnedKeys.Add(efKey); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var sqlKey = await sqlUserRepo.GetPublicKeyAsync(postSqlUser.Id); + returnedKeys.Add(sqlKey); + + Assert.True(!returnedKeys.Distinct().Skip(1).Any()); + } + + [CiSkippedTheory, EfUserAutoData] + public async void GetAccountRevisionDateAsync(User user, List<EfRepo.UserRepository> suts, + SqlRepo.UserRepository sqlUserRepo) + { + var returnedKeys = new List<string>(); + foreach (var sut in suts) + { + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + + var efKey = await sut.GetPublicKeyAsync(postEfUser.Id); + returnedKeys.Add(efKey); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var sqlKey = await sqlUserRepo.GetPublicKeyAsync(postSqlUser.Id); + returnedKeys.Add(sqlKey); + + Assert.True(!returnedKeys.Distinct().Skip(1).Any()); + } + + [CiSkippedTheory, EfUserAutoData] + public async void UpdateRenewalReminderDateAsync_Works_DataMatches(User user, + DateTime updatedReminderDate, List<EfRepo.UserRepository> suts, + SqlRepo.UserRepository sqlUserRepo) + { + var savedDates = new List<DateTime?>(); + foreach (var sut in suts) + { + var postEfUser = user; + postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + + await sut.UpdateRenewalReminderDateAsync(postEfUser.Id, updatedReminderDate); + sut.ClearChangeTracking(); + + var replacedUser = await sut.GetByIdAsync(postEfUser.Id); + savedDates.Add(replacedUser.RenewalReminderDate); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + await sqlUserRepo.UpdateRenewalReminderDateAsync(postSqlUser.Id, updatedReminderDate); + var replacedSqlUser = await sqlUserRepo.GetByIdAsync(postSqlUser.Id); + savedDates.Add(replacedSqlUser.RenewalReminderDate); + + var distinctItems = savedDates.GroupBy(e => e.ToString()); + Assert.True(!distinctItems.Skip(1).Any() && + savedDates.All(e => e.ToString() == updatedReminderDate.ToString())); + } + + [CiSkippedTheory, EfUserAutoData] + public async void GetBySsoUserAsync_Works_DataMatches(User user, Organization org, + SsoUser ssoUser, UserCompare equalityComparer, List<EfRepo.UserRepository> suts, + List<EfRepo.SsoUserRepository> ssoUserRepos, List<EfRepo.OrganizationRepository> orgRepos, + SqlRepo.UserRepository sqlUserRepo, SqlRepo.SsoUserRepository sqlSsoUserRepo, + SqlRepo.OrganizationRepository sqlOrgRepo) + { + var returnedList = new List<User>(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var postEfUser = await sut.CreateAsync(user); + sut.ClearChangeTracking(); + + var efOrg = await orgRepos[i].CreateAsync(org); + sut.ClearChangeTracking(); + + ssoUser.UserId = postEfUser.Id; + ssoUser.OrganizationId = efOrg.Id; + var postEfSsoUser = await ssoUserRepos[i].CreateAsync(ssoUser); + sut.ClearChangeTracking(); + + var returnedUser = await sut.GetBySsoUserAsync(postEfSsoUser.ExternalId.ToUpperInvariant(), efOrg.Id); + returnedList.Add(returnedUser); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + var sqlOrganization = await sqlOrgRepo.CreateAsync(org); + + ssoUser.UserId = sqlUser.Id; + ssoUser.OrganizationId = sqlOrganization.Id; + var postSqlSsoUser = await sqlSsoUserRepo.CreateAsync(ssoUser); + + var returnedSqlUser = await sqlUserRepo + .GetBySsoUserAsync(postSqlSsoUser.ExternalId, sqlOrganization.Id); + returnedList.Add(returnedSqlUser); + + var distinctItems = returnedList.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } + } +} diff --git a/test/Core.Test/Services/CollectionServiceTests.cs b/test/Core.Test/Services/CollectionServiceTests.cs index d41dcb030f..b1748e8587 100644 --- a/test/Core.Test/Services/CollectionServiceTests.cs +++ b/test/Core.Test/Services/CollectionServiceTests.cs @@ -9,6 +9,7 @@ using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture; using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.CollectionFixtures; using NSubstitute; using Xunit; diff --git a/test/Core.Test/Services/GroupServiceTests.cs b/test/Core.Test/Services/GroupServiceTests.cs index 3cb14f85a1..3b8467ce37 100644 --- a/test/Core.Test/Services/GroupServiceTests.cs +++ b/test/Core.Test/Services/GroupServiceTests.cs @@ -9,6 +9,7 @@ using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture; using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.GroupFixtures; using NSubstitute; using Xunit; diff --git a/test/Core.Test/Services/OrganizationServiceTests.cs b/test/Core.Test/Services/OrganizationServiceTests.cs index 93afbc5651..033202202b 100644 --- a/test/Core.Test/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/Services/OrganizationServiceTests.cs @@ -20,6 +20,7 @@ using Bit.Core.Test.AutoFixture.OrganizationUserFixtures; using Organization = Bit.Core.Models.Table.Organization; using OrganizationUser = Bit.Core.Models.Table.OrganizationUser; using Policy = Bit.Core.Models.Table.Policy; +using Bit.Core.Test.AutoFixture.PolicyFixtures; namespace Bit.Core.Test.Services { diff --git a/test/Core.Test/Services/PolicyServiceTests.cs b/test/Core.Test/Services/PolicyServiceTests.cs index ae04805b34..b344e1877c 100644 --- a/test/Core.Test/Services/PolicyServiceTests.cs +++ b/test/Core.Test/Services/PolicyServiceTests.cs @@ -8,6 +8,7 @@ using Bit.Core.Services; using Bit.Core.Test.AutoFixture; using Bit.Core.Test.AutoFixture.Attributes; using Bit.Core.Test.AutoFixture.OrganizationUserFixtures; +using PolicyFixtures = Bit.Core.Test.AutoFixture.PolicyFixtures; using NSubstitute; using Xunit; @@ -16,7 +17,7 @@ namespace Bit.Core.Test.Services public class PolicyServiceTests { [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([Policy(Enums.PolicyType.DisableSend)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) + public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyFixtures.Policy(Enums.PolicyType.DisableSend)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) { SetupOrg(sutProvider, policy.OrganizationId, null); @@ -38,7 +39,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([Policy(Enums.PolicyType.DisableSend)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) + public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([PolicyFixtures.Policy(Enums.PolicyType.DisableSend)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) { var orgId = Guid.NewGuid(); @@ -65,7 +66,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_SingleOrg_RequireSsoEnabled_ThrowsBadRequest([Policy(Enums.PolicyType.SingleOrg)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) + public async Task SaveAsync_SingleOrg_RequireSsoEnabled_ThrowsBadRequest([PolicyFixtures.Policy(Enums.PolicyType.SingleOrg)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) { policy.Enabled = false; @@ -97,7 +98,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_RequireSsoPolicy_NotEnabled_ThrowsBadRequestAsync([Policy(Enums.PolicyType.RequireSso)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) + public async Task SaveAsync_RequireSsoPolicy_NotEnabled_ThrowsBadRequestAsync([PolicyFixtures.Policy(Enums.PolicyType.RequireSso)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) { policy.Enabled = true; @@ -129,7 +130,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_NewPolicy_Created([Policy(Enums.PolicyType.MasterPassword)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) + public async Task SaveAsync_NewPolicy_Created([PolicyFixtures.Policy(Enums.PolicyType.MasterPassword)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) { policy.Id = default; @@ -154,7 +155,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_ExistingPolicy_UpdateTwoFactor([Policy(Enums.PolicyType.TwoFactorAuthentication)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) + public async Task SaveAsync_ExistingPolicy_UpdateTwoFactor([PolicyFixtures.Policy(Enums.PolicyType.TwoFactorAuthentication)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) { // If the policy that this is updating isn't enabled then do some work now that the current one is enabled @@ -223,7 +224,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_ExistingPolicy_UpdateSingleOrg([Policy(Enums.PolicyType.TwoFactorAuthentication)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) + public async Task SaveAsync_ExistingPolicy_UpdateSingleOrg([PolicyFixtures.Policy(Enums.PolicyType.TwoFactorAuthentication)] Core.Models.Table.Policy policy, SutProvider<PolicyService> sutProvider) { // If the policy that this is updating isn't enabled then do some work now that the current one is enabled diff --git a/util/Migrator/DbScripts/2021-07-08_00_EntityFrameworkSupport.sql b/util/Migrator/DbScripts/2021-07-08_00_EntityFrameworkSupport.sql new file mode 100644 index 0000000000..570a8c44a6 --- /dev/null +++ b/util/Migrator/DbScripts/2021-07-08_00_EntityFrameworkSupport.sql @@ -0,0 +1,1047 @@ +-- Tech debt: creates a few views and procedures that we implement as base repository methods but that would throw exceptions on use for not existing in the DB +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'SsoUserView') +BEGIN + DROP VIEW [dbo].[SsoUserView]; +END +GO + +CREATE VIEW [dbo].[SsoUserView] +AS +SELECT + * +FROM + [dbo].[SsoUser] +GO + +IF OBJECT_ID('[dbo].[SsoConfig_ReadById]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[SsoConfig_ReadById] +END +GO + +CREATE PROCEDURE [dbo].[SsoConfig_ReadById] + @Id BIGINT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[SsoConfigView] + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[SsoUser_DeleteById]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[SsoUser_DeleteById] +END +GO + +CREATE PROCEDURE [dbo].[SsoUser_DeleteById] + @Id BIGINT +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[SsoUser] + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[SsoConfig_DeleteById]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[SsoConfig_DeleteById] +END +GO +CREATE PROCEDURE [dbo].[SsoConfig_DeleteById] + @Id BIGINT +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[SsoConfig] + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[Event_ReadById]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Event_ReadById] +END +GO +CREATE PROCEDURE [dbo].[Event_ReadById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[Event] + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[U2f_ReadById]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[U2f_ReadById] +END +GO +CREATE PROCEDURE [dbo].[U2f_ReadById] + @Id BIGINT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[U2f] + WHERE + [Id] = @Id +END +GO +-- Refactor: Set all the base Create procs to output the ID for testing +IF OBJECT_ID('[dbo].[Organization_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_Create] +END +GO + +CREATE PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @ApiKey VARCHAR(30), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Organization] + ( + [Id], + [Identifier], + [Name], + [BusinessName], + [BusinessAddress1], + [BusinessAddress2], + [BusinessAddress3], + [BusinessCountry], + [BusinessTaxNumber], + [BillingEmail], + [Plan], + [PlanType], + [Seats], + [MaxCollections], + [UsePolicies], + [UseSso], + [UseGroups], + [UseDirectory], + [UseEvents], + [UseTotp], + [Use2fa], + [UseApi], + [UseResetPassword], + [SelfHost], + [UsersGetPremium], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [Enabled], + [LicenseKey], + [ApiKey], + [PublicKey], + [PrivateKey], + [TwoFactorProviders], + [ExpirationDate], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @Identifier, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UsePolicies, + @UseSso, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @UseApi, + @UseResetPassword, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @Enabled, + @LicenseKey, + @ApiKey, + @PublicKey, + @PrivateKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate + ) +END +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 OUTPUT, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @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), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30) +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], + [ReferenceData], + [LicenseKey], + [Kdf], + [KdfIterations], + [CreationDate], + [RevisionDate], + [ApiKey] + ) + 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, + @ReferenceData, + @LicenseKey, + @Kdf, + @KdfIterations, + @CreationDate, + @RevisionDate, + @ApiKey + ) +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUser_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[OrganizationUser_Create] +END +GO + +CREATE PROCEDURE [dbo].[OrganizationUser_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status TINYINT, + @Type TINYINT, + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[OrganizationUser] + ( + [Id], + [OrganizationId], + [UserId], + [Email], + [Key], + [Status], + [Type], + [AccessAll], + [ExternalId], + [CreationDate], + [RevisionDate], + [Permissions], + [ResetPasswordKey] + ) + VALUES + ( + @Id, + @OrganizationId, + @UserId, + @Email, + @Key, + @Status, + @Type, + @AccessAll, + @ExternalId, + @CreationDate, + @RevisionDate, + @Permissions, + @ResetPasswordKey + ) +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), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @DeletedDate DATETIME2(7), + @Reprompt TINYINT +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Cipher] + ( + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Favorites], + [Folders], + [Attachments], + [CreationDate], + [RevisionDate], + [DeletedDate], + [Reprompt] + ) + VALUES + ( + @Id, + CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END, + @OrganizationId, + @Type, + @Data, + @Favorites, + @Folders, + @Attachments, + @CreationDate, + @RevisionDate, + @DeletedDate, + @Reprompt + ) + + 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].[Device_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Device_Create] +END +GO +CREATE PROCEDURE [dbo].[Device_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @UserId UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @Type TINYINT, + @Identifier NVARCHAR(50), + @PushToken NVARCHAR(255), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Device] + ( + [Id], + [UserId], + [Name], + [Type], + [Identifier], + [PushToken], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @UserId, + @Name, + @Type, + @Identifier, + @PushToken, + @CreationDate, + @RevisionDate + ) +END +GO + +IF OBJECT_ID('[dbo].[Collection_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Collection_Create] +END +GO +CREATE PROCEDURE [dbo].[Collection_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Name VARCHAR(MAX), + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Collection] + ( + [Id], + [OrganizationId], + [Name], + [ExternalId], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Name, + @ExternalId, + @CreationDate, + @RevisionDate + ) + + EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @Id, @OrganizationId +END +GO + +IF OBJECT_ID('[dbo].[EmergencyAccess_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[EmergencyAccess_Create] +END +GO +CREATE PROCEDURE [dbo].[EmergencyAccess_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @GrantorId UNIQUEIDENTIFIER, + @GranteeId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @KeyEncrypted VARCHAR(MAX), + @Type TINYINT, + @Status TINYINT, + @WaitTimeDays SMALLINT, + @RecoveryInitiatedDate DATETIME2(7), + @LastNotificationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[EmergencyAccess] + ( + [Id], + [GrantorId], + [GranteeId], + [Email], + [KeyEncrypted], + [Type], + [Status], + [WaitTimeDays], + [RecoveryInitiatedDate], + [LastNotificationDate], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @GrantorId, + @GranteeId, + @Email, + @KeyEncrypted, + @Type, + @Status, + @WaitTimeDays, + @RecoveryInitiatedDate, + @LastNotificationDate, + @CreationDate, + @RevisionDate + ) +END +GO + +IF OBJECT_ID('[dbo].[Event_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Event_Create] +END +GO +CREATE PROCEDURE [dbo].[Event_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Type INT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @CipherId UNIQUEIDENTIFIER, + @CollectionId UNIQUEIDENTIFIER, + @PolicyId UNIQUEIDENTIFIER, + @GroupId UNIQUEIDENTIFIER, + @OrganizationUserId UNIQUEIDENTIFIER, + @ActingUserId UNIQUEIDENTIFIER, + @DeviceType SMALLINT, + @IpAddress VARCHAR(50), + @Date DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Event] + ( + [Id], + [Type], + [UserId], + [OrganizationId], + [CipherId], + [CollectionId], + [PolicyId], + [GroupId], + [OrganizationUserId], + [ActingUserId], + [DeviceType], + [IpAddress], + [Date] + ) + VALUES + ( + @Id, + @Type, + @UserId, + @OrganizationId, + @CipherId, + @CollectionId, + @PolicyId, + @GroupId, + @OrganizationUserId, + @ActingUserId, + @DeviceType, + @IpAddress, + @Date + ) +END +GO + +IF OBJECT_ID('[dbo].[Folder_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Folder_Create] +END +GO + +CREATE PROCEDURE [dbo].[Folder_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @UserId UNIQUEIDENTIFIER, + @Name VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Folder] + ( + [Id], + [UserId], + [Name], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @UserId, + @Name, + @CreationDate, + @RevisionDate + ) + + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId +END +GO +IF OBJECT_ID('[dbo].[Group_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Group_Create] +END +GO +CREATE PROCEDURE [dbo].[Group_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Group] + ( + [Id], + [OrganizationId], + [Name], + [AccessAll], + [ExternalId], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Name, + @AccessAll, + @ExternalId, + @CreationDate, + @RevisionDate + ) +END +GO +IF OBJECT_ID('[dbo].[Installation_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Installation_Create] +END +GO +CREATE PROCEDURE [dbo].[Installation_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Email NVARCHAR(256), + @Key VARCHAR(150), + @Enabled BIT, + @CreationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Installation] + ( + [Id], + [Email], + [Key], + [Enabled], + [CreationDate] + ) + VALUES + ( + @Id, + @Email, + @Key, + @Enabled, + @CreationDate + ) +END +GO +IF OBJECT_ID('[dbo].[Policy_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Policy_Create] +END +GO +CREATE PROCEDURE [dbo].[Policy_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Enabled BIT, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Policy] + ( + [Id], + [OrganizationId], + [Type], + [Data], + [Enabled], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Type, + @Data, + @Enabled, + @CreationDate, + @RevisionDate + ) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END +GO + +IF OBJECT_ID('[dbo].[Send_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Send_Create] +END +GO + +CREATE PROCEDURE [dbo].[Send_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data VARCHAR(MAX), + @Key VARCHAR(MAX), + @Password NVARCHAR(300), + @MaxAccessCount INT, + @AccessCount INT, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ExpirationDate DATETIME2(7), + @DeletionDate DATETIME2(7), + @Disabled BIT, + @HideEmail BIT +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Send] + ( + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Key], + [Password], + [MaxAccessCount], + [AccessCount], + [CreationDate], + [RevisionDate], + [ExpirationDate], + [DeletionDate], + [Disabled], + [HideEmail] + ) + VALUES + ( + @Id, + @UserId, + @OrganizationId, + @Type, + @Data, + @Key, + @Password, + @MaxAccessCount, + @AccessCount, + @CreationDate, + @RevisionDate, + @ExpirationDate, + @DeletionDate, + @Disabled, + @HideEmail + ) + + IF @UserId IS NOT NULL + BEGIN + IF @Type = 1 --File + BEGIN + EXEC [dbo].[User_UpdateStorage] @UserId + END + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END + -- TODO: OrganizationId bump? +END +GO + +IF OBJECT_ID('[dbo].[TaxRate_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[TaxRate_Create] +END +GO + + +CREATE PROCEDURE [dbo].[TaxRate_Create] + @Id VARCHAR(40) OUTPUT, + @Country VARCHAR(50), + @State VARCHAR(2), + @PostalCode VARCHAR(10), + @Rate DECIMAL(5,2), + @Active BIT +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[TaxRate] + ( + [Id], + [Country], + [State], + [PostalCode], + [Rate], + [Active] + ) + VALUES + ( + @Id, + @Country, + @State, + @PostalCode, + @Rate, + 1 + ) +END +GO + +IF OBJECT_ID('[dbo].[Transaction_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Transaction_Create] +END +GO + +CREATE PROCEDURE [dbo].[Transaction_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Amount MONEY, + @Refunded BIT, + @RefundedAmount MONEY, + @Details NVARCHAR(100), + @PaymentMethodType TINYINT, + @Gateway TINYINT, + @GatewayId VARCHAR(50), + @CreationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Transaction] + ( + [Id], + [UserId], + [OrganizationId], + [Type], + [Amount], + [Refunded], + [RefundedAmount], + [Details], + [PaymentMethodType], + [Gateway], + [GatewayId], + [CreationDate] + ) + VALUES + ( + @Id, + @UserId, + @OrganizationId, + @Type, + @Amount, + @Refunded, + @RefundedAmount, + @Details, + @PaymentMethodType, + @Gateway, + @GatewayId, + @CreationDate + ) +END +GO + +IF OBJECT_ID('[dbo].[U2f_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[U2f_Create] +END +GO + +CREATE PROCEDURE [dbo].[U2f_Create] + @Id INT OUTPUT, + @UserId UNIQUEIDENTIFIER, + @KeyHandle VARCHAR(200), + @Challenge VARCHAR(200), + @AppId VARCHAR(50), + @Version VARCHAR(20), + @CreationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[U2f] + ( + [UserId], + [KeyHandle], + [Challenge], + [AppId], + [Version], + [CreationDate] + ) + VALUES + ( + @UserId, + @KeyHandle, + @Challenge, + @AppId, + @Version, + @CreationDate + ) + + SET @Id = (SELECT scope_identity()) +END +GO diff --git a/util/Migrator/Migrator.csproj b/util/Migrator/Migrator.csproj index 2c994fe5f4..2623e74c38 100644 --- a/util/Migrator/Migrator.csproj +++ b/util/Migrator/Migrator.csproj @@ -6,7 +6,7 @@ <ItemGroup> <PackageReference Include="dbup-sqlserver" Version="4.4.0" /> - <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" /> + <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" /> </ItemGroup> <ItemGroup> diff --git a/util/Migrator/MySql/Init.sql b/util/Migrator/MySql/Init.sql new file mode 100644 index 0000000000..3c902002c0 --- /dev/null +++ b/util/Migrator/MySql/Init.sql @@ -0,0 +1,1028 @@ +ALTER DATABASE CHARACTER SET utf8mb4; +CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( + `MigrationId` varchar(150) CHARACTER SET utf8mb4 NOT NULL, + `ProductVersion` varchar(32) CHARACTER SET utf8mb4 NOT NULL, + CONSTRAINT `PK___EFMigrationsHistory` PRIMARY KEY (`MigrationId`) +) CHARACTER SET utf8mb4; + +START TRANSACTION; + +CREATE TABLE `Event` ( + `Id` uuid NOT NULL, + `Date` timestamp without time zone NOT NULL, + `Type` integer NOT NULL, + `UserId` uuid NULL, + `OrganizationId` uuid NULL, + `CipherId` uuid NULL, + `CollectionId` uuid NULL, + `PolicyId` uuid NULL, + `GroupId` uuid NULL, + `OrganizationUserId` uuid NULL, + `DeviceType` smallint NULL, + `IpAddress` character varying(50) NULL, + `ActingUserId` uuid NULL, + CONSTRAINT `PK_Event` PRIMARY KEY (`Id`) +); + +CREATE TABLE `Grant` ( + `Key` character varying(200) NOT NULL, + `Type` character varying(50) NULL, + `SubjectId` character varying(200) NULL, + `SessionId` character varying(100) NULL, + `ClientId` character varying(200) NULL, + `Description` character varying(200) NULL, + `CreationDate` timestamp without time zone NOT NULL, + `ExpirationDate` timestamp without time zone NULL, + `ConsumedDate` timestamp without time zone NULL, + `Data` text NULL, + CONSTRAINT `PK_Grant` PRIMARY KEY (`Key`) +); + +CREATE TABLE `Installation` ( + `Id` uuid NOT NULL, + `Email` character varying(256) NULL, + `Key` character varying(150) NULL, + `Enabled` boolean NOT NULL, + `CreationDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Installation` PRIMARY KEY (`Id`) +); + +CREATE TABLE `Organization` ( + `Id` uuid NOT NULL, + `Identifier` character varying(50) COLLATE postgresIndetermanisticCollation NULL, + `Name` character varying(50) NULL, + `BusinessName` character varying(50) NULL, + `BusinessAddress1` character varying(50) NULL, + `BusinessAddress2` character varying(50) NULL, + `BusinessAddress3` character varying(50) NULL, + `BusinessCountry` character varying(2) NULL, + `BusinessTaxNumber` character varying(30) NULL, + `BillingEmail` character varying(256) NULL, + `Plan` character varying(50) NULL, + `PlanType` smallint NOT NULL, + `Seats` integer NULL, + `MaxCollections` smallint NULL, + `UsePolicies` boolean NOT NULL, + `UseSso` boolean NOT NULL, + `UseGroups` boolean NOT NULL, + `UseDirectory` boolean NOT NULL, + `UseEvents` boolean NOT NULL, + `UseTotp` boolean NOT NULL, + `Use2fa` boolean NOT NULL, + `UseApi` boolean NOT NULL, + `UseResetPassword` boolean NOT NULL, + `SelfHost` boolean NOT NULL, + `UsersGetPremium` boolean NOT NULL, + `Storage` bigint NULL, + `MaxStorageGb` smallint NULL, + `Gateway` smallint NULL, + `GatewayCustomerId` character varying(50) NULL, + `GatewaySubscriptionId` character varying(50) NULL, + `ReferenceData` text NULL, + `Enabled` boolean NOT NULL, + `LicenseKey` character varying(100) NULL, + `ApiKey` character varying(30) NULL, + `PublicKey` text NULL, + `PrivateKey` text NULL, + `TwoFactorProviders` text NULL, + `ExpirationDate` timestamp without time zone NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Organization` PRIMARY KEY (`Id`) +); + +CREATE TABLE `Provider` ( + `Id` uuid NOT NULL, + `Name` text NULL, + `BusinessName` text NULL, + `BusinessAddress1` text NULL, + `BusinessAddress2` text NULL, + `BusinessAddress3` text NULL, + `BusinessCountry` text NULL, + `BusinessTaxNumber` text NULL, + `BillingEmail` text NULL, + `Status` smallint NOT NULL, + `Enabled` boolean NOT NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Provider` PRIMARY KEY (`Id`) +); + +CREATE TABLE `TaxRate` ( + `Id` character varying(40) NOT NULL, + `Country` character varying(50) NULL, + `State` character varying(2) NULL, + `PostalCode` character varying(10) NULL, + `Rate` numeric NOT NULL, + `Active` boolean NOT NULL, + CONSTRAINT `PK_TaxRate` PRIMARY KEY (`Id`) +); + +CREATE TABLE `User` ( + `Id` uuid NOT NULL, + `Name` character varying(50) NULL, + `Email` character varying(256) COLLATE postgresIndetermanisticCollation NOT NULL, + `EmailVerified` boolean NOT NULL, + `MasterPassword` character varying(300) NULL, + `MasterPasswordHint` character varying(50) NULL, + `Culture` character varying(10) NULL, + `SecurityStamp` character varying(50) NOT NULL, + `TwoFactorProviders` text NULL, + `TwoFactorRecoveryCode` character varying(32) NULL, + `EquivalentDomains` text NULL, + `ExcludedGlobalEquivalentDomains` text NULL, + `AccountRevisionDate` timestamp without time zone NOT NULL, + `Key` text NULL, + `PublicKey` text NULL, + `PrivateKey` text NULL, + `Premium` boolean NOT NULL, + `PremiumExpirationDate` timestamp without time zone NULL, + `RenewalReminderDate` timestamp without time zone NULL, + `Storage` bigint NULL, + `MaxStorageGb` smallint NULL, + `Gateway` smallint NULL, + `GatewayCustomerId` character varying(50) NULL, + `GatewaySubscriptionId` character varying(50) NULL, + `ReferenceData` text NULL, + `LicenseKey` character varying(100) NULL, + `ApiKey` character varying(30) NOT NULL, + `Kdf` smallint NOT NULL, + `KdfIterations` integer NOT NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_User` PRIMARY KEY (`Id`) +); + +CREATE TABLE `Collection` ( + `Id` uuid NOT NULL, + `OrganizationId` uuid NOT NULL, + `Name` text NULL, + `ExternalId` character varying(300) NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Collection` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Collection_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `Group` ( + `Id` uuid NOT NULL, + `OrganizationId` uuid NOT NULL, + `Name` character varying(100) NULL, + `AccessAll` boolean NOT NULL, + `ExternalId` character varying(300) NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Group` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Group_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `Policy` ( + `Id` uuid NOT NULL, + `OrganizationId` uuid NOT NULL, + `Type` smallint NOT NULL, + `Data` text NULL, + `Enabled` boolean NOT NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Policy` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Policy_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `SsoConfig` ( + `Id` bigint NOT NULL, + `Enabled` boolean NOT NULL, + `OrganizationId` uuid NOT NULL, + `Data` text NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_SsoConfig` PRIMARY KEY (`Id`), + CONSTRAINT `FK_SsoConfig_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `ProviderOrganization` ( + `Id` uuid NOT NULL, + `ProviderId` uuid NOT NULL, + `OrganizationId` uuid NOT NULL, + `Key` text NULL, + `Settings` text NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_ProviderOrganization` PRIMARY KEY (`Id`), + CONSTRAINT `FK_ProviderOrganization_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_ProviderOrganization_Provider_ProviderId` FOREIGN KEY (`ProviderId`) REFERENCES `Provider` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `Cipher` ( + `Id` uuid NOT NULL, + `UserId` uuid NULL, + `OrganizationId` uuid NULL, + `Type` smallint NOT NULL, + `Data` text NULL, + `Favorites` text NULL, + `Folders` text NULL, + `Attachments` text NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + `DeletedDate` timestamp without time zone NULL, + `Reprompt` smallint NULL, + CONSTRAINT `PK_Cipher` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Cipher_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_Cipher_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +); + +CREATE TABLE `Device` ( + `Id` uuid NOT NULL, + `UserId` uuid NOT NULL, + `Name` character varying(50) NULL, + `Type` smallint NOT NULL, + `Identifier` character varying(50) NULL, + `PushToken` character varying(255) NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Device` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Device_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `EmergencyAccess` ( + `Id` uuid NOT NULL, + `GrantorId` uuid NOT NULL, + `GranteeId` uuid NULL, + `Email` character varying(256) NULL, + `KeyEncrypted` text NULL, + `Type` smallint NOT NULL, + `Status` smallint NOT NULL, + `WaitTimeDays` integer NOT NULL, + `RecoveryInitiatedDate` timestamp without time zone NULL, + `LastNotificationDate` timestamp without time zone NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_EmergencyAccess` PRIMARY KEY (`Id`), + CONSTRAINT `FK_EmergencyAccess_User_GranteeId` FOREIGN KEY (`GranteeId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_EmergencyAccess_User_GrantorId` FOREIGN KEY (`GrantorId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `Folder` ( + `Id` uuid NOT NULL, + `UserId` uuid NOT NULL, + `Name` text NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Folder` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Folder_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `OrganizationUser` ( + `Id` uuid NOT NULL, + `OrganizationId` uuid NOT NULL, + `UserId` uuid NULL, + `Email` character varying(256) NULL, + `Key` text NULL, + `ResetPasswordKey` text NULL, + `Status` smallint NOT NULL, + `Type` smallint NOT NULL, + `AccessAll` boolean NOT NULL, + `ExternalId` character varying(300) NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + `Permissions` text NULL, + CONSTRAINT `PK_OrganizationUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_OrganizationUser_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_OrganizationUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +); + +CREATE TABLE `ProviderUser` ( + `Id` uuid NOT NULL, + `ProviderId` uuid NOT NULL, + `UserId` uuid NULL, + `Email` text NULL, + `Key` text NULL, + `Status` smallint NOT NULL, + `Type` smallint NOT NULL, + `Permissions` text NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_ProviderUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_ProviderUser_Provider_ProviderId` FOREIGN KEY (`ProviderId`) REFERENCES `Provider` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_ProviderUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +); + +CREATE TABLE `Send` ( + `Id` uuid NOT NULL, + `UserId` uuid NULL, + `OrganizationId` uuid NULL, + `Type` smallint NOT NULL, + `Data` text NULL, + `Key` text NULL, + `Password` character varying(300) NULL, + `MaxAccessCount` integer NULL, + `AccessCount` integer NOT NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + `ExpirationDate` timestamp without time zone NULL, + `DeletionDate` timestamp without time zone NOT NULL, + `Disabled` boolean NOT NULL, + `HideEmail` boolean NULL, + CONSTRAINT `PK_Send` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Send_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_Send_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +); + +CREATE TABLE `SsoUser` ( + `Id` bigint NOT NULL, + `UserId` uuid NOT NULL, + `OrganizationId` uuid NULL, + `ExternalId` character varying(50) COLLATE postgresIndetermanisticCollation NULL, + `CreationDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_SsoUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_SsoUser_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_SsoUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `Transaction` ( + `Id` uuid NOT NULL, + `UserId` uuid NULL, + `OrganizationId` uuid NULL, + `Type` smallint NOT NULL, + `Amount` numeric NOT NULL, + `Refunded` boolean NULL, + `RefundedAmount` numeric NULL, + `Details` character varying(100) NULL, + `PaymentMethodType` smallint NULL, + `Gateway` smallint NULL, + `GatewayId` character varying(50) NULL, + `CreationDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_Transaction` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Transaction_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_Transaction_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +); + +CREATE TABLE `U2f` ( + `Id` integer NOT NULL, + `UserId` uuid NOT NULL, + `KeyHandle` character varying(200) NULL, + `Challenge` character varying(200) NULL, + `AppId` character varying(50) NULL, + `Version` character varying(20) NULL, + `CreationDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_U2f` PRIMARY KEY (`Id`), + CONSTRAINT `FK_U2f_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `CollectionGroups` ( + `CollectionId` uuid NOT NULL, + `GroupId` uuid NOT NULL, + `ReadOnly` boolean NOT NULL, + `HidePasswords` boolean NOT NULL, + CONSTRAINT `PK_CollectionGroups` PRIMARY KEY (`CollectionId`, `GroupId`), + CONSTRAINT `FK_CollectionGroups_Collection_CollectionId` FOREIGN KEY (`CollectionId`) REFERENCES `Collection` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionGroups_Group_GroupId` FOREIGN KEY (`GroupId`) REFERENCES `Group` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `CollectionCipher` ( + `CollectionId` uuid NOT NULL, + `CipherId` uuid NOT NULL, + CONSTRAINT `PK_CollectionCipher` PRIMARY KEY (`CollectionId`, `CipherId`), + CONSTRAINT `FK_CollectionCipher_Cipher_CipherId` FOREIGN KEY (`CipherId`) REFERENCES `Cipher` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionCipher_Collection_CollectionId` FOREIGN KEY (`CollectionId`) REFERENCES `Collection` (`Id`) ON DELETE CASCADE +); + +CREATE TABLE `CollectionUsers` ( + `CollectionId` uuid NOT NULL, + `OrganizationUserId` uuid NOT NULL, + `UserId` uuid NULL, + `ReadOnly` boolean NOT NULL, + `HidePasswords` boolean NOT NULL, + CONSTRAINT `PK_CollectionUsers` PRIMARY KEY (`CollectionId`, `OrganizationUserId`), + CONSTRAINT `FK_CollectionUsers_Collection_CollectionId` FOREIGN KEY (`CollectionId`) REFERENCES `Collection` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionUsers_OrganizationUser_OrganizationUserId` FOREIGN KEY (`OrganizationUserId`) REFERENCES `OrganizationUser` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionUsers_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +); + +CREATE TABLE `GroupUser` ( + `GroupId` uuid NOT NULL, + `OrganizationUserId` uuid NOT NULL, + `UserId` uuid NULL, + CONSTRAINT `PK_GroupUser` PRIMARY KEY (`GroupId`, `OrganizationUserId`), + CONSTRAINT `FK_GroupUser_Group_GroupId` FOREIGN KEY (`GroupId`) REFERENCES `Group` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_GroupUser_OrganizationUser_OrganizationUserId` FOREIGN KEY (`OrganizationUserId`) REFERENCES `OrganizationUser` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_GroupUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +); + +CREATE TABLE `ProviderOrganizationProviderUser` ( + `Id` uuid NOT NULL, + `ProviderOrganizationId` uuid NOT NULL, + `ProviderUserId` uuid NOT NULL, + `Type` smallint NOT NULL, + `Permissions` text NULL, + `CreationDate` timestamp without time zone NOT NULL, + `RevisionDate` timestamp without time zone NOT NULL, + CONSTRAINT `PK_ProviderOrganizationProviderUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_ProviderOrganizationProviderUser_ProviderOrganization_Provi~` FOREIGN KEY (`ProviderOrganizationId`) REFERENCES `ProviderOrganization` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_ProviderOrganizationProviderUser_ProviderUser_ProviderUserId` FOREIGN KEY (`ProviderUserId`) REFERENCES `ProviderUser` (`Id`) ON DELETE CASCADE +); + +CREATE INDEX `IX_Cipher_OrganizationId` ON `Cipher` (`OrganizationId`); + +CREATE INDEX `IX_Cipher_UserId` ON `Cipher` (`UserId`); + +CREATE INDEX `IX_Collection_OrganizationId` ON `Collection` (`OrganizationId`); + +CREATE INDEX `IX_CollectionCipher_CipherId` ON `CollectionCipher` (`CipherId`); + +CREATE INDEX `IX_CollectionGroups_GroupId` ON `CollectionGroups` (`GroupId`); + +CREATE INDEX `IX_CollectionUsers_OrganizationUserId` ON `CollectionUsers` (`OrganizationUserId`); + +CREATE INDEX `IX_CollectionUsers_UserId` ON `CollectionUsers` (`UserId`); + +CREATE INDEX `IX_Device_UserId` ON `Device` (`UserId`); + +CREATE INDEX `IX_EmergencyAccess_GranteeId` ON `EmergencyAccess` (`GranteeId`); + +CREATE INDEX `IX_EmergencyAccess_GrantorId` ON `EmergencyAccess` (`GrantorId`); + +CREATE INDEX `IX_Folder_UserId` ON `Folder` (`UserId`); + +CREATE INDEX `IX_Group_OrganizationId` ON `Group` (`OrganizationId`); + +CREATE INDEX `IX_GroupUser_OrganizationUserId` ON `GroupUser` (`OrganizationUserId`); + +CREATE INDEX `IX_GroupUser_UserId` ON `GroupUser` (`UserId`); + +CREATE INDEX `IX_OrganizationUser_OrganizationId` ON `OrganizationUser` (`OrganizationId`); + +CREATE INDEX `IX_OrganizationUser_UserId` ON `OrganizationUser` (`UserId`); + +CREATE INDEX `IX_Policy_OrganizationId` ON `Policy` (`OrganizationId`); + +CREATE INDEX `IX_ProviderOrganization_OrganizationId` ON `ProviderOrganization` (`OrganizationId`); + +CREATE INDEX `IX_ProviderOrganization_ProviderId` ON `ProviderOrganization` (`ProviderId`); + +CREATE INDEX `IX_ProviderOrganizationProviderUser_ProviderOrganizationId` ON `ProviderOrganizationProviderUser` (`ProviderOrganizationId`); + +CREATE INDEX `IX_ProviderOrganizationProviderUser_ProviderUserId` ON `ProviderOrganizationProviderUser` (`ProviderUserId`); + +CREATE INDEX `IX_ProviderUser_ProviderId` ON `ProviderUser` (`ProviderId`); + +CREATE INDEX `IX_ProviderUser_UserId` ON `ProviderUser` (`UserId`); + +CREATE INDEX `IX_Send_OrganizationId` ON `Send` (`OrganizationId`); + +CREATE INDEX `IX_Send_UserId` ON `Send` (`UserId`); + +CREATE INDEX `IX_SsoConfig_OrganizationId` ON `SsoConfig` (`OrganizationId`); + +CREATE INDEX `IX_SsoUser_OrganizationId` ON `SsoUser` (`OrganizationId`); + +CREATE INDEX `IX_SsoUser_UserId` ON `SsoUser` (`UserId`); + +CREATE INDEX `IX_Transaction_OrganizationId` ON `Transaction` (`OrganizationId`); + +CREATE INDEX `IX_Transaction_UserId` ON `Transaction` (`UserId`); + +CREATE INDEX `IX_U2f_UserId` ON `U2f` (`UserId`); + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20210617143411_InitPostgres', '5.0.5'); + +COMMIT; + +START TRANSACTION; + +ALTER TABLE `ProviderOrganizationProviderUser` DROP FOREIGN KEY `FK_ProviderOrganizationProviderUser_ProviderOrganization_Provi~`; + +ALTER TABLE `User` MODIFY COLUMN `TwoFactorRecoveryCode` varchar(32) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `TwoFactorProviders` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `SecurityStamp` varchar(50) CHARACTER SET utf8mb4 NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `RenewalReminderDate` datetime(6) NULL; + +ALTER TABLE `User` MODIFY COLUMN `ReferenceData` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `PublicKey` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `PrivateKey` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `PremiumExpirationDate` datetime(6) NULL; + +ALTER TABLE `User` MODIFY COLUMN `Premium` tinyint(1) NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `Name` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `MasterPasswordHint` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `MasterPassword` varchar(300) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `LicenseKey` varchar(100) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `Key` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `KdfIterations` int NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `Kdf` tinyint unsigned NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `GatewaySubscriptionId` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `GatewayCustomerId` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `Gateway` tinyint unsigned NULL; + +ALTER TABLE `User` MODIFY COLUMN `ExcludedGlobalEquivalentDomains` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `EquivalentDomains` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `EmailVerified` tinyint(1) NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `Email` varchar(256) CHARACTER SET utf8mb4 NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `Culture` varchar(10) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `User` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `ApiKey` varchar(30) CHARACTER SET utf8mb4 NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `AccountRevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `User` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `U2f` MODIFY COLUMN `Version` varchar(20) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `U2f` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `U2f` MODIFY COLUMN `KeyHandle` varchar(200) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `U2f` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `U2f` MODIFY COLUMN `Challenge` varchar(200) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `U2f` MODIFY COLUMN `AppId` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `U2f` MODIFY COLUMN `Id` int NOT NULL AUTO_INCREMENT; + +ALTER TABLE `Transaction` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `RefundedAmount` decimal(65,30) NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `Refunded` tinyint(1) NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `PaymentMethodType` tinyint unsigned NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `GatewayId` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `Gateway` tinyint unsigned NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `Details` varchar(100) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `Amount` decimal(65,30) NOT NULL; + +ALTER TABLE `Transaction` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `TaxRate` MODIFY COLUMN `State` varchar(2) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `TaxRate` MODIFY COLUMN `Rate` decimal(65,30) NOT NULL; + +ALTER TABLE `TaxRate` MODIFY COLUMN `PostalCode` varchar(10) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `TaxRate` MODIFY COLUMN `Country` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `TaxRate` MODIFY COLUMN `Active` tinyint(1) NOT NULL; + +ALTER TABLE `TaxRate` MODIFY COLUMN `Id` varchar(40) CHARACTER SET utf8mb4 NOT NULL; + +ALTER TABLE `SsoUser` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `SsoUser` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `SsoUser` MODIFY COLUMN `ExternalId` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `SsoUser` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `SsoConfig` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `SsoConfig` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `SsoConfig` MODIFY COLUMN `Enabled` tinyint(1) NOT NULL; + +ALTER TABLE `SsoConfig` MODIFY COLUMN `Data` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `SsoConfig` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Send` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Send` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `Send` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Send` MODIFY COLUMN `Password` varchar(300) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Send` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Send` MODIFY COLUMN `MaxAccessCount` int NULL; + +ALTER TABLE `Send` MODIFY COLUMN `Key` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Send` MODIFY COLUMN `HideEmail` tinyint(1) NULL; + +ALTER TABLE `Send` MODIFY COLUMN `ExpirationDate` datetime(6) NULL; + +ALTER TABLE `Send` MODIFY COLUMN `Disabled` tinyint(1) NOT NULL; + +ALTER TABLE `Send` MODIFY COLUMN `DeletionDate` datetime(6) NOT NULL; + +ALTER TABLE `Send` MODIFY COLUMN `Data` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Send` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Send` MODIFY COLUMN `AccessCount` int NOT NULL; + +ALTER TABLE `Send` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `Status` tinyint unsigned NOT NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `ProviderId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `Permissions` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `Key` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `Email` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `ProviderUser` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` MODIFY COLUMN `ProviderUserId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` MODIFY COLUMN `ProviderOrganizationId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` MODIFY COLUMN `Permissions` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderOrganization` MODIFY COLUMN `Settings` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `ProviderOrganization` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `ProviderOrganization` MODIFY COLUMN `ProviderId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderOrganization` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderOrganization` MODIFY COLUMN `Key` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `ProviderOrganization` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `ProviderOrganization` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `Status` tinyint unsigned NOT NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `Name` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `Enabled` tinyint(1) NOT NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `BusinessTaxNumber` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `BusinessName` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `BusinessCountry` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `BusinessAddress3` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `BusinessAddress2` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `BusinessAddress1` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `BillingEmail` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Provider` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Policy` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `Policy` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Policy` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Policy` MODIFY COLUMN `Enabled` tinyint(1) NOT NULL; + +ALTER TABLE `Policy` MODIFY COLUMN `Data` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Policy` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Policy` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `Status` tinyint unsigned NOT NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `ResetPasswordKey` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `Permissions` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `Key` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `ExternalId` varchar(300) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `Email` varchar(256) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `AccessAll` tinyint(1) NOT NULL; + +ALTER TABLE `OrganizationUser` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UsersGetPremium` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UseTotp` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UseSso` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UseResetPassword` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UsePolicies` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UseGroups` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UseEvents` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UseDirectory` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `UseApi` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Use2fa` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `TwoFactorProviders` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `SelfHost` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Seats` int NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `ReferenceData` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `PublicKey` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `PrivateKey` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `PlanType` tinyint unsigned NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Plan` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Name` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `LicenseKey` varchar(100) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Identifier` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `GatewaySubscriptionId` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `GatewayCustomerId` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Gateway` tinyint unsigned NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `ExpirationDate` datetime(6) NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Enabled` tinyint(1) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `BusinessTaxNumber` varchar(30) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `BusinessName` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `BusinessCountry` varchar(2) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `BusinessAddress3` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `BusinessAddress2` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `BusinessAddress1` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `BillingEmail` varchar(256) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `ApiKey` varchar(30) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Organization` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Installation` MODIFY COLUMN `Key` varchar(150) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Installation` MODIFY COLUMN `Enabled` tinyint(1) NOT NULL; + +ALTER TABLE `Installation` MODIFY COLUMN `Email` varchar(256) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Installation` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Installation` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `GroupUser` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `GroupUser` MODIFY COLUMN `OrganizationUserId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `GroupUser` MODIFY COLUMN `GroupId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Group` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Group` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Group` MODIFY COLUMN `Name` varchar(100) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Group` MODIFY COLUMN `ExternalId` varchar(300) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Group` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Group` MODIFY COLUMN `AccessAll` tinyint(1) NOT NULL; + +ALTER TABLE `Group` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `Type` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `SubjectId` varchar(200) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `SessionId` varchar(100) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `ExpirationDate` datetime(6) NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `Description` varchar(200) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `Data` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `ConsumedDate` datetime(6) NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `ClientId` varchar(200) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Grant` MODIFY COLUMN `Key` varchar(200) CHARACTER SET utf8mb4 NOT NULL; + +ALTER TABLE `Folder` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Folder` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Folder` MODIFY COLUMN `Name` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Folder` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Folder` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Event` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `Type` int NOT NULL; + +ALTER TABLE `Event` MODIFY COLUMN `PolicyId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `OrganizationUserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `IpAddress` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Event` MODIFY COLUMN `GroupId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `DeviceType` tinyint unsigned NULL; + +ALTER TABLE `Event` MODIFY COLUMN `Date` datetime(6) NOT NULL; + +ALTER TABLE `Event` MODIFY COLUMN `CollectionId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `CipherId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `ActingUserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Event` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `WaitTimeDays` int NOT NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `Status` tinyint unsigned NOT NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `RecoveryInitiatedDate` datetime(6) NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `LastNotificationDate` datetime(6) NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `KeyEncrypted` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `GrantorId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `GranteeId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `Email` varchar(256) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `EmergencyAccess` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Device` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Device` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `Device` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Device` MODIFY COLUMN `PushToken` varchar(255) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Device` MODIFY COLUMN `Name` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Device` MODIFY COLUMN `Identifier` varchar(50) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Device` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Device` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `CollectionUsers` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `CollectionUsers` MODIFY COLUMN `ReadOnly` tinyint(1) NOT NULL; + +ALTER TABLE `CollectionUsers` MODIFY COLUMN `HidePasswords` tinyint(1) NOT NULL; + +ALTER TABLE `CollectionUsers` MODIFY COLUMN `OrganizationUserId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `CollectionUsers` MODIFY COLUMN `CollectionId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `CollectionGroups` MODIFY COLUMN `ReadOnly` tinyint(1) NOT NULL; + +ALTER TABLE `CollectionGroups` MODIFY COLUMN `HidePasswords` tinyint(1) NOT NULL; + +ALTER TABLE `CollectionGroups` MODIFY COLUMN `GroupId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `CollectionGroups` MODIFY COLUMN `CollectionId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `CollectionCipher` MODIFY COLUMN `CipherId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `CollectionCipher` MODIFY COLUMN `CollectionId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Collection` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Collection` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Collection` MODIFY COLUMN `Name` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Collection` MODIFY COLUMN `ExternalId` varchar(300) CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Collection` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Collection` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `UserId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `Type` tinyint unsigned NOT NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `RevisionDate` datetime(6) NOT NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `Reprompt` tinyint unsigned NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `OrganizationId` char(36) COLLATE ascii_general_ci NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `Folders` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `Favorites` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `DeletedDate` datetime(6) NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `Data` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `CreationDate` datetime(6) NOT NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `Attachments` longtext CHARACTER SET utf8mb4 NULL; + +ALTER TABLE `Cipher` MODIFY COLUMN `Id` char(36) COLLATE ascii_general_ci NOT NULL; + +ALTER TABLE `ProviderOrganizationProviderUser` ADD CONSTRAINT `FK_ProviderOrganizationProviderUser_ProviderOrganization_Provid~` FOREIGN KEY (`ProviderOrganizationId`) REFERENCES `ProviderOrganization` (`Id`) ON DELETE CASCADE; + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20210617152444_InitMySql', '5.0.5'); + +COMMIT; \ No newline at end of file diff --git a/util/MySqlMigrations/Factories.cs b/util/MySqlMigrations/Factories.cs new file mode 100644 index 0000000000..1fad15f524 --- /dev/null +++ b/util/MySqlMigrations/Factories.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Settings; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Bit.Core.Enums; +using Microsoft.EntityFrameworkCore.Design; +using System; + +namespace MySqlMigrations +{ + public static class GlobalSettingsFactory + { + public static GlobalSettings GlobalSettings { get; } = new GlobalSettings(); + static GlobalSettingsFactory() + { + var configBuilder = new ConfigurationBuilder().AddUserSecrets<Bit.Api.Startup>(); + var Configuration = configBuilder.Build(); + ConfigurationBinder.Bind(Configuration.GetSection("GlobalSettings"), GlobalSettings); + } + } + + public class DatabaseContextFactory : IDesignTimeDbContextFactory<DatabaseContext> + { + public DatabaseContext CreateDbContext(string[] args) + { + var globalSettings = GlobalSettingsFactory.GlobalSettings; + var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>(); + + var selectedDatabaseProvider = globalSettings.DatabaseProvider; + var provider = SupportedDatabaseProviders.Postgres; + var connectionString = string.Empty; + if (!string.IsNullOrWhiteSpace(selectedDatabaseProvider)) + { + switch (selectedDatabaseProvider.ToLowerInvariant()) + { + case "postgres": + case "postgresql": + provider = SupportedDatabaseProviders.Postgres; + connectionString = globalSettings.PostgreSql.ConnectionString; + break; + case "mysql": + case "mariadb": + provider = SupportedDatabaseProviders.MySql; + connectionString = globalSettings.MySql.ConnectionString; + break; + default: + throw new Exception("No database provider selected"); + break; + } + } + if (provider.Equals(SupportedDatabaseProviders.Postgres)) + { + optionsBuilder.UseNpgsql( + connectionString, + b => b.MigrationsAssembly("PostgresMigrations")); + } + else if (provider.Equals(SupportedDatabaseProviders.MySql)) + { + optionsBuilder.UseMySql( + connectionString, + ServerVersion.AutoDetect(connectionString), + b => b.MigrationsAssembly("MySqlMigrations")); + } + return new DatabaseContext(optionsBuilder.Options); + } + } +} diff --git a/util/MySqlMigrations/Migrations/20210617183900_Init.Designer.cs b/util/MySqlMigrations/Migrations/20210617183900_Init.Designer.cs new file mode 100644 index 0000000000..932da8d2bb --- /dev/null +++ b/util/MySqlMigrations/Migrations/20210617183900_Init.Designer.cs @@ -0,0 +1,1522 @@ +// <auto-generated /> +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20210617183900_Init")] + partial class Init + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("Attachments") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Favorites") + .HasColumnType("longtext"); + + b.Property<string>("Folders") + .HasColumnType("longtext"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<Guid?>("GranteeId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GrantorId") + .HasColumnType("char(36)"); + + b.Property<string>("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property<DateTime?>("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Event", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ActingUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CipherId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("Date") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("GroupId") + .HasColumnType("char(36)"); + + b.Property<string>("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("PolicyId") + .HasColumnType("char(36)"); + + b.Property<int>("Type") + .HasColumnType("int"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Grant", b => + { + b.Property<string>("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<string>("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Installation", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<string>("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property<string>("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<short?>("MaxCollections") + .HasColumnType("smallint"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<int?>("Seats") + .HasColumnType("int"); + + b.Property<bool>("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<bool>("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<string>("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("BillingEmail") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property<string>("BusinessCountry") + .HasColumnType("longtext"); + + b.Property<string>("BusinessName") + .HasColumnType("longtext"); + + b.Property<string>("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<Guid>("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderOrganizationId"); + + b.HasIndex("ProviderUserId"); + + b.ToTable("ProviderOrganizationProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasColumnType("longtext"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<int>("AccessCount") + .HasColumnType("int"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime>("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool?>("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<int?>("MaxAccessCount") + .HasColumnType("int"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.TaxRate", b => + { + b.Property<string>("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property<bool>("Active") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<decimal>("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property<string>("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<decimal>("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property<bool?>("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property<decimal?>("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property<string>("AppId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Challenge") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("KeyHandle") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.Property<string>("Version") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property<string>("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property<string>("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("KdfIterations") + .HasColumnType("int"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<bool>("Premium") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime?>("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<string>("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", "ProviderOrganization") + .WithMany() + .HasForeignKey("ProviderOrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderUser", "ProviderUser") + .WithMany() + .HasForeignKey("ProviderUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProviderOrganization"); + + b.Navigation("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20210617183900_Init.cs b/util/MySqlMigrations/Migrations/20210617183900_Init.cs new file mode 100644 index 0000000000..2da6edc446 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20210617183900_Init.cs @@ -0,0 +1,1130 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Bit.MySqlMigrations.Migrations +{ + public partial class Init : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Event", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Date = table.Column<DateTime>(type: "datetime(6)", nullable: false), + Type = table.Column<int>(type: "int", nullable: false), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + CipherId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + CollectionId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + PolicyId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + GroupId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + OrganizationUserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + DeviceType = table.Column<byte>(type: "tinyint unsigned", nullable: true), + IpAddress = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ActingUserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci") + }, + constraints: table => + { + table.PrimaryKey("PK_Event", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Grant", + columns: table => new + { + Key = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Type = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + SubjectId = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + SessionId = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ClientId = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + ExpirationDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + ConsumedDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + Data = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Grant", x => x.Key); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Installation", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Key = table.Column<string>(type: "varchar(150)", maxLength: 150, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Enabled = table.Column<bool>(type: "tinyint(1)", nullable: false), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Installation", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Organization", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Identifier = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessName = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessAddress1 = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessAddress2 = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessAddress3 = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessCountry = table.Column<string>(type: "varchar(2)", maxLength: 2, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessTaxNumber = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BillingEmail = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Plan = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PlanType = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Seats = table.Column<int>(type: "int", nullable: true), + MaxCollections = table.Column<short>(type: "smallint", nullable: true), + UsePolicies = table.Column<bool>(type: "tinyint(1)", nullable: false), + UseSso = table.Column<bool>(type: "tinyint(1)", nullable: false), + UseGroups = table.Column<bool>(type: "tinyint(1)", nullable: false), + UseDirectory = table.Column<bool>(type: "tinyint(1)", nullable: false), + UseEvents = table.Column<bool>(type: "tinyint(1)", nullable: false), + UseTotp = table.Column<bool>(type: "tinyint(1)", nullable: false), + Use2fa = table.Column<bool>(type: "tinyint(1)", nullable: false), + UseApi = table.Column<bool>(type: "tinyint(1)", nullable: false), + UseResetPassword = table.Column<bool>(type: "tinyint(1)", nullable: false), + SelfHost = table.Column<bool>(type: "tinyint(1)", nullable: false), + UsersGetPremium = table.Column<bool>(type: "tinyint(1)", nullable: false), + Storage = table.Column<long>(type: "bigint", nullable: true), + MaxStorageGb = table.Column<short>(type: "smallint", nullable: true), + Gateway = table.Column<byte>(type: "tinyint unsigned", nullable: true), + GatewayCustomerId = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + GatewaySubscriptionId = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ReferenceData = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Enabled = table.Column<bool>(type: "tinyint(1)", nullable: false), + LicenseKey = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ApiKey = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PublicKey = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PrivateKey = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + TwoFactorProviders = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ExpirationDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Organization", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Provider", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessName = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessAddress1 = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessAddress2 = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessAddress3 = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessCountry = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BusinessTaxNumber = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BillingEmail = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Status = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Enabled = table.Column<bool>(type: "tinyint(1)", nullable: false), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Provider", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "TaxRate", + columns: table => new + { + Id = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Country = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + State = table.Column<string>(type: "varchar(2)", maxLength: 2, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PostalCode = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Rate = table.Column<decimal>(type: "decimal(65,30)", nullable: false), + Active = table.Column<bool>(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TaxRate", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "User", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + EmailVerified = table.Column<bool>(type: "tinyint(1)", nullable: false), + MasterPassword = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + MasterPasswordHint = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Culture = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + SecurityStamp = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + TwoFactorProviders = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + TwoFactorRecoveryCode = table.Column<string>(type: "varchar(32)", maxLength: 32, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + EquivalentDomains = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ExcludedGlobalEquivalentDomains = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + AccountRevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + Key = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PublicKey = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PrivateKey = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Premium = table.Column<bool>(type: "tinyint(1)", nullable: false), + PremiumExpirationDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + RenewalReminderDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + Storage = table.Column<long>(type: "bigint", nullable: true), + MaxStorageGb = table.Column<short>(type: "smallint", nullable: true), + Gateway = table.Column<byte>(type: "tinyint unsigned", nullable: true), + GatewayCustomerId = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + GatewaySubscriptionId = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ReferenceData = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + LicenseKey = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ApiKey = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Kdf = table.Column<byte>(type: "tinyint unsigned", nullable: false), + KdfIterations = table.Column<int>(type: "int", nullable: false), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_User", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Collection", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ExternalId = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Collection", x => x.Id); + table.ForeignKey( + name: "FK_Collection_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Group", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + AccessAll = table.Column<bool>(type: "tinyint(1)", nullable: false), + ExternalId = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Group", x => x.Id); + table.ForeignKey( + name: "FK_Group_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Policy", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Data = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Enabled = table.Column<bool>(type: "tinyint(1)", nullable: false), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Policy", x => x.Id); + table.ForeignKey( + name: "FK_Policy_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "SsoConfig", + columns: table => new + { + Id = table.Column<long>(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Enabled = table.Column<bool>(type: "tinyint(1)", nullable: false), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Data = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SsoConfig", x => x.Id); + table.ForeignKey( + name: "FK_SsoConfig_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ProviderOrganization", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ProviderId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Key = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Settings = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProviderOrganization", x => x.Id); + table.ForeignKey( + name: "FK_ProviderOrganization_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProviderOrganization_Provider_ProviderId", + column: x => x.ProviderId, + principalTable: "Provider", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Cipher", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Data = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Favorites = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Folders = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Attachments = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + DeletedDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + Reprompt = table.Column<byte>(type: "tinyint unsigned", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Cipher", x => x.Id); + table.ForeignKey( + name: "FK_Cipher_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Cipher_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Device", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Identifier = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PushToken = table.Column<string>(type: "varchar(255)", maxLength: 255, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Device", x => x.Id); + table.ForeignKey( + name: "FK_Device_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "EmergencyAccess", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + GrantorId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + GranteeId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + KeyEncrypted = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Status = table.Column<byte>(type: "tinyint unsigned", nullable: false), + WaitTimeDays = table.Column<int>(type: "int", nullable: false), + RecoveryInitiatedDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + LastNotificationDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EmergencyAccess", x => x.Id); + table.ForeignKey( + name: "FK_EmergencyAccess_User_GranteeId", + column: x => x.GranteeId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_EmergencyAccess_User_GrantorId", + column: x => x.GrantorId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Folder", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Folder", x => x.Id); + table.ForeignKey( + name: "FK_Folder_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "OrganizationUser", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Key = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ResetPasswordKey = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Status = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + AccessAll = table.Column<bool>(type: "tinyint(1)", nullable: false), + ExternalId = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + Permissions = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationUser", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationUser_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrganizationUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ProviderUser", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ProviderId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Email = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Key = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Status = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Permissions = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProviderUser", x => x.Id); + table.ForeignKey( + name: "FK_ProviderUser_Provider_ProviderId", + column: x => x.ProviderId, + principalTable: "Provider", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProviderUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Send", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Data = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Key = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Password = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + MaxAccessCount = table.Column<int>(type: "int", nullable: true), + AccessCount = table.Column<int>(type: "int", nullable: false), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + ExpirationDate = table.Column<DateTime>(type: "datetime(6)", nullable: true), + DeletionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + Disabled = table.Column<bool>(type: "tinyint(1)", nullable: false), + HideEmail = table.Column<bool>(type: "tinyint(1)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Send", x => x.Id); + table.ForeignKey( + name: "FK_Send_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Send_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "SsoUser", + columns: table => new + { + Id = table.Column<long>(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + ExternalId = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SsoUser", x => x.Id); + table.ForeignKey( + name: "FK_SsoUser_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SsoUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Transaction", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + OrganizationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Amount = table.Column<decimal>(type: "decimal(65,30)", nullable: false), + Refunded = table.Column<bool>(type: "tinyint(1)", nullable: true), + RefundedAmount = table.Column<decimal>(type: "decimal(65,30)", nullable: true), + Details = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PaymentMethodType = table.Column<byte>(type: "tinyint unsigned", nullable: true), + Gateway = table.Column<byte>(type: "tinyint unsigned", nullable: true), + GatewayId = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Transaction", x => x.Id); + table.ForeignKey( + name: "FK_Transaction_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Transaction_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "U2f", + columns: table => new + { + Id = table.Column<int>(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + KeyHandle = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Challenge = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + AppId = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Version = table.Column<string>(type: "varchar(20)", maxLength: 20, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_U2f", x => x.Id); + table.ForeignKey( + name: "FK_U2f_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "CollectionGroups", + columns: table => new + { + CollectionId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + GroupId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ReadOnly = table.Column<bool>(type: "tinyint(1)", nullable: false), + HidePasswords = table.Column<bool>(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionGroups", x => new { x.CollectionId, x.GroupId }); + table.ForeignKey( + name: "FK_CollectionGroups_Collection_CollectionId", + column: x => x.CollectionId, + principalTable: "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionGroups_Group_GroupId", + column: x => x.GroupId, + principalTable: "Group", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "CollectionCipher", + columns: table => new + { + CollectionId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + CipherId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci") + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionCipher", x => new { x.CollectionId, x.CipherId }); + table.ForeignKey( + name: "FK_CollectionCipher_Cipher_CipherId", + column: x => x.CipherId, + principalTable: "Cipher", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionCipher_Collection_CollectionId", + column: x => x.CollectionId, + principalTable: "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "CollectionUsers", + columns: table => new + { + CollectionId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationUserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + ReadOnly = table.Column<bool>(type: "tinyint(1)", nullable: false), + HidePasswords = table.Column<bool>(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionUsers", x => new { x.CollectionId, x.OrganizationUserId }); + table.ForeignKey( + name: "FK_CollectionUsers_Collection_CollectionId", + column: x => x.CollectionId, + principalTable: "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionUsers_OrganizationUser_OrganizationUserId", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionUsers_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "GroupUser", + columns: table => new + { + GroupId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationUserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci") + }, + constraints: table => + { + table.PrimaryKey("PK_GroupUser", x => new { x.GroupId, x.OrganizationUserId }); + table.ForeignKey( + name: "FK_GroupUser_Group_GroupId", + column: x => x.GroupId, + principalTable: "Group", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GroupUser_OrganizationUser_OrganizationUserId", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GroupUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ProviderOrganizationProviderUser", + columns: table => new + { + Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ProviderOrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ProviderUserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Type = table.Column<byte>(type: "tinyint unsigned", nullable: false), + Permissions = table.Column<string>(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false), + RevisionDate = table.Column<DateTime>(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProviderOrganizationProviderUser", x => x.Id); + table.ForeignKey( + name: "FK_ProviderOrganizationProviderUser_ProviderOrganization_Provid~", + column: x => x.ProviderOrganizationId, + principalTable: "ProviderOrganization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProviderOrganizationProviderUser_ProviderUser_ProviderUserId", + column: x => x.ProviderUserId, + principalTable: "ProviderUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_Cipher_OrganizationId", + table: "Cipher", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Cipher_UserId", + table: "Cipher", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Collection_OrganizationId", + table: "Collection", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionCipher_CipherId", + table: "CollectionCipher", + column: "CipherId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionGroups_GroupId", + table: "CollectionGroups", + column: "GroupId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionUsers_OrganizationUserId", + table: "CollectionUsers", + column: "OrganizationUserId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionUsers_UserId", + table: "CollectionUsers", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Device_UserId", + table: "Device", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_EmergencyAccess_GranteeId", + table: "EmergencyAccess", + column: "GranteeId"); + + migrationBuilder.CreateIndex( + name: "IX_EmergencyAccess_GrantorId", + table: "EmergencyAccess", + column: "GrantorId"); + + migrationBuilder.CreateIndex( + name: "IX_Folder_UserId", + table: "Folder", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Group_OrganizationId", + table: "Group", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_GroupUser_OrganizationUserId", + table: "GroupUser", + column: "OrganizationUserId"); + + migrationBuilder.CreateIndex( + name: "IX_GroupUser_UserId", + table: "GroupUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationUser_OrganizationId", + table: "OrganizationUser", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationUser_UserId", + table: "OrganizationUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Policy_OrganizationId", + table: "Policy", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganization_OrganizationId", + table: "ProviderOrganization", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganization_ProviderId", + table: "ProviderOrganization", + column: "ProviderId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganizationProviderUser_ProviderOrganizationId", + table: "ProviderOrganizationProviderUser", + column: "ProviderOrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganizationProviderUser_ProviderUserId", + table: "ProviderOrganizationProviderUser", + column: "ProviderUserId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderUser_ProviderId", + table: "ProviderUser", + column: "ProviderId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderUser_UserId", + table: "ProviderUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Send_OrganizationId", + table: "Send", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Send_UserId", + table: "Send", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_SsoConfig_OrganizationId", + table: "SsoConfig", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_SsoUser_OrganizationId", + table: "SsoUser", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_SsoUser_UserId", + table: "SsoUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Transaction_OrganizationId", + table: "Transaction", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Transaction_UserId", + table: "Transaction", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_U2f_UserId", + table: "U2f", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CollectionCipher"); + + migrationBuilder.DropTable( + name: "CollectionGroups"); + + migrationBuilder.DropTable( + name: "CollectionUsers"); + + migrationBuilder.DropTable( + name: "Device"); + + migrationBuilder.DropTable( + name: "EmergencyAccess"); + + migrationBuilder.DropTable( + name: "Event"); + + migrationBuilder.DropTable( + name: "Folder"); + + migrationBuilder.DropTable( + name: "Grant"); + + migrationBuilder.DropTable( + name: "GroupUser"); + + migrationBuilder.DropTable( + name: "Installation"); + + migrationBuilder.DropTable( + name: "Policy"); + + migrationBuilder.DropTable( + name: "ProviderOrganizationProviderUser"); + + migrationBuilder.DropTable( + name: "Send"); + + migrationBuilder.DropTable( + name: "SsoConfig"); + + migrationBuilder.DropTable( + name: "SsoUser"); + + migrationBuilder.DropTable( + name: "TaxRate"); + + migrationBuilder.DropTable( + name: "Transaction"); + + migrationBuilder.DropTable( + name: "U2f"); + + migrationBuilder.DropTable( + name: "Cipher"); + + migrationBuilder.DropTable( + name: "Collection"); + + migrationBuilder.DropTable( + name: "Group"); + + migrationBuilder.DropTable( + name: "OrganizationUser"); + + migrationBuilder.DropTable( + name: "ProviderOrganization"); + + migrationBuilder.DropTable( + name: "ProviderUser"); + + migrationBuilder.DropTable( + name: "Organization"); + + migrationBuilder.DropTable( + name: "Provider"); + + migrationBuilder.DropTable( + name: "User"); + } + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..244f0865df --- /dev/null +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -0,0 +1,1520 @@ +// <auto-generated /> +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("Attachments") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Favorites") + .HasColumnType("longtext"); + + b.Property<string>("Folders") + .HasColumnType("longtext"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<Guid?>("GranteeId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GrantorId") + .HasColumnType("char(36)"); + + b.Property<string>("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property<DateTime?>("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Event", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ActingUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CipherId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("Date") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("GroupId") + .HasColumnType("char(36)"); + + b.Property<string>("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("PolicyId") + .HasColumnType("char(36)"); + + b.Property<int>("Type") + .HasColumnType("int"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Grant", b => + { + b.Property<string>("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<string>("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Installation", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<string>("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property<string>("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<short?>("MaxCollections") + .HasColumnType("smallint"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<int?>("Seats") + .HasColumnType("int"); + + b.Property<bool>("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<bool>("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<string>("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("BillingEmail") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property<string>("BusinessCountry") + .HasColumnType("longtext"); + + b.Property<string>("BusinessName") + .HasColumnType("longtext"); + + b.Property<string>("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<Guid>("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderOrganizationId"); + + b.HasIndex("ProviderUserId"); + + b.ToTable("ProviderOrganizationProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasColumnType("longtext"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<int>("AccessCount") + .HasColumnType("int"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime>("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool?>("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<int?>("MaxAccessCount") + .HasColumnType("int"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.TaxRate", b => + { + b.Property<string>("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property<bool>("Active") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<decimal>("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property<string>("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<decimal>("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property<bool?>("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property<decimal?>("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property<string>("AppId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Challenge") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("KeyHandle") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.Property<string>("Version") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property<string>("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property<string>("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("KdfIterations") + .HasColumnType("int"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<bool>("Premium") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime?>("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<string>("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", "ProviderOrganization") + .WithMany() + .HasForeignKey("ProviderOrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderUser", "ProviderUser") + .WithMany() + .HasForeignKey("ProviderUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProviderOrganization"); + + b.Navigation("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/MySqlMigrations.csproj b/util/MySqlMigrations/MySqlMigrations.csproj new file mode 100644 index 0000000000..d07adb1dc6 --- /dev/null +++ b/util/MySqlMigrations/MySqlMigrations.csproj @@ -0,0 +1,19 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Core\Core.csproj" /> + <ProjectReference Include="..\..\src\Api\Api.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + </ItemGroup> + +</Project> diff --git a/util/MySqlMigrations/Scripts/Init.sql b/util/MySqlMigrations/Scripts/Init.sql new file mode 100644 index 0000000000..9dc51948a4 --- /dev/null +++ b/util/MySqlMigrations/Scripts/Init.sql @@ -0,0 +1,491 @@ +ALTER DATABASE CHARACTER SET utf8mb4; +CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( + `MigrationId` varchar(150) CHARACTER SET utf8mb4 NOT NULL, + `ProductVersion` varchar(32) CHARACTER SET utf8mb4 NOT NULL, + CONSTRAINT `PK___EFMigrationsHistory` PRIMARY KEY (`MigrationId`) +) CHARACTER SET utf8mb4; + +START TRANSACTION; + +ALTER DATABASE CHARACTER SET utf8mb4; + +CREATE TABLE `Event` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `Date` datetime(6) NOT NULL, + `Type` int NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NULL, + `CipherId` char(36) COLLATE ascii_general_ci NULL, + `CollectionId` char(36) COLLATE ascii_general_ci NULL, + `PolicyId` char(36) COLLATE ascii_general_ci NULL, + `GroupId` char(36) COLLATE ascii_general_ci NULL, + `OrganizationUserId` char(36) COLLATE ascii_general_ci NULL, + `DeviceType` tinyint unsigned NULL, + `IpAddress` varchar(50) CHARACTER SET utf8mb4 NULL, + `ActingUserId` char(36) COLLATE ascii_general_ci NULL, + CONSTRAINT `PK_Event` PRIMARY KEY (`Id`) +) CHARACTER SET utf8mb4; + +CREATE TABLE `Grant` ( + `Key` varchar(200) CHARACTER SET utf8mb4 NOT NULL, + `Type` varchar(50) CHARACTER SET utf8mb4 NULL, + `SubjectId` varchar(200) CHARACTER SET utf8mb4 NULL, + `SessionId` varchar(100) CHARACTER SET utf8mb4 NULL, + `ClientId` varchar(200) CHARACTER SET utf8mb4 NULL, + `Description` varchar(200) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `ExpirationDate` datetime(6) NULL, + `ConsumedDate` datetime(6) NULL, + `Data` longtext CHARACTER SET utf8mb4 NULL, + CONSTRAINT `PK_Grant` PRIMARY KEY (`Key`) +) CHARACTER SET utf8mb4; + +CREATE TABLE `Installation` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `Email` varchar(256) CHARACTER SET utf8mb4 NULL, + `Key` varchar(150) CHARACTER SET utf8mb4 NULL, + `Enabled` tinyint(1) NOT NULL, + `CreationDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Installation` PRIMARY KEY (`Id`) +) CHARACTER SET utf8mb4; + +CREATE TABLE `Organization` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `Identifier` varchar(50) CHARACTER SET utf8mb4 NULL, + `Name` varchar(50) CHARACTER SET utf8mb4 NULL, + `BusinessName` varchar(50) CHARACTER SET utf8mb4 NULL, + `BusinessAddress1` varchar(50) CHARACTER SET utf8mb4 NULL, + `BusinessAddress2` varchar(50) CHARACTER SET utf8mb4 NULL, + `BusinessAddress3` varchar(50) CHARACTER SET utf8mb4 NULL, + `BusinessCountry` varchar(2) CHARACTER SET utf8mb4 NULL, + `BusinessTaxNumber` varchar(30) CHARACTER SET utf8mb4 NULL, + `BillingEmail` varchar(256) CHARACTER SET utf8mb4 NULL, + `Plan` varchar(50) CHARACTER SET utf8mb4 NULL, + `PlanType` tinyint unsigned NOT NULL, + `Seats` int NULL, + `MaxCollections` smallint NULL, + `UsePolicies` tinyint(1) NOT NULL, + `UseSso` tinyint(1) NOT NULL, + `UseGroups` tinyint(1) NOT NULL, + `UseDirectory` tinyint(1) NOT NULL, + `UseEvents` tinyint(1) NOT NULL, + `UseTotp` tinyint(1) NOT NULL, + `Use2fa` tinyint(1) NOT NULL, + `UseApi` tinyint(1) NOT NULL, + `UseResetPassword` tinyint(1) NOT NULL, + `SelfHost` tinyint(1) NOT NULL, + `UsersGetPremium` tinyint(1) NOT NULL, + `Storage` bigint NULL, + `MaxStorageGb` smallint NULL, + `Gateway` tinyint unsigned NULL, + `GatewayCustomerId` varchar(50) CHARACTER SET utf8mb4 NULL, + `GatewaySubscriptionId` varchar(50) CHARACTER SET utf8mb4 NULL, + `ReferenceData` longtext CHARACTER SET utf8mb4 NULL, + `Enabled` tinyint(1) NOT NULL, + `LicenseKey` varchar(100) CHARACTER SET utf8mb4 NULL, + `ApiKey` varchar(30) CHARACTER SET utf8mb4 NULL, + `PublicKey` longtext CHARACTER SET utf8mb4 NULL, + `PrivateKey` longtext CHARACTER SET utf8mb4 NULL, + `TwoFactorProviders` longtext CHARACTER SET utf8mb4 NULL, + `ExpirationDate` datetime(6) NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Organization` PRIMARY KEY (`Id`) +) CHARACTER SET utf8mb4; + +CREATE TABLE `Provider` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `Name` longtext CHARACTER SET utf8mb4 NULL, + `BusinessName` longtext CHARACTER SET utf8mb4 NULL, + `BusinessAddress1` longtext CHARACTER SET utf8mb4 NULL, + `BusinessAddress2` longtext CHARACTER SET utf8mb4 NULL, + `BusinessAddress3` longtext CHARACTER SET utf8mb4 NULL, + `BusinessCountry` longtext CHARACTER SET utf8mb4 NULL, + `BusinessTaxNumber` longtext CHARACTER SET utf8mb4 NULL, + `BillingEmail` longtext CHARACTER SET utf8mb4 NULL, + `Status` tinyint unsigned NOT NULL, + `Enabled` tinyint(1) NOT NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Provider` PRIMARY KEY (`Id`) +) CHARACTER SET utf8mb4; + +CREATE TABLE `TaxRate` ( + `Id` varchar(40) CHARACTER SET utf8mb4 NOT NULL, + `Country` varchar(50) CHARACTER SET utf8mb4 NULL, + `State` varchar(2) CHARACTER SET utf8mb4 NULL, + `PostalCode` varchar(10) CHARACTER SET utf8mb4 NULL, + `Rate` decimal(65,30) NOT NULL, + `Active` tinyint(1) NOT NULL, + CONSTRAINT `PK_TaxRate` PRIMARY KEY (`Id`) +) CHARACTER SET utf8mb4; + +CREATE TABLE `User` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `Name` varchar(50) CHARACTER SET utf8mb4 NULL, + `Email` varchar(256) CHARACTER SET utf8mb4 NOT NULL, + `EmailVerified` tinyint(1) NOT NULL, + `MasterPassword` varchar(300) CHARACTER SET utf8mb4 NULL, + `MasterPasswordHint` varchar(50) CHARACTER SET utf8mb4 NULL, + `Culture` varchar(10) CHARACTER SET utf8mb4 NULL, + `SecurityStamp` varchar(50) CHARACTER SET utf8mb4 NOT NULL, + `TwoFactorProviders` longtext CHARACTER SET utf8mb4 NULL, + `TwoFactorRecoveryCode` varchar(32) CHARACTER SET utf8mb4 NULL, + `EquivalentDomains` longtext CHARACTER SET utf8mb4 NULL, + `ExcludedGlobalEquivalentDomains` longtext CHARACTER SET utf8mb4 NULL, + `AccountRevisionDate` datetime(6) NOT NULL, + `Key` longtext CHARACTER SET utf8mb4 NULL, + `PublicKey` longtext CHARACTER SET utf8mb4 NULL, + `PrivateKey` longtext CHARACTER SET utf8mb4 NULL, + `Premium` tinyint(1) NOT NULL, + `PremiumExpirationDate` datetime(6) NULL, + `RenewalReminderDate` datetime(6) NULL, + `Storage` bigint NULL, + `MaxStorageGb` smallint NULL, + `Gateway` tinyint unsigned NULL, + `GatewayCustomerId` varchar(50) CHARACTER SET utf8mb4 NULL, + `GatewaySubscriptionId` varchar(50) CHARACTER SET utf8mb4 NULL, + `ReferenceData` longtext CHARACTER SET utf8mb4 NULL, + `LicenseKey` varchar(100) CHARACTER SET utf8mb4 NULL, + `ApiKey` varchar(30) CHARACTER SET utf8mb4 NOT NULL, + `Kdf` tinyint unsigned NOT NULL, + `KdfIterations` int NOT NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_User` PRIMARY KEY (`Id`) +) CHARACTER SET utf8mb4; + +CREATE TABLE `Collection` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `Name` longtext CHARACTER SET utf8mb4 NULL, + `ExternalId` varchar(300) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Collection` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Collection_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `Group` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `Name` varchar(100) CHARACTER SET utf8mb4 NULL, + `AccessAll` tinyint(1) NOT NULL, + `ExternalId` varchar(300) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Group` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Group_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `Policy` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `Type` tinyint unsigned NOT NULL, + `Data` longtext CHARACTER SET utf8mb4 NULL, + `Enabled` tinyint(1) NOT NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Policy` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Policy_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `SsoConfig` ( + `Id` bigint NOT NULL AUTO_INCREMENT, + `Enabled` tinyint(1) NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `Data` longtext CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_SsoConfig` PRIMARY KEY (`Id`), + CONSTRAINT `FK_SsoConfig_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `ProviderOrganization` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `ProviderId` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `Key` longtext CHARACTER SET utf8mb4 NULL, + `Settings` longtext CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_ProviderOrganization` PRIMARY KEY (`Id`), + CONSTRAINT `FK_ProviderOrganization_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_ProviderOrganization_Provider_ProviderId` FOREIGN KEY (`ProviderId`) REFERENCES `Provider` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `Cipher` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NULL, + `Type` tinyint unsigned NOT NULL, + `Data` longtext CHARACTER SET utf8mb4 NULL, + `Favorites` longtext CHARACTER SET utf8mb4 NULL, + `Folders` longtext CHARACTER SET utf8mb4 NULL, + `Attachments` longtext CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + `DeletedDate` datetime(6) NULL, + `Reprompt` tinyint unsigned NULL, + CONSTRAINT `PK_Cipher` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Cipher_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_Cipher_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +) CHARACTER SET utf8mb4; + +CREATE TABLE `Device` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NOT NULL, + `Name` varchar(50) CHARACTER SET utf8mb4 NULL, + `Type` tinyint unsigned NOT NULL, + `Identifier` varchar(50) CHARACTER SET utf8mb4 NULL, + `PushToken` varchar(255) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Device` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Device_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `EmergencyAccess` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `GrantorId` char(36) COLLATE ascii_general_ci NOT NULL, + `GranteeId` char(36) COLLATE ascii_general_ci NULL, + `Email` varchar(256) CHARACTER SET utf8mb4 NULL, + `KeyEncrypted` longtext CHARACTER SET utf8mb4 NULL, + `Type` tinyint unsigned NOT NULL, + `Status` tinyint unsigned NOT NULL, + `WaitTimeDays` int NOT NULL, + `RecoveryInitiatedDate` datetime(6) NULL, + `LastNotificationDate` datetime(6) NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_EmergencyAccess` PRIMARY KEY (`Id`), + CONSTRAINT `FK_EmergencyAccess_User_GranteeId` FOREIGN KEY (`GranteeId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_EmergencyAccess_User_GrantorId` FOREIGN KEY (`GrantorId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `Folder` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NOT NULL, + `Name` longtext CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Folder` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Folder_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `OrganizationUser` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + `Email` varchar(256) CHARACTER SET utf8mb4 NULL, + `Key` longtext CHARACTER SET utf8mb4 NULL, + `ResetPasswordKey` longtext CHARACTER SET utf8mb4 NULL, + `Status` tinyint unsigned NOT NULL, + `Type` tinyint unsigned NOT NULL, + `AccessAll` tinyint(1) NOT NULL, + `ExternalId` varchar(300) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + `Permissions` longtext CHARACTER SET utf8mb4 NULL, + CONSTRAINT `PK_OrganizationUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_OrganizationUser_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_OrganizationUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +) CHARACTER SET utf8mb4; + +CREATE TABLE `ProviderUser` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `ProviderId` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + `Email` longtext CHARACTER SET utf8mb4 NULL, + `Key` longtext CHARACTER SET utf8mb4 NULL, + `Status` tinyint unsigned NOT NULL, + `Type` tinyint unsigned NOT NULL, + `Permissions` longtext CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_ProviderUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_ProviderUser_Provider_ProviderId` FOREIGN KEY (`ProviderId`) REFERENCES `Provider` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_ProviderUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +) CHARACTER SET utf8mb4; + +CREATE TABLE `Send` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NULL, + `Type` tinyint unsigned NOT NULL, + `Data` longtext CHARACTER SET utf8mb4 NULL, + `Key` longtext CHARACTER SET utf8mb4 NULL, + `Password` varchar(300) CHARACTER SET utf8mb4 NULL, + `MaxAccessCount` int NULL, + `AccessCount` int NOT NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + `ExpirationDate` datetime(6) NULL, + `DeletionDate` datetime(6) NOT NULL, + `Disabled` tinyint(1) NOT NULL, + `HideEmail` tinyint(1) NULL, + CONSTRAINT `PK_Send` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Send_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_Send_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +) CHARACTER SET utf8mb4; + +CREATE TABLE `SsoUser` ( + `Id` bigint NOT NULL AUTO_INCREMENT, + `UserId` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NULL, + `ExternalId` varchar(50) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + CONSTRAINT `PK_SsoUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_SsoUser_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_SsoUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `Transaction` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NULL, + `Type` tinyint unsigned NOT NULL, + `Amount` decimal(65,30) NOT NULL, + `Refunded` tinyint(1) NULL, + `RefundedAmount` decimal(65,30) NULL, + `Details` varchar(100) CHARACTER SET utf8mb4 NULL, + `PaymentMethodType` tinyint unsigned NULL, + `Gateway` tinyint unsigned NULL, + `GatewayId` varchar(50) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + CONSTRAINT `PK_Transaction` PRIMARY KEY (`Id`), + CONSTRAINT `FK_Transaction_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT, + CONSTRAINT `FK_Transaction_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +) CHARACTER SET utf8mb4; + +CREATE TABLE `U2f` ( + `Id` int NOT NULL AUTO_INCREMENT, + `UserId` char(36) COLLATE ascii_general_ci NOT NULL, + `KeyHandle` varchar(200) CHARACTER SET utf8mb4 NULL, + `Challenge` varchar(200) CHARACTER SET utf8mb4 NULL, + `AppId` varchar(50) CHARACTER SET utf8mb4 NULL, + `Version` varchar(20) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + CONSTRAINT `PK_U2f` PRIMARY KEY (`Id`), + CONSTRAINT `FK_U2f_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `CollectionGroups` ( + `CollectionId` char(36) COLLATE ascii_general_ci NOT NULL, + `GroupId` char(36) COLLATE ascii_general_ci NOT NULL, + `ReadOnly` tinyint(1) NOT NULL, + `HidePasswords` tinyint(1) NOT NULL, + CONSTRAINT `PK_CollectionGroups` PRIMARY KEY (`CollectionId`, `GroupId`), + CONSTRAINT `FK_CollectionGroups_Collection_CollectionId` FOREIGN KEY (`CollectionId`) REFERENCES `Collection` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionGroups_Group_GroupId` FOREIGN KEY (`GroupId`) REFERENCES `Group` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `CollectionCipher` ( + `CollectionId` char(36) COLLATE ascii_general_ci NOT NULL, + `CipherId` char(36) COLLATE ascii_general_ci NOT NULL, + CONSTRAINT `PK_CollectionCipher` PRIMARY KEY (`CollectionId`, `CipherId`), + CONSTRAINT `FK_CollectionCipher_Cipher_CipherId` FOREIGN KEY (`CipherId`) REFERENCES `Cipher` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionCipher_Collection_CollectionId` FOREIGN KEY (`CollectionId`) REFERENCES `Collection` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE TABLE `CollectionUsers` ( + `CollectionId` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationUserId` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + `ReadOnly` tinyint(1) NOT NULL, + `HidePasswords` tinyint(1) NOT NULL, + CONSTRAINT `PK_CollectionUsers` PRIMARY KEY (`CollectionId`, `OrganizationUserId`), + CONSTRAINT `FK_CollectionUsers_Collection_CollectionId` FOREIGN KEY (`CollectionId`) REFERENCES `Collection` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionUsers_OrganizationUser_OrganizationUserId` FOREIGN KEY (`OrganizationUserId`) REFERENCES `OrganizationUser` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_CollectionUsers_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +) CHARACTER SET utf8mb4; + +CREATE TABLE `GroupUser` ( + `GroupId` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationUserId` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NULL, + CONSTRAINT `PK_GroupUser` PRIMARY KEY (`GroupId`, `OrganizationUserId`), + CONSTRAINT `FK_GroupUser_Group_GroupId` FOREIGN KEY (`GroupId`) REFERENCES `Group` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_GroupUser_OrganizationUser_OrganizationUserId` FOREIGN KEY (`OrganizationUserId`) REFERENCES `OrganizationUser` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_GroupUser_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE RESTRICT +) CHARACTER SET utf8mb4; + +CREATE TABLE `ProviderOrganizationProviderUser` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `ProviderOrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `ProviderUserId` char(36) COLLATE ascii_general_ci NOT NULL, + `Type` tinyint unsigned NOT NULL, + `Permissions` longtext CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `RevisionDate` datetime(6) NOT NULL, + CONSTRAINT `PK_ProviderOrganizationProviderUser` PRIMARY KEY (`Id`), + CONSTRAINT `FK_ProviderOrganizationProviderUser_ProviderOrganization_Provid~` FOREIGN KEY (`ProviderOrganizationId`) REFERENCES `ProviderOrganization` (`Id`) ON DELETE CASCADE, + CONSTRAINT `FK_ProviderOrganizationProviderUser_ProviderUser_ProviderUserId` FOREIGN KEY (`ProviderUserId`) REFERENCES `ProviderUser` (`Id`) ON DELETE CASCADE +) CHARACTER SET utf8mb4; + +CREATE INDEX `IX_Cipher_OrganizationId` ON `Cipher` (`OrganizationId`); + +CREATE INDEX `IX_Cipher_UserId` ON `Cipher` (`UserId`); + +CREATE INDEX `IX_Collection_OrganizationId` ON `Collection` (`OrganizationId`); + +CREATE INDEX `IX_CollectionCipher_CipherId` ON `CollectionCipher` (`CipherId`); + +CREATE INDEX `IX_CollectionGroups_GroupId` ON `CollectionGroups` (`GroupId`); + +CREATE INDEX `IX_CollectionUsers_OrganizationUserId` ON `CollectionUsers` (`OrganizationUserId`); + +CREATE INDEX `IX_CollectionUsers_UserId` ON `CollectionUsers` (`UserId`); + +CREATE INDEX `IX_Device_UserId` ON `Device` (`UserId`); + +CREATE INDEX `IX_EmergencyAccess_GranteeId` ON `EmergencyAccess` (`GranteeId`); + +CREATE INDEX `IX_EmergencyAccess_GrantorId` ON `EmergencyAccess` (`GrantorId`); + +CREATE INDEX `IX_Folder_UserId` ON `Folder` (`UserId`); + +CREATE INDEX `IX_Group_OrganizationId` ON `Group` (`OrganizationId`); + +CREATE INDEX `IX_GroupUser_OrganizationUserId` ON `GroupUser` (`OrganizationUserId`); + +CREATE INDEX `IX_GroupUser_UserId` ON `GroupUser` (`UserId`); + +CREATE INDEX `IX_OrganizationUser_OrganizationId` ON `OrganizationUser` (`OrganizationId`); + +CREATE INDEX `IX_OrganizationUser_UserId` ON `OrganizationUser` (`UserId`); + +CREATE INDEX `IX_Policy_OrganizationId` ON `Policy` (`OrganizationId`); + +CREATE INDEX `IX_ProviderOrganization_OrganizationId` ON `ProviderOrganization` (`OrganizationId`); + +CREATE INDEX `IX_ProviderOrganization_ProviderId` ON `ProviderOrganization` (`ProviderId`); + +CREATE INDEX `IX_ProviderOrganizationProviderUser_ProviderOrganizationId` ON `ProviderOrganizationProviderUser` (`ProviderOrganizationId`); + +CREATE INDEX `IX_ProviderOrganizationProviderUser_ProviderUserId` ON `ProviderOrganizationProviderUser` (`ProviderUserId`); + +CREATE INDEX `IX_ProviderUser_ProviderId` ON `ProviderUser` (`ProviderId`); + +CREATE INDEX `IX_ProviderUser_UserId` ON `ProviderUser` (`UserId`); + +CREATE INDEX `IX_Send_OrganizationId` ON `Send` (`OrganizationId`); + +CREATE INDEX `IX_Send_UserId` ON `Send` (`UserId`); + +CREATE INDEX `IX_SsoConfig_OrganizationId` ON `SsoConfig` (`OrganizationId`); + +CREATE INDEX `IX_SsoUser_OrganizationId` ON `SsoUser` (`OrganizationId`); + +CREATE INDEX `IX_SsoUser_UserId` ON `SsoUser` (`UserId`); + +CREATE INDEX `IX_Transaction_OrganizationId` ON `Transaction` (`OrganizationId`); + +CREATE INDEX `IX_Transaction_UserId` ON `Transaction` (`UserId`); + +CREATE INDEX `IX_U2f_UserId` ON `U2f` (`UserId`); + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20210617183900_Init', '5.0.5'); + +COMMIT; \ No newline at end of file diff --git a/util/PostgresMigrations/Factories.cs b/util/PostgresMigrations/Factories.cs new file mode 100644 index 0000000000..1fad15f524 --- /dev/null +++ b/util/PostgresMigrations/Factories.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using Bit.Core.Repositories.EntityFramework; +using Bit.Core.Settings; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Bit.Core.Enums; +using Microsoft.EntityFrameworkCore.Design; +using System; + +namespace MySqlMigrations +{ + public static class GlobalSettingsFactory + { + public static GlobalSettings GlobalSettings { get; } = new GlobalSettings(); + static GlobalSettingsFactory() + { + var configBuilder = new ConfigurationBuilder().AddUserSecrets<Bit.Api.Startup>(); + var Configuration = configBuilder.Build(); + ConfigurationBinder.Bind(Configuration.GetSection("GlobalSettings"), GlobalSettings); + } + } + + public class DatabaseContextFactory : IDesignTimeDbContextFactory<DatabaseContext> + { + public DatabaseContext CreateDbContext(string[] args) + { + var globalSettings = GlobalSettingsFactory.GlobalSettings; + var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>(); + + var selectedDatabaseProvider = globalSettings.DatabaseProvider; + var provider = SupportedDatabaseProviders.Postgres; + var connectionString = string.Empty; + if (!string.IsNullOrWhiteSpace(selectedDatabaseProvider)) + { + switch (selectedDatabaseProvider.ToLowerInvariant()) + { + case "postgres": + case "postgresql": + provider = SupportedDatabaseProviders.Postgres; + connectionString = globalSettings.PostgreSql.ConnectionString; + break; + case "mysql": + case "mariadb": + provider = SupportedDatabaseProviders.MySql; + connectionString = globalSettings.MySql.ConnectionString; + break; + default: + throw new Exception("No database provider selected"); + break; + } + } + if (provider.Equals(SupportedDatabaseProviders.Postgres)) + { + optionsBuilder.UseNpgsql( + connectionString, + b => b.MigrationsAssembly("PostgresMigrations")); + } + else if (provider.Equals(SupportedDatabaseProviders.MySql)) + { + optionsBuilder.UseMySql( + connectionString, + ServerVersion.AutoDetect(connectionString), + b => b.MigrationsAssembly("MySqlMigrations")); + } + return new DatabaseContext(optionsBuilder.Options); + } + } +} diff --git a/util/PostgresMigrations/Migrations/20210617163014_Postgres_Init.Designer.cs b/util/PostgresMigrations/Migrations/20210617163014_Postgres_Init.Designer.cs new file mode 100644 index 0000000000..1cdd6c9bd3 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20210617163014_Postgres_Init.Designer.cs @@ -0,0 +1,1531 @@ +// <auto-generated /> +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bit.EntityFrameworkMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20210617163014_Postgres_Init")] + partial class Postgres_Init + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.5") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("Attachments") + .HasColumnType("text"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Favorites") + .HasColumnType("text"); + + b.Property<string>("Folders") + .HasColumnType("text"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<byte?>("Reprompt") + .HasColumnType("smallint"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("uuid"); + + b.Property<Guid>("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("uuid"); + + b.Property<Guid>("GroupId") + .HasColumnType("uuid"); + + b.Property<bool>("HidePasswords") + .HasColumnType("boolean"); + + b.Property<bool>("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("uuid"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property<bool>("HidePasswords") + .HasColumnType("boolean"); + + b.Property<bool>("ReadOnly") + .HasColumnType("boolean"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<Guid?>("GranteeId") + .HasColumnType("uuid"); + + b.Property<Guid>("GrantorId") + .HasColumnType("uuid"); + + b.Property<string>("KeyEncrypted") + .HasColumnType("text"); + + b.Property<DateTime?>("LastNotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<DateTime?>("RecoveryInitiatedDate") + .HasColumnType("timestamp without time zone"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<int>("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Event", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<Guid?>("ActingUserId") + .HasColumnType("uuid"); + + b.Property<Guid?>("CipherId") + .HasColumnType("uuid"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("uuid"); + + b.Property<DateTime>("Date") + .HasColumnType("timestamp without time zone"); + + b.Property<byte?>("DeviceType") + .HasColumnType("smallint"); + + b.Property<Guid?>("GroupId") + .HasColumnType("uuid"); + + b.Property<string>("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid?>("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property<Guid?>("PolicyId") + .HasColumnType("uuid"); + + b.Property<int>("Type") + .HasColumnType("integer"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Grant", b => + { + b.Property<string>("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<string>("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<DateTime?>("ConsumedDate") + .HasColumnType("timestamp without time zone"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<string>("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<string>("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<string>("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<bool>("AccessAll") + .HasColumnType("boolean"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property<Guid>("GroupId") + .HasColumnType("uuid"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Installation", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<string>("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property<string>("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property<string>("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte?>("Gateway") + .HasColumnType("smallint"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<short?>("MaxCollections") + .HasColumnType("smallint"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<byte>("PlanType") + .HasColumnType("smallint"); + + b.Property<string>("PrivateKey") + .HasColumnType("text"); + + b.Property<string>("PublicKey") + .HasColumnType("text"); + + b.Property<string>("ReferenceData") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<int?>("Seats") + .HasColumnType("integer"); + + b.Property<bool>("SelfHost") + .HasColumnType("boolean"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("text"); + + b.Property<bool>("Use2fa") + .HasColumnType("boolean"); + + b.Property<bool>("UseApi") + .HasColumnType("boolean"); + + b.Property<bool>("UseDirectory") + .HasColumnType("boolean"); + + b.Property<bool>("UseEvents") + .HasColumnType("boolean"); + + b.Property<bool>("UseGroups") + .HasColumnType("boolean"); + + b.Property<bool>("UsePolicies") + .HasColumnType("boolean"); + + b.Property<bool>("UseResetPassword") + .HasColumnType("boolean"); + + b.Property<bool>("UseSso") + .HasColumnType("boolean"); + + b.Property<bool>("UseTotp") + .HasColumnType("boolean"); + + b.Property<bool>("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<bool>("AccessAll") + .HasColumnType("boolean"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<string>("Permissions") + .HasColumnType("text"); + + b.Property<string>("ResetPasswordKey") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("BillingEmail") + .HasColumnType("text"); + + b.Property<string>("BusinessAddress1") + .HasColumnType("text"); + + b.Property<string>("BusinessAddress2") + .HasColumnType("text"); + + b.Property<string>("BusinessAddress3") + .HasColumnType("text"); + + b.Property<string>("BusinessCountry") + .HasColumnType("text"); + + b.Property<string>("BusinessName") + .HasColumnType("text"); + + b.Property<string>("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid>("ProviderId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Permissions") + .HasColumnType("text"); + + b.Property<Guid>("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid>("ProviderUserId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderOrganizationId"); + + b.HasIndex("ProviderUserId"); + + b.ToTable("ProviderOrganizationProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Email") + .HasColumnType("text"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<string>("Permissions") + .HasColumnType("text"); + + b.Property<Guid>("ProviderId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<int>("AccessCount") + .HasColumnType("integer"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<DateTime>("DeletionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<bool>("Disabled") + .HasColumnType("boolean"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<bool?>("HideEmail") + .HasColumnType("boolean"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<int?>("MaxAccessCount") + .HasColumnType("integer"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<string>("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.TaxRate", b => + { + b.Property<string>("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property<bool>("Active") + .HasColumnType("boolean"); + + b.Property<string>("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property<decimal>("Rate") + .HasColumnType("numeric"); + + b.Property<string>("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<decimal>("Amount") + .HasColumnType("numeric"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<byte?>("Gateway") + .HasColumnType("smallint"); + + b.Property<string>("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<byte?>("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property<bool?>("Refunded") + .HasColumnType("boolean"); + + b.Property<decimal?>("RefundedAmount") + .HasColumnType("numeric"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("AppId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Challenge") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("KeyHandle") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<string>("Version") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("AccountRevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property<bool>("EmailVerified") + .HasColumnType("boolean"); + + b.Property<string>("EquivalentDomains") + .HasColumnType("text"); + + b.Property<string>("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property<byte?>("Gateway") + .HasColumnType("smallint"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<byte>("Kdf") + .HasColumnType("smallint"); + + b.Property<int>("KdfIterations") + .HasColumnType("integer"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<string>("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<bool>("Premium") + .HasColumnType("boolean"); + + b.Property<DateTime?>("PremiumExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("PrivateKey") + .HasColumnType("text"); + + b.Property<string>("PublicKey") + .HasColumnType("text"); + + b.Property<string>("ReferenceData") + .HasColumnType("text"); + + b.Property<DateTime?>("RenewalReminderDate") + .HasColumnType("timestamp without time zone"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property<string>("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("text"); + + b.Property<string>("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", "ProviderOrganization") + .WithMany() + .HasForeignKey("ProviderOrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderUser", "ProviderUser") + .WithMany() + .HasForeignKey("ProviderUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProviderOrganization"); + + b.Navigation("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20210617163014_Postgres_Init.cs b/util/PostgresMigrations/Migrations/20210617163014_Postgres_Init.cs new file mode 100644 index 0000000000..1f0344dcb7 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20210617163014_Postgres_Init.cs @@ -0,0 +1,1008 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bit.EntityFrameworkMigrations.Migrations +{ + public partial class Postgres_Init : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False"); + + migrationBuilder.CreateTable( + name: "Event", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + Date = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + Type = table.Column<int>(type: "integer", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: true), + CipherId = table.Column<Guid>(type: "uuid", nullable: true), + CollectionId = table.Column<Guid>(type: "uuid", nullable: true), + PolicyId = table.Column<Guid>(type: "uuid", nullable: true), + GroupId = table.Column<Guid>(type: "uuid", nullable: true), + OrganizationUserId = table.Column<Guid>(type: "uuid", nullable: true), + DeviceType = table.Column<byte>(type: "smallint", nullable: true), + IpAddress = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + ActingUserId = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Event", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Grant", + columns: table => new + { + Key = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false), + Type = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + SubjectId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true), + SessionId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true), + ClientId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + ExpirationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + ConsumedDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + Data = table.Column<string>(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Grant", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "Installation", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), + Key = table.Column<string>(type: "character varying(150)", maxLength: 150, nullable: true), + Enabled = table.Column<bool>(type: "boolean", nullable: false), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Installation", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Organization", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + Identifier = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true, collation: "postgresIndetermanisticCollation"), + Name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + BusinessName = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + BusinessAddress1 = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + BusinessAddress2 = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + BusinessAddress3 = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + BusinessCountry = table.Column<string>(type: "character varying(2)", maxLength: 2, nullable: true), + BusinessTaxNumber = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: true), + BillingEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), + Plan = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + PlanType = table.Column<byte>(type: "smallint", nullable: false), + Seats = table.Column<int>(type: "integer", nullable: true), + MaxCollections = table.Column<short>(type: "smallint", nullable: true), + UsePolicies = table.Column<bool>(type: "boolean", nullable: false), + UseSso = table.Column<bool>(type: "boolean", nullable: false), + UseGroups = table.Column<bool>(type: "boolean", nullable: false), + UseDirectory = table.Column<bool>(type: "boolean", nullable: false), + UseEvents = table.Column<bool>(type: "boolean", nullable: false), + UseTotp = table.Column<bool>(type: "boolean", nullable: false), + Use2fa = table.Column<bool>(type: "boolean", nullable: false), + UseApi = table.Column<bool>(type: "boolean", nullable: false), + UseResetPassword = table.Column<bool>(type: "boolean", nullable: false), + SelfHost = table.Column<bool>(type: "boolean", nullable: false), + UsersGetPremium = table.Column<bool>(type: "boolean", nullable: false), + Storage = table.Column<long>(type: "bigint", nullable: true), + MaxStorageGb = table.Column<short>(type: "smallint", nullable: true), + Gateway = table.Column<byte>(type: "smallint", nullable: true), + GatewayCustomerId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + GatewaySubscriptionId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + ReferenceData = table.Column<string>(type: "text", nullable: true), + Enabled = table.Column<bool>(type: "boolean", nullable: false), + LicenseKey = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true), + ApiKey = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: true), + PublicKey = table.Column<string>(type: "text", nullable: true), + PrivateKey = table.Column<string>(type: "text", nullable: true), + TwoFactorProviders = table.Column<string>(type: "text", nullable: true), + ExpirationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Organization", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Provider", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + Name = table.Column<string>(type: "text", nullable: true), + BusinessName = table.Column<string>(type: "text", nullable: true), + BusinessAddress1 = table.Column<string>(type: "text", nullable: true), + BusinessAddress2 = table.Column<string>(type: "text", nullable: true), + BusinessAddress3 = table.Column<string>(type: "text", nullable: true), + BusinessCountry = table.Column<string>(type: "text", nullable: true), + BusinessTaxNumber = table.Column<string>(type: "text", nullable: true), + BillingEmail = table.Column<string>(type: "text", nullable: true), + Status = table.Column<byte>(type: "smallint", nullable: false), + Enabled = table.Column<bool>(type: "boolean", nullable: false), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Provider", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TaxRate", + columns: table => new + { + Id = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false), + Country = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + State = table.Column<string>(type: "character varying(2)", maxLength: 2, nullable: true), + PostalCode = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: true), + Rate = table.Column<decimal>(type: "numeric", nullable: false), + Active = table.Column<bool>(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TaxRate", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "User", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + Name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false, collation: "postgresIndetermanisticCollation"), + EmailVerified = table.Column<bool>(type: "boolean", nullable: false), + MasterPassword = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: true), + MasterPasswordHint = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + Culture = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: true), + SecurityStamp = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false), + TwoFactorProviders = table.Column<string>(type: "text", nullable: true), + TwoFactorRecoveryCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true), + EquivalentDomains = table.Column<string>(type: "text", nullable: true), + ExcludedGlobalEquivalentDomains = table.Column<string>(type: "text", nullable: true), + AccountRevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + Key = table.Column<string>(type: "text", nullable: true), + PublicKey = table.Column<string>(type: "text", nullable: true), + PrivateKey = table.Column<string>(type: "text", nullable: true), + Premium = table.Column<bool>(type: "boolean", nullable: false), + PremiumExpirationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + RenewalReminderDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + Storage = table.Column<long>(type: "bigint", nullable: true), + MaxStorageGb = table.Column<short>(type: "smallint", nullable: true), + Gateway = table.Column<byte>(type: "smallint", nullable: true), + GatewayCustomerId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + GatewaySubscriptionId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + ReferenceData = table.Column<string>(type: "text", nullable: true), + LicenseKey = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true), + ApiKey = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false), + Kdf = table.Column<byte>(type: "smallint", nullable: false), + KdfIterations = table.Column<int>(type: "integer", nullable: false), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_User", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Collection", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: false), + Name = table.Column<string>(type: "text", nullable: true), + ExternalId = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Collection", x => x.Id); + table.ForeignKey( + name: "FK_Collection_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Group", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: false), + Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true), + AccessAll = table.Column<bool>(type: "boolean", nullable: false), + ExternalId = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Group", x => x.Id); + table.ForeignKey( + name: "FK_Group_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Policy", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: false), + Type = table.Column<byte>(type: "smallint", nullable: false), + Data = table.Column<string>(type: "text", nullable: true), + Enabled = table.Column<bool>(type: "boolean", nullable: false), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Policy", x => x.Id); + table.ForeignKey( + name: "FK_Policy_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SsoConfig", + columns: table => new + { + Id = table.Column<long>(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Enabled = table.Column<bool>(type: "boolean", nullable: false), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: false), + Data = table.Column<string>(type: "text", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SsoConfig", x => x.Id); + table.ForeignKey( + name: "FK_SsoConfig_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProviderOrganization", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + ProviderId = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: false), + Key = table.Column<string>(type: "text", nullable: true), + Settings = table.Column<string>(type: "text", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProviderOrganization", x => x.Id); + table.ForeignKey( + name: "FK_ProviderOrganization_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProviderOrganization_Provider_ProviderId", + column: x => x.ProviderId, + principalTable: "Provider", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Cipher", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: true), + Type = table.Column<byte>(type: "smallint", nullable: false), + Data = table.Column<string>(type: "text", nullable: true), + Favorites = table.Column<string>(type: "text", nullable: true), + Folders = table.Column<string>(type: "text", nullable: true), + Attachments = table.Column<string>(type: "text", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + DeletedDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + Reprompt = table.Column<byte>(type: "smallint", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Cipher", x => x.Id); + table.ForeignKey( + name: "FK_Cipher_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Cipher_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Device", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: false), + Name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + Type = table.Column<byte>(type: "smallint", nullable: false), + Identifier = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + PushToken = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Device", x => x.Id); + table.ForeignKey( + name: "FK_Device_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EmergencyAccess", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + GrantorId = table.Column<Guid>(type: "uuid", nullable: false), + GranteeId = table.Column<Guid>(type: "uuid", nullable: true), + Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), + KeyEncrypted = table.Column<string>(type: "text", nullable: true), + Type = table.Column<byte>(type: "smallint", nullable: false), + Status = table.Column<byte>(type: "smallint", nullable: false), + WaitTimeDays = table.Column<int>(type: "integer", nullable: false), + RecoveryInitiatedDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + LastNotificationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EmergencyAccess", x => x.Id); + table.ForeignKey( + name: "FK_EmergencyAccess_User_GranteeId", + column: x => x.GranteeId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_EmergencyAccess_User_GrantorId", + column: x => x.GrantorId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Folder", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: false), + Name = table.Column<string>(type: "text", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Folder", x => x.Id); + table.ForeignKey( + name: "FK_Folder_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrganizationUser", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true), + Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true), + Key = table.Column<string>(type: "text", nullable: true), + ResetPasswordKey = table.Column<string>(type: "text", nullable: true), + Status = table.Column<byte>(type: "smallint", nullable: false), + Type = table.Column<byte>(type: "smallint", nullable: false), + AccessAll = table.Column<bool>(type: "boolean", nullable: false), + ExternalId = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + Permissions = table.Column<string>(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationUser", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationUser_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrganizationUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ProviderUser", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + ProviderId = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true), + Email = table.Column<string>(type: "text", nullable: true), + Key = table.Column<string>(type: "text", nullable: true), + Status = table.Column<byte>(type: "smallint", nullable: false), + Type = table.Column<byte>(type: "smallint", nullable: false), + Permissions = table.Column<string>(type: "text", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProviderUser", x => x.Id); + table.ForeignKey( + name: "FK_ProviderUser_Provider_ProviderId", + column: x => x.ProviderId, + principalTable: "Provider", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProviderUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Send", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: true), + Type = table.Column<byte>(type: "smallint", nullable: false), + Data = table.Column<string>(type: "text", nullable: true), + Key = table.Column<string>(type: "text", nullable: true), + Password = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: true), + MaxAccessCount = table.Column<int>(type: "integer", nullable: true), + AccessCount = table.Column<int>(type: "integer", nullable: false), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + ExpirationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), + DeletionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + Disabled = table.Column<bool>(type: "boolean", nullable: false), + HideEmail = table.Column<bool>(type: "boolean", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Send", x => x.Id); + table.ForeignKey( + name: "FK_Send_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Send_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SsoUser", + columns: table => new + { + Id = table.Column<long>(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: true), + ExternalId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true, collation: "postgresIndetermanisticCollation"), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SsoUser", x => x.Id); + table.ForeignKey( + name: "FK_SsoUser_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SsoUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Transaction", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true), + OrganizationId = table.Column<Guid>(type: "uuid", nullable: true), + Type = table.Column<byte>(type: "smallint", nullable: false), + Amount = table.Column<decimal>(type: "numeric", nullable: false), + Refunded = table.Column<bool>(type: "boolean", nullable: true), + RefundedAmount = table.Column<decimal>(type: "numeric", nullable: true), + Details = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true), + PaymentMethodType = table.Column<byte>(type: "smallint", nullable: true), + Gateway = table.Column<byte>(type: "smallint", nullable: true), + GatewayId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Transaction", x => x.Id); + table.ForeignKey( + name: "FK_Transaction_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Transaction_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "U2f", + columns: table => new + { + Id = table.Column<int>(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column<Guid>(type: "uuid", nullable: false), + KeyHandle = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true), + Challenge = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true), + AppId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true), + Version = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_U2f", x => x.Id); + table.ForeignKey( + name: "FK_U2f_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CollectionGroups", + columns: table => new + { + CollectionId = table.Column<Guid>(type: "uuid", nullable: false), + GroupId = table.Column<Guid>(type: "uuid", nullable: false), + ReadOnly = table.Column<bool>(type: "boolean", nullable: false), + HidePasswords = table.Column<bool>(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionGroups", x => new { x.CollectionId, x.GroupId }); + table.ForeignKey( + name: "FK_CollectionGroups_Collection_CollectionId", + column: x => x.CollectionId, + principalTable: "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionGroups_Group_GroupId", + column: x => x.GroupId, + principalTable: "Group", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CollectionCipher", + columns: table => new + { + CollectionId = table.Column<Guid>(type: "uuid", nullable: false), + CipherId = table.Column<Guid>(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionCipher", x => new { x.CollectionId, x.CipherId }); + table.ForeignKey( + name: "FK_CollectionCipher_Cipher_CipherId", + column: x => x.CipherId, + principalTable: "Cipher", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionCipher_Collection_CollectionId", + column: x => x.CollectionId, + principalTable: "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CollectionUsers", + columns: table => new + { + CollectionId = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationUserId = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true), + ReadOnly = table.Column<bool>(type: "boolean", nullable: false), + HidePasswords = table.Column<bool>(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionUsers", x => new { x.CollectionId, x.OrganizationUserId }); + table.ForeignKey( + name: "FK_CollectionUsers_Collection_CollectionId", + column: x => x.CollectionId, + principalTable: "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionUsers_OrganizationUser_OrganizationUserId", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionUsers_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "GroupUser", + columns: table => new + { + GroupId = table.Column<Guid>(type: "uuid", nullable: false), + OrganizationUserId = table.Column<Guid>(type: "uuid", nullable: false), + UserId = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GroupUser", x => new { x.GroupId, x.OrganizationUserId }); + table.ForeignKey( + name: "FK_GroupUser_Group_GroupId", + column: x => x.GroupId, + principalTable: "Group", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GroupUser_OrganizationUser_OrganizationUserId", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GroupUser_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ProviderOrganizationProviderUser", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + ProviderOrganizationId = table.Column<Guid>(type: "uuid", nullable: false), + ProviderUserId = table.Column<Guid>(type: "uuid", nullable: false), + Type = table.Column<byte>(type: "smallint", nullable: false), + Permissions = table.Column<string>(type: "text", nullable: true), + CreationDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), + RevisionDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProviderOrganizationProviderUser", x => x.Id); + table.ForeignKey( + name: "FK_ProviderOrganizationProviderUser_ProviderOrganization_Provi~", + column: x => x.ProviderOrganizationId, + principalTable: "ProviderOrganization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProviderOrganizationProviderUser_ProviderUser_ProviderUserId", + column: x => x.ProviderUserId, + principalTable: "ProviderUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Cipher_OrganizationId", + table: "Cipher", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Cipher_UserId", + table: "Cipher", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Collection_OrganizationId", + table: "Collection", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionCipher_CipherId", + table: "CollectionCipher", + column: "CipherId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionGroups_GroupId", + table: "CollectionGroups", + column: "GroupId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionUsers_OrganizationUserId", + table: "CollectionUsers", + column: "OrganizationUserId"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionUsers_UserId", + table: "CollectionUsers", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Device_UserId", + table: "Device", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_EmergencyAccess_GranteeId", + table: "EmergencyAccess", + column: "GranteeId"); + + migrationBuilder.CreateIndex( + name: "IX_EmergencyAccess_GrantorId", + table: "EmergencyAccess", + column: "GrantorId"); + + migrationBuilder.CreateIndex( + name: "IX_Folder_UserId", + table: "Folder", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Group_OrganizationId", + table: "Group", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_GroupUser_OrganizationUserId", + table: "GroupUser", + column: "OrganizationUserId"); + + migrationBuilder.CreateIndex( + name: "IX_GroupUser_UserId", + table: "GroupUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationUser_OrganizationId", + table: "OrganizationUser", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationUser_UserId", + table: "OrganizationUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Policy_OrganizationId", + table: "Policy", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganization_OrganizationId", + table: "ProviderOrganization", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganization_ProviderId", + table: "ProviderOrganization", + column: "ProviderId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganizationProviderUser_ProviderOrganizationId", + table: "ProviderOrganizationProviderUser", + column: "ProviderOrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderOrganizationProviderUser_ProviderUserId", + table: "ProviderOrganizationProviderUser", + column: "ProviderUserId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderUser_ProviderId", + table: "ProviderUser", + column: "ProviderId"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderUser_UserId", + table: "ProviderUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Send_OrganizationId", + table: "Send", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Send_UserId", + table: "Send", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_SsoConfig_OrganizationId", + table: "SsoConfig", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_SsoUser_OrganizationId", + table: "SsoUser", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_SsoUser_UserId", + table: "SsoUser", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Transaction_OrganizationId", + table: "Transaction", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Transaction_UserId", + table: "Transaction", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_U2f_UserId", + table: "U2f", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CollectionCipher"); + + migrationBuilder.DropTable( + name: "CollectionGroups"); + + migrationBuilder.DropTable( + name: "CollectionUsers"); + + migrationBuilder.DropTable( + name: "Device"); + + migrationBuilder.DropTable( + name: "EmergencyAccess"); + + migrationBuilder.DropTable( + name: "Event"); + + migrationBuilder.DropTable( + name: "Folder"); + + migrationBuilder.DropTable( + name: "Grant"); + + migrationBuilder.DropTable( + name: "GroupUser"); + + migrationBuilder.DropTable( + name: "Installation"); + + migrationBuilder.DropTable( + name: "Policy"); + + migrationBuilder.DropTable( + name: "ProviderOrganizationProviderUser"); + + migrationBuilder.DropTable( + name: "Send"); + + migrationBuilder.DropTable( + name: "SsoConfig"); + + migrationBuilder.DropTable( + name: "SsoUser"); + + migrationBuilder.DropTable( + name: "TaxRate"); + + migrationBuilder.DropTable( + name: "Transaction"); + + migrationBuilder.DropTable( + name: "U2f"); + + migrationBuilder.DropTable( + name: "Cipher"); + + migrationBuilder.DropTable( + name: "Collection"); + + migrationBuilder.DropTable( + name: "Group"); + + migrationBuilder.DropTable( + name: "OrganizationUser"); + + migrationBuilder.DropTable( + name: "ProviderOrganization"); + + migrationBuilder.DropTable( + name: "ProviderUser"); + + migrationBuilder.DropTable( + name: "Organization"); + + migrationBuilder.DropTable( + name: "Provider"); + + migrationBuilder.DropTable( + name: "User"); + } + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..74399182ab --- /dev/null +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -0,0 +1,1520 @@ +// <auto-generated /> +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Bit.EntityFrameworkMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("Attachments") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Favorites") + .HasColumnType("longtext"); + + b.Property<string>("Folders") + .HasColumnType("longtext"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<Guid?>("GranteeId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GrantorId") + .HasColumnType("char(36)"); + + b.Property<string>("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property<DateTime?>("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Event", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ActingUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CipherId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("Date") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("GroupId") + .HasColumnType("char(36)"); + + b.Property<string>("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("PolicyId") + .HasColumnType("char(36)"); + + b.Property<int>("Type") + .HasColumnType("int"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Grant", b => + { + b.Property<string>("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<string>("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Installation", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<string>("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property<string>("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<short?>("MaxCollections") + .HasColumnType("smallint"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<int?>("Seats") + .HasColumnType("int"); + + b.Property<bool>("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<bool>("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<string>("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("BillingEmail") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property<string>("BusinessCountry") + .HasColumnType("longtext"); + + b.Property<string>("BusinessName") + .HasColumnType("longtext"); + + b.Property<string>("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<Guid>("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderOrganizationId"); + + b.HasIndex("ProviderUserId"); + + b.ToTable("ProviderOrganizationProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasColumnType("longtext"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<int>("AccessCount") + .HasColumnType("int"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime>("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool?>("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<int?>("MaxAccessCount") + .HasColumnType("int"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.Property<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.TaxRate", b => + { + b.Property<string>("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property<bool>("Active") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<decimal>("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property<string>("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<decimal>("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property<bool?>("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property<decimal?>("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property<string>("AppId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Challenge") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("KeyHandle") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<Guid>("UserId") + .HasColumnType("char(36)"); + + b.Property<string>("Version") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property<string>("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property<string>("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("KdfIterations") + .HasColumnType("int"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<bool>("Premium") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime?>("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<string>("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganizationProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", "ProviderOrganization") + .WithMany() + .HasForeignKey("ProviderOrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.ProviderUser", "ProviderUser") + .WithMany() + .HasForeignKey("ProviderUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProviderOrganization"); + + b.Navigation("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/PostgresMigrations.csproj b/util/PostgresMigrations/PostgresMigrations.csproj new file mode 100644 index 0000000000..d07adb1dc6 --- /dev/null +++ b/util/PostgresMigrations/PostgresMigrations.csproj @@ -0,0 +1,19 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Core\Core.csproj" /> + <ProjectReference Include="..\..\src\Api\Api.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + </ItemGroup> + +</Project> diff --git a/util/PostgresMigrations/Scripts/Init.psql b/util/PostgresMigrations/Scripts/Init.psql new file mode 100644 index 0000000000..4ad6012448 --- /dev/null +++ b/util/PostgresMigrations/Scripts/Init.psql @@ -0,0 +1,494 @@ +CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( + "MigrationId" character varying(150) NOT NULL, + "ProductVersion" character varying(32) NOT NULL, + CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") +); + +START TRANSACTION; + +CREATE COLLATION "postgresIndetermanisticCollation" (LC_COLLATE = 'en-u-ks-primary', + LC_CTYPE = 'en-u-ks-primary', + PROVIDER = icu, + DETERMINISTIC = False +); + +CREATE TABLE "Event" ( + "Id" uuid NOT NULL, + "Date" timestamp without time zone NOT NULL, + "Type" integer NOT NULL, + "UserId" uuid NULL, + "OrganizationId" uuid NULL, + "CipherId" uuid NULL, + "CollectionId" uuid NULL, + "PolicyId" uuid NULL, + "GroupId" uuid NULL, + "OrganizationUserId" uuid NULL, + "DeviceType" smallint NULL, + "IpAddress" character varying(50) NULL, + "ActingUserId" uuid NULL, + CONSTRAINT "PK_Event" PRIMARY KEY ("Id") +); + +CREATE TABLE "Grant" ( + "Key" character varying(200) NOT NULL, + "Type" character varying(50) NULL, + "SubjectId" character varying(200) NULL, + "SessionId" character varying(100) NULL, + "ClientId" character varying(200) NULL, + "Description" character varying(200) NULL, + "CreationDate" timestamp without time zone NOT NULL, + "ExpirationDate" timestamp without time zone NULL, + "ConsumedDate" timestamp without time zone NULL, + "Data" text NULL, + CONSTRAINT "PK_Grant" PRIMARY KEY ("Key") +); + +CREATE TABLE "Installation" ( + "Id" uuid NOT NULL, + "Email" character varying(256) NULL, + "Key" character varying(150) NULL, + "Enabled" boolean NOT NULL, + "CreationDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Installation" PRIMARY KEY ("Id") +); + +CREATE TABLE "Organization" ( + "Id" uuid NOT NULL, + "Identifier" character varying(50) COLLATE "postgresIndetermanisticCollation" NULL, + "Name" character varying(50) NULL, + "BusinessName" character varying(50) NULL, + "BusinessAddress1" character varying(50) NULL, + "BusinessAddress2" character varying(50) NULL, + "BusinessAddress3" character varying(50) NULL, + "BusinessCountry" character varying(2) NULL, + "BusinessTaxNumber" character varying(30) NULL, + "BillingEmail" character varying(256) NULL, + "Plan" character varying(50) NULL, + "PlanType" smallint NOT NULL, + "Seats" integer NULL, + "MaxCollections" smallint NULL, + "UsePolicies" boolean NOT NULL, + "UseSso" boolean NOT NULL, + "UseGroups" boolean NOT NULL, + "UseDirectory" boolean NOT NULL, + "UseEvents" boolean NOT NULL, + "UseTotp" boolean NOT NULL, + "Use2fa" boolean NOT NULL, + "UseApi" boolean NOT NULL, + "UseResetPassword" boolean NOT NULL, + "SelfHost" boolean NOT NULL, + "UsersGetPremium" boolean NOT NULL, + "Storage" bigint NULL, + "MaxStorageGb" smallint NULL, + "Gateway" smallint NULL, + "GatewayCustomerId" character varying(50) NULL, + "GatewaySubscriptionId" character varying(50) NULL, + "ReferenceData" text NULL, + "Enabled" boolean NOT NULL, + "LicenseKey" character varying(100) NULL, + "ApiKey" character varying(30) NULL, + "PublicKey" text NULL, + "PrivateKey" text NULL, + "TwoFactorProviders" text NULL, + "ExpirationDate" timestamp without time zone NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Organization" PRIMARY KEY ("Id") +); + +CREATE TABLE "Provider" ( + "Id" uuid NOT NULL, + "Name" text NULL, + "BusinessName" text NULL, + "BusinessAddress1" text NULL, + "BusinessAddress2" text NULL, + "BusinessAddress3" text NULL, + "BusinessCountry" text NULL, + "BusinessTaxNumber" text NULL, + "BillingEmail" text NULL, + "Status" smallint NOT NULL, + "Enabled" boolean NOT NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Provider" PRIMARY KEY ("Id") +); + +CREATE TABLE "TaxRate" ( + "Id" character varying(40) NOT NULL, + "Country" character varying(50) NULL, + "State" character varying(2) NULL, + "PostalCode" character varying(10) NULL, + "Rate" numeric NOT NULL, + "Active" boolean NOT NULL, + CONSTRAINT "PK_TaxRate" PRIMARY KEY ("Id") +); + +CREATE TABLE "User" ( + "Id" uuid NOT NULL, + "Name" character varying(50) NULL, + "Email" character varying(256) COLLATE "postgresIndetermanisticCollation" NOT NULL, + "EmailVerified" boolean NOT NULL, + "MasterPassword" character varying(300) NULL, + "MasterPasswordHint" character varying(50) NULL, + "Culture" character varying(10) NULL, + "SecurityStamp" character varying(50) NOT NULL, + "TwoFactorProviders" text NULL, + "TwoFactorRecoveryCode" character varying(32) NULL, + "EquivalentDomains" text NULL, + "ExcludedGlobalEquivalentDomains" text NULL, + "AccountRevisionDate" timestamp without time zone NOT NULL, + "Key" text NULL, + "PublicKey" text NULL, + "PrivateKey" text NULL, + "Premium" boolean NOT NULL, + "PremiumExpirationDate" timestamp without time zone NULL, + "RenewalReminderDate" timestamp without time zone NULL, + "Storage" bigint NULL, + "MaxStorageGb" smallint NULL, + "Gateway" smallint NULL, + "GatewayCustomerId" character varying(50) NULL, + "GatewaySubscriptionId" character varying(50) NULL, + "ReferenceData" text NULL, + "LicenseKey" character varying(100) NULL, + "ApiKey" character varying(30) NOT NULL, + "Kdf" smallint NOT NULL, + "KdfIterations" integer NOT NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_User" PRIMARY KEY ("Id") +); + +CREATE TABLE "Collection" ( + "Id" uuid NOT NULL, + "OrganizationId" uuid NOT NULL, + "Name" text NULL, + "ExternalId" character varying(300) NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Collection" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Collection_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "Group" ( + "Id" uuid NOT NULL, + "OrganizationId" uuid NOT NULL, + "Name" character varying(100) NULL, + "AccessAll" boolean NOT NULL, + "ExternalId" character varying(300) NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Group" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Group_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "Policy" ( + "Id" uuid NOT NULL, + "OrganizationId" uuid NOT NULL, + "Type" smallint NOT NULL, + "Data" text NULL, + "Enabled" boolean NOT NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Policy" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Policy_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "SsoConfig" ( + "Id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "Enabled" boolean NOT NULL, + "OrganizationId" uuid NOT NULL, + "Data" text NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_SsoConfig" PRIMARY KEY ("Id"), + CONSTRAINT "FK_SsoConfig_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "ProviderOrganization" ( + "Id" uuid NOT NULL, + "ProviderId" uuid NOT NULL, + "OrganizationId" uuid NOT NULL, + "Key" text NULL, + "Settings" text NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_ProviderOrganization" PRIMARY KEY ("Id"), + CONSTRAINT "FK_ProviderOrganization_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_ProviderOrganization_Provider_ProviderId" FOREIGN KEY ("ProviderId") REFERENCES "Provider" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "Cipher" ( + "Id" uuid NOT NULL, + "UserId" uuid NULL, + "OrganizationId" uuid NULL, + "Type" smallint NOT NULL, + "Data" text NULL, + "Favorites" text NULL, + "Folders" text NULL, + "Attachments" text NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + "DeletedDate" timestamp without time zone NULL, + "Reprompt" smallint NULL, + CONSTRAINT "PK_Cipher" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Cipher_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_Cipher_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "Device" ( + "Id" uuid NOT NULL, + "UserId" uuid NOT NULL, + "Name" character varying(50) NULL, + "Type" smallint NOT NULL, + "Identifier" character varying(50) NULL, + "PushToken" character varying(255) NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Device" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Device_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "EmergencyAccess" ( + "Id" uuid NOT NULL, + "GrantorId" uuid NOT NULL, + "GranteeId" uuid NULL, + "Email" character varying(256) NULL, + "KeyEncrypted" text NULL, + "Type" smallint NOT NULL, + "Status" smallint NOT NULL, + "WaitTimeDays" integer NOT NULL, + "RecoveryInitiatedDate" timestamp without time zone NULL, + "LastNotificationDate" timestamp without time zone NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_EmergencyAccess" PRIMARY KEY ("Id"), + CONSTRAINT "FK_EmergencyAccess_User_GranteeId" FOREIGN KEY ("GranteeId") REFERENCES "User" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_EmergencyAccess_User_GrantorId" FOREIGN KEY ("GrantorId") REFERENCES "User" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "Folder" ( + "Id" uuid NOT NULL, + "UserId" uuid NOT NULL, + "Name" text NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Folder" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Folder_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "OrganizationUser" ( + "Id" uuid NOT NULL, + "OrganizationId" uuid NOT NULL, + "UserId" uuid NULL, + "Email" character varying(256) NULL, + "Key" text NULL, + "ResetPasswordKey" text NULL, + "Status" smallint NOT NULL, + "Type" smallint NOT NULL, + "AccessAll" boolean NOT NULL, + "ExternalId" character varying(300) NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + "Permissions" text NULL, + CONSTRAINT "PK_OrganizationUser" PRIMARY KEY ("Id"), + CONSTRAINT "FK_OrganizationUser_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_OrganizationUser_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "ProviderUser" ( + "Id" uuid NOT NULL, + "ProviderId" uuid NOT NULL, + "UserId" uuid NULL, + "Email" text NULL, + "Key" text NULL, + "Status" smallint NOT NULL, + "Type" smallint NOT NULL, + "Permissions" text NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_ProviderUser" PRIMARY KEY ("Id"), + CONSTRAINT "FK_ProviderUser_Provider_ProviderId" FOREIGN KEY ("ProviderId") REFERENCES "Provider" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_ProviderUser_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "Send" ( + "Id" uuid NOT NULL, + "UserId" uuid NULL, + "OrganizationId" uuid NULL, + "Type" smallint NOT NULL, + "Data" text NULL, + "Key" text NULL, + "Password" character varying(300) NULL, + "MaxAccessCount" integer NULL, + "AccessCount" integer NOT NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + "ExpirationDate" timestamp without time zone NULL, + "DeletionDate" timestamp without time zone NOT NULL, + "Disabled" boolean NOT NULL, + "HideEmail" boolean NULL, + CONSTRAINT "PK_Send" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Send_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_Send_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "SsoUser" ( + "Id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "UserId" uuid NOT NULL, + "OrganizationId" uuid NULL, + "ExternalId" character varying(50) COLLATE "postgresIndetermanisticCollation" NULL, + "CreationDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_SsoUser" PRIMARY KEY ("Id"), + CONSTRAINT "FK_SsoUser_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_SsoUser_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "Transaction" ( + "Id" uuid NOT NULL, + "UserId" uuid NULL, + "OrganizationId" uuid NULL, + "Type" smallint NOT NULL, + "Amount" numeric NOT NULL, + "Refunded" boolean NULL, + "RefundedAmount" numeric NULL, + "Details" character varying(100) NULL, + "PaymentMethodType" smallint NULL, + "Gateway" smallint NULL, + "GatewayId" character varying(50) NULL, + "CreationDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_Transaction" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Transaction_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_Transaction_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "U2f" ( + "Id" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "UserId" uuid NOT NULL, + "KeyHandle" character varying(200) NULL, + "Challenge" character varying(200) NULL, + "AppId" character varying(50) NULL, + "Version" character varying(20) NULL, + "CreationDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_U2f" PRIMARY KEY ("Id"), + CONSTRAINT "FK_U2f_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "CollectionGroups" ( + "CollectionId" uuid NOT NULL, + "GroupId" uuid NOT NULL, + "ReadOnly" boolean NOT NULL, + "HidePasswords" boolean NOT NULL, + CONSTRAINT "PK_CollectionGroups" PRIMARY KEY ("CollectionId", "GroupId"), + CONSTRAINT "FK_CollectionGroups_Collection_CollectionId" FOREIGN KEY ("CollectionId") REFERENCES "Collection" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_CollectionGroups_Group_GroupId" FOREIGN KEY ("GroupId") REFERENCES "Group" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "CollectionCipher" ( + "CollectionId" uuid NOT NULL, + "CipherId" uuid NOT NULL, + CONSTRAINT "PK_CollectionCipher" PRIMARY KEY ("CollectionId", "CipherId"), + CONSTRAINT "FK_CollectionCipher_Cipher_CipherId" FOREIGN KEY ("CipherId") REFERENCES "Cipher" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_CollectionCipher_Collection_CollectionId" FOREIGN KEY ("CollectionId") REFERENCES "Collection" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "CollectionUsers" ( + "CollectionId" uuid NOT NULL, + "OrganizationUserId" uuid NOT NULL, + "UserId" uuid NULL, + "ReadOnly" boolean NOT NULL, + "HidePasswords" boolean NOT NULL, + CONSTRAINT "PK_CollectionUsers" PRIMARY KEY ("CollectionId", "OrganizationUserId"), + CONSTRAINT "FK_CollectionUsers_Collection_CollectionId" FOREIGN KEY ("CollectionId") REFERENCES "Collection" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_CollectionUsers_OrganizationUser_OrganizationUserId" FOREIGN KEY ("OrganizationUserId") REFERENCES "OrganizationUser" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_CollectionUsers_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "GroupUser" ( + "GroupId" uuid NOT NULL, + "OrganizationUserId" uuid NOT NULL, + "UserId" uuid NULL, + CONSTRAINT "PK_GroupUser" PRIMARY KEY ("GroupId", "OrganizationUserId"), + CONSTRAINT "FK_GroupUser_Group_GroupId" FOREIGN KEY ("GroupId") REFERENCES "Group" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_GroupUser_OrganizationUser_OrganizationUserId" FOREIGN KEY ("OrganizationUserId") REFERENCES "OrganizationUser" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_GroupUser_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "ProviderOrganizationProviderUser" ( + "Id" uuid NOT NULL, + "ProviderOrganizationId" uuid NOT NULL, + "ProviderUserId" uuid NOT NULL, + "Type" smallint NOT NULL, + "Permissions" text NULL, + "CreationDate" timestamp without time zone NOT NULL, + "RevisionDate" timestamp without time zone NOT NULL, + CONSTRAINT "PK_ProviderOrganizationProviderUser" PRIMARY KEY ("Id"), + CONSTRAINT "FK_ProviderOrganizationProviderUser_ProviderOrganization_Provi~" FOREIGN KEY ("ProviderOrganizationId") REFERENCES "ProviderOrganization" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_ProviderOrganizationProviderUser_ProviderUser_ProviderUserId" FOREIGN KEY ("ProviderUserId") REFERENCES "ProviderUser" ("Id") ON DELETE CASCADE +); + +CREATE INDEX "IX_Cipher_OrganizationId" ON "Cipher" ("OrganizationId"); + +CREATE INDEX "IX_Cipher_UserId" ON "Cipher" ("UserId"); + +CREATE INDEX "IX_Collection_OrganizationId" ON "Collection" ("OrganizationId"); + +CREATE INDEX "IX_CollectionCipher_CipherId" ON "CollectionCipher" ("CipherId"); + +CREATE INDEX "IX_CollectionGroups_GroupId" ON "CollectionGroups" ("GroupId"); + +CREATE INDEX "IX_CollectionUsers_OrganizationUserId" ON "CollectionUsers" ("OrganizationUserId"); + +CREATE INDEX "IX_CollectionUsers_UserId" ON "CollectionUsers" ("UserId"); + +CREATE INDEX "IX_Device_UserId" ON "Device" ("UserId"); + +CREATE INDEX "IX_EmergencyAccess_GranteeId" ON "EmergencyAccess" ("GranteeId"); + +CREATE INDEX "IX_EmergencyAccess_GrantorId" ON "EmergencyAccess" ("GrantorId"); + +CREATE INDEX "IX_Folder_UserId" ON "Folder" ("UserId"); + +CREATE INDEX "IX_Group_OrganizationId" ON "Group" ("OrganizationId"); + +CREATE INDEX "IX_GroupUser_OrganizationUserId" ON "GroupUser" ("OrganizationUserId"); + +CREATE INDEX "IX_GroupUser_UserId" ON "GroupUser" ("UserId"); + +CREATE INDEX "IX_OrganizationUser_OrganizationId" ON "OrganizationUser" ("OrganizationId"); + +CREATE INDEX "IX_OrganizationUser_UserId" ON "OrganizationUser" ("UserId"); + +CREATE INDEX "IX_Policy_OrganizationId" ON "Policy" ("OrganizationId"); + +CREATE INDEX "IX_ProviderOrganization_OrganizationId" ON "ProviderOrganization" ("OrganizationId"); + +CREATE INDEX "IX_ProviderOrganization_ProviderId" ON "ProviderOrganization" ("ProviderId"); + +CREATE INDEX "IX_ProviderOrganizationProviderUser_ProviderOrganizationId" ON "ProviderOrganizationProviderUser" ("ProviderOrganizationId"); + +CREATE INDEX "IX_ProviderOrganizationProviderUser_ProviderUserId" ON "ProviderOrganizationProviderUser" ("ProviderUserId"); + +CREATE INDEX "IX_ProviderUser_ProviderId" ON "ProviderUser" ("ProviderId"); + +CREATE INDEX "IX_ProviderUser_UserId" ON "ProviderUser" ("UserId"); + +CREATE INDEX "IX_Send_OrganizationId" ON "Send" ("OrganizationId"); + +CREATE INDEX "IX_Send_UserId" ON "Send" ("UserId"); + +CREATE INDEX "IX_SsoConfig_OrganizationId" ON "SsoConfig" ("OrganizationId"); + +CREATE INDEX "IX_SsoUser_OrganizationId" ON "SsoUser" ("OrganizationId"); + +CREATE INDEX "IX_SsoUser_UserId" ON "SsoUser" ("UserId"); + +CREATE INDEX "IX_Transaction_OrganizationId" ON "Transaction" ("OrganizationId"); + +CREATE INDEX "IX_Transaction_UserId" ON "Transaction" ("UserId"); + +CREATE INDEX "IX_U2f_UserId" ON "U2f" ("UserId"); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20210617163014_Postgres_Init', '5.0.5'); + +COMMIT; \ No newline at end of file diff --git a/util/Server/Dockerfile b/util/Server/Dockerfile index 69de097f43..114deb18c7 100644 --- a/util/Server/Dockerfile +++ b/util/Server/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" diff --git a/util/Setup/Dockerfile b/util/Setup/Dockerfile index f63e4ed8cc..cbf000747a 100644 --- a/util/Setup/Dockerfile +++ b/util/Setup/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +FROM mcr.microsoft.com/dotnet/aspnet:5.0 LABEL com.bitwarden.product="bitwarden" com.bitwarden.project="setup" diff --git a/util/Setup/Setup.csproj b/util/Setup/Setup.csproj index daa6754460..744242e60e 100644 --- a/util/Setup/Setup.csproj +++ b/util/Setup/Setup.csproj @@ -11,7 +11,7 @@ <ItemGroup> <PackageReference Include="Handlebars.Net" Version="1.10.1" /> - <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" /> + <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="YamlDotNet" Version="8.1.2" /> </ItemGroup>